/* * Keypad ladder driver for Freescale MXS boards * * Author: dmitry pervushin * * Copyright 2008-2010 Freescale Semiconductor, Inc. * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. */ /* * The code contained herein is licensed under the GNU General Public * License. You may obtain a copy of the GNU General Public License * Version 2 or later at the following locations: * * http://www.opensource.org/licenses/gpl-license.html * http://www.gnu.org/copyleft/gpl.html */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define BUTTON_PRESS_THRESHOLD 3300 #define LRADC_NOISE_MARGIN 100 /* this value represents the the lradc value at 3.3V ( 3.3V / 0.000879 V/b ) */ #define TARGET_VDDIO_LRADC_VALUE 3754 struct mxskbd_data { struct input_dev *input; int last_button; int irq; int btn_irq; struct mxskbd_keypair *keycodes; unsigned int base; int chan; unsigned int btn_enable; /* detect enable bits */ unsigned int btn_irq_stat; /* detect irq status bits */ unsigned int btn_irq_ctrl; /* detect irq enable bits */ }; static int delay1 = 500; static int delay2 = 200; static int mxskbd_open(struct input_dev *dev); static void mxskbd_close(struct input_dev *dev); static struct mxskbd_data *mxskbd_data_alloc(struct platform_device *pdev, struct mxskbd_keypair *keys) { struct mxskbd_data *d = kzalloc(sizeof(*d), GFP_KERNEL); if (!d) return NULL; if (!keys) { dev_err(&pdev->dev, "No keycodes in platform_data, bailing out.\n"); kfree(d); return NULL; } d->keycodes = keys; d->input = input_allocate_device(); if (!d->input) { kfree(d); return NULL; } d->input->phys = "onboard"; d->input->uniq = "0000'0000"; d->input->name = pdev->name; d->input->id.bustype = BUS_HOST; d->input->open = mxskbd_open; d->input->close = mxskbd_close; d->input->dev.parent = &pdev->dev; set_bit(EV_KEY, d->input->evbit); set_bit(EV_REL, d->input->evbit); set_bit(EV_REP, d->input->evbit); d->last_button = -1; while (keys->raw >= 0) { set_bit(keys->kcode, d->input->keybit); keys++; } return d; } static inline struct input_dev *GET_INPUT_DEV(struct mxskbd_data *d) { BUG_ON(!d); return d->input; } static void mxskbd_data_free(struct mxskbd_data *d) { if (!d) return; if (d->input) input_free_device(d->input); kfree(d); } static unsigned mxskbd_decode_button(struct mxskbd_keypair *codes, int raw_button) { pr_debug("Decoding %d\n", raw_button); while (codes->raw != -1) { if ((raw_button >= (codes->raw - LRADC_NOISE_MARGIN)) && (raw_button < (codes->raw + LRADC_NOISE_MARGIN))) { pr_debug("matches code 0x%x = %d\n", codes->kcode, codes->kcode); return codes->kcode; } codes++; } return (unsigned)-1; /* invalid key */ } static irqreturn_t mxskbd_irq_handler(int irq, void *dev_id) { struct platform_device *pdev = dev_id; struct mxskbd_data *devdata = platform_get_drvdata(pdev); u16 raw_button, normalized_button, vddio; unsigned btn; if (devdata->btn_irq == irq) { __raw_writel(devdata->btn_irq_stat, devdata->base + HW_LRADC_CTRL1_CLR); __raw_writel(BM_LRADC_CTRL1_LRADC0_IRQ << devdata->chan, devdata->base + HW_LRADC_CTRL1_CLR); __raw_writel(BM_LRADC_CTRL1_LRADC0_IRQ_EN << devdata->chan, devdata->base + HW_LRADC_CTRL1_SET); return IRQ_HANDLED; } raw_button = __raw_readl(devdata->base + HW_LRADC_CHn(devdata->chan)) & BM_LRADC_CHn_VALUE; vddio = hw_lradc_vddio(); BUG_ON(vddio == 0); normalized_button = (raw_button * TARGET_VDDIO_LRADC_VALUE) / vddio; if (normalized_button < BUTTON_PRESS_THRESHOLD && devdata->last_button < 0) { btn = mxskbd_decode_button(devdata->keycodes, normalized_button); if (btn < KEY_MAX) { devdata->last_button = btn; input_report_key(GET_INPUT_DEV(devdata), devdata->last_button, !0); } else dev_err(&pdev->dev, "Invalid button: raw = %d, " "normalized = %d, vddio = %d\n", raw_button, normalized_button, vddio); } else if (devdata->last_button > 0 && normalized_button >= BUTTON_PRESS_THRESHOLD) { input_report_key(GET_INPUT_DEV(devdata), devdata->last_button, 0); devdata->last_button = -1; if (devdata->btn_irq > 0) __raw_writel(BM_LRADC_CTRL1_LRADC0_IRQ_EN << devdata->chan, devdata->base + HW_LRADC_CTRL1_CLR); } __raw_writel(BM_LRADC_CTRL1_LRADC0_IRQ << devdata->chan, devdata->base + HW_LRADC_CTRL1_CLR); return IRQ_HANDLED; } static int mxskbd_open(struct input_dev *dev) { /* enable clock */ return 0; } static void mxskbd_close(struct input_dev *dev) { /* disable clock */ } static void mxskbd_hwinit(struct platform_device *pdev) { struct mxskbd_data *d = platform_get_drvdata(pdev); hw_lradc_init_ladder(d->chan, LRADC_DELAY_TRIGGER_BUTTON, 200); if (d->btn_irq > 0) { __raw_writel(d->btn_enable, d->base + HW_LRADC_CTRL0_SET); __raw_writel(d->btn_irq_ctrl, d->base + HW_LRADC_CTRL1_SET); } else { __raw_writel(BM_LRADC_CTRL1_LRADC0_IRQ << d->chan, d->base + HW_LRADC_CTRL1_CLR); __raw_writel(BM_LRADC_CTRL1_LRADC0_IRQ_EN << d->chan, d->base + HW_LRADC_CTRL1_SET); } hw_lradc_set_delay_trigger_kick(LRADC_DELAY_TRIGGER_BUTTON, !0); } #ifdef CONFIG_PM static int mxskbd_suspend(struct platform_device *pdev, pm_message_t state) { struct mxskbd_data *d = platform_get_drvdata(pdev); hw_lradc_stop_ladder(d->chan, LRADC_DELAY_TRIGGER_BUTTON); hw_lradc_set_delay_trigger_kick(LRADC_DELAY_TRIGGER_BUTTON, 0); hw_lradc_unuse_channel(d->chan); __raw_writel(BM_LRADC_CTRL1_LRADC0_IRQ_EN << d->chan, d->base + HW_LRADC_CTRL1_CLR); mxskbd_close(d->input); return 0; } static int mxskbd_resume(struct platform_device *pdev) { struct mxskbd_data *d = platform_get_drvdata(pdev); __raw_writel(BM_LRADC_CTRL1_LRADC0_IRQ_EN << d->chan, d->base + HW_LRADC_CTRL1_SET); mxskbd_open(d->input); hw_lradc_use_channel(d->chan); mxskbd_hwinit(pdev); return 0; } #endif static int __devinit mxskbd_probe(struct platform_device *pdev) { int err = 0; struct resource *res; struct mxskbd_data *d; struct mxs_kbd_plat_data *plat_data; plat_data = (struct mxs_kbd_plat_data *)pdev->dev.platform_data; if (plat_data == NULL) return -ENODEV; /* Create and register the input driver. */ d = mxskbd_data_alloc(pdev, plat_data->keypair); if (!d) { dev_err(&pdev->dev, "Cannot allocate driver structures\n"); err = -ENOMEM; goto err_out; } res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { err = -ENODEV; goto err_out; } d->base = (unsigned int)IO_ADDRESS(res->start); d->chan = plat_data->channel; d->irq = platform_get_irq(pdev, 0); d->btn_irq = platform_get_irq(pdev, 1); d->btn_enable = plat_data->btn_enable; d->btn_irq_stat = plat_data->btn_irq_stat; d->btn_irq_ctrl = plat_data->btn_irq_ctrl; platform_set_drvdata(pdev, d); err = request_irq(d->irq, mxskbd_irq_handler, IRQF_DISABLED, pdev->name, pdev); if (err) { dev_err(&pdev->dev, "Cannot request keypad IRQ\n"); goto err_free_dev; } if (d->btn_irq > 0) { err = request_irq(d->btn_irq, mxskbd_irq_handler, IRQF_DISABLED, pdev->name, pdev); if (err) { dev_err(&pdev->dev, "Cannot request keybad detect IRQ\n"); goto err_free_irq; } } /* Register the input device */ err = input_register_device(GET_INPUT_DEV(d)); if (err) goto err_free_irq2; /* these two have to be set after registering the input device */ d->input->rep[REP_DELAY] = delay1; d->input->rep[REP_PERIOD] = delay2; hw_lradc_use_channel(d->chan); mxskbd_hwinit(pdev); return 0; err_free_irq2: platform_set_drvdata(pdev, NULL); if (d->btn_irq > 0) free_irq(d->btn_irq, pdev); err_free_irq: free_irq(d->irq, pdev); err_free_dev: mxskbd_data_free(d); err_out: return err; } static int __devexit mxskbd_remove(struct platform_device *pdev) { struct mxskbd_data *d = platform_get_drvdata(pdev); hw_lradc_unuse_channel(d->chan); input_unregister_device(GET_INPUT_DEV(d)); free_irq(d->irq, pdev); if (d->btn_irq > 0) free_irq(d->btn_irq, pdev); mxskbd_data_free(d); platform_set_drvdata(pdev, NULL); return 0; } static struct platform_driver mxskbd_driver = { .probe = mxskbd_probe, .remove = __devexit_p(mxskbd_remove), #ifdef CONFIG_PM .suspend = mxskbd_suspend, .resume = mxskbd_resume, #endif .driver = { .name = "mxs-kbd", }, }; static int __init mxskbd_init(void) { return platform_driver_register(&mxskbd_driver); } static void __exit mxskbd_exit(void) { platform_driver_unregister(&mxskbd_driver); } module_init(mxskbd_init); module_exit(mxskbd_exit); MODULE_DESCRIPTION("Freescale keyboard driver for mxs family"); MODULE_AUTHOR("dmitry pervushin ") MODULE_LICENSE("GPL");