Escrevendo um módulo do kernel Linux: I2C

Habr, olá!

Este artigo se concentra no desenvolvimento do módulo do kernel do Linux I2C (Inter-Integrated Circuit). A seguir, descreve o processo de implementação da estrutura básica do driver I2C, na qual você pode adicionar facilmente a implementação da funcionalidade necessária.

Nós descrevemos os dados de entrada: bloco I2C para o novo processador "conectado" ao FPGA, executando a versão 3.18.19 do Linux e periféricos (EEPROM AT24C64 e BME280).

O princípio de operação do I2C é bastante simples, mas se você precisar aprimorar o conhecimento, pode ler aqui .


Figura 1. Diagrama de tempo dos sinais do barramento I2C

Antes de começar a desenvolver um driver, vamos ver como os aplicativos de espaço do usuário interagem com o módulo do kernel, para isso:

  1. Implementamos um pequeno aplicativo de espaço do usuário, cujo objetivo é ler o ID de registro I2C exclusivo do dispositivo. Esta etapa permitirá que você entenda a interface através da qual a troca ocorre entre o módulo do kernel e o aplicativo do usuário;
  2. Vamos nos familiarizar com a opção de transmitir mensagens I2C pelo módulo do kernel;
  3. Adicione o módulo do kernel ao assembly e descreva o hardware dos dispositivos na árvore de dispositivos;
  4. Implementamos a estrutura geral (esqueleto) do driver I2C com algumas explicações.

Infelizmente, não é possível anexar as fontes reais do driver desenvolvido. Além disso, quero observar que todos os nomes, nomes e cartão de registro do controlador foram alterados. Nem metade da funcionalidade desenvolvida foi incluída no esqueleto do motorista; no entanto, a estrutura do driver é um bom ponto de partida para o desenvolvimento. Exemplos de drivers I2C podem ser encontrados aqui .

Primeiro passo


Primeiro, vamos nos familiarizar com o utilitário i2cdetect. O resultado do i2cdetect é o seguinte:
./i2cdetect -y 0 0 1 2 3 4 5 6 7 8 9 abcdef 00: — — — — — — — — — — — — — 10: — — — — — — — — — — — — — — — — 20: — — — — — — — — — — — — — — — — 30: — — — — — — — — — — — — — — — — 40: — — — — — — — — — — — — — — — — 50: 50 — — — — — — — — — — — — — — — 60: — — — — — — — — — — — — — — — — 70: — — — — — — — — 

O utilitário expõe seqüencialmente o barramento de endereço do dispositivo no I2C e, ao receber uma resposta positiva (nesse caso, a resposta positiva é ACK), exibe o número do endereço do dispositivo no barramento no console.

Escreveremos um pequeno programa que lê o ID exclusivo do sensor de temperatura e exibe o resultado de seu trabalho no console. Parece muito simples:

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

Torna-se claro que o módulo do kernel recebe dados no formato de campos de mensagem i2c_rdwr_ioctl_data. A estrutura contém campos como i2c_msg e nmsgs, que são usados ​​para transmissão:

  • .addr - endereços de dispositivos;
  • .flags - tipo de operação (leitura ou gravação);
  • .len - o tamanho da mensagem atual;
  • .buf- área de transferência.

Segundo passo


Agora, não me aprofundo por dentro, familiarize-me com uma versão do driver I2C.
Como já estabelecido, o módulo do kernel recebe mensagens na forma de uma estrutura. Por exemplo, considere o algoritmo do driver ao executar uma operação de gravação (parte dependente de hardware):

  • O primeiro TX FIFO é preenchido: o endereço do dispositivo vem primeiro e, em seguida, os dados restantes são transmitidos;
  • O registro de status de interrupção ISR é limpo e as interrupções no registro IER são ativadas (nesse caso, ocorre uma interrupção quando não há dados no TX FIFO);
  • A transmissão de dados é permitida e o bit de início é definido no barramento.

Todas as trocas de dados subsequentes ocorrerão no manipulador de interrupções.
Os drivers que funcionam nesse algoritmo podem ser encontrados aqui . Além disso, o controlador pode não ter FIFO, mas apenas um único registro de transferência, mas este é um caso especial com o tamanho FIFO igual a um.

Passo três


Adicione o módulo do kernel ao assembly e descreva o hardware dos dispositivos na árvore de dispositivos:

1. Crie um arquivo de origem no seguinte diretório:

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

Como resultado, o arquivo aparece:

 drivers/i2c/busses/i2c-skel.c 


2. Adicione a configuração do driver aos drivers / 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. Adicione o driver drivers / i2c / busses / Makefile ao conjunto:

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

4. Adicione uma descrição do bloco I2C ao devicetree (* .dts) e também ofereça suporte imediato ao dispositivo 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>; }; } ; 

As etapas acima não serão consideradas em detalhes, mas leitores curiosos podem dar uma olhada aqui .

Quarto passo


Depois de se familiarizar com o princípio do driver, vamos prosseguir com a implementação.
Primeiro, conecte os arquivos de cabeçalho, descreva o cartão de registro “virtual”, bem como a apresentação do driver 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; }; 

Os principais registros de controle do controlador são:

  • Control Register (CTRL) - registro de controle;
  • Registro de status de interrupção (ISR) - registro de status de interrupção;
  • Registro de ativação de interrupção (IER) - registro de máscara de interrupção.

O coração do driver é a estrutura skel_i2c, que contém campos como:

  • .base - ponteiro para o início do cartão de registro;
  • .msg - ponteiro para a mensagem atual;
  • .adap - abstração I2C (clique) .

Vamos para a parte mais prática, descreva os tipos de dispositivos suportados pelo driver,
Funcionalidade do adaptador I2C e interface do sistema de mensagens 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"); 

A partir dos nomes de estruturas e funções, seu objetivo é óbvio, descrevemos apenas a estrutura principal acima:

  • skel_i2c_driver - descreve o nome do driver, uma tabela de dispositivos e funções suportados chamados quando o módulo do kernel é carregado ou removido do sistema.

É hora de registrar o driver no sistema, o que significa implementar a função de inicialização do controlador, e também descrever o skel_i2c_probe (chamado quando o driver é carregado no sistema) e skel_i2c_remove (chamado quando o driver é removido do sistema).

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

A função mais simples é skel_i2c_remove, que desliga a fonte do relógio e libera a memória usada. A função skel_i2c_init executa a inicialização do controlador I2C.

Como mencionado anteriormente, skel_i2c_probe registra o driver no sistema. A sequência de ações, condicionalmente, pode ser dividida em duas etapas:

  • Obtendo recursos do sistema e registrando um manipulador de interrupção skel_i2c_isr;
  • Preenchendo os campos da estrutura e chamando o procedimento para adicionar um novo adaptador I2C.

Depois que o driver é registrado no sistema, você pode implementar a lógica de transferência de mensagens na interface:

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

A primeira etapa descreveu a interação do aplicativo de espaço do usuário com o módulo do kernel do sistema. Depois de implementarmos os drivers internos, é fácil ver a interface pela qual a troca ocorre. Em geral, a passagem de mensagens é a seguinte:

  • skel_i2c_xfer - a função recebe diretamente mensagens para transmissão e transfere sequencialmente cada mensagem para skel_i2c_xfer_msg. Se ocorreu um erro durante a transferência de dados, a transferência é interrompida;
  • skel_i2c_xfer_msg - a função define todos os campos necessários do driver e inicia o início da transmissão da mensagem
  • skel_i2c_isr - interrompe a rotina de processamento. É aqui que ocorre a manipulação de erros e a comunicação de barramento. Se todos os dados forem enviados / recebidos, o sinalizador concluído é definido chamando a função completa, que sinaliza a conclusão da transmissão da mensagem.

O artigo não descreve algumas das sutilezas do trabalho. Por exemplo, a sequência de ações para o envio de mensagens, pois a implementação desse algoritmo depende do hardware. Focamos na implementação da parte geral do driver, independentemente dos recursos de hardware do controlador.

O esqueleto completo do driver está anexado abaixo. Por favor, se você encontrar erros / imprecisões ou se tiver algo a acrescentar, escreva no PM ou nos comentários.

Esqueleto do motorista
 /* 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"); 


Obrigado pela atenção!

Source: https://habr.com/ru/post/pt413249/


All Articles