基于Linux-2.6.32.2在mini2440驱动分析一:串口驱动

 

串口驱动文件位于: linux-2.6.32.2/drivers/serial/s3c2440.c,省去非重点部分分析。

 

#include <linux/module.h>
#include <linux/ioport.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/init.h>
#include <linux/serial_core.h>
#include <linux/serial.h>

#include <asm/irq.h>
#include <mach/hardware.h>

#include <plat/regs-serial.h>
#include <mach/regs-gpio.h>

#include "samsung.h"


static int s3c2440_serial_setsource(struct uart_port *port,
         struct s3c24xx_uart_clksrc *clk)
{
 unsigned long ucon = rd_regl(port, S3C2410_UCON);

 /* todo - proper fclk<>nonfclk switch. */

 ucon &= ~S3C2440_UCON_CLKMASK;

 if (strcmp(clk->name, "uclk") == 0)
  ucon |= S3C2440_UCON_UCLK;
 else if (strcmp(clk->name, "pclk") == 0)
  ucon |= S3C2440_UCON_PCLK;
 else if (strcmp(clk->name, "fclk") == 0)
  ucon |= S3C2440_UCON_FCLK;
 else {
  printk(KERN_ERR "unknown clock source %s\n", clk->name);
  return -EINVAL;
 }

 wr_regl(port, S3C2410_UCON, ucon);
 return 0;
}


static int s3c2440_serial_getsource(struct uart_port *port,
        struct s3c24xx_uart_clksrc *clk)
{
 unsigned long ucon = rd_regl(port, S3C2410_UCON);
 unsigned long ucon0, ucon1, ucon2;

 switch (ucon & S3C2440_UCON_CLKMASK) {
 case S3C2440_UCON_UCLK:
  clk->divisor = 1;
  clk->name = "uclk";
  break;

 case S3C2440_UCON_PCLK:
 case S3C2440_UCON_PCLK2:
  clk->divisor = 1;
  clk->name = "pclk";
  break;

 case S3C2440_UCON_FCLK:
  /* the fun of calculating the uart divisors on
   * the s3c2440 */

  ucon0 = __raw_readl(S3C24XX_VA_UART0 + S3C2410_UCON);
  ucon1 = __raw_readl(S3C24XX_VA_UART1 + S3C2410_UCON);
  ucon2 = __raw_readl(S3C24XX_VA_UART2 + S3C2410_UCON);

  printk("ucons: %08lx, %08lx, %08lx\n", ucon0, ucon1, ucon2);

  ucon0 &= S3C2440_UCON0_DIVMASK;
  ucon1 &= S3C2440_UCON1_DIVMASK;
  ucon2 &= S3C2440_UCON2_DIVMASK;

  if (ucon0 != 0) {
   clk->divisor = ucon0 >> S3C2440_UCON_DIVSHIFT;
   clk->divisor += 6;
  } else if (ucon1 != 0) {
   clk->divisor = ucon1 >> S3C2440_UCON_DIVSHIFT;
   clk->divisor += 21;
  } else if (ucon2 != 0) {
   clk->divisor = ucon2 >> S3C2440_UCON_DIVSHIFT;
   clk->divisor += 36;
  } else {
   /* manual calims 44, seems to be 9 */
   clk->divisor = 9;
  }

  clk->name = "fclk";
  break;
 }

 return 0;
}

static int s3c2440_serial_resetport(struct uart_port *port,
        struct s3c2410_uartcfg *cfg)
{
 unsigned long ucon = rd_regl(port, S3C2410_UCON);

 dbg("s3c2440_serial_resetport: port=%p (%08lx), cfg=%p\n",
     port, port->mapbase, cfg);

 /* ensure we don't change the clock settings... */

 ucon &= (S3C2440_UCON0_DIVMASK | (3<<10));

 wr_regl(port, S3C2410_UCON,  ucon | cfg->ucon);
 wr_regl(port, S3C2410_ULCON, cfg->ulcon);

 /* reset both fifos */

 wr_regl(port, S3C2410_UFCON, cfg->ufcon | S3C2410_UFCON_RESETBOTH);
 wr_regl(port, S3C2410_UFCON, cfg->ufcon);

 return 0;
}

static struct s3c24xx_uart_info s3c2440_uart_inf = {
 .name  = "Samsung S3C2440 UART",
 .type  = PORT_S3C2440,
 .fifosize = 64,
 .rx_fifomask = S3C2440_UFSTAT_RXMASK,
 .rx_fifoshift = S3C2440_UFSTAT_RXSHIFT,
 .rx_fifofull = S3C2440_UFSTAT_RXFULL,
 .tx_fifofull = S3C2440_UFSTAT_TXFULL,
 .tx_fifomask = S3C2440_UFSTAT_TXMASK,
 .tx_fifoshift = S3C2440_UFSTAT_TXSHIFT,
 .get_clksrc = s3c2440_serial_getsource,
 .set_clksrc = s3c2440_serial_setsource,
 .reset_port = s3c2440_serial_resetport,
};

/* device management */

static int s3c2440_serial_probe(struct platform_device *dev)
{
 dbg("s3c2440_serial_probe: dev=%p\n", dev);
 return s3c24xx_serial_probe(dev, &s3c2440_uart_inf);
}

static struct platform_driver s3c2440_serial_driver = {   //这个应该就是类似于file_operation结构体
 .probe  = s3c2440_serial_probe,
 .remove  = __devexit_p(s3c24xx_serial_remove),
 .driver  = {
  .name = "s3c2440-uart",
  .owner = THIS_MODULE,
 },
};

s3c24xx_console_init(&s3c2440_serial_driver, &s3c2440_uart_inf);

static int __init s3c2440_serial_init(void)
{
 return s3c24xx_serial_init(&s3c2440_serial_driver, &s3c2440_uart_inf);
}

static void __exit s3c2440_serial_exit(void)
{
 platform_driver_unregister(&s3c2440_serial_driver);
}

module_init(s3c2440_serial_init);
module_exit(s3c2440_serial_exit);

MODULE_DESCRIPTION("Samsung S3C2440,S3C2442 SoC Serial port driver");
MODULE_AUTHOR("Ben Dooks <
ben@simtec.co.uk>");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:s3c2440-uart");

 

 

 

 

 

辅助理解:

1、s3c24xx_serial_init(struct platform_driver *drv, struct s3c24xx_uart_info *info) 函数

int s3c24xx_serial_init(struct platform_driver *drv,
   struct s3c24xx_uart_info *info)
{
 dbg("s3c24xx_serial_init(%p,%p)\n", drv, info);

#ifdef CONFIG_PM
 drv->suspend = s3c24xx_serial_suspend;
 drv->resume = s3c24xx_serial_resume;
#endif

 return platform_driver_register(drv);
}

2、struct platform_driver 结构体

struct platform_driver {
 int (*probe)(struct platform_device *);
 int (*remove)(struct platform_device *);
 void (*shutdown)(struct platform_device *);
 int (*suspend)(struct platform_device *, pm_message_t state);
 int (*resume)(struct platform_device *);
 struct device_driver driver;
 struct platform_device_id *id_table;
};

 

以下部分涉及S3C2440芯片手册部分内容,附上宏的头文件

/* arch/arm/mach-s3c2410/include/mach/regs-serial.h
 *
 *  From linux/include/asm-arm/hardware/serial_s3c2410.h
 *
 *  Internal header file for Samsung S3C2410 serial ports (UART0-2)*/

#ifndef __ASM_ARM_REGS_SERIAL_H
#define __ASM_ARM_REGS_SERIAL_H

#define S3C24XX_VA_UART0      (S3C_VA_UART)
#define S3C24XX_VA_UART1      (S3C_VA_UART + 0x4000 )
#define S3C24XX_VA_UART2      (S3C_VA_UART + 0x8000 )
#define S3C24XX_VA_UART3      (S3C_VA_UART + 0xC000 )

#define S3C2410_PA_UART0      (S3C24XX_PA_UART)
#define S3C2410_PA_UART1      (S3C24XX_PA_UART + 0x4000 )
#define S3C2410_PA_UART2      (S3C24XX_PA_UART + 0x8000 )
#define S3C2443_PA_UART3      (S3C24XX_PA_UART + 0xC000 )

#define S3C2410_URXH   (0x24)
#define S3C2410_UTXH   (0x20)
#define S3C2410_ULCON   (0x00)
#define S3C2410_UCON   (0x04)
#define S3C2410_UFCON   (0x08)
#define S3C2410_UMCON   (0x0C)
#define S3C2410_UBRDIV   (0x28)
#define S3C2410_UTRSTAT   (0x10)
#define S3C2410_UERSTAT   (0x14)
#define S3C2410_UFSTAT   (0x18)
#define S3C2410_UMSTAT   (0x1C)

#define S3C2410_LCON_CFGMASK   ((0xF<<3)|(0x3))

#define S3C2410_LCON_CS5   (0x0)
#define S3C2410_LCON_CS6   (0x1)
#define S3C2410_LCON_CS7   (0x2)
#define S3C2410_LCON_CS8   (0x3)
#define S3C2410_LCON_CSMASK   (0x3)

#define S3C2410_LCON_PNONE   (0x0)
#define S3C2410_LCON_PEVEN   (0x5 << 3)
#define S3C2410_LCON_PODD   (0x4 << 3)
#define S3C2410_LCON_PMASK   (0x7 << 3)

#define S3C2410_LCON_STOPB   (1<<2)
#define S3C2410_LCON_IRM          (1<<6)

#define S3C2440_UCON_CLKMASK   (3<<10)
#define S3C2440_UCON_PCLK   (0<<10)
#define S3C2440_UCON_UCLK   (1<<10)
#define S3C2440_UCON_PCLK2   (2<<10)
#define S3C2440_UCON_FCLK   (3<<10)

#define S3C2440_UCON2_FCLK_EN   (1<<15)
#define S3C2440_UCON0_DIVMASK   (15 << 12)
#define S3C2440_UCON1_DIVMASK   (15 << 12)
#define S3C2440_UCON2_DIVMASK   (7 << 12)
#define S3C2440_UCON_DIVSHIFT   (12)

#define S3C2410_UCON_UCLK   (1<<10)
#define S3C2410_UCON_SBREAK   (1<<4)

#define S3C2410_UCON_TXILEVEL   (1<<9)
#define S3C2410_UCON_RXILEVEL   (1<<8)
#define S3C2410_UCON_TXIRQMODE   (1<<2)
#define S3C2410_UCON_RXIRQMODE   (1<<0)
#define S3C2410_UCON_RXFIFO_TOI   (1<<7)
#define S3C2443_UCON_RXERR_IRQEN  (1<<6)
#define S3C2443_UCON_LOOPBACK   (1<<5)

#define S3C2410_UCON_DEFAULT   (S3C2410_UCON_TXILEVEL  | \
       S3C2410_UCON_RXILEVEL  | \
       S3C2410_UCON_TXIRQMODE | \
       S3C2410_UCON_RXIRQMODE | \
       S3C2410_UCON_RXFIFO_TOI)

#define S3C2410_UFCON_FIFOMODE   (1<<0)
#define S3C2410_UFCON_TXTRIG0   (0<<6)
#define S3C2410_UFCON_RXTRIG8   (1<<4)
#define S3C2410_UFCON_RXTRIG12   (2<<4)

/* S3C2440 FIFO trigger levels */
#define S3C2440_UFCON_RXTRIG1   (0<<4)
#define S3C2440_UFCON_RXTRIG8   (1<<4)
#define S3C2440_UFCON_RXTRIG16   (2<<4)
#define S3C2440_UFCON_RXTRIG32   (3<<4)

#define S3C2440_UFCON_TXTRIG0   (0<<6)
#define S3C2440_UFCON_TXTRIG16   (1<<6)
#define S3C2440_UFCON_TXTRIG32   (2<<6)
#define S3C2440_UFCON_TXTRIG48   (3<<6)

#define S3C2410_UFCON_RESETBOTH   (3<<1)
#define S3C2410_UFCON_RESETTX   (1<<2)
#define S3C2410_UFCON_RESETRX   (1<<1)

#define S3C2410_UFCON_DEFAULT   (S3C2410_UFCON_FIFOMODE | \
       S3C2410_UFCON_TXTRIG0  | \
       S3C2410_UFCON_RXTRIG8 )

#define S3C2410_UMCOM_AFC   (1<<4)
#define S3C2410_UMCOM_RTS_LOW   (1<<0)

#define S3C2410_UFSTAT_TXFULL   (1<<9)
#define S3C2410_UFSTAT_RXFULL   (1<<8)
#define S3C2410_UFSTAT_TXMASK   (15<<4)
#define S3C2410_UFSTAT_TXSHIFT   (4)
#define S3C2410_UFSTAT_RXMASK   (15<<0)
#define S3C2410_UFSTAT_RXSHIFT   (0)

/* UFSTAT S3C2443 same as S3C2440 */
#define S3C2440_UFSTAT_TXFULL   (1<<14)
#define S3C2440_UFSTAT_RXFULL   (1<<6)
#define S3C2440_UFSTAT_TXSHIFT   (8)
#define S3C2440_UFSTAT_RXSHIFT   (0)
#define S3C2440_UFSTAT_TXMASK   (63<<8)
#define S3C2440_UFSTAT_RXMASK   (63)

#define S3C2410_UTRSTAT_TXE   (1<<2)
#define S3C2410_UTRSTAT_TXFE   (1<<1)
#define S3C2410_UTRSTAT_RXDR   (1<<0)

#define S3C2410_UERSTAT_OVERRUN   (1<<0)
#define S3C2410_UERSTAT_FRAME   (1<<2)
#define S3C2410_UERSTAT_BREAK   (1<<3)
#define S3C2443_UERSTAT_PARITY   (1<<1)

#define S3C2410_UERSTAT_ANY   (S3C2410_UERSTAT_OVERRUN | \
       S3C2410_UERSTAT_FRAME | \
       S3C2410_UERSTAT_BREAK)

#define S3C2410_UMSTAT_CTS   (1<<0)
#define S3C2410_UMSTAT_DeltaCTS   (1<<2)

#ifndef __ASSEMBLY__

/* struct s3c24xx_uart_clksrc
 *
 * this structure defines a named clock source that can be used for the
 * uart, so that the best clock can be selected for the requested baud
 * rate.
 *
 * min_baud and max_baud define the range of baud-rates this clock is
 * acceptable for, if they are both zero, it is assumed any baud rate that
 * can be generated from this clock will be used.
 *
 * divisor gives the divisor from the clock to the one seen by the uart
*/

struct s3c24xx_uart_clksrc {
 const char *name;
 unsigned int  divisor;
 unsigned int  min_baud;
 unsigned int  max_baud;
};

/* configuration structure for per-machine configurations for the
 * serial port
 *
 * the pointer is setup by the machine specific initialisation from the
 * arch/arm/mach-s3c2410/ directory.
*/

struct s3c2410_uartcfg {
 unsigned char    hwport;  /* hardware port number */
 unsigned char    unused;
 unsigned short    flags;
 upf_t     uart_flags;  /* default uart flags */

 unsigned long    ucon;  /* value of ucon for port */
 unsigned long    ulcon;  /* value of ulcon for port */
 unsigned long    ufcon;  /* value of ufcon for port */

 struct s3c24xx_uart_clksrc *clocks;
 unsigned int      clocks_size;
};

/* s3c24xx_uart_devs
 *
 * this is exported from the core as we cannot use driver_register(),
 * or platform_add_device() before the console_initcall()
*/

extern struct platform_device *s3c24xx_uart_devs[4];

#endif /* __ASSEMBLY__ */

#endif /* __ASM_ARM_REGS_SERIAL_H */

 

 

Logo

更多推荐