在Mini2440裸机开发之SPI(OLED SSD1306)我们介绍了S3C2440这款SOC的I2C结构,其内部只有一个SPI控制器:S3C2440 SPI相关引脚定义:
SPI | SCLK | MOSI | MISO | SS |
SPI0 | GPE13 | GPE12 | GPE11 | GPG2 |
SPI1 | GPG7 | GPG6 | GPG5 | GPG3 |
这一节我们将研究S3C2440的SPI控制器驱动,或者说SPI Master驱动。
SPI控制器驱动是基于platform模型的,主要提供一个SPI片选以及SPI协议的收发函数。在platform driver中probe函数中:
- 动态分配spi_controller,并进行初始化,包括设置set_cs、transfer_one;
- 初始化GPIO功能复用为SPI;
- 最后将spi_controller注册到spi_bus_type总线,并且注册时会调用spi_match_controller_to_boardinfo,扫描borad_list链表并使用spi_new_device注册SPI从设备。
一、 platform设备注册(s3c2410-spi)
1.1 s3c_device_spi1
我们定位到arch/arm/plat-samsung/devs.c定义,可以发现有platform设备s3c_device_spi1变量,
static struct resource s3c_spi1_resource[] = { [0] = DEFINE_RES_MEM(S3C24XX_PA_SPI1, SZ_32), [1] = DEFINE_RES_IRQ(IRQ_SPI1), }; struct platform_device s3c_device_spi1 = { // platform设备信息 .name = "s3c2410-spi", .id = 1, .num_resources = ARRAY_SIZE(s3c_spi1_resource), .resource = s3c_spi1_resource, .dev = { .dma_mask = &samsung_device_dma_mask, .coherent_dma_mask = DMA_BIT_MASK(32), } };
我们重点关注一下platform设备资源定义,这里定义了2个资源,第1个是内存资源、第2个是中断资源:
- 第一个资源是IO内存资源,起始地址是SPI1寄存器寄基地址,即0x59000020,大小位32字节;
- 第二个资源是中断资源,中断号位IRQ_SPI1,即29;
此外dma_mask设置为:
#define samsung_device_dma_mask (*((u64[]) { DMA_BIT_MASK(32) }))
我们在arch/arm/plat-samsung/devs.文件追加如下代码:
void __init s3c_spi1_set_platdata(struct s3c2410_spi_info *pd) // 用于设置SPI平台设备dev.platform_data { struct s3c2410_spi_info *npd; npd = s3c_set_platdata(pd, sizeof(*npd), &s3c_device_spi1); if (!npd){ printk(KERN_ERR "no memory for SPI platform data\n"); } }
这里已经定义了SPI控制器1相关的platform_device设备s3c_device_spi1,并进行了初始化,那platform设备啥时候注册的呢?
linux内核启动的时候会根据uboot中设置的机器id执行相应的初始化工作,比如.init_machine、.init_irq。
我们首先定位到arch/arm/mach-s3c24xx/mach-smdk2440.c:
MACHINE_START(S3C2440, "SMDK2440") /* Maintainer: Ben Dooks <[email protected]> */ .atag_offset = 0x100, .init_irq = s3c2440_init_irq, .map_io = smdk2440_map_io, .init_machine = smdk2440_machine_init, .init_time = smdk2440_init_time, MACHINE_END
重点关注init_machine,init_machine中保存的是开发板资源注册的初始化代码。
1.2 smdk2440_machine_init
static void __init smdk2440_machine_init(void) { s3c24xx_fb_set_platdata(&smdk2440_fb_info); s3c_i2c0_set_platdata(NULL); // 初始化s3c_device_i2c0->dev.platform_data platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices)); // s3c2440若干个platform设备注册 usb host controller、lcd、wdt等 smdk_machine_init(); // s3c24x0系列若干个platform设备注册(通用) }
这里利用platform_add_devices进行若干个platform设备的注册,我们修改smdk2440_machine_init添加如下代码:
s3c_spi1_set_platdata(&default_spi1_data); // 初始化s3c_device_spi1->dev.platform_data
同时需要在该文件定义:
struct s3c2410_spi_info default_spi1_data = { // 用于初始化&s3c_device_spi1->dev.platform_data .pin_cs = S3C2410_GPG(3), // 这里我们使用GPG3作为SPI从设备的片选引脚,宏定义在arch/arm/mach-s3c24xx/include/mach/gpio-samsung.h .num_cs = 1, // 片选数量 .bus_num = 1 // SPI控制器编号 };
smdk2440_machine_init函数通过调用platform_device_register实现platform设备注册。
static struct platform_device *smdk2440_devices[] __initdata = { &s3c_device_ohci, &s3c_device_lcd, &s3c_device_wdt, &s3c_device_i2c0, &s3c_device_iis, &smdk2440_device_eth, };
我们需要在smdk2440_devices数组添加&s3c_device_spi1(platform设备s3c_device_spi1是通过arch/arm/plat-samsung/include/plat/devs.h头文件引入的)。
二、platform驱动注册(s3c2410-spi)
SPI控制器platform驱动定义位于drivers/spi/spi-s3c24xx.c文件中。
2.1 入口和出口函数
我们在spi-s3c24xx.c文件定位到驱动模块的入口和出口:
MODULE_ALIAS("platform:s3c2410-spi"); static struct platform_driver s3c24xx_spi_driver = { .probe = s3c24xx_spi_probe, .remove = s3c24xx_spi_remove, .driver = { .name = "s3c2410-spi", .pm = S3C24XX_SPI_PMOPS, }, }; module_platform_driver(s3c24xx_spi_driver);
在plaftrom总线设备驱动模型中,我们知道当内核中有platform设备platform驱动匹配,会调用到platform_driver里的成员.probe,在这里就是s3c24xx_spi_probe函数。
2.2 SPI相关数据结构
在介绍s3c24xx_spi_probe之前,先来看一下s3c24xx_spi结构体定义,其定义在drivers/spi/spi-s3c24xx.c文件:
struct s3c24xx_spi { // 用来表示SoC的一个SPI控制器 /* bitbang has to be first */ struct spi_bitbang bitbang; // 位宽,默认为8 struct completion done; // 同步机制 void __iomem *regs; int irq; // 中断号 int len; int count; struct fiq_handler fiq_handler; enum spi_fiq_mode fiq_mode; unsigned char fiq_inuse; unsigned char fiq_claimed; void (*set_cs)(struct s3c2410_spi_info *spi, // 设置片选引脚 int cs, int pol); /* data buffers */ const unsigned char *tx; // 传输缓冲区 unsigned char *rx; // 接收缓冲区 struct clk *clk; // 时钟 struct spi_master *master; // SPI控制器 struct spi_device *curdev; // 当前SPI从设备 struct device *dev; / 存储platform设备的dev成员 struct s3c2410_spi_info *pdata; // 私有数据 默认初始化为&default_spi_data };
s3c2410_spi_info定义在include/linux/spi/s3c24xx.h:
struct s3c2410_spi_info { int pin_cs; /* simple gpio cs 片选引脚 */ unsigned int num_cs; /* total chipselects 片选数量,也就是说当前SPI控制前连接了多少个SPI从设备 */ int bus_num; /* bus number to use. SPI控制器编号 */ unsigned int use_fiq:1; /* use fiq,是否使用中断 */ void (*gpio_setup)(struct s3c2410_spi_info *spi, int enable); // SPI控制器初始化函数指针,用来设置SPI控制器和工作方式、clock等; void (*set_cs)(struct s3c2410_spi_info *spi, int cs, int pol); // 用于控制GPIO发出片选、取消片选信号 };
2.3 s3c24xx_spi_probe
s3c24xx_spi_probe定义在drivers/spi/spi-s3c24xx.c文件:
static int s3c24xx_spi_probe(struct platform_device *pdev) { struct s3c2410_spi_info *pdata; struct s3c24xx_spi *hw; struct spi_master *master; struct resource *res; int err = 0; master = spi_alloc_master(&pdev->dev, sizeof(struct s3c24xx_spi)); // 动态分配spi_controller,额外分配空间大小为s3c24xx_spi,并赋值给master->dev.driver_data if (master == NULL) { dev_err(&pdev->dev, "No memory for spi_master\n"); return -ENOMEM; } hw = spi_master_get_devdata(master); // 获取master->dev.driver_data hw->master = master; // 设置SPI控制器 hw->pdata = pdata = dev_get_platdata(&pdev->dev); // 获取pdev->dev.platform_data hw->dev = &pdev->dev; if (pdata == NULL) { // 没有设置平台数据 dev_err(&pdev->dev, "No platform data supplied\n"); err = -ENOENT; goto err_no_pdata; } platform_set_drvdata(pdev, hw); // 设置驱动数据 pdev.dev.plat_data = hw init_completion(&hw->done); // 初始化completion /* initialise fiq handler */ s3c24xx_spi_initfiq(hw); // 初始化hw成员fiq_handler /* setup the master state. */ /* the spi->mode bits understood by this driver: */ master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH; // 时钟极性、时钟相位、高片选 master->num_chipselect = hw->pdata->num_cs; // 片选数量 master->bus_num = pdata->bus_num; // SPI控制器编号 master->bits_per_word_mask = SPI_BPW_MASK(8); // 设置bit_per_world的掩码,等于bits_per_word-1,比如这里就是7 /* setup the state for the bitbang driver */ hw->bitbang.master = hw->master; hw->bitbang.setup_transfer = s3c24xx_spi_setupxfer; // 实现了SPI数据传输 hw->bitbang.chipselect = s3c24xx_spi_chipsel; // 实现了SPI从设备片选 hw->bitbang.txrx_bufs = s3c24xx_spi_txrx; hw->master->setup = s3c24xx_spi_setup; // SPI控制器初始化函数指针,用来设置SPI控制器和工作方式、clock等; dev_dbg(hw->dev, "bitbang at %p\n", &hw->bitbang); /* find and map our resources */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); // 获取第一个IO内存资源 hw->regs = devm_ioremap_resource(&pdev->dev, res); // 物理地址->虚拟地址映射 映射是SPI控制器1寄存器地址 if (IS_ERR(hw->regs)) { err = PTR_ERR(hw->regs); goto err_no_pdata; } hw->irq = platform_get_irq(pdev, 0); // 获取中断资源 if (hw->irq < 0) { // 未指定中断 异常 dev_err(&pdev->dev, "No IRQ specified\n"); err = -ENOENT; goto err_no_pdata; } err = devm_request_irq(&pdev->dev, hw->irq, s3c24xx_spi_irq, 0, // 注册中断服务 pdev->name, hw); if (err) { dev_err(&pdev->dev, "Cannot claim IRQ\n"); goto err_no_pdata; } hw->clk = devm_clk_get(&pdev->dev, "spi"); // 获取spi时钟 if (IS_ERR(hw->clk)) { dev_err(&pdev->dev, "No clock for device\n"); err = PTR_ERR(hw->clk); goto err_no_pdata; } /* setup any gpio we can */ if (!pdata->set_cs) { // 如果s3c2410_spi_info中未设置片选/取消片选函数 if (pdata->pin_cs < 0) { // 片选引脚无效 dev_err(&pdev->dev, "No chipselect pin\n"); err = -EINVAL; goto err_register; } err = devm_gpio_request(&pdev->dev, pdata->pin_cs, // 为片选引脚申请GPIO资源 dev_name(&pdev->dev)); if (err) { dev_err(&pdev->dev, "Failed to get gpio for cs\n"); goto err_register; } hw->set_cs = s3c24xx_spi_gpiocs; // 设置片选/取消片选函数 gpio_direction_output(pdata->pin_cs, 1); // 设置GPIO为输出,输出高电平 } else hw->set_cs = pdata->set_cs; s3c24xx_spi_initialsetup(hw); /* register our spi controller */ err = spi_bitbang_start(&hw->bitbang); if (err) { dev_err(&pdev->dev, "Failed to register SPI master\n"); goto err_register; } return 0; err_register: clk_disable(hw->clk); err_no_pdata: spi_master_put(hw->master); return err; }
这段代码属实有点长了,让人一眼看过去,就有点想放弃去读的想法,既然都学习到了这一步,我们还是耐着性去分析吧。这里代码理解起来还是比较简单的,主要流程如下:
- 设置i2c_adapter适配器结构体;
- 设置name为s3c2410-i2c;
- 设置algo为s3c24xx_i2c_algorithm;
- 设置retries为2;
- 设置algo_data 为i2c;
- 设置dev.parent为&pdev->dev;
- 设置nr为i2c->pdata->bus_num;
- ...
- 调用s3c_i2c0_cfg_gpio配置I2C总线所使用的GPIO引脚,即配置GPE15为SDA功能,GPE14为SCA功能;
- 调用s3c24xx_i2c_init初始化S3C2440 I2C相关的寄存器,设置从设备地址、以及I2C时钟预分频系数;
- 调用devm_request_irq注册I2C中断,中断处理服务函数为s3c24xx_i2c_irq;
- 调用i2c_add_numbered_adapter注册i2c_adapter适配器;
2.3.1 devm_gpio_request
devm_gpio_request用来申请GPIO资源,定义在drivers/gpio/gpiolib-devres.c文件:
/** * devm_gpio_request - request a GPIO for a managed device * @dev: device to request the GPIO for * @gpio: GPIO to allocate * @label: the name of the requested GPIO * * Except for the extra @dev argument, this function takes the * same arguments and performs the same function as * gpio_request(). GPIOs requested with this function will be * automatically freed on driver detach. * * If an GPIO allocated with this function needs to be freed * separately, devm_gpio_free() must be used. */ int devm_gpio_request(struct device *dev, unsigned gpio, const char *label) { unsigned *dr; int rc; dr = devres_alloc(devm_gpio_release, sizeof(unsigned), GFP_KERNEL); if (!dr) return -ENOMEM; rc = gpio_request(gpio, label); if (rc) { devres_free(dr); return rc; } *dr = gpio; devres_add(dev, dr); return 0; }
2.3.2 s3c24xx_spi_gpiocs
s3c24xx_spi_gpiocs提供了片选和取消片选的功能,实际上就是调用gpio_set_value来设置输出高、低电平,定义在drivers/spi/spi-s3c24xx.c文件:
static void s3c24xx_spi_gpiocs(struct s3c2410_spi_info *spi, int cs, int pol) { gpio_set_value(spi->pin_cs, pol); }
2.3.3 s3c24xx_spi_initialsetup
s3c24xx_spi_initialsetup用于使能spi时钟,初始化S3C2440 SPI1相关的寄存器,设置S3C2410_SPPIN、S3C2410_SPCON,定义在drivers/spi/spi-s3c24xx.c文件:
static void s3c24xx_spi_initialsetup(struct s3c24xx_spi *hw) { /* for the moment, permanently enable the clock */ clk_enable(hw->clk); /* program defaults into the registers */ writeb(0xff, hw->regs + S3C2410_SPPRE); writeb(SPPIN_DEFAULT, hw->regs + S3C2410_SPPIN); writeb(SPCON_DEFAULT, hw->regs + S3C2410_SPCON); if (hw->pdata) { if (hw->set_cs == s3c24xx_spi_gpiocs) // 默认走这里 gpio_direction_output(hw->pdata->pin_cs, 1); // 设置片选引脚GPIO为输出,输出高电平 if (hw->pdata->gpio_setup) // default_spi1_data中未设定该函数指针,不会走这里 hw->pdata->gpio_setup(hw->pdata, 1); } }
2.4 s3c24xx_i2c_algorithm
/* i2c bus registration info */ static const struct i2c_algorithm s3c24xx_i2c_algorithm = { .master_xfer = s3c24xx_i2c_xfer, /* 数据传输,这个上一节已经介绍了 */ .functionality = s3c24xx_i2c_func, };
s3c24xx_i2c_xfer函数在上一节已经介绍了,与SOC的I2C控制器寄存器设置相关了,基本是裸机相关的内容,无非就是产生 I2C 通信时序,如开始信号,停止信号、应答信号等。
2.5 s3c24xx_i2c_init
s3c24xx_i2c_init函数主要就是进行I2C控制器的初始化工作:
/* * initialise the controller, set the IO lines and frequency */ static int s3c24xx_i2c_init(struct s3c24xx_i2c *i2c) { struct s3c2410_platform_i2c *pdata; unsigned int freq; /* get the plafrom data */ pdata = i2c->pdata; /* write slave address */ writeb(pdata->slave_addr, i2c->regs + S3C2410_IICADD); // 写入从设备地址 dev_info(i2c->dev, "slave address 0x%02x\n", pdata->slave_addr); writel(0, i2c->regs + S3C2410_IICCON); // 初始化IICCON=0 writel(0, i2c->regs + S3C2410_IICSTAT); // 初始化IICSTAT=0 /* we need to work out the divisors for the clock... */ if (s3c24xx_i2c_clockrate(i2c, &freq) != 0) { // 这个函数主要就是根据当前i2c时钟频率以及预设的I2C控制器目标频率,计算IIC总线发送时钟预分频系数,freq为设置的实际频率 dev_err(i2c->dev, "cannot meet bus frequency required\n"); return -EINVAL; } /* todo - check that the i2c lines aren't being dragged anywhere */ dev_info(i2c->dev, "bus frequency set to %d KHz\n", freq); dev_dbg(i2c->dev, "S3C2410_IICCON=0x%02x\n", readl(i2c->regs + S3C2410_IICCON)); return 0; }
2.6 s3c24xx_i2c_irq
s3c24xx_i2c_irq调用下一个字节传输函数i2s_s3c_irq_nextbyte()来传输数据。
/* * top level IRQ servicing routine */ static irqreturn_t s3c24xx_i2c_irq(int irqno, void *dev_id) { struct s3c24xx_i2c *i2c = dev_id; unsigned long status; unsigned long tmp; status = readl(i2c->regs + S3C2410_IICSTAT); if (status & S3C2410_IICSTAT_ARBITR) { // 仲裁失败 /* deal with arbitration loss */ dev_err(i2c->dev, "deal with arbitration loss\n"); } if (i2c->state == STATE_IDLE) { // 空闲状态 dev_dbg(i2c->dev, "IRQ: error i2c->state == IDLE\n"); tmp = readl(i2c->regs + S3C2410_IICCON); tmp &= ~S3C2410_IICCON_IRQPEND; writel(tmp, i2c->regs + S3C2410_IICCON); // 清I2C中断挂起 IICCON位[4] goto out; } /* * pretty much this leaves us with the fact that we've * transmitted or received whatever byte we last sent */ i2c_s3c_irq_nextbyte(i2c, status); // 继续数据传输 out: return IRQ_HANDLED; }
参考文章
标签:i2c,s3c24xx,SPI,hw,dev,spi,linux,控制器驱动 From: https://www.cnblogs.com/zyly/p/17155289.html