كتابة وحدة نواة لينكس: I2C

هبر ، مرحباً!

تركز هذه المقالة على تطوير وحدة نواة لينكس I2C (الدائرة المتكاملة). فيما يلي وصف لعملية تنفيذ الهيكل الأساسي لبرنامج I2C ، حيث يمكنك بسهولة إضافة تنفيذ الوظائف الضرورية.

نحن نصف بيانات الإدخال: كتلة I2C للمعالج الجديد "سلكيًا" إلى FPGA ، الذي يعمل بنظام Linux الإصدار 3.18.19 والأجهزة الطرفية (EEPROM AT24C64 و BME280).

مبدأ تشغيل I2C بسيط للغاية ، ولكن إذا كنت بحاجة إلى تحسين المعرفة ، يمكنك القراءة هنا .


الشكل 1. الشكل البياني لتوقيت إشارات الناقل I2C

قبل البدء في تطوير برنامج التشغيل ، دعنا نرى كيف تتفاعل تطبيقات مساحة المستخدم مع وحدة kernel ، لهذا:

  1. ننفذ تطبيقًا صغيرًا لمساحة المستخدم ، والغرض منه قراءة معرف تسجيل I2C الفريد للجهاز. ستتيح لك هذه الخطوة فهم الواجهة التي يتم من خلالها التبادل بين وحدة kernel وتطبيق المستخدم ؛
  2. دعونا نتعرف على خيار إرسال رسائل I2C بواسطة وحدة النواة ؛
  3. إضافة وحدة kernel إلى التجميع ووصف أجهزة الأجهزة الموجودة في شجرة الجهاز ؛
  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) ، تعرض رقم عنوان الجهاز على الناقل في وحدة التحكم.

سنكتب برنامجًا صغيرًا يقرأ المعرف الفريد لمستشعر درجة الحرارة ويعرض نتيجة عمله في وحدة التحكم. تبدو بسيطة للغاية:

 #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; } 

يصبح من الواضح أن وحدة kernel تستقبل البيانات في شكل حقول رسالة i2c_rdwr_ioctl_data. يحتوي الهيكل على حقول مثل i2c_msg و nmsgs ، والتي تُستخدم للإرسال:

  • .addr - عناوين الجهاز ؛
  • .flags - نوع العملية (قراءة أو كتابة) ؛
  • .len - طول الرسالة الحالية ؛
  • .buf- الحافظة.

الخطوة الثانية


الآن ، أنا لا أخوض في الدواخل ، والتعرف على إصدار واحد من برنامج تشغيل I2C.
كما تم إنشاؤه بالفعل ، تتلقى وحدة kernel الرسائل في شكل هيكل. على سبيل المثال ، ضع في اعتبارك خوارزمية برنامج التشغيل عند إجراء عملية كتابة (الجزء التابع للأجهزة):

  • يتم تعبئة TX TXO الأول: يأتي عنوان الجهاز أولاً ، ثم يتم إرسال البيانات المتبقية ؛
  • يتم مسح سجل حالة مقاطعة ISR ويتم تمكين المقاطعات في سجل IER (في هذه الحالة ، يحدث مقاطعة عندما لا توجد بيانات في TX FIFO)
  • يسمح بنقل البيانات وتعيين بت البداية على الناقل.

سيتم إجراء كافة عمليات تبادل البيانات اللاحقة في معالج المقاطعة.
يمكن العثور على برامج التشغيل التي تعمل على هذه الخوارزمية هنا . أيضًا ، قد لا يكون لجهاز التحكم FIFO ، ولكن فقط سجل نقل واحد ، ولكن هذه حالة خاصة مع حجم FIFO يساوي واحد.

الخطوة الثالثة


أضف وحدة kernel إلى التجميع ووصف أجهزة الأجهزة الموجودة في شجرة الجهاز:

1. قم بإنشاء ملف مصدر في الدليل التالي:

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

نتيجة لذلك ، يظهر الملف:

 drivers/i2c/busses/i2c-skel.c 


2. أضف تكوين برنامج التشغيل إلى برامج التشغيل / i2c / busses / 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 / busses / Makefile driver إلى التجميع:

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

4. إضافة وصف كتلة I2C إلى devicetree (* .dts) ، وأيضًا دعم جهاز 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 - يصف اسم برنامج التشغيل ، وهو جدول بالأجهزة والوظائف المدعومة التي يتم استدعاؤها عند تحميل وحدة kernel أو إزالتها من النظام.

حان الوقت لتسجيل برنامج التشغيل في النظام ، مما يعني تنفيذ وظيفة تهيئة وحدة التحكم ، وأيضًا وصف 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 - روتين معالجة المقاطعة. هذا هو المكان الذي يتم فيه معالجة الأخطاء واتصالات الناقل. إذا تم إرسال / استقبال جميع البيانات ، يتم تعيين العلم المنجز عن طريق استدعاء الوظيفة الكاملة ، والتي تشير إلى اكتمال إرسال الرسالة.

لا تصف المقالة بعض التفاصيل الدقيقة للعمل. على سبيل المثال ، تسلسل الإجراءات لإرسال الرسائل ، حيث أن تنفيذ هذه الخوارزمية يعتمد على الأجهزة. ركزنا على تنفيذ الجزء العام من برنامج التشغيل ، بغض النظر عن ميزات أجهزة التحكم.

يتم إرفاق الهيكل العظمي للسائق الكامل أدناه. من فضلك ، إذا وجدت أخطاء / عدم دقة ، أو لديك شيء لتضيفه ، اكتبه في 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/ar413249/


All Articles