编写Linux内核模块:I2C

哈伯,你好!

本文重点介绍I2C(集成电路间)Linux内核模块的开发。 下面描述了实现I2C驱动程序基本结构的过程,您可以在其中轻松添加必要功能的实现。

我们描述输入数据:运行在Linux 3.18.19版和外围设备(EEPROM AT24C64和BME280)上的“连接”到FPGA的新处理器的I2C模块。

I2C的操作原理很简单,但是如果您需要学习知识,可以在这里阅读。


图1. I2C总线信号的时序图

在开始开发驱动程序之前,让我们看看用户空间应用程序如何与内核模块交互,为此:

  1. 我们实施了一个小型用户空间应用程序,目的是读取设备的唯一I2C寄存器ID。 此步骤将使您了解内核模块和用户应用程序之间进行交换的接口。
  2. 让我们熟悉内核模块传输I2C消息的选项;
  3. 将内核模块添加到程序集中,并在设备树中描述设备的硬件;
  4. 我们通过一些解释来实现I2C驱动程序的一般结构(框架)。

不幸的是,不可能附加已开发驱动程序的真实资源。 另外,我想指出,控制器的所有名称,名称和注册卡均已更改。 驱动程序的骨架中甚至没有包括一半已开发功能,但是,驱动程序结构是开发的良好起点。 在这里可以找到I2C驱动程序的示例。

第一步


首先,让我们熟悉i2cdetect实用程序。 i2cdetect的结果如下:
./i2cdetect -y 0 0 1 2 3 4 5 6 7 8 9 abcdef 00: — — — — — — — — — — — — — 10: — — — — — — — — — — — — — — — — 20: — — — — — — — — — — — — — — — — 30: — — — — — — — — — — — — — — — — 40: — — — — — — — — — — — — — — — — 50: 50 — — — — — — — — — — — — — — — 60: — — — — — — — — — — — — — — — — 70: — — — — — — — — 

该实用程序顺序地在I2C上公开设备地址总线,并在收到肯定响应(在这种情况下,肯定回答为ACK)后,在控制台中的总线上显示设备地址号。

我们将编写一个小程序,该程序读取温度传感器的唯一ID,并在控制台中显示其工作结果。 看起来很简单:

 #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <sys/ioctl.h> #include <linux/i2c.h> #include <linux/i2c-dev.h> #define I2C_ADAPTER "/dev/i2c-0" int read_buffer(int fd) { struct i2c_rdwr_ioctl_data data; struct i2c_msg messages[2]; unsigned char write_buf[1] = {0xD0}, read_buf[1] = {0x00}; unsigned char write[200]; /* * .addr -   () * .flags -     (0 - w, 1 - r) * .len - - /  * .buf -      */ messages[0].addr = 0x50; messages[0].flags = 0; messages[0].len = 1; messages[0].buf = write_buf; messages[1].addr = 0x50; messages[1].flags = 1; messages[1].len = 1; messages[1].buf = read_buf; data.msgs = messages; data.nmsgs = 2; if (ioctl(fd, I2C_RDWR, &data) < 0) printf("Cant send data!\n"); else printf("ID = 0x%x\n", read_buf[0]); } int main(int argc, char **argv) { int fd; /* * Open I2C file descriptor. */ fd = open(I2C_ADAPTER, O_RDWR); if (fd < 0) { printf("Unable to open i2c file\n"); return 0; } read_buffer(fd); return 0; } 

显然,内核模块以i2c_rdwr_ioctl_data消息字段的形式接收数据。 该结构包含诸如i2c_msg和nmsgs之类的字段,用于传输:

  • .addr-设备地址;
  • .flags-操作类型(读或写);
  • .len-当前消息的长度;
  • .buf-剪贴板。

第二步


现在,我不深入研究,不熟悉I2C驱动程序的一个版本。
如已经建立的,内核模块接收结构形式的消息。 例如,在执行写操作(与硬件有关的部分)时,请考虑驱动程序的算法:

  • 首先填充TX FIFO:首先发送设备的地址,然后发送剩余的数据。
  • ISR中断状态寄存器被清除并且IER寄存器中的中断被允许(在这种情况下,当TX FIFO中没有数据时发生中断)。
  • 允许数据传输,并且在总线上设置起始位。

所有后续数据交换将在中断处理程序中进行。
可以在此处找到使用该算法的驱动程序。 同样,控制器可能没有FIFO,而只有一个传输寄存器,但这是FIFO大小等于1的特殊情况。

第三步


将内核模块添加到程序集中,并在设备树中描述设备的硬件:

1.在以下目录中创建源文件:

 cd drivers/i2c/busses/ vim i2c-skel.c :wq 

结果,文件出现:

 drivers/i2c/busses/i2c-skel.c 


2.将驱动程序配置添加到驱动程序/ i2c /总线/ Kconfig中

 config I2C_SKEL tristate "I2C adapter" help If you say yes to this option, support will be included for the I2C interface. 

3.将驱动程序/ i2c /总线/ Makefile驱动程序添加到程序集中

 obj-$(CONFIG_I2C_SKEL) += i2c-skel.o 

4.在设备树(* .dts)中添加I2C块的描述,并立即支持eeprom设备:

  i2c: i2c@f8f01d00 { compatible = "skel,skel-i2c"; #address-cells = <1>; #size-cells = <0>; reg = <0x43c00000 0x100>; interrupt-parent = <&ps7_scugic_0>; interrupts = <0 29 4>; clock-names = "skel-i2c"; clocks = <&clkc 38>; clock-frequency = <100000>; 24c64@50 { compatible = "at,24c64"; pagesize = <32>; reg = <0x50>; }; } ; 

上面的步骤将不会被详细考虑,但是好奇的读者可以在这里看看。

第四步


在熟悉了驱动程序的原理之后,让我们继续执行。
首先,连接头文件,描述“虚拟”注册卡以及I2C驱动程序的介绍。

 /* i2c-skel.c: I2C bus driver. * * Name Surname <name@surname.ru> * * This file is licensed under the terms of the GNU General Public License * version 2. This program is licensed "as is" without any warranty of any * kind, whether express or implied. */ #include <linux/module.h> #include <linux/kernel.h> #include <linux/platform_device.h> #include <linux/i2c.h> #include <linux/io.h> #include <linux/clk.h> #include <linux/interrupt.h> #include <linux/time.h> #include <linux/delay.h> #include <linux/device.h> /* * Registers description. */ #define SKEL_I2C_ID 0x00 /* Core Identifier register */ #define SKEL_I2C_ISR 0x14 /* Interrupt Status Register */ #define SKEL_I2C_ISR_DNE BIT(0) /* One byte transaction done */ #define SKEL_I2C_ISR_ARB BIT(1) /* Arbitration lost */ #define SKEL_I2C_ISR_TXE BIT(2) /* RX FIFO nearly full */ #define SKEL_I2C_ISR_NACK BIT(3) /* No ACK */ #define SKEL_I2C_IER 0x18 /* Interrupt Enable Register */ #define SKEL_I2C_IER_DNE BIT(0) /* Enable DNE IRQ */ #define SKEL_I2C_IER_ARB BIT(1) /* Enable ARB LOSR IRQ */ #define SKEL_I2C_IER_TXE BIT(2) /* Enable TX FIFO EPMTY IRQ */ #define SKEL_I2C_IER_NACK BIT(3) /* Enable NACK IRQ */ #define SKEL_I2C_CTRL 0x1C /* Control Register */ #define SKEL_I2C_CTRL_EN BIT(0) /* Enable I2C controller */ #define SKEL_I2C_CTRL_START BIT(1) /* Send START condition */ #define SKEL_I2C_CTRL_R BIT(2) /* Read command */ #define SKEL_I2C_CTRL_W BIT(3) /* Write command */ #define SKEL_I2C_CTRL_STOP BIT(4) /* Send STOP cindition */ #define SKEL_I2C_TX 0x20 /* TX FIFO */ #define SKEL_I2C_RX 0x24 /* RX FIFO */ #define SKEL_I2C_CLK 0x28 /* Clock Prescale Register*/ #define SKEL_I2C_TIMEOUT 100000 #define SKEL_I2C_XFER_TIMEOUT (msecs_to_jiffies(500)) #define FIFO_SIZE_TX 1024 #define FIFO_SIZE_RX 1024 int presc = -1; module_param(presc, int, S_IRUGO | S_IWUSR); /* * skel_i2c - I2C device context * @base: pointer to register struct * @msg: pointer to current message * @mlen: number of bytes transferred in msg * @dev: device reference * @adap: i2c core abstraction * @msg_complete: xfer completion object * @clk: reference for i2c input clock * @err: error occured * @buf: ptr to msg buffer * @bus_clock: current i2c bus clock rate * @lock: spinlock for IRQ synchronization */ struct skel_i2c { void __iomem *base; struct i2c_msg *msg; size_t mlen; struct device *dev; struct i2c_adapter adap; struct completion msg_complete; struct clk *clk; u32 bus_clock; int err; u32 addr; u8 *buf; spinlock_t lock; }; 

控制器的主要控制寄存器为:

  • 控制寄存器(CTRL)-控制寄存器;
  • 中断状态寄存器(ISR)-中断状态寄存器;
  • 中断使能寄存器(IER)-中断屏蔽寄存器。

驱动程序的核心是skel_i2c结构,其中包含以下字段:

  • .base-指向注册卡开头的指针;
  • .msg-指向当前消息的指针;
  • .adap-I2C抽象(单击)

让我们继续进行更实际的部分,描述驱动程序支持的设备类型,
I2C适配器功能和I2C消息传递接口:

 static const struct of_device_id skel_i2c_match[] = { { .compatible = "skel,skel-i2c", }, { .compatible = "at,24c64", }, {}, }; static u32 skel_i2c_func(struct i2c_adapter *adap) { return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; } static const struct i2c_algorithm skel_i2c_algo = { .master_xfer = skel_i2c_xfer, .functionality = skel_i2c_func, }; static struct platform_driver skel_i2c_driver = { .probe = skel_i2c_probe, .remove = skel_i2c_remove, .driver = { .name = "skel-i2c", .of_match_table = skel_i2c_match, }, }; module_platform_driver(skel_i2c_driver); MODULE_AUTHOR("Name Surname"); MODULE_DESCRIPTION("I2C bus driver"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:skel-i2c"); 

从结构和功能的名称来看,它们的用途显而易见,我们仅从上面描述了主要结构:

  • skel_i2c_driver-描述驱动程序的名称,支持的设备和函数的表,这些函数在内核模块从系统中加载或删除时被调用。

现在该在系统中注册驱动程序了,这意味着实现控制器初始化功能,并描述skel_i2c_probe(在将驱动程序加载到系统中时调用)和skel_i2c_remove(在从系统中删除驱动程序时调用)。

 static int skel_i2c_init(struct skel_i2c *rdev) { u32 bus_clk_khz = rdev->bus_clock / 1000; u32 clk_khz = clk_get_rate(rdev->clk) / 1000; int prescale; int diff; prescale = clk_khz / (5 * bus_clk_khz) - 1; prescale = clamp(prescale, 0, 0xFFFF); diff = clk_khz / (5 * (prescale 1)) - bus_clk_khz; if (abs(diff) > bus_clk_khz / 10) { dev_err(rdev->dev, "Unsupported clock settings: clk: %d KHz, bus: %d KHz\n", clk_khz, bus_clk_khz); return -EINVAL; } if (presc != -1) i2c_write(presc, rdev->base, SKEL_I2C_CLK); else i2c_write(prescale, rdev->base, SKEL_I2C_CLK); return 0; } static int skel_i2c_probe(struct platform_device *pdev) { struct skel_i2c *rdev = NULL; struct resource *res; int irq, ret; u32 val; rdev = devm_kzalloc(&pdev->dev, sizeof(*rdev), GFP_KERNEL); if (!rdev) return -ENOMEM; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); rdev->base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(rdev->base)) return PTR_ERR(rdev->base); irq = platform_get_irq(pdev, 0); if (irq < 0) { dev_err(&pdev->dev, "Missing interrupt resource\n"); return irq; } rdev->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(rdev->clk)) { dev_err(&pdev->dev, "Missing clock\n"); return PTR_ERR(rdev->clk); } rdev->dev = &pdev->dev; init_completion(&rdev->msg_complete); spin_lock_init(&rdev->lock); val = of_property_read_u32(pdev->dev.of_node, "clock-frequency", &rdev->bus_clock); if (val) { dev_err(&pdev->dev, "Default to 100kHz\n"); rdev->bus_clock = 100000; /* default clock rate */ } if (rdev->bus_clock > 400000) { dev_err(&pdev->dev, "Invalid clock-frequency %d\n", rdev->bus_clock); return -EINVAL; } ret = devm_request_irq(&pdev->dev, irq, skel_i2c_isr, 0, pdev->name, rdev); if (ret) { dev_err(&pdev->dev, "Failed to claim IRQ %d\n", irq); return ret; } ret = clk_prepare_enable(rdev->clk); if (ret) { dev_err(&pdev->dev, "Failed to enable clock\n"); return ret; } skel_i2c_init(rdev); i2c_set_adapdata(&rdev->adap, rdev); strlcpy(rdev->adap.name, pdev->name, sizeof(rdev->adap.name)); rdev->adap.owner = THIS_MODULE; rdev->adap.algo = &skel_i2c_algo; rdev->adap.dev.parent = &pdev->dev; rdev->adap.dev.of_node = pdev->dev.of_node; platform_set_drvdata(pdev, rdev); ret = i2c_add_adapter(&rdev->adap); if (ret) { clk_disable_unprepare(rdev->clk); return ret; } dev_info(&pdev->dev, "I2C probe complete\n"); return 0; } static int skel_i2c_remove(struct platform_device *pdev) { struct skel_i2c *rdev = platform_get_drvdata(pdev); clk_disable_unprepare(rdev->clk); i2c_del_adapter(&rdev->adap); return 0; } 

最简单的功能是skel_i2c_remove,它将关闭时钟源并释放已用的内存。 skel_i2c_init函数执行I2C控制器的初始化。

如前所述,skel_i2c_probe在系统中注册了驱动程序。 有条件的操作顺序可以分为两个阶段:

  • 获取系统资源并注册中断处理程序skel_i2c_isr;
  • 填写结构字段并调用添加新I2C适配器的过程。

在系统中注册驱动程序后,可以在接口上实现消息传输逻辑:

 static inline void i2c_write(uint32_t value, void *base, uint32_t addr) { writel(value, base addr); #if defined DEBUG dev_dbg(rdev->dev, "iowrite32(0x%x, base 0x%x);\n", value, addr); #endif } static inline uint32_t i2c_read(void *base, uint32_t addr) { uint32_t reg = readl(base addr); #if defined DEBUG dev_dbg(rdev->dev, "/* ioread32(base 0x%x) == 0x%x */\n", addr, reg); #endif return reg; } static irqreturn_t skel_i2c_isr(int irq, void *dev) { if (unlikely(int_stat & skel_I2C_ISR_ARB)) { } else if (unlikely(int_stat & skel_I2C_ISR_NACK)) { } if (read) fill_rx_fifo(rdev); else fill_tx_fifo(rdev); complete(&rdev->msg_complete); return IRQ_HANDLED; } static int skel_i2c_xfer_msg(struct skel_i2c *rdev, struct i2c_msg *msg) { unsigned long time; rdev->msg = msg; rdev->mlen = msg->len; rdev->addr = msg->addr; rdev->buf = msg->buf; rdev->err = 0; reinit_completion(&rdev->msg_complete); skel_i2c_start_trans(rdev, msg); time = wait_for_completion_timeout(&rdev->msg_complete, skel_I2C_XFER_TIMEOUT); if (time == 0) rdev->err = -ETIMEDOUT; rdev->curr; return rdev->err; } static int skel_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) { struct skel_i2c *rdev = i2c_get_adapdata(adap); int i, ret = 0; for (i = 0; (ret == 0) && (i < num); i) ret = skel_i2c_xfer_msg(rdev, msgs); skel_i2c_snd_stop(rdev); return ret ? : num; } 

第一步描述了用户空间应用程序与系统内核模块的交互。 在我们实现了驱动程序的内部结构之后,很容易看到进行交换的接口。 通常,消息传递如下:

  • skel_i2c_xfer-该函数直接接收要传输的消息,并将每个消息顺序传输到skel_i2c_xfer_msg。 如果在数据传输过程中发生错误,则数据传输将停止;
  • skel_i2c_xfer_msg-该函数设置所有必要的驱动程序字段并启动消息传输的开始;
  • skel_i2c_isr-中断处理程序。 在这里进行错误处理和总线通信。 如果所有数据都已发送/接收,则通过调用complete函数来设置完成标志,该标志表示消息传输已完成。

本文没有描述这项工作的一些细微之处。 例如,发送消息的操作顺序,因为此算法的实现取决于硬件。 无论控制器的硬件功能如何,我们都专注于驱动程序常规部分的实现。

完整的驱动程序框架如下所示。 请,如果您发现错误/不正确,或者您有什么要补充的内容,请写在PM或注释中。

驱动程序骨架
 /* i2c-skel.c: I2C bus driver. * * Name Surname <name@surname.ru> * * This file is licensed under the terms of the GNU General Public License * version 2. This program is licensed "as is" without any warranty of any * kind, whether express or implied. */ #include <linux/module.h> #include <linux/kernel.h> #include <linux/platform_device.h> #include <linux/i2c.h> #include <linux/io.h> #include <linux/clk.h> #include <linux/interrupt.h> #include <linux/time.h> #include <linux/delay.h> #include <linux/device.h> /* * Registers description. */ #define SKEL_I2C_ID 0x00 /* Core Identifier register */ #define SKEL_I2C_ISR 0x14 /* Interrupt Status Register */ #define SKEL_I2C_ISR_DNE BIT(0) /* One byte transaction done */ #define SKEL_I2C_ISR_ARB BIT(1) /* Arbitration lost */ #define SKEL_I2C_ISR_TXE BIT(2) /* RX FIFO nearly full */ #define SKEL_I2C_ISR_NACK BIT(3) /* No ACK */ #define SKEL_I2C_IER 0x18 /* Interrupt Enable Register */ #define SKEL_I2C_IER_DNE BIT(0) /* Enable DNE IRQ */ #define SKEL_I2C_IER_ARB BIT(1) /* Enable ARB LOSR IRQ */ #define SKEL_I2C_IER_TXE BIT(2) /* Enable TX FIFO EPMTY IRQ */ #define SKEL_I2C_IER_NACK BIT(3) /* Enable NACK IRQ */ #define SKEL_I2C_CTRL 0x1C /* Control Register */ #define SKEL_I2C_CTRL_EN BIT(0) /* Enable I2C controller */ #define SKEL_I2C_CTRL_START BIT(1) /* Send START condition */ #define SKEL_I2C_CTRL_R BIT(2) /* Read command */ #define SKEL_I2C_CTRL_W BIT(3) /* Write command */ #define SKEL_I2C_CTRL_STOP BIT(4) /* Send STOP cindition */ #define SKEL_I2C_TX 0x20 /* TX FIFO */ #define SKEL_I2C_RX 0x24 /* RX FIFO */ #define SKEL_I2C_CLK 0x28 /* Clock Prescale Register*/ #define SKEL_I2C_TIMEOUT 100000 #define SKEL_I2C_XFER_TIMEOUT (msecs_to_jiffies(500)) #define FIFO_SIZE_TX 1024 #define FIFO_SIZE_RX 1024 int presc = -1; module_param(presc, int, S_IRUGO | S_IWUSR); /* * skel_i2c - I2C device context * @base: pointer to register struct * @msg: pointer to current message * @mlen: number of bytes transferred in msg * @dev: device reference * @adap: i2c core abstraction * @msg_complete: xfer completion object * @clk: reference for i2c input clock * @err: error occured * @buf: ptr to msg buffer * @bus_clock: current i2c bus clock rate * @lock: spinlock for IRQ synchronization */ struct skel_i2c { void __iomem *base; struct i2c_msg *msg; size_t mlen; struct device *dev; struct i2c_adapter adap; struct completion msg_complete; struct clk *clk; u32 bus_clock; int err;; u32 addr; u8 *buf; spinlock_t lock; }; static const struct of_device_id skel_i2c_match[] = { { .compatible = "skel,skel-i2c", }, { .compatible = "at,24c64", }, {}, }; static inline void i2c_write(uint32_t value, void *base, uint32_t addr) { writel(value, base + addr); #if defined DEBUG dev_dbg(rdev->dev, "iowrite32(0x%x, base 0x%x);\n", value, addr); #endif } static inline uint32_t i2c_read(void *base, uint32_t addr) { uint32_t reg = readl(base + addr); #if defined DEBUG dev_dbg(rdev->dev, "/* ioread32(base 0x%x) == 0x%x */\n", addr, reg); #endif return reg; } static void skel_i2c_transfer(struct skel_i2c *rdev, u32 data) { i2c_write(data, rdev->base, SKEL_I2C_TX); } static void fill_tx_fifo(struct skel_i2c *rdev) { size_t tx_fifo_avail = FIFO_SIZE_TX; int bytes_to_transfer = min(tx_fifo_avail, rdev->mlen); while (bytes_to_transfer-- > 0) { skel_i2c_transfer(rdev, *rdev->buf); rdev->mlen--; } } static void fill_rx_fifo(struct skel_i2c *rdev) { size_t rx_fifo_avail = FIFO_SIZE_RX; int receive = min(rx_fifo_avail, rdev->mlen); while (receive-- > 0) { *rdev->buf = i2c_read(rdev->base, SKEL_I2C_RX); rdev->mlen--; } } void skel_i2c_snd_stop(struct skel_i2c *rdev) { u32 control = i2c_read(rdev->base, SKEL_I2C_CTRL); i2c_write(control | SKEL_I2C_CTRL_STOP, rdev->base, SKEL_I2C_CTRL); } static irqreturn_t skel_i2c_isr(int irq, void *dev) { struct skel_i2c *rdev = dev; u32 int_stat, read; int_stat = i2c_read(rdev->base, SKEL_I2C_ISR); read = rdev->msg->flags & I2C_M_RD; if (unlikely(int_stat & SKEL_I2C_ISR_ARB)) { } else if (unlikely(int_stat & SKEL_I2C_ISR_NACK)) { } if (read) fill_rx_fifo(rdev); else fill_tx_fifo(rdev); complete(&rdev->msg_complete); return IRQ_HANDLED; } static void skel_i2c_start_trans(struct skel_i2c *rdev, struct i2c_msg *msg) { } static int skel_i2c_xfer_msg(struct skel_i2c *rdev, struct i2c_msg *msg) { unsigned long time; rdev->msg = msg; rdev->mlen = msg->len; rdev->addr = msg->addr; rdev->buf = msg->buf; rdev->err = 0; reinit_completion(&rdev->msg_complete); skel_i2c_start_trans(rdev, msg); time = wait_for_completion_timeout(&rdev->msg_complete, SKEL_I2C_XFER_TIMEOUT); if (time == 0) rdev->err = -ETIMEDOUT; return rdev->err; } static int skel_i2c_init(struct skel_i2c *rdev) { u32 bus_clk_khz = rdev->bus_clock / 1000; u32 clk_khz = clk_get_rate(rdev->clk) / 1000; int prescale; int diff; prescale = clk_khz / (5 * bus_clk_khz) - 1; prescale = clamp(prescale, 0, 0xFFFF); diff = clk_khz / (5 * (prescale - 1)) - bus_clk_khz; if (abs(diff) > bus_clk_khz / 10) { dev_err(rdev->dev, "Unsupported clock settings: clk: %d KHz, bus: %d KHz\n", clk_khz, bus_clk_khz); return -EINVAL; } if (presc != -1) i2c_write(presc, rdev->base, SKEL_I2C_CLK); else i2c_write(prescale, rdev->base, SKEL_I2C_CLK); return 0; } static int skel_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) { struct skel_i2c *rdev = i2c_get_adapdata(adap); int i, ret = 0; for (i = 0; (ret == 0) && (i < num); i++) ret = skel_i2c_xfer_msg(rdev, msgs); skel_i2c_snd_stop(rdev); return ret ? : num; } static u32 skel_i2c_func(struct i2c_adapter *adap) { return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; } static const struct i2c_algorithm skel_i2c_algo = { .master_xfer = skel_i2c_xfer, .functionality = skel_i2c_func, }; static int skel_i2c_probe(struct platform_device *pdev) { struct skel_i2c *rdev = NULL; struct resource *res; int irq, ret; u32 val; rdev = devm_kzalloc(&pdev->dev, sizeof(*rdev), GFP_KERNEL); if (!rdev) return -ENOMEM; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); rdev->base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(rdev->base)) return PTR_ERR(rdev->base); irq = platform_get_irq(pdev, 0); if (irq < 0) { dev_err(&pdev->dev, "Missing interrupt resource\n"); return irq; } rdev->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(rdev->clk)) { dev_err(&pdev->dev, "Missing clock\n"); return PTR_ERR(rdev->clk); } rdev->dev = &pdev->dev; init_completion(&rdev->msg_complete); spin_lock_init(&rdev->lock); val = of_property_read_u32(pdev->dev.of_node, "clock-frequency", &rdev->bus_clock); if (val) { dev_err(&pdev->dev, "Default to 100kHz\n"); rdev->bus_clock = 100000; /* default clock rate */ } if (rdev->bus_clock > 400000) { dev_err(&pdev->dev, "Invalid clock-frequency %d\n", rdev->bus_clock); return -EINVAL; } ret = devm_request_irq(&pdev->dev, irq, skel_i2c_isr, 0, pdev->name, rdev); if (ret) { dev_err(&pdev->dev, "Failed to claim IRQ %d\n", irq); return ret; } ret = clk_prepare_enable(rdev->clk); if (ret) { dev_err(&pdev->dev, "Failed to enable clock\n"); return ret; } skel_i2c_init(rdev); i2c_set_adapdata(&rdev->adap, rdev); strlcpy(rdev->adap.name, pdev->name, sizeof(rdev->adap.name)); rdev->adap.owner = THIS_MODULE; rdev->adap.algo = &skel_i2c_algo; rdev->adap.dev.parent = &pdev->dev; rdev->adap.dev.of_node = pdev->dev.of_node; platform_set_drvdata(pdev, rdev); ret = i2c_add_adapter(&rdev->adap); if (ret) { clk_disable_unprepare(rdev->clk); return ret; } dev_info(&pdev->dev, "I2C probe complete\n"); return 0; } static int skel_i2c_remove(struct platform_device *pdev) { struct skel_i2c *rdev = platform_get_drvdata(pdev); clk_disable_unprepare(rdev->clk); i2c_del_adapter(&rdev->adap); return 0; } static struct platform_driver skel_i2c_driver = { .probe = skel_i2c_probe, .remove = skel_i2c_remove, .driver = { .name = "skel-i2c", .of_match_table = skel_i2c_match, }, }; module_platform_driver(skel_i2c_driver); MODULE_AUTHOR("Name Surname"); MODULE_DESCRIPTION("I2C bus driver"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:skel-i2c"); 


感谢您的关注!

Source: https://habr.com/ru/post/zh-CN413249/


All Articles