From 6d341675f8e715464849e5d5563a72c1d39e800d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Va=C5=A1ut?= Date: Sat, 16 Aug 2008 15:35:57 +0100 Subject: [ARM] 5199/1: PalmLD: PCMCIA driver PCMCIA driver for Palm LifeDrive Signed-off-by: Marek Vasut Signed-off-by: Russell King --- drivers/pcmcia/Makefile | 3 +- drivers/pcmcia/pxa2xx_palmld.c | 151 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 drivers/pcmcia/pxa2xx_palmld.c (limited to 'drivers') diff --git a/drivers/pcmcia/Makefile b/drivers/pcmcia/Makefile index 269a9e913ba..c6d89fd3dcf 100644 --- a/drivers/pcmcia/Makefile +++ b/drivers/pcmcia/Makefile @@ -72,5 +72,6 @@ pxa2xx_cs-$(CONFIG_ARCH_LUBBOCK) += pxa2xx_lubbock.o sa1111_generic.o pxa2xx_cs-$(CONFIG_MACH_MAINSTONE) += pxa2xx_mainstone.o pxa2xx_cs-$(CONFIG_PXA_SHARPSL) += pxa2xx_sharpsl.o pxa2xx_cs-$(CONFIG_MACH_ARMCORE) += pxa2xx_cm_x270.o -pxa2xx_cs-$(CONFIG_MACH_PALMTX) += pxa2xx_palmtx.o +pxa2xx_cs-$(CONFIG_MACH_PALMTX) += pxa2xx_palmtx.o +pxa2xx_cs-$(CONFIG_MACH_PALMLD) += pxa2xx_palmld.o diff --git a/drivers/pcmcia/pxa2xx_palmld.c b/drivers/pcmcia/pxa2xx_palmld.c new file mode 100644 index 00000000000..1736c67e547 --- /dev/null +++ b/drivers/pcmcia/pxa2xx_palmld.c @@ -0,0 +1,151 @@ +/* + * linux/drivers/pcmcia/pxa2xx_palmld.c + * + * Driver for Palm LifeDrive PCMCIA + * + * Copyright (C) 2006 Alex Osborne + * Copyright (C) 2007-2008 Marek Vasut + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include + +#include +#include +#include "soc_common.h" + +static int palmld_pcmcia_hw_init(struct soc_pcmcia_socket *skt) +{ + int ret; + + ret = gpio_request(GPIO_NR_PALMLD_PCMCIA_POWER, "PCMCIA PWR"); + if (ret) + goto err1; + ret = gpio_direction_output(GPIO_NR_PALMLD_PCMCIA_POWER, 0); + if (ret) + goto err2; + + ret = gpio_request(GPIO_NR_PALMLD_PCMCIA_RESET, "PCMCIA RST"); + if (ret) + goto err2; + ret = gpio_direction_output(GPIO_NR_PALMLD_PCMCIA_RESET, 1); + if (ret) + goto err3; + + ret = gpio_request(GPIO_NR_PALMLD_PCMCIA_READY, "PCMCIA RDY"); + if (ret) + goto err3; + ret = gpio_direction_input(GPIO_NR_PALMLD_PCMCIA_READY); + if (ret) + goto err4; + + skt->irq = IRQ_GPIO(GPIO_NR_PALMLD_PCMCIA_READY); + return 0; + +err4: + gpio_free(GPIO_NR_PALMLD_PCMCIA_READY); +err3: + gpio_free(GPIO_NR_PALMLD_PCMCIA_RESET); +err2: + gpio_free(GPIO_NR_PALMLD_PCMCIA_POWER); +err1: + return ret; +} + +static void palmld_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt) +{ + gpio_free(GPIO_NR_PALMLD_PCMCIA_READY); + gpio_free(GPIO_NR_PALMLD_PCMCIA_RESET); + gpio_free(GPIO_NR_PALMLD_PCMCIA_POWER); +} + +static void palmld_pcmcia_socket_state(struct soc_pcmcia_socket *skt, + struct pcmcia_state *state) +{ + state->detect = 1; /* always inserted */ + state->ready = !!gpio_get_value(GPIO_NR_PALMLD_PCMCIA_READY); + state->bvd1 = 1; + state->bvd2 = 1; + state->wrprot = 0; + state->vs_3v = 1; + state->vs_Xv = 0; +} + +static int palmld_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, + const socket_state_t *state) +{ + gpio_set_value(GPIO_NR_PALMLD_PCMCIA_POWER, 1); + gpio_set_value(GPIO_NR_PALMLD_PCMCIA_RESET, + !!(state->flags & SS_RESET)); + + return 0; +} + +static void palmld_pcmcia_socket_init(struct soc_pcmcia_socket *skt) +{ +} + +static void palmld_pcmcia_socket_suspend(struct soc_pcmcia_socket *skt) +{ +} + +static struct pcmcia_low_level palmld_pcmcia_ops = { + .owner = THIS_MODULE, + + .first = 0, + .nr = 2, + + .hw_init = palmld_pcmcia_hw_init, + .hw_shutdown = palmld_pcmcia_hw_shutdown, + + .socket_state = palmld_pcmcia_socket_state, + .configure_socket = palmld_pcmcia_configure_socket, + + .socket_init = palmld_pcmcia_socket_init, + .socket_suspend = palmld_pcmcia_socket_suspend, +}; + +static struct platform_device *palmld_pcmcia_device; + +static int __init palmld_pcmcia_init(void) +{ + int ret; + + if (!machine_is_palmld()) + return -ENODEV; + + palmld_pcmcia_device = platform_device_alloc("pxa2xx-pcmcia", -1); + if (!palmld_pcmcia_device) + return -ENOMEM; + + ret = platform_device_add_data(palmld_pcmcia_device, &palmld_pcmcia_ops, + sizeof(palmld_pcmcia_ops)); + + if (!ret) + ret = platform_device_add(palmld_pcmcia_device); + + if (ret) + platform_device_put(palmld_pcmcia_device); + + return ret; +} + +static void __exit palmld_pcmcia_exit(void) +{ + platform_device_unregister(palmld_pcmcia_device); +} + +module_init(palmld_pcmcia_init); +module_exit(palmld_pcmcia_exit); + +MODULE_AUTHOR("Alex Osborne ," + " Marek Vasut "); +MODULE_DESCRIPTION("PCMCIA support for Palm LifeDrive"); +MODULE_ALIAS("platform:pxa2xx-pcmcia"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From d9105c2b01eedb620cae96073dde4f760367817f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Va=C5=A1ut?= Date: Sun, 3 Aug 2008 21:34:08 +0100 Subject: [ARM] 5184/1: Split ucb1400_ts into core and touchscreen This patch splits ucb1400_ts into ucb1400_ts and ucb1400_core. Since this chip supports more features than only touchscreen, it was necessary to prepare it for feature addition. The previous functionality is preserved by applying this patch. [Build fixes for non-ARM by Stephen Rothwell and Takashi Iwai] Signed-off-by: Marek Vasut Signed-off-by: Russell King --- drivers/input/touchscreen/Kconfig | 1 + drivers/input/touchscreen/ucb1400_ts.c | 382 ++++++++++++--------------------- drivers/mfd/Kconfig | 9 + drivers/mfd/Makefile | 1 + drivers/mfd/ucb1400_core.c | 106 +++++++++ 5 files changed, 251 insertions(+), 248 deletions(-) create mode 100644 drivers/mfd/ucb1400_core.c (limited to 'drivers') diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index 6e60a97a234..fcabff9b39b 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -220,6 +220,7 @@ config TOUCHSCREEN_ATMEL_TSADCC config TOUCHSCREEN_UCB1400 tristate "Philips UCB1400 touchscreen" select AC97_BUS + depends on UCB1400_CORE help This enables support for the Philips UCB1400 touchscreen interface. The UCB1400 is an AC97 audio codec. The touchscreen interface diff --git a/drivers/input/touchscreen/ucb1400_ts.c b/drivers/input/touchscreen/ucb1400_ts.c index bce018e45bc..54986627def 100644 --- a/drivers/input/touchscreen/ucb1400_ts.c +++ b/drivers/input/touchscreen/ucb1400_ts.c @@ -5,6 +5,10 @@ * Created: September 25, 2006 * Copyright: MontaVista Software, Inc. * + * Spliting done by: Marek Vasut + * If something doesnt work and it worked before spliting, e-mail me, + * dont bother Nicolas please ;-) + * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. @@ -25,124 +29,16 @@ #include #include #include - -#include -#include - - -/* - * Interesting UCB1400 AC-link registers - */ - -#define UCB_IE_RIS 0x5e -#define UCB_IE_FAL 0x60 -#define UCB_IE_STATUS 0x62 -#define UCB_IE_CLEAR 0x62 -#define UCB_IE_ADC (1 << 11) -#define UCB_IE_TSPX (1 << 12) - -#define UCB_TS_CR 0x64 -#define UCB_TS_CR_TSMX_POW (1 << 0) -#define UCB_TS_CR_TSPX_POW (1 << 1) -#define UCB_TS_CR_TSMY_POW (1 << 2) -#define UCB_TS_CR_TSPY_POW (1 << 3) -#define UCB_TS_CR_TSMX_GND (1 << 4) -#define UCB_TS_CR_TSPX_GND (1 << 5) -#define UCB_TS_CR_TSMY_GND (1 << 6) -#define UCB_TS_CR_TSPY_GND (1 << 7) -#define UCB_TS_CR_MODE_INT (0 << 8) -#define UCB_TS_CR_MODE_PRES (1 << 8) -#define UCB_TS_CR_MODE_POS (2 << 8) -#define UCB_TS_CR_BIAS_ENA (1 << 11) -#define UCB_TS_CR_TSPX_LOW (1 << 12) -#define UCB_TS_CR_TSMX_LOW (1 << 13) - -#define UCB_ADC_CR 0x66 -#define UCB_ADC_SYNC_ENA (1 << 0) -#define UCB_ADC_VREFBYP_CON (1 << 1) -#define UCB_ADC_INP_TSPX (0 << 2) -#define UCB_ADC_INP_TSMX (1 << 2) -#define UCB_ADC_INP_TSPY (2 << 2) -#define UCB_ADC_INP_TSMY (3 << 2) -#define UCB_ADC_INP_AD0 (4 << 2) -#define UCB_ADC_INP_AD1 (5 << 2) -#define UCB_ADC_INP_AD2 (6 << 2) -#define UCB_ADC_INP_AD3 (7 << 2) -#define UCB_ADC_EXT_REF (1 << 5) -#define UCB_ADC_START (1 << 7) -#define UCB_ADC_ENA (1 << 15) - -#define UCB_ADC_DATA 0x68 -#define UCB_ADC_DAT_VALID (1 << 15) -#define UCB_ADC_DAT_VALUE(x) ((x) & 0x3ff) - -#define UCB_ID 0x7e -#define UCB_ID_1400 0x4304 - - -struct ucb1400 { - struct snd_ac97 *ac97; - struct input_dev *ts_idev; - - int irq; - - wait_queue_head_t ts_wait; - struct task_struct *ts_task; - - unsigned int irq_pending; /* not bit field shared */ - unsigned int ts_restart:1; - unsigned int adcsync:1; -}; +#include static int adcsync; static int ts_delay = 55; /* us */ static int ts_delay_pressure; /* us */ -static inline u16 ucb1400_reg_read(struct ucb1400 *ucb, u16 reg) -{ - return ucb->ac97->bus->ops->read(ucb->ac97, reg); -} - -static inline void ucb1400_reg_write(struct ucb1400 *ucb, u16 reg, u16 val) -{ - ucb->ac97->bus->ops->write(ucb->ac97, reg, val); -} - -static inline void ucb1400_adc_enable(struct ucb1400 *ucb) -{ - ucb1400_reg_write(ucb, UCB_ADC_CR, UCB_ADC_ENA); -} - -static unsigned int ucb1400_adc_read(struct ucb1400 *ucb, u16 adc_channel) -{ - unsigned int val; - - if (ucb->adcsync) - adc_channel |= UCB_ADC_SYNC_ENA; - - ucb1400_reg_write(ucb, UCB_ADC_CR, UCB_ADC_ENA | adc_channel); - ucb1400_reg_write(ucb, UCB_ADC_CR, UCB_ADC_ENA | adc_channel | UCB_ADC_START); - - for (;;) { - val = ucb1400_reg_read(ucb, UCB_ADC_DATA); - if (val & UCB_ADC_DAT_VALID) - break; - /* yield to other processes */ - schedule_timeout_uninterruptible(1); - } - - return UCB_ADC_DAT_VALUE(val); -} - -static inline void ucb1400_adc_disable(struct ucb1400 *ucb) -{ - ucb1400_reg_write(ucb, UCB_ADC_CR, 0); -} - /* Switch to interrupt mode. */ -static inline void ucb1400_ts_mode_int(struct ucb1400 *ucb) +static inline void ucb1400_ts_mode_int(struct snd_ac97 *ac97) { - ucb1400_reg_write(ucb, UCB_TS_CR, + ucb1400_reg_write(ac97, UCB_TS_CR, UCB_TS_CR_TSMX_POW | UCB_TS_CR_TSPX_POW | UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_GND | UCB_TS_CR_MODE_INT); @@ -152,14 +48,14 @@ static inline void ucb1400_ts_mode_int(struct ucb1400 *ucb) * Switch to pressure mode, and read pressure. We don't need to wait * here, since both plates are being driven. */ -static inline unsigned int ucb1400_ts_read_pressure(struct ucb1400 *ucb) +static inline unsigned int ucb1400_ts_read_pressure(struct ucb1400_ts *ucb) { - ucb1400_reg_write(ucb, UCB_TS_CR, + ucb1400_reg_write(ucb->ac97, UCB_TS_CR, UCB_TS_CR_TSMX_POW | UCB_TS_CR_TSPX_POW | UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_GND | UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); udelay(ts_delay_pressure); - return ucb1400_adc_read(ucb, UCB_ADC_INP_TSPY); + return ucb1400_adc_read(ucb->ac97, UCB_ADC_INP_TSPY, adcsync); } /* @@ -168,21 +64,21 @@ static inline unsigned int ucb1400_ts_read_pressure(struct ucb1400 *ucb) * gives a faster response time. Even so, we need to wait about 55us * for things to stabilise. */ -static inline unsigned int ucb1400_ts_read_xpos(struct ucb1400 *ucb) +static inline unsigned int ucb1400_ts_read_xpos(struct ucb1400_ts *ucb) { - ucb1400_reg_write(ucb, UCB_TS_CR, + ucb1400_reg_write(ucb->ac97, UCB_TS_CR, UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW | UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); - ucb1400_reg_write(ucb, UCB_TS_CR, + ucb1400_reg_write(ucb->ac97, UCB_TS_CR, UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW | UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); - ucb1400_reg_write(ucb, UCB_TS_CR, + ucb1400_reg_write(ucb->ac97, UCB_TS_CR, UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW | UCB_TS_CR_MODE_POS | UCB_TS_CR_BIAS_ENA); udelay(ts_delay); - return ucb1400_adc_read(ucb, UCB_ADC_INP_TSPY); + return ucb1400_adc_read(ucb->ac97, UCB_ADC_INP_TSPY, adcsync); } /* @@ -191,63 +87,63 @@ static inline unsigned int ucb1400_ts_read_xpos(struct ucb1400 *ucb) * gives a faster response time. Even so, we need to wait about 55us * for things to stabilise. */ -static inline unsigned int ucb1400_ts_read_ypos(struct ucb1400 *ucb) +static inline unsigned int ucb1400_ts_read_ypos(struct ucb1400_ts *ucb) { - ucb1400_reg_write(ucb, UCB_TS_CR, + ucb1400_reg_write(ucb->ac97, UCB_TS_CR, UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW | UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); - ucb1400_reg_write(ucb, UCB_TS_CR, + ucb1400_reg_write(ucb->ac97, UCB_TS_CR, UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW | UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); - ucb1400_reg_write(ucb, UCB_TS_CR, + ucb1400_reg_write(ucb->ac97, UCB_TS_CR, UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW | UCB_TS_CR_MODE_POS | UCB_TS_CR_BIAS_ENA); udelay(ts_delay); - return ucb1400_adc_read(ucb, UCB_ADC_INP_TSPX); + return ucb1400_adc_read(ucb->ac97, UCB_ADC_INP_TSPX, adcsync); } /* * Switch to X plate resistance mode. Set MX to ground, PX to * supply. Measure current. */ -static inline unsigned int ucb1400_ts_read_xres(struct ucb1400 *ucb) +static inline unsigned int ucb1400_ts_read_xres(struct ucb1400_ts *ucb) { - ucb1400_reg_write(ucb, UCB_TS_CR, + ucb1400_reg_write(ucb->ac97, UCB_TS_CR, UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW | UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); - return ucb1400_adc_read(ucb, 0); + return ucb1400_adc_read(ucb->ac97, 0, adcsync); } /* * Switch to Y plate resistance mode. Set MY to ground, PY to * supply. Measure current. */ -static inline unsigned int ucb1400_ts_read_yres(struct ucb1400 *ucb) +static inline unsigned int ucb1400_ts_read_yres(struct ucb1400_ts *ucb) { - ucb1400_reg_write(ucb, UCB_TS_CR, + ucb1400_reg_write(ucb->ac97, UCB_TS_CR, UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW | UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); - return ucb1400_adc_read(ucb, 0); + return ucb1400_adc_read(ucb->ac97, 0, adcsync); } -static inline int ucb1400_ts_pen_down(struct ucb1400 *ucb) +static inline int ucb1400_ts_pen_down(struct snd_ac97 *ac97) { - unsigned short val = ucb1400_reg_read(ucb, UCB_TS_CR); - return (val & (UCB_TS_CR_TSPX_LOW | UCB_TS_CR_TSMX_LOW)); + unsigned short val = ucb1400_reg_read(ac97, UCB_TS_CR); + return val & (UCB_TS_CR_TSPX_LOW | UCB_TS_CR_TSMX_LOW); } -static inline void ucb1400_ts_irq_enable(struct ucb1400 *ucb) +static inline void ucb1400_ts_irq_enable(struct snd_ac97 *ac97) { - ucb1400_reg_write(ucb, UCB_IE_CLEAR, UCB_IE_TSPX); - ucb1400_reg_write(ucb, UCB_IE_CLEAR, 0); - ucb1400_reg_write(ucb, UCB_IE_FAL, UCB_IE_TSPX); + ucb1400_reg_write(ac97, UCB_IE_CLEAR, UCB_IE_TSPX); + ucb1400_reg_write(ac97, UCB_IE_CLEAR, 0); + ucb1400_reg_write(ac97, UCB_IE_FAL, UCB_IE_TSPX); } -static inline void ucb1400_ts_irq_disable(struct ucb1400 *ucb) +static inline void ucb1400_ts_irq_disable(struct snd_ac97 *ac97) { - ucb1400_reg_write(ucb, UCB_IE_FAL, 0); + ucb1400_reg_write(ac97, UCB_IE_FAL, 0); } static void ucb1400_ts_evt_add(struct input_dev *idev, u16 pressure, u16 x, u16 y) @@ -264,25 +160,24 @@ static void ucb1400_ts_event_release(struct input_dev *idev) input_sync(idev); } -static void ucb1400_handle_pending_irq(struct ucb1400 *ucb) +static void ucb1400_handle_pending_irq(struct ucb1400_ts *ucb) { unsigned int isr; - isr = ucb1400_reg_read(ucb, UCB_IE_STATUS); - ucb1400_reg_write(ucb, UCB_IE_CLEAR, isr); - ucb1400_reg_write(ucb, UCB_IE_CLEAR, 0); + isr = ucb1400_reg_read(ucb->ac97, UCB_IE_STATUS); + ucb1400_reg_write(ucb->ac97, UCB_IE_CLEAR, isr); + ucb1400_reg_write(ucb->ac97, UCB_IE_CLEAR, 0); - if (isr & UCB_IE_TSPX) - ucb1400_ts_irq_disable(ucb); - else + if (isr & UCB_IE_TSPX) { + ucb1400_ts_irq_disable(ucb->ac97); + enable_irq(ucb->irq); + } else printk(KERN_ERR "ucb1400: unexpected IE_STATUS = %#x\n", isr); - - enable_irq(ucb->irq); } static int ucb1400_ts_thread(void *_ucb) { - struct ucb1400 *ucb = _ucb; + struct ucb1400_ts *ucb = _ucb; struct task_struct *tsk = current; int valid = 0; struct sched_param param = { .sched_priority = 1 }; @@ -301,19 +196,19 @@ static int ucb1400_ts_thread(void *_ucb) ucb1400_handle_pending_irq(ucb); } - ucb1400_adc_enable(ucb); + ucb1400_adc_enable(ucb->ac97); x = ucb1400_ts_read_xpos(ucb); y = ucb1400_ts_read_ypos(ucb); p = ucb1400_ts_read_pressure(ucb); - ucb1400_adc_disable(ucb); + ucb1400_adc_disable(ucb->ac97); /* Switch back to interrupt mode. */ - ucb1400_ts_mode_int(ucb); + ucb1400_ts_mode_int(ucb->ac97); msleep(10); - if (ucb1400_ts_pen_down(ucb)) { - ucb1400_ts_irq_enable(ucb); + if (ucb1400_ts_pen_down(ucb->ac97)) { + ucb1400_ts_irq_enable(ucb->ac97); /* * If we spat out a valid sample set last time, @@ -332,8 +227,8 @@ static int ucb1400_ts_thread(void *_ucb) } wait_event_freezable_timeout(ucb->ts_wait, - ucb->irq_pending || ucb->ts_restart || kthread_should_stop(), - timeout); + ucb->irq_pending || ucb->ts_restart || + kthread_should_stop(), timeout); } /* Send the "pen off" if we are stopping with the pen still active */ @@ -356,7 +251,7 @@ static int ucb1400_ts_thread(void *_ucb) */ static irqreturn_t ucb1400_hard_irq(int irqnr, void *devid) { - struct ucb1400 *ucb = devid; + struct ucb1400_ts *ucb = devid; if (irqnr == ucb->irq) { disable_irq(ucb->irq); @@ -369,7 +264,7 @@ static irqreturn_t ucb1400_hard_irq(int irqnr, void *devid) static int ucb1400_ts_open(struct input_dev *idev) { - struct ucb1400 *ucb = input_get_drvdata(idev); + struct ucb1400_ts *ucb = input_get_drvdata(idev); int ret = 0; BUG_ON(ucb->ts_task); @@ -385,34 +280,14 @@ static int ucb1400_ts_open(struct input_dev *idev) static void ucb1400_ts_close(struct input_dev *idev) { - struct ucb1400 *ucb = input_get_drvdata(idev); + struct ucb1400_ts *ucb = input_get_drvdata(idev); if (ucb->ts_task) kthread_stop(ucb->ts_task); - ucb1400_ts_irq_disable(ucb); - ucb1400_reg_write(ucb, UCB_TS_CR, 0); -} - -#ifdef CONFIG_PM -static int ucb1400_ts_resume(struct device *dev) -{ - struct ucb1400 *ucb = dev_get_drvdata(dev); - - if (ucb->ts_task) { - /* - * Restart the TS thread to ensure the - * TS interrupt mode is set up again - * after sleep. - */ - ucb->ts_restart = 1; - wake_up(&ucb->ts_wait); - } - return 0; + ucb1400_ts_irq_disable(ucb->ac97); + ucb1400_reg_write(ucb->ac97, UCB_TS_CR, 0); } -#else -#define ucb1400_ts_resume NULL -#endif #ifndef NO_IRQ #define NO_IRQ 0 @@ -422,25 +297,26 @@ static int ucb1400_ts_resume(struct device *dev) * Try to probe our interrupt, rather than relying on lots of * hard-coded machine dependencies. */ -static int ucb1400_detect_irq(struct ucb1400 *ucb) +static int ucb1400_ts_detect_irq(struct ucb1400_ts *ucb) { unsigned long mask, timeout; mask = probe_irq_on(); /* Enable the ADC interrupt. */ - ucb1400_reg_write(ucb, UCB_IE_RIS, UCB_IE_ADC); - ucb1400_reg_write(ucb, UCB_IE_FAL, UCB_IE_ADC); - ucb1400_reg_write(ucb, UCB_IE_CLEAR, 0xffff); - ucb1400_reg_write(ucb, UCB_IE_CLEAR, 0); + ucb1400_reg_write(ucb->ac97, UCB_IE_RIS, UCB_IE_ADC); + ucb1400_reg_write(ucb->ac97, UCB_IE_FAL, UCB_IE_ADC); + ucb1400_reg_write(ucb->ac97, UCB_IE_CLEAR, 0xffff); + ucb1400_reg_write(ucb->ac97, UCB_IE_CLEAR, 0); /* Cause an ADC interrupt. */ - ucb1400_reg_write(ucb, UCB_ADC_CR, UCB_ADC_ENA); - ucb1400_reg_write(ucb, UCB_ADC_CR, UCB_ADC_ENA | UCB_ADC_START); + ucb1400_reg_write(ucb->ac97, UCB_ADC_CR, UCB_ADC_ENA); + ucb1400_reg_write(ucb->ac97, UCB_ADC_CR, UCB_ADC_ENA | UCB_ADC_START); /* Wait for the conversion to complete. */ timeout = jiffies + HZ/2; - while (!(ucb1400_reg_read(ucb, UCB_ADC_DATA) & UCB_ADC_DAT_VALID)) { + while (!(ucb1400_reg_read(ucb->ac97, UCB_ADC_DATA) & + UCB_ADC_DAT_VALID)) { cpu_relax(); if (time_after(jiffies, timeout)) { printk(KERN_ERR "ucb1400: timed out in IRQ probe\n"); @@ -448,13 +324,13 @@ static int ucb1400_detect_irq(struct ucb1400 *ucb) return -ENODEV; } } - ucb1400_reg_write(ucb, UCB_ADC_CR, 0); + ucb1400_reg_write(ucb->ac97, UCB_ADC_CR, 0); /* Disable and clear interrupt. */ - ucb1400_reg_write(ucb, UCB_IE_RIS, 0); - ucb1400_reg_write(ucb, UCB_IE_FAL, 0); - ucb1400_reg_write(ucb, UCB_IE_CLEAR, 0xffff); - ucb1400_reg_write(ucb, UCB_IE_CLEAR, 0); + ucb1400_reg_write(ucb->ac97, UCB_IE_RIS, 0); + ucb1400_reg_write(ucb->ac97, UCB_IE_FAL, 0); + ucb1400_reg_write(ucb->ac97, UCB_IE_CLEAR, 0xffff); + ucb1400_reg_write(ucb->ac97, UCB_IE_CLEAR, 0); /* Read triggered interrupt. */ ucb->irq = probe_irq_off(mask); @@ -464,36 +340,25 @@ static int ucb1400_detect_irq(struct ucb1400 *ucb) return 0; } -static int ucb1400_ts_probe(struct device *dev) +static int ucb1400_ts_probe(struct platform_device *dev) { - struct ucb1400 *ucb; - struct input_dev *idev; - int error, id, x_res, y_res; + int error, x_res, y_res; + struct ucb1400_ts *ucb = dev->dev.platform_data; - ucb = kzalloc(sizeof(struct ucb1400), GFP_KERNEL); - idev = input_allocate_device(); - if (!ucb || !idev) { + ucb->ts_idev = input_allocate_device(); + if (!ucb->ts_idev) { error = -ENOMEM; - goto err_free_devs; + goto err; } - ucb->ts_idev = idev; - ucb->adcsync = adcsync; - ucb->ac97 = to_ac97_t(dev); - init_waitqueue_head(&ucb->ts_wait); - - id = ucb1400_reg_read(ucb, UCB_ID); - if (id != UCB_ID_1400) { - error = -ENODEV; - goto err_free_devs; - } - - error = ucb1400_detect_irq(ucb); + error = ucb1400_ts_detect_irq(ucb); if (error) { printk(KERN_ERR "UCB1400: IRQ probe failed\n"); goto err_free_devs; } + init_waitqueue_head(&ucb->ts_wait); + error = request_irq(ucb->irq, ucb1400_hard_irq, IRQF_TRIGGER_RISING, "UCB1400", ucb); if (error) { @@ -503,80 +368,101 @@ static int ucb1400_ts_probe(struct device *dev) } printk(KERN_DEBUG "UCB1400: found IRQ %d\n", ucb->irq); - input_set_drvdata(idev, ucb); + input_set_drvdata(ucb->ts_idev, ucb); - idev->dev.parent = dev; - idev->name = "UCB1400 touchscreen interface"; - idev->id.vendor = ucb1400_reg_read(ucb, AC97_VENDOR_ID1); - idev->id.product = id; - idev->open = ucb1400_ts_open; - idev->close = ucb1400_ts_close; - idev->evbit[0] = BIT_MASK(EV_ABS); + ucb->ts_idev->dev.parent = &dev->dev; + ucb->ts_idev->name = "UCB1400 touchscreen interface"; + ucb->ts_idev->id.vendor = ucb1400_reg_read(ucb->ac97, + AC97_VENDOR_ID1); + ucb->ts_idev->id.product = ucb->id; + ucb->ts_idev->open = ucb1400_ts_open; + ucb->ts_idev->close = ucb1400_ts_close; + ucb->ts_idev->evbit[0] = BIT_MASK(EV_ABS); - ucb1400_adc_enable(ucb); + ucb1400_adc_enable(ucb->ac97); x_res = ucb1400_ts_read_xres(ucb); y_res = ucb1400_ts_read_yres(ucb); - ucb1400_adc_disable(ucb); + ucb1400_adc_disable(ucb->ac97); printk(KERN_DEBUG "UCB1400: x/y = %d/%d\n", x_res, y_res); - input_set_abs_params(idev, ABS_X, 0, x_res, 0, 0); - input_set_abs_params(idev, ABS_Y, 0, y_res, 0, 0); - input_set_abs_params(idev, ABS_PRESSURE, 0, 0, 0, 0); + input_set_abs_params(ucb->ts_idev, ABS_X, 0, x_res, 0, 0); + input_set_abs_params(ucb->ts_idev, ABS_Y, 0, y_res, 0, 0); + input_set_abs_params(ucb->ts_idev, ABS_PRESSURE, 0, 0, 0, 0); - error = input_register_device(idev); + error = input_register_device(ucb->ts_idev); if (error) goto err_free_irq; - dev_set_drvdata(dev, ucb); return 0; - err_free_irq: +err_free_irq: free_irq(ucb->irq, ucb); - err_free_devs: - input_free_device(idev); - kfree(ucb); +err_free_devs: + input_free_device(ucb->ts_idev); +err: return error; + } -static int ucb1400_ts_remove(struct device *dev) +static int ucb1400_ts_remove(struct platform_device *dev) { - struct ucb1400 *ucb = dev_get_drvdata(dev); + struct ucb1400_ts *ucb = dev->dev.platform_data; free_irq(ucb->irq, ucb); input_unregister_device(ucb->ts_idev); - dev_set_drvdata(dev, NULL); - kfree(ucb); return 0; } -static struct device_driver ucb1400_ts_driver = { - .name = "ucb1400_ts", - .owner = THIS_MODULE, - .bus = &ac97_bus_type, - .probe = ucb1400_ts_probe, - .remove = ucb1400_ts_remove, - .resume = ucb1400_ts_resume, +#ifdef CONFIG_PM +static int ucb1400_ts_resume(struct platform_device *dev) +{ + struct ucb1400_ts *ucb = platform_get_drvdata(dev); + + if (ucb->ts_task) { + /* + * Restart the TS thread to ensure the + * TS interrupt mode is set up again + * after sleep. + */ + ucb->ts_restart = 1; + wake_up(&ucb->ts_wait); + } + return 0; +} +#else +#define ucb1400_ts_resume NULL +#endif + +static struct platform_driver ucb1400_ts_driver = { + .probe = ucb1400_ts_probe, + .remove = ucb1400_ts_remove, + .resume = ucb1400_ts_resume, + .driver = { + .name = "ucb1400_ts", + }, }; static int __init ucb1400_ts_init(void) { - return driver_register(&ucb1400_ts_driver); + return platform_driver_register(&ucb1400_ts_driver); } static void __exit ucb1400_ts_exit(void) { - driver_unregister(&ucb1400_ts_driver); + platform_driver_unregister(&ucb1400_ts_driver); } module_param(adcsync, bool, 0444); MODULE_PARM_DESC(adcsync, "Synchronize touch readings with ADCSYNC pin."); module_param(ts_delay, int, 0444); -MODULE_PARM_DESC(ts_delay, "Delay between panel setup and position read. Default = 55us."); +MODULE_PARM_DESC(ts_delay, "Delay between panel setup and" + " position read. Default = 55us."); module_param(ts_delay_pressure, int, 0444); MODULE_PARM_DESC(ts_delay_pressure, - "delay between panel setup and pressure read. Default = 0us."); + "delay between panel setup and pressure read." + " Default = 0us."); module_init(ucb1400_ts_init); module_exit(ucb1400_ts_exit); diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 883e7ea31de..371d22a98f1 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -50,6 +50,15 @@ config HTC_PASIC3 HTC Magician devices, respectively. Actual functionality is handled by the leds-pasic3 and ds1wm drivers. +config UCB1400_CORE + tristate "Philips UCB1400 Core driver" + help + This enables support for the Philips UCB1400 core functions. + The UCB1400 is an AC97 audio codec. + + To compile this driver as a module, choose M here: the + module will be called ucb1400_core. + config MFD_TC6393XB bool "Support Toshiba TC6393XB" depends on GPIOLIB && ARM diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 33daa2f45dd..f7cfd5b4119 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -20,3 +20,4 @@ obj-$(CONFIG_MCP_UCB1200_TS) += ucb1x00-ts.o ifeq ($(CONFIG_SA1100_ASSABET),y) obj-$(CONFIG_MCP_UCB1200) += ucb1x00-assabet.o endif +obj-$(CONFIG_UCB1400_CORE) += ucb1400_core.o diff --git a/drivers/mfd/ucb1400_core.c b/drivers/mfd/ucb1400_core.c new file mode 100644 index 00000000000..178159e264c --- /dev/null +++ b/drivers/mfd/ucb1400_core.c @@ -0,0 +1,106 @@ +/* + * Core functions for: + * Philips UCB1400 multifunction chip + * + * Based on ucb1400_ts.c: + * Author: Nicolas Pitre + * Created: September 25, 2006 + * Copyright: MontaVista Software, Inc. + * + * Spliting done by: Marek Vasut + * If something doesnt work and it worked before spliting, e-mail me, + * dont bother Nicolas please ;-) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This code is heavily based on ucb1x00-*.c copyrighted by Russell King + * covering the UCB1100, UCB1200 and UCB1300.. Support for the UCB1400 has + * been made separate from ucb1x00-core/ucb1x00-ts on Russell's request. + */ + +#include +#include + +static int ucb1400_core_probe(struct device *dev) +{ + int err; + struct ucb1400 *ucb; + struct ucb1400_ts ucb_ts; + struct snd_ac97 *ac97; + + memset(&ucb_ts, 0, sizeof(ucb_ts)); + + ucb = kzalloc(sizeof(struct ucb1400), GFP_KERNEL); + if (!ucb) { + err = -ENOMEM; + goto err; + } + + dev_set_drvdata(dev, ucb); + + ac97 = to_ac97_t(dev); + + ucb_ts.id = ucb1400_reg_read(ac97, UCB_ID); + if (ucb_ts.id != UCB_ID_1400) { + err = -ENODEV; + goto err0; + } + + /* TOUCHSCREEN */ + ucb_ts.ac97 = ac97; + ucb->ucb1400_ts = platform_device_alloc("ucb1400_ts", -1); + if (!ucb->ucb1400_ts) { + err = -ENOMEM; + goto err0; + } + err = platform_device_add_data(ucb->ucb1400_ts, &ucb_ts, + sizeof(ucb_ts)); + if (err) + goto err1; + err = platform_device_add(ucb->ucb1400_ts); + if (err) + goto err1; + + return 0; + +err1: + platform_device_put(ucb->ucb1400_ts); +err0: + kfree(ucb); +err: + return err; +} + +static int ucb1400_core_remove(struct device *dev) +{ + struct ucb1400 *ucb = dev_get_drvdata(dev); + + platform_device_unregister(ucb->ucb1400_ts); + kfree(ucb); + return 0; +} + +static struct device_driver ucb1400_core_driver = { + .name = "ucb1400_core", + .bus = &ac97_bus_type, + .probe = ucb1400_core_probe, + .remove = ucb1400_core_remove, +}; + +static int __init ucb1400_core_init(void) +{ + return driver_register(&ucb1400_core_driver); +} + +static void __exit ucb1400_core_exit(void) +{ + driver_unregister(&ucb1400_core_driver); +} + +module_init(ucb1400_core_init); +module_exit(ucb1400_core_exit); + +MODULE_DESCRIPTION("Philips UCB1400 driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 4e9687d9c843dc34d368358a36f5f1610e4fbab3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Va=C5=A1ut?= Date: Thu, 11 Sep 2008 19:37:32 +0100 Subject: [ARM] 5248/1: wm97xx generic battery driver This patch adds generic battery driver for wm97xx chips. Signed-off-by: Marek Vasut Acked-by: Anton Vorontsov Acked-by: Mark Brown Signed-off-by: Russell King --- drivers/power/Kconfig | 8 +- drivers/power/Makefile | 2 +- drivers/power/palmtx_battery.c | 198 ------------------------------ drivers/power/wm97xx_battery.c | 272 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 277 insertions(+), 203 deletions(-) delete mode 100644 drivers/power/palmtx_battery.c create mode 100644 drivers/power/wm97xx_battery.c (limited to 'drivers') diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 9ce55850271..1982f8b4278 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -56,10 +56,10 @@ config BATTERY_TOSA Say Y to enable support for the battery on the Sharp Zaurus SL-6000 (tosa) models. -config BATTERY_PALMTX - tristate "Palm T|X battery" - depends on MACH_PALMTX +config BATTERY_WM97XX + bool "WM97xx generic battery driver" + depends on TOUCHSCREEN_WM97XX help - Say Y to enable support for the battery in Palm T|X. + Say Y to enable support for battery measured by WM97xx aux port. endif # POWER_SUPPLY diff --git a/drivers/power/Makefile b/drivers/power/Makefile index 4706bf8ff45..4e20026cc45 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -21,4 +21,4 @@ obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o obj-$(CONFIG_BATTERY_PMU) += pmu_battery.o obj-$(CONFIG_BATTERY_OLPC) += olpc_battery.o obj-$(CONFIG_BATTERY_TOSA) += tosa_battery.o -obj-$(CONFIG_BATTERY_PALMTX) += palmtx_battery.o +obj-$(CONFIG_BATTERY_WM97XX) += wm97xx_battery.o \ No newline at end of file diff --git a/drivers/power/palmtx_battery.c b/drivers/power/palmtx_battery.c deleted file mode 100644 index 7035bfa41c6..00000000000 --- a/drivers/power/palmtx_battery.c +++ /dev/null @@ -1,198 +0,0 @@ -/* - * linux/drivers/power/palmtx_battery.c - * - * Battery measurement code for Palm T|X Handheld computer - * - * based on tosa_battery.c - * - * Copyright (C) 2008 Marek Vasut - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - */ -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -static DEFINE_MUTEX(bat_lock); -static struct work_struct bat_work; -struct mutex work_lock; -int bat_status = POWER_SUPPLY_STATUS_DISCHARGING; - -static unsigned long palmtx_read_bat(struct power_supply *bat_ps) -{ - return wm97xx_read_aux_adc(bat_ps->dev->parent->driver_data, - WM97XX_AUX_ID3) * 1000 / 414; -} - -static unsigned long palmtx_read_temp(struct power_supply *bat_ps) -{ - return wm97xx_read_aux_adc(bat_ps->dev->parent->driver_data, - WM97XX_AUX_ID2); -} - -static int palmtx_bat_get_property(struct power_supply *bat_ps, - enum power_supply_property psp, - union power_supply_propval *val) -{ - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - val->intval = bat_status; - break; - case POWER_SUPPLY_PROP_TECHNOLOGY: - val->intval = POWER_SUPPLY_TECHNOLOGY_LIPO; - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - val->intval = palmtx_read_bat(bat_ps); - break; - case POWER_SUPPLY_PROP_VOLTAGE_MAX: - case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: - val->intval = PALMTX_BAT_MAX_VOLTAGE; - break; - case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: - val->intval = PALMTX_BAT_MIN_VOLTAGE; - break; - case POWER_SUPPLY_PROP_TEMP: - val->intval = palmtx_read_temp(bat_ps); - break; - case POWER_SUPPLY_PROP_PRESENT: - val->intval = 1; - break; - default: - return -EINVAL; - } - return 0; -} - -static void palmtx_bat_external_power_changed(struct power_supply *bat_ps) -{ - schedule_work(&bat_work); -} - -static char *status_text[] = { - [POWER_SUPPLY_STATUS_UNKNOWN] = "Unknown", - [POWER_SUPPLY_STATUS_CHARGING] = "Charging", - [POWER_SUPPLY_STATUS_DISCHARGING] = "Discharging", -}; - -static void palmtx_bat_update(struct power_supply *bat_ps) -{ - int old_status = bat_status; - - mutex_lock(&work_lock); - - bat_status = gpio_get_value(GPIO_NR_PALMTX_POWER_DETECT) ? - POWER_SUPPLY_STATUS_CHARGING : - POWER_SUPPLY_STATUS_DISCHARGING; - - if (old_status != bat_status) { - pr_debug("%s %s -> %s\n", bat_ps->name, - status_text[old_status], - status_text[bat_status]); - power_supply_changed(bat_ps); - } - - mutex_unlock(&work_lock); -} - -static enum power_supply_property palmtx_bat_main_props[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_TECHNOLOGY, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_VOLTAGE_MAX, - POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, - POWER_SUPPLY_PROP_TEMP, - POWER_SUPPLY_PROP_PRESENT, -}; - -struct power_supply bat_ps = { - .name = "main-battery", - .type = POWER_SUPPLY_TYPE_BATTERY, - .properties = palmtx_bat_main_props, - .num_properties = ARRAY_SIZE(palmtx_bat_main_props), - .get_property = palmtx_bat_get_property, - .external_power_changed = palmtx_bat_external_power_changed, - .use_for_apm = 1, -}; - -static void palmtx_bat_work(struct work_struct *work) -{ - palmtx_bat_update(&bat_ps); -} - -#ifdef CONFIG_PM -static int palmtx_bat_suspend(struct platform_device *dev, pm_message_t state) -{ - flush_scheduled_work(); - return 0; -} - -static int palmtx_bat_resume(struct platform_device *dev) -{ - schedule_work(&bat_work); - return 0; -} -#else -#define palmtx_bat_suspend NULL -#define palmtx_bat_resume NULL -#endif - -static int __devinit palmtx_bat_probe(struct platform_device *dev) -{ - int ret = 0; - - if (!machine_is_palmtx()) - return -ENODEV; - - mutex_init(&work_lock); - - INIT_WORK(&bat_work, palmtx_bat_work); - - ret = power_supply_register(&dev->dev, &bat_ps); - if (!ret) - schedule_work(&bat_work); - - return ret; -} - -static int __devexit palmtx_bat_remove(struct platform_device *dev) -{ - power_supply_unregister(&bat_ps); - return 0; -} - -static struct platform_driver palmtx_bat_driver = { - .driver.name = "wm97xx-battery", - .driver.owner = THIS_MODULE, - .probe = palmtx_bat_probe, - .remove = __devexit_p(palmtx_bat_remove), - .suspend = palmtx_bat_suspend, - .resume = palmtx_bat_resume, -}; - -static int __init palmtx_bat_init(void) -{ - return platform_driver_register(&palmtx_bat_driver); -} - -static void __exit palmtx_bat_exit(void) -{ - platform_driver_unregister(&palmtx_bat_driver); -} - -module_init(palmtx_bat_init); -module_exit(palmtx_bat_exit); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Marek Vasut "); -MODULE_DESCRIPTION("Palm T|X battery driver"); diff --git a/drivers/power/wm97xx_battery.c b/drivers/power/wm97xx_battery.c new file mode 100644 index 00000000000..8bde92126d3 --- /dev/null +++ b/drivers/power/wm97xx_battery.c @@ -0,0 +1,272 @@ +/* + * linux/drivers/power/wm97xx_battery.c + * + * Battery measurement code for WM97xx + * + * based on tosa_battery.c + * + * Copyright (C) 2008 Marek Vasut + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static DEFINE_MUTEX(bat_lock); +static struct work_struct bat_work; +struct mutex work_lock; +static int bat_status = POWER_SUPPLY_STATUS_UNKNOWN; +static struct wm97xx_batt_info *pdata; +static enum power_supply_property *prop; + +static unsigned long wm97xx_read_bat(struct power_supply *bat_ps) +{ + return wm97xx_read_aux_adc(bat_ps->dev->parent->driver_data, + pdata->batt_aux) * pdata->batt_mult / + pdata->batt_div; +} + +static unsigned long wm97xx_read_temp(struct power_supply *bat_ps) +{ + return wm97xx_read_aux_adc(bat_ps->dev->parent->driver_data, + pdata->temp_aux) * pdata->temp_mult / + pdata->temp_div; +} + +static int wm97xx_bat_get_property(struct power_supply *bat_ps, + enum power_supply_property psp, + union power_supply_propval *val) +{ + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = bat_status; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = pdata->batt_tech; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + if (pdata->batt_aux >= 0) + val->intval = wm97xx_read_bat(bat_ps); + else + return -EINVAL; + break; + case POWER_SUPPLY_PROP_TEMP: + if (pdata->temp_aux >= 0) + val->intval = wm97xx_read_temp(bat_ps); + else + return -EINVAL; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + if (pdata->max_voltage >= 0) + val->intval = pdata->max_voltage; + else + return -EINVAL; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + if (pdata->min_voltage >= 0) + val->intval = pdata->min_voltage; + else + return -EINVAL; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = 1; + break; + default: + return -EINVAL; + } + return 0; +} + +static void wm97xx_bat_external_power_changed(struct power_supply *bat_ps) +{ + schedule_work(&bat_work); +} + +static void wm97xx_bat_update(struct power_supply *bat_ps) +{ + int old_status = bat_status; + + mutex_lock(&work_lock); + + bat_status = (pdata->charge_gpio >= 0) ? + (gpio_get_value(pdata->charge_gpio) ? + POWER_SUPPLY_STATUS_DISCHARGING : + POWER_SUPPLY_STATUS_CHARGING) : + POWER_SUPPLY_STATUS_UNKNOWN; + + if (old_status != bat_status) { + pr_debug("%s: %i -> %i\n", bat_ps->name, old_status, + bat_status); + power_supply_changed(bat_ps); + } + + mutex_unlock(&work_lock); +} + +static struct power_supply bat_ps = { + .type = POWER_SUPPLY_TYPE_BATTERY, + .get_property = wm97xx_bat_get_property, + .external_power_changed = wm97xx_bat_external_power_changed, + .use_for_apm = 1, +}; + +static void wm97xx_bat_work(struct work_struct *work) +{ + wm97xx_bat_update(&bat_ps); +} + +#ifdef CONFIG_PM +static int wm97xx_bat_suspend(struct platform_device *dev, pm_message_t state) +{ + flush_scheduled_work(); + return 0; +} + +static int wm97xx_bat_resume(struct platform_device *dev) +{ + schedule_work(&bat_work); + return 0; +} +#else +#define wm97xx_bat_suspend NULL +#define wm97xx_bat_resume NULL +#endif + +static int __devinit wm97xx_bat_probe(struct platform_device *dev) +{ + int ret = 0; + int props = 1; /* POWER_SUPPLY_PROP_PRESENT */ + int i = 0; + + if (dev->id != -1) + return -EINVAL; + + mutex_init(&work_lock); + + if (!pdata) { + dev_err(&dev->dev, "Please use wm97xx_bat_set_pdata\n"); + return -EINVAL; + } + + if (pdata->charge_gpio >= 0 && gpio_is_valid(pdata->charge_gpio)) { + ret = gpio_request(pdata->charge_gpio, "BATT CHRG"); + if (ret) + goto err; + ret = gpio_direction_input(pdata->charge_gpio); + if (ret) + goto err2; + props++; /* POWER_SUPPLY_PROP_STATUS */ + } + + if (pdata->batt_tech >= 0) + props++; /* POWER_SUPPLY_PROP_TECHNOLOGY */ + if (pdata->temp_aux >= 0) + props++; /* POWER_SUPPLY_PROP_TEMP */ + if (pdata->batt_aux >= 0) + props++; /* POWER_SUPPLY_PROP_VOLTAGE_NOW */ + if (pdata->max_voltage >= 0) + props++; /* POWER_SUPPLY_PROP_VOLTAGE_MAX */ + if (pdata->min_voltage >= 0) + props++; /* POWER_SUPPLY_PROP_VOLTAGE_MIN */ + + prop = kzalloc(props * sizeof(*prop), GFP_KERNEL); + if (!prop) + goto err2; + + prop[i++] = POWER_SUPPLY_PROP_PRESENT; + if (pdata->charge_gpio >= 0) + prop[i++] = POWER_SUPPLY_PROP_STATUS; + if (pdata->batt_tech >= 0) + prop[i++] = POWER_SUPPLY_PROP_TECHNOLOGY; + if (pdata->temp_aux >= 0) + prop[i++] = POWER_SUPPLY_PROP_TEMP; + if (pdata->batt_aux >= 0) + prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_NOW; + if (pdata->max_voltage >= 0) + prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MAX; + if (pdata->min_voltage >= 0) + prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MIN; + + INIT_WORK(&bat_work, wm97xx_bat_work); + + if (!pdata->batt_name) { + dev_info(&dev->dev, "Please consider setting proper battery " + "name in platform definition file, falling " + "back to name \"wm97xx-batt\"\n"); + bat_ps.name = "wm97xx-batt"; + } else + bat_ps.name = pdata->batt_name; + + bat_ps.properties = prop; + bat_ps.num_properties = props; + + ret = power_supply_register(&dev->dev, &bat_ps); + if (!ret) + schedule_work(&bat_work); + else + goto err3; + + return 0; +err3: + kfree(prop); +err2: + gpio_free(pdata->charge_gpio); +err: + return ret; +} + +static int __devexit wm97xx_bat_remove(struct platform_device *dev) +{ + if (pdata && pdata->charge_gpio && pdata->charge_gpio >= 0) + gpio_free(pdata->charge_gpio); + flush_scheduled_work(); + power_supply_unregister(&bat_ps); + kfree(prop); + return 0; +} + +static struct platform_driver wm97xx_bat_driver = { + .driver = { + .name = "wm97xx-battery", + .owner = THIS_MODULE, + }, + .probe = wm97xx_bat_probe, + .remove = __devexit_p(wm97xx_bat_remove), + .suspend = wm97xx_bat_suspend, + .resume = wm97xx_bat_resume, +}; + +static int __init wm97xx_bat_init(void) +{ + return platform_driver_register(&wm97xx_bat_driver); +} + +static void __exit wm97xx_bat_exit(void) +{ + platform_driver_unregister(&wm97xx_bat_driver); +} + +void __init wm97xx_bat_set_pdata(struct wm97xx_batt_info *data) +{ + pdata = data; +} +EXPORT_SYMBOL_GPL(wm97xx_bat_set_pdata); + +module_init(wm97xx_bat_init); +module_exit(wm97xx_bat_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Marek Vasut "); +MODULE_DESCRIPTION("WM97xx battery driver"); -- cgit v1.2.3