5.3.2 Архитектура программного обеспечения I2C

Предыдущая  Содержание  Следующая V*D*V

Подсистема I2C появилась после большой реконструкции, с выходом ядра версии 2.6. В этом разделе мы обсудим архитектуру I2C в ядре версии 2.6. Хотя эта шина сама по себе очень проста, архитектура подсистемы I2C в Linux является довольно сложной и лучше всего её можно понять на примере.

Предположим, что ваша плата использует I2C, как показано на Рисунке 5.8, который показывает на плате две шины I2C; каждая шина I2C находится под управлением адаптера шины I2C, подобного PCF8584, который действует как ведущее устройство I2C для данной шины и, кроме того, работает как интерфейс между шиной процессора и шиной I2C. Таким образом, процессор может обращаться к любому из устройств I2C на шинах I2C путём программирования этих адаптеров. К первой шине I2C подключены две микросхемы EEPROM, а к другой шине I2C подключена микросхема RTC.

 

Рисунок 5.8 Пример топологии шины I2C.

Рисунок 5.8 Пример топологии шины I2C.

 

Подсистемой I2C в Linux определены следующие логические компоненты программного обеспечения:

 

Драйвер алгоритма I2C: каждый адаптер шины I2C имеет свой собственный способ взаимодействия с процессором и шиной I2C. В приведённом выше примере оба шинных адаптера используют стиль сопряжения микросхемы PCF, который определяет регистры, которые должны быть реализованы адаптером шины и реализацию алгоритмов для передачи и приёма данных. Драйвер алгоритма реализует основные процедуры обмена данными (передача и приём). Например, на Рисунке 5.8 показан только один драйвер алгоритма, драйвер алгоритма PCF8584.

Драйвер адаптера I2C: это можно рассматривать как уровень BSP для подсистемы I2C. Драйвер адаптера I2C и драйвер алгоритма вместе управляют шинами I2C в системе. В приведённом выше примере мы определяем два драйвера адаптера I2C для каждой из двух шин в системе. Оба эти драйвера адаптера связаны с драйвером алгоритма PCF8584.

Драйвер ведомого устройства I2C: драйвер ведомого устройства содержит процедуры для доступа к определённому виду ведомого устройства на шине I2C. В нашем примере мы предоставляем два драйвера: один для доступа к EEPROM на первой шине I2C, а другой для доступа к микросхеме RTC на второй шине I2C.

Драйвер клиента I2C: один драйвер клиента является представлением одной единицы оборудования, которое должно быть доступно через шину I2C. Драйверы ведомого устройства и клиента связаны друг с другом. В нашем примере мы должны определить три клиентских драйвера: два клиентских драйвера EEPROM и один клиентский драйвер RTC.

 

Почему подсистема разделена таким образом? Это делается для как можно большего повторного использования программного обеспечения и чтобы дать возможность переносить код. Это достигается за счёт сложности. Подсистема I2C находится в каталоге drivers/i2c дерева исходных текстов ядра. В этом каталоге подкаталог buses содержит различные драйверы адаптера шины, algos содержит различные драйверы микросхем алгоритма, а каталог chips содержит различные драйверы ведомых устройств и клиентов. Общая часть всей подсистемы I2C называется ядром I2C и реализована в файле drivers/ic2/i2c-core.c.

 

Драйвер алгоритма и адаптера шины

 

Для того, чтобы лучше понять, как писать эти драйверы, мы рассмотрим реализацию драйвера алгоритма для PCF8584 для Linux и шинный адаптер, который использует этот алгоритм. Прежде чем мы углубимся в исходный код, давайте сделаем очень общий обзор микросхемы интерфейса I2C PCF8584. PCF8584 представляет собой интерфейсное устройство между стандартными высокоскоростными параллельными шинами и шиной I2C. Микросхема осуществляет передачу данных между шиной I2C и широко распространённой параллельный шиной микроконтроллера, используя либо прерывание, либо опрос. PCF8584 определяет следующие регистры адаптера шины I2C.

 

S0: регистр буфера данных/сдвига, который выполняет параллельно-последовательное преобразование между процессором и шиной I2C.

S0': это внутренний регистр адрес и он заполняется во время инициализации.

S1: управляющий регистр и регистр состояния, используемый для доступа к шине и управления.

S2: регистр тактовой частоты.

S3: регистр вектора прерывания.

 

Спецификация PCF8584 содержит более подробную информацию о том, как программировать регистры для инициализации, передачи и приёма. Спецификацию можно загрузить с веб-сайта компании Philips Semiconductor.

Каждый драйвер алгоритма связан со структурой данных i2c_algorithm, объявленной в файле include/linux/i2c.h. Эта структура данных имеет указатель на функцию master_xfer, который указывает на функцию, которая реализует фактический алгоритм передачи и приёма по I2C. Другими важными полями этой структуры являются:

 

name: название алгоритма.

id: каждый алгоритм определяется с помощью уникального номера. Различные типы алгоритмов определены в заголовочном файле include/linux/i2c-id.h.

algo_control: это указатель на функцию, подобную ioctl.

functionality: это указатель на функцию, которая возвращает функции, поддерживаемые адаптером, например, какие типы сообщений поддерживаются драйвером I2C.

 

static struct i2c_algorithm pcf_algo = {
  .name          = "PCF8584 algorithm",
  .id            = I2C_ALGO_PCF,
  .master_xfer   = pcf_xfer,
  .functionality = pcf_func,
};

 

Драйвер алгоритма сам по себе не имеет смысла, если он не связан с драйвером адаптера шины I2C. Драйвер алгоритма PCF обеспечивает для этой цели функцию привязки: i2c_pcf_add_bus(). Каждый драйвер адаптер связан со структурой данных i2c_adapter (объявленной в файле include/ linux/i2c.h), экземпляр которой создаётся драйвером адаптера. Драйвер адаптера вызывает функцию i2c_pcf_add_bus с указателем на структуру i2c_adapter. Важными полями структуры i2c_adapter, которые настраиваются драйвером адаптера, являются:

 

name: имя для адаптера.

class: указывает тип класса устройств I2C, который поддерживает этот драйвер.

algo: указатель на структуру данных i2c_algorithm. i2c_pcf_add_bus() устанавливает algo указывающим на pcf_algo.

algo_data: указатель на закрытую, зависимую от алгоритма структуру данных. Например, драйвер алгоритма PCF  присваивает этому полю внутренний указатель на структуру данных i2c_algo_pcf_data. Эта структура данных содержит указатель на процедуры для доступа к различным регистрам адаптера. Таким образом, драйвер алгоритма отделён от деталей конструкции платы; драйвер адаптера экспортирует особенности конструкции платы используя эту структуру данных. Драйвер адаптера определяет различные процедуры, которые определены в структуре данных i2c_algo_pcf_data следующим образом:

 

static struct i2c_algo_pcf_data pcf_data = {

  .setpcf     = pcf_setbyte,

  .getpcf     = pcf_getbyte,

  .getown     = pcf_getown,

  .getclock   = pcf_getclock,

  .waitforpin = pcf_waitforpin,

  .udelay     = 10,

  .mdelay     = 10,

  .timeout    = 100,

};

 

Драйверу адаптера шины необходимо выполнить следующие действия, чтобы связать себя с драйвером алгоритма.

 

Определить структуру типа i2c_adapter следующим образом:
 
static struct i2c_adapter pcf_ops = {
 .owner     = THIS_MODULE,
 .id        = I2C_HW_P_ID,
 .algo_data = &pcf_data,
 .name      = "PCF8584 type adapter",
};
 

Определить функции инициализации, которые делают следующее:
– Запрашивают различные ресурсы, необходимые для драйвера адаптера, такие как линия прерывания.
– Вызывают функцию i2c_pcf_add_bus для связи алгоритма PCF с этим драйвером адаптера. Функция i2c_pcf_add_bus выполняет внутренний вызов функции ядра I2C i2c_add_adapter, которая регистрирует в ядре новый драйвер адаптера. После этого адаптер доступен для регистрации клиентами.

 

I2C драйверы ведомых устройств и клиентов

 

Чтобы понять модель драйвера клиента I2C, предположим, что есть вымышленное устройство, подключенное по шине I2C, которое содержит один 32-х разрядный регистр. Функциональность драйвера заключается в предоставлении процедур для выполнения чтения и записи регистра. Мы также предполагаем наличие программного обеспечения драйвера алгоритма и драйвера адаптера. Это включает в себя создание драйвера ведомого устройства и клиента. Драйвер ведомого устройства использует структуру данных i2c_driver, объявленную в заголовочном файле include/linux/i2c.h. Важными полями этой структуры данных являются:

 

name: название клиента.

id: уникальный идентификатор этого устройства. Список всех идентификаторов может быть найден в файле include/linux/i2c-id.h.

flags: устанавливается в I2C_DF_NOTIFY, что разрешает уведомления при обнаружении устройств на шине, так что драйвер сможет обнаруживать новые устройства.

attach_adapter: указывает на функцию, которая определяет наличие устройств I2C на шине I2C. Если устройство найдено, то она вызывает функцию для создания нового экземпляра клиента и подключения клиента к ядру I2C.

detach_client: указывает на функцию, которая удаляет экземпляр клиента и уведомляет ядро I2C о его удалении.

command: это подобная ioctl команда, которая может быть использована для специальных функций в устройстве.

 

В нашем примере драйвера мы определяем структуру i2c_driver следующим образом:

 

static struct i2c_driver i2c_test_driver = {

  .owner          = THIS_MODULE,

  .name           = "TEST",

  .id             = I2C_DRIVERID_TEST,

  .flags          = I2C_DF_NOTIFY,

  .attach_adapter = i2c_test_scan_bus

  .detach_client  = i2c_test_detach,

  .command        = i2c_test_command

};

 

Сначала рассмотрим функцию i2c_test_scan_bus, которая вызывается, когда добавляется новый адаптер или новое устройство. Аргументом этой функции является указатель на структуру i2c_adapter для шины, на которой обнаружено и добавлено ведомое устройство.

 

static int i2c_test_scan_bus(struct i2c_adapter *d)

{

  return i2c_probe(d, &addr_data, i2c_test_attach);

}

 

Функция i2c_probe предоставляется ядром I2C; эта функция использует информацию в структуре данных addr_data, чтобы вызвать функцию i2c_test_attach; последняя создаёт нового экземпляр клиента и регистрирует его в подсистеме. Структура addr_data объявлена в файле include/linux/i2c.h. Функция addr_data используется для выполнения следующего:

 

Принудительной регистрации устройства I2C по указанному адресу в качестве клиента без проверки

Игнорирования устройства I2C по указанному адресу

Зондирования устройства I2C по указанному адресу с помощью адаптера и обнаружения его присутствия

Функционирования в нормальном режиме, просто обращаясь к устройству I2C по указанному адресу и проверяя его присутствие

 

Функция i2c_test_attach создаёт структуру данных i2c_client для клиента и заполняет её. Структура данных i2c_client также объявлена в файле include/linux/i2c.h и её важными полями являются:

 

id: идентификация

addr: I2C адрес, по которому было обнаружено ведомое устройство

adapter: указатель на структуру i2c_adapter для шины, на которой был обнаружен клиент

driver: указатель на структуру i2c_driver

 

static int

i2c_test_attach(struct i2c_adapter *adap, int addr,

                int type)

{

  struct i2c_client *client =

      kmalloc(sizeof(struct i2c_client), GFP_KERNEL);

  client->id = TEST_CLIENT_ID:

  client->addr = addr;

  client->adapter = adapter;

  client->driver = &i2c_test_driver;

 

  return(i2c_attach_client(client));

}

 

Наконец, командная функция, которая реализует функциональные возможности чтения и записи одного регистра микросхемы, выглядит в следующим образом:

 

static int

i2c_test_command(struct i2c_client *client,

                 unsigned int cmd, void *arg)

{

  if(cmd == READ)

    return i2c_test_read(client, arg);

  else if(cmd == WRITE)

    return i2c_test_write(client, arg); return -EINVAL;

}

 

static int

i2c_test_read(struct i2c_client *client, void *arg)

{

  i2c_master_recv(client,arg,4);

}

 

static int

i2c_test_write(struct i2c_client *client, void *arg)

{

  i2c_master_send(client, arg ,4);

}

 

Функции i2c_master_recv и i2c_master_send читают и записывают байты от данного клиента. Внутри они вызывают функцию драйвера master_xfer. Также доступна ещё одна функция, i2c_transfer; она посылает серию сообщений, которые могут быть смесью операций чтения и записи, приводя к комбинированным операциям.

 

Предыдущая  Содержание  Следующая