Anbieter zum Thema
Teil 2: Board-Treiber
Der Board-Treiber muss zunächst das Mainboard anhand der vom BIOS gelieferten DMI-Informatioenn erkennen. Wir bedienen uns hier der bereits eingebaute Mechanismen, die auch das Modul automatisch laden. Hierzu definieren wir eine entsprechende match-table:
/* note: matching works on string prefix, so „apu2" must come before „apu" */
static const struct dmi_system_id apu_gpio_dmi_table[] __initconst = {
{
.ident = „apu2",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, „PC Engines"),
DMI_MATCH(DMI_BOARD_NAME, „APU2")
},
.driver_data = (void*)&board_apu2,
},
{
.ident = „apu2",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, „PC Engines"),
DMI_MATCH(DMI_BOARD_NAME, „apu2")
},
.driver_data = (void*)&board_apu2,
}
...
};
MODULE_DEVICE_TABLE(dmi, apu_gpio_dmi_table); Im Feld 'driver_data' können wir einen Zeiger auf Board-spezifische Daten mitgeben. Das ist z.B. hilfreich, wenn das Modul verschiedene Board-Varianten unterstützt, die unterschiedliche Konfiguration benötigen. Das Macro MODULE_DEVICE_TABLE erzeugt die Metadaten für den Module-Loader, damit das Modul automatisch geladen werden kann, wenn das BIOS die passenden DMI-Informationen meldet.
In der Init-Route des Moduls testen wir, ob das Board wirklich vorhanden ist und holen uns die DMI-Daten:
static int __init apu_gpio_init(void)
{
int rc;
const struct dmi_system_id *dmi = dmi_first_match(apu_gpio_dmi_table);
if (!dmi) {
pr_err(KBUILD_MODNAME „: failed to detect apu board via dmi\n");
return -ENODEV; }
... Nun initialisieren wir unseren GPIO-Treiber aus Teil 1 mit der Konfiguration aus obiger DMI-Struktur:
if (IS_ERR(apu_gpio_pdev = platform_device_register_resndata(
NULL, „**gpio_amd_fch**", -1, NULL, 0, **dmi->driver_data**, **sizeof(struct amd_fch_gpio_pdata)**))) {
rc = PTR_ERR(apu_gpio_pdev);
goto fail;
} Anschließend können wir die LED- und Key-Treiber initialisieren und mit den GPIOs verbinden:
static const struct gpio_led apu2_leds[] = {
{ .name = „apu:green:1", .gpio = GPIO_LED1, .active_low = 1, },
{ .name = „apu:green:2", .gpio = GPIO_LED2, .active_low = 1, },
{ .name = „apu:green:3", .gpio = GPIO_LED3, .active_low = 1, }
};
static const struct gpio_led_platform_data apu2_leds_pdata = {
.num_leds = ARRAY_SIZE(apu2_leds),
.leds = apu2_leds,
};
...
if (IS_ERR(apu_leds_pdev = platform_device_register_resndata(
NULL, „leds-gpio", -1, NULL, 0, &apu2_leds_pdata, sizeof(apu2_leds_pdata)))) {
rc = PTR_ERR(apu_leds_pdev);
goto fail;
}
...
static struct gpio_keys_button apu2_keys_buttons[] = {
{
.code = KEY_A,
.gpio = GPIO_MODESW,
.active_low = 1,
.desc = „modeswitch",
.type = EV_KEY, /* or EV_SW ? */
.debounce_interval = 10,
.value = 1,
}
};
static const struct gpio_keys_platform_data apu2_keys_pdata = {
.buttons = apu2_keys_buttons,
.nbuttons = ARRAY_SIZE(apu2_keys_buttons),
.poll_interval = 100,
.rep = 0,
.name = „apu2-keys",
};
...
if (IS_ERR(apu_keys_pdev = platform_device_register_resndata(
NULL, „gpio-keys-polled", -1, NULL, 0, &apu2_keys_pdata, sizeof(apu2_keys_pdata)))) {
rc = PTR_ERR(apu_keys_pdev);
goto fail;
} Fazit: So entsteht ein generischer GPIO-Treiber
Wir haben nun mit einfachen Mitteln zunächst einen generischen GPIO-Treiber für den AMD GX-412TC SoC gebaut und anschließend die verschiedenen generischen Treiber board-spezfisch verdrahtet. Das Patchset fügt hier weniger als 500 Codezeilen ein – das meiste davon sind Konfigurations-Daten.
Der Treiber lässt sich zukünftig in den Mainline-Kernel aufnehmen, so dass für zukünftige Projekte hier keine Anpassungen mehr nötig sind, und auch Standard-Distributionen wie z.B. Debian das APU-Board auch out-of-the-Box unterstützen können. Dies entspricht auch der Philosopie von GNU/Linux und gewährleistet langfristige Wartbarkeit mit geringem Aufwand.
Hardwarenahe Softwareentwicklung
Einbindung von Sensoren und Aktoren mit Industrial-IO in Linux
*Enrico Weigelt ist Gründer und Inhaber des IT-Servicebüros meTUX.
(ID:45724613)