Habr, halo!
Artikel ini berfokus pada pengembangan modul kernel Linux I2C (Inter-Integrated Circuit). Berikut ini menjelaskan proses penerapan struktur dasar driver I2C, di mana Anda dapat dengan mudah menambahkan implementasi fungsionalitas yang diperlukan.
Kami menjelaskan data input: blok I2C untuk prosesor baru "kabel" ke FPGA, menjalankan Linux versi 3.18.19 dan periferal (EEPROM AT24C64 dan BME280).
Prinsip operasi I2C cukup sederhana, tetapi jika Anda perlu memoles pengetahuan, Anda dapat membaca di
sini .
Gambar 1. Diagram waktu sinyal bus I2CSebelum mulai mengembangkan driver, mari kita lihat bagaimana aplikasi ruang pengguna berinteraksi dengan modul kernel, untuk ini:
- Kami menerapkan aplikasi ruang pengguna kecil, yang tujuannya adalah untuk membaca ID register I2C unik perangkat. Langkah ini akan memungkinkan Anda untuk memahami antarmuka di mana pertukaran terjadi antara modul kernel dan aplikasi pengguna;
- Mari berkenalan dengan opsi pengiriman pesan I2C oleh modul kernel;
- Tambahkan modul kernel ke rakitan dan jelaskan perangkat keras perangkat di pohon perangkat;
- Kami menerapkan struktur umum (kerangka) driver I2C dengan beberapa penjelasan.
Sayangnya, tidak mungkin untuk melampirkan sumber nyata dari driver yang dikembangkan. Juga, saya ingin mencatat bahwa semua nama, nama, dan kartu register dari pengontrol diubah. Bahkan tidak setengah dari fungsi yang dikembangkan dimasukkan dalam kerangka pengemudi, namun, struktur driver adalah titik awal yang baik untuk pengembangan. Contoh driver I2C dapat ditemukan di
sini .
Langkah pertama
Pertama, mari kita berkenalan dengan utilitas i2cdetect. Hasil dari i2cdetect adalah sebagai berikut:
./i2cdetect -y 0 0 1 2 3 4 5 6 7 8 9 abcdef 00: — — — — — — — — — — — — — 10: — — — — — — — — — — — — — — — — 20: — — — — — — — — — — — — — — — — 30: — — — — — — — — — — — — — — — — 40: — — — — — — — — — — — — — — — — 50: 50 — — — — — — — — — — — — — — — 60: — — — — — — — — — — — — — — — — 70: — — — — — — — —
Utilitas secara berurutan memaparkan bus alamat perangkat pada I2C dan, setelah menerima respons positif (dalam hal ini, jawaban positif adalah ACK), menampilkan nomor alamat perangkat pada bus di konsol.
Kami akan menulis sebuah program kecil yang membaca ID unik dari sensor suhu dan menampilkan hasil kerjanya di konsol. Ini terlihat sangat sederhana:
#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; }
Menjadi jelas bahwa modul kernel menerima data dalam bentuk bidang pesan i2c_rdwr_ioctl_data. Struktur berisi bidang-bidang seperti i2c_msg dan nmsgs, yang digunakan untuk transmisi:
- .addr - alamat perangkat;
- .flags - jenis operasi (baca atau tulis);
- .len - panjang pesan saat ini;
- .buf- papan klip.
Langkah kedua
Sekarang, saya tidak menyelidiki bagian dalam, berkenalan dengan satu versi driver I2C.
Seperti yang telah ditetapkan, modul kernel menerima pesan dalam bentuk struktur. Misalnya, pertimbangkan algoritma driver ketika melakukan operasi tulis (bagian yang bergantung pada perangkat keras):
- Pertama TX FIFO diisi: alamat perangkat yang lebih dulu, dan kemudian data yang tersisa ditransmisikan;
- Register status interupsi ISR dihapus dan interupsi dalam register IER diaktifkan (dalam hal ini, interupsi terjadi ketika tidak ada data di TX FIFO);
- Transmisi data diperbolehkan dan bit mulai diatur di bus.
Semua pertukaran data selanjutnya akan terjadi pada interrupt handler.
Driver yang berfungsi pada algoritma ini dapat ditemukan di
sini . Juga, pengontrol mungkin tidak memiliki FIFO, tetapi hanya register transfer tunggal, tetapi ini adalah kasus khusus dengan ukuran FIFO sama dengan satu.
Langkah ketiga
Tambahkan modul kernel ke rakitan dan jelaskan perangkat keras perangkat di bagan perangkat:
1. Buat file sumber di direktori berikut:
cd drivers/i2c/busses/ vim i2c-skel.c :wq
Akibatnya, file tersebut muncul:
drivers/i2c/busses/i2c-skel.c
2. Tambahkan konfigurasi
driver ke
driver / 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. Tambahkan
driver / i2c / busses / makefile driver ke majelis:
obj-$(CONFIG_I2C_SKEL) += i2c-skel.o
4. Tambahkan deskripsi blok I2C ke devicetree (* .dts), dan juga segera mendukung perangkat 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>; }; } ;
Langkah-langkah di atas tidak akan dipertimbangkan secara rinci, tetapi pembaca yang ingin tahu dapat melihat di
sini .
Langkah keempat
Setelah membiasakan diri dengan prinsip pengemudi, mari kita lanjutkan dengan implementasinya.
Pertama, sambungkan file header, jelaskan kartu register "virtual", serta presentasi driver I2C.
#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; };
Register kontrol utama controller adalah:
- Control Register (CTRL) - register kontrol;
- Interrupt Status Register (ISR) - interrupt status register;
- Interrupt Enable Register (IER) - Interrupt register mask.
Inti dari driver adalah struktur skel_i2c, yang berisi bidang-bidang seperti:
- .base - penunjuk ke awal kartu register;
- .msg - penunjuk ke pesan saat ini;
- .adap - abstraksi I2C (klik) .
Mari kita beralih ke bagian yang lebih praktis, menjelaskan jenis perangkat yang didukung oleh pengemudi,
Fungsi adaptor I2C dan antarmuka pengiriman pesan 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");
Dari nama-nama struktur dan fungsi, tujuannya jelas, kami hanya menggambarkan struktur utama dari yang di atas:
- skel_i2c_driver - menjelaskan nama driver, tabel perangkat yang didukung dan fungsi yang dipanggil saat modul kernel dimuat atau dihapus dari sistem.
Saatnya untuk mendaftarkan driver dalam sistem, yang berarti mengimplementasikan fungsi inisialisasi controller, dan juga menggambarkan skel_i2c_probe (dipanggil ketika driver dimuat ke dalam sistem) dan skel_i2c_remove (dipanggil saat driver dilepaskan dari sistem).
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; } 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; }
Fungsi paling sederhana adalah skel_i2c_remove, yang mematikan sumber jam dan membebaskan memori yang digunakan. Fungsi skel_i2c_init melakukan inisialisasi pengontrol I2C.
Seperti disebutkan sebelumnya, skel_i2c_probe mendaftarkan driver dalam sistem. Urutan tindakan, secara kondisional, dapat dibagi menjadi dua tahap:
- Mendapatkan sumber daya sistem dan mendaftarkan skel_i2c_isr handler interrupt;
- Mengisi bidang struktur dan memanggil prosedur untuk menambahkan adaptor I2C baru.
Setelah driver terdaftar di sistem, Anda dapat menerapkan logika transfer pesan pada antarmuka:
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; }
Langkah pertama menggambarkan interaksi aplikasi ruang pengguna dengan modul kernel sistem. Setelah kami mengimplementasikan driver internal, mudah untuk melihat antarmuka melalui pertukaran yang terjadi. Secara umum, passing pesan adalah sebagai berikut:
- skel_i2c_xfer - fungsi langsung menerima pesan untuk transmisi dan secara berurutan mentransfer setiap pesan ke skel_i2c_xfer_msg. Jika kesalahan terjadi selama transfer data, transfer data berhenti;
- skel_i2c_xfer_msg - fungsi ini mengatur semua bidang driver yang diperlukan dan memulai awal pengiriman pesan;
- skel_i2c_isr - interupsi rutin pemrosesan. Di sinilah penanganan kesalahan dan komunikasi bus berlangsung. Jika semua data dikirim / diterima, flag yang dikerjakan diatur dengan memanggil fungsi lengkap, yang menandakan penyelesaian pengiriman pesan.
Artikel ini tidak menjelaskan beberapa seluk-beluk pekerjaan. Misalnya, urutan tindakan untuk mengirim pesan, karena penerapan algoritma ini bergantung pada perangkat keras. Kami fokus pada implementasi bagian umum dari driver terlepas dari fitur perangkat keras dari controller.
Kerangka driver lengkap terlampir di bawah ini. Tolong, jika Anda menemukan kesalahan / ketidakakuratan, atau Anda memiliki sesuatu untuk ditambahkan, tulis di PM atau di komentar.
Kerangka pengemudi #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");
Terima kasih atas perhatian anda!