| /* |
| * intel-mid.c: Intel MID platform setup code |
| * |
| * (C) Copyright 2008, 2012 Intel Corporation |
| * Author: Jacob Pan (jacob.jun.pan@intel.com) |
| * Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com> |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * as published by the Free Software Foundation; version 2 |
| * of the License. |
| */ |
| |
| #define pr_fmt(fmt) "intel_mid: " fmt |
| |
| #include <linux/init.h> |
| #include <linux/kernel.h> |
| #include <linux/interrupt.h> |
| #include <linux/scatterlist.h> |
| #include <linux/sfi.h> |
| #include <linux/irq.h> |
| #include <linux/module.h> |
| #include <linux/notifier.h> |
| |
| #include <asm/setup.h> |
| #include <asm/mpspec_def.h> |
| #include <asm/hw_irq.h> |
| #include <asm/apic.h> |
| #include <asm/io_apic.h> |
| #include <asm/intel-mid.h> |
| #include <asm/intel_mid_vrtc.h> |
| #include <asm/io.h> |
| #include <asm/i8259.h> |
| #include <asm/intel_scu_ipc.h> |
| #include <asm/apb_timer.h> |
| #include <asm/reboot.h> |
| |
| /* |
| * the clockevent devices on Moorestown/Medfield can be APBT or LAPIC clock, |
| * cmdline option x86_intel_mid_timer can be used to override the configuration |
| * to prefer one or the other. |
| * at runtime, there are basically three timer configurations: |
| * 1. per cpu apbt clock only |
| * 2. per cpu always-on lapic clocks only, this is Penwell/Medfield only |
| * 3. per cpu lapic clock (C3STOP) and one apbt clock, with broadcast. |
| * |
| * by default (without cmdline option), platform code first detects cpu type |
| * to see if we are on lincroft or penwell, then set up both lapic or apbt |
| * clocks accordingly. |
| * i.e. by default, medfield uses configuration #2, moorestown uses #1. |
| * config #3 is supported but not recommended on medfield. |
| * |
| * rating and feature summary: |
| * lapic (with C3STOP) --------- 100 |
| * apbt (always-on) ------------ 110 |
| * lapic (always-on,ARAT) ------ 150 |
| */ |
| |
| enum intel_mid_timer_options intel_mid_timer_options; |
| |
| enum intel_mid_cpu_type __intel_mid_cpu_chip; |
| EXPORT_SYMBOL_GPL(__intel_mid_cpu_chip); |
| |
| static void __init ipc_device_handler(struct sfi_device_table_entry *pentry, |
| struct devs_id *dev); |
| static void intel_mid_power_off(void) |
| { |
| } |
| |
| static void intel_mid_reboot(void) |
| { |
| intel_scu_ipc_simple_command(IPCMSG_COLD_BOOT, 0); |
| } |
| |
| static unsigned long __init intel_mid_calibrate_tsc(void) |
| { |
| unsigned long fast_calibrate; |
| u32 lo, hi, ratio, fsb; |
| |
| rdmsr(MSR_IA32_PERF_STATUS, lo, hi); |
| pr_debug("IA32 perf status is 0x%x, 0x%0x\n", lo, hi); |
| ratio = (hi >> 8) & 0x1f; |
| pr_debug("ratio is %d\n", ratio); |
| if (!ratio) { |
| pr_err("read a zero ratio, should be incorrect!\n"); |
| pr_err("force tsc ratio to 16 ...\n"); |
| ratio = 16; |
| } |
| rdmsr(MSR_FSB_FREQ, lo, hi); |
| if ((lo & 0x7) == 0x7) |
| fsb = PENWELL_FSB_FREQ_83SKU; |
| else |
| fsb = PENWELL_FSB_FREQ_100SKU; |
| fast_calibrate = ratio * fsb; |
| pr_debug("read penwell tsc %lu khz\n", fast_calibrate); |
| lapic_timer_frequency = fsb * 1000 / HZ; |
| /* mark tsc clocksource as reliable */ |
| set_cpu_cap(&boot_cpu_data, X86_FEATURE_TSC_RELIABLE); |
| |
| if (fast_calibrate) |
| return fast_calibrate; |
| |
| return 0; |
| } |
| |
| static void __init intel_mid_time_init(void) |
| { |
| sfi_table_parse(SFI_SIG_MTMR, NULL, NULL, sfi_parse_mtmr); |
| switch (intel_mid_timer_options) { |
| case INTEL_MID_TIMER_APBT_ONLY: |
| break; |
| case INTEL_MID_TIMER_LAPIC_APBT: |
| x86_init.timers.setup_percpu_clockev = setup_boot_APIC_clock; |
| x86_cpuinit.setup_percpu_clockev = setup_secondary_APIC_clock; |
| break; |
| default: |
| if (!boot_cpu_has(X86_FEATURE_ARAT)) |
| break; |
| x86_init.timers.setup_percpu_clockev = setup_boot_APIC_clock; |
| x86_cpuinit.setup_percpu_clockev = setup_secondary_APIC_clock; |
| return; |
| } |
| /* we need at least one APB timer */ |
| pre_init_apic_IRQ0(); |
| apbt_time_init(); |
| } |
| |
| static void __cpuinit intel_mid_arch_setup(void) |
| { |
| if (boot_cpu_data.x86 == 6 && boot_cpu_data.x86_model == 0x27) |
| __intel_mid_cpu_chip = INTEL_MID_CPU_CHIP_PENWELL; |
| else { |
| pr_err("Unknown Intel MID CPU (%d:%d), default to Penwell\n", |
| boot_cpu_data.x86, boot_cpu_data.x86_model); |
| __intel_mid_cpu_chip = INTEL_MID_CPU_CHIP_PENWELL; |
| } |
| } |
| |
| /* MID systems don't have i8042 controller */ |
| static int intel_mid_i8042_detect(void) |
| { |
| return 0; |
| } |
| |
| /* |
| * Moorestown does not have external NMI source nor port 0x61 to report |
| * NMI status. The possible NMI sources are from pmu as a result of NMI |
| * watchdog or lock debug. Reading io port 0x61 results in 0xff which |
| * misled NMI handler. |
| */ |
| static unsigned char intel_mid_get_nmi_reason(void) |
| { |
| return 0; |
| } |
| |
| /* |
| * Moorestown specific x86_init function overrides and early setup |
| * calls. |
| */ |
| void __init x86_intel_mid_early_setup(void) |
| { |
| x86_init.resources.probe_roms = x86_init_noop; |
| x86_init.resources.reserve_resources = x86_init_noop; |
| |
| x86_init.timers.timer_init = intel_mid_time_init; |
| x86_init.timers.setup_percpu_clockev = x86_init_noop; |
| |
| x86_init.irqs.pre_vector_init = x86_init_noop; |
| |
| x86_init.oem.arch_setup = intel_mid_arch_setup; |
| |
| x86_cpuinit.setup_percpu_clockev = apbt_setup_secondary_clock; |
| |
| x86_platform.calibrate_tsc = intel_mid_calibrate_tsc; |
| x86_platform.i8042_detect = intel_mid_i8042_detect; |
| x86_init.timers.wallclock_init = intel_mid_rtc_init; |
| x86_platform.get_nmi_reason = intel_mid_get_nmi_reason; |
| |
| x86_init.pci.init = intel_mid_pci_init; |
| x86_init.pci.fixup_irqs = x86_init_noop; |
| |
| legacy_pic = &null_legacy_pic; |
| |
| pm_power_off = intel_mid_power_off; |
| machine_ops.emergency_restart = intel_mid_reboot; |
| |
| /* Avoid searching for BIOS MP tables */ |
| x86_init.mpparse.find_smp_config = x86_init_noop; |
| x86_init.mpparse.get_smp_config = x86_init_uint_noop; |
| set_bit(MP_BUS_ISA, mp_bus_not_pci); |
| } |
| |
| /* |
| * if user does not want to use per CPU apb timer, just give it a lower rating |
| * than local apic timer and skip the late per cpu timer init. |
| */ |
| static inline int __init setup_x86_intel_mid_timer(char *arg) |
| { |
| if (!arg) |
| return -EINVAL; |
| |
| if (strcmp("apbt_only", arg) == 0) |
| intel_mid_timer_options = INTEL_MID_TIMER_APBT_ONLY; |
| else if (strcmp("lapic_and_apbt", arg) == 0) |
| intel_mid_timer_options = INTEL_MID_TIMER_LAPIC_APBT; |
| else { |
| pr_warn("X86 INTEL_MID timer option %s not recognised" |
| " use x86_intel_mid_timer=apbt_only or lapic_and_apbt\n", |
| arg); |
| return -EINVAL; |
| } |
| return 0; |
| } |
| __setup("x86_intel_mid_timer=", setup_x86_intel_mid_timer); |
| |
| /* the offset for the mapping of global gpio pin to irq */ |
| #define INTEL_MID_IRQ_OFFSET 0x100 |
| |
| static void __init *pmic_gpio_platform_data(void *info) |
| { |
| static struct intel_pmic_gpio_platform_data pmic_gpio_pdata; |
| int gpio_base = get_gpio_by_name("pmic_gpio_base"); |
| |
| if (gpio_base == -1) |
| gpio_base = 64; |
| pmic_gpio_pdata.gpio_base = gpio_base; |
| pmic_gpio_pdata.irq_base = gpio_base + INTEL_MID_IRQ_OFFSET; |
| pmic_gpio_pdata.gpiointr = 0xffffeff8; |
| |
| return &pmic_gpio_pdata; |
| } |
| |
| static void __init *max3111_platform_data(void *info) |
| { |
| struct spi_board_info *spi_info = info; |
| int intr = get_gpio_by_name("max3111_int"); |
| |
| spi_info->mode = SPI_MODE_0; |
| if (intr == -1) |
| return NULL; |
| spi_info->irq = intr + INTEL_MID_IRQ_OFFSET; |
| return NULL; |
| } |
| |
| /* we have multiple max7315 on the board ... */ |
| #define MAX7315_NUM 2 |
| static void __init *max7315_platform_data(void *info) |
| { |
| static struct pca953x_platform_data max7315_pdata[MAX7315_NUM]; |
| static int nr; |
| struct pca953x_platform_data *max7315 = &max7315_pdata[nr]; |
| struct i2c_board_info *i2c_info = info; |
| int gpio_base, intr; |
| char base_pin_name[SFI_NAME_LEN + 1]; |
| char intr_pin_name[SFI_NAME_LEN + 1]; |
| |
| if (nr == MAX7315_NUM) { |
| pr_err("too many max7315s, we only support %d\n", |
| MAX7315_NUM); |
| return NULL; |
| } |
| /* we have several max7315 on the board, we only need load several |
| * instances of the same pca953x driver to cover them |
| */ |
| strcpy(i2c_info->type, "max7315"); |
| if (nr++) { |
| sprintf(base_pin_name, "max7315_%d_base", nr); |
| sprintf(intr_pin_name, "max7315_%d_int", nr); |
| } else { |
| strcpy(base_pin_name, "max7315_base"); |
| strcpy(intr_pin_name, "max7315_int"); |
| } |
| |
| gpio_base = get_gpio_by_name(base_pin_name); |
| intr = get_gpio_by_name(intr_pin_name); |
| |
| if (gpio_base == -1) |
| return NULL; |
| max7315->gpio_base = gpio_base; |
| if (intr != -1) { |
| i2c_info->irq = intr + INTEL_MID_IRQ_OFFSET; |
| max7315->irq_base = gpio_base + INTEL_MID_IRQ_OFFSET; |
| } else { |
| i2c_info->irq = -1; |
| max7315->irq_base = -1; |
| } |
| return max7315; |
| } |
| |
| static void *tca6416_platform_data(void *info) |
| { |
| static struct pca953x_platform_data tca6416; |
| struct i2c_board_info *i2c_info = info; |
| int gpio_base, intr; |
| char base_pin_name[SFI_NAME_LEN + 1]; |
| char intr_pin_name[SFI_NAME_LEN + 1]; |
| |
| strcpy(i2c_info->type, "tca6416"); |
| strcpy(base_pin_name, "tca6416_base"); |
| strcpy(intr_pin_name, "tca6416_int"); |
| |
| gpio_base = get_gpio_by_name(base_pin_name); |
| intr = get_gpio_by_name(intr_pin_name); |
| |
| if (gpio_base == -1) |
| return NULL; |
| tca6416.gpio_base = gpio_base; |
| if (intr != -1) { |
| i2c_info->irq = intr + INTEL_MID_IRQ_OFFSET; |
| tca6416.irq_base = gpio_base + INTEL_MID_IRQ_OFFSET; |
| } else { |
| i2c_info->irq = -1; |
| tca6416.irq_base = -1; |
| } |
| return &tca6416; |
| } |
| |
| static void *mpu3050_platform_data(void *info) |
| { |
| struct i2c_board_info *i2c_info = info; |
| int intr = get_gpio_by_name("mpu3050_int"); |
| |
| if (intr == -1) |
| return NULL; |
| |
| i2c_info->irq = intr + INTEL_MID_IRQ_OFFSET; |
| return NULL; |
| } |
| |
| static void __init *emc1403_platform_data(void *info) |
| { |
| static short intr2nd_pdata; |
| struct i2c_board_info *i2c_info = info; |
| int intr = get_gpio_by_name("thermal_int"); |
| int intr2nd = get_gpio_by_name("thermal_alert"); |
| |
| if (intr == -1 || intr2nd == -1) |
| return NULL; |
| |
| i2c_info->irq = intr + INTEL_MID_IRQ_OFFSET; |
| intr2nd_pdata = intr2nd + INTEL_MID_IRQ_OFFSET; |
| |
| return &intr2nd_pdata; |
| } |
| |
| static void __init *lis331dl_platform_data(void *info) |
| { |
| static short intr2nd_pdata; |
| struct i2c_board_info *i2c_info = info; |
| int intr = get_gpio_by_name("accel_int"); |
| int intr2nd = get_gpio_by_name("accel_2"); |
| |
| if (intr == -1 || intr2nd == -1) |
| return NULL; |
| |
| i2c_info->irq = intr + INTEL_MID_IRQ_OFFSET; |
| intr2nd_pdata = intr2nd + INTEL_MID_IRQ_OFFSET; |
| |
| return &intr2nd_pdata; |
| } |
| |
| static void __init *no_platform_data(void *info) |
| { |
| return NULL; |
| } |
| |
| static struct resource msic_resources[] = { |
| { |
| .start = INTEL_MSIC_IRQ_PHYS_BASE, |
| .end = INTEL_MSIC_IRQ_PHYS_BASE + 64 - 1, |
| .flags = IORESOURCE_MEM, |
| }, |
| }; |
| |
| static struct intel_msic_platform_data msic_pdata; |
| |
| static struct platform_device msic_device = { |
| .name = "intel_msic", |
| .id = -1, |
| .dev = { |
| .platform_data = &msic_pdata, |
| }, |
| .num_resources = ARRAY_SIZE(msic_resources), |
| .resource = msic_resources, |
| }; |
| |
| static inline bool intel_mid_has_msic(void) |
| { |
| return intel_mid_identify_cpu() == INTEL_MID_CPU_CHIP_PENWELL; |
| } |
| |
| static int msic_scu_status_change(struct notifier_block *nb, |
| unsigned long code, void *data) |
| { |
| if (code == SCU_DOWN) { |
| platform_device_unregister(&msic_device); |
| return 0; |
| } |
| |
| return platform_device_register(&msic_device); |
| } |
| |
| static int __init msic_init(void) |
| { |
| static struct notifier_block msic_scu_notifier = { |
| .notifier_call = msic_scu_status_change, |
| }; |
| |
| /* |
| * We need to be sure that the SCU IPC is ready before MSIC device |
| * can be registered. |
| */ |
| if (intel_mid_has_msic()) |
| intel_scu_notifier_add(&msic_scu_notifier); |
| |
| return 0; |
| } |
| arch_initcall(msic_init); |
| |
| /* |
| * msic_generic_platform_data - sets generic platform data for the block |
| * @info: pointer to the SFI device table entry for this block |
| * @block: MSIC block |
| * |
| * Function sets IRQ number from the SFI table entry for given device to |
| * the MSIC platform data. |
| */ |
| static void *msic_generic_platform_data(void *info, enum intel_msic_block block) |
| { |
| struct sfi_device_table_entry *entry = info; |
| |
| BUG_ON(block < 0 || block >= INTEL_MSIC_BLOCK_LAST); |
| msic_pdata.irq[block] = entry->irq; |
| |
| return no_platform_data(info); |
| } |
| |
| static void *msic_battery_platform_data(void *info) |
| { |
| return msic_generic_platform_data(info, INTEL_MSIC_BLOCK_BATTERY); |
| } |
| |
| static void *msic_gpio_platform_data(void *info) |
| { |
| static struct intel_msic_gpio_pdata pdata; |
| int gpio = get_gpio_by_name("msic_gpio_base"); |
| |
| if (gpio < 0) |
| return NULL; |
| |
| pdata.gpio_base = gpio; |
| msic_pdata.gpio = &pdata; |
| |
| return msic_generic_platform_data(info, INTEL_MSIC_BLOCK_GPIO); |
| } |
| |
| static void *msic_audio_platform_data(void *info) |
| { |
| struct platform_device *pdev; |
| |
| pdev = platform_device_register_simple("sst-platform", -1, NULL, 0); |
| if (IS_ERR(pdev)) { |
| pr_err("failed to create audio platform device\n"); |
| return NULL; |
| } |
| |
| return msic_generic_platform_data(info, INTEL_MSIC_BLOCK_AUDIO); |
| } |
| |
| static void *msic_power_btn_platform_data(void *info) |
| { |
| return msic_generic_platform_data(info, INTEL_MSIC_BLOCK_POWER_BTN); |
| } |
| |
| static void *msic_ocd_platform_data(void *info) |
| { |
| static struct intel_msic_ocd_pdata pdata; |
| int gpio = get_gpio_by_name("ocd_gpio"); |
| |
| if (gpio < 0) |
| return NULL; |
| |
| pdata.gpio = gpio; |
| msic_pdata.ocd = &pdata; |
| |
| return msic_generic_platform_data(info, INTEL_MSIC_BLOCK_OCD); |
| } |
| |
| static void *msic_thermal_platform_data(void *info) |
| { |
| return msic_generic_platform_data(info, INTEL_MSIC_BLOCK_THERMAL); |
| } |
| |
| /* tc35876x DSI-LVDS bridge chip and panel platform data */ |
| static void *tc35876x_platform_data(void *data) |
| { |
| static struct tc35876x_platform_data pdata; |
| |
| /* gpio pins set to -1 will not be used by the driver */ |
| pdata.gpio_bridge_reset = get_gpio_by_name("LCMB_RXEN"); |
| pdata.gpio_panel_bl_en = get_gpio_by_name("6S6P_BL_EN"); |
| pdata.gpio_panel_vadd = get_gpio_by_name("EN_VREG_LCD_V3P3"); |
| |
| return &pdata; |
| } |
| |
| static const struct devs_id __initconst device_ids[] = { |
| {"bma023", SFI_DEV_TYPE_I2C, 1, &no_platform_data, NULL}, |
| {"pmic_gpio", SFI_DEV_TYPE_SPI, 1, &pmic_gpio_platform_data, NULL}, |
| {"pmic_gpio", SFI_DEV_TYPE_IPC, 1, &pmic_gpio_platform_data, &ipc_device_handler}, |
| {"spi_max3111", SFI_DEV_TYPE_SPI, 0, &max3111_platform_data, NULL}, |
| {"i2c_max7315", SFI_DEV_TYPE_I2C, 1, &max7315_platform_data, NULL}, |
| {"i2c_max7315_2", SFI_DEV_TYPE_I2C, 1, &max7315_platform_data, NULL}, |
| {"tca6416", SFI_DEV_TYPE_I2C, 1, &tca6416_platform_data, NULL}, |
| {"emc1403", SFI_DEV_TYPE_I2C, 1, &emc1403_platform_data, NULL}, |
| {"i2c_accel", SFI_DEV_TYPE_I2C, 0, &lis331dl_platform_data, NULL}, |
| {"pmic_audio", SFI_DEV_TYPE_IPC, 1, &no_platform_data, &ipc_device_handler}, |
| {"mpu3050", SFI_DEV_TYPE_I2C, 1, &mpu3050_platform_data, NULL}, |
| {"i2c_disp_brig", SFI_DEV_TYPE_I2C, 0, &tc35876x_platform_data, NULL}, |
| |
| /* MSIC subdevices */ |
| {"msic_battery", SFI_DEV_TYPE_IPC, 1, &msic_battery_platform_data, &ipc_device_handler}, |
| {"msic_gpio", SFI_DEV_TYPE_IPC, 1, &msic_gpio_platform_data, &ipc_device_handler}, |
| {"msic_audio", SFI_DEV_TYPE_IPC, 1, &msic_audio_platform_data, &ipc_device_handler}, |
| {"msic_power_btn", SFI_DEV_TYPE_IPC, 1, &msic_power_btn_platform_data, &ipc_device_handler}, |
| {"msic_ocd", SFI_DEV_TYPE_IPC, 1, &msic_ocd_platform_data, &ipc_device_handler}, |
| {"msic_thermal", SFI_DEV_TYPE_IPC, 1, &msic_thermal_platform_data, &ipc_device_handler}, |
| { 0 } |
| }; |
| |
| static void __init ipc_device_handler(struct sfi_device_table_entry *pentry, |
| struct devs_id *dev) |
| { |
| struct platform_device *pdev; |
| void *pdata = NULL; |
| static struct resource res __initdata = { |
| .name = "IRQ", |
| .flags = IORESOURCE_IRQ, |
| }; |
| |
| pr_debug("IPC bus, name = %16.16s, irq = 0x%2x\n", |
| pentry->name, pentry->irq); |
| |
| /* |
| * We need to call platform init of IPC devices to fill misc_pdata |
| * structure. It will be used in msic_init for initialization. |
| */ |
| if (dev != NULL) |
| pdata = dev->get_platform_data(pentry); |
| |
| /* |
| * On Medfield the platform device creation is handled by the MSIC |
| * MFD driver so we don't need to do it here. |
| */ |
| if (intel_mid_has_msic()) |
| return; |
| |
| pdev = platform_device_alloc(pentry->name, 0); |
| if (pdev == NULL) { |
| pr_err("out of memory for SFI platform device '%s'.\n", |
| pentry->name); |
| return; |
| } |
| res.start = pentry->irq; |
| platform_device_add_resources(pdev, &res, 1); |
| |
| pdev->dev.platform_data = pdata; |
| intel_scu_device_register(pdev); |
| } |
| |
| |
| /* |
| * we will search these buttons in SFI GPIO table (by name) |
| * and register them dynamically. Please add all possible |
| * buttons here, we will shrink them if no GPIO found. |
| */ |
| static struct gpio_keys_button gpio_button[] = { |
| {KEY_POWER, -1, 1, "power_btn", EV_KEY, 0, 3000}, |
| {KEY_PROG1, -1, 1, "prog_btn1", EV_KEY, 0, 20}, |
| {KEY_PROG2, -1, 1, "prog_btn2", EV_KEY, 0, 20}, |
| {SW_LID, -1, 1, "lid_switch", EV_SW, 0, 20}, |
| {KEY_VOLUMEUP, -1, 1, "vol_up", EV_KEY, 0, 20}, |
| {KEY_VOLUMEDOWN, -1, 1, "vol_down", EV_KEY, 0, 20}, |
| {KEY_CAMERA, -1, 1, "camera_full", EV_KEY, 0, 20}, |
| {KEY_CAMERA_FOCUS, -1, 1, "camera_half", EV_KEY, 0, 20}, |
| {SW_KEYPAD_SLIDE, -1, 1, "MagSw1", EV_SW, 0, 20}, |
| {SW_KEYPAD_SLIDE, -1, 1, "MagSw2", EV_SW, 0, 20}, |
| }; |
| |
| static struct gpio_keys_platform_data intel_mid_gpio_keys = { |
| .buttons = gpio_button, |
| .rep = 1, |
| .nbuttons = -1, /* will fill it after search */ |
| }; |
| |
| static struct platform_device pb_device = { |
| .name = "gpio-keys", |
| .id = -1, |
| .dev = { |
| .platform_data = &intel_mid_gpio_keys, |
| }, |
| }; |
| |
| /* |
| * Shrink the non-existent buttons, register the gpio button |
| * device if there is some |
| */ |
| static int __init pb_keys_init(void) |
| { |
| struct gpio_keys_button *gb = gpio_button; |
| int i, num, good = 0; |
| |
| num = sizeof(gpio_button) / sizeof(struct gpio_keys_button); |
| for (i = 0; i < num; i++) { |
| gb[i].gpio = get_gpio_by_name(gb[i].desc); |
| pr_debug("info[%2d]: name = %s, gpio = %d\n", i, gb[i].desc, |
| gb[i].gpio); |
| if (gb[i].gpio == -1) |
| continue; |
| |
| if (i != good) |
| gb[good] = gb[i]; |
| good++; |
| } |
| |
| if (good) { |
| intel_mid_gpio_keys.nbuttons = good; |
| return platform_device_register(&pb_device); |
| } |
| return 0; |
| } |
| late_initcall(pb_keys_init); |