Хабр, привет!
Данная статья посвящена разработке I2C (Inter-Integrated Circuit) модуля ядра Linux. Далее описан процесс реализация базовой структуры I2C драйвера, в которую можно легко добавить реализацию необходимого функционала.
Опишем входные данные: I2C блок для нового процессора «зашитый» на ПЛИС, запущенный Linux версии 3.18.19 и периферийные устройства (EEPROM AT24C64 и BME280).
Принцип работы I2C достаточно прост, но если нужно освежить знания, то можно почитать тут.
![](https://habrastorage.org/r/w1560/webt/jq/rh/yg/jqrhyg8bveyubub7hysu0gxrkms.png)
Рисунок 1. Временная диаграмма сигналов шины I2C
Перед тем как начать разрабатывать драйвер посмотрим как user space приложения взаимодействуют с модулем ядра, для этого:
К сожалению, прикрепить реальные исходники разработанного драйвера не представляется возможным. Также, хочу заметить, все имена, названия и регистровая карта контроллера изменены. В скелет драйвера ни вошло и половины разработанного функционала, тем не менее, структура драйвера является хорошей отправной точкой при разработке. Примеры I2C драйверов можно посмотреть тут.
Для начала познакомимся с утилитой i2cdetect. Результат работы i2cdetect выглядит следующим образом:
Утилита последовательно выставляет на I2C шину адреса устройств и при получении положительного ответа (в данном случае положительным ответом является ACK) выводит в консоль номер адреса устройства на шине.
Напишем небольшую программку, которая считывает уникальный ID датчика температуры, и выведем результат ее работы в консоль. Выглядит очень просто:
Становятся понятно что модуль ядра принимает данные в виде полей сообщения i2c_rdwr_ioctl_data. Структура содержит такие поля как i2c_msg и nmsgs, которые используется для передачи:
Теперь, не углубляюсь во внутренности, познакомимся с одним вариантом работы I2C драйвера.
Как уже было установлено, модуль ядра получает сообщения в виде структуры. Для примера рассмотрим алгоритм работы драйвера при выполнении операции записи (аппаратно-зависимая часть):
Весь последующей обмен данными будет происходить в обработчике прерывания.
Драйвера, которые работают по данному алгоритму, можно найти тут. Также у контроллера может не быть FIFO, а только единственный регистр на передачу, но это частный случай с размером FIFO равным одному.
Добавим модуль ядра в сборку и опишем аппаратную часть устройств в device tree:
1. Создадим source файл в следующей директории:
В результате появится файл:
2. Добавим конфигурацию драйвера в drivers/i2c/busses/Kconfig:
3. Добавим в сборку драйвер drivers/i2c/busses/Makefile:
4. Добавим в devicetree (*.dts) описание I2C блока, а также сразу поддержу eeprom устройства:
Подробно рассматриваться выше перечисленные шаги не будут, но любопытным читателям можно заглянуть сюда.
После ознакомления с принципом работы драйвера приступим к реализации.
Сначала подключим заголовочные файлы, опишем «виртуальную» регистровую карту, а также представление драйвера I2C.
Главными управляющими регистрами контроллера являются:
Сердцем драйвера является структура skel_i2c, которая содержит такие поля как:
Перейдем к более практической части, опишем типы поддерживаемых драйвером устройств,
функционал I2C адаптера и интерфейс передачи I2C сообщений:
Из названий структур и функций очевидно их назначение, опишем только главную структуру из представленных выше:
Пора зарегистрировать драйвер в системе, а значит реализовать функцию инициализации контроллера, а также описать skel_i2c_probe (вызывается в момент загрузки драйвера в систему) и skel_i2c_remove (вызывается в момент удаления драйвера из системы).
Наиболее простой функцией является skel_i2c_remove, которая отключает источник тактовой частоты и освобождает используемую память. Функция skel_i2c_init выполняет первичную инициализацию I2C контроллера.
Как упоминалось ранее skel_i2c_probe регистрирует драйвер в системе. Последовательность действий, условно, можно разделить на два этапа:
После того как драйвер зарегистрирован в системе, можно реализовать логику передачи сообщений по интерфейсу:
В первом шаге было описано взаимодействие user space приложения с модулем ядра системы. После того как мы реализовали внутренности драйвера легко увидеть интерфейс, через который происходит обмен. В общем случае передача сообщений происходит следующем образом:
В статье не описаны некоторые тонкости работы. Например, последовательность действий передачи сообщений, так как реализация данного алгоритма является аппаратно зависимой. Мы же сосредоточились на реализации общей части драйвера вне зависимости от аппаратных особенностей контроллера.
Полный скелет драйвера прикреплен ниже. Пожалуйста, если вы нашли ошибки/неточности, или вам есть что добавить — напишите в ЛС или в комментарии.
Спасибо за ваше внимание!
Данная статья посвящена разработке I2C (Inter-Integrated Circuit) модуля ядра Linux. Далее описан процесс реализация базовой структуры I2C драйвера, в которую можно легко добавить реализацию необходимого функционала.
Опишем входные данные: I2C блок для нового процессора «зашитый» на ПЛИС, запущенный Linux версии 3.18.19 и периферийные устройства (EEPROM AT24C64 и BME280).
Принцип работы I2C достаточно прост, но если нужно освежить знания, то можно почитать тут.
![](https://habrastorage.org/webt/jq/rh/yg/jqrhyg8bveyubub7hysu0gxrkms.png)
Рисунок 1. Временная диаграмма сигналов шины I2C
Перед тем как начать разрабатывать драйвер посмотрим как user space приложения взаимодействуют с модулем ядра, для этого:
- Реализуем небольшое user space приложение, цель которого прочитать уникальный ID регистра I2C устройства. Данный шаг позволит понять интерфей, через который происходит обмен между модулем ядра и пользовательским приложением;
- Познакомимся с вариантом передачи I2C сообщений модулем ядра;
- Добавим модуль ядра в сборку и опишем аппаратную часть устройств в device tree;
- Реализуем общую структуру (скелет) I2C драйвера с небольшими пояснениями.
К сожалению, прикрепить реальные исходники разработанного драйвера не представляется возможным. Также, хочу заметить, все имена, названия и регистровая карта контроллера изменены. В скелет драйвера ни вошло и половины разработанного функционала, тем не менее, структура драйвера является хорошей отправной точкой при разработке. Примеры I2C драйверов можно посмотреть тут.
Шаг первый
Для начала познакомимся с утилитой i2cdetect. Результат работы i2cdetect выглядит следующим образом:
./i2cdetect -y 0
0 1 2 3 4 5 6 7 8 9 a b c d e f
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 равным одному.
Шаг третий
Добавим модуль ядра в сборку и опишем аппаратную часть устройств в device tree:
1. Создадим source файл в следующей директории:
cd drivers/i2c/busses/
vim i2c-skel.c
:wq
В результате появится файл:
drivers/i2c/busses/i2c-skel.c
2. Добавим конфигурацию драйвера в 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. Добавим в сборку драйвер drivers/i2c/busses/Makefile:
obj-$(CONFIG_I2C_SKEL) += i2c-skel.o
4. Добавим в devicetree (*.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;
};
Главными управляющими регистрами контроллера являются:
- Control Register (CTRL) — регистр управления;
- Interrupt Status Register (ISR) — статусный регистр прерывания;
- Interrupt Enable Register (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;
}
В первом шаге было описано взаимодействие user space приложения с модулем ядра системы. После того как мы реализовали внутренности драйвера легко увидеть интерфейс, через который происходит обмен. В общем случае передача сообщений происходит следующем образом:
- skel_i2c_xfer — функция напрямую получает сообщения на передачу и последовательно передает каждое сообщение в skel_i2c_xfer_msg. Если во время передачи данных произошла ошибка, то передача данных останавливается;
- skel_i2c_xfer_msg — функция устанавливает все необходимые поля драйвера и инициирует начало передачи сообщений;
- skel_i2c_isr — процедура обработки прерывания. Здесь происходит обработка ошибок, а также обмен данными по шине. Если все данные отправлены/приняты устанавливается флаг done с помощью вызова функции complete, которая сигнализирует о завершении передачи сообщения.
В статье не описаны некоторые тонкости работы. Например, последовательность действий передачи сообщений, так как реализация данного алгоритма является аппаратно зависимой. Мы же сосредоточились на реализации общей части драйвера вне зависимости от аппаратных особенностей контроллера.
Полный скелет драйвера прикреплен ниже. Пожалуйста, если вы нашли ошибки/неточности, или вам есть что добавить — напишите в ЛС или в комментарии.
Скелет драйвера
/* 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");
Спасибо за ваше внимание!