Android ICS 4.0.3 от Texas Instruments на процессорном модуле SomIQ-AM37

В качестве готовой сборки Android ICS берем дистрибутив Android IceCream Sandwich (ICS) 4.0.3 для процессоров Texas Instruments Sitara(TM) AM3715, DevKit 3.0.0. Этот дистрибутив содержит: драйвер SGX (3D графический акселератор), WLAN и Bluetooth для чипа TI WL1271 (см WLAN адаптер для Somiqboard), USB mass storage, и т.п. Также в дистрибутиве присутствует компилятор ARM GCC. Изначально в качестве дистрибутива Android ICS выбрана сборка от проекта Rowboat: http://code.google.com/p/rowboat/wiki/ICSonBeagleboard, который находится в нерабочем состоянии, но инженеры TI всё же дают рабочие исходники.

Подробности на этой странице:
http://software-dl.ti.com/dsps/dsps_public_sw/sdo_tii/TI_Android_DevKit/TI_Android_ICS_4_0_3_DevKit_3_0_0/index_FDS.html

Руководство разработчика для TI_Android_ICS_4_0_3_DevKit_3_0_0 находится здесь:
http://processors.wiki.ti.com/index.php/TI-Android-ICS-4.0.3-DevKit-3.0.0_DevelopersGuide

В данном дистрибутиве присутствуют исходники для 3-х платформ:

  • AM37x EVM
  • Flashboard
  • Beagleboard-XM

Нас интересуют только поддержка Beagleboard и позднее AM37EVM, чтобы добавить WLAN.

Какие цели ставим:
1) Android запускается и выводит графику на HDMI монитор
2) Мышь работает
3) Сеть Ethernet работает
4) Работают утилиты, бенчмарки из сборки

Чтобы полностью оценить всю масштабность процесса можно выполнить все шаги, описанные в Руководстве разработчика http://processors.wiki.ti.com/index.php/TI-Android-ICS-4.0.3-DevKit-3.0.0_DevelopersGuide, но для достижения поставленных целей нам это всё не нужно. В действительности нам потребуются только загрузчики MLO, u-boot, ядро Linux-Android и файловая система. Файловую систему мы возьмем «как есть» от сборки для Beagleboard, а загрузчики и ядро соберем руками. Затем создадим загрузочную SD карту и получим Android ICS на платформе Somiqboard с процессорным модулем SomIQ-AM37. Т.е. по сути мы повторяем работу Android ICS для Beagleboard на другом железе — процессорном модуле SomIQ-AM37. В данной работе мы не ставим целью найти и исправить ошибки или что-то добавить, мы только повторяем все ошибки и недочеты, которые присутствуют в сборке для Beagleboard.

Шаг 1. Портируем ядро Linux

Далее описан достаточно общий процесс адаптации ядра Linux от одной платформы для схожей другой платформы, поэтому правила, изложенные здесь, могут быть применимы не только к Android ICS и SomIQ-AM37, но и другим платформам.

Ядро Linux представляет собой сборку различных системных утилит как планировщик задач, управление памятью, файловая система и т.п. Эти части являются общими для всех платформ и не требуют (обычно) доработок для отдельной платформы, т.к. являются аппаратно независимыми. Что является аппаратно зависимым в Linux:

  • начальная инициализация аппаратуры
  • набор драйверов

Начальная инициализация платформы выполняется в файле (мы рассматриваем только ARM архитектуру, процессоры TI Cortex-A8) /arch/arm/mach-omap2/board-<название платформы>.c. Например, для Beagleboard инициализация выполняется в файле «arch/arm/mach-omap2/board-omap3beagle.c». Так как схема SomIQ-AM37 основана на схеме Beagleboard, поэтому мы возьмем за основу BSP (board support package) для Beagleboard.

Шаг 1.1 Добавим архитектуру SOMIQ_AM37 в ядро Linux

В дереве ядра Linux каждое устройство идентифицируется посредством machine ID. Это позволяет хранить одновременно в бинарном образе ядра поддержку сразу нескольких устройств и на этапе передачи управления от загрузчика к ядру — последнее на основе Machine ID выберет нужную конфигурацию для конкретного устройства. В ядре Linux все Machine ID для ARM архитектуры хранятся в файле «arch/arm/tools/mach-types». Глобально абсолютно все Machine ID для ARM архитектуры хранятся по адресу http://www.arm.linux.org.uk/developer/machines/download.php. Вы можете либо скачать этот файл и поместить его в ваше дерево Linux или руками добавить нужную архитектуру. Добавим вручную архитектуру SOMIQ_AM37. Для этого откройте файл «arch/arm/tools/mach-types» в редакторе Gedit или vim и добавьте в конец файла следующую строку:

somiq_am37 MACH_SOMIQ_AM37 SOMIQ_AM37 4196

Шаг 1.2 Добавим файл board- в ядро

Следующим шагом мы скопируем файл начальной инициализации Beagleboard в новый и исправим его. В директории «arch/arm/mach-omap2» скопируем файл board-omap3beagle.c в board-somiq_am37.c

cp board-omap3beagle.c board-somiq_am37.c

Теперь добавим новый файл в Makefile. Откройте Makefile в той же директории «arch/arm/mach-omap2» и найдите строчки, относящиеся к BEAGLE:

obj-$(CONFIG_MACH_OMAP3_BEAGLE) += board-omap3beagle.o \
board-flash.o \
hsmmc.o

Копируем эти строчки и заменяем в копии CONFIG_MACH_OMAP3_BEAGLE на CONFIG_MACH_SOMIQ_AM37, также замените название board-файла:

obj-$(CONFIG_MACH_SOMIQ_AM37) += board-somiq_am37.o \
board-flash.o \
hsmmc.o

Также в этом Makefile файле присутствует включение файла для поддержки камер, но нам пока это не нужно.

Теперь нужно добавить архитектуру SOMIQ_AM37 в файл конфигурации Kconfig. Откройте файл Kconfig в директории «arch/arm/mach-omap2» и найдите секцию, относящуюся к BEAGLE:

config MACH_OMAP3_BEAGLE
bool "OMAP3 BEAGLE board"
depends on ARCH_OMAP3
default y
select OMAP_PACKAGE_CBB

Скопируйте эти строки и замените MACH_OMAP3_BEAGLE на MACH_SOMIQ_AM37, а OMAP_PACKAGE_CBB на OMAP_PACKAGE_CUS. Последняя замена необходима, т.к. на Beagleboard используют процессор в корпусе CBB, а на SomIQ-AM37 используют процессоры в корпусе CUS. Эта настройка нужна, чтобы корректно настраивать PIN MUX в ядре. PIN MUX это выбор одной из семи функций принадлежащей каждому выводу процессора. В итоге вы добавите следующие строки в файл:

config MACH_SOMIQ_AM37
bool "SomIQ-AM37 board"
depends on ARCH_OMAP3
default y
select OMAP_PACKAGE_CUS

1.3 Редактирование board-файла

Теперь приступим к самому сложному и ответственному процессу — редактирование кода инициализации всей аппаратуры. Сначала давайте проанализируем структуру файла. Откройте файл board-omap3beagle.с в редакторе.

Сначала идет блок включений файлов заголовков:

#include <linux/kernel.h>;
#include <linux/init.h>;
#include <linux/platform_device.h>;
.......

Затем идет кусок кода, отвечающий за подключение устройства по USB (здесь мы ничего менять не будем):

#ifdef CONFIG_USB_ANDROID
#define GOOGLE_VENDOR_ID 0x18d1
#define GOOGLE_PRODUCT_ID 0x9018
#define GOOGLE_ADB_PRODUCT_ID 0x9015
.......

Затем начинается код, относящийся к платформе Beagleboard:

/*
* OMAP3 Beagle revision
* Run time detection of Beagle revision is done by reading GPIO.
....

Т.к. Beagleboard еволюционирует в течение нескольких лет, то схема менялась неоднократно, менялись процессоры, память, расположение линий GPIO и т.п. Чтобы иметь одно ядро, которое будет управлять всеми этими конфигурациями разработчики Beagleboard отвели 3 линии процессора, которыми они задают ревизию платы. Поэтому первым делом BSP Beagleboard определяет на какой плате он работает. Модуль SomIQ-AM37 выполняется в одной конфигурации (пока) и поэтому определение ревизий платы не нужно. Поэтому удаляем следующий код:

enum {
OMAP3BEAGLE_BOARD_UNKN = 0,
OMAP3BEAGLE_BOARD_AXBX,
OMAP3BEAGLE_BOARD_C1_3,
OMAP3BEAGLE_BOARD_C4,
OMAP3BEAGLE_BOARD_XM,
OMAP3BEAGLE_BOARD_XMC,
};

static u8 omap3_beagle_version;

static u8 omap3_beagle_get_rev(void)
{
return omap3_beagle_version;
}

static void __init omap3_beagle_init_rev(void)

Далее идет описание разделов на флеш NAND, если она присутствует на плате (оставляем без изменений):

static struct mtd_partition omap3beagle_nand_partitions[] = {
/* All the partition sizes are listed in terms of NAND block size */
{
.name = "X-Loader",
.offset = 0,
.size = 4 * NAND_BLOCK_SIZE,
.mask_flags = MTD_WRITEABLE, /* force read-only */
},
.....
}

Затем начинается непосредственно код, относящийся к различным периферийным устройствам на плате. Здесь добавляют функции работы с DSS контроллером процессора, который отвечает за вывод графики на конечное устройство. Beagleboard напрямую может работать только с телевизором через S-Video или HDMI/DVI дисплеем, поэтому для них всё и настраивают:

  • функции beagle_enable_dvi/beagle_disable_dvi — соответственно включают и выключают монитор (управляют GPIO линией процессора, которая подключена к микросхеме TFP410, входной сигнал Power-Down. Т.е. перевод микросхемы в режим Power Down отключает вывод на монитор;
  • создают объекты структуры omap_dss_device и описывают физические устройства: beagle_tv_device (ТВ) и beagle_dvi_device (DVI монитор);
  • привязывают регуляторы напряжения к выходам ТВ и DVI: beagle_vdac_supply и beagle_vdvi_supply. Это нужно, чтобы по возможности снизить потребление энергии и выключать соответствующий регулятор, если он не используется;
  • функция beagle_display_init() получает доступ к линии GPIO для включения/выключения дисплея DVI. Номер GPIO хранят в поле структуры beagle_dvi_device.reset_gpio

С DSS закончили и настраиваем MMC карту, она же SD:

static struct omap2_hsmmc_info mmc[] = {
{
.mmc = 1,
.caps = MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA,
.gpio_wp = 29,
},
{} /* Terminator */
};

Обратите внимание, что в созданной структуре присутствует поле gpio_wp (Write Protect от карты SD), но на несущей плате Somiqboard установлен слот карты MicroSD, у которого нет линии Write Protect. Поэтому вместо присвоения номера GPIO линии 29 запишем ‘-EINVAL’, сообщив тем самым драйверу MMC, что у нас такой сигнал отсутствует. Также можно удалить флаг MMC_CAP_8_BIT_DATA, т.к. карта у нас 4-х битная. После исправлений:

static struct omap2_hsmmc_info mmc[] = {
{
.mmc = 1,
.caps = MMC_CAP_4_BIT_DATA,
.gpio_wp = -EINVAL,
},
{} /* Terminator */
};

Теперь привязывают физические регуляторы напряжения к драйверам:

static struct regulator_consumer_supply beagle_vmmc1_supply = {
.supply = "vmmc",
};

static struct regulator_consumer_supply beagle_vsim_supply = {
.supply = "vmmc_aux",
};

static struct regulator_consumer_supply beagle_vaux3_supply = {
.supply = "cam_1v8",
};

static struct regulator_consumer_supply beagle_vaux4_supply = {
.supply = "cam_2v8",
};

beagle_vmmc1_supply оставляем, а beagle_vsim_supply, beagle_vaux3_supply, beagle_vaux4_supply смело удаляем, т.к. VSIM, VAUX3, VAUX4 есть только у контроллера питания TPS65950, а на используемом нами TPS65920 они физически отсутствуют. Однако эти определения регуляторов можно и оставить, т.к. они в работе мешать не будут, но и толку от них нет.

Теперь настраиваем GPIO, которые привязаны к чипу питания TPS65920 (он имеет ряд функций помимо питания). В функции beagle_twl_gpio_setup() удаляем следующие строки:

В этом фрагменте настраивают линию Write Protect, которую мы не используем

if (omap3_beagle_get_rev() == OMAP3BEAGLE_BOARD_XM || omap3_beagle_get_rev() == OMAP3BEAGLE_BOARD_XMC) {
mmc[0].gpio_wp = -EINVAL;
} else if ((omap3_beagle_get_rev() == OMAP3BEAGLE_BOARD_C1_3) ||
(omap3_beagle_get_rev() == OMAP3BEAGLE_BOARD_C4)) {
omap_mux_init_gpio(23, OMAP_PIN_INPUT);
mmc[0].gpio_wp = 23;
} else {
omap_mux_init_gpio(29, OMAP_PIN_INPUT);
}

Удаляем упоминание регулятора VSIM, т.к. его у нас физически нет:

beagle_vsim_supply.dev = mmc[0].dev;

Далее удаляем управление ключом питания USB, т.к. на Somiqboard его нет:

/* REVISIT: need ehci-omap hooks for external VBUS
* power switch and overcurrent detect
*/
if (omap3_beagle_get_rev() != OMAP3BEAGLE_BOARD_XM || omap3_beagle_get_rev() == OMAP3BEAGLE_BOARD_XMC) {
gpio_request(gpio + 1, "EHCI_nOC");
gpio_direction_input(gpio + 1);
}

Также удаляем по той же причине:

/*
* TWL4030_GPIO_MAX + 0 == ledA, EHCI nEN_USB_PWR (out, XM active
* high / others active low)
*/
gpio_request(gpio + TWL4030_GPIO_MAX, "nEN_USB_PWR");
gpio_direction_output(gpio + TWL4030_GPIO_MAX, 0);
if (omap3_beagle_get_rev() == OMAP3BEAGLE_BOARD_XM)
gpio_direction_output(gpio + TWL4030_GPIO_MAX, 1);
else
gpio_direction_output(gpio + TWL4030_GPIO_MAX, 0);

Теперь в этой функции назначают линию управления выключением TFP410, хотя эта линия привязана к процессору, а не TPS65920:

/* DVI reset GPIO is different between beagle revisions */
if (omap3_beagle_get_rev() == OMAP3BEAGLE_BOARD_XM || omap3_beagle_get_rev() == OMAP3BEAGLE_BOARD_XMC)
beagle_dvi_device.reset_gpio = 129;
else
beagle_dvi_device.reset_gpio = 170;

Сохраним преемственность, но назначим другую линию (на связке Somiqboard+SomIQ-AM37 это линия GPIO_10) и остальное удаляем, оставляя:

beagle_dvi_device.reset_gpio = 10;

Полностью удаляем следующий блок:

if (omap3_beagle_get_rev() == OMAP3BEAGLE_BOARD_XM) {
/* Power on camera interface */
gpio_request(gpio + 2, "CAM_EN");
gpio_direction_output(gpio + 2, 1);

/* TWL4030_GPIO_MAX + 0 == ledA, EHCI nEN_USB_PWR (out, active low) */
gpio_request(gpio + TWL4030_GPIO_MAX, "nEN_USB_PWR");
gpio_direction_output(gpio + TWL4030_GPIO_MAX, 1);
} else {
gpio_request(gpio + 1, "EHCI_nOC");
gpio_direction_input(gpio + 1);

/* TWL4030_GPIO_MAX + 0 == ledA, EHCI nEN_USB_PWR (out, active low) */
gpio_request(gpio + TWL4030_GPIO_MAX, "nEN_USB_PWR");
gpio_direction_output(gpio + TWL4030_GPIO_MAX, 0);
}

Оставляем строки управления светодиодом на модуле SomIQ-AM37, отмеченный как RDY. Его включает u-boot и он гаснет, когда загрузится ядро.

/* TWL4030_GPIO_MAX + 1 == ledB, PMU_STAT (out, active low LED) */
gpio_leds[2].gpio = gpio + TWL4030_GPIO_MAX + 1;

Следующий блок удаляем полностью:

/*
* gpio + 1 on Xm controls the TFP410's enable line (active low)
* gpio + 2 control varies depending on the board rev as follows:
* P7/P8 revisions(prototype): Camera EN
* A2+ revisions (production): LDO (supplies DVI, serial, led blocks)
*/
if (omap3_beagle_get_rev() == OMAP3BEAGLE_BOARD_XM || omap3_beagle_get_rev() == OMAP3BEAGLE_BOARD_XMC) {
gpio_request(gpio + 1, "nDVI_PWR_EN");
gpio_direction_output(gpio + 1, 0);
gpio_request(gpio + 2, "DVI_LDO_EN");
gpio_direction_output(gpio + 2, 1);
}

С функцией beagle_twl_gpio_setup закончили и далее структуру beagle_gpio_data оставляем без изменений.

Теперь идет настройка регуляторов напряжений и привязка их к конкретным устройствам:

  • beagle_vmmc1 — оставляем без изменений
  • beagle_vsim — удаляем всю структуру, т.к. у нас нет регулятора
  • beagle_vdac — оставляем без изменений
  • beagle_vpll2 — оставляем без изменений
  • beagle_vaux3 — удаляем
  • beagle_vaux4 — удаляем

Теперь настраивают звуковой кодек в TPS65950, но на SomIQ-AM37 стоит TPS65920, у которого нет звукового кодека, поэтому удаляем:

static struct twl4030_codec_audio_data beagle_audio_data = {
.audio_mclk = 26000000,
.digimic_delay = 1,
.ramp_delay_value = 1,
.offset_cncl_path = 1,
.check_defaults = false,
.reset_registers = false,
.reset_registers = false,
};

static struct twl4030_codec_data beagle_codec_data = {
.audio_mclk = 26000000,
.audio = &beagle_audio_data,
};

И наконец, в структуре описывающей TPS65920 (TWL) удаляем строки, что мы уже удалили ранее и получим:

static struct twl4030_platform_data beagle_twldata = {
.irq_base = TWL4030_IRQ_BASE,
.irq_end = TWL4030_IRQ_END,

/* platform_data for children goes here */
.usb = &beagle_usb_data,
.gpio = &beagle_gpio_data,
.vmmc1 = &beagle_vmmc1,
.vdac = &beagle_vdac,
.vpll2 = &beagle_vpll2,
};

Далее структуры beagle_i2c_boardinfo, beagle_i2c_eeprom и функцию omap3_beagle_i2c_init оставим без изменений. В структуре gpio_leds удалим строки, описывающие светодиоды usr0 и usr1, т.к. из нет на Somiqboard.

Далее идет назначение линии GPIO, к которой подключена кнопка USER на Beagleboard и Somiqboard — оставляем без изменений:

static struct gpio_keys_button gpio_buttons[] = {
{
.code = BTN_EXTRA,
.gpio = 7,
.desc = "user",
.wakeup = 1,
},
};

Переходим к функции omap3_beagle_init_irq() и удаляем вхождение omap3_beagle_version, получим:

#ifdef CONFIG_OMAP_32K_TIMER
omap2_gp_clockevent_set_gptimer(12);
#endif

Далее оставляем всё без изменений до объявления структуры ehci_pdata{}:

static const struct ehci_hcd_omap_platform_data ehci_pdata __initconst = {

.port_mode[0] = EHCI_HCD_OMAP_MODE_PHY,
.port_mode[1] = EHCI_HCD_OMAP_MODE_PHY,
.port_mode[2] = EHCI_HCD_OMAP_MODE_UNKNOWN,

.phy_reset = true,
.reset_gpio_port[0] = -EINVAL,
.reset_gpio_port[1] = 147,
.reset_gpio_port[2] = -EINVAL
};

В этой структуре для Beagleboard назначают линию GPIO_147 чтобы сбросить (reset) USB PHY. На модуле SomIQ-AM37 для этого используют линию 186, поэтому исправим 147 на 186.

Теперь переходим к главной функции, которую вызывает ядро при инициализации платы: omap3_beagle_init(). Удаляем вызов функции omap3_beagle_init_rev() — она определяет какая ревизия платы Beagleboard. Затем заменяем цифры 170 на 10, т.к. именно линия 10 управляет включением TFP410 (HDMI):

omap_mux_init_gpio(10, OMAP_PIN_INPUT);
gpio_request(10, "DVI_nPD");
/* REVISIT leave DVI powered down until it's needed ... */
gpio_direction_output(10, true);

Теперь надо изменить Machine ID в этом файле, чтобы вызывались функции именно из этого файла, когда будет передача управления от u-boot. Посмотрите на самый последний блок в файле:

MACHINE_START(OMAP3_BEAGLE, "OMAP3 Beagle Board")
/* Maintainer: Syed Mohammed Khasim - http://beagleboard.org */
.boot_params = 0x80000100,
.map_io = omap3_map_io,
.reserve = omap_reserve,
.init_irq = omap3_beagle_init_irq,
.init_machine = omap3_beagle_init,
.timer = &omap_timer,
MACHINE_END

В макросе MACHINE_START указан идентификатор Machine ID — OMAP3_BEAGLE и текстовая строка, которая печатается ядром при загрузке. Заменим их на следующую:

MACHINE_START(SOMIQ_AM37, "SomIQ-AM37 SOM")

На данном этапе мы практически полностью завершили работу над созданием своего BSP для модуля SomIQ-AM37. Однако у нас осталась нереализованная функция сети Ethernet. На модуле SomIQ-AM37 установлен Ethernet контроллер Micrel KSZ8851SNLI, подключенный к SPI1 процессора. Поэтому надо добавить следующие строки до вызова функции omap3_beagle_init():

#if defined(CONFIG_KS8851) || defined(CONFIG_KS8851_MODULE)

#include <plat/mcspi.h>;
#include <linux/spi/spi.h>;

#define GPIO_KS8851_IRQ 1

static struct omap2_mcspi_device_config ks8851_spi_chip_info = {
.turbo_mode = 0,
.single_channel = 1, /* 0: slave, 1: master */
};

static struct spi_board_info somiq_zippy2_spi_board_info[] __initdata = {
{
.modalias = "ks8851",
.bus_num = 1,
.chip_select = 0,
.max_speed_hz = 36000000,
.controller_data = &ks8851_spi_chip_info,
},
};

static void __init somiq_ks8851_init(void)
{
if ((gpio_request(GPIO_KS8851_IRQ, "KS8851_IRQ") == 0) &&
(gpio_direction_input(GPIO_KS8851_IRQ) == 0)) {
gpio_export(GPIO_KS8851_IRQ, 0);
somiq_zippy2_spi_board_info[0].irq = OMAP_GPIO_IRQ(GPIO_KS8851_IRQ);
set_irq_type (somiq_zippy2_spi_board_info[0].irq, IRQ_TYPE_EDGE_FALLING);
} else {
printk(KERN_ERR "could not obtain gpio for KS8851_IRQ\n");
return;
}

spi_register_board_info(somiq_zippy2_spi_board_info,
ARRAY_SIZE(somiq_zippy2_spi_board_info));
}

#else
static inline void __init somiq_ks8851_init(void) { return; }
#endif

Также нужно включить файл заголовков irq.h, чтобы можно было работать с прерываниями. Добавьте следующую строку в начале файле, где идут все включения «#include ..»

#include <linux/irq.h>;

Теперь нужно добавить инициализацию Ethernet контроллера в функцию omap3_beagle_init(). Добавьте вызов somiq_ks8851_init() перед строкой beagle_display_init():

printk(KERN_INFO "initializing ks_8851\n");
somiq_ks8851_init();

Ну, и наконец можно исправить слова «beagle» на «somiq», чтобы в дальнейшем не путаться.

Шаг 1.4 Создание файла конфигурации

Финальным шагом будет создание файла конфигурации. Для этого воспользуемся готовой конфигурацией для Beagleboard и исправим её. В корне исходников ядра выполните команду:

make ARCH=arm omap3_beagle_android_defconfig

Появился файл .config в котором содержится вся конфигурация ядра. Руками этот файл не правят, а нужно выполнить команду:

make ARCH=arm menuconfig

Появится целое структурированное меню, содержащее конфигурацию ядра. Заходим в System Type->TI OMAP 2/3/4 Specific Features-> и пробелом убираем звездочку напротив Beagleboard и наоборот добавляем звездочку напротив SomIQ-AM37. Далее по меню поднимаемся выше до корня и выбираем Device Drivers->Network device support->Ethernet (10 or 100Mbit). Здесь отмечаем пункт «Micrel KS8851 SPI» звездочкой.

Всё. Теперь нажимаем EXIT несколько раз, пока не возникнет вопрос «Сохранит изменения в конфигурации?» — выбираем YES. Теперь можно скомпилировать ядро.