I2C — это последовательная шина, широко используемая в компьютерах для связи между процессором и его периферийными устройствами.
- Serial Clock Line (SCL)
- Serial Data Address (SDA) Каждое устройство имеет уникальный адрес устройства, который передается по 8 бит за раз, причем сначала старший бит, а затем младший бит. Полная связь I2C должна пройти полную последовательность синхронизации. Полная последовательность синхронизации связи по шине I2C следующая. Как правило, нет необходимости заботиться о конкретном времени в драйвере. Вам нужно только управлять контроллером I2C в SoC. Это необходимо только тогда, когда вам нужно использовать GPIO для имитации связи I2C под «голым железом», поэтому автор это сделает. не буду подробно останавливаться на синхронизации I2C в этой статье (на самом деле это лень). O__O “…)。
- Стандартный режим 100kbps
- Быстрый режим 400kbps
- Высокоскоростной режим 3.4Mbps
drivers/i2c/i2c-dev.c
середина。 СюдаОн просто инкапсулирует основные операции I2C.,ЭквивалентноОн просто инкапсулирует базовую синхронизацию I2C.,Предоставляет только интерфейсы для базовых операций I2C на уровне приложений.,Этот интерфейс является общим для всех устройств I2C. Операции, связанные со специфическим оборудованием,Разработчики обязаны выполнять операции на устройстве на основе характеристик оборудования на уровне приложения.。Преимущество этого метода в том, что он универсален, но и недостатки очевидны. Инкапсуляция недостаточно тщательна и требует от разработчиков приложений определенной степени понимания аппаратного обеспечения.。Исходный код будет включать некоторый контент, связанный с SMBus. SMBus — это I2C-подобная шина, разработанная Intel на основе I2C. В этой статье не обсуждается контент, связанный с SMBus (на самом деле, откровенно говоря, я все еще ленивый QAQ). ). Автор вообще будет анализировать исходный код подсистемы I2C. Если в анализе есть какие-то неточности, хотелось бы на это указать.
Базовый уровень I2Cизвыполнитьродыdrivers/i2c/i2c-core.c
середина,Автор начинает сi2c_init
Функция начинает анализировать。
static int __init i2c_init(void)
{
int retval;
retval = bus_register(&i2c_bus_type); // Зарегистрировать шину I2C
if (retval)
return retval;
#ifdef CONFIG_I2C_COMPAT
i2c_adapter_compat_class = class_compat_register("i2c-adapter");
if (!i2c_adapter_compat_class) {
retval = -ENOMEM;
goto bus_err;
}
#endif
retval = i2c_add_driver(&dummy_driver); // Зарегистрировал поддельный драйвер I2C
if (retval)
goto class_err;
return 0;
class_err:
#ifdef CONFIG_I2C_COMPAT
class_compat_unregister(i2c_adapter_compat_class);
bus_err:
#endif
bus_unregister(&i2c_bus_type);
return retval;
}
Эта функция сначала вызываетсяbus_register
Функция зарегистрированаI2Cавтобус,впоследствии названныйi2c_add_driver
функционировать, чтобы Зарегистрировал поддельный драйвер I2C。
Сначала зарегистрируйтесьI2Cавтобусi2c_bus_type
провести анализ
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
.pm = &i2c_device_pm_ops,
};
По принципу модели драйвера устройства Linux,Два связанных списка будут смонтированы по шине I2C.,Это цепь оборудования и приводная цепь соответственно.,Пока в один из связанных списков вставлен узел,пройдетi2c_device_match
функционировать, чтобы Перейдите еще один связанный список, чтобы найти устройствоиводить машину,После сопоставления он будет называтьсяi2c_device_probe
функция,иi2c_device_probe
Функция будет вызвана сноваi2c_driverизprobe
функция。входитьi2c_device_match
иi2c_device_probe
провести анализ。
static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
struct i2c_client *client = i2c_verify_client(dev);
struct i2c_driver *driver;
if (!client)
return 0;
driver = to_i2c_driver(drv);
/* match on an id table if there is one */
if (driver->id_table)
return i2c_match_id(driver->id_table, client) != NULL;
return 0;
}
Вы можете видеть, i2c_device_match
функциявызовиздаi2c_match_id
функционировать, чтобы Сделать матч。от Исходный кодсерединавидимый,Следует отметить, что метод сопоставления шины I2C отличается от метода сопоставления шины платформы. Соответствует только шина I2C**.id_table
*Имя в * не будет совпадать с именем в драйвере。
static int i2c_device_probe(struct device *dev)
{
struct i2c_client *client = i2c_verify_client(dev);
struct i2c_driver *driver;
int status;
if (!client)
return 0;
driver = to_i2c_driver(dev->driver);
if (!driver->probe || !driver->id_table)
return -ENODEV;
client->driver = driver;
if (!device_can_wakeup(&client->dev))
device_init_wakeup(&client->dev,
client->flags & I2C_CLIENT_WAKE);
dev_dbg(dev, "probe\n");
/* Вызов функции зонда в драйвере */
status = driver->probe(client, i2c_match_id(driver->id_table, client));
if (status) {
client->driver = NULL;
i2c_set_clientdata(client, NULL);
}
return status;
}
можно увидеть,из确давызовdriver->probe
серьезноизprobe。Следует отметить, что**if (!driver->probe || !driver->id_table) return -ENODEV;
**Средняя пара**id_table
**Выносится непустое суждение, поэтому, если вы используете метод дерева устройств для сопоставления, вам также необходимо **.id_table
**Выполните допустимое присвоение, иначе возникнет странное явление, когда сопоставление выполняется, но функция проверки не вызывается.,Лично я думаю, что это баг,В конце концов, этот базовый уровень существовал до того, как появилось дерево устройств.
вернуться вi2c_init
функция,Затем Зарегистрированныйнулевойизназванныйdummy
изi2c_driver。
static int dummy_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
return 0;
}
static int dummy_remove(struct i2c_client *client)
{
return 0;
}
static struct i2c_driver dummy_driver = {
.driver.name = "dummy",
.probe = dummy_probe,
.remove = dummy_remove,
.id_table = dummy_id,
};
можно увидеть这да一个完全нулевойиз ЛОЖЬводить машину,Неизвестно, почему Базовый уровень I2C зарегистрировал поддельный драйвер,Автор сверился с информацией в Интернете и не смог узнать.,но/sys/bus/i2c/drivers/dummy
существует,Так что автор догадывается, что его следует разрабатывать исключительно для отладки на этом уровне.
Уровень ядра также предоставляет разработчикам драйверов ряд функциональных интерфейсов для регистрации и отмены регистрации драйверов:
Другие функции пока не будут анализироваться и будут анализироваться при вызове при анализе других слоев.
Автор сначала начинает с анализа универсального драйвера, предоставляемого ядром.,наконецсуществовать Конкретная информация приведена в конце статьи.водить машинуизанализировать。Ядро обеспечивает общийиз Драйвер устройства I2C,Пользователи могут реализовать драйвер I2C на уровне приложения.,Чтовыполнитьродыdrivers/i2c/i2c-dev.c
середина。Также изinitфункцияначинать,Автор начинает сi2c_dev_init
Функция начинает анализировать。
static int __init i2c_dev_init(void)
{
int res;
printk(KERN_INFO "i2c /dev entries driver\n");
/* Зарегистрируйте универсальный драйвер как драйвер символьного устройства и укажите file_operations. Как работать */
res = register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);
if (res)
goto out;
/* Создать класс */
i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");
if (IS_ERR(i2c_dev_class)) {
res = PTR_ERR(i2c_dev_class);
goto out_unreg_chrdev;
}
/* Зарегистрируйте драйвер ведомого устройства I2C */
res = i2c_add_driver(&i2cdev_driver);
if (res)
goto out_unreg_class;
return 0;
out_unreg_class:
class_destroy(i2c_dev_class);
out_unreg_chrdev:
unregister_chrdev(I2C_MAJOR, "i2c");
out:
printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);
return res;
}
i2c_dev_init
функция先давызов Понятноregister_chrdev
Функция зарегистрированаустройство персонажаводить машину,и предоставляет файл file_operations. Видно, что,Это реализация универсального драйвера как драйвера символьного устройства.,и своимfile_operationsСтруктураизметод Обеспечивает общий интерфейс для прикладного уровня.。а потом позвониclass_create
Создал класс,номожно увидетьи не позвонилdevice_create
Создать устройства в этом классе,Обратите внимание, что здесь не создается узел устройства.。последний звонокi2c_add_driver
ЗарегистрированныйI2CДрайвер ведомого устройстваi2cdev_driver
。i2cdev_driver
Определяется следующим образом。
static struct i2c_driver i2cdev_driver = {
.driver = {
.name = "dev_driver",
},
.attach_adapter = i2cdev_attach_adapter,
.detach_adapter = i2cdev_detach_adapter,
};
сверхуможно увидетьне правильноid_table
Сделать задание,Из приведенного выше анализа Базового уровня I2C,I2Cавтобусдав соответствии сid_table
Сделать матч,Таким образом, проверка после совпадения не будет выполняться в соответствии с традиционной моделью драйверов Linux.,Более того, в этом драйвере нет метода зондирования. Так что же именно происходит? Не паникуйте,Хотя нетid_tableиprobe,но它单独поставлять Понятно两个методattach_adapter
иdetach_adapter
。Вот предзнаменование в первую очередь,Нет анализа,приезжать Уровень драйвера шины После анализа I2C это, естественно, станет ясно.
SoC, используемый автором, — S5PV210.,Его контроллер в основном такой же, как S3C2410.,Поэтому разработчики драйверов Samsung не написали еще один драйвер хост-адаптера для S5PV210.,Вместо этого используется драйвер хост-адаптера S3C2410.,Чтородыdrivers/i2c/busses/i2c-s3c2410.c
середина。
отi2c_adap_s3c_init
Функция начинает анализировать。
static int __init i2c_adap_s3c_init(void)
{
return platform_driver_register(&s3c24xx_i2c_driver);
}
можно увидеть Что作为平台настраивать备водить машинуивыполнить,Зарегистрированs3c24xx_i2c_driver
водить машину。
static struct platform_device_id s3c24xx_driver_ids[] = {
{
.name = "s3c2410-i2c",
.driver_data = TYPE_S3C2410,
}, {
.name = "s3c2440-i2c",
.driver_data = TYPE_S3C2440,
}, { },
};
MODULE_DEVICE_TABLE(platform, s3c24xx_driver_ids);
static struct platform_driver s3c24xx_i2c_driver = {
.probe = s3c24xx_i2c_probe,
.remove = s3c24xx_i2c_remove,
.id_table = s3c24xx_driver_ids,
.driver = {
.owner = THIS_MODULE,
.name = "s3c-i2c",
.pm = S3C24XX_DEV_PM_OPS,
},
};
По принципу платформенного автобуса,Легко узнать, что вarch/arm/mach-s5pv210/mach-x210.c
серединаверно Чтоводить машинупереписыватьсяизнастраивать备руководить Понятнозарегистрироваться,Чтозарегистрироватьсяиз Определение устройствародыdev-i2c0.c
,Это файл ресурсов для I2C. Ресурсы определены следующим образом.
static struct resource s3c_i2c_resource[] = {
[0] = {
.start = S3C_PA_IIC,
.end = S3C_PA_IIC + SZ_4K - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_IIC,
.end = IRQ_IIC,
.flags = IORESOURCE_IRQ,
},
};
struct platform_device s3c_device_i2c0 = {
.name = "s3c2410-i2c",
.id = 0,
.num_resources = ARRAY_SIZE(s3c_i2c_resource),
.resource = s3c_i2c_resource,
};
Это можно узнать по названию,иs3c24xx_i2c_driver
да匹配из。кроме,Данные платформы также определеныdefault_i2c_data0
иdefault_i2c_data0
функция。Что Связанныйизвызоввозвращатьсядасуществоватьarch/arm/mach-s5pv210/mach-x210.c
серединаруководитьиз,существоватьmach-x210.c
серединаизsmdkc110_machine_init
функциясерединаруководить Понятнонравиться Внизвызов
/* i2c */
// Установите данные платформы I2C NULL означает установку данных платформы по умолчанию.
s3c_i2c0_set_platdata(NULL);
s3c_i2c1_set_platdata(NULL);
s3c_i2c2_set_platdata(NULL);
сейчассуществоватьвходитьs3c_i2c0_set_platdata
функцияпровести анализ。
static struct s3c2410_platform_i2c default_i2c_data0 __initdata = {
.flags = 0,
.slave_addr = 0x10, // Адрес, используемый контроллером I2C при работе в качестве ведомого устройства.
.frequency = 400*1000, // 400kbps
.sda_delay = S3C2410_IICLC_SDA_DELAY15 | S3C2410_IICLC_FILTER_ON, // Интервальное время
};
void __init s3c_i2c0_set_platdata(struct s3c2410_platform_i2c *pd)
{
struct s3c2410_platform_i2c *npd;
if (!pd) // Если параметр имеет значение NULL, будут использоваться данные платформы по умолчанию, определенные выше функции.
pd = &default_i2c_data0;
npd = kmemdup(pd, sizeof(struct s3c2410_platform_i2c), GFP_KERNEL);
if (!npd)
printk(KERN_ERR "%s: no memory for platform data\n", __func__);
else if (!npd->cfg_gpio)
npd->cfg_gpio = s3c_i2c0_cfg_gpio; // Метод инициализации GPIO
// Установить как данные платформы
s3c_device_i2c0.dev.platform_data = npd;
}
Вы можете видеть, что при передаче NULL используются данные платформы по умолчанию. Воляs3c_i2c0_cfg_gpio
функциянастраиватьприезжать Понятно Данные платформыcfg_gpio
методсередина,наконец Воля Перехват данных платформыприезжатьs3c_device_i2c0
на этом устройстве。
void s3c_i2c0_cfg_gpio(struct platform_device *dev)
{
s3c_gpio_cfgpin(S5PV210_GPD1(0), S3C_GPIO_SFN(2)); // Установите регистр управления в режим I2C0_SDA.
s3c_gpio_setpull(S5PV210_GPD1(0), S3C_GPIO_PULL_NONE);
s3c_gpio_cfgpin(S5PV210_GPD1(1), S3C_GPIO_SFN(2)); // Установите регистр управления в режим I2C0_SCL.
s3c_gpio_setpull(S5PV210_GPD1(1), S3C_GPIO_PULL_NONE);
}
можно увидетьs3c_i2c0_cfg_gpio
функция只даверноI2CДве линии связи для контроллераизGPIOинициализация。
Далее вернитесь к Уровень драйвера шины I2Ci2c-s3c2410.c
середина, Входитьприезжатьs3c24xx_i2c_probe
функцияпровести анализ。 Функция проверки содержит много кода и будет анализироваться по частям.
struct s3c24xx_i2c *i2c;
struct s3c2410_platform_i2c *pdata;
struct resource *res;
int ret;
// Получить данные платформы I2C
pdata = pdev->dev.platform_data;
if (!pdata) {
dev_err(&pdev->dev, "no platform data\n");
return -EINVAL;
}
i2c = kzalloc(sizeof(struct s3c24xx_i2c), GFP_KERNEL);
if (!i2c) {
dev_err(&pdev->dev, "no memory for state\n");
return -ENOMEM;
}
strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name));
i2c->adap.owner = THIS_MODULE;
i2c->adap.algo = &s3c24xx_i2c_algorithm; // I2Cхост-контроллериз Как работать
i2c->adap.retries = 2;
i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
i2c->tx_setup = 50;
Samsung принимаетstruct s3c24xx_i2c
Структура получается правильнаяSoCизконтроллерруководитьабстрактный,Эта структура наследуется отstruct i2c_adapter
。Должен段代码先даотdeviceсередина获取Понятно Данные платформы,Данные платформы указаны выше.s3c_i2c0_set_platdata
функция时настраиватьиз。затем, чтобыi2c->adap
Были даны соответствующие поручения,Ключевая частьi2c->adap.algo = &s3c24xx_i2c_algorithm;
,adap.algo
выражатьI2Cхост-контроллериз Как работать,Воля ДолженSoCиз Как работатьмонтироватьприезжать Понятноадаптерначальство。s3c24xx_i2c_algorithm
Два определены Как работать,В основномmaster_xfer
метод,Используется для отправки сообщений. Код следующий.
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
.master_xfer = s3c24xx_i2c_xfer,
.functionality = s3c24xx_i2c_func,
};
s3c24xx_i2c_xfer
с участиемприезжатьконкретному контроллеруиздействовать,Не расширяйте,но Уведомлениеизда Чтовнутреннийвызовиздаs3c24xx_i2c_doxfer
,существоватьs3c24xx_i2c_doxfer
функция После отправки данных внутри,вызовwait_event_timeout
функционировать, чтобыруководить睡眠ждатьотответ машины。поэтому Это может быть известно ЯдросерединаI2Cизждатьотмашинаизсигнал подтвержденияда通过середина断выполнитьиз,То есть после того, как хост отправит данные, он переходит в режим сна и ждет ведомого устройства.,После того как ведомое устройство отвечает, оно уведомляет хост через прерывание и просыпается.
Затем функция зонда получает часы и включает их. Соответствующий код выглядит следующим образом.
// Получить часы
i2c->clk = clk_get(&pdev->dev, "i2c");
if (IS_ERR(i2c->clk)) {
dev_err(&pdev->dev, "cannot get clock\n");
ret = -ENOENT;
goto err_noclk;
}
dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk);
// включить часы
clk_enable(i2c->clk);
Затем приступайте к работе с конкретным вводом-выводом и прерыванием.
// Получить ресурсы платформы I2C (адрес памяти ввода-вывода, IRQ)
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res == NULL) {
dev_err(&pdev->dev, "cannot find IO resource\n");
ret = -ENOENT;
goto err_clk;
}
i2c->ioarea = request_mem_region(res->start, resource_size(res),
pdev->name);
if (i2c->ioarea == NULL) {
dev_err(&pdev->dev, "cannot request IO\n");
ret = -ENXIO;
goto err_clk;
}
// Сопоставить физический адрес с виртуальным адресом
i2c->regs = ioremap(res->start, resource_size(res));
if (i2c->regs == NULL) {
dev_err(&pdev->dev, "cannot map IO\n");
ret = -ENXIO;
goto err_ioarea;
}
dev_dbg(&pdev->dev, "registers %p (%p, %p)\n",
i2c->regs, i2c->ioarea, res);
/* setup info block for the i2c core */
i2c->adap.algo_data = i2c;
i2c->adap.dev.parent = &pdev->dev;
/* initialise the i2c controller */
// Инициализируйте контроллер I2C
ret = s3c24xx_i2c_init(i2c);
if (ret != 0)
goto err_iomap;
// Получить ресурс IRQ
i2c->irq = ret = platform_get_irq(pdev, 0);
if (ret <= 0) {
dev_err(&pdev->dev, "cannot find IRQ\n");
goto err_iomap;
}
// Подать заявку на IRQ (Голое железо обычно использует метод запроса для определения ответа ведомого устройства, в то время как ядро обычно использует прерывания для ожидания ответа ведомого устройства)
ret = request_irq(i2c->irq, s3c24xx_i2c_irq, IRQF_DISABLED,
dev_name(&pdev->dev), i2c);
Пучоксосредоточиться на Нажмите и отпуститесуществовать Инициализируйте контроллер I2Cизs3c24xx_i2c_init
функцияи Подать заявку на IRQначальство。
static int s3c24xx_i2c_init(struct s3c24xx_i2c *i2c)
{
unsigned long iicon = S3C2410_IICCON_IRQEN | S3C2410_IICCON_ACKEN;
struct s3c2410_platform_i2c *pdata;
unsigned int freq;
/* get the plafrom data */
pdata = i2c->dev->platform_data;
/* inititalise the gpio */
if (pdata->cfg_gpio)
pdata->cfg_gpio(to_platform_device(i2c->dev)); // Установите соответствующий вывод I2C
/* write slave address */
// Установите адрес контроллера I2C как ведомого устройства.
writeb(pdata->slave_addr, i2c->regs + S3C2410_IICADD);
dev_dbg(i2c->dev, "slave address 0x%02x\n", pdata->slave_addr);
writel(iicon, i2c->regs + S3C2410_IICCON); // давать возможность Tx/Rx Interrupt и сигнал подтверждения
/* we need to work out the divisors for the clock... */
// Настройка тактовой частоты I2C
if (s3c24xx_i2c_clockrate(i2c, &freq) != 0) {
writel(0, i2c->regs + S3C2410_IICCON);
dev_err(i2c->dev, "cannot meet bus frequency required\n");
return -EINVAL;
}
/* todo - check that the i2c lines aren't being dragged anywhere */
dev_dbg(i2c->dev, "bus frequency set to %d KHz\n", freq);
dev_dbg(i2c->dev, "S3C2410_IICCON=0x%02lx\n", iicon);
dev_dbg(i2c->dev, "S3C2440_IICLC=%08x\n", pdata->sda_delay);
writel(pdata->sda_delay, i2c->regs + S3C2440_IICLC);
return 0;
}
можно увидеть Установите соответствующий вывод I2Cдавызов Данные платформысерединаизcfg_gpio
,Что实看приезжать Если здесь еще осталось впечатлениеиз话就能反应出Приходить这дасуществоватьвызовs3c_i2c0_set_platdata
серединанастраиватьиз。Долженфункциявозвращатьсянастраивать ПонятноI2Cконтроллеризотадрес,Этот адрес используется, когда контроллер используется в качестве подчиненного адреса.,ноэта ситуацияиз出сейчас微乎Что微。кромедавать возможностьTx/Rx Сигналы прерывания и подтверждения настраивают тактовую частоту I2C.
Обратите внимание, что из анализа предыдущего абзаца мы знаем,I2C в ядре использует режим прерывания для ожидания ответа ведомого устройства.,такprobeфункция Этот фрагмент кодасередина申请ПонятноIRQи связансерединаобработка прерыванийфункцияs3c24xx_i2c_irq
。
static irqreturn_t s3c24xx_i2c_irq(int irqno, void *dev_id)
{
struct s3c24xx_i2c *i2c = dev_id;
unsigned long status;
unsigned long tmp;
// Получить значение регистра I2CSTAT
status = readl(i2c->regs + S3C2410_IICSTAT);
if (status & S3C2410_IICSTAT_ARBITR) { // Арбитраж шины I2C не удался
/* deal with arbitration loss */
dev_err(i2c->dev, "deal with arbitration loss\n");
}
if (i2c->state == STATE_IDLE) {
dev_dbg(i2c->dev, "IRQ: error i2c->state == IDLE\n");
tmp = readl(i2c->regs + S3C2410_IICCON);
tmp &= ~S3C2410_IICCON_IRQPEND;
writel(tmp, i2c->regs + S3C2410_IICCON);
goto out;
}
/* pretty much this leaves us with the fact that we've
* transmitted or received whatever byte we last sent */
// Обработка отправки и получения данных I2C
i2c_s3c_irq_nextbyte(i2c, status);
out:
return IRQ_HANDLED;
}
Подробный анализ проводиться не будет.,нохотеть Уведомлениеизда有这么一条线:Долженсерединаобработка прерыванийфункциявызов Понятноi2c_s3c_irq_nextbyte
,Затемвнутреннийвызов Понятноs3c24xx_i2c_stop
,снова внутривызов Понятноs3c24xx_i2c_master_complete
,Наконец, код ключа выполняется внутриwake_up(&i2c->wait);
,Это ожидание сна при отправке данных перед пробуждением через прерывание.
Вернитесь к функции зонда и, наконец, проанализируйте подсветку.
ret = i2c_add_numbered_adapter(&i2c->adap);
if (ret < 0) {
dev_err(&pdev->dev, "failed to add bus to i2c core\n");
goto err_cpufreq;
}
Должен代码ВоляI2Cадаптерзарегистрироватьсяприезжать Понятно Ядросередина。i2c_add_numbered_adapter
функция Предоставляется базовым слоем,Чтоопределениероды Базовый уровень I2Cdrivers/i2c/i2c-core.c
середина,используется для регистрацииI2Cадаптер。Что实существовать Ядросерединапоставлять Понятно两个adapterИнтерфейс регистрации,соответственноi2c_add_adapter
иi2c_add_numbered_adapter
потому чтосуществоватьсистемасередина Может существоватьсуществоватьнесколькоadapter, Следовательно, каждой шине I2C (контроллеру) соответствует номер. Этот номер шины (этот номер можно назвать номером шины) отличается от номера шины в PCI. К аппаратному обеспечению это не имеет никакого отношения. 只да软件начальстволегко отличитьиуже。дляi2c_add_adapter
и Слово, Он использует динамический номер шины, То есть система присваивает ему номер автобуса. иi2c_add_numbered_adapter
则да自己指定автобус Число, Если этот номер автобуса незаконен или занят, Регистрация не удастся。неважно какой Интерфейс регистрации,Что核心都давызовi2c_register_adapter
функционировать, чтобыруководитьнастоящийиззарегистрироваться。выигратьi2c_register_adapter
функцияизключевая частьпровести анализ。
res = device_register(&adap->dev);
if (adap->nr < __i2c_first_dynamic_bus_num)
i2c_scan_static_board_info(adap);
dummy = bus_for_each_drv(&i2c_bus_type, NULL, adap,
__process_new_adapter);
device_register(&adap->dev);
выражать主машинаадаптерadapterиззарегистрироваться。
i2c_scan_static_board_info(adap);
Сначала пройдите внутренний путь__i2c_board_list
выиграть Информация о плате(описыватьизда板子начальствоизI2Cснаружинастраиватьизинформация,То есть информация подчиненного устройства I2C),Должен链表из生成дасуществоватьarch/arm/mach-s5pv210/mach-x210.c
серединаруководитьиз,существоватьmach-x210.c
серединаизsmdkc110_machine_init
функциясерединаруководить Понятно除之前анализироватьизвызовs3c_i2c0_set_platdata
снаружи,возвращатьсявызов Понятноi2c_register_board_info
Информация о зарегистрированной доске。
int __init
i2c_register_board_info(int busnum,
struct i2c_board_info const *info, unsigned len)
{
int status;
down_write(&__i2c_board_lock);
/* dynamic bus numbers will be assigned after the last static one */
// __i2c_first_dynamic_bus_num — это глобальная переменная, которая не инициализируется явно, поэтому при первом входе в эту функцию ее значение равно 0.
if (busnum >= __i2c_first_dynamic_bus_num)
__i2c_first_dynamic_bus_num = busnum + 1;
for (status = 0; len; len--, info++) {
struct i2c_devinfo *devinfo;
devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
if (!devinfo) {
pr_debug("i2c-core: can't register boardinfo!\n");
status = -ENOMEM;
break;
}
devinfo->busnum = busnum;
devinfo->board_info = *info;
list_add_tail(&devinfo->list, &__i2c_board_list); // Управление board_info с помощью связанного списка
}
up_write(&__i2c_board_lock);
return status;
}
Описание информации о плате в основном определяет имя ее устройства и адрес подчиненного устройства. Пример следующий.
#define I2C_BOARD_INFO(dev_type, dev_addr) \
.type = dev_type, .addr = (dev_addr)
#ifdef CONFIG_TOUCHSCREEN_GSLX680
{
I2C_BOARD_INFO("gslX680", 0x40), // В основном из-за названия устройстваиотадрес Сделать задание
},
#endif
Затемсуществоватьi2c_scan_static_board_info
внутренний利用Информация о плате作为原料вызовi2c_new_device
создатьclient,Указывает подчиненное устройство,и Воляadapterмонтироватьприезжать ПонятноclientСтруктуравнутреннийизуказательначальство。i2c_scan_static_board_info
Код выглядит следующим образом。
static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
{
struct i2c_devinfo *devinfo;
down_read(&__i2c_board_lock);
// __i2c_board_list связывается при вызове i2c_register_board_info
list_for_each_entry(devinfo, &__i2c_board_list, list) {
if (devinfo->busnum == adapter->nr
&& !i2c_new_device(adapter,
&devinfo->board_info))
dev_err(&adapter->dev,
"Can't create device at 0x%02x\n",
devinfo->board_info.addr);
}
up_read(&__i2c_board_lock);
}
После создания клиента,вернуться вi2c_register_adapter
функция,Наконец выполненоdummy = bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);
,Эта функция обходит драйверы, зарегистрированные на шине I2C, и использует обратные вызовы**__process_new_adapter
**Функциональный метод, после перехода к универсальному драйверу i2c-dev он будет использоваться**i2cdev_attach_adapter
**Метод подключения к драйверу символьного устройства, зарегистрированному в i2c-dev, и использования основного номера устройства этого драйвера символьного устройства и номера шины в адаптере (в качестве младшего номера устройства) для создания узла устройства с именем i2c-x. , После того, как уровень приложения получит доступ к этому узлу устройства, Вызовите метод операции в file_operations, зарегистрированный в i2c-dev. Из исходного кода метода операции конечные вызовы чтения и записи — это методы чтения и записи в адаптере (то есть метод, определенный в i2c-s3c2410.c в). эта платформа)。Проверьте это ниже。
__process_new_adapter
Развернуть следующим образом
static int i2c_do_add_adapter(struct i2c_driver *driver,
struct i2c_adapter *adap)
{
/* Detect supported devices on that bus, and instantiate them */
i2c_detect(adap, driver);
/* Let legacy drivers scan this bus for matching devices */
if (driver->attach_adapter) {
/* We ignore the return code; if it fails, too bad */
driver->attach_adapter(adap); // Вызовите метод i2cdev_attach_adapter в i2c-dev.
}
return 0;
}
static int __process_new_adapter(struct device_driver *d, void *data)
{
return i2c_do_add_adapter(to_i2c_driver(d), data);
}
можно увидетьdriver->attach_adapter(adap);
,из确давызовI2Cавтобус Внизизводить машинусерединаизattach_adapter
метод,приезжать Понятно这里существовать Уровень драйвера устройства Интрига, похороненная I2C, наконец-то выходит на свет (это непросто), путешествие во времени обратно на Уровень драйвера устройства I2Cпровести анализ,Входитьdrivers/i2c/i2c-dev.c
анализироватьi2cdev_attach_adapter
метод。
static int i2cdev_attach_adapter(struct i2c_adapter *adap)
{
struct i2c_dev *i2c_dev;
int res;
i2c_dev = get_free_i2c_dev(adap);
if (IS_ERR(i2c_dev))
return PTR_ERR(i2c_dev);
/* register this i2c device with the driver core */
/* Использовать основной номер устройстваиadapterсерединаизавтобус Число(как младший номер устройства)чтобы создать файл с именемi2c-xизнастраивать备节点 */
i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,
MKDEV(I2C_MAJOR, adap->nr), NULL,
"i2c-%d", adap->nr);
if (IS_ERR(i2c_dev->dev)) {
res = PTR_ERR(i2c_dev->dev);
goto error;
}
res = device_create_file(i2c_dev->dev, &dev_attr_name);
if (res)
goto error_destroy;
pr_debug("i2c-dev: adapter [%s] registered as minor %d\n",
adap->name, adap->nr);
return 0;
error_destroy:
device_destroy(i2c_dev_class, MKDEV(I2C_MAJOR, adap->nr));
error:
return_i2c_dev(i2c_dev);
return res;
}
i2c_dev->dev = device_create(i2c_dev_class, &adap->dev, MKDEV(I2C_MAJOR, adap->nr), NULL, "i2c-%d", adap->nr);
Использовать основной номер устройстваиadapterсерединаизавтобус Число(как младший номер устройства)чтобы создать файл с именемi2c-xизнастраивать备节点。
static ssize_t i2cdev_write(struct file *file, const char __user *buf,
size_t count, loff_t *offset)
{
int ret;
char *tmp;
// Удалить i2c_client
struct i2c_client *client = file->private_data;
if (count > 8192)
count = 8192;
tmp = kmalloc(count, GFP_KERNEL);
if (tmp == NULL)
return -ENOMEM;
// Скопируйте пользовательские данные в пространство ядра
if (copy_from_user(tmp, buf, count)) {
kfree(tmp);
return -EFAULT;
}
pr_debug("i2c-dev: i2c-%d writing %zu bytes.\n",
iminor(file->f_path.dentry->d_inode), count);
// Отправить данные I2C
ret = i2c_master_send(client, tmp, count);
kfree(tmp);
return ret;
}
Возьмем функцию записи в качестве примера,можно увидеть Запись данных черезret = i2c_master_send(client, tmp, count);
Заканчиватьиз。
int i2c_master_send(struct i2c_client *client, const char *buf, int count)
{
int ret;
// Получить адаптер I2C
struct i2c_adapter *adap = client->adapter;
struct i2c_msg msg;
// Инкапсулированный пакет I2C
msg.addr = client->addr;
msg.flags = client->flags & I2C_M_TEN; // Отправить флаг
msg.len = count;
msg.buf = (char *)buf;
// Отправить данные пакет I2C
ret = i2c_transfer(adap, &msg, 1);
/* If everything went ok (i.e. 1 msg transmitted), return #bytes
transmitted, else error code. */
return (ret == 1) ? count : ret;
}
можно увидеть,После инкапсуляции пакетов данных I2C,настоящийизфинальный Запись данных черезret = i2c_transfer(adap, &msg, 1);
Заканчиватьиз。Входитьприезжатьi2c_transfer
функция,Вырезаем ключевые детали.
for (ret = 0, try = 0; try <= adap->retries; try++) {
// Вызовите метод отправки драйвера шины I2C конкретного SoC.
ret = adap->algo->master_xfer(adap, msgs, num);
if (ret != -EAGAIN)
break;
if (time_after(jiffies, orig_jiffies + adap->timeout))
break;
}
Вас не видно на извилистой горной дороге, оставляющей место для прогулки лошадям в небе над снегом.
adap->algo->master_xfer(adap, msgs, num);
окончательновернуться в Понятно原点见приезжать Понятно Уровень драйвера шины I2Cсерединаопределениеиз Как работать。
можно увидетьпроцессиз Действительноначальство Что сказано в статье,Показано какОт уровня драйвера шины I2C снизу вверх к процессу вызова сверху вниз,Он просто подскочил на тысячу миль, а затем снова полил.
В качестве примера для объяснения автор использует драйвер E2PROM S5PV210. Посмотреть исходный кодссылка на гитхаб。
struct e2prom_device {
struct i2c_client *at24c02_client; /* I2C клиент (ведомое устройство) */
/* classиdevice используется для автоматического создания узлов устройств */
struct class *at24c02_class;
struct device *at24c02_device;
};
struct e2prom_device *e2prom_dev;
Инкапсулироватьe2prom_device
СтруктуравыражатьверноE2PROMизабстрактный,Чтосередина ВключатьI2C клиент (используется для представления подчиненных устройств I2C), а также класс и устройство (оба из которых просто используются для автоматического создания узлов устройств).
struct i2c_device_id e2prom_table[] = {
[0] = {
.name = "24c02",
.driver_data = 0,
},
[1] = {
.name = "24c08",
.driver_data = 0,
},
};
/* Драйвер устройства I2C */
struct i2c_driver e2prom_driver = {
.probe = e2prom_probe,
.remove = e2prom_remove,
.id_table = e2prom_table,
.driver = {
.name = "e2prom",
},
};
static int __init e2prom_init(void)
{
return i2c_add_driver(&e2prom_driver); /* зарегистрироваться Драйвер устройства I2C */
}
先давызовi2c_add_driver
зарегистрироваться Драйвер устройства I2C。в соответствии сначальствоискусствосуществовать Базовый уровень I2Cиз Анализ исходного Код Познаваемый,пройдетсуществоватьосновной слойсерединазарегистрироватьсяизi2c_bus_type
Внизизi2c_device_match
функционировать, чтобы匹配настраивать备иводить машину,После сопоставления он будет называться Чтоi2c_device_probe
функция,иi2c_device_probe
Функция будет вызвана сноваi2c_driverизprobeфункция。Уведомлениенравитьсяначальствоискусствоанализироватьзнать,Исходный материал, сгенерированный клиентом, — board_info.,Итак, чтобы этот драйвер успешно соответствовал,需хотетьсуществоватьarch/arm/mach-s5pv210/mach-x210.c
серединаиспользоватьi2c_register_board_info
Приходитьзарегистрироватьсяboard_info。Идите прямоprobфункцияпровести анализ。
struct file_operations e2prom_fops = {
.owner = THIS_MODULE,
.open = e2prom_open,
.write = e2prom_write,
.read = e2prom_read,
};
static int e2prom_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret;
printk(KERN_INFO "e2prom probe!\n");
e2prom_dev = kmalloc(sizeof(struct e2prom_device), GFP_KERNEL);
if (!e2prom_dev) {
printk(KERN_ERR "malloc failed!\n");
return -ENOMEM;
}
e2prom_dev->at24c02_client = client;
/* Зарегистрируйтесь как драйвер символьного устройства */
ret = register_chrdev(E2PROM_MAJOR, "e2prom_module", &e2prom_fops);
if (ret < 0) {
printk(KERN_ERR "malloc failed\n");
ret = -ENOMEM;
goto err0;
}
/* Создать класс */
e2prom_dev->at24c02_class = class_create(THIS_MODULE, "e2prom_class");
if (IS_ERR(e2prom_dev->at24c02_class)) {
printk(KERN_ERR "class create failed!\n");
ret = PTR_ERR(e2prom_dev->at24c02_class);
goto err1;
}
/* Создать устройство в классе */
e2prom_dev->at24c02_device = device_create(e2prom_dev->at24c02_class, NULL, MKDEV(E2PROM_MAJOR, 0), NULL, "at24c08");
if (IS_ERR(e2prom_dev->at24c02_device)) {
printk(KERN_ERR "class create failed!\n");
ret = PTR_ERR(e2prom_dev->at24c02_device);
goto err1;
}
return 0;
err1:
unregister_chrdev(E2PROM_MAJOR, "e2prom_module");
err0:
kfree(e2prom_dev);
ret
существоватьprobeфункциясерединавызовregister_chrdev
функционировать, чтобы ВоляE2PROMводить машинузарегистрироваться为Понятно字符настраивать备водить машину,и связанfops。а потом позвониclass_create
иdevice_create
Автоматически генерировать узлы устройств。
static int e2prom_open(struct inode *inode, struct file *file)
{
return 0;
}
Метод open пуст. Метод записи используется в качестве примера для объяснения конкретной операции. Метод чтения аналогичен.
static ssize_t e2prom_write(struct file *file, const char __user *buf,
size_t size, loff_t *offset)
{
int ret = 0;
char *tmp;
tmp = kmalloc(size, GFP_KERNEL);
if (tmp == NULL) {
printk(KERN_ERR "mallo failed!\n");
return -ENOMEM;
}
/* Скопируйте данные пользовательского пространства в пространство ядра */
ret = copy_from_user(tmp, buf, size);
if (ret) {
printk("copy data faile!\n");
goto err0;
}
/* I2C write */
ret = i2c_write_byte(tmp, size);
if (ret) {
printk(KERN_ERR "wrtie byte failed!\n");
goto err0;
}
kfree(tmp);
return size;
err0:
kfree(tmp);
return -EINVAL;
}
можно увидетьнастоящийиздействоватьI2Cсуществоватьi2c_write_byte
функция。
static int i2c_write_byte(char *buf, int count)
{
int ret = 0;
struct i2c_msg msg;
/* Инкапсулированный пакет I2C */
msg.addr = e2prom_dev->at24c02_client->addr; /* Адрес подчиненного устройства I2C */
msg.flags = 0; /* write flag */
msg.len = count; /* Длина данных */
msg.buf = buf; /* письменные данные */
/* вызов Базовый уровень I2Cпоставлятьизпередача инфекциифункция,Что本质возвращатьсядавызовизI2Cавтобусводить машину(хост-контроллерводить машину)层Внизвыполнитьизalgo->master_xfeметод */
ret = i2c_transfer(e2prom_dev->at24c02_client->adapter, &msg, 1);
if (ret < 0) {
printk(KERN_ERR "i2c transfer failed!\n");
return -EINVAL;
}
return ret;
}
можно увидетьдавызовсуществовать Базовый уровень I2Cпоставлятьизпередача инфекциифункция,Что本质возвращатьсядасуществоватьпередача инфекциифункциявнутреннийвызов Понятно跟具体SoCСвязанныйизI2Cхост-контроллер Как работатьсерединаизпередача инфекцииметод。Долженфункция接口需хотетьпоставлять一个i2c_msg
,Итак, он создан и заполнен,Уведомлениеmsg.flags = 0;
середина0выражать Писать,1 означает чтение.
Наконец-то разбросайте цветы! ! ! ✿✿✿~
Автор этой статьи: Ifan Tsai (кай-кай)
Ссылка на эту статью: https://cloud.tencent.com/developer/article/2164591
Заявление об авторских правах: Эта статья принимает Creative Commons Attribution-NonCommercial-ShareAlike 4.0 Международное лицензионное соглашение Дайте разрешение. При перепечатке просьба указывать источник!