Анализ подсистемы I2C драйвера Linux
Анализ подсистемы I2C драйвера Linux

I2C — это последовательная шина, широко используемая в компьютерах для связи между процессором и его периферийными устройствами.

Основные понятия об аппаратном обеспечении I2C

  • Шина I2C состоит из двух двунаправленных сигнальных линий, передающих данные, и заземляющего провода. Связь осуществляется в полудуплексном режиме «ведущий-подчиненный».
Язык кода:txt
копировать
- Serial Clock Line (SCL)
- Serial Data Address (SDA) Каждое устройство имеет уникальный адрес устройства, который передается по 8 бит за раз, причем сначала старший бит, а затем младший бит. Полная связь I2C должна пройти полную последовательность синхронизации. Полная последовательность синхронизации связи по шине I2C следующая. Как правило, нет необходимости заботиться о конкретном времени в драйвере. Вам нужно только управлять контроллером I2C в SoC. Это необходимо только тогда, когда вам нужно использовать GPIO для имитации связи I2C под «голым железом», поэтому автор это сделает. не буду подробно останавливаться на синхронизации I2C в этой статье (на самом деле это лень). O__O “…)。
  • Есть три режима скорости автобуса.
Язык кода:txt
копировать
- Стандартный режим 100kbps
- Быстрый режим 400kbps
- Высокоскоростной режим 3.4Mbps

Структура подсистемы I2C

  • Уровень драйвера устройства I2C:drivers/i2c/i2c-dev.c (Общий тип) Или драйвер устройства, настроенный для конкретного устройства (например, драйвер E2PROM).
  • Базовый уровень I2C: drivers/i2c/i2c-coere.c
  • Уровень драйвера шины I2C (уровень драйвера хост-контроллера): driver/i2c/busses/i2c-s3c2410.c

Уровень драйвера устройства I2C

  • Является ли драйвер подчиненного устройства I2C
  • Предоставьте пользователям интерфейс вызова
  • Ядро предоставляет два способа реализации драйверов устройств.:
    • Первый реализован по умолчанию в ядре.Универсальный драйвер устройства I2C,родыdrivers/i2c/i2c-dev.cсередина。 СюдаОн просто инкапсулирует основные операции I2C.,ЭквивалентноОн просто инкапсулирует базовую синхронизацию I2C.,Предоставляет только интерфейсы для базовых операций I2C на уровне приложений.,Этот интерфейс является общим для всех устройств I2C. Операции, связанные со специфическим оборудованием,Разработчики обязаны выполнять операции на устройстве на основе характеристик оборудования на уровне приложения.。Преимущество этого метода в том, что он универсален, но и недостатки очевидны. Инкапсуляция недостаточно тщательна и требует от разработчиков приложений определенной степени понимания аппаратного обеспечения.
    • Второй тип написан для конкретных устройств.Специальный драйвер устройства I2C, путьПолностью инкапсулирует аппаратные операции,Интерфейс, предоставляемый прикладному уровнюПолностью экранируйте детали связи I2C。путьизПреимущество заключается в том, что разработчикам приложений не нужно беспокоиться об оборудовании.

Базовый уровень I2C

  • Зарегистрировать шину I2C
  • Написано разработчиками ядра и не связано с конкретным оборудованием.
  • Предоставление интерфейса программирования для программистов драйверов

Уровень драйвера шины I2C

  • Это драйвер хост-адаптера I2C?
  • Инициализируйте адаптер I2C (контроллер)
  • Метод реализации: используйте контроллер I2C в соответствии с последовательностью операций I2C для отправки и получения данных.

Анализ исходного кода

Исходный код будет включать некоторый контент, связанный с SMBus. SMBus — это I2C-подобная шина, разработанная Intel на основе I2C. В этой статье не обсуждается контент, связанный с SMBus (на самом деле, откровенно говоря, я все еще ленивый QAQ). ). Автор вообще будет анализировать исходный код подсистемы I2C. Если в анализе есть какие-то неточности, хотелось бы на это указать.

Базовый уровень I2C

Базовый уровень I2Cизвыполнитьродыdrivers/i2c/i2c-core.cсередина,Автор начинает сi2c_initФункция начинает анализировать。

Язык кода:javascript
копировать
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провести анализ

Язык кода:javascript
копировать
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провести анализ。

Язык кода:javascript
копировать
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*Имя в * не будет совпадать с именем в драйвере

Язык кода:javascript
копировать
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。

Язык кода:javascript
копировать
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_add_adapter регистрирует драйвер хост-адаптера I2C (динамически назначает номер шины)
  • i2c_add_numbered_adapter регистрирует драйвер хост-адаптера I2C (статически указывает номер шины)
  • i2c_del_adapter отменяет регистрацию драйвера хост-адаптера I2C
  • i2c_add_driver регистрирует драйвер ведомого устройства I2C
  • i2c_del_driver отменяет регистрацию драйвера ведомого устройства I2C.

Другие функции пока не будут анализироваться и будут анализироваться при вызове при анализе других слоев.

Уровень драйвера устройства I2C

Автор сначала начинает с анализа универсального драйвера, предоставляемого ядром.,наконецсуществовать Конкретная информация приведена в конце статьи.водить машинуизанализировать。Ядро обеспечивает общийиз Драйвер устройства I2C,Пользователи могут реализовать драйвер I2C на уровне приложения.,Чтовыполнитьродыdrivers/i2c/i2c-dev.cсередина。Также изinitфункцияначинать,Автор начинает сi2c_dev_initФункция начинает анализировать。

Язык кода:javascript
копировать
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_driveri2cdev_driverОпределяется следующим образом。

Язык кода:javascript
копировать
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 это, естественно, станет ясно.

Уровень драйвера шины I2C

SoC, используемый автором, — S5PV210.,Его контроллер в основном такой же, как S3C2410.,Поэтому разработчики драйверов Samsung не написали еще один драйвер хост-адаптера для S5PV210.,Вместо этого используется драйвер хост-адаптера S3C2410.,Чтородыdrivers/i2c/busses/i2c-s3c2410.cсередина。

отi2c_adap_s3c_initФункция начинает анализировать。

Язык кода:javascript
копировать
static int __init i2c_adap_s3c_init(void)
{
    return platform_driver_register(&s3c24xx_i2c_driver);
}

можно увидеть Что作为平台настраивать备водить машинуивыполнить,Зарегистрированs3c24xx_i2c_driverводить машину。

Язык кода:javascript
копировать
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. Ресурсы определены следующим образом.

Язык кода:javascript
копировать
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функциясерединаруководить Понятнонравиться Внизвызов

Язык кода:javascript
копировать
/* i2c */
// Установите данные платформы I2C       NULL означает установку данных платформы по умолчанию.
s3c_i2c0_set_platdata(NULL);
s3c_i2c1_set_platdata(NULL);
s3c_i2c2_set_platdata(NULL);

сейчассуществоватьвходитьs3c_i2c0_set_platdataфункцияпровести анализ。

Язык кода:javascript
копировать
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на этом устройстве。

Язык кода:javascript
копировать
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функцияпровести анализ。 Функция проверки содержит много кода и будет анализироваться по частям.

Язык кода:javascript
копировать
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метод,Используется для отправки сообщений. Код следующий.

Язык кода:javascript
копировать
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изждатьотмашинаизсигнал подтвержденияда通过середина断выполнитьиз,То есть после того, как хост отправит данные, он переходит в режим сна и ждет ведомого устройства.,После того как ведомое устройство отвечает, оно уведомляет хост через прерывание и просыпается.

Затем функция зонда получает часы и включает их. Соответствующий код выглядит следующим образом.

Язык кода:javascript
копировать
// Получить часы
    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);

Затем приступайте к работе с конкретным вводом-выводом и прерыванием.

Язык кода:javascript
копировать
// Получить ресурсы платформы 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начальство。

Язык кода:javascript
копировать
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

Язык кода:javascript
копировать
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);,Это ожидание сна при отправке данных перед пробуждением через прерывание.

Вернитесь к функции зонда и, наконец, проанализируйте подсветку.

Язык кода:javascript
копировать
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функцияизключевая частьпровести анализ。

Язык кода:javascript
копировать
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Информация о зарегистрированной доске。

Язык кода:javascript
копировать
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;
}

Описание информации о плате в основном определяет имя ее устройства и адрес подчиненного устройства. Пример следующий.

Язык кода:javascript
копировать
#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Код выглядит следующим образом。

Язык кода:javascript
копировать
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Развернуть следующим образом

Язык кода:javascript
копировать
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метод。

Язык кода:javascript
копировать
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изнастраивать备节点。

Язык кода:javascript
копировать
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);Заканчиватьиз。

Язык кода:javascript
копировать
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функция,Вырезаем ключевые детали.

Язык кода:javascript
копировать
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 снизу вверх к процессу вызова сверху вниз,Он просто подскочил на тысячу миль, а затем снова полил.

Анализ драйверов устройств I2C

В качестве примера для объяснения автор использует драйвер E2PROM S5PV210. Посмотреть исходный кодссылка на гитхаб

Язык кода:javascript
копировать
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), а также класс и устройство (оба из которых просто используются для автоматического создания узлов устройств).

Язык кода:javascript
копировать
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функцияпровести анализ。

Язык кода:javascript
копировать
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Автоматически генерировать узлы устройств。

Язык кода:javascript
копировать
static int e2prom_open(struct inode *inode, struct file *file)
{
    return 0;
}

Метод open пуст. Метод записи используется в качестве примера для объяснения конкретной операции. Метод чтения аналогичен.

Язык кода:javascript
копировать
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функция。

Язык кода:javascript
копировать
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 Международное лицензионное соглашение Дайте разрешение. При перепечатке просьба указывать источник!

boy illustration
Учебное пособие по Jetpack Compose для начинающих, базовые элементы управления и макет
boy illustration
Код js веб-страницы, фон частицы, код спецэффектов
boy illustration
【новый! Суперподробное】Полное руководство по свойствам компонентов Figma.
boy illustration
🎉Обязательно к прочтению новичкам: полное руководство по написанию мини-программ WeChat с использованием программного обеспечения Cursor.
boy illustration
[Забавный проект Docker] VoceChat — еще одно приложение для мгновенного чата (IM)! Может быть встроен в любую веб-страницу!
boy illustration
Как реализовать переход по странице в HTML (html переходит на указанную страницу)
boy illustration
Как решить проблему зависания и низкой скорости при установке зависимостей с помощью npm. Существуют ли доступные источники npm, которые могут решить эту проблему?
boy illustration
Серия From Zero to Fun: Uni-App WeChat Payment Practice WeChat авторизует вход в систему и украшает страницу заказа, создает интерфейс заказа и инициирует запрос заказа
boy illustration
Серия uni-app: uni.navigateЧтобы передать скачок значения
boy illustration
Апплет WeChat настраивает верхнюю панель навигации и адаптируется к различным моделям.
boy illustration
JS-время конвертации
boy illustration
Обеспечьте бесперебойную работу ChromeDriver 125: советы по решению проблемы chromedriver.exe не найдены
boy illustration
Поле комментария, щелчок мышью, специальные эффекты, js-код
boy illustration
Объект массива перемещения объекта JS
boy illustration
Как открыть разрешение на позиционирование апплета WeChat_Как использовать WeChat для определения местонахождения друзей
boy illustration
Я даю вам два набора из 18 простых в использовании фонов холста Power BI, так что вам больше не придется возиться с цветами!
boy illustration
Получить текущее время в js_Как динамически отображать дату и время в js
boy illustration
Вам необходимо изучить сочетания клавиш vsCode для форматирования и организации кода, чтобы вам больше не приходилось настраивать формат вручную.
boy illustration
У ChatGPT большое обновление. Всего за 45 минут пресс-конференция показывает, что OpenAI сделал еще один шаг вперед.
boy illustration
Copilot облачной разработки — упрощение разработки
boy illustration
Микросборка xChatGPT с низким кодом, создание апплета чат-бота с искусственным интеллектом за пять шагов
boy illustration
CUDA Out of Memory: идеальное решение проблемы нехватки памяти CUDA
boy illustration
Анализ кластеризации отдельных ячеек, который должен освоить каждый&MarkerгенетическийВизуализация
boy illustration
vLLM: мощный инструмент для ускорения вывода ИИ
boy illustration
CodeGeeX: мощный инструмент генерации кода искусственного интеллекта, который можно использовать бесплатно в дополнение к второму пилоту.
boy illustration
Машинное обучение Реальный бой LightGBM + настройка параметров случайного поиска: точность 96,67%
boy illustration
Бесшовная интеграция, мгновенный интеллект [1]: платформа больших моделей Dify-LLM, интеграция без кодирования и встраивание в сторонние системы, более 42 тысяч звезд, чтобы стать свидетелями эксклюзивных интеллектуальных решений.
boy illustration
LM Studio для создания локальных больших моделей
boy illustration
Как определить количество слоев и нейронов скрытых слоев нейронной сети?
boy illustration
[Отслеживание целей] Подробное объяснение ByteTrack и детали кода