diff options
56 files changed, 5287 insertions, 1035 deletions
diff --git a/arch/arm/boot/compressed/head.S b/arch/arm/boot/compressed/head.S index fa6fbf45cf3..4f0708b83ed 100644 --- a/arch/arm/boot/compressed/head.S +++ b/arch/arm/boot/compressed/head.S @@ -58,7 +58,7 @@ add \rb, \rb, #0x00010000 @ Ser1 #endif .endm -#elif defined(CONFIG_ARCH_S3C2410) +#elif defined(CONFIG_ARCH_S3C2410) && CONFIG_S3C_LOWLEVEL_UART_PORT >= 0 .macro loadsp, rb mov \rb, #0x50000000 add \rb, \rb, #0x4000 * CONFIG_S3C_LOWLEVEL_UART_PORT diff --git a/arch/arm/include/asm/fiq.h b/arch/arm/include/asm/fiq.h index 2242ce22ec6..7ade2b8445d 100644 --- a/arch/arm/include/asm/fiq.h +++ b/arch/arm/include/asm/fiq.h @@ -29,8 +29,9 @@ struct fiq_handler { extern int claim_fiq(struct fiq_handler *f); extern void release_fiq(struct fiq_handler *f); extern void set_fiq_handler(void *start, unsigned int length); -extern void set_fiq_regs(struct pt_regs *regs); -extern void get_fiq_regs(struct pt_regs *regs); +extern void set_fiq_c_handler(void (*handler)(void)); +extern void __attribute__((naked)) set_fiq_regs(struct pt_regs *regs); +extern void __attribute__((naked)) get_fiq_regs(struct pt_regs *regs); extern void enable_fiq(int fiq); extern void disable_fiq(int fiq); diff --git a/arch/arm/kernel/fiq.c b/arch/arm/kernel/fiq.c index 6ff7919613d..c07691ef4c0 100644 --- a/arch/arm/kernel/fiq.c +++ b/arch/arm/kernel/fiq.c @@ -8,6 +8,8 @@ * * FIQ support re-written by Russell King to be more generic * + * FIQ handler in C supoprt written by Andy Green <andy@openmoko.com> + * * We now properly support a method by which the FIQ handlers can * be stacked onto the vector. We still do not support sharing * the FIQ vector itself. @@ -124,6 +126,83 @@ void __naked get_fiq_regs(struct pt_regs *regs) : "r" (®s->ARM_r8), "I" (PSR_I_BIT | PSR_F_BIT | FIQ_MODE)); } +/* -------- FIQ handler in C --------- + * + * Major Caveats for using this + * --------------------------- + * * + * * 1) it CANNOT touch any vmalloc()'d memory, only memory + * that was kmalloc()'d. Static allocations in the monolithic kernel + * are kmalloc()'d so they are okay. You can touch memory-mapped IO, but + * the pointer for it has to have been stored in kmalloc'd memory. The + * reason for this is simple: every now and then Linux turns off interrupts + * and reorders the paging tables. If a FIQ happens during this time, the + * virtual memory space can be partly or entirely disordered or missing. + * + * 2) Because vmalloc() is used when a module is inserted, THIS FIQ + * ISR HAS TO BE IN THE MONOLITHIC KERNEL, not a module. But the way + * it is set up, you can all to enable and disable it from your module + * and intercommunicate with it through struct fiq_ipc + * fiq_ipc which you can define in + * asm/archfiq_ipc_type.h. The reason is the same as above, a + * FIQ could happen while even the ISR is not present in virtual memory + * space due to pagetables being changed at the time. + * + * 3) You can't call any Linux API code except simple macros + * - understand that FIQ can come in at any time, no matter what + * state of undress the kernel may privately be in, thinking it + * locked the door by turning off interrupts... FIQ is an + * unstoppable monster force (which is its value) + * - they are not vmalloc()'d memory safe + * - they might do crazy stuff like sleep: FIQ pisses fire and + * is not interested in 'sleep' that the weak seem to need + * - calling APIs from FIQ can re-enter un-renterable things + * - summary: you cannot interoperate with linux APIs directly in the FIQ ISR + * + * If you follow these rules, it is fantastic, an extremely powerful, solid, + * genuine hard realtime feature. + */ + +static void (*current_fiq_c_isr)(void); +#define FIQ_C_ISR_STACK_SIZE 256 + +static void __attribute__((naked)) __jump_to_isr(void) +{ + asm __volatile__ ("mov pc, r8"); +} + + +static void __attribute__((naked)) __actual_isr(void) +{ + asm __volatile__ ( + "stmdb sp!, {r0-r12, lr};" + "mov fp, sp;" + ); + + current_fiq_c_isr(); + + asm __volatile__ ( + "ldmia sp!, {r0-r12, lr};" + "subs pc, lr, #4;" + ); +} + +void set_fiq_c_handler(void (*isr)(void)) +{ + struct pt_regs regs; + + memset(®s, 0, sizeof(regs)); + regs.ARM_r8 = (unsigned long) __actual_isr; + regs.ARM_sp = 0xffff001c + FIQ_C_ISR_STACK_SIZE; + + set_fiq_handler(__jump_to_isr, 4); + + current_fiq_c_isr = isr; + + set_fiq_regs(®s); +} +/* -------- FIQ handler in C ---------*/ + int claim_fiq(struct fiq_handler *f) { int ret = 0; diff --git a/arch/arm/mach-s3c2410/include/mach/gpio.h b/arch/arm/mach-s3c2410/include/mach/gpio.h index 15f0b3e7ce6..0b53cad80fe 100644 --- a/arch/arm/mach-s3c2410/include/mach/gpio.h +++ b/arch/arm/mach-s3c2410/include/mach/gpio.h @@ -20,10 +20,11 @@ * devices that need GPIO. */ -#define ARCH_NR_GPIOS (256 + CONFIG_S3C24XX_GPIO_EXTRA) +#define ARCH_NR_GPIOS (512 + CONFIG_S3C24XX_GPIO_EXTRA) #include <asm-generic/gpio.h> #include <mach/gpio-nrs.h> #include <mach/gpio-fns.h> +#include <mach/regs-gpioj.h> -#define S3C_GPIO_END (S3C2410_GPIO_BANKH + 32) +#define S3C_GPIO_END (S3C2440_GPIO_BANKJ + 32) diff --git a/arch/arm/mach-s3c2410/include/mach/irqs.h b/arch/arm/mach-s3c2410/include/mach/irqs.h index 6c12c6312ad..6986d02fd4d 100644 --- a/arch/arm/mach-s3c2410/include/mach/irqs.h +++ b/arch/arm/mach-s3c2410/include/mach/irqs.h @@ -153,9 +153,9 @@ #define IRQ_S3C2443_AC97 S3C2410_IRQSUB(28) #ifdef CONFIG_CPU_S3C2443 -#define NR_IRQS (IRQ_S3C2443_AC97+1) +#define S3C2410_NR_INTERNAL_IRQS (IRQ_S3C2443_AC97+1) #else -#define NR_IRQS (IRQ_S3C2440_AC97+1) +#define S3C2410_NR_INTERNAL_IRQS (IRQ_S3C2440_AC97+1) #endif /* compatibility define. */ @@ -173,4 +173,22 @@ /* Our FIQs are routable from IRQ_EINT0 to IRQ_ADCPARENT */ #define FIQ_START IRQ_EINT0 +/* Board specific IRQs + * If your board needs a extra set of IRQ numbers add it to the list here. + * Make sure that the numbers are kept in descending, so if multiple boards are + * selected the maximum will be used and there are enough IRQ numbers available + * for each board. + */ + +#if defined(CONFIG_MACH_NEO1973_GTA02) +#define S3C2410_NR_BOARD_IRQS 9 +#else +#define S3C2410_NR_BOARD_IRQS 0 +#endif + +#define S3C2410_BOARD_IRQ_START S3C2410_NR_INTERNAL_IRQS +#define S3C2410_BOARD_IRQ_END (S3C2410_BOARD_IRQ_START + S3C2410_NR_BOARD_IRQS) + +#define NR_IRQS S3C2410_BOARD_IRQ_END + #endif /* __ASM_ARCH_IRQ_H */ diff --git a/arch/arm/mach-s3c2410/include/mach/ts.h b/arch/arm/mach-s3c2410/include/mach/ts.h new file mode 100644 index 00000000000..ac0c727d155 --- /dev/null +++ b/arch/arm/mach-s3c2410/include/mach/ts.h @@ -0,0 +1,35 @@ +/* arch/arm/mach-s3c2410/include/mach/ts.h + * + * Copyright (c) 2005 Arnaud Patard <arnaud.patard@rtp-net.org> + * + * + * 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. + * + * + * Changelog: + * 24-Mar-2005 RTP Created file + * 03-Aug-2005 RTP Renamed to ts.h + */ + +#ifndef __ASM_ARM_TS_H +#define __ASM_ARM_TS_H + +#include <linux/input/touchscreen/ts_filter.h> + +struct s3c2410_ts_mach_info { + /* Touchscreen delay. */ + int delay; + /* Prescaler value. */ + int presc; + /* + * Null-terminated array of pointers to filter APIs and configurations + * we want to use. In the same order they will be applied. + */ + const struct ts_filter_chain_configuration *filter_config; +}; + +void set_s3c2410ts_info(const struct s3c2410_ts_mach_info *hard_s3c2410ts_info); + +#endif /* __ASM_ARM_TS_H */ diff --git a/arch/arm/plat-s3c/include/plat/devs.h b/arch/arm/plat-s3c/include/plat/devs.h index 0f540ea1e99..50d50a2f086 100644 --- a/arch/arm/plat-s3c/include/plat/devs.h +++ b/arch/arm/plat-s3c/include/plat/devs.h @@ -53,6 +53,8 @@ extern struct platform_device s3c_device_nand; extern struct platform_device s3c_device_usbgadget; extern struct platform_device s3c_device_usb_hsotg; +extern struct platform_device s3c_device_ts; + /* s3c2440 specific devices */ #ifdef CONFIG_CPU_S3C2440 diff --git a/arch/arm/plat-s3c/include/plat/nand.h b/arch/arm/plat-s3c/include/plat/nand.h index 18f958801e6..723ab032059 100644 --- a/arch/arm/plat-s3c/include/plat/nand.h +++ b/arch/arm/plat-s3c/include/plat/nand.h @@ -47,6 +47,7 @@ struct s3c2410_platform_nand { int twrph1; /* time for release CLE/ALE from nWE/nOE inactive */ unsigned int ignore_unset_ecc:1; + unsigned int software_ecc:1; /* force software ecc at runtime */ int nr_sets; struct s3c2410_nand_set *sets; diff --git a/arch/arm/plat-s3c/include/plat/uncompress.h b/arch/arm/plat-s3c/include/plat/uncompress.h index dc66a477f62..8e02073e307 100644 --- a/arch/arm/plat-s3c/include/plat/uncompress.h +++ b/arch/arm/plat-s3c/include/plat/uncompress.h @@ -37,6 +37,8 @@ static void arch_detect_cpu(void); /* how many bytes we allow into the FIFO at a time in FIFO mode */ #define FIFO_MAX (14) +#if CONFIG_S3C_LOWLEVEL_UART_PORT >= 0 + #define uart_base S3C_PA_UART + (S3C_UART_OFFSET * CONFIG_S3C_LOWLEVEL_UART_PORT) static __inline__ void @@ -86,10 +88,6 @@ static void putc(int ch) uart_wr(S3C2410_UTXH, ch); } -static inline void flush(void) -{ -} - #define __raw_writel(d, ad) \ do { \ *((volatile unsigned int __force *)(ad)) = (d); \ @@ -163,6 +161,19 @@ static inline void arch_enable_uart_fifo(void) #define arch_enable_uart_fifo() do { } while(0) #endif +#else +static inline void putc(int ch) +{ +} + +#define arch_enable_uart_fifo() do { } while(0) +#define arch_decomp_wdog_start() +#define arch_decomp_wdog() +#endif + +static inline void flush(void) +{ +} static void arch_decomp_setup(void) diff --git a/arch/arm/plat-s3c/pwm.c b/arch/arm/plat-s3c/pwm.c index 4fdc5b307fd..2a5275d98ca 100644 --- a/arch/arm/plat-s3c/pwm.c +++ b/arch/arm/plat-s3c/pwm.c @@ -21,6 +21,8 @@ #include <mach/irqs.h> #include <mach/map.h> +#include <mach/gpio.h> +#include <mach/regs-gpio.h> #include <plat/devs.h> #include <plat/regs-timer.h> @@ -40,6 +42,10 @@ struct pwm_device { unsigned char running; unsigned char use_count; unsigned char pwm_id; + + /* Used for saving state during suspend */ + unsigned long tcon; + unsigned char routed_to_gpio:1; }; #define pwm_dbg(_pwm, msg...) dev_dbg(&(_pwm)->pdev->dev, msg) @@ -130,6 +136,7 @@ void pwm_free(struct pwm_device *pwm) EXPORT_SYMBOL(pwm_free); +#define pwm_tcon_mask(pwm) (0xf << (pwm->tcon_base)) #define pwm_tcon_start(pwm) (1 << (pwm->tcon_base + 0)) #define pwm_tcon_invert(pwm) (1 << (pwm->tcon_base + 2)) #define pwm_tcon_autoreload(pwm) (1 << (pwm->tcon_base + 3)) @@ -379,10 +386,75 @@ static int s3c_pwm_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM + +static int s3c_pwm_suspend(struct device *dev) +{ + struct pwm_device *pwm = dev_get_drvdata(dev); + unsigned int gpio_cfg = s3c2410_gpio_getcfg(S3C2410_GPB(pwm->pwm_id)); + + /* During suspend the pwm registers are reseted. If the invert bit is not + * set the output of TOUTn is high when the pwm is not active. In order to + * prevent spurious high output on the gpio at resume time, we stop routing + * the pwm signal to the gpio until tcon has been restored. */ + if (gpio_cfg == S3C2410_GPIO_SFN2) { + s3c2410_gpio_cfgpin(S3C2410_GPB(pwm->pwm_id), S3C2410_GPIO_INPUT); + pwm->routed_to_gpio = 1; + } else { + pwm->routed_to_gpio = 0; + } + + pwm->tcon = __raw_readl(S3C2410_TCON); + + return 0; +} + +static int s3c_pwm_resume(struct device *dev) +{ + struct pwm_device *pwm = dev_get_drvdata(dev); + unsigned long flags; + unsigned long tcon; + int duty_ns, period_ns; + + duty_ns = pwm->duty_ns; + period_ns = pwm->period_ns; + pwm->duty_ns = -1; + pwm->period_ns = -1; + pwm_config(pwm, duty_ns, period_ns); + + local_irq_save(flags); + tcon = __raw_readl(S3C2410_TCON); + tcon = __raw_readl(S3C2410_TCON); + tcon &= ~pwm_tcon_mask(pwm); + tcon |= pwm->tcon; + __raw_writel(tcon, S3C2410_TCON); + local_irq_restore(flags); + + if (pwm->routed_to_gpio) + s3c2410_gpio_cfgpin(S3C2410_GPB(pwm->pwm_id), S3C2410_GPIO_SFN2); + + return 0; +} + +struct dev_pm_ops s3c_pwm_pm_ops = { + .suspend = s3c_pwm_suspend, + .resume = s3c_pwm_resume, + .freeze = s3c_pwm_suspend, + .thaw = s3c_pwm_resume, +}; + +#define S3C_PWM_PM_OPS (&s3c_pwm_pm_ops) + +#else +#define S3C_PWM_PM_OPS NULL +#endif + + static struct platform_driver s3c_pwm_driver = { .driver = { .name = "s3c24xx-pwm", .owner = THIS_MODULE, + .pm = S3C_PWM_PM_OPS, }, .probe = s3c_pwm_probe, .remove = __devexit_p(s3c_pwm_remove), diff --git a/arch/arm/plat-s3c24xx/adc.c b/arch/arm/plat-s3c24xx/adc.c index df47322492d..8c443153d05 100644 --- a/arch/arm/plat-s3c24xx/adc.c +++ b/arch/arm/plat-s3c24xx/adc.c @@ -46,6 +46,7 @@ struct s3c_adc_client { int result; unsigned char is_ts; unsigned char channel; + unsigned int selected; void (*select_cb)(struct s3c_adc_client *c, unsigned selected); void (*convert_cb)(struct s3c_adc_client *c, @@ -62,20 +63,31 @@ struct adc_device { void __iomem *regs; unsigned int prescale; + unsigned int delay; int irq; }; static struct adc_device *adc_dev; +static struct work_struct resume_work; + static LIST_HEAD(adc_pending); #define adc_dbg(_adc, msg...) dev_dbg(&(_adc)->pdev->dev, msg) +#define AUTOPST (S3C2410_ADCTSC_YM_SEN | S3C2410_ADCTSC_YP_SEN | \ + S3C2410_ADCTSC_XP_SEN | S3C2410_ADCTSC_AUTO_PST | \ + S3C2410_ADCTSC_XY_PST(0)) + static inline void s3c_adc_convert(struct adc_device *adc) { unsigned con = readl(adc->regs + S3C2410_ADCCON); + if (adc->cur->is_ts) + writel(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, + adc->regs + S3C2410_ADCTSC); + con |= S3C2410_ADCCON_ENABLE_START; writel(con, adc->regs + S3C2410_ADCCON); } @@ -85,7 +97,10 @@ static inline void s3c_adc_select(struct adc_device *adc, { unsigned con = readl(adc->regs + S3C2410_ADCCON); - client->select_cb(client, 1); + if (!client->selected) { + client->selected = 1; + client->select_cb(client, 1); + } con &= ~S3C2410_ADCCON_MUXMASK; con &= ~S3C2410_ADCCON_STDBM; @@ -109,12 +124,9 @@ static void s3c_adc_try(struct adc_device *adc) { struct s3c_adc_client *next = adc->ts_pend; - if (!next && !list_empty(&adc_pending)) { + if (!next && !list_empty(&adc_pending)) next = list_first_entry(&adc_pending, struct s3c_adc_client, pend); - list_del(&next->pend); - } else - adc->ts_pend = NULL; if (next) { adc_dbg(adc, "new client is %p\n", next); @@ -277,14 +289,20 @@ static irqreturn_t s3c_adc_irq(int irq, void *pw) if (client->nr_samples > 0) { /* fire another conversion for this */ - + client->selected = 1; client->select_cb(client, 1); s3c_adc_convert(adc); } else { local_irq_save(flags); - (client->select_cb)(client, 0); + client->selected = 0; + if (!adc->cur->is_ts) + list_del(&adc->cur->pend); + else + adc->ts_pend = NULL; adc->cur = NULL; + (client->select_cb)(client, 0); + s3c_adc_try(adc); local_irq_restore(flags); } @@ -307,6 +325,7 @@ static int s3c_adc_probe(struct platform_device *pdev) adc->pdev = pdev; adc->prescale = S3C2410_ADCCON_PRSCVL(49); + adc->delay = 0x2710; adc->irq = platform_get_irq(pdev, 1); if (adc->irq <= 0) { @@ -346,6 +365,7 @@ static int s3c_adc_probe(struct platform_device *pdev) writel(adc->prescale | S3C2410_ADCCON_PRSCEN, adc->regs + S3C2410_ADCCON); + writel(adc->delay, adc->regs + S3C2410_ADCDLY); dev_info(dev, "attached adc driver\n"); @@ -389,18 +409,42 @@ static int s3c_adc_suspend(struct platform_device *pdev, pm_message_t state) writel(con, adc->regs + S3C2410_ADCCON); clk_disable(adc->clk); + disable_irq(IRQ_ADC); + if (!list_empty(&adc_pending) || adc->ts_pend) + dev_info(&pdev->dev, "We still have adc clients pending\n"); return 0; } +/* It seems this is not needed. This is under upstream review now. */ +static void adc_resume_work(struct work_struct *work) +{ + if (!adc_dev) /* Have no ADC here */ + return; + + if (!list_empty(&adc_pending) || adc_dev->ts_pend) + /* We still have adc clients pending */ + s3c_adc_try(adc_dev); +} + static int s3c_adc_resume(struct platform_device *pdev) { struct adc_device *adc = platform_get_drvdata(pdev); + enable_irq(IRQ_ADC); clk_enable(adc->clk); writel(adc->prescale | S3C2410_ADCCON_PRSCEN, adc->regs + S3C2410_ADCCON); + writel(adc->delay, adc->regs + S3C2410_ADCDLY); + + /* Schedule task if there are clients pending. */ + if (!list_empty(&adc_pending) || adc_dev->ts_pend) { + INIT_WORK(&resume_work, adc_resume_work); + if (!schedule_work(&resume_work)) + dev_err(&pdev->dev, + "Failed to schedule adc_resume work!\n"); + } return 0; } diff --git a/arch/arm/plat-s3c24xx/devs.c b/arch/arm/plat-s3c24xx/devs.c index f52a92ce8dd..9ef0e97d56e 100644 --- a/arch/arm/plat-s3c24xx/devs.c +++ b/arch/arm/plat-s3c24xx/devs.c @@ -30,6 +30,8 @@ #include <mach/irqs.h> #include <asm/irq.h> +#include <mach/ts.h> + #include <plat/regs-serial.h> #include <plat/udc.h> @@ -182,6 +184,24 @@ void __init s3c24xx_fb_set_platdata(struct s3c2410fb_mach_info *pd) } } +/* Touchscreen */ + +struct platform_device s3c_device_ts = { + .name = "s3c2410-ts", + .id = -1, + .dev.parent = &s3c_device_adc.dev, +}; + +static struct s3c2410_ts_mach_info s3c2410ts_info; + +void set_s3c2410ts_info(const struct s3c2410_ts_mach_info *hard_s3c2410ts_info) +{ + memcpy(&s3c2410ts_info, hard_s3c2410ts_info, + sizeof(struct s3c2410_ts_mach_info)); + s3c_device_ts.dev.platform_data = &s3c2410ts_info; +} +EXPORT_SYMBOL(set_s3c2410ts_info); + /* USB Device (Gadget)*/ static struct resource s3c_usbgadget_resource[] = { diff --git a/arch/arm/plat-s3c24xx/gpiolib.c b/arch/arm/plat-s3c24xx/gpiolib.c index 6d7a961d326..c2dd900660b 100644 --- a/arch/arm/plat-s3c24xx/gpiolib.c +++ b/arch/arm/plat-s3c24xx/gpiolib.c @@ -26,6 +26,7 @@ #include <plat/pm.h> #include <mach/regs-gpio.h> +#include <mach/regs-gpioj.h> static int s3c24xx_gpiolib_banka_input(struct gpio_chip *chip, unsigned offset) { @@ -160,8 +161,16 @@ struct s3c_gpio_chip s3c24xx_gpios[] = { .label = "GPIOH", .ngpio = 11, }, - }, -}; + }, { + .base = S3C2440_GPJCON, + .pm = __gpio_pm(&s3c_gpio_pm_2bit), + .chip = { + .base = S3C2440_GPJ0, + .owner = THIS_MODULE, + .label = "GPIOJ", + .ngpio = 11, + }, + },}; static __init int s3c24xx_gpiolib_init(void) { diff --git a/arch/arm/plat-s3c24xx/include/plat/irq.h b/arch/arm/plat-s3c24xx/include/plat/irq.h index 69e1be8bec3..11a866466d8 100644 --- a/arch/arm/plat-s3c24xx/include/plat/irq.h +++ b/arch/arm/plat-s3c24xx/include/plat/irq.h @@ -12,6 +12,7 @@ #include <linux/io.h> +#include <mach/irqs.h> #include <mach/hardware.h> #include <mach/regs-irq.h> #include <mach/regs-gpio.h> @@ -31,8 +32,15 @@ s3c_irqsub_mask(unsigned int irqno, unsigned int parentbit, { unsigned long mask; unsigned long submask; +#ifdef CONFIG_S3C2440_C_FIQ + unsigned long flags; +#endif submask = __raw_readl(S3C2410_INTSUBMSK); +#ifdef CONFIG_S3C2440_C_FIQ + local_save_flags(flags); + local_fiq_disable(); +#endif mask = __raw_readl(S3C2410_INTMSK); submask |= (1UL << (irqno - IRQ_S3CUART_RX0)); @@ -45,6 +53,9 @@ s3c_irqsub_mask(unsigned int irqno, unsigned int parentbit, /* write back masks */ __raw_writel(submask, S3C2410_INTSUBMSK); +#ifdef CONFIG_S3C2440_C_FIQ + local_irq_restore(flags); +#endif } @@ -53,8 +64,15 @@ s3c_irqsub_unmask(unsigned int irqno, unsigned int parentbit) { unsigned long mask; unsigned long submask; +#ifdef CONFIG_S3C2440_C_FIQ + unsigned long flags; +#endif submask = __raw_readl(S3C2410_INTSUBMSK); +#ifdef CONFIG_S3C2440_C_FIQ + local_save_flags(flags); + local_fiq_disable(); +#endif mask = __raw_readl(S3C2410_INTMSK); submask &= ~(1UL << (irqno - IRQ_S3CUART_RX0)); @@ -63,6 +81,9 @@ s3c_irqsub_unmask(unsigned int irqno, unsigned int parentbit) /* write back masks */ __raw_writel(submask, S3C2410_INTSUBMSK); __raw_writel(mask, S3C2410_INTMSK); +#ifdef CONFIG_S3C2440_C_FIQ + local_irq_restore(flags); +#endif } diff --git a/arch/arm/plat-s3c24xx/irq.c b/arch/arm/plat-s3c24xx/irq.c index d02f5f02045..14aa528d985 100644 --- a/arch/arm/plat-s3c24xx/irq.c +++ b/arch/arm/plat-s3c24xx/irq.c @@ -28,6 +28,8 @@ #include <asm/mach/irq.h> #include <plat/regs-irqtype.h> +#include <mach/regs-irq.h> +#include <mach/regs-gpio.h> #include <plat/cpu.h> #include <plat/pm.h> @@ -37,12 +39,20 @@ static void s3c_irq_mask(unsigned int irqno) { unsigned long mask; - +#ifdef CONFIG_S3C2440_C_FIQ + unsigned long flags; +#endif irqno -= IRQ_EINT0; - +#ifdef CONFIG_S3C2440_C_FIQ + local_save_flags(flags); + local_fiq_disable(); +#endif mask = __raw_readl(S3C2410_INTMSK); mask |= 1UL << irqno; __raw_writel(mask, S3C2410_INTMSK); +#ifdef CONFIG_S3C2440_C_FIQ + local_irq_restore(flags); +#endif } static inline void @@ -59,9 +69,19 @@ s3c_irq_maskack(unsigned int irqno) { unsigned long bitval = 1UL << (irqno - IRQ_EINT0); unsigned long mask; +#ifdef CONFIG_S3C2440_C_FIQ + unsigned long flags; +#endif +#ifdef CONFIG_S3C2440_C_FIQ + local_save_flags(flags); + local_fiq_disable(); +#endif mask = __raw_readl(S3C2410_INTMSK); __raw_writel(mask|bitval, S3C2410_INTMSK); +#ifdef CONFIG_S3C2440_C_FIQ + local_irq_restore(flags); +#endif __raw_writel(bitval, S3C2410_SRCPND); __raw_writel(bitval, S3C2410_INTPND); @@ -72,15 +92,25 @@ static void s3c_irq_unmask(unsigned int irqno) { unsigned long mask; +#ifdef CONFIG_S3C2440_C_FIQ + unsigned long flags; +#endif if (irqno != IRQ_TIMER4 && irqno != IRQ_EINT8t23) irqdbf2("s3c_irq_unmask %d\n", irqno); irqno -= IRQ_EINT0; +#ifdef CONFIG_S3C2440_C_FIQ + local_save_flags(flags); + local_fiq_disable(); +#endif mask = __raw_readl(S3C2410_INTMSK); mask &= ~(1UL << irqno); __raw_writel(mask, S3C2410_INTMSK); +#ifdef CONFIG_S3C2440_C_FIQ + local_irq_restore(flags); +#endif } struct irq_chip s3c_irq_level_chip = { @@ -559,26 +589,26 @@ void __init s3c24xx_init_irq(void) last = 0; for (i = 0; i < 4; i++) { - pend = __raw_readl(S3C2410_INTPND); + pend = __raw_readl(S3C2410_SUBSRCPND); if (pend == 0 || pend == last) break; - __raw_writel(pend, S3C2410_SRCPND); - __raw_writel(pend, S3C2410_INTPND); - printk("irq: clearing pending status %08x\n", (int)pend); + printk("irq: clearing subpending status %08x\n", (int)pend); + __raw_writel(pend, S3C2410_SUBSRCPND); last = pend; } last = 0; for (i = 0; i < 4; i++) { - pend = __raw_readl(S3C2410_SUBSRCPND); + pend = __raw_readl(S3C2410_INTPND); if (pend == 0 || pend == last) break; - printk("irq: clearing subpending status %08x\n", (int)pend); - __raw_writel(pend, S3C2410_SUBSRCPND); + __raw_writel(pend, S3C2410_SRCPND); + __raw_writel(pend, S3C2410_INTPND); + printk("irq: clearing pending status %08x\n", (int)pend); last = pend; } diff --git a/drivers/input/misc/pcf50633-input.c b/drivers/input/misc/pcf50633-input.c index 039dcb00ebd..008de0c5834 100644 --- a/drivers/input/misc/pcf50633-input.c +++ b/drivers/input/misc/pcf50633-input.c @@ -55,7 +55,6 @@ pcf50633_input_irq(int irq, void *data) static int __devinit pcf50633_input_probe(struct platform_device *pdev) { struct pcf50633_input *input; - struct pcf50633_subdev_pdata *pdata = pdev->dev.platform_data; struct input_dev *input_dev; int ret; @@ -71,7 +70,7 @@ static int __devinit pcf50633_input_probe(struct platform_device *pdev) } platform_set_drvdata(pdev, input); - input->pcf = pdata->pcf; + input->pcf = dev_to_pcf50633(pdev->dev.parent); input->input_dev = input_dev; input_dev->name = "PCF50633 PMU events"; @@ -85,9 +84,9 @@ static int __devinit pcf50633_input_probe(struct platform_device *pdev) kfree(input); return ret; } - pcf50633_register_irq(pdata->pcf, PCF50633_IRQ_ONKEYR, + pcf50633_register_irq(input->pcf, PCF50633_IRQ_ONKEYR, pcf50633_input_irq, input); - pcf50633_register_irq(pdata->pcf, PCF50633_IRQ_ONKEYF, + pcf50633_register_irq(input->pcf, PCF50633_IRQ_ONKEYF, pcf50633_input_irq, input); return 0; diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index 8cc453c85ea..ac11fd783a7 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -11,6 +11,69 @@ menuconfig INPUT_TOUCHSCREEN if INPUT_TOUCHSCREEN +menuconfig TOUCHSCREEN_FILTER + boolean "Touchscreen Filtering" + depends on INPUT_TOUCHSCREEN + select TOUCHSCREEN_FILTER_GROUP + select TOUCHSCREEN_FILTER_MEDIAN + select TOUCHSCREEN_FILTER_MEAN + select TOUCHSCREEN_FILTER_LINEAR + help + Select this to include kernel touchscreen filter support. The filters + can be combined in any order in your machine init and the parameters + for them can also be set there. + +config TOUCHSCREEN_FILTER_GROUP + bool "Group Touchscreen Filter" + depends on INPUT_TOUCHSCREEN && TOUCHSCREEN_FILTER + default Y + help + Say Y here if you want to use the Group touchscreen filter, it + avoids using atypical samples. + +config TOUCHSCREEN_FILTER_MEDIAN + bool "Median Average Touchscreen Filter" + depends on INPUT_TOUCHSCREEN && TOUCHSCREEN_FILTER + default Y + help + Say Y here if you want to use the Median touchscreen filter, it's + highly effective if you data is noisy with occasional excursions. + +config TOUCHSCREEN_FILTER_MEAN + bool "Mean Average Touchscreen Filter" + depends on INPUT_TOUCHSCREEN && TOUCHSCREEN_FILTER + default Y + help + Say Y here if you want to use the Mean touchscreen filter, it + can further improve decent quality data by removing jitter + +config TOUCHSCREEN_FILTER_LINEAR + bool "Linear Touchscreen Filter" + depends on INPUT_TOUCHSCREEN && TOUCHSCREEN_FILTER + default Y + help + Say Y here if you want to use the Linear touchscreen filter, it + enables the use of calibration data for the touchscreen. + +config TOUCHSCREEN_S3C2410 + tristate "Samsung S3C2410 touchscreen input driver" + depends on ARCH_S3C2410 && INPUT && INPUT_TOUCHSCREEN + select S3C24XX_ADC + select SERIO + help + Say Y here if you have the s3c2410 touchscreen. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called s3c2410_ts. + +config TOUCHSCREEN_S3C2410_DEBUG + boolean "Samsung S3C2410 touchscreen debug messages" + depends on TOUCHSCREEN_S3C2410 + help + Select this if you want debug messages + config TOUCHSCREEN_ADS7846 tristate "ADS7846/TSC2046 and ADS7843 based touchscreens" depends on SPI_MASTER diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 15fa62cffc7..453477fe012 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -42,3 +42,9 @@ obj-$(CONFIG_TOUCHSCREEN_WM97XX_MAINSTONE) += mainstone-wm97xx.o obj-$(CONFIG_TOUCHSCREEN_WM97XX_ZYLONITE) += zylonite-wm97xx.o obj-$(CONFIG_TOUCHSCREEN_W90X900) += w90p910_ts.o obj-$(CONFIG_TOUCHSCREEN_PCAP) += pcap_ts.o +obj-$(CONFIG_TOUCHSCREEN_FILTER) += ts_filter_chain.o +obj-$(CONFIG_TOUCHSCREEN_FILTER_GROUP) += ts_filter_group.o +obj-$(CONFIG_TOUCHSCREEN_FILTER_LINEAR) += ts_filter_linear.o +obj-$(CONFIG_TOUCHSCREEN_FILTER_MEDIAN) += ts_filter_median.o +obj-$(CONFIG_TOUCHSCREEN_FILTER_MEAN) += ts_filter_mean.o +obj-$(CONFIG_TOUCHSCREEN_S3C2410) += s3c2410_ts.o diff --git a/drivers/input/touchscreen/s3c2410_ts.c b/drivers/input/touchscreen/s3c2410_ts.c new file mode 100644 index 00000000000..db083a16284 --- /dev/null +++ b/drivers/input/touchscreen/s3c2410_ts.c @@ -0,0 +1,552 @@ +/* + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Copyright (c) 2004 Arnaud Patard <arnaud.patard@rtp-net.org> + * iPAQ H1940 touchscreen support + * + * ChangeLog + * + * 2004-09-05: Herbert Pötzl <herbert@13thfloor.at> + * - Added clock (de-)allocation code + * + * 2005-03-06: Arnaud Patard <arnaud.patard@rtp-net.org> + * - h1940_ -> s3c2410 (this driver is now also used on the n30 + * machines :P) + * - Debug messages are now enabled with the config option + * TOUCHSCREEN_S3C2410_DEBUG + * - Changed the way the value are read + * - Input subsystem should now work + * - Use ioremap and readl/writel + * + * 2005-03-23: Arnaud Patard <arnaud.patard@rtp-net.org> + * - Make use of some undocumented features of the touchscreen + * controller + * + * 2007-05-23: Harald Welte <laforge@openmoko.org> + * - Add proper support for S32440 + * + * 2008-06-23: Andy Green <andy@warmcat.com> + * - Removed averaging system + * - Added generic Touchscreen filter stuff + * + * 2008-11-27: Nelson Castillo <arhuaco@freaks-unidos.net> + * - Improve interrupt handling + * + * 2009-04-09: Nelson Castillo <arhuaco@freaks-unidos.net> + * - Use s3c-adc API (Vasily Khoruzhick <anarsoul@gmail.com> provided + * a working example for a simpler version of this driver). + */ + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/init.h> +#include <linux/serio.h> +#include <linux/timer.h> +#include <linux/kfifo.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <asm/irq.h> + +#include <mach/regs-gpio.h> +#include <mach/gpio-nrs.h> +#include <mach/gpio-fns.h> +#include <mach/ts.h> +#include <mach/hardware.h> +#include <plat/regs-adc.h> +#include <plat/adc.h> + +#include <linux/input/touchscreen/ts_filter_chain.h> + +/* For ts.dev.id.version */ +#define S3C2410TSVERSION 0x0101 + +#define WAIT4INT(x) (((x)<<8) | \ + S3C2410_ADCTSC_YM_SEN | \ + S3C2410_ADCTSC_YP_SEN | \ + S3C2410_ADCTSC_XP_SEN | \ + S3C2410_ADCTSC_XY_PST(3)) + +#define TSPRINTK(fmt, args...) \ + printk(KERN_DEBUG "%s: " fmt, __func__ , ## args) +#ifdef CONFIG_TOUCHSCREEN_S3C2410_DEBUG +# define DPRINTK TSPRINTK +#else +# define DPRINTK(fmt, args...) +#endif + + +MODULE_AUTHOR("Arnaud Patard <arnaud.patard@rtp-net.org>"); +MODULE_DESCRIPTION("s3c2410 touchscreen driver"); +MODULE_LICENSE("GPL"); + +/* + * Definitions & global arrays. + */ + +static char *s3c2410ts_name = "s3c2410 TouchScreen"; + +#define TS_RELEASE_TIMEOUT (HZ >> 7 ? HZ >> 7 : 1) /* 8ms (5ms if HZ is 200) */ +#define TS_EVENT_FIFO_SIZE (2 << 6) /* Must be a power of 2. */ + +/* + * Per-touchscreen data. + */ + +enum ts_state {TS_STATE_STANDBY, TS_STATE_PRESSED, TS_STATE_RELEASE_PENDING, + TS_STATE_RELEASE}; + +struct s3c2410ts { + struct input_dev *dev; + struct ts_filter_chain *chain; + enum ts_state state; + int is_down; + struct kfifo *event_fifo; + struct s3c_adc_client *adc_client; + unsigned adc_selected; +}; + +static struct s3c2410ts ts; + +static void __iomem *base_addr; + +/* + * A few low level functions. + */ + +static inline void s3c2410_ts_connect(void) +{ + s3c2410_gpio_cfgpin(S3C2410_GPG(12), S3C2410_GPG12_XMON); + s3c2410_gpio_cfgpin(S3C2410_GPG(13), S3C2410_GPG13_nXPON); + s3c2410_gpio_cfgpin(S3C2410_GPG(14), S3C2410_GPG14_YMON); + s3c2410_gpio_cfgpin(S3C2410_GPG(15), S3C2410_GPG15_nYPON); +} + +/* + * Code that starts ADC conversions. + */ + +static void ts_adc_timer_f(unsigned long data); +static struct timer_list ts_adc_timer = TIMER_INITIALIZER(ts_adc_timer_f, 0, 0); + +static void ts_adc_timer_f(unsigned long data) +{ + if (s3c_adc_start(ts.adc_client, 0, 1)) + mod_timer(&ts_adc_timer, jiffies + 1); +} + +static void s3c2410_ts_start_adc_conversion(void) +{ + if (ts.adc_selected) + mod_timer(&ts_adc_timer, jiffies + 1); + else + ts_adc_timer_f(0); +} + +/* Callback for the s3c-adc API. */ +void adc_selected_f(unsigned selected) +{ + ts.adc_selected = selected; +} + +/* + * Just send the input events. + */ + +enum ts_input_event {IE_DOWN = 0, IE_UP}; + +static void ts_input_report(int event, int coords[]) +{ +#ifdef CONFIG_TOUCHSCREEN_S3C2410_DEBUG + static char *s[] = {"down", "up"}; + struct timeval tv; + + do_gettimeofday(&tv); +#endif + + if (event == IE_DOWN) { + input_report_abs(ts.dev, ABS_X, coords[0]); + input_report_abs(ts.dev, ABS_Y, coords[1]); + input_report_key(ts.dev, BTN_TOUCH, 1); + input_report_abs(ts.dev, ABS_PRESSURE, 1); + + DPRINTK("T:%06d %6s (X:%03d, Y:%03d)\n", + (int)tv.tv_usec, s[event], coords[0], coords[1]); + } else { + input_report_key(ts.dev, BTN_TOUCH, 0); + input_report_abs(ts.dev, ABS_PRESSURE, 0); + + DPRINTK("T:%06d %6s\n", + (int)tv.tv_usec, s[event]); + } + + input_sync(ts.dev); +} + +/* + * Manage the state of the touchscreen. + */ + +static void event_send_timer_f(unsigned long data); + +static struct timer_list event_send_timer = + TIMER_INITIALIZER(event_send_timer_f, 0, 0); + +static void event_send_timer_f(unsigned long data) +{ + static int noop_counter; + int event_type; + + while (__kfifo_get(ts.event_fifo, (unsigned char *)&event_type, + sizeof(int))) { + int buf[2]; + + switch (event_type) { + case 'D': + if (ts.state == TS_STATE_RELEASE_PENDING) + /* Ignore short UP event. */ + ts.state = TS_STATE_PRESSED; + break; + + case 'U': + ts.state = TS_STATE_RELEASE_PENDING; + break; + + case 'P': + if (ts.is_down) /* Stylus_action needs a conversion. */ + s3c2410_ts_start_adc_conversion(); + + if (unlikely(__kfifo_get(ts.event_fifo, + (unsigned char *)buf, + sizeof(int) * 2) + != sizeof(int) * 2)) { + /* This will only happen if we have a bug. */ + TSPRINTK("Invalid packet\n"); + return; + } + + ts_input_report(IE_DOWN, buf); + ts.state = TS_STATE_PRESSED; + break; + } + + noop_counter = 0; + } + + if (noop_counter++ >= 1) { + noop_counter = 0; + if (ts.state == TS_STATE_RELEASE_PENDING) { + /* + * We delay the UP event for a while to avoid jitter. + * If we get a DOWN event we do not send it. + */ + ts_input_report(IE_UP, NULL); + ts.state = TS_STATE_STANDBY; + + ts_filter_chain_clear(ts.chain); + } + } else { + mod_timer(&event_send_timer, jiffies + TS_RELEASE_TIMEOUT); + } +} + +/* + * Manage interrupts. + */ + +static irqreturn_t stylus_updown(int irq, void *dev_id) +{ + unsigned long data0; + unsigned long data1; + int event_type; + + data0 = readl(base_addr + S3C2410_ADCDAT0); + data1 = readl(base_addr + S3C2410_ADCDAT1); + + ts.is_down = !(data0 & S3C2410_ADCDAT0_UPDOWN) && + !(data1 & S3C2410_ADCDAT0_UPDOWN); + + event_type = ts.is_down ? 'D' : 'U'; + + if (unlikely(__kfifo_put(ts.event_fifo, (unsigned char *)&event_type, + sizeof(int)) != sizeof(int))) + /* Only happens if we have a bug. */ + TSPRINTK("FIFO full\n"); + + if (ts.is_down) + s3c2410_ts_start_adc_conversion(); + else + writel(WAIT4INT(0), base_addr + S3C2410_ADCTSC); + + mod_timer(&event_send_timer, jiffies + 1); + + return IRQ_HANDLED; +} + +static void stylus_adc_action(unsigned p0, unsigned p1, unsigned *conv_left) +{ + int buf[3]; + + /* TODO: Do we really need this? */ + if (p0 & S3C2410_ADCDAT0_AUTO_PST || + p1 & S3C2410_ADCDAT1_AUTO_PST) { + *conv_left = 1; + return; + } + + buf[1] = p0; + buf[2] = p1; + + switch (ts_filter_chain_feed(ts.chain, &buf[1])) { + case 0: + /* The filter wants more points. */ + *conv_left = 1; + return; + case 1: + /* We have a point from the filters or no filtering enabled. */ + buf[0] = 'P'; + break; + default: + TSPRINTK("invalid return value\n"); + case -1: + /* Too much noise. Ignore the event. */ + ts_filter_chain_clear(ts.chain); + writel(WAIT4INT(1), base_addr + S3C2410_ADCTSC); + return; + }; + + if (unlikely(__kfifo_put(ts.event_fifo, (unsigned char *)buf, + sizeof(int) * 3) != sizeof(int) * 3)) + /* This will only happen if we have a bug. */ + TSPRINTK("FIFO full\n"); + + writel(WAIT4INT(1), base_addr + S3C2410_ADCTSC); + mod_timer(&event_send_timer, jiffies + 1); + + return; +} + +/* + * The functions for inserting/removing us as a module. + */ + +static int __init s3c2410ts_probe(struct platform_device *pdev) +{ + int rc; + struct s3c2410_ts_mach_info *info; + struct input_dev *input_dev; + int ret = 0; + + dev_info(&pdev->dev, "Starting\n"); + + info = (struct s3c2410_ts_mach_info *)pdev->dev.platform_data; + + if (!info) { + dev_err(&pdev->dev, "No platform data\n"); + return -EINVAL; + } + + base_addr = ioremap(S3C2410_PA_ADC, 0x20); + if (base_addr == NULL) { + dev_err(&pdev->dev, "Failed to remap register block\n"); + ret = -ENOMEM; + goto bail0; + } + + /* If we acutally are a S3C2410: Configure GPIOs */ + if (!strcmp(pdev->name, "s3c2410-ts")) + s3c2410_ts_connect(); + + writel(WAIT4INT(0), base_addr + S3C2410_ADCTSC); + + /* Initialise input stuff */ + memset(&ts, 0, sizeof(struct s3c2410ts)); + + ts.adc_client = + s3c_adc_register(pdev, adc_selected_f, stylus_adc_action, 1); + if (!ts.adc_client) { + dev_err(&pdev->dev, + "Unable to register s3c2410_ts as s3_adc client\n"); + iounmap(base_addr); + ret = -EIO; + goto bail0; + } + + input_dev = input_allocate_device(); + if (!input_dev) { + dev_err(&pdev->dev, "Unable to allocate the input device\n"); + ret = -ENOMEM; + goto bail1; + } + + ts.dev = input_dev; + ts.dev->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY) | + BIT_MASK(EV_ABS); + ts.dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + input_set_abs_params(ts.dev, ABS_X, 0, 0x3FF, 0, 0); + input_set_abs_params(ts.dev, ABS_Y, 0, 0x3FF, 0, 0); + input_set_abs_params(ts.dev, ABS_PRESSURE, 0, 1, 0, 0); + + ts.dev->name = s3c2410ts_name; + ts.dev->id.bustype = BUS_RS232; + ts.dev->id.vendor = 0xDEAD; + ts.dev->id.product = 0xBEEF; + ts.dev->id.version = S3C2410TSVERSION; + ts.state = TS_STATE_STANDBY; + ts.event_fifo = kfifo_alloc(TS_EVENT_FIFO_SIZE, GFP_KERNEL, NULL); + if (IS_ERR(ts.event_fifo)) { + ret = -EIO; + goto bail2; + } + + /* Create the filter chain set up for the 2 coordinates we produce. */ + ts.chain = ts_filter_chain_create(pdev, info->filter_config, 2); + + if (IS_ERR(ts.chain)) + goto bail2; + + ts_filter_chain_clear(ts.chain); + + /* Get IRQ. */ + if (request_irq(IRQ_TC, stylus_updown, 0, "s3c2410_action", ts.dev)) { + dev_err(&pdev->dev, "Could not allocate ts IRQ_TC !\n"); + iounmap(base_addr); + ret = -EIO; + goto bail3; + } + + dev_info(&pdev->dev, "Successfully loaded\n"); + + /* All went ok. Register to the input system. */ + rc = input_register_device(ts.dev); + if (rc) { + dev_info(&pdev->dev, "Could not register input device\n"); + ret = -EIO; + goto bail4; + } + + return 0; + +bail4: + free_irq(IRQ_TC, ts.dev); + iounmap(base_addr); + disable_irq(IRQ_TC); +bail3: + ts_filter_chain_destroy(ts.chain); + kfifo_free(ts.event_fifo); +bail2: + input_unregister_device(ts.dev); +bail1: + iounmap(base_addr); +bail0: + + return ret; +} + +static int s3c2410ts_remove(struct platform_device *pdev) +{ + disable_irq(IRQ_TC); + free_irq(IRQ_TC, ts.dev); + + s3c_adc_release(ts.adc_client); + input_unregister_device(ts.dev); + iounmap(base_addr); + + ts_filter_chain_destroy(ts.chain); + + kfifo_free(ts.event_fifo); + + return 0; +} + +#ifdef CONFIG_PM + +#define TSC_SLEEP (S3C2410_ADCTSC_PULL_UP_DISABLE | \ + S3C2410_ADCTSC_XY_PST(0)) + +static int s3c2410ts_suspend(struct platform_device *pdev, pm_message_t state) +{ + writel(TSC_SLEEP, base_addr + S3C2410_ADCTSC); + writel(readl(base_addr + S3C2410_ADCCON) | S3C2410_ADCCON_STDBM, + base_addr + S3C2410_ADCCON); + disable_irq(IRQ_TC); + + return 0; +} + +static int s3c2410ts_resume(struct platform_device *pdev) +{ + ts_filter_chain_clear(ts.chain); + enable_irq(IRQ_TC); + writel(WAIT4INT(0), base_addr + S3C2410_ADCTSC); + + return 0; +} + +#else +#define s3c2410ts_suspend NULL +#define s3c2410ts_resume NULL +#endif + +static struct platform_driver s3c2410ts_driver = { + .driver = { + .name = "s3c2410-ts", + .owner = THIS_MODULE, + }, + .probe = s3c2410ts_probe, + .remove = s3c2410ts_remove, + .suspend = s3c2410ts_suspend, + .resume = s3c2410ts_resume, +}; + +static struct platform_driver s3c2440ts_driver = { + .driver = { + .name = "s3c2440-ts", + .owner = THIS_MODULE, + }, + .probe = s3c2410ts_probe, + .remove = s3c2410ts_remove, + .suspend = s3c2410ts_suspend, + .resume = s3c2410ts_resume, +}; + +static int __init s3c2410ts_init(void) +{ + int rc; + + rc = platform_driver_register(&s3c2410ts_driver); + if (rc < 0) + return rc; + + rc = platform_driver_register(&s3c2440ts_driver); + if (rc < 0) + platform_driver_unregister(&s3c2410ts_driver); + + return rc; +} + +static void __exit s3c2410ts_exit(void) +{ + platform_driver_unregister(&s3c2440ts_driver); + platform_driver_unregister(&s3c2410ts_driver); +} + +module_init(s3c2410ts_init); +module_exit(s3c2410ts_exit); + diff --git a/drivers/input/touchscreen/ts_filter_chain.c b/drivers/input/touchscreen/ts_filter_chain.c new file mode 100644 index 00000000000..8b5e2145b27 --- /dev/null +++ b/drivers/input/touchscreen/ts_filter_chain.c @@ -0,0 +1,183 @@ +/* + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Copyright (c) 2008,2009 Andy Green <andy@warmcat.com> + */ + +#include <linux/kernel.h> +#include <linux/device.h> + +#include <linux/input/touchscreen/ts_filter_chain.h> +#include <linux/input/touchscreen/ts_filter.h> + +/* + * Tux, would you like the following function in /lib? + * It helps us avoid silly code. + */ + +/** + * sptrlen - Count how many non-null pointers are in a pointer array + * @arr: The array of pointers + */ +static int sptrlen(const void *arr) +{ + /* All pointers have the same size. */ + const int **p = (const int **)arr; + int len = 0; + + while (*(p++)) + len++; + + return len; +} + + +struct ts_filter_chain { + /* All of the filters. */ + struct ts_filter **arr; + /* Filters that can propagate values in the chain. */ + struct ts_filter **pchain; + /* Length of the pchain array. */ + int pchain_len; + /* FIXME: Add a spinlock and use it. */ +}; + +struct ts_filter_chain *ts_filter_chain_create( + struct platform_device *pdev, + const struct ts_filter_chain_configuration conf[], + int count_coords) +{ + struct ts_filter_chain *c; + int count = 0; + int len; + + BUG_ON((count_coords < 1)); + BUG_ON(count_coords > MAX_TS_FILTER_COORDS); + + c = kzalloc(sizeof(struct ts_filter_chain), GFP_KERNEL); + if (!c) + goto create_err_1; + + len = (sptrlen(conf) + 1); + /* Memory for two null-terminated arrays of filters. */ + c->arr = kzalloc(2 * sizeof(struct ts_filter *) * len, GFP_KERNEL); + if (!c->arr) + goto create_err_1; + c->pchain = c->arr + len; + + while (conf->api) { + /* TODO: Can we get away with only sending pdev->dev? */ + struct ts_filter *f = + (conf->api->create)(pdev, conf->config, count_coords); + if (!f) { + dev_info(&pdev->dev, "Filter %d creation failed\n", + count); + goto create_err_2; + } + + f->api = conf->api; + c->arr[count++] = f; + + if (f->api->haspoint && f->api->getpoint && f->api->process) + c->pchain[c->pchain_len++] = f; + + conf++; + } + + dev_info(&pdev->dev, "%d filter(s) initialized\n", count); + + return c; + +create_err_2: + ts_filter_chain_destroy(c); /* Also frees c. */ +create_err_1: + dev_info(&pdev->dev, "Error in filter chain initialization\n"); + /* + * FIXME: Individual filters have to return errors this way. + * We only have to forward the errors we find. + */ + return ERR_PTR(-ENOMEM); +} +EXPORT_SYMBOL_GPL(ts_filter_chain_create); + +void ts_filter_chain_destroy(struct ts_filter_chain *c) +{ + if (c->arr) { + struct ts_filter **a = c->arr; + while (*a) { + ((*a)->api->destroy)(*a); + a++; + } + kfree(c->arr); + } + kfree(c); +} +EXPORT_SYMBOL_GPL(ts_filter_chain_destroy); + +void ts_filter_chain_clear(struct ts_filter_chain *c) +{ + struct ts_filter **a = c->arr; + + while (*a) { + if ((*a)->api->clear) + ((*a)->api->clear)(*a); + a++; + } +} +EXPORT_SYMBOL_GPL(ts_filter_chain_clear); + +static void ts_filter_chain_scale(struct ts_filter_chain *c, int *coords) +{ + struct ts_filter **a = c->arr; + while (*a) { + if ((*a)->api->scale) + ((*a)->api->scale)(*a, coords); + a++; + } +} + +int ts_filter_chain_feed(struct ts_filter_chain *c, int *coords) +{ + int len = c->pchain_len; + int i = len - 1; + + if (!c->pchain[0]) + return 1; /* Nothing to do. */ + + BUG_ON(c->pchain[0]->api->haspoint(c->pchain[0])); + + if (c->pchain[0]->api->process(c->pchain[0], coords)) + return -1; + + while (i >= 0 && i < len) { + if (c->pchain[i]->api->haspoint(c->pchain[i])) { + c->pchain[i]->api->getpoint(c->pchain[i], coords); + if (++i < len && + c->pchain[i]->api->process(c->pchain[i], coords)) + return -1; /* Error. */ + } else { + i--; + } + } + + if (i >= 0) { /* Same as i == len. */ + ts_filter_chain_scale(c, coords); + return 1; + } + + return 0; +} +EXPORT_SYMBOL_GPL(ts_filter_chain_feed); + diff --git a/drivers/input/touchscreen/ts_filter_group.c b/drivers/input/touchscreen/ts_filter_group.c new file mode 100644 index 00000000000..9e344c5f19f --- /dev/null +++ b/drivers/input/touchscreen/ts_filter_group.c @@ -0,0 +1,330 @@ +/* + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Copyright (C) 2008,2009 by Openmoko, Inc. + * Author: Nelson Castillo <arhuaco@freaks-unidos.net> + * All rights reserved. + * + * + * This filter is useful to reject samples that are not reliable. We consider + * that a sample is not reliable if it deviates form the Majority. + * This filter mixes information from all the available dimensions. It means + * that for two dimensions we draw a rectangle where the thought-to-be good + * points can be found. + * + * The implementation would be more efficient with a double-linked list but + * let's keep it simple for now. + * + * 1) We collect S samples and keep it in sorted sets. + * - Points that are "close enough" are considered to be in the same set. + * We don't actually keep the sets but ranges of points. + * + * 2) For each dimension: + * - We choose the range with more elements. If more than "threshold" + * points are in this range we use the minimum and the maximum point + * of the range to define the valid range for this dimension [min, max], + * otherwise we discard all the points and the ranges and go to step 1. + * + * 3) We consider the unsorted S samples and try to feed them to the next + * filter in the chain. If one of the points of each sample + * is not in the allowed range for its dimension we discard the sample. + * + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/input/touchscreen/ts_filter_group.h> + +struct coord_range { + int min; /* Minimum value of the range. */ + int max; /* Maximum value of the range */ + int N; /* Number of points in the range. */ +}; + +struct ts_filter_group { + /* Private filter configuration. */ + struct ts_filter_group_configuration *config; + /* Filter API. */ + struct ts_filter tsf; + + int N; /* How many samples we have. */ + int *samples[MAX_TS_FILTER_COORDS]; /* The samples: our input. */ + + /* Temporal values that help us compute range_min and range_max. */ + struct coord_range *ranges[MAX_TS_FILTER_COORDS]; /* Ranges. */ + int n_ranges[MAX_TS_FILTER_COORDS]; /* Number of ranges */ + + /* Computed ranges that help us filter the points. */ + int range_max[MAX_TS_FILTER_COORDS]; /* Max. computed ranges. */ + int range_min[MAX_TS_FILTER_COORDS]; /* Min. computed ranges. */ + + int tries_left; /* We finish if we can't get enough samples. */ + int ready; /* If we are ready to deliver samples. */ + int result; /* Index of the point being returned. */ +}; + +#define ts_filter_to_filter_group(f) \ + container_of(f, struct ts_filter_group, tsf) + + +static void ts_filter_group_clear_internal(struct ts_filter_group *tsfg, + int attempts) +{ + int n; + tsfg->N = 0; + tsfg->tries_left = attempts; + tsfg->ready = 0; + tsfg->result = 0; + for (n = 0; n < tsfg->tsf.count_coords; n++) + tsfg->n_ranges[n] = 0; +} + +static void ts_filter_group_clear(struct ts_filter *tsf) +{ + struct ts_filter_group *tsfg = ts_filter_to_filter_group(tsf); + + ts_filter_group_clear_internal(tsfg, tsfg->config->attempts); +} + +static struct ts_filter *ts_filter_group_create( + struct platform_device *pdev, + const struct ts_filter_configuration *conf, + int count_coords) +{ + struct ts_filter_group *tsfg; + int i; + + tsfg = kzalloc(sizeof(struct ts_filter_group), GFP_KERNEL); + if (!tsfg) + return NULL; + + tsfg->config = container_of(conf, + struct ts_filter_group_configuration, + config); + tsfg->tsf.count_coords = count_coords; + + BUG_ON(tsfg->config->attempts <= 0); + BUG_ON(tsfg->config->length < tsfg->config->threshold); + + tsfg->samples[0] = kmalloc(count_coords * sizeof(int) * + tsfg->config->length, GFP_KERNEL); + if (!tsfg->samples[0]) { + kfree(tsfg); + return NULL; + } + for (i = 1; i < count_coords; ++i) + tsfg->samples[i] = tsfg->samples[0] + i * tsfg->config->length; + + tsfg->ranges[0] = kmalloc(count_coords * sizeof(struct coord_range) * + tsfg->config->length, GFP_KERNEL); + if (!tsfg->ranges[0]) { + kfree(tsfg->samples[0]); + kfree(tsfg); + return NULL; + } + for (i = 1; i < count_coords; ++i) + tsfg->ranges[i] = tsfg->ranges[0] + i * tsfg->config->length; + + ts_filter_group_clear_internal(tsfg, tsfg->config->attempts); + + dev_info(&pdev->dev, "Created Group filter len:%d coords:%d close:%d " + "thresh:%d\n", tsfg->config->length, count_coords, + tsfg->config->close_enough, tsfg->config->threshold); + + return &tsfg->tsf; +} + +static void ts_filter_group_destroy(struct ts_filter *tsf) +{ + struct ts_filter_group *tsfg = ts_filter_to_filter_group(tsf); + + kfree(tsfg->samples[0]); + kfree(tsfg->ranges[0]); + kfree(tsf); +} + +static void ts_filter_group_prepare_next(struct ts_filter *tsf); + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define IN_RANGE(c, r) ((c) >= (r).min - tsfg->config->close_enough && \ + (c) <= (r).max + tsfg->config->close_enough) + +static void delete_spot(struct coord_range *v, int n, int size) +{ + int i; + for (i = n; i < size - 1; ++i) + v[i] = v[i + 1]; +} + +static int ts_filter_group_process(struct ts_filter *tsf, int *coords) +{ + struct ts_filter_group *tsfg = ts_filter_to_filter_group(tsf); + int n; + int j; + + BUG_ON(tsfg->N >= tsfg->config->length); + BUG_ON(tsfg->ready); + + for (n = 0; n < tsfg->tsf.count_coords; n++) { + int i; + struct coord_range *range = tsfg->ranges[n]; + int *n_ranges = &tsfg->n_ranges[n]; + int found = 0; + + tsfg->samples[n][tsfg->N] = coords[n]; + + for (i = 0; i < *n_ranges; ++i) { + if (IN_RANGE(coords[n], range[i])) { + range[i].min = MIN(range[i].min, coords[n]); + range[i].max = MAX(range[i].max, coords[n]); + range[i].N++; + found = 1; + break; + } else if (coords[n] <= range[i].min) + break; /* We need to insert a range. */ + } + if (found) { /* We might need to melt ranges. */ + if (i && range[i - 1].max + tsfg->config->close_enough + >= range[i].min) { + BUG_ON(range[i - 1].max >= range[i].max); + range[i - 1].max = range[i].max; + range[i - 1].N += range[i].N; + delete_spot(range, i, *n_ranges); + (*n_ranges)--; + i--; + } + if (i < *n_ranges - 1 && range[i + 1].min - + tsfg->config->close_enough <= range[i].max) { + range[i].max = range[i + 1].max; + range[i].N += range[i + 1].N; + delete_spot(range, i + 1, *n_ranges); + (*n_ranges)--; + } + } else { + BUG_ON((*n_ranges) >= tsfg->config->length); + (*n_ranges)++; + for (j = *n_ranges - 1; j > i; --j) + range[j] = range[j - 1]; + range[i].N = 1; + range[i].min = coords[n]; + range[i].max = coords[n]; + } + } + + if (++tsfg->N < tsfg->config->length) + return 0; + + for (n = 0; n < tsfg->tsf.count_coords; ++n) { + int best = 0; + for (j = 1; j < tsfg->n_ranges[n]; ++j) + if (tsfg->ranges[n][best].N < tsfg->ranges[n][j].N) + best = j; + if (tsfg->ranges[n][best].N < tsfg->config->threshold) { + /* This set of points is not good enough for us. */ + if (--tsfg->tries_left) { + ts_filter_group_clear_internal + (tsfg, tsfg->tries_left); + /* No errors but we need more samples. */ + return 0; + } + return 1; /* We give up: error. */ + } + tsfg->range_min[n] = tsfg->ranges[n][best].min; + tsfg->range_max[n] = tsfg->ranges[n][best].max; + } + + ts_filter_group_prepare_next(tsf); + + return 0; +} + +/* + * This private function prepares a point that will be returned + * in ts_filter_group_getpoint if it is available. It updates + * the priv->ready state also. + */ +static void ts_filter_group_prepare_next(struct ts_filter *tsf) +{ + struct ts_filter_group *priv = ts_filter_to_filter_group(tsf); + int n; + + while (priv->result < priv->N) { + for (n = 0; n < priv->tsf.count_coords; ++n) { + if (priv->samples[n][priv->result] < + priv->range_min[n] || + priv->samples[n][priv->result] > priv->range_max[n]) + break; + } + + if (n == priv->tsf.count_coords) /* Sample is OK. */ + break; + + priv->result++; + } + + if (unlikely(priv->result >= priv->N)) { /* No sample to deliver. */ + ts_filter_group_clear_internal(priv, priv->config->attempts); + priv->ready = 0; + } else { + priv->ready = 1; + } +} + +static int ts_filter_group_haspoint(struct ts_filter *tsf) +{ + struct ts_filter_group *priv = ts_filter_to_filter_group(tsf); + + return priv->ready; +} + +static void ts_filter_group_getpoint(struct ts_filter *tsf, int *point) +{ + struct ts_filter_group *priv = ts_filter_to_filter_group(tsf); + int n; + + BUG_ON(!priv->ready); + + for (n = 0; n < priv->tsf.count_coords; n++) + point[n] = priv->samples[n][priv->result]; + + priv->result++; + + /* This call will update priv->ready. */ + ts_filter_group_prepare_next(tsf); +} + +/* + * Get ready to process the next batch of points, forget + * points we could have delivered. + */ +static void ts_filter_group_scale(struct ts_filter *tsf, int *coords) +{ + struct ts_filter_group *priv = ts_filter_to_filter_group(tsf); + + ts_filter_group_clear_internal(priv, priv->config->attempts); +} + +const struct ts_filter_api ts_filter_group_api = { + .create = ts_filter_group_create, + .destroy = ts_filter_group_destroy, + .clear = ts_filter_group_clear, + .process = ts_filter_group_process, + .haspoint = ts_filter_group_haspoint, + .getpoint = ts_filter_group_getpoint, + .scale = ts_filter_group_scale, +}; +EXPORT_SYMBOL_GPL(ts_filter_group_api); + diff --git a/drivers/input/touchscreen/ts_filter_linear.c b/drivers/input/touchscreen/ts_filter_linear.c new file mode 100644 index 00000000000..8b496591eec --- /dev/null +++ b/drivers/input/touchscreen/ts_filter_linear.c @@ -0,0 +1,212 @@ +/* + * 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, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Copyright (C) 2008,2009 by Openmoko, Inc. + * Author: Nelson Castillo <arhuaco@freaks-unidos.net> + * All rights reserved. + * + * Linearly scale touchscreen values. + * + * Expose the TS_FILTER_LINEAR_NCONSTANTS for the linear transformation + * using sysfs. + * + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/string.h> + +#include <linux/input/touchscreen/ts_filter_linear.h> + +struct ts_filter_linear; + +/* Sysfs code. */ + +struct const_obj { + /* The actual private object. */ + struct ts_filter_linear *tsfl; + /* Our kobject. */ + struct kobject kobj; +}; + +#define to_const_obj(x) container_of(x, struct const_obj, kobj) + +struct const_attribute { + struct attribute attr; + ssize_t (*show)(struct const_obj *const, struct const_attribute *attr, + char *buf); + ssize_t (*store)(struct const_obj *const, struct const_attribute *attr, + const char *buf, size_t count); +}; + +#define to_const_attr(x) container_of(x, struct const_attribute, attr) + + +/* Private linear filter structure. */ + +struct ts_filter_linear { + /* Private configuration for this filter. */ + struct ts_filter_linear_configuration *config; + + /* Generic filter API. */ + struct ts_filter tsf; + + /* Linear constants for the transformation. */ + int constants[TS_FILTER_LINEAR_NCONSTANTS]; + + /* Sysfs. */ + + /* Our const_object. */ + struct const_obj c_obj; + /* Our type. We will stick operations to it. */ + struct kobj_type const_ktype; + /* Attrs. of the virtual files. */ + struct const_attribute kattrs[TS_FILTER_LINEAR_NCONSTANTS]; + /* Default Attrs. Always NULL for us. */ + struct attribute *attrs[TS_FILTER_LINEAR_NCONSTANTS + 1]; + /* Storage for the name of the virtual files. */ + char attr_names[TS_FILTER_LINEAR_NCONSTANTS][2]; +}; + +#define ts_filter_to_filter_linear(f) \ + container_of(f, struct ts_filter_linear, tsf) + +/* Sysfs functions. */ + +static ssize_t const_attr_show(struct kobject *kobj, + struct attribute *attr, + char *buf) +{ + struct const_attribute *a = to_const_attr(attr); + + return a->show(to_const_obj(kobj), a, buf); +} + +static ssize_t const_attr_store(struct kobject *kobj, + struct attribute *attr, + const char *buf, size_t len) +{ + struct const_attribute *a = to_const_attr(attr); + + return a->store(to_const_obj(kobj), a, buf, len); +} + +static struct sysfs_ops const_sysfs_ops = { + .show = const_attr_show, + .store = const_attr_store, +}; + +static void const_release(struct kobject *kobj) +{ + kfree(to_const_obj(kobj)->tsfl); +} + +static ssize_t const_show(struct const_obj *obj, struct const_attribute *attr, + char *buf) +{ + int who; + + sscanf(attr->attr.name, "%d", &who); + return sprintf(buf, "%d\n", obj->tsfl->constants[who]); +} + +static ssize_t const_store(struct const_obj *obj, struct const_attribute *attr, + const char *buf, size_t count) +{ + int who; + + sscanf(attr->attr.name, "%d", &who); + sscanf(buf, "%d", &obj->tsfl->constants[who]); + return count; +} + +/* Filter functions. */ + +static struct ts_filter *ts_filter_linear_create( + struct platform_device *pdev, + const struct ts_filter_configuration *conf, + int count_coords) +{ + struct ts_filter_linear *tsfl; + int i; + int ret; + + tsfl = kzalloc(sizeof(struct ts_filter_linear), GFP_KERNEL); + if (!tsfl) + return NULL; + + tsfl->config = container_of(conf, + struct ts_filter_linear_configuration, + config); + + tsfl->tsf.count_coords = count_coords; + + for (i = 0; i < TS_FILTER_LINEAR_NCONSTANTS; ++i) { + tsfl->constants[i] = tsfl->config->constants[i]; + + /* sysfs */ + sprintf(tsfl->attr_names[i], "%d", i); + tsfl->kattrs[i].attr.name = tsfl->attr_names[i]; + tsfl->kattrs[i].attr.mode = 0666; + tsfl->kattrs[i].show = const_show; + tsfl->kattrs[i].store = const_store; + tsfl->attrs[i] = &tsfl->kattrs[i].attr; + } + tsfl->attrs[i] = NULL; + + tsfl->const_ktype.sysfs_ops = &const_sysfs_ops; + tsfl->const_ktype.release = const_release; + tsfl->const_ktype.default_attrs = tsfl->attrs; + tsfl->c_obj.tsfl = tsfl; /* kernel frees tsfl in const_release */ + + ret = kobject_init_and_add(&tsfl->c_obj.kobj, &tsfl->const_ktype, + &pdev->dev.kobj, "calibration"); + if (ret) { + kobject_put(&tsfl->c_obj.kobj); + return NULL; + } + + dev_info(&pdev->dev, "Created Linear filter coords:%d\n", count_coords); + + return &tsfl->tsf; +} + +static void ts_filter_linear_destroy(struct ts_filter *tsf) +{ + struct ts_filter_linear *tsfl = ts_filter_to_filter_linear(tsf); + + /* Kernel frees tsfl in const_release. */ + kobject_put(&tsfl->c_obj.kobj); +} + +static void ts_filter_linear_scale(struct ts_filter *tsf, int *coords) +{ + struct ts_filter_linear *tsfl = ts_filter_to_filter_linear(tsf); + + int *k = tsfl->constants; + int c0 = coords[tsfl->config->coord0]; + int c1 = coords[tsfl->config->coord1]; + + coords[tsfl->config->coord0] = (k[2] + k[0] * c0 + k[1] * c1) / k[6]; + coords[tsfl->config->coord1] = (k[5] + k[3] * c0 + k[4] * c1) / k[6]; +} + +const struct ts_filter_api ts_filter_linear_api = { + .create = ts_filter_linear_create, + .destroy = ts_filter_linear_destroy, + .scale = ts_filter_linear_scale, +}; +EXPORT_SYMBOL_GPL(ts_filter_linear_api); + diff --git a/drivers/input/touchscreen/ts_filter_mean.c b/drivers/input/touchscreen/ts_filter_mean.c new file mode 100644 index 00000000000..ad4e9c1207c --- /dev/null +++ b/drivers/input/touchscreen/ts_filter_mean.c @@ -0,0 +1,174 @@ +/* + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Copyright (c) 2008,2009 + * Andy Green <andy@warmcat.com> + * Nelson Castillo <arhuaco@freaks-unidos.net> + * + * Simple mean filter. + * + */ + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/slab.h> + +#include <linux/input/touchscreen/ts_filter_mean.h> + +struct ts_filter_mean { + /* Copy of the private filter configuration. */ + struct ts_filter_mean_configuration *config; + /* Filter API. */ + struct ts_filter tsf; + + /* Index on a circular buffer. */ + int curr; + /* Useful to tell if the circular buffer is full(read:ready). */ + int count; + /* Sumation used to compute the mean. */ + int sum[MAX_TS_FILTER_COORDS]; + /* Keep point values and decrement them from the sum on time. */ + int *fifo[MAX_TS_FILTER_COORDS]; + /* Store the output of this filter. */ + int ready; +}; + +#define ts_filter_to_filter_mean(f) container_of(f, struct ts_filter_mean, tsf) + + +static void ts_filter_mean_clear(struct ts_filter *tsf); + +static struct ts_filter *ts_filter_mean_create( + struct platform_device *pdev, + const struct ts_filter_configuration *conf, + int count_coords) +{ + struct ts_filter_mean *priv; + int *v; + int n; + + priv = kzalloc(sizeof(struct ts_filter_mean), GFP_KERNEL); + if (!priv) + return NULL; + + priv->tsf.count_coords = count_coords; + priv->config = container_of(conf, + struct ts_filter_mean_configuration, + config); + + BUG_ON(priv->config->length <= 0); + + v = kmalloc(priv->config->length * sizeof(int) * count_coords, + GFP_KERNEL); + if (!v) + return NULL; + + for (n = 0; n < count_coords; n++) { + priv->fifo[n] = v; + v += priv->config->length; + } + + ts_filter_mean_clear(&priv->tsf); + + dev_info(&pdev->dev, "Created Mean filter len:%d coords:%d\n", + priv->config->length, count_coords); + + return &priv->tsf; +} + +static void ts_filter_mean_destroy(struct ts_filter *tsf) +{ + struct ts_filter_mean *priv = ts_filter_to_filter_mean(tsf); + + kfree(priv->fifo[0]); /* first guy has pointer from kmalloc */ + kfree(tsf); +} + +static void ts_filter_mean_clear(struct ts_filter *tsf) +{ + struct ts_filter_mean *priv = ts_filter_to_filter_mean(tsf); + + priv->count = 0; + priv->curr = 0; + priv->ready = 0; + memset(priv->sum, 0, tsf->count_coords * sizeof(int)); +} + +static int ts_filter_mean_process(struct ts_filter *tsf, int *coords) +{ + struct ts_filter_mean *priv = ts_filter_to_filter_mean(tsf); + int n; + + BUG_ON(priv->ready); + + for (n = 0; n < tsf->count_coords; n++) { + priv->sum[n] += coords[n]; + priv->fifo[n][priv->curr] = coords[n]; + } + + if (priv->count + 1 == priv->config->length) + priv->ready = 1; + else + priv->count++; + + priv->curr = (priv->curr + 1) % priv->config->length; + + return 0; /* No error. */ +} + +static int ts_filter_mean_haspoint(struct ts_filter *tsf) +{ + struct ts_filter_mean *priv = ts_filter_to_filter_mean(tsf); + + return priv->ready; +} + +static void ts_filter_mean_getpoint(struct ts_filter *tsf, int *point) +{ + struct ts_filter_mean *priv = ts_filter_to_filter_mean(tsf); + int n; + + BUG_ON(!priv->ready); + + for (n = 0; n < tsf->count_coords; n++) { + point[n] = priv->sum[n]; + priv->sum[n] -= priv->fifo[n][priv->curr]; + } + + priv->ready = 0; +} + +static void ts_filter_mean_scale(struct ts_filter *tsf, int *coords) +{ + int n; + struct ts_filter_mean *priv = ts_filter_to_filter_mean(tsf); + + for (n = 0; n < tsf->count_coords; n++) { + coords[n] += priv->config->length >> 1; /* Rounding. */ + coords[n] /= priv->config->length; + } +} + +const struct ts_filter_api ts_filter_mean_api = { + .create = ts_filter_mean_create, + .destroy = ts_filter_mean_destroy, + .clear = ts_filter_mean_clear, + .process = ts_filter_mean_process, + .scale = ts_filter_mean_scale, + .haspoint = ts_filter_mean_haspoint, + .getpoint = ts_filter_mean_getpoint, +}; +EXPORT_SYMBOL_GPL(ts_filter_mean_api); + diff --git a/drivers/input/touchscreen/ts_filter_median.c b/drivers/input/touchscreen/ts_filter_median.c new file mode 100644 index 00000000000..c608f7a9794 --- /dev/null +++ b/drivers/input/touchscreen/ts_filter_median.c @@ -0,0 +1,261 @@ +/* + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Copyright (c) 2008 Andy Green <andy@warmcat.com> + * + * + * Median averaging stuff. We sort incoming raw samples into an array of + * MEDIAN_SIZE length, discarding the oldest sample each time once we are full. + * We then return the sum of the middle three samples for X and Y. It means + * the final result must be divided by (3 * scaling factor) to correct for + * avoiding the repeated /3. + * + * This strongly rejects brief excursions away from a central point that is + * sticky in time compared to the excursion duration. + * + * Thanks to Dale Schumacher (who wrote some example code) and Carl-Daniel + * Halifinger who pointed out this would be a good method. + */ + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/input/touchscreen/ts_filter_median.h> + +struct ts_filter_median { + /* Private configuration. */ + struct ts_filter_median_configuration *config; + /* Generic Filter API. */ + struct ts_filter tsf; + + /* Count raw samples we get. */ + int samples_count; + /* + * Remember the last coordinates we got in order to know if + * we are moving slow or fast. + */ + int last_issued[MAX_TS_FILTER_COORDS]; + /* How many samples in the sort buffer are valid. */ + int valid; + /* Samples taken for median in sorted form. */ + int *sort[MAX_TS_FILTER_COORDS]; + /* Samples taken for median. */ + int *fifo[MAX_TS_FILTER_COORDS]; + /* Where we are in the fifo sample memory. */ + int pos; + /* Do we have a sample to deliver? */ + int ready; +}; + +#define ts_filter_to_filter_median(f) \ + container_of(f, struct ts_filter_median, tsf) + + +static void ts_filter_median_insert(int *p, int sample, int count) +{ + int n; + + /* Search through what we got so far to find where to put sample. */ + for (n = 0; n < count; n++) + if (sample < p[n]) { /* We met somebody bigger than us? */ + /* Starting from the end, push bigger guys down one. */ + for (count--; count >= n; count--) + p[count + 1] = p[count]; + p[n] = sample; /* Put us in place of first bigger. */ + return; + } + + p[count] = sample; /* Nobody was bigger than us, add us on the end. */ +} + +static void ts_filter_median_del(int *p, int value, int count) +{ + int index; + + for (index = 0; index < count; index++) + if (p[index] == value) { + for (; index < count; index++) + p[index] = p[index + 1]; + return; + } +} + + +static void ts_filter_median_clear(struct ts_filter *tsf) +{ + struct ts_filter_median *tsfm = ts_filter_to_filter_median(tsf); + + tsfm->pos = 0; + tsfm->valid = 0; + tsfm->ready = 0; + memset(&tsfm->last_issued[0], 1, tsf->count_coords * sizeof(int)); +} + +static struct ts_filter *ts_filter_median_create( + struct platform_device *pdev, + const struct ts_filter_configuration *conf, + int count_coords) +{ + int *p; + int n; + struct ts_filter_median *tsfm = kzalloc(sizeof(struct ts_filter_median), + GFP_KERNEL); + + if (!tsfm) + return NULL; + + tsfm->config = container_of(conf, + struct ts_filter_median_configuration, + config); + + tsfm->tsf.count_coords = count_coords; + + tsfm->config->midpoint = (tsfm->config->extent >> 1) + 1; + + p = kmalloc(2 * count_coords * sizeof(int) * (tsfm->config->extent + 1), + GFP_KERNEL); + if (!p) { + kfree(tsfm); + return NULL; + } + + for (n = 0; n < count_coords; n++) { + tsfm->sort[n] = p; + p += tsfm->config->extent + 1; + tsfm->fifo[n] = p; + p += tsfm->config->extent + 1; + } + + ts_filter_median_clear(&tsfm->tsf); + + dev_info(&pdev->dev, + "Created Median filter len:%d coords:%d dec_threshold:%d\n", + tsfm->config->extent, count_coords, + tsfm->config->decimation_threshold); + + return &tsfm->tsf; +} + +static void ts_filter_median_destroy(struct ts_filter *tsf) +{ + struct ts_filter_median *tsfm = ts_filter_to_filter_median(tsf); + + kfree(tsfm->sort[0]); /* First guy has pointer from kmalloc. */ + kfree(tsf); +} + +static void ts_filter_median_scale(struct ts_filter *tsf, int *coords) +{ + int n; + + for (n = 0; n < tsf->count_coords; n++) + coords[n] = (coords[n] + 2) / 3; +} + +/* + * Give us the raw sample data coords, and if we return 1 then you can + * get a filtered coordinate from coords. If we return 0 you didn't + * fill all the filters with samples yet. + */ + +static int ts_filter_median_process(struct ts_filter *tsf, int *coords) +{ + struct ts_filter_median *tsfm = ts_filter_to_filter_median(tsf); + int n; + int movement = 1; + + for (n = 0; n < tsf->count_coords; n++) { + /* Grab copy in insertion order to remove when oldest. */ + tsfm->fifo[n][tsfm->pos] = coords[n]; + /* Insert these samples in sorted order in the median arrays. */ + ts_filter_median_insert(tsfm->sort[n], coords[n], tsfm->valid); + } + /* Move us on in the fifo. */ + if (++tsfm->pos == (tsfm->config->extent + 1)) + tsfm->pos = 0; + + /* Have we finished a median sampling? */ + if (++tsfm->valid < tsfm->config->extent) + goto process_exit; /* No valid sample to use. */ + + BUG_ON(tsfm->valid != tsfm->config->extent); + + tsfm->valid--; + + /* + * Sum the middle 3 in the median sorted arrays. We don't divide back + * down which increases the sum resolution by a factor of 3 until the + * scale API function is called. + */ + for (n = 0; n < tsf->count_coords; n++) + /* Perform the deletion of the oldest sample. */ + ts_filter_median_del(tsfm->sort[n], tsfm->fifo[n][tsfm->pos], + tsfm->valid); + + tsfm->samples_count--; + if (tsfm->samples_count >= 0) + goto process_exit; + + for (n = 0; n < tsf->count_coords; n++) { + /* Give the coordinate result from summing median 3. */ + coords[n] = tsfm->sort[n][tsfm->config->midpoint - 1] + + tsfm->sort[n][tsfm->config->midpoint] + + tsfm->sort[n][tsfm->config->midpoint + 1]; + + movement += abs(tsfm->last_issued[n] - coords[n]); + } + + if (movement > tsfm->config->decimation_threshold) /* Moving fast. */ + tsfm->samples_count = tsfm->config->decimation_above; + else + tsfm->samples_count = tsfm->config->decimation_below; + + memcpy(&tsfm->last_issued[0], coords, tsf->count_coords * sizeof(int)); + + tsfm->ready = 1; + +process_exit: + return 0; +} + +static int ts_filter_median_haspoint(struct ts_filter *tsf) +{ + struct ts_filter_median *priv = ts_filter_to_filter_median(tsf); + + return priv->ready; +} + +static void ts_filter_median_getpoint(struct ts_filter *tsf, int *point) +{ + struct ts_filter_median *priv = ts_filter_to_filter_median(tsf); + + BUG_ON(!priv->ready); + + memcpy(point, &priv->last_issued[0], tsf->count_coords * sizeof(int)); + + priv->ready = 0; +} + +const struct ts_filter_api ts_filter_median_api = { + .create = ts_filter_median_create, + .destroy = ts_filter_median_destroy, + .clear = ts_filter_median_clear, + .process = ts_filter_median_process, + .scale = ts_filter_median_scale, + .haspoint = ts_filter_median_haspoint, + .getpoint = ts_filter_median_getpoint, +}; +EXPORT_SYMBOL_GPL(ts_filter_median_api); + diff --git a/drivers/mfd/pcf50633-adc.c b/drivers/mfd/pcf50633-adc.c index 3d31e97d6a4..6d2e8466df1 100644 --- a/drivers/mfd/pcf50633-adc.c +++ b/drivers/mfd/pcf50633-adc.c @@ -209,17 +209,16 @@ static void pcf50633_adc_irq(int irq, void *data) static int __devinit pcf50633_adc_probe(struct platform_device *pdev) { - struct pcf50633_subdev_pdata *pdata = pdev->dev.platform_data; struct pcf50633_adc *adc; adc = kzalloc(sizeof(*adc), GFP_KERNEL); if (!adc) return -ENOMEM; - adc->pcf = pdata->pcf; + adc->pcf = dev_to_pcf50633(pdev->dev.parent); platform_set_drvdata(pdev, adc); - pcf50633_register_irq(pdata->pcf, PCF50633_IRQ_ADCRDY, + pcf50633_register_irq(adc->pcf, PCF50633_IRQ_ADCRDY, pcf50633_adc_irq, adc); mutex_init(&adc->queue_mutex); diff --git a/drivers/mfd/pcf50633-core.c b/drivers/mfd/pcf50633-core.c index d26d7747175..41ac3a52df1 100644 --- a/drivers/mfd/pcf50633-core.c +++ b/drivers/mfd/pcf50633-core.c @@ -290,7 +290,7 @@ out: int pcf50633_irq_mask(struct pcf50633 *pcf, int irq) { - dev_info(pcf->dev, "Masking IRQ %d\n", irq); + dev_dbg(pcf->dev, "Masking IRQ %d\n", irq); return __pcf50633_irq_mask_set(pcf, irq, 1); } @@ -298,7 +298,7 @@ EXPORT_SYMBOL_GPL(pcf50633_irq_mask); int pcf50633_irq_unmask(struct pcf50633 *pcf, int irq) { - dev_info(pcf->dev, "Unmasking IRQ %d\n", irq); + dev_dbg(pcf->dev, "Unmasking IRQ %d\n", irq); return __pcf50633_irq_mask_set(pcf, irq, 0); } @@ -345,23 +345,26 @@ static void pcf50633_irq_worker(struct work_struct *work) goto out; } + /* defeat 8s death from lowsys on A5 */ + pcf50633_reg_write(pcf, PCF50633_REG_OOCSHDWN, 0x04); + /* We immediately read the usb and adapter status. We thus make sure * only of USBINS/USBREM IRQ handlers are called */ if (pcf_int[0] & (PCF50633_INT1_USBINS | PCF50633_INT1_USBREM)) { chgstat = pcf50633_reg_read(pcf, PCF50633_REG_MBCS2); if (chgstat & (0x3 << 4)) - pcf_int[0] &= ~(1 << PCF50633_INT1_USBREM); + pcf_int[0] &= ~PCF50633_INT1_USBREM; else - pcf_int[0] &= ~(1 << PCF50633_INT1_USBINS); + pcf_int[0] &= ~PCF50633_INT1_USBINS; } /* Make sure only one of ADPINS or ADPREM is set */ if (pcf_int[0] & (PCF50633_INT1_ADPINS | PCF50633_INT1_ADPREM)) { chgstat = pcf50633_reg_read(pcf, PCF50633_REG_MBCS2); if (chgstat & (0x3 << 4)) - pcf_int[0] &= ~(1 << PCF50633_INT1_ADPREM); + pcf_int[0] &= ~PCF50633_INT1_ADPREM; else - pcf_int[0] &= ~(1 << PCF50633_INT1_ADPINS); + pcf_int[0] &= ~PCF50633_INT1_ADPINS; } dev_dbg(pcf->dev, "INT1=0x%02x INT2=0x%02x INT3=0x%02x " @@ -453,7 +456,6 @@ static void pcf50633_client_dev_register(struct pcf50633 *pcf, const char *name, struct platform_device **pdev) { - struct pcf50633_subdev_pdata *subdev_pdata; int ret; *pdev = platform_device_alloc(name, -1); @@ -462,15 +464,6 @@ pcf50633_client_dev_register(struct pcf50633 *pcf, const char *name, return; } - subdev_pdata = kmalloc(sizeof(*subdev_pdata), GFP_KERNEL); - if (!subdev_pdata) { - dev_err(pcf->dev, "Error allocating subdev pdata\n"); - platform_device_put(*pdev); - } - - subdev_pdata->pcf = pcf; - platform_device_add_data(*pdev, subdev_pdata, sizeof(*subdev_pdata)); - (*pdev)->dev.parent = pcf->dev; ret = platform_device_add(*pdev); @@ -482,13 +475,13 @@ pcf50633_client_dev_register(struct pcf50633 *pcf, const char *name, } #ifdef CONFIG_PM -static int pcf50633_suspend(struct device *dev, pm_message_t state) +static int pcf50633_suspend(struct i2c_client *client, pm_message_t state) { struct pcf50633 *pcf; int ret = 0, i; u8 res[5]; - pcf = dev_get_drvdata(dev); + pcf = i2c_get_clientdata(client); /* Make sure our interrupt handlers are not called * henceforth */ @@ -523,12 +516,12 @@ out: return ret; } -static int pcf50633_resume(struct device *dev) +static int pcf50633_resume(struct i2c_client *client) { struct pcf50633 *pcf; int ret; - pcf = dev_get_drvdata(dev); + pcf = i2c_get_clientdata(client); /* Write the saved mask registers */ ret = pcf50633_write_block(pcf, PCF50633_REG_INT1M, @@ -560,9 +553,14 @@ static int __devinit pcf50633_probe(struct i2c_client *client, { struct pcf50633 *pcf; struct pcf50633_platform_data *pdata = client->dev.platform_data; - int i, ret = 0; + int i, ret; int version, variant; + if (!client->irq) { + dev_err(&client->dev, "Missing IRQ\n"); + return -ENOENT; + } + pcf = kzalloc(sizeof(*pcf), GFP_KERNEL); if (!pcf) return -ENOMEM; @@ -577,6 +575,11 @@ static int __devinit pcf50633_probe(struct i2c_client *client, pcf->irq = client->irq; pcf->work_queue = create_singlethread_workqueue("pcf50633"); + if (!pcf->work_queue) { + dev_err(&client->dev, "Failed to alloc workqueue\n"); + goto err_free; + } + INIT_WORK(&pcf->irq_work, pcf50633_irq_worker); version = pcf50633_reg_read(pcf, 0); @@ -584,7 +587,7 @@ static int __devinit pcf50633_probe(struct i2c_client *client, if (version < 0 || variant < 0) { dev_err(pcf->dev, "Unable to probe pcf50633\n"); ret = -ENODEV; - goto err; + goto err_destroy_workqueue; } dev_info(pcf->dev, "Probed device version %d variant %d\n", @@ -598,6 +601,15 @@ static int __devinit pcf50633_probe(struct i2c_client *client, pcf50633_reg_write(pcf, PCF50633_REG_INT4M, 0x00); pcf50633_reg_write(pcf, PCF50633_REG_INT5M, 0x00); + set_irq_handler(client->irq, handle_level_irq); + ret = request_irq(client->irq, pcf50633_irq, + IRQF_TRIGGER_LOW, "pcf50633", pcf); + + if (ret) { + dev_err(pcf->dev, "Failed to request IRQ %d\n", ret); + goto err_destroy_workqueue; + } + /* Create sub devices */ pcf50633_client_dev_register(pcf, "pcf50633-input", &pcf->input_pdev); @@ -607,53 +619,46 @@ static int __devinit pcf50633_probe(struct i2c_client *client, &pcf->mbc_pdev); pcf50633_client_dev_register(pcf, "pcf50633-adc", &pcf->adc_pdev); + pcf50633_client_dev_register(pcf, "pcf50633-backlight", + &pcf->bl_pdev); + for (i = 0; i < PCF50633_NUM_REGULATORS; i++) { struct platform_device *pdev; pdev = platform_device_alloc("pcf50633-regltr", i); if (!pdev) { - dev_err(pcf->dev, "Cannot create regulator\n"); + dev_err(pcf->dev, "Cannot create regulator %d\n", i); continue; } pdev->dev.parent = pcf->dev; - pdev->dev.platform_data = &pdata->reg_init_data[i]; - dev_set_drvdata(&pdev->dev, pcf); + platform_device_add_data(pdev, &pdata->reg_init_data[i], + sizeof(pdata->reg_init_data[i])); pcf->regulator_pdev[i] = pdev; platform_device_add(pdev); } - if (client->irq) { - ret = request_irq(client->irq, pcf50633_irq, - IRQF_TRIGGER_LOW, "pcf50633", pcf); - - if (ret) { - dev_err(pcf->dev, "Failed to request IRQ %d\n", ret); - goto err; - } - } else { - dev_err(pcf->dev, "No IRQ configured\n"); - goto err; - } - if (enable_irq_wake(client->irq) < 0) - dev_err(pcf->dev, "IRQ %u cannot be enabled as wake-up source" + dev_info(pcf->dev, "IRQ %u cannot be enabled as wake-up source" "in this hardware revision", client->irq); ret = sysfs_create_group(&client->dev.kobj, &pcf_attr_group); if (ret) - dev_err(pcf->dev, "error creating sysfs entries\n"); + dev_info(pcf->dev, "Failed to create sysfs entries\n"); if (pdata->probe_done) pdata->probe_done(pcf); return 0; -err: +err_destroy_workqueue: destroy_workqueue(pcf->work_queue); +err_free: + i2c_set_clientdata(client, NULL); kfree(pcf); + return ret; } @@ -686,12 +691,12 @@ static struct i2c_device_id pcf50633_id_table[] = { static struct i2c_driver pcf50633_driver = { .driver = { .name = "pcf50633", - .suspend = pcf50633_suspend, - .resume = pcf50633_resume, }, .id_table = pcf50633_id_table, .probe = pcf50633_probe, .remove = __devexit_p(pcf50633_remove), + .suspend = pcf50633_suspend, + .resume = pcf50633_resume, }; static int __init pcf50633_init(void) diff --git a/drivers/mtd/nand/s3c2410.c b/drivers/mtd/nand/s3c2410.c index 11dc7e69c4f..7dc854c3fd8 100644 --- a/drivers/mtd/nand/s3c2410.c +++ b/drivers/mtd/nand/s3c2410.c @@ -816,7 +816,7 @@ static void s3c2410_nand_init_chip(struct s3c2410_nand_info *info, nmtd->mtd.owner = THIS_MODULE; nmtd->set = set; - if (hardware_ecc) { + if (!(info->platform && info->platform->software_ecc) && hardware_ecc) { chip->ecc.calculate = s3c2410_nand_calculate_ecc; chip->ecc.correct = s3c2410_nand_correct_data; chip->ecc.mode = NAND_ECC_HW; diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index cea6cef27e8..9f6a8275e12 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -110,4 +110,16 @@ config CHARGER_PCF50633 help Say Y to include support for NXP PCF50633 Main Battery Charger. +config BATTERY_BQ27000_HDQ + tristate "BQ27000 HDQ battery monitor driver" + help + Say Y to enable support for the battery on the Neo Freerunner + +config HDQ_GPIO_BITBANG + bool "Generic gpio based HDQ bitbang" + help + Say Y to enable supoort for generic gpio based HDQ bitbang driver. + This can not be built as a module. + endif # POWER_SUPPLY + diff --git a/drivers/power/Makefile b/drivers/power/Makefile index b96f29d91c2..225abe32627 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -29,3 +29,6 @@ obj-$(CONFIG_BATTERY_BQ27x00) += bq27x00_battery.o obj-$(CONFIG_BATTERY_DA9030) += da9030_battery.o obj-$(CONFIG_BATTERY_MAX17040) += max17040_battery.o obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o +obj-$(CONFIG_BATTERY_BQ27000_HDQ) += bq27000_battery.o + +obj-$(CONFIG_HDQ_GPIO_BITBANG) += hdq.o diff --git a/drivers/power/bq27000_battery.c b/drivers/power/bq27000_battery.c new file mode 100644 index 00000000000..32655dce1f3 --- /dev/null +++ b/drivers/power/bq27000_battery.c @@ -0,0 +1,477 @@ +/* + * Driver for batteries with bq27000 chips inside via HDQ + * + * Copyright 2008 Openmoko, Inc + * Andy Green <andy@openmoko.com> + * + * based on ds2760 driver, original copyright notice for that ---> + * + * Copyright © 2007 Anton Vorontsov + * 2004-2007 Matt Reimer + * 2004 Szabolcs Gyurko + * + * Use consistent with the GNU GPL is permitted, + * provided that this copyright notice is + * preserved in its entirety in all copies and derived works. + * + * Author: Anton Vorontsov <cbou@mail.ru> + * February 2007 + * + * Matt Reimer <mreimer@vpop.net> + * April 2004, 2005, 2007 + * + * Szabolcs Gyurko <szabolcs.gyurko@tlt.hu> + * September 2004 + */ + +#include <linux/module.h> +#include <linux/param.h> +#include <linux/jiffies.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/workqueue.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/bq27000_battery.h> + +enum bq27000_regs { + /* RAM regs */ + /* read-write after this */ + BQ27000_CTRL = 0, /* Device Control Register */ + BQ27000_MODE, /* Device Mode Register */ + BQ27000_AR_L, /* At-Rate H L */ + BQ27000_AR_H, + /* read-only after this */ + BQ27000_ARTTE_L, /* At-Rate Time To Empty H L */ + BQ27000_ARTTE_H, + BQ27000_TEMP_L, /* Reported Temperature H L */ + BQ27000_TEMP_H, + BQ27000_VOLT_L, /* Reported Voltage H L */ + BQ27000_VOLT_H, + BQ27000_FLAGS, /* Status Flags */ + BQ27000_RSOC, /* Relative State of Charge */ + BQ27000_NAC_L, /* Nominal Available Capacity H L */ + BQ27000_NAC_H, + BQ27000_CACD_L, /* Discharge Compensated H L */ + BQ27000_CACD_H, + BQ27000_CACT_L, /* Temperature Compensated H L */ + BQ27000_CACT_H, + BQ27000_LMD_L, /* Last measured discharge H L */ + BQ27000_LMD_H, + BQ27000_AI_L, /* Average Current H L */ + BQ27000_AI_H, + BQ27000_TTE_L, /* Time to Empty H L */ + BQ27000_TTE_H, + BQ27000_TTF_L, /* Time to Full H L */ + BQ27000_TTF_H, + BQ27000_SI_L, /* Standby Current H L */ + BQ27000_SI_H, + BQ27000_STTE_L, /* Standby Time To Empty H L */ + BQ27000_STTE_H, + BQ27000_MLI_L, /* Max Load Current H L */ + BQ27000_MLI_H, + BQ27000_MLTTE_L, /* Max Load Time To Empty H L */ + BQ27000_MLTTE_H, + BQ27000_SAE_L, /* Available Energy H L */ + BQ27000_SAE_H, + BQ27000_AP_L, /* Available Power H L */ + BQ27000_AP_H, + BQ27000_TTECP_L, /* Time to Empty at Constant Power H L */ + BQ27000_TTECP_H, + BQ27000_CYCL_L, /* Cycle count since learning cycle H L */ + BQ27000_CYCL_H, + BQ27000_CYCT_L, /* Cycle Count Total H L */ + BQ27000_CYCT_H, + BQ27000_CSOC, /* Compensated State Of Charge */ + /* EEPROM regs */ + /* read-write after this */ + BQ27000_EE_EE_EN = 0x6e, /* EEPROM Program Enable */ + BQ27000_EE_ILMD = 0x76, /* Initial Last Measured Discharge High Byte */ + BQ27000_EE_SEDVF, /* Scaled EDVF Threshold */ + BQ27000_EE_SEDV1, /* Scaled EDV1 Threshold */ + BQ27000_EE_ISLC, /* Initial Standby Load Current */ + BQ27000_EE_DMFSD, /* Digital Magnitude Filter and Self Discharge */ + BQ27000_EE_TAPER, /* Aging Estimate Enable, Charge Termination Taper */ + BQ27000_EE_PKCFG, /* Pack Configuration Values */ + BQ27000_EE_IMLC, /* Initial Max Load Current or ID #3 */ + BQ27000_EE_DCOMP, /* Discharge rate compensation constants or ID #2 */ + BQ27000_EE_TCOMP, /* Temperature Compensation constants or ID #1 */ +}; + +enum bq27000_status_flags { + BQ27000_STATUS_CHGS = 0x80, /* 1 = being charged */ + BQ27000_STATUS_NOACT = 0x40, /* 1 = no activity */ + BQ27000_STATUS_IMIN = 0x20, /* 1 = Lion taper current mode */ + BQ27000_STATUS_CI = 0x10, /* 1 = capacity likely innacurate */ + BQ27000_STATUS_CALIP = 0x08, /* 1 = calibration in progress */ + BQ27000_STATUS_VDQ = 0x04, /* 1 = capacity should be accurate */ + BQ27000_STATUS_EDV1 = 0x02, /* 1 = end of discharge.. <6% left */ + BQ27000_STATUS_EDVF = 0x01, /* 1 = no, it's really empty now */ +}; + +#define NANOVOLTS_UNIT 3750 + +struct bq27000_bat_regs { + int ai; + int flags; + int lmd; + int rsoc; + int temp; + int tte; + int ttf; + int volt; +}; + +struct bq27000_device_info { + struct device *dev; + struct power_supply bat; + struct power_supply ac; + struct power_supply usb; + struct delayed_work work; + struct bq27000_platform_data *pdata; + + struct bq27000_bat_regs regs; +}; + +static unsigned int cache_time = 5000; +module_param(cache_time, uint, 0644); +MODULE_PARM_DESC(cache_time, "cache time in milliseconds"); + +/* + * reading 16 bit values over HDQ has a special hazard where the + * hdq device firmware can update the 16-bit register during the time we + * read the two halves. TI document SLUS556D recommends the algorithm here + * to avoid trouble + */ + +static int hdq_read16(struct bq27000_device_info *di, int address) +{ + int acc; + int high; + int retries = 3; + + while (retries--) { + + high = (di->pdata->hdq_read)(address + 1); /* high part */ + + if (high < 0) + return high; + acc = (di->pdata->hdq_read)(address); + if (acc < 0) + return acc; + + /* confirm high didn't change between reading it and low */ + if (high == (di->pdata->hdq_read)(address + 1)) + return (high << 8) | acc; + } + + return -ETIME; +} + +static void bq27000_battery_external_power_changed(struct power_supply *psy) +{ + struct bq27000_device_info *di = container_of(psy, struct bq27000_device_info, bat); + + dev_dbg(di->dev, "%s\n", __FUNCTION__); + schedule_delayed_work(&di->work, 0); +} + +static int bq27000_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int n; + struct bq27000_device_info *di = container_of(psy, struct bq27000_device_info, bat); + + if (di->regs.rsoc < 0 && psp != POWER_SUPPLY_PROP_PRESENT) + return -ENODEV; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + + if (!di->pdata->get_charger_online_status) + goto use_bat; + if ((di->pdata->get_charger_online_status)()) { + /* + * charger is definitively present + * we report our state in terms of what it says it + * is doing + */ + if (!di->pdata->get_charger_active_status) + goto use_bat; + + if ((di->pdata->get_charger_active_status)()) { + val->intval = POWER_SUPPLY_STATUS_CHARGING; + break; + } + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + } + + /* + * platform provided definite indication of charger presence, + * and it is telling us it isn't there... but we are on so we + * must be running from battery ---> + */ + + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + break; + +use_bat: + /* + * either the charger is not connected, or the + * platform doesn't give info about charger, use battery state + * but... battery state can be out of date by 4 seconds or + * so... use the platform callbacks if possible. + */ + + /* no real activity on the battery */ + if (di->regs.ai < 2) { + if (!di->regs.ttf) + val->intval = POWER_SUPPLY_STATUS_FULL; + else + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + } + /* power is actually going in or out... */ + if (di->regs.flags < 0) + return di->regs.flags; + if (di->regs.flags & BQ27000_STATUS_CHGS) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; + /* Do we have accurate readings... */ + if (di->regs.flags < 0) + return di->regs.flags; + if (di->regs.flags & BQ27000_STATUS_VDQ) + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + if (di->regs.volt < 0) + return di->regs.volt; + /* mV -> uV */ + val->intval = di->regs.volt * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + if (di->regs.flags < 0) + return di->regs.flags; + if (di->regs.flags & BQ27000_STATUS_CHGS) + n = -NANOVOLTS_UNIT; + else + n = NANOVOLTS_UNIT; + if (di->regs.ai < 0) + return di->regs.ai; + val->intval = (di->regs.ai * n) / di->pdata->rsense_mohms; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + if (di->regs.lmd < 0) + return di->regs.lmd; + val->intval = (di->regs.lmd * 3570) / di->pdata->rsense_mohms; + break; + case POWER_SUPPLY_PROP_TEMP: + if (di->regs.temp < 0) + return di->regs.temp; + /* K (in 0.25K units) is 273.15 up from C (in 0.1C)*/ + /* 10926 = 27315 * 4 / 10 */ + val->intval = (((long)di->regs.temp * 10l) - 10926) / 4; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = di->regs.rsoc; + if (val->intval < 0) + return val->intval; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = !(di->regs.rsoc < 0); + break; + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: + if (di->regs.tte < 0) + return di->regs.tte; + val->intval = 60 * di->regs.tte; + break; + case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW: + if (di->regs.ttf < 0) + return di->regs.ttf; + val->intval = 60 * di->regs.ttf; + break; + case POWER_SUPPLY_PROP_ONLINE: + if (di->pdata->get_charger_online_status) + val->intval = (di->pdata->get_charger_online_status)(); + else + return -EINVAL; + break; + default: + return -EINVAL; + } + + return 0; +} + +static void bq27000_battery_work(struct work_struct *work) +{ + struct bq27000_device_info *di = + container_of(work, struct bq27000_device_info, work.work); + + if ((di->pdata->hdq_initialized)()) { + struct bq27000_bat_regs regs; + + regs.ai = hdq_read16(di, BQ27000_AI_L); + regs.flags = (di->pdata->hdq_read)(BQ27000_FLAGS); + regs.lmd = hdq_read16(di, BQ27000_LMD_L); + regs.rsoc = (di->pdata->hdq_read)(BQ27000_RSOC); + regs.temp = hdq_read16(di, BQ27000_TEMP_L); + regs.tte = hdq_read16(di, BQ27000_TTE_L); + regs.ttf = hdq_read16(di, BQ27000_TTF_L); + regs.volt = hdq_read16(di, BQ27000_VOLT_L); + + if (memcmp (®s, &di->regs, sizeof(regs)) != 0) { + di->regs = regs; + power_supply_changed(&di->bat); + } + } + + if (!schedule_delayed_work(&di->work, cache_time)) + dev_err(di->dev, "battery service reschedule failed\n"); +} + +static enum power_supply_property bq27000_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, + POWER_SUPPLY_PROP_TIME_TO_FULL_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_ONLINE +}; + +static int bq27000_battery_probe(struct platform_device *pdev) +{ + int retval = 0; + struct bq27000_device_info *di; + struct bq27000_platform_data *pdata; + + dev_info(&pdev->dev, "BQ27000 Battery Driver (C) 2008 Openmoko, Inc\n"); + + di = kzalloc(sizeof(*di), GFP_KERNEL); + if (!di) { + retval = -ENOMEM; + goto di_alloc_failed; + } + + platform_set_drvdata(pdev, di); + + pdata = pdev->dev.platform_data; + di->dev = &pdev->dev; + /* di->w1_dev = pdev->dev.parent; */ + di->bat.name = pdata->name; + di->bat.type = POWER_SUPPLY_TYPE_BATTERY; + di->bat.properties = bq27000_battery_props; + di->bat.num_properties = ARRAY_SIZE(bq27000_battery_props); + di->bat.get_property = bq27000_battery_get_property; + di->bat.external_power_changed = + bq27000_battery_external_power_changed; + di->bat.use_for_apm = 1; + di->pdata = pdata; + + retval = power_supply_register(&pdev->dev, &di->bat); + if (retval) { + dev_err(di->dev, "failed to register battery\n"); + goto batt_failed; + } + + INIT_DELAYED_WORK(&di->work, bq27000_battery_work); + + if (!schedule_delayed_work(&di->work, 0)) + dev_err(di->dev, "failed to schedule bq27000_battery_work\n"); + + return 0; + +batt_failed: + kfree(di); +di_alloc_failed: + return retval; +} + +static int bq27000_battery_remove(struct platform_device *pdev) +{ + struct bq27000_device_info *di = platform_get_drvdata(pdev); + + cancel_delayed_work(&di->work); + + power_supply_unregister(&di->bat); + + return 0; +} + +void bq27000_charging_state_change(struct platform_device *pdev) +{ + struct bq27000_device_info *di = platform_get_drvdata(pdev); + + if (!di) + return; +} +EXPORT_SYMBOL_GPL(bq27000_charging_state_change); + +#ifdef CONFIG_PM + +static int bq27000_battery_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct bq27000_device_info *di = platform_get_drvdata(pdev); + + cancel_delayed_work(&di->work); + return 0; +} + +static int bq27000_battery_resume(struct platform_device *pdev) +{ + struct bq27000_device_info *di = platform_get_drvdata(pdev); + + schedule_delayed_work(&di->work, 0); + return 0; +} + +#else + +#define bq27000_battery_suspend NULL +#define bq27000_battery_resume NULL + +#endif /* CONFIG_PM */ + +static struct platform_driver bq27000_battery_driver = { + .driver = { + .name = "bq27000-battery", + }, + .probe = bq27000_battery_probe, + .remove = bq27000_battery_remove, + .suspend = bq27000_battery_suspend, + .resume = bq27000_battery_resume, +}; + +static int __init bq27000_battery_init(void) +{ + return platform_driver_register(&bq27000_battery_driver); +} + +static void __exit bq27000_battery_exit(void) +{ + platform_driver_unregister(&bq27000_battery_driver); +} + +module_init(bq27000_battery_init); +module_exit(bq27000_battery_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Andy Green <andy@openmoko.com>"); +MODULE_DESCRIPTION("bq27000 battery driver"); diff --git a/drivers/power/hdq.c b/drivers/power/hdq.c new file mode 100644 index 00000000000..f03fabfb1aa --- /dev/null +++ b/drivers/power/hdq.c @@ -0,0 +1,515 @@ +/* + * HDQ generic GPIO bitbang driver using FIQ + * + * (C) 2006-2007 by Openmoko, Inc. + * Author: Andy Green <andy@openmoko.com> + * All rights reserved. + * + * 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 <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/hdq.h> + +#define HDQ_READ 0 +#define HDQ_WRITE 0x80 + +enum hdq_bitbang_states { + HDQB_IDLE = 0, + HDQB_TX_BREAK, + HDQB_TX_BREAK_RECOVERY, + HDQB_ADS_CALC, + HDQB_ADS_LOW, + HDQB_ADS_HIGH, + HDQB_WAIT_RX, + HDQB_DATA_RX_LOW, + HDQB_DATA_RX_HIGH, + HDQB_WAIT_TX, +}; + +static struct hdq_priv { + u8 hdq_probed; /* nonzero after HDQ driver probed */ + struct mutex hdq_lock; /* if you want to use hdq, you have to take lock */ + unsigned long hdq_gpio_pin; /* GTA02 = GPD14 which pin to meddle with */ + u8 hdq_ads; /* b7..b6 = register address, b0 = r/w */ + u8 hdq_tx_data; /* data to tx for write action */ + u8 hdq_rx_data; /* data received in read action */ + u8 hdq_request_ctr; /* incremented by "user" to request a transfer */ + u8 hdq_transaction_ctr; /* incremented after each transfer */ + u8 hdq_error; /* 0 = no error */ + u8 hdq_ctr; + u8 hdq_ctr2; + u8 hdq_bit; + u8 hdq_shifter; + u8 hdq_tx_data_done; + enum hdq_bitbang_states hdq_state; + int reported_error; + + struct hdq_platform_data *pdata; +} hdq_priv; + + +static void hdq_bad(void) +{ + if (!hdq_priv.reported_error) + printk(KERN_ERR "HDQ error: %d\n", hdq_priv.hdq_error); + hdq_priv.reported_error = 1; +} + +static void hdq_good(void) +{ + if (hdq_priv.reported_error) + printk(KERN_INFO "HDQ responds again\n"); + hdq_priv.reported_error = 0; +} + +int hdq_fiq_handler(void) +{ + if (!hdq_priv.hdq_probed) + return 0; + + switch (hdq_priv.hdq_state) { + case HDQB_IDLE: + if (hdq_priv.hdq_request_ctr == hdq_priv.hdq_transaction_ctr) + break; + hdq_priv.hdq_ctr = 250 / HDQ_SAMPLE_PERIOD_US; + hdq_priv.pdata->gpio_set(0); + hdq_priv.pdata->gpio_dir_out(); + hdq_priv.hdq_tx_data_done = 0; + hdq_priv.hdq_state = HDQB_TX_BREAK; + break; + + case HDQB_TX_BREAK: /* issue low for > 190us */ + if (--hdq_priv.hdq_ctr == 0) { + hdq_priv.hdq_ctr = 60 / HDQ_SAMPLE_PERIOD_US; + hdq_priv.hdq_state = HDQB_TX_BREAK_RECOVERY; + hdq_priv.pdata->gpio_set(1); + } + break; + + case HDQB_TX_BREAK_RECOVERY: /* issue low for > 40us */ + if (--hdq_priv.hdq_ctr) + break; + hdq_priv.hdq_shifter = hdq_priv.hdq_ads; + hdq_priv.hdq_bit = 8; /* 8 bits of ads / rw */ + hdq_priv.hdq_tx_data_done = 0; /* doing ads */ + /* fallthru on last one */ + case HDQB_ADS_CALC: + if (hdq_priv.hdq_shifter & 1) + hdq_priv.hdq_ctr = 50 / HDQ_SAMPLE_PERIOD_US; + else + hdq_priv.hdq_ctr = 120 / HDQ_SAMPLE_PERIOD_US; + /* carefully precompute the other phase length */ + hdq_priv.hdq_ctr2 = (210 - (hdq_priv.hdq_ctr * HDQ_SAMPLE_PERIOD_US)) / + HDQ_SAMPLE_PERIOD_US; + hdq_priv.hdq_state = HDQB_ADS_LOW; + hdq_priv.hdq_shifter >>= 1; + hdq_priv.hdq_bit--; + hdq_priv.pdata->gpio_set(0); + break; + + case HDQB_ADS_LOW: + if (--hdq_priv.hdq_ctr) + break; + hdq_priv.pdata->gpio_set(1); + hdq_priv.hdq_state = HDQB_ADS_HIGH; + break; + + case HDQB_ADS_HIGH: + if (--hdq_priv.hdq_ctr2 > 1) /* account for HDQB_ADS_CALC */ + break; + if (hdq_priv.hdq_bit) { /* more bits to do */ + hdq_priv.hdq_state = HDQB_ADS_CALC; + break; + } + /* no more bits, wait it out until hdq_priv.hdq_ctr2 exhausted */ + if (hdq_priv.hdq_ctr2) + break; + /* ok no more bits and very last state */ + hdq_priv.hdq_ctr = 60 / HDQ_SAMPLE_PERIOD_US; + /* FIXME 0 = read */ + if (hdq_priv.hdq_ads & 0x80) { /* write the byte out */ + /* set delay before payload */ + hdq_priv.hdq_ctr = 300 / HDQ_SAMPLE_PERIOD_US; + /* already high, no need to write */ + hdq_priv.hdq_state = HDQB_WAIT_TX; + break; + } + /* read the next byte */ + hdq_priv.hdq_bit = 8; /* 8 bits of data */ + hdq_priv.hdq_ctr = 2500 / HDQ_SAMPLE_PERIOD_US; + hdq_priv.hdq_state = HDQB_WAIT_RX; + hdq_priv.pdata->gpio_dir_in(); + break; + + case HDQB_WAIT_TX: /* issue low for > 40us */ + if (--hdq_priv.hdq_ctr) + break; + if (!hdq_priv.hdq_tx_data_done) { /* was that the data sent? */ + hdq_priv.hdq_tx_data_done++; + hdq_priv.hdq_shifter = hdq_priv.hdq_tx_data; + hdq_priv.hdq_bit = 8; /* 8 bits of data */ + hdq_priv.hdq_state = HDQB_ADS_CALC; /* start sending */ + break; + } + hdq_priv.hdq_error = 0; + hdq_priv.hdq_transaction_ctr = hdq_priv.hdq_request_ctr; + hdq_priv.hdq_state = HDQB_IDLE; /* all tx is done */ + /* idle in input mode, it's pulled up by 10K */ + hdq_priv.pdata->gpio_dir_in(); + break; + + case HDQB_WAIT_RX: /* wait for battery to talk to us */ + if (hdq_priv.pdata->gpio_get() == 0) { + /* it talks to us! */ + hdq_priv.hdq_ctr2 = 1; + hdq_priv.hdq_bit = 8; /* 8 bits of data */ + /* timeout */ + hdq_priv.hdq_ctr = 500 / HDQ_SAMPLE_PERIOD_US; + hdq_priv.hdq_state = HDQB_DATA_RX_LOW; + break; + } + if (--hdq_priv.hdq_ctr == 0) { /* timed out, error */ + hdq_priv.hdq_error = 1; + hdq_priv.hdq_transaction_ctr = hdq_priv.hdq_request_ctr; + hdq_priv.hdq_state = HDQB_IDLE; /* abort */ + } + break; + + /* + * HDQ basically works by measuring the low time of the bit cell + * 32-50us --> '1', 80 - 145us --> '0' + */ + + case HDQB_DATA_RX_LOW: + if (hdq_priv.pdata->gpio_get()) { + hdq_priv.hdq_rx_data >>= 1; + if (hdq_priv.hdq_ctr2 <= (65 / HDQ_SAMPLE_PERIOD_US)) + hdq_priv.hdq_rx_data |= 0x80; + + if (--hdq_priv.hdq_bit == 0) { + hdq_priv.hdq_error = 0; + hdq_priv.hdq_transaction_ctr = + hdq_priv.hdq_request_ctr; + + hdq_priv.hdq_state = HDQB_IDLE; + } else + hdq_priv.hdq_state = HDQB_DATA_RX_HIGH; + /* timeout */ + hdq_priv.hdq_ctr = 1000 / HDQ_SAMPLE_PERIOD_US; + hdq_priv.hdq_ctr2 = 1; + break; + } + hdq_priv.hdq_ctr2++; + if (--hdq_priv.hdq_ctr) + break; + /* timed out, error */ + hdq_priv.hdq_error = 2; + hdq_priv.hdq_transaction_ctr = hdq_priv.hdq_request_ctr; + hdq_priv.hdq_state = HDQB_IDLE; /* abort */ + break; + + case HDQB_DATA_RX_HIGH: + if (!hdq_priv.pdata->gpio_get()) { + /* it talks to us! */ + hdq_priv.hdq_ctr2 = 1; + /* timeout */ + hdq_priv.hdq_ctr = 400 / HDQ_SAMPLE_PERIOD_US; + hdq_priv.hdq_state = HDQB_DATA_RX_LOW; + break; + } + if (--hdq_priv.hdq_ctr) + break; + /* timed out, error */ + hdq_priv.hdq_error = 3; + hdq_priv.hdq_transaction_ctr = hdq_priv.hdq_request_ctr; + + /* we're in input mode already */ + hdq_priv.hdq_state = HDQB_IDLE; /* abort */ + break; + } + + /* Are we interested in keeping the FIQ source alive ? */ + if (hdq_priv.hdq_state != HDQB_IDLE) + return 1; + else + return 0; +} +static int fiq_busy(void) +{ + int request = (volatile u8)hdq_priv.hdq_request_ctr; + int transact = (volatile u8)hdq_priv.hdq_transaction_ctr; + + + return (request != transact); +} + +int hdq_initialized(void) +{ + return hdq_priv.hdq_probed; +} +EXPORT_SYMBOL_GPL(hdq_initialized); + +int hdq_read(int address) +{ + int count_sleeps = 5; + int ret = -ETIME; + + if (!hdq_priv.hdq_probed) + return -EINVAL; + + mutex_lock(&hdq_priv.hdq_lock); + + hdq_priv.hdq_error = 0; + hdq_priv.hdq_ads = address | HDQ_READ; + hdq_priv.hdq_request_ctr++; + hdq_priv.pdata->kick_fiq(); + /* + * FIQ takes care of it while we block our calling process + * But we're not spinning -- other processes run normally while + * we wait for the result + */ + while (count_sleeps--) { + msleep(10); /* valid transaction always completes in < 10ms */ + + if (fiq_busy()) + continue; + + if (hdq_priv.hdq_error) { + hdq_bad(); + goto done; /* didn't see a response in good time */ + } + hdq_good(); + + ret = hdq_priv.hdq_rx_data; + goto done; + } + +done: + mutex_unlock(&hdq_priv.hdq_lock); + return ret; +} +EXPORT_SYMBOL_GPL(hdq_read); + +int hdq_write(int address, u8 data) +{ + int count_sleeps = 5; + int ret = -ETIME; + + if (!hdq_priv.hdq_probed) + return -EINVAL; + + mutex_lock(&hdq_priv.hdq_lock); + + hdq_priv.hdq_error = 0; + hdq_priv.hdq_ads = address | HDQ_WRITE; + hdq_priv.hdq_tx_data = data; + hdq_priv.hdq_request_ctr++; + hdq_priv.pdata->kick_fiq(); + /* + * FIQ takes care of it while we block our calling process + * But we're not spinning -- other processes run normally while + * we wait for the result + */ + while (count_sleeps--) { + msleep(10); /* valid transaction always completes in < 10ms */ + + if (fiq_busy()) + continue; /* something bad with FIQ */ + + if (hdq_priv.hdq_error) { + hdq_bad(); + goto done; /* didn't see a response in good time */ + } + hdq_good(); + + ret = 0; + goto done; + } + +done: + mutex_unlock(&hdq_priv.hdq_lock); + return ret; +} +EXPORT_SYMBOL_GPL(hdq_write); + +/* sysfs */ + +static ssize_t hdq_sysfs_dump(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int n; + int v; + u8 u8a[128]; /* whole address space for HDQ */ + char *end = buf; + + if (!hdq_priv.hdq_probed) + return -EINVAL; + + /* the dump does not take care about 16 bit regs, because at this + * bus level we don't know about the chip details + */ + for (n = 0; n < sizeof(u8a); n++) { + v = hdq_read(n); + if (v < 0) + goto bail; + u8a[n] = v; + } + + for (n = 0; n < sizeof(u8a); n += 16) { + hex_dump_to_buffer(u8a + n, sizeof(u8a), 16, 1, end, 4096, 0); + end += strlen(end); + *end++ = '\n'; + *end = '\0'; + } + return (end - buf); + +bail: + return sprintf(buf, "ERROR %d\n", v); +} + +/* you write by <address> <data>, eg, "34 128" */ + +#define atoi(str) simple_strtoul(((str != NULL) ? str : ""), NULL, 0) + +static ssize_t hdq_sysfs_write(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + const char *end = buf + count; + int address = atoi(buf); + + if (!hdq_priv.hdq_probed) + return -EINVAL; + + while ((buf != end) && (*buf != ' ')) + buf++; + if (buf >= end) + return 0; + while ((buf < end) && (*buf == ' ')) + buf++; + if (buf >= end) + return 0; + + hdq_write(address, atoi(buf)); + + return count; +} + +static DEVICE_ATTR(dump, 0400, hdq_sysfs_dump, NULL); +static DEVICE_ATTR(write, 0600, NULL, hdq_sysfs_write); + +static struct attribute *hdq_sysfs_entries[] = { + &dev_attr_dump.attr, + &dev_attr_write.attr, + NULL +}; + +static struct attribute_group hdq_attr_group = { + .name = "hdq", + .attrs = hdq_sysfs_entries, +}; + + +#ifdef CONFIG_PM +static int hdq_suspend(struct platform_device *pdev, pm_message_t state) +{ + /* after 18s of this, the battery monitor will also go to sleep */ + hdq_priv.pdata->gpio_dir_in(); + hdq_priv.pdata->disable_fiq(); + return 0; +} + +static int hdq_resume(struct platform_device *pdev) +{ + hdq_priv.pdata->gpio_set(1); + hdq_priv.pdata->gpio_dir_out(); + hdq_priv.pdata->enable_fiq(); + return 0; +} +#endif + +static int __init hdq_probe(struct platform_device *pdev) +{ + struct resource *r = platform_get_resource(pdev, 0, 0); + int ret; + struct hdq_platform_data *pdata = pdev->dev.platform_data; + + if (!r || !pdata) + return -EINVAL; + + platform_set_drvdata(pdev, NULL); + + mutex_init(&hdq_priv.hdq_lock); + + /* set our HDQ comms pin from the platform data */ + hdq_priv.hdq_gpio_pin = r->start; + hdq_priv.pdata = pdata; + + hdq_priv.pdata->gpio_set(1); + hdq_priv.pdata->gpio_dir_out(); + + /* Initialize FIQ */ + if (hdq_priv.pdata->enable_fiq() < 0) { + dev_err(&pdev->dev, "Could not enable FIQ source\n"); + return -EINVAL; + } + + ret = sysfs_create_group(&pdev->dev.kobj, &hdq_attr_group); + if (ret) + return ret; + + hdq_priv.hdq_probed = 1; /* we are ready to do stuff now */ + + /* + * if wanted, users can defer registration of devices + * that depend on HDQ until after we register, and can use our + * device as parent so suspend-resume ordering is correct + */ + if (pdata->attach_child_devices) + (pdata->attach_child_devices)(&pdev->dev); + + hdq_priv.pdata = pdata; + + return 0; +} + +static int hdq_remove(struct platform_device *pdev) +{ + sysfs_remove_group(&pdev->dev.kobj, &hdq_attr_group); + return 0; +} + +static struct platform_driver hdq_driver = { + .probe = hdq_probe, + .remove = hdq_remove, +#ifdef CONFIG_PM + .suspend = hdq_suspend, + .resume = hdq_resume, +#endif + .driver = { + .name = "hdq", + }, +}; + +static int __init hdq_init(void) +{ + return platform_driver_register(&hdq_driver); +} + +static void __exit hdq_exit(void) +{ + platform_driver_unregister(&hdq_driver); +} + +module_init(hdq_init); +module_exit(hdq_exit); + +MODULE_AUTHOR("Andy Green <andy@openmoko.com>"); +MODULE_DESCRIPTION("HDQ driver"); diff --git a/drivers/power/pcf50633-charger.c b/drivers/power/pcf50633-charger.c index e8b278f7178..8424c40672a 100644 --- a/drivers/power/pcf50633-charger.c +++ b/drivers/power/pcf50633-charger.c @@ -29,15 +29,12 @@ struct pcf50633_mbc { struct pcf50633 *pcf; - int adapter_active; int adapter_online; - int usb_active; int usb_online; struct power_supply usb; struct power_supply adapter; - - struct delayed_work charging_restart_work; + struct power_supply ac; }; int pcf50633_mbc_usb_curlim_set(struct pcf50633 *pcf, int ma) @@ -47,16 +44,21 @@ int pcf50633_mbc_usb_curlim_set(struct pcf50633 *pcf, int ma) u8 bits; int charging_start = 1; u8 mbcs2, chgmod; + unsigned int mbcc5; - if (ma >= 1000) + if (ma >= 1000) { bits = PCF50633_MBCC7_USB_1000mA; - else if (ma >= 500) + ma = 1000; + } else if (ma >= 500) { bits = PCF50633_MBCC7_USB_500mA; - else if (ma >= 100) + ma = 500; + } else if (ma >= 100) { bits = PCF50633_MBCC7_USB_100mA; - else { + ma = 100; + } else { bits = PCF50633_MBCC7_USB_SUSPEND; charging_start = 0; + ma = 0; } ret = pcf50633_reg_set_bit_mask(pcf, PCF50633_REG_MBCC7, @@ -66,21 +68,40 @@ int pcf50633_mbc_usb_curlim_set(struct pcf50633 *pcf, int ma) else dev_info(pcf->dev, "usb curlim to %d mA\n", ma); - /* Manual charging start */ - mbcs2 = pcf50633_reg_read(pcf, PCF50633_REG_MBCS2); + /* + * We limit the charging current to be the USB current limit. + * The reason is that on pcf50633, when it enters PMU Standby mode, + * which it does when the device goes "off", the USB current limit + * reverts to the variant default. In at least one common case, that + * default is 500mA. By setting the charging current to be the same + * as the USB limit we set here before PMU standby, we enforce it only + * using the correct amount of current even when the USB current limit + * gets reset to the wrong thing + */ + + if (mbc->pcf->pdata->chg_ref_current_ma) { + mbcc5 = (ma << 8) / mbc->pcf->pdata->chg_ref_current_ma; + if (mbcc5 > 255) + mbcc5 = 255; + pcf50633_reg_write(mbc->pcf, PCF50633_REG_MBCC5, mbcc5); + } + + mbcs2 = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCS2); chgmod = (mbcs2 & PCF50633_MBCS2_MBC_MASK); /* If chgmod == BATFULL, setting chgena has no effect. - * We need to set resume instead. + * Datasheet says we need to set resume instead but when autoresume is + * used resume doesn't work. Clear and set chgena instead. */ if (chgmod != PCF50633_MBCS2_MBC_BAT_FULL) pcf50633_reg_set_bit_mask(pcf, PCF50633_REG_MBCC1, PCF50633_MBCC1_CHGENA, PCF50633_MBCC1_CHGENA); - else + else { + pcf50633_reg_clear_bits(pcf, PCF50633_REG_MBCC1, + PCF50633_MBCC1_CHGENA); pcf50633_reg_set_bit_mask(pcf, PCF50633_REG_MBCC1, - PCF50633_MBCC1_RESUME, PCF50633_MBCC1_RESUME); - - mbc->usb_active = charging_start; + PCF50633_MBCC1_CHGENA, PCF50633_MBCC1_CHGENA); + } power_supply_changed(&mbc->usb); @@ -92,20 +113,44 @@ int pcf50633_mbc_get_status(struct pcf50633 *pcf) { struct pcf50633_mbc *mbc = platform_get_drvdata(pcf->mbc_pdev); int status = 0; + u8 chgmod; + + if (!mbc) + return 0; + + chgmod = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCS2) + & PCF50633_MBCS2_MBC_MASK; if (mbc->usb_online) status |= PCF50633_MBC_USB_ONLINE; - if (mbc->usb_active) + if (chgmod == PCF50633_MBCS2_MBC_USB_PRE || + chgmod == PCF50633_MBCS2_MBC_USB_PRE_WAIT || + chgmod == PCF50633_MBCS2_MBC_USB_FAST || + chgmod == PCF50633_MBCS2_MBC_USB_FAST_WAIT) status |= PCF50633_MBC_USB_ACTIVE; if (mbc->adapter_online) status |= PCF50633_MBC_ADAPTER_ONLINE; - if (mbc->adapter_active) + if (chgmod == PCF50633_MBCS2_MBC_ADP_PRE || + chgmod == PCF50633_MBCS2_MBC_ADP_PRE_WAIT || + chgmod == PCF50633_MBCS2_MBC_ADP_FAST || + chgmod == PCF50633_MBCS2_MBC_ADP_FAST_WAIT) status |= PCF50633_MBC_ADAPTER_ACTIVE; return status; } EXPORT_SYMBOL_GPL(pcf50633_mbc_get_status); +int pcf50633_mbc_get_usb_online_status(struct pcf50633 *pcf) +{ + struct pcf50633_mbc *mbc = platform_get_drvdata(pcf->mbc_pdev); + + if (!mbc) + return 0; + + return mbc->usb_online; +} +EXPORT_SYMBOL_GPL(pcf50633_mbc_get_usb_online_status); + static ssize_t show_chgmode(struct device *dev, struct device_attribute *attr, char *buf) { @@ -156,9 +201,50 @@ static ssize_t set_usblim(struct device *dev, static DEVICE_ATTR(usb_curlim, S_IRUGO | S_IWUSR, show_usblim, set_usblim); +static ssize_t +show_chglim(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct pcf50633_mbc *mbc = dev_get_drvdata(dev); + u8 mbcc5 = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCC5); + unsigned int ma; + + if (!mbc->pcf->pdata->chg_ref_current_ma) + return -ENODEV; + + ma = (mbc->pcf->pdata->chg_ref_current_ma * mbcc5) >> 8; + + return sprintf(buf, "%u\n", ma); +} + +static ssize_t set_chglim(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct pcf50633_mbc *mbc = dev_get_drvdata(dev); + unsigned long ma; + unsigned int mbcc5; + int ret; + + if (!mbc->pcf->pdata->chg_ref_current_ma) + return -ENODEV; + + ret = strict_strtoul(buf, 10, &ma); + if (ret) + return -EINVAL; + + mbcc5 = (ma << 8) / mbc->pcf->pdata->chg_ref_current_ma; + if (mbcc5 > 255) + mbcc5 = 255; + pcf50633_reg_write(mbc->pcf, PCF50633_REG_MBCC5, mbcc5); + + return count; +} + +static DEVICE_ATTR(chg_curlim, S_IRUGO | S_IWUSR, show_chglim, set_chglim); + static struct attribute *pcf50633_mbc_sysfs_entries[] = { &dev_attr_chgmode.attr, &dev_attr_usb_curlim.attr, + &dev_attr_chg_curlim.attr, NULL, }; @@ -167,76 +253,26 @@ static struct attribute_group mbc_attr_group = { .attrs = pcf50633_mbc_sysfs_entries, }; -/* MBC state machine switches into charging mode when the battery voltage - * falls below 96% of a battery float voltage. But the voltage drop in Li-ion - * batteries is marginal(1~2 %) till about 80% of its capacity - which means, - * after a BATFULL, charging won't be restarted until 80%. - * - * This work_struct function restarts charging at regular intervals to make - * sure we don't discharge too much - */ - -static void pcf50633_mbc_charging_restart(struct work_struct *work) -{ - struct pcf50633_mbc *mbc; - u8 mbcs2, chgmod; - - mbc = container_of(work, struct pcf50633_mbc, - charging_restart_work.work); - - mbcs2 = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCS2); - chgmod = (mbcs2 & PCF50633_MBCS2_MBC_MASK); - - if (chgmod != PCF50633_MBCS2_MBC_BAT_FULL) - return; - - /* Restart charging */ - pcf50633_reg_set_bit_mask(mbc->pcf, PCF50633_REG_MBCC1, - PCF50633_MBCC1_RESUME, PCF50633_MBCC1_RESUME); - mbc->usb_active = 1; - power_supply_changed(&mbc->usb); - - dev_info(mbc->pcf->dev, "Charging restarted\n"); -} - static void pcf50633_mbc_irq_handler(int irq, void *data) { struct pcf50633_mbc *mbc = data; - int chg_restart_interval = - mbc->pcf->pdata->charging_restart_interval; /* USB */ if (irq == PCF50633_IRQ_USBINS) { mbc->usb_online = 1; } else if (irq == PCF50633_IRQ_USBREM) { mbc->usb_online = 0; - mbc->usb_active = 0; pcf50633_mbc_usb_curlim_set(mbc->pcf, 0); - cancel_delayed_work_sync(&mbc->charging_restart_work); } /* Adapter */ - if (irq == PCF50633_IRQ_ADPINS) { + if (irq == PCF50633_IRQ_ADPINS) mbc->adapter_online = 1; - mbc->adapter_active = 1; - } else if (irq == PCF50633_IRQ_ADPREM) { + else if (irq == PCF50633_IRQ_ADPREM) mbc->adapter_online = 0; - mbc->adapter_active = 0; - } - - if (irq == PCF50633_IRQ_BATFULL) { - mbc->usb_active = 0; - mbc->adapter_active = 0; - - if (chg_restart_interval > 0) - schedule_delayed_work(&mbc->charging_restart_work, - chg_restart_interval); - } else if (irq == PCF50633_IRQ_USBLIMON) - mbc->usb_active = 0; - else if (irq == PCF50633_IRQ_USBLIMOFF) - mbc->usb_active = 1; + power_supply_changed(&mbc->ac); power_supply_changed(&mbc->usb); power_supply_changed(&mbc->adapter); @@ -269,10 +305,34 @@ static int usb_get_property(struct power_supply *psy, { struct pcf50633_mbc *mbc = container_of(psy, struct pcf50633_mbc, usb); int ret = 0; + u8 usblim = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCC7) & + PCF50633_MBCC7_USB_MASK; switch (psp) { case POWER_SUPPLY_PROP_ONLINE: - val->intval = mbc->usb_online; + val->intval = mbc->usb_online && + (usblim <= PCF50633_MBCC7_USB_500mA); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int ac_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct pcf50633_mbc *mbc = container_of(psy, struct pcf50633_mbc, ac); + int ret = 0; + u8 usblim = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCC7) & + PCF50633_MBCC7_USB_MASK; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = mbc->usb_online && + (usblim == PCF50633_MBCC7_USB_1000mA); break; default: ret = -EINVAL; @@ -303,7 +363,6 @@ static const u8 mbc_irq_handlers[] = { static int __devinit pcf50633_mbc_probe(struct platform_device *pdev) { struct pcf50633_mbc *mbc; - struct pcf50633_subdev_pdata *pdata = pdev->dev.platform_data; int ret; int i; u8 mbcs1; @@ -313,7 +372,7 @@ static int __devinit pcf50633_mbc_probe(struct platform_device *pdev) return -ENOMEM; platform_set_drvdata(pdev, mbc); - mbc->pcf = pdata->pcf; + mbc->pcf = dev_to_pcf50633(pdev->dev.parent); /* Set up IRQ handlers */ for (i = 0; i < ARRAY_SIZE(mbc_irq_handlers); i++) @@ -337,6 +396,14 @@ static int __devinit pcf50633_mbc_probe(struct platform_device *pdev) mbc->usb.supplied_to = mbc->pcf->pdata->batteries; mbc->usb.num_supplicants = mbc->pcf->pdata->num_batteries; + mbc->ac.name = "ac"; + mbc->ac.type = POWER_SUPPLY_TYPE_MAINS; + mbc->ac.properties = power_props; + mbc->ac.num_properties = ARRAY_SIZE(power_props); + mbc->ac.get_property = ac_get_property; + mbc->ac.supplied_to = mbc->pcf->pdata->batteries; + mbc->ac.num_supplicants = mbc->pcf->pdata->num_batteries; + ret = power_supply_register(&pdev->dev, &mbc->adapter); if (ret) { dev_err(mbc->pcf->dev, "failed to register adapter\n"); @@ -352,8 +419,14 @@ static int __devinit pcf50633_mbc_probe(struct platform_device *pdev) return ret; } - INIT_DELAYED_WORK(&mbc->charging_restart_work, - pcf50633_mbc_charging_restart); + ret = power_supply_register(&pdev->dev, &mbc->ac); + if (ret) { + dev_err(mbc->pcf->dev, "failed to register ac\n"); + power_supply_unregister(&mbc->adapter); + power_supply_unregister(&mbc->usb); + kfree(mbc); + return ret; + } ret = sysfs_create_group(&pdev->dev.kobj, &mbc_attr_group); if (ret) @@ -379,8 +452,7 @@ static int __devexit pcf50633_mbc_remove(struct platform_device *pdev) power_supply_unregister(&mbc->usb); power_supply_unregister(&mbc->adapter); - - cancel_delayed_work_sync(&mbc->charging_restart_work); + power_supply_unregister(&mbc->ac); kfree(mbc); diff --git a/drivers/regulator/pcf50633-regulator.c b/drivers/regulator/pcf50633-regulator.c index 0803ffe6236..c8f41dc05b7 100644 --- a/drivers/regulator/pcf50633-regulator.c +++ b/drivers/regulator/pcf50633-regulator.c @@ -314,13 +314,15 @@ static int __devinit pcf50633_regulator_probe(struct platform_device *pdev) struct pcf50633 *pcf; /* Already set by core driver */ - pcf = platform_get_drvdata(pdev); + pcf = dev_to_pcf50633(pdev->dev.parent); rdev = regulator_register(®ulators[pdev->id], &pdev->dev, pdev->dev.platform_data, pcf); if (IS_ERR(rdev)) return PTR_ERR(rdev); + platform_set_drvdata(pdev, rdev); + if (pcf->pdata->regulator_registered) pcf->pdata->regulator_registered(pcf, pdev->id); @@ -331,6 +333,7 @@ static int __devexit pcf50633_regulator_remove(struct platform_device *pdev) { struct regulator_dev *rdev = platform_get_drvdata(pdev); + platform_set_drvdata(pdev, NULL); regulator_unregister(rdev); return 0; diff --git a/drivers/rtc/rtc-pcf50633.c b/drivers/rtc/rtc-pcf50633.c index 4c5d5d0c4cf..854c3cb365a 100644 --- a/drivers/rtc/rtc-pcf50633.c +++ b/drivers/rtc/rtc-pcf50633.c @@ -58,6 +58,7 @@ struct pcf50633_time { struct pcf50633_rtc { int alarm_enabled; int second_enabled; + int alarm_pending; struct pcf50633 *pcf; struct rtc_device *rtc_dev; @@ -209,6 +210,7 @@ static int pcf50633_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) rtc = dev_get_drvdata(dev); alrm->enabled = rtc->alarm_enabled; + alrm->pending = rtc->alarm_pending; ret = pcf50633_read_block(rtc->pcf, PCF50633_REG_RTCSCA, PCF50633_TI_EXTENT, &pcf_tm.time[0]); @@ -244,6 +246,8 @@ static int pcf50633_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) /* Returns 0 on success */ ret = pcf50633_write_block(rtc->pcf, PCF50633_REG_RTCSCA, PCF50633_TI_EXTENT, &pcf_tm.time[0]); + if (!alrm->enabled) + rtc->alarm_pending = 0; if (!alarm_masked || alrm->enabled) pcf50633_irq_unmask(rtc->pcf, PCF50633_IRQ_ALARM); @@ -268,6 +272,7 @@ static void pcf50633_rtc_irq(int irq, void *data) switch (irq) { case PCF50633_IRQ_ALARM: rtc_update_irq(rtc->rtc_dev, 1, RTC_AF | RTC_IRQF); + rtc->alarm_pending = 1; break; case PCF50633_IRQ_SECOND: rtc_update_irq(rtc->rtc_dev, 1, RTC_UF | RTC_IRQF); @@ -277,16 +282,13 @@ static void pcf50633_rtc_irq(int irq, void *data) static int __devinit pcf50633_rtc_probe(struct platform_device *pdev) { - struct pcf50633_subdev_pdata *pdata; struct pcf50633_rtc *rtc; - rtc = kzalloc(sizeof(*rtc), GFP_KERNEL); if (!rtc) return -ENOMEM; - pdata = pdev->dev.platform_data; - rtc->pcf = pdata->pcf; + rtc->pcf = dev_to_pcf50633(pdev->dev.parent); platform_set_drvdata(pdev, rtc); rtc->rtc_dev = rtc_device_register("pcf50633-rtc", &pdev->dev, &pcf50633_rtc_ops, THIS_MODULE); diff --git a/drivers/serial/samsung.c b/drivers/serial/samsung.c index 1523e8d9ae7..e0062b5626c 100644 --- a/drivers/serial/samsung.c +++ b/drivers/serial/samsung.c @@ -1263,6 +1263,13 @@ module_exit(s3c24xx_serial_modexit); #ifdef CONFIG_SERIAL_SAMSUNG_CONSOLE static struct uart_port *cons_uart; +static int cons_silenced; + +void s3c24xx_serial_console_set_silence(int silenced) +{ + cons_silenced = silenced; +} +EXPORT_SYMBOL(s3c24xx_serial_console_set_silence); static int s3c24xx_serial_console_txrdy(struct uart_port *port, unsigned int ufcon) @@ -1287,9 +1294,21 @@ static void s3c24xx_serial_console_putchar(struct uart_port *port, int ch) { unsigned int ufcon = rd_regl(cons_uart, S3C2410_UFCON); + unsigned int umcon = rd_regl(cons_uart, S3C2410_UMCON); + + if (cons_silenced) + return; + + /* If auto HW flow control enabled, temporarily turn it off */ + if (umcon & S3C2410_UMCOM_AFC) + wr_regl(port, S3C2410_UMCON, (umcon & !S3C2410_UMCOM_AFC)); + while (!s3c24xx_serial_console_txrdy(port, ufcon)) barrier(); wr_regb(cons_uart, S3C2410_UTXH, ch); + + if (umcon & S3C2410_UMCOM_AFC) + wr_regl(port, S3C2410_UMCON, umcon); } static void diff --git a/drivers/usb/gadget/s3c2410_udc.c b/drivers/usb/gadget/s3c2410_udc.c index d5f4c1d45c9..e6b76d812c3 100644 --- a/drivers/usb/gadget/s3c2410_udc.c +++ b/drivers/usb/gadget/s3c2410_udc.c @@ -842,6 +842,7 @@ static void s3c2410_udc_handle_ep(struct s3c2410_ep *ep) u32 ep_csr1; u32 idx; +handle_ep_again: if (likely (!list_empty(&ep->queue))) req = list_entry(ep->queue.next, struct s3c2410_request, queue); @@ -881,6 +882,8 @@ static void s3c2410_udc_handle_ep(struct s3c2410_ep *ep) if ((ep_csr1 & S3C2410_UDC_OCSR1_PKTRDY) && req) { s3c2410_udc_read_fifo(ep,req); + if (s3c2410_udc_fifo_count_out()) + goto handle_ep_again; } } } diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig index 09bfa9662e4..ced9bff3a6b 100644 --- a/drivers/video/backlight/Kconfig +++ b/drivers/video/backlight/Kconfig @@ -100,6 +100,20 @@ config LCD_HP700 If you have an HP Jornada 700 series handheld (710/720/728) say Y to enable LCD control driver. +config LCD_JBT6K74 + tristate "TPO JBT6K74-AS TFT display ASIC control interface" + depends on SPI_MASTER && SYSFS && LCD_CLASS_DEVICE + help + SPI driver for the control interface of TFT panels containing + the TPO JBT6K74-AS controller ASIC, such as the TPO TD028TTEC1 + TFT diplay module used in the Openmoko Freerunner GSM phone. + + The control interface is required for display operation, as it + controls power management, display timing and gamma calibration. + + This driver can also be build as a module, if so it will be called + jbt6k74. + # # Backlight # @@ -262,3 +276,9 @@ config BACKLIGHT_ADP5520 To compile this driver as a module, choose M here: the module will be called adp5520_bl. +config BACKLIGHT_PCF50633 + tristate "Backlight driver for NXP PCF50633 MFD" + depends on BACKLIGHT_CLASS_DEVICE && MFD_PCF50633 + help + If you have a backlight driven by a NXP PCF50633 MFD, say Y here to + enable its driver. diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile index 9a405548874..c2ceb18c0b1 100644 --- a/drivers/video/backlight/Makefile +++ b/drivers/video/backlight/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_LCD_PLATFORM) += platform_lcd.o obj-$(CONFIG_LCD_VGG2432A4) += vgg2432a4.o obj-$(CONFIG_LCD_TDO24M) += tdo24m.o obj-$(CONFIG_LCD_TOSA) += tosa_lcd.o +obj-$(CONFIG_LCD_JBT6K74) += jbt6k74.o obj-$(CONFIG_BACKLIGHT_CLASS_DEVICE) += backlight.o obj-$(CONFIG_BACKLIGHT_ATMEL_PWM) += atmel-pwm-bl.o @@ -28,4 +29,5 @@ obj-$(CONFIG_BACKLIGHT_SAHARA) += kb3886_bl.o obj-$(CONFIG_BACKLIGHT_WM831X) += wm831x_bl.o obj-$(CONFIG_BACKLIGHT_ADX) += adx_bl.o obj-$(CONFIG_BACKLIGHT_ADP5520) += adp5520_bl.o +obj-$(CONFIG_BACKLIGHT_PCF50633) += pcf50633-backlight.o diff --git a/drivers/video/backlight/jbt6k74.c b/drivers/video/backlight/jbt6k74.c new file mode 100644 index 00000000000..8450904d585 --- /dev/null +++ b/drivers/video/backlight/jbt6k74.c @@ -0,0 +1,896 @@ +/* Linux kernel driver for the tpo JBT6K74-AS LCM ASIC + * + * Copyright (C) 2006-2007 by Openmoko, Inc. + * Author: Harald Welte <laforge@openmoko.org>, + * Stefan Schmidt <stefan@openmoko.org> + * Copyright (C) 2008 by Harald Welte <laforge@openmoko.org> + * Copyright (C) 2009 by Lars-Peter Clausen <lars@metafoo.de> + * All rights reserved. + * + * 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; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + */ + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/workqueue.h> +#include <linux/jbt6k74.h> +#include <linux/fb.h> +#include <linux/lcd.h> +#include <linux/time.h> +#include <linux/regulator/consumer.h> +#include <linux/gpio.h> + +enum jbt_register { + JBT_REG_SLEEP_IN = 0x10, + JBT_REG_SLEEP_OUT = 0x11, + + JBT_REG_DISPLAY_OFF = 0x28, + JBT_REG_DISPLAY_ON = 0x29, + + JBT_REG_RGB_FORMAT = 0x3a, + JBT_REG_QUAD_RATE = 0x3b, + + JBT_REG_POWER_ON_OFF = 0xb0, + JBT_REG_BOOSTER_OP = 0xb1, + JBT_REG_BOOSTER_MODE = 0xb2, + JBT_REG_BOOSTER_FREQ = 0xb3, + JBT_REG_OPAMP_SYSCLK = 0xb4, + JBT_REG_VSC_VOLTAGE = 0xb5, + JBT_REG_VCOM_VOLTAGE = 0xb6, + JBT_REG_EXT_DISPL = 0xb7, + JBT_REG_OUTPUT_CONTROL = 0xb8, + JBT_REG_DCCLK_DCEV = 0xb9, + JBT_REG_DISPLAY_MODE1 = 0xba, + JBT_REG_DISPLAY_MODE2 = 0xbb, + JBT_REG_DISPLAY_MODE = 0xbc, + JBT_REG_ASW_SLEW = 0xbd, + JBT_REG_DUMMY_DISPLAY = 0xbe, + JBT_REG_DRIVE_SYSTEM = 0xbf, + + JBT_REG_SLEEP_OUT_FR_A = 0xc0, + JBT_REG_SLEEP_OUT_FR_B = 0xc1, + JBT_REG_SLEEP_OUT_FR_C = 0xc2, + JBT_REG_SLEEP_IN_LCCNT_D = 0xc3, + JBT_REG_SLEEP_IN_LCCNT_E = 0xc4, + JBT_REG_SLEEP_IN_LCCNT_F = 0xc5, + JBT_REG_SLEEP_IN_LCCNT_G = 0xc6, + + JBT_REG_GAMMA1_FINE_1 = 0xc7, + JBT_REG_GAMMA1_FINE_2 = 0xc8, + JBT_REG_GAMMA1_INCLINATION = 0xc9, + JBT_REG_GAMMA1_BLUE_OFFSET = 0xca, + + /* VGA */ + JBT_REG_BLANK_CONTROL = 0xcf, + JBT_REG_BLANK_TH_TV = 0xd0, + JBT_REG_CKV_ON_OFF = 0xd1, + JBT_REG_CKV_1_2 = 0xd2, + JBT_REG_OEV_TIMING = 0xd3, + JBT_REG_ASW_TIMING_1 = 0xd4, + JBT_REG_ASW_TIMING_2 = 0xd5, + + /* QVGA */ + JBT_REG_BLANK_CONTROL_QVGA = 0xd6, + JBT_REG_BLANK_TH_TV_QVGA = 0xd7, + JBT_REG_CKV_ON_OFF_QVGA = 0xd8, + JBT_REG_CKV_1_2_QVGA = 0xd9, + JBT_REG_OEV_TIMING_QVGA = 0xde, + JBT_REG_ASW_TIMING_1_QVGA = 0xdf, + JBT_REG_ASW_TIMING_2_QVGA = 0xe0, + + + JBT_REG_HCLOCK_VGA = 0xec, + JBT_REG_HCLOCK_QVGA = 0xed, +}; + +enum jbt_resolution { + JBT_RESOLUTION_VGA, + JBT_RESOLUTION_QVGA, +}; + +enum jbt_power_mode { + JBT_POWER_MODE_DEEP_STANDBY, + JBT_POWER_MODE_SLEEP, + JBT_POWER_MODE_NORMAL, +}; + +static const char *jbt_power_mode_names[] = { + [JBT_POWER_MODE_DEEP_STANDBY] = "deep-standby", + [JBT_POWER_MODE_SLEEP] = "sleep", + [JBT_POWER_MODE_NORMAL] = "normal", +}; + +static const char *jbt_resolution_names[] = { + [JBT_RESOLUTION_VGA] = "vga", + [JBT_RESOLUTION_QVGA] = "qvga", +}; + +struct jbt_info { + struct mutex lock; /* protects this structure */ + enum jbt_resolution resolution; + enum jbt_power_mode power_mode; + enum jbt_power_mode suspend_mode; + int suspended; + + struct spi_device *spi; + struct lcd_device *lcd_dev; + + unsigned long next_sleep; + struct delayed_work blank_work; + int blank_mode; + struct regulator_bulk_data supplies[2]; + uint16_t tx_buf[3]; + uint16_t reg_cache[0xEE]; +}; + +#define JBT_COMMAND 0x000 +#define JBT_DATA 0x100 + +static int jbt_reg_write_nodata(struct jbt_info *jbt, uint8_t reg) +{ + int ret; + + jbt->tx_buf[0] = JBT_COMMAND | reg; + ret = spi_write(jbt->spi, (uint8_t *)jbt->tx_buf, + sizeof(uint16_t)); + if (ret == 0) + jbt->reg_cache[reg] = 0; + else + dev_err(&jbt->spi->dev, "Write failed: %d\n", ret); + + return ret; +} + + +static int jbt_reg_write(struct jbt_info *jbt, uint8_t reg, uint8_t data) +{ + int ret; + + jbt->tx_buf[0] = JBT_COMMAND | reg; + jbt->tx_buf[1] = JBT_DATA | data; + ret = spi_write(jbt->spi, (uint8_t *)jbt->tx_buf, + 2*sizeof(uint16_t)); + if (ret == 0) + jbt->reg_cache[reg] = data; + else + dev_err(&jbt->spi->dev, "Write failed: %d\n", ret); + + return ret; +} + +static int jbt_reg_write16(struct jbt_info *jbt, uint8_t reg, uint16_t data) +{ + int ret; + + jbt->tx_buf[0] = JBT_COMMAND | reg; + jbt->tx_buf[1] = JBT_DATA | (data >> 8); + jbt->tx_buf[2] = JBT_DATA | (data & 0xff); + + ret = spi_write(jbt->spi, (uint8_t *)jbt->tx_buf, + 3*sizeof(uint16_t)); + if (ret == 0) + jbt->reg_cache[reg] = data; + else + dev_err(&jbt->spi->dev, "Write failed: %d\n", ret); + + return ret; +} + +static int jbt_init_regs(struct jbt_info *jbt) +{ + int ret; + + dev_dbg(&jbt->spi->dev, "entering %cVGA mode\n", + jbt->resolution == JBT_RESOLUTION_QVGA ? 'Q' : ' '); + + ret = jbt_reg_write(jbt, JBT_REG_DISPLAY_MODE1, 0x01); + ret |= jbt_reg_write(jbt, JBT_REG_DISPLAY_MODE2, 0x00); + ret |= jbt_reg_write(jbt, JBT_REG_RGB_FORMAT, 0x60); + ret |= jbt_reg_write(jbt, JBT_REG_DRIVE_SYSTEM, 0x10); + ret |= jbt_reg_write(jbt, JBT_REG_BOOSTER_OP, 0x56); + ret |= jbt_reg_write(jbt, JBT_REG_BOOSTER_MODE, 0x33); + ret |= jbt_reg_write(jbt, JBT_REG_BOOSTER_FREQ, 0x11); + ret |= jbt_reg_write(jbt, JBT_REG_OPAMP_SYSCLK, 0x02); + ret |= jbt_reg_write(jbt, JBT_REG_VSC_VOLTAGE, 0x2b); + ret |= jbt_reg_write(jbt, JBT_REG_VCOM_VOLTAGE, 0x40); + ret |= jbt_reg_write(jbt, JBT_REG_EXT_DISPL, 0x03); + ret |= jbt_reg_write(jbt, JBT_REG_DCCLK_DCEV, 0x04); + /* + * default of 0x02 in JBT_REG_ASW_SLEW responsible for 72Hz requirement + * to avoid red / blue flicker + */ + ret |= jbt_reg_write(jbt, JBT_REG_ASW_SLEW, 0x00 | (1 << 5)); + ret |= jbt_reg_write(jbt, JBT_REG_DUMMY_DISPLAY, 0x00); + + ret |= jbt_reg_write(jbt, JBT_REG_SLEEP_OUT_FR_A, 0x11); + ret |= jbt_reg_write(jbt, JBT_REG_SLEEP_OUT_FR_B, 0x11); + ret |= jbt_reg_write(jbt, JBT_REG_SLEEP_OUT_FR_C, 0x11); + ret |= jbt_reg_write16(jbt, JBT_REG_SLEEP_IN_LCCNT_D, 0x2040); + ret |= jbt_reg_write16(jbt, JBT_REG_SLEEP_IN_LCCNT_E, 0x60c0); + ret |= jbt_reg_write16(jbt, JBT_REG_SLEEP_IN_LCCNT_F, 0x1020); + ret |= jbt_reg_write16(jbt, JBT_REG_SLEEP_IN_LCCNT_G, 0x60c0); + + ret |= jbt_reg_write16(jbt, JBT_REG_GAMMA1_FINE_1, 0x5533); + ret |= jbt_reg_write(jbt, JBT_REG_GAMMA1_FINE_2, 0x00); + ret |= jbt_reg_write(jbt, JBT_REG_GAMMA1_INCLINATION, 0x00); + ret |= jbt_reg_write(jbt, JBT_REG_GAMMA1_BLUE_OFFSET, 0x00); + + if (jbt->resolution != JBT_RESOLUTION_QVGA) { + ret |= jbt_reg_write16(jbt, JBT_REG_HCLOCK_VGA, 0x1f0); + ret |= jbt_reg_write(jbt, JBT_REG_BLANK_CONTROL, 0x02); + ret |= jbt_reg_write16(jbt, JBT_REG_BLANK_TH_TV, 0x0804); + + ret |= jbt_reg_write(jbt, JBT_REG_CKV_ON_OFF, 0x01); + ret |= jbt_reg_write16(jbt, JBT_REG_CKV_1_2, 0x0000); + + ret |= jbt_reg_write16(jbt, JBT_REG_OEV_TIMING, 0x0d0e); + ret |= jbt_reg_write16(jbt, JBT_REG_ASW_TIMING_1, 0x11a4); + ret |= jbt_reg_write(jbt, JBT_REG_ASW_TIMING_2, 0x0e); + } else { + ret |= jbt_reg_write16(jbt, JBT_REG_HCLOCK_QVGA, 0x00ff); + ret |= jbt_reg_write(jbt, JBT_REG_BLANK_CONTROL_QVGA, 0x02); + ret |= jbt_reg_write16(jbt, JBT_REG_BLANK_TH_TV_QVGA, 0x0804); + + ret |= jbt_reg_write(jbt, JBT_REG_CKV_ON_OFF_QVGA, 0x01); + ret |= jbt_reg_write16(jbt, JBT_REG_CKV_1_2_QVGA, 0x0008); + + ret |= jbt_reg_write16(jbt, JBT_REG_OEV_TIMING_QVGA, 0x050a); + ret |= jbt_reg_write16(jbt, JBT_REG_ASW_TIMING_1_QVGA, 0x0a19); + ret |= jbt_reg_write(jbt, JBT_REG_ASW_TIMING_2_QVGA, 0x0a); + } + + return ret ? -EIO : 0; +} + +static int jbt_standby_to_sleep(struct jbt_info *jbt) +{ + int ret; + struct jbt6k74_platform_data *pdata = jbt->spi->dev.platform_data; + + gpio_set_value_cansleep(pdata->gpio_reset, 1); + ret = regulator_bulk_enable(ARRAY_SIZE(jbt->supplies), jbt->supplies); + + /* three times command zero */ + ret |= jbt_reg_write_nodata(jbt, 0x00); + mdelay(1); + ret |= jbt_reg_write_nodata(jbt, 0x00); + mdelay(1); + ret |= jbt_reg_write_nodata(jbt, 0x00); + mdelay(1); + + /* deep standby out */ + ret |= jbt_reg_write(jbt, JBT_REG_POWER_ON_OFF, 0x11); + mdelay(1); + ret = jbt_reg_write(jbt, JBT_REG_DISPLAY_MODE, 0x28); + + /* (re)initialize register set */ + ret |= jbt_init_regs(jbt); + + return ret ? -EIO : 0; +} + +static int jbt_sleep_to_normal(struct jbt_info *jbt) +{ + int ret; + + /* Make sure we are 120 ms after SLEEP_OUT */ + if (time_before(jiffies, jbt->next_sleep)) + mdelay(jiffies_to_msecs(jbt->next_sleep - jiffies)); + + if (jbt->resolution == JBT_RESOLUTION_VGA) { + /* RGB I/F on, RAM wirte off, QVGA through, SIGCON enable */ + ret = jbt_reg_write(jbt, JBT_REG_DISPLAY_MODE, 0x80); + + /* Quad mode off */ + ret |= jbt_reg_write(jbt, JBT_REG_QUAD_RATE, 0x00); + } else { + /* RGB I/F on, RAM wirte off, QVGA through, SIGCON enable */ + ret = jbt_reg_write(jbt, JBT_REG_DISPLAY_MODE, 0x81); + + /* Quad mode on */ + ret |= jbt_reg_write(jbt, JBT_REG_QUAD_RATE, 0x22); + } + + /* AVDD on, XVDD on */ + ret |= jbt_reg_write(jbt, JBT_REG_POWER_ON_OFF, 0x16); + + /* Output control */ + ret |= jbt_reg_write16(jbt, JBT_REG_OUTPUT_CONTROL, 0xdff9); + + /* Turn on display */ + ret |= jbt_reg_write_nodata(jbt, JBT_REG_DISPLAY_ON); + + /* Sleep mode off */ + ret |= jbt_reg_write_nodata(jbt, JBT_REG_SLEEP_OUT); + jbt->next_sleep = jiffies + msecs_to_jiffies(120); + + /* Allow the booster and display controller to restart stably */ + mdelay(5); + + return ret ? -EIO : 0; +} + +static int jbt_normal_to_sleep(struct jbt_info *jbt) +{ + int ret; + + /* Make sure we are 120 ms after SLEEP_OUT */ + while (time_before(jiffies, jbt->next_sleep)) + cpu_relax(); + + ret = jbt_reg_write_nodata(jbt, JBT_REG_DISPLAY_OFF); + ret |= jbt_reg_write16(jbt, JBT_REG_OUTPUT_CONTROL, 0x8000 | 1 << 3); + ret |= jbt_reg_write_nodata(jbt, JBT_REG_SLEEP_IN); + jbt->next_sleep = jiffies + msecs_to_jiffies(120); + + /* Allow the internal circuits to stop automatically */ + mdelay(5); + + return ret ? -EIO : 0; +} + +static int jbt_sleep_to_standby(struct jbt_info *jbt) +{ + int ret; + struct jbt6k74_platform_data *pdata = jbt->spi->dev.platform_data; + + ret = jbt_reg_write(jbt, JBT_REG_POWER_ON_OFF, 0x00); + + if (!ret) + ret = regulator_bulk_disable(ARRAY_SIZE(jbt->supplies), jbt->supplies); + + if (!ret) + gpio_set_value_cansleep(pdata->gpio_reset, 0); + + return ret; +} + +static int jbt6k74_enter_power_mode(struct jbt_info *jbt, + enum jbt_power_mode new_mode) +{ + struct jbt6k74_platform_data *pdata = jbt->spi->dev.platform_data; + int ret = -EINVAL; + + dev_dbg(&jbt->spi->dev, "entering (old_state=%s, new_state=%s)\n", + jbt_power_mode_names[jbt->power_mode], + jbt_power_mode_names[new_mode]); + + mutex_lock(&jbt->lock); + + if (jbt->suspended) { + switch (new_mode) { + case JBT_POWER_MODE_DEEP_STANDBY: + case JBT_POWER_MODE_SLEEP: + case JBT_POWER_MODE_NORMAL: + ret = 0; + jbt->suspend_mode = new_mode; + break; + default: + break; + } + } else if (new_mode == JBT_POWER_MODE_NORMAL && + pdata->enable_pixel_clock) { + pdata->enable_pixel_clock(&jbt->spi->dev, 1); + } + + switch (jbt->power_mode) { + case JBT_POWER_MODE_DEEP_STANDBY: + switch (new_mode) { + case JBT_POWER_MODE_DEEP_STANDBY: + ret = 0; + break; + case JBT_POWER_MODE_SLEEP: + ret = jbt_standby_to_sleep(jbt); + break; + case JBT_POWER_MODE_NORMAL: + /* first transition into sleep */ + ret = jbt_standby_to_sleep(jbt); + /* then transition into normal */ + ret |= jbt_sleep_to_normal(jbt); + break; + } + break; + case JBT_POWER_MODE_SLEEP: + switch (new_mode) { + case JBT_POWER_MODE_SLEEP: + ret = 0; + break; + case JBT_POWER_MODE_DEEP_STANDBY: + ret = jbt_sleep_to_standby(jbt); + break; + case JBT_POWER_MODE_NORMAL: + ret = jbt_sleep_to_normal(jbt); + break; + } + break; + case JBT_POWER_MODE_NORMAL: + switch (new_mode) { + case JBT_POWER_MODE_NORMAL: + ret = 0; + break; + case JBT_POWER_MODE_DEEP_STANDBY: + /* first transition into sleep */ + ret = jbt_normal_to_sleep(jbt); + /* then transition into deep standby */ + ret |= jbt_sleep_to_standby(jbt); + break; + case JBT_POWER_MODE_SLEEP: + ret = jbt_normal_to_sleep(jbt); + break; + } + } + + if (ret == 0) { + jbt->power_mode = new_mode; + if (new_mode != JBT_POWER_MODE_NORMAL && + pdata->enable_pixel_clock) + pdata->enable_pixel_clock(&jbt->spi->dev, 0); + } else { + dev_err(&jbt->spi->dev, "Failed enter state '%s': %d\n", + jbt_power_mode_names[new_mode], ret); + } + + mutex_unlock(&jbt->lock); + + return ret; +} + +static int jbt6k74_set_resolution(struct jbt_info *jbt, + enum jbt_resolution new_resolution) +{ + int ret = 0; + enum jbt_resolution old_resolution; + + mutex_lock(&jbt->lock); + + if (jbt->resolution == new_resolution) + goto out_unlock; + + old_resolution = jbt->resolution; + jbt->resolution = new_resolution; + + if (jbt->power_mode == JBT_POWER_MODE_NORMAL) { + + /* first transition into sleep */ + ret = jbt_normal_to_sleep(jbt); + ret |= jbt_sleep_to_normal(jbt); + + if (ret) { + jbt->resolution = old_resolution; + dev_err(&jbt->spi->dev, "Failed to set resolution '%s')\n", + jbt_resolution_names[new_resolution]); + } + } + +out_unlock: + mutex_unlock(&jbt->lock); + + return ret; +} + +static ssize_t resolution_read(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct jbt_info *jbt = dev_get_drvdata(dev); + + if (jbt->resolution >= ARRAY_SIZE(jbt_resolution_names)) + return -EIO; + + return sprintf(buf, "%s\n", jbt_resolution_names[jbt->resolution]); +} + +static ssize_t resolution_write(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct jbt_info *jbt = dev_get_drvdata(dev); + int i, ret; + + for (i = 0; i < ARRAY_SIZE(jbt_resolution_names); i++) { + if (!strncmp(buf, jbt_resolution_names[i], + strlen(jbt_resolution_names[i]))) { + ret = jbt6k74_set_resolution(jbt, i); + if (ret) + return ret; + return count; + } + } + + return -EINVAL; +} + +static DEVICE_ATTR(resolution, 0644, resolution_read, resolution_write); + +static int reg_by_string(const char *name) +{ + if (!strcmp(name, "gamma_fine1")) + return JBT_REG_GAMMA1_FINE_1; + else if (!strcmp(name, "gamma_fine2")) + return JBT_REG_GAMMA1_FINE_2; + else if (!strcmp(name, "gamma_inclination")) + return JBT_REG_GAMMA1_INCLINATION; + else + return JBT_REG_GAMMA1_BLUE_OFFSET; +} + +static ssize_t gamma_read(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct jbt_info *jbt = dev_get_drvdata(dev); + int reg = reg_by_string(attr->attr.name); + uint16_t val; + + mutex_lock(&jbt->lock); + val = jbt->reg_cache[reg]; + mutex_unlock(&jbt->lock); + + return sprintf(buf, "0x%04x\n", val); +} + +static ssize_t gamma_write(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + struct jbt_info *jbt = dev_get_drvdata(dev); + int reg = reg_by_string(attr->attr.name); + unsigned long val; + + ret = strict_strtoul(buf, 10, &val); + if (ret) + return ret; + + mutex_lock(&jbt->lock); + jbt_reg_write(jbt, reg, val & 0xff); + mutex_unlock(&jbt->lock); + + return count; +} + +static ssize_t reset_write(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + struct jbt_info *jbt = dev_get_drvdata(dev); + struct jbt6k74_platform_data *pdata = jbt->spi->dev.platform_data; + enum jbt_power_mode old_power_mode = jbt->power_mode; + + mutex_lock(&jbt->lock); + + if (gpio_is_valid(pdata->gpio_reset)) { + /* hard reset the jbt6k74 */ + gpio_set_value_cansleep(pdata->gpio_reset, 0); + mdelay(120); + gpio_set_value_cansleep(pdata->gpio_reset, 1); + mdelay(120); + } + + ret = jbt_reg_write_nodata(jbt, 0x01); + if (ret < 0) + dev_err(&jbt->spi->dev, "cannot soft reset\n"); + mdelay(120); + + mutex_unlock(&jbt->lock); + + jbt->power_mode = JBT_POWER_MODE_DEEP_STANDBY; + jbt6k74_enter_power_mode(jbt, old_power_mode); + + return count; +} + +static DEVICE_ATTR(gamma_fine1, 0644, gamma_read, gamma_write); +static DEVICE_ATTR(gamma_fine2, 0644, gamma_read, gamma_write); +static DEVICE_ATTR(gamma_inclination, 0644, gamma_read, gamma_write); +static DEVICE_ATTR(gamma_blue_offset, 0644, gamma_read, gamma_write); +static DEVICE_ATTR(reset, 0600, NULL, reset_write); + +static struct attribute *jbt_sysfs_entries[] = { + &dev_attr_resolution.attr, + &dev_attr_gamma_fine1.attr, + &dev_attr_gamma_fine2.attr, + &dev_attr_gamma_inclination.attr, + &dev_attr_gamma_blue_offset.attr, + &dev_attr_reset.attr, + NULL, +}; + +static struct attribute_group jbt_attr_group = { + .name = NULL, + .attrs = jbt_sysfs_entries, +}; + +/* FIXME: This in an ugly hack to delay display blanking. + When the jbt is in sleep mode it displays an all white screen and thus one + will a see a short flash. + By delaying the blanking we will give the backlight a chance to turn off and + thus avoid getting the flash */ +static void jbt_blank_worker(struct work_struct *work) +{ + struct jbt_info *jbt = container_of(work, struct jbt_info, + blank_work.work); + + switch (jbt->blank_mode) { + case FB_BLANK_NORMAL: + jbt6k74_enter_power_mode(jbt, JBT_POWER_MODE_SLEEP); + break; + case FB_BLANK_POWERDOWN: + jbt6k74_enter_power_mode(jbt, JBT_POWER_MODE_DEEP_STANDBY); + break; + default: + break; + } +} + +static int jbt6k74_set_mode(struct lcd_device *ld, struct fb_videomode *m) +{ + int ret = -EINVAL; + struct jbt_info *jbt = dev_get_drvdata(&ld->dev); + + if (m->xres == 240 && m->yres == 320) { + ret = jbt6k74_set_resolution(jbt, JBT_RESOLUTION_QVGA); + } else if (m->xres == 480 && m->yres == 640) { + ret = jbt6k74_set_resolution(jbt, JBT_RESOLUTION_VGA); + } else { + dev_err(&jbt->spi->dev, "Unknown resolution.\n"); + jbt6k74_enter_power_mode(jbt, JBT_POWER_MODE_SLEEP); + } + + return ret; +} + +static int jbt6k74_set_power(struct lcd_device *ld, int power) +{ + int ret = -EINVAL; + struct jbt_info *jbt = dev_get_drvdata(&ld->dev); + + jbt->blank_mode = power; + cancel_rearming_delayed_work(&jbt->blank_work); + + switch (power) { + case FB_BLANK_UNBLANK: + dev_dbg(&jbt->spi->dev, "unblank\n"); + mdelay(20); + ret = jbt6k74_enter_power_mode(jbt, JBT_POWER_MODE_NORMAL); + break; + case FB_BLANK_NORMAL: + dev_dbg(&jbt->spi->dev, "blank\n"); + ret = schedule_delayed_work(&jbt->blank_work, HZ); + break; + case FB_BLANK_POWERDOWN: + dev_dbg(&jbt->spi->dev, "powerdown\n"); + ret = schedule_delayed_work(&jbt->blank_work, HZ); + break; + default: + break; + } + + return ret; +} + +static int jbt6k74_get_power(struct lcd_device *ld) +{ + struct jbt_info *jbt = dev_get_drvdata(&ld->dev); + + switch (jbt->power_mode) { + case JBT_POWER_MODE_NORMAL: + return FB_BLANK_UNBLANK; + case JBT_POWER_MODE_SLEEP: + return FB_BLANK_NORMAL; + default: + return JBT_POWER_MODE_DEEP_STANDBY; + } +} + +struct lcd_ops jbt6k74_lcd_ops = { + .set_power = jbt6k74_set_power, + .get_power = jbt6k74_get_power, + .set_mode = jbt6k74_set_mode, +}; + +/* linux device model infrastructure */ + +static int __devinit jbt_probe(struct spi_device *spi) +{ + int ret; + struct jbt_info *jbt; + struct jbt6k74_platform_data *pdata = spi->dev.platform_data; + + /* the controller doesn't have a MISO pin; we can't do detection */ + + spi->mode = SPI_CPOL | SPI_CPHA; + spi->bits_per_word = 9; + + ret = spi_setup(spi); + if (ret < 0) { + dev_err(&spi->dev, + "Failed to setup spi\n"); + return ret; + } + + jbt = kzalloc(sizeof(*jbt), GFP_KERNEL); + if (!jbt) + return -ENOMEM; + + jbt->spi = spi; + + jbt->lcd_dev = lcd_device_register("jbt6k74-lcd", &spi->dev, jbt, + &jbt6k74_lcd_ops); + + if (IS_ERR(jbt->lcd_dev)) { + ret = PTR_ERR(jbt->lcd_dev); + goto err_free_drvdata; + } + + INIT_DELAYED_WORK(&jbt->blank_work, jbt_blank_worker); + + jbt->resolution = JBT_RESOLUTION_VGA; + jbt->power_mode = JBT_POWER_MODE_DEEP_STANDBY; + jbt->next_sleep = jiffies + msecs_to_jiffies(120); + mutex_init(&jbt->lock); + + dev_set_drvdata(&spi->dev, jbt); + + jbt->supplies[0].supply = "VDC"; + jbt->supplies[1].supply = "VDDIO"; + + ret = regulator_bulk_get(&spi->dev, ARRAY_SIZE(jbt->supplies), + jbt->supplies); + if (ret) { + dev_err(&spi->dev, "Failed to power get supplies: %d\n", ret); + goto err_unregister_lcd; + } + + if (gpio_is_valid(pdata->gpio_reset)) { + ret = gpio_request(pdata->gpio_reset, "jbt6k74 reset"); + if (ret) { + dev_err(&spi->dev, "Failed to request reset gpio: %d\n", + ret); + goto err_free_supplies; + } + + ret = gpio_direction_output(pdata->gpio_reset, 1); + if (ret) { + dev_err(&spi->dev, "Failed to set reset gpio direction: %d\n", + ret); + goto err_gpio_free; + } + } + + ret = sysfs_create_group(&spi->dev.kobj, &jbt_attr_group); + if (ret < 0) { + dev_err(&spi->dev, "cannot create sysfs group\n"); + goto err_gpio_free; + } + + mdelay(50); + ret = jbt6k74_enter_power_mode(jbt, JBT_POWER_MODE_NORMAL); + if (ret < 0) { + dev_err(&spi->dev, "cannot enter NORMAL state\n"); + goto err_sysfs_remove; + } + + + if (pdata->probe_completed) + (pdata->probe_completed)(&spi->dev); + + return 0; + +err_sysfs_remove: + sysfs_remove_group(&spi->dev.kobj, &jbt_attr_group); +err_gpio_free: + gpio_free(pdata->gpio_reset); +err_free_supplies: + regulator_bulk_free(ARRAY_SIZE(jbt->supplies), jbt->supplies); +err_unregister_lcd: + lcd_device_unregister(jbt->lcd_dev); +err_free_drvdata: + dev_set_drvdata(&spi->dev, NULL); + kfree(jbt); + + return ret; +} + +static int __devexit jbt_remove(struct spi_device *spi) +{ + struct jbt_info *jbt = dev_get_drvdata(&spi->dev); + struct jbt6k74_platform_data *pdata = jbt->spi->dev.platform_data; + + /* We don't want to switch off the display in case the user + * accidentially unloads the module (whose use count normally is 0) */ + jbt6k74_enter_power_mode(jbt, JBT_POWER_MODE_NORMAL); + + sysfs_remove_group(&spi->dev.kobj, &jbt_attr_group); + + if (gpio_is_valid(pdata->gpio_reset)) + gpio_free(pdata->gpio_reset); + + lcd_device_unregister(jbt->lcd_dev); + + dev_set_drvdata(&spi->dev, NULL); + + regulator_bulk_free(ARRAY_SIZE(jbt->supplies), jbt->supplies); + kfree(jbt); + + return 0; +} + +#ifdef CONFIG_PM +static int jbt_suspend(struct spi_device *spi, pm_message_t state) +{ + struct jbt_info *jbt = dev_get_drvdata(&spi->dev); + + jbt->suspend_mode = jbt->power_mode; + + jbt6k74_enter_power_mode(jbt, JBT_POWER_MODE_DEEP_STANDBY); + jbt->suspended = 1; + + dev_info(&spi->dev, "suspended\n"); + + return 0; +} + +int jbt6k74_resume(struct spi_device *spi) +{ + struct jbt_info *jbt = dev_get_drvdata(&spi->dev); + dev_info(&spi->dev, "starting resume: %d\n", jbt->suspend_mode); + + mdelay(20); + + jbt->suspended = 0; + jbt6k74_enter_power_mode(jbt, jbt->suspend_mode); + + dev_info(&spi->dev, "resumed: %d\n", jbt->suspend_mode); + + return 0; +} +EXPORT_SYMBOL_GPL(jbt6k74_resume); + +#else +#define jbt_suspend NULL +#define jbt6k74_resume NULL +#endif + +static struct spi_driver jbt6k74_driver = { + .driver = { + .name = "jbt6k74", + .owner = THIS_MODULE, + }, + + .probe = jbt_probe, + .remove = __devexit_p(jbt_remove), + .suspend = jbt_suspend, + .resume = jbt6k74_resume, +}; + +static int __init jbt_init(void) +{ + return spi_register_driver(&jbt6k74_driver); +} +module_init(jbt_init); + +static void __exit jbt_exit(void) +{ + spi_unregister_driver(&jbt6k74_driver); +} +module_exit(jbt_exit); + +MODULE_DESCRIPTION("SPI driver for tpo JBT6K74-AS LCM control interface"); +MODULE_AUTHOR("Harald Welte <laforge@openmoko.org>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/backlight/pcf50633-backlight.c b/drivers/video/backlight/pcf50633-backlight.c new file mode 100644 index 00000000000..10b9c31bc43 --- /dev/null +++ b/drivers/video/backlight/pcf50633-backlight.c @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2009, Lars-Peter Clausen <lars@metafoo.de> + * PCF50633 backlight device driver + * + * 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; either version 2 of the License, or (at your + * option) any later version. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/fb.h> +#include <linux/backlight.h> +#include <linux/mfd/pcf50633/core.h> +#include <linux/mfd/pcf50633/backlight.h> + +struct pcf50633_bl { + struct pcf50633 *pcf; + struct backlight_device *bl; + + unsigned int brightness; + unsigned int brightness_limit; +}; + +/* + * pcf50633_bl_set_brightness_limit + * + * Update the brightness limit for the pc50633 backlight. The actuall brightness + * will not go above the limit. This is useful to limit power drain for example + * on low battery. + * + * @dev: Pointer to a pcf50633 device + * @limit: The brightness limit. Valid values are 0-63 + */ +int pcf50633_bl_set_brightness_limit(struct pcf50633 *pcf, unsigned int limit) +{ + struct pcf50633_bl *pcf_bl = platform_get_drvdata(pcf->bl_pdev); + + if (!pcf_bl) + return -ENODEV; + + pcf_bl->brightness_limit = limit & 0x3f; + backlight_update_status(pcf_bl->bl); + + return 0; +} + +static int pcf50633_bl_update_status(struct backlight_device *bl) +{ + struct pcf50633_bl *pcf_bl = bl_get_data(bl); + unsigned int new_brightness; + + + if (bl->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK) || + bl->props.power != FB_BLANK_UNBLANK) + new_brightness = 0; + else if (bl->props.brightness < pcf_bl->brightness_limit) + new_brightness = bl->props.brightness; + else + new_brightness = pcf_bl->brightness_limit; + + + if (pcf_bl->brightness == new_brightness) + return 0; + + if (new_brightness) { + pcf50633_reg_write(pcf_bl->pcf, PCF50633_REG_LEDOUT, + new_brightness); + if (!pcf_bl->brightness) + pcf50633_reg_write(pcf_bl->pcf, PCF50633_REG_LEDENA, 1); + } else { + pcf50633_reg_write(pcf_bl->pcf, PCF50633_REG_LEDENA, 0); + } + + pcf_bl->brightness = new_brightness; + + return 0; +} + +static int pcf50633_bl_get_brightness(struct backlight_device *bl) +{ + struct pcf50633_bl *pcf_bl = bl_get_data(bl); + return pcf_bl->brightness; +} + +static struct backlight_ops pcf50633_bl_ops = { + .get_brightness = pcf50633_bl_get_brightness, + .update_status = pcf50633_bl_update_status, + .options = BL_CORE_SUSPENDRESUME, +}; + +static int __devinit pcf50633_bl_probe(struct platform_device *pdev) +{ + int ret; + struct pcf50633_bl *pcf_bl; + struct pcf50633_platform_data *pcf50633_data = pdev->dev.parent->platform_data; + struct pcf50633_bl_platform_data *pdata = pcf50633_data->backlight_data; + + pcf_bl = kzalloc(sizeof(*pcf_bl), GFP_KERNEL); + if (!pcf_bl) + return -ENOMEM; + + pcf_bl->pcf = dev_to_pcf50633(pdev->dev.parent); + + pcf_bl->bl = backlight_device_register(pdev->name, &pdev->dev, pcf_bl, + &pcf50633_bl_ops); + + if (IS_ERR(pcf_bl->bl)) { + ret = PTR_ERR(pcf_bl->bl); + goto err_free; + } + + platform_set_drvdata(pdev, pcf_bl); + + pcf_bl->bl->props.max_brightness = 0x3f; + pcf_bl->bl->props.power = FB_BLANK_UNBLANK; + + if (pdata) { + pcf_bl->bl->props.brightness = pdata->default_brightness; + pcf_bl->brightness_limit = pdata->default_brightness_limit; + } else { + pcf_bl->bl->props.brightness = 0x3f; + pcf_bl->brightness_limit = 0x3f; + } + + pcf50633_reg_write(pcf_bl->pcf, PCF50633_REG_LEDDIM, pdata->ramp_time); + + /* Should be different from props.brightness, so we don't + * update_status early the first time it's called */ + pcf_bl->brightness = pcf_bl->bl->props.brightness + 1; + + backlight_update_status(pcf_bl->bl); + + return 0; + +err_free: + kfree(pcf_bl); + + return ret; +} + +static int __devexit pcf50633_bl_remove(struct platform_device *pdev) +{ + struct pcf50633_bl *pcf_bl = platform_get_drvdata(pdev); + + backlight_device_unregister(pcf_bl->bl); + + platform_set_drvdata(pdev, NULL); + + kfree(pcf_bl); + + return 0; +} + +static struct platform_driver pcf50633_bl_driver = { + .probe = pcf50633_bl_probe, + .remove = __devexit_p(pcf50633_bl_remove), + .driver = { + .name = "pcf50633-backlight", + }, +}; + +static int __init pcf50633_bl_init(void) +{ + return platform_driver_register(&pcf50633_bl_driver); +} +module_init(pcf50633_bl_init); + +static void __exit pcf50633_bl_exit(void) +{ + platform_driver_unregister(&pcf50633_bl_driver); +} +module_exit(pcf50633_bl_exit); + +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("PCF50633 backlight driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:pcf50633-backlight"); diff --git a/include/linux/bq27000_battery.h b/include/linux/bq27000_battery.h new file mode 100644 index 00000000000..a617466a517 --- /dev/null +++ b/include/linux/bq27000_battery.h @@ -0,0 +1,16 @@ +#ifndef __BQ27000_BATTERY_H__ +#define __BQ27000_BATTERY_H__ + +void bq27000_charging_state_change(struct platform_device *pdev); + +struct bq27000_platform_data { + const char *name; + int rsense_mohms; + int (*hdq_read)(int); + int (*hdq_write)(int, u8); + int (*hdq_initialized)(void); + int (*get_charger_online_status)(void); + int (*get_charger_active_status)(void); +}; + +#endif diff --git a/include/linux/hdq.h b/include/linux/hdq.h new file mode 100644 index 00000000000..377ab387eb3 --- /dev/null +++ b/include/linux/hdq.h @@ -0,0 +1,32 @@ +#ifndef __LINUX_HDQ_H__ +#define __LINUX_HDQ_H__ + +#include <linux/device.h> + +#define HDQ_SAMPLE_PERIOD_US 10 + +/* platform data */ + +struct hdq_platform_data { + /* + * give an opportunity to use us as parent for + * devices that depend on us + */ + void (*attach_child_devices)(struct device *parent_device); + + void (*gpio_dir_out)(void); + void (*gpio_dir_in)(void); + void (*gpio_set)(int); + int (*gpio_get)(void); + + int (*enable_fiq)(void); + void (*disable_fiq)(void); + void (*kick_fiq)(void); + +}; + +int hdq_read(int address); +int hdq_write(int address, u8 data); +int hdq_initialized(void); + +#endif diff --git a/include/linux/input/touchscreen/ts_filter.h b/include/linux/input/touchscreen/ts_filter.h new file mode 100644 index 00000000000..632e5fb3f4e --- /dev/null +++ b/include/linux/input/touchscreen/ts_filter.h @@ -0,0 +1,74 @@ +#ifndef __TS_FILTER_H__ +#define __TS_FILTER_H__ + +/* + * Touchscreen filter. + * + * (c) 2008,2009 Andy Green <andy@warmcat.com> + */ + +#include <linux/platform_device.h> + +#define MAX_TS_FILTER_COORDS 3 /* X, Y and Z (pressure). */ + +struct ts_filter; +struct ts_filter_configuration; + +/* Operations that a filter can perform. */ + +struct ts_filter_api { + /* Create the filter - mandatory. */ + struct ts_filter * (*create)( + struct platform_device *pdev, + const struct ts_filter_configuration *config, + int count_coords); + /* Destroy the filter - mandatory. */ + void (*destroy)(struct ts_filter *filter); + /* Clear the filter - optional. */ + void (*clear)(struct ts_filter *filter); + + + /* + * The next three API functions only make sense if all of them are + * set for a filter. If a filter has the next three methods then + * it can propagate coordinates in the chain. + */ + + /* + * Process the filter. + * It returns non-zero if the filter reaches an error. + */ + int (*process)(struct ts_filter *filter, int *coords); + /* + * Is the filter ready to return a point? + * Please do not code side effects in this function. + */ + int (*haspoint)(struct ts_filter *filter); + /* + * Get a point. + * Do not call unless the filter actually has a point to deliver. + */ + void (*getpoint)(struct ts_filter *filter, int *coords); + + /* + * Scale the points - optional. + * A filter could only scale coordinates. + */ + void (*scale)(struct ts_filter *filter, int *coords); +}; + +/* + * Generic filter configuration. Actual configurations have this structure + * as a member. + */ +struct ts_filter_configuration { +}; + +struct ts_filter { + /* Operations for this filter. */ + const struct ts_filter_api *api; + /* Number of coordinates to process. */ + int count_coords; +}; + +#endif diff --git a/include/linux/input/touchscreen/ts_filter_chain.h b/include/linux/input/touchscreen/ts_filter_chain.h new file mode 100644 index 00000000000..a021ba25bd4 --- /dev/null +++ b/include/linux/input/touchscreen/ts_filter_chain.h @@ -0,0 +1,58 @@ +#ifndef __TS_FILTER_CHAIN_H__ +#define __TS_FILTER_CHAIN_H__ + +/* + * Touchscreen filter chains. + * + * (c) 2008,2009 Andy Green <andy@warmcat.com> + */ + +#include <linux/input/touchscreen/ts_filter.h> + +#include <linux/err.h> + +struct ts_filter_chain_configuration { + /* API to use. */ + const struct ts_filter_api *api; + /* Generic filter configuration. Different for each filter. */ + const struct ts_filter_configuration *config; +}; + +struct ts_filter_chain; + +#ifdef CONFIG_TOUCHSCREEN_FILTER + +/* + * Create a filter chain. It will allocate an array of + * null-terminated pointers to filters. On error it will return + * an error you can check with IS_ERR. + */ +extern struct ts_filter_chain *ts_filter_chain_create( + struct platform_device *pdev, + const struct ts_filter_chain_configuration conf[], + int count_coords); + +/* Destroy the chain. */ +extern void ts_filter_chain_destroy(struct ts_filter_chain *c); + +/* Clear the filter chain. */ +extern void ts_filter_chain_clear(struct ts_filter_chain *c); + +/* + * Try to get one point. Returns 0 if no points are available. + * coords will be used as temporal space, thus you supply a point + * using coords but you shouldn't rely on its value on return unless + * it returns a nonzero value that is not -1. + * If one of the filters find an error then this function will + * return -1. + */ +int ts_filter_chain_feed(struct ts_filter_chain *c, int *coords); + +#else /* !CONFIG_TOUCHSCREEN_FILTER */ +#define ts_filter_chain_create(pdev, config, count_coords) (NULL) +#define ts_filter_chain_destroy(c) do { } while (0) +#define ts_filter_chain_clear(c) do { } while (0) +#define ts_filter_chain_feed(c, coords) (1) +#endif + +#endif diff --git a/include/linux/input/touchscreen/ts_filter_group.h b/include/linux/input/touchscreen/ts_filter_group.h new file mode 100644 index 00000000000..2d727f30086 --- /dev/null +++ b/include/linux/input/touchscreen/ts_filter_group.h @@ -0,0 +1,36 @@ +#ifndef __TS_FILTER_GROUP_H__ +#define __TS_FILTER_GROUP_H__ + +#include <linux/input/touchscreen/ts_filter.h> + +/* + * Touchscreen group filter. + * + * Copyright (C) 2008,2009 by Openmoko, Inc. + * Author: Nelson Castillo <arhuaco@freaks-unidos.net> + * + */ + +struct ts_filter_group_configuration { + /* Size of the filter. */ + int length; + /* + * If two points are separated by this distance or less they + * are considered to be members of the same group. + */ + int close_enough; + /* Minimum allowed size for the biggest group in the sample set. */ + int threshold; + /* + * Number of times we try to get a group of points with at least + * threshold points. + */ + int attempts; + + /* Generic filter configuration. */ + struct ts_filter_configuration config; +}; + +extern const struct ts_filter_api ts_filter_group_api; + +#endif diff --git a/include/linux/input/touchscreen/ts_filter_linear.h b/include/linux/input/touchscreen/ts_filter_linear.h new file mode 100644 index 00000000000..6f5e8307456 --- /dev/null +++ b/include/linux/input/touchscreen/ts_filter_linear.h @@ -0,0 +1,31 @@ +#ifndef __TS_FILTER_LINEAR_H__ +#define __TS_FILTER_LINEAR_H__ + +#include <linux/input/touchscreen/ts_filter.h> +#include <linux/kobject.h> + +/* + * Touchscreen linear filter. + * + * Copyright (C) 2008,2009 by Openmoko, Inc. + * Author: Nelson Castillo <arhuaco@freaks-unidos.net> + * + */ + +#define TS_FILTER_LINEAR_NCONSTANTS 7 + +struct ts_filter_linear_configuration { + /* Calibration constants. */ + int constants[TS_FILTER_LINEAR_NCONSTANTS]; + /* First coordinate. */ + int coord0; + /* Second coordinate. */ + int coord1; + + /* Generic filter configuration. */ + struct ts_filter_configuration config; +}; + +extern const struct ts_filter_api ts_filter_linear_api; + +#endif diff --git a/include/linux/input/touchscreen/ts_filter_mean.h b/include/linux/input/touchscreen/ts_filter_mean.h new file mode 100644 index 00000000000..d4870413987 --- /dev/null +++ b/include/linux/input/touchscreen/ts_filter_mean.h @@ -0,0 +1,28 @@ +#ifndef __TS_FILTER_MEAN_H__ +#define __TS_FILTER_MEAN_H__ + +#include <linux/input/touchscreen/ts_filter.h> + +/* + * Touchscreen filter. + * + * mean + * + * (c) 2008,2009 + * Andy Green <andy@warmcat.com> + * Nelson Castillo <arhuaco@freaks-unidos.net> + */ + +/* Configuration for this filter. */ +struct ts_filter_mean_configuration { + /* Number of points for the mean. */ + int length; + + /* Generic filter configuration. */ + struct ts_filter_configuration config; +}; + +/* API functions for the mean filter */ +extern const struct ts_filter_api ts_filter_mean_api; + +#endif /* __TS_FILTER_MEAN_H__ */ diff --git a/include/linux/input/touchscreen/ts_filter_median.h b/include/linux/input/touchscreen/ts_filter_median.h new file mode 100644 index 00000000000..08051619531 --- /dev/null +++ b/include/linux/input/touchscreen/ts_filter_median.h @@ -0,0 +1,32 @@ +#ifndef __TS_FILTER_MEDIAN_H__ +#define __TS_FILTER_MEDIAN_H__ + +#include <linux/input/touchscreen/ts_filter.h> + +/* + * Touchscreen filter. + * + * median + * + * (c) 2008 Andy Green <andy@warmcat.com> + */ + +struct ts_filter_median_configuration { + /* Size of the filter. */ + int extent; + /* Precomputed midpoint. */ + int midpoint; + /* A reference value for us to check if we are going fast or slow. */ + int decimation_threshold; + /* How many points to replace if we're going fast. */ + int decimation_above; + /* How many points to replace if we're going slow. */ + int decimation_below; + + /* Generic configuration. */ + struct ts_filter_configuration config; +}; + +extern const struct ts_filter_api ts_filter_median_api; + +#endif diff --git a/include/linux/jbt6k74.h b/include/linux/jbt6k74.h new file mode 100644 index 00000000000..75488c41136 --- /dev/null +++ b/include/linux/jbt6k74.h @@ -0,0 +1,21 @@ +#ifndef __JBT6K74_H__ +#define __JBT6K74_H__ + +#include <linux/spi/spi.h> + +/* + * struct jbt6k74_platform_data - Platform data for jbt6k74 driver + * @probe_completed: Callback to be called when the driver has been + * successfully probed. + * @enable_pixel_clock: Callback to enable or disable the pixelclock of the + * gpu. + * @gpio_reset: Reset gpio pin number. + */ +struct jbt6k74_platform_data { + void (*probe_completed)(struct device *dev); + void (*enable_pixel_clock)(struct device *dev, int enable); + + int gpio_reset; +}; + +#endif diff --git a/include/linux/mfd/pcf50633/backlight.h b/include/linux/mfd/pcf50633/backlight.h new file mode 100644 index 00000000000..9d3646af056 --- /dev/null +++ b/include/linux/mfd/pcf50633/backlight.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2009, Lars-Peter Clausen <lars@metafoo.de> + * PCF50633 backlight device driver + * + * 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; either version 2 of the License, or (at your + * option) any later version. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef __LINUX_MFD_PCF50633_BACKLIGHT +#define __LINUX_MFD_PCF50633_BACKLIGHT + +/* +* @default_brightness: Brightness to be used after the driver has been + probed. Valid range 0-63. +* @default_brightness_limit: Brightness limit to be used after the driver has +* been probed. This is usfull when it is not known +* how much power is available for the backlight +* during probe. Valid range 0-63 +* @ramp_time: When changing the backlights brightness the change +* is not instant, instead it fades smooth from one +* state to another. This value specifies how long the +* fade should take. The lower the value the higher +* the fade time. Valid range 0-255 +*/ +struct pcf50633_bl_platform_data { + unsigned int default_brightness; + unsigned int default_brightness_limit; + uint8_t ramp_time; +}; + + +struct pcf50633; + +int pcf50633_bl_set_brightness_limit(struct pcf50633 *pcf, unsigned int limit); + +#endif + diff --git a/include/linux/mfd/pcf50633/core.h b/include/linux/mfd/pcf50633/core.h index 9aba7b779fb..42f9af553ed 100644 --- a/include/linux/mfd/pcf50633/core.h +++ b/include/linux/mfd/pcf50633/core.h @@ -18,6 +18,7 @@ #include <linux/regulator/driver.h> #include <linux/regulator/machine.h> #include <linux/power_supply.h> +#include <linux/mfd/pcf50633/backlight.h> struct pcf50633; @@ -29,7 +30,7 @@ struct pcf50633_platform_data { char **batteries; int num_batteries; - int charging_restart_interval; + int chg_ref_current_ma; /* Callbacks */ void (*probe_done)(struct pcf50633 *); @@ -38,10 +39,8 @@ struct pcf50633_platform_data { void (*force_shutdown)(struct pcf50633 *); u8 resumers[5]; -}; -struct pcf50633_subdev_pdata { - struct pcf50633 *pcf; + struct pcf50633_bl_platform_data *backlight_data; }; struct pcf50633_irq { @@ -151,6 +150,7 @@ struct pcf50633 { struct platform_device *mbc_pdev; struct platform_device *adc_pdev; struct platform_device *input_pdev; + struct platform_device *bl_pdev; struct platform_device *regulator_pdev[PCF50633_NUM_REGULATORS]; }; @@ -217,5 +217,9 @@ enum pcf50633_reg_int5 { #define PCF50633_REG_LEDCTL 0x2a #define PCF50633_REG_LEDDIM 0x2b -#endif +static inline struct pcf50633 *dev_to_pcf50633(struct device *dev) +{ + return dev_get_drvdata(dev); +} +#endif diff --git a/include/linux/mfd/pcf50633/mbc.h b/include/linux/mfd/pcf50633/mbc.h index 4119579acf2..df4f5fa88de 100644 --- a/include/linux/mfd/pcf50633/mbc.h +++ b/include/linux/mfd/pcf50633/mbc.h @@ -128,6 +128,7 @@ enum pcf50633_reg_mbcs3 { int pcf50633_mbc_usb_curlim_set(struct pcf50633 *pcf, int ma); int pcf50633_mbc_get_status(struct pcf50633 *); +int pcf50633_mbc_get_usb_online_status(struct pcf50633 *); #endif diff --git a/sound/soc/s3c24xx/Kconfig b/sound/soc/s3c24xx/Kconfig index 923428fc1ad..e8c13762a8f 100644 --- a/sound/soc/s3c24xx/Kconfig +++ b/sound/soc/s3c24xx/Kconfig @@ -31,22 +31,13 @@ config SND_S3C2443_SOC_AC97 select SND_SOC_AC97_BUS config SND_S3C24XX_SOC_NEO1973_WM8753 - tristate "SoC I2S Audio support for NEO1973 - WM8753" - depends on SND_S3C24XX_SOC && MACH_NEO1973_GTA01 + tristate "Audio support for Openmoko Neo1973 Smartphones (GTA01/GTA02)" + depends on SND_S3C24XX_SOC && (MACH_NEO1973_GTA01 || MACH_NEO1973_GTA02) select SND_S3C24XX_SOC_I2S select SND_SOC_WM8753 help - Say Y if you want to add support for SoC audio on smdk2440 - with the WM8753. - -config SND_S3C24XX_SOC_NEO1973_GTA02_WM8753 - tristate "Audio support for the Openmoko Neo FreeRunner (GTA02)" - depends on SND_S3C24XX_SOC && MACH_NEO1973_GTA02 - select SND_S3C24XX_SOC_I2S - select SND_SOC_WM8753 - help - This driver provides audio support for the Openmoko Neo FreeRunner - smartphone. + Say Y here to enable audio support for the Openmoko Neo1973 + Smartphones. config SND_S3C24XX_SOC_JIVE_WM8750 tristate "SoC I2S Audio support for Jive" diff --git a/sound/soc/s3c24xx/Makefile b/sound/soc/s3c24xx/Makefile index 99f5a7dd3fc..bcf1258a930 100644 --- a/sound/soc/s3c24xx/Makefile +++ b/sound/soc/s3c24xx/Makefile @@ -16,7 +16,6 @@ obj-$(CONFIG_SND_S3C_I2SV2_SOC) += snd-soc-s3c-i2s-v2.o # S3C24XX Machine Support snd-soc-jive-wm8750-objs := jive_wm8750.o snd-soc-neo1973-wm8753-objs := neo1973_wm8753.o -snd-soc-neo1973-gta02-wm8753-objs := neo1973_gta02_wm8753.o snd-soc-smdk2443-wm9710-objs := smdk2443_wm9710.o snd-soc-ln2440sbc-alc650-objs := ln2440sbc_alc650.o snd-soc-s3c24xx-uda134x-objs := s3c24xx_uda134x.o @@ -26,7 +25,6 @@ snd-soc-s3c24xx-simtec-tlv320aic23-objs := s3c24xx_simtec_tlv320aic23.o obj-$(CONFIG_SND_S3C24XX_SOC_JIVE_WM8750) += snd-soc-jive-wm8750.o obj-$(CONFIG_SND_S3C24XX_SOC_NEO1973_WM8753) += snd-soc-neo1973-wm8753.o -obj-$(CONFIG_SND_S3C24XX_SOC_NEO1973_GTA02_WM8753) += snd-soc-neo1973-gta02-wm8753.o obj-$(CONFIG_SND_S3C24XX_SOC_SMDK2443_WM9710) += snd-soc-smdk2443-wm9710.o obj-$(CONFIG_SND_S3C24XX_SOC_LN2440SBC_ALC650) += snd-soc-ln2440sbc-alc650.o obj-$(CONFIG_SND_S3C24XX_SOC_S3C24XX_UDA134X) += snd-soc-s3c24xx-uda134x.o diff --git a/sound/soc/s3c24xx/neo1973_gta02_wm8753.c b/sound/soc/s3c24xx/neo1973_gta02_wm8753.c deleted file mode 100644 index 0c52e36ddd8..00000000000 --- a/sound/soc/s3c24xx/neo1973_gta02_wm8753.c +++ /dev/null @@ -1,498 +0,0 @@ -/* - * neo1973_gta02_wm8753.c -- SoC audio for Openmoko Freerunner(GTA02) - * - * Copyright 2007 Openmoko Inc - * Author: Graeme Gregory <graeme@openmoko.org> - * Copyright 2007 Wolfson Microelectronics PLC. - * Author: Graeme Gregory <linux@wolfsonmicro.com> - * Copyright 2009 Wolfson Microelectronics - * - * 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; either version 2 of the License, or (at your - * option) any later version. - */ - -#include <linux/module.h> -#include <linux/moduleparam.h> -#include <linux/timer.h> -#include <linux/interrupt.h> -#include <linux/platform_device.h> -#include <linux/gpio.h> -#include <sound/core.h> -#include <sound/pcm.h> -#include <sound/soc.h> -#include <sound/soc-dapm.h> - -#include <asm/mach-types.h> - -#include <plat/regs-iis.h> - -#include <mach/regs-clock.h> -#include <asm/io.h> -#include <mach/gta02.h> -#include "../codecs/wm8753.h" -#include "s3c24xx-pcm.h" -#include "s3c24xx-i2s.h" - -static struct snd_soc_card neo1973_gta02; - -static int neo1973_gta02_hifi_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; - struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; - unsigned int pll_out = 0, bclk = 0; - int ret = 0; - unsigned long iis_clkrate; - - iis_clkrate = s3c24xx_i2s_get_clockrate(); - - switch (params_rate(params)) { - case 8000: - case 16000: - pll_out = 12288000; - break; - case 48000: - bclk = WM8753_BCLK_DIV_4; - pll_out = 12288000; - break; - case 96000: - bclk = WM8753_BCLK_DIV_2; - pll_out = 12288000; - break; - case 11025: - bclk = WM8753_BCLK_DIV_16; - pll_out = 11289600; - break; - case 22050: - bclk = WM8753_BCLK_DIV_8; - pll_out = 11289600; - break; - case 44100: - bclk = WM8753_BCLK_DIV_4; - pll_out = 11289600; - break; - case 88200: - bclk = WM8753_BCLK_DIV_2; - pll_out = 11289600; - break; - } - - /* set codec DAI configuration */ - ret = snd_soc_dai_set_fmt(codec_dai, - SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBM_CFM); - if (ret < 0) - return ret; - - /* set cpu DAI configuration */ - ret = snd_soc_dai_set_fmt(cpu_dai, - SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBM_CFM); - if (ret < 0) - return ret; - - /* set the codec system clock for DAC and ADC */ - ret = snd_soc_dai_set_sysclk(codec_dai, WM8753_MCLK, pll_out, - SND_SOC_CLOCK_IN); - if (ret < 0) - return ret; - - /* set MCLK division for sample rate */ - ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK, - S3C2410_IISMOD_32FS); - if (ret < 0) - return ret; - - /* set codec BCLK division for sample rate */ - ret = snd_soc_dai_set_clkdiv(codec_dai, - WM8753_BCLKDIV, bclk); - if (ret < 0) - return ret; - - /* set prescaler division for sample rate */ - ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER, - S3C24XX_PRESCALE(4, 4)); - if (ret < 0) - return ret; - - /* codec PLL input is PCLK/4 */ - ret = snd_soc_dai_set_pll(codec_dai, WM8753_PLL1, - iis_clkrate / 4, pll_out); - if (ret < 0) - return ret; - - return 0; -} - -static int neo1973_gta02_hifi_hw_free(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; - - /* disable the PLL */ - return snd_soc_dai_set_pll(codec_dai, WM8753_PLL1, 0, 0); -} - -/* - * Neo1973 WM8753 HiFi DAI opserations. - */ -static struct snd_soc_ops neo1973_gta02_hifi_ops = { - .hw_params = neo1973_gta02_hifi_hw_params, - .hw_free = neo1973_gta02_hifi_hw_free, -}; - -static int neo1973_gta02_voice_hw_params( - struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; - unsigned int pcmdiv = 0; - int ret = 0; - unsigned long iis_clkrate; - - iis_clkrate = s3c24xx_i2s_get_clockrate(); - - if (params_rate(params) != 8000) - return -EINVAL; - if (params_channels(params) != 1) - return -EINVAL; - - pcmdiv = WM8753_PCM_DIV_6; /* 2.048 MHz */ - - /* todo: gg check mode (DSP_B) against CSR datasheet */ - /* set codec DAI configuration */ - ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_B | - SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); - if (ret < 0) - return ret; - - /* set the codec system clock for DAC and ADC */ - ret = snd_soc_dai_set_sysclk(codec_dai, WM8753_PCMCLK, - 12288000, SND_SOC_CLOCK_IN); - if (ret < 0) - return ret; - - /* set codec PCM division for sample rate */ - ret = snd_soc_dai_set_clkdiv(codec_dai, WM8753_PCMDIV, - pcmdiv); - if (ret < 0) - return ret; - - /* configue and enable PLL for 12.288MHz output */ - ret = snd_soc_dai_set_pll(codec_dai, WM8753_PLL2, - iis_clkrate / 4, 12288000); - if (ret < 0) - return ret; - - return 0; -} - -static int neo1973_gta02_voice_hw_free(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; - - /* disable the PLL */ - return snd_soc_dai_set_pll(codec_dai, WM8753_PLL2, 0, 0); -} - -static struct snd_soc_ops neo1973_gta02_voice_ops = { - .hw_params = neo1973_gta02_voice_hw_params, - .hw_free = neo1973_gta02_voice_hw_free, -}; - -#define LM4853_AMP 1 -#define LM4853_SPK 2 - -static u8 lm4853_state; - -/* This has no effect, it exists only to maintain compatibility with - * existing ALSA state files. - */ -static int lm4853_set_state(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - int val = ucontrol->value.integer.value[0]; - - if (val) - lm4853_state |= LM4853_AMP; - else - lm4853_state &= ~LM4853_AMP; - - return 0; -} - -static int lm4853_get_state(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - ucontrol->value.integer.value[0] = lm4853_state & LM4853_AMP; - - return 0; -} - -static int lm4853_set_spk(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - int val = ucontrol->value.integer.value[0]; - - if (val) { - lm4853_state |= LM4853_SPK; - gpio_set_value(GTA02_GPIO_HP_IN, 0); - } else { - lm4853_state &= ~LM4853_SPK; - gpio_set_value(GTA02_GPIO_HP_IN, 1); - } - - return 0; -} - -static int lm4853_get_spk(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - ucontrol->value.integer.value[0] = (lm4853_state & LM4853_SPK) >> 1; - - return 0; -} - -static int lm4853_event(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *k, - int event) -{ - gpio_set_value(GTA02_GPIO_AMP_SHUT, SND_SOC_DAPM_EVENT_OFF(value)); - - return 0; -} - -static const struct snd_soc_dapm_widget wm8753_dapm_widgets[] = { - SND_SOC_DAPM_SPK("Stereo Out", lm4853_event), - SND_SOC_DAPM_LINE("GSM Line Out", NULL), - SND_SOC_DAPM_LINE("GSM Line In", NULL), - SND_SOC_DAPM_MIC("Headset Mic", NULL), - SND_SOC_DAPM_MIC("Handset Mic", NULL), - SND_SOC_DAPM_SPK("Handset Spk", NULL), -}; - - -/* example machine audio_mapnections */ -static const struct snd_soc_dapm_route audio_map[] = { - - /* Connections to the lm4853 amp */ - {"Stereo Out", NULL, "LOUT1"}, - {"Stereo Out", NULL, "ROUT1"}, - - /* Connections to the GSM Module */ - {"GSM Line Out", NULL, "MONO1"}, - {"GSM Line Out", NULL, "MONO2"}, - {"RXP", NULL, "GSM Line In"}, - {"RXN", NULL, "GSM Line In"}, - - /* Connections to Headset */ - {"MIC1", NULL, "Mic Bias"}, - {"Mic Bias", NULL, "Headset Mic"}, - - /* Call Mic */ - {"MIC2", NULL, "Mic Bias"}, - {"MIC2N", NULL, "Mic Bias"}, - {"Mic Bias", NULL, "Handset Mic"}, - - /* Call Speaker */ - {"Handset Spk", NULL, "LOUT2"}, - {"Handset Spk", NULL, "ROUT2"}, - - /* Connect the ALC pins */ - {"ACIN", NULL, "ACOP"}, -}; - -static const struct snd_kcontrol_new wm8753_neo1973_gta02_controls[] = { - SOC_DAPM_PIN_SWITCH("Stereo Out"), - SOC_DAPM_PIN_SWITCH("GSM Line Out"), - SOC_DAPM_PIN_SWITCH("GSM Line In"), - SOC_DAPM_PIN_SWITCH("Headset Mic"), - SOC_DAPM_PIN_SWITCH("Handset Mic"), - SOC_DAPM_PIN_SWITCH("Handset Spk"), - - /* This has no effect, it exists only to maintain compatibility with - * existing ALSA state files. - */ - SOC_SINGLE_EXT("Amp State Switch", 6, 0, 1, 0, - lm4853_get_state, - lm4853_set_state), - SOC_SINGLE_EXT("Amp Spk Switch", 7, 0, 1, 0, - lm4853_get_spk, - lm4853_set_spk), -}; - -/* - * This is an example machine initialisation for a wm8753 connected to a - * neo1973 GTA02. - */ -static int neo1973_gta02_wm8753_init(struct snd_soc_codec *codec) -{ - int err; - - /* set up NC codec pins */ - snd_soc_dapm_nc_pin(codec, "OUT3"); - snd_soc_dapm_nc_pin(codec, "OUT4"); - snd_soc_dapm_nc_pin(codec, "LINE1"); - snd_soc_dapm_nc_pin(codec, "LINE2"); - - /* Add neo1973 gta02 specific widgets */ - snd_soc_dapm_new_controls(codec, wm8753_dapm_widgets, - ARRAY_SIZE(wm8753_dapm_widgets)); - - /* add neo1973 gta02 specific controls */ - err = snd_soc_add_controls(codec, wm8753_neo1973_gta02_controls, - ARRAY_SIZE(wm8753_neo1973_gta02_controls)); - - if (err < 0) - return err; - - /* set up neo1973 gta02 specific audio path audio_map */ - snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); - - /* set endpoints to default off mode */ - snd_soc_dapm_disable_pin(codec, "Stereo Out"); - snd_soc_dapm_disable_pin(codec, "GSM Line Out"); - snd_soc_dapm_disable_pin(codec, "GSM Line In"); - snd_soc_dapm_disable_pin(codec, "Headset Mic"); - snd_soc_dapm_disable_pin(codec, "Handset Mic"); - snd_soc_dapm_disable_pin(codec, "Handset Spk"); - - snd_soc_dapm_sync(codec); - - return 0; -} - -/* - * BT Codec DAI - */ -static struct snd_soc_dai bt_dai = { - .name = "Bluetooth", - .id = 0, - .playback = { - .channels_min = 1, - .channels_max = 1, - .rates = SNDRV_PCM_RATE_8000, - .formats = SNDRV_PCM_FMTBIT_S16_LE,}, - .capture = { - .channels_min = 1, - .channels_max = 1, - .rates = SNDRV_PCM_RATE_8000, - .formats = SNDRV_PCM_FMTBIT_S16_LE,}, -}; - -static struct snd_soc_dai_link neo1973_gta02_dai[] = { -{ /* Hifi Playback - for similatious use with voice below */ - .name = "WM8753", - .stream_name = "WM8753 HiFi", - .cpu_dai = &s3c24xx_i2s_dai, - .codec_dai = &wm8753_dai[WM8753_DAI_HIFI], - .init = neo1973_gta02_wm8753_init, - .ops = &neo1973_gta02_hifi_ops, -}, -{ /* Voice via BT */ - .name = "Bluetooth", - .stream_name = "Voice", - .cpu_dai = &bt_dai, - .codec_dai = &wm8753_dai[WM8753_DAI_VOICE], - .ops = &neo1973_gta02_voice_ops, -}, -}; - -static struct snd_soc_card neo1973_gta02 = { - .name = "neo1973-gta02", - .platform = &s3c24xx_soc_platform, - .dai_link = neo1973_gta02_dai, - .num_links = ARRAY_SIZE(neo1973_gta02_dai), -}; - -static struct snd_soc_device neo1973_gta02_snd_devdata = { - .card = &neo1973_gta02, - .codec_dev = &soc_codec_dev_wm8753, -}; - -static struct platform_device *neo1973_gta02_snd_device; - -static int __init neo1973_gta02_init(void) -{ - int ret; - - if (!machine_is_neo1973_gta02()) { - printk(KERN_INFO - "Only GTA02 is supported by this ASoC driver\n"); - return -ENODEV; - } - - /* register bluetooth DAI here */ - ret = snd_soc_register_dai(&bt_dai); - if (ret) - return ret; - - neo1973_gta02_snd_device = platform_device_alloc("soc-audio", -1); - if (!neo1973_gta02_snd_device) - return -ENOMEM; - - platform_set_drvdata(neo1973_gta02_snd_device, - &neo1973_gta02_snd_devdata); - neo1973_gta02_snd_devdata.dev = &neo1973_gta02_snd_device->dev; - ret = platform_device_add(neo1973_gta02_snd_device); - - if (ret) { - platform_device_put(neo1973_gta02_snd_device); - return ret; - } - - /* Initialise GPIOs used by amp */ - ret = gpio_request(GTA02_GPIO_HP_IN, "GTA02_HP_IN"); - if (ret) { - pr_err("gta02_wm8753: Failed to register GPIO %d\n", GTA02_GPIO_HP_IN); - goto err_unregister_device; - } - - ret = gpio_direction_output(GTA02_GPIO_AMP_HP_IN, 1); - if (ret) { - pr_err("gta02_wm8753: Failed to configure GPIO %d\n", GTA02_GPIO_HP_IN); - goto err_free_gpio_hp_in; - } - - ret = gpio_request(GTA02_GPIO_AMP_SHUT, "GTA02_AMP_SHUT"); - if (ret) { - pr_err("gta02_wm8753: Failed to register GPIO %d\n", GTA02_GPIO_AMP_SHUT); - goto err_free_gpio_hp_in; - } - - ret = gpio_direction_output(GTA02_GPIO_AMP_SHUT, 1); - if (ret) { - pr_err("gta02_wm8753: Failed to configure GPIO %d\n", GTA02_GPIO_AMP_SHUT); - goto err_free_gpio_amp_shut; - } - - return 0; - -err_free_gpio_amp_shut: - gpio_free(GTA02_GPIO_AMP_SHUT); -err_free_gpio_hp_in: - gpio_free(GTA02_GPIO_HP_IN); -err_unregister_device: - platform_device_unregister(neo1973_gta02_snd_device); - return ret; -} -module_init(neo1973_gta02_init); - -static void __exit neo1973_gta02_exit(void) -{ - snd_soc_unregister_dai(&bt_dai); - platform_device_unregister(neo1973_gta02_snd_device); - gpio_free(GTA02_GPIO_HP_IN); - gpio_free(GTA02_GPIO_AMP_SHUT); -} -module_exit(neo1973_gta02_exit); - -/* Module information */ -MODULE_AUTHOR("Graeme Gregory, graeme@openmoko.org"); -MODULE_DESCRIPTION("ALSA SoC WM8753 Neo1973 GTA02"); -MODULE_LICENSE("GPL"); diff --git a/sound/soc/s3c24xx/neo1973_wm8753.c b/sound/soc/s3c24xx/neo1973_wm8753.c index 906709e6dd5..5cc1ef2c8fe 100644 --- a/sound/soc/s3c24xx/neo1973_wm8753.c +++ b/sound/soc/s3c24xx/neo1973_wm8753.c @@ -1,15 +1,16 @@ /* - * neo1973_wm8753.c -- SoC audio for Neo1973 + * neo1973_gta02_wm8753.c -- SoC audio for Openmoko Freerunner(GTA02) * + * Copyright 2007 Openmoko Inc + * Author: Graeme Gregory <graeme@openmoko.org> * Copyright 2007 Wolfson Microelectronics PLC. - * Author: Graeme Gregory - * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com + * Author: Graeme Gregory <linux@wolfsonmicro.com> + * Copyright 2009 Wolfson Microelectronics * * 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; either version 2 of the License, or (at your * option) any later version. - * */ #include <linux/module.h> @@ -17,7 +18,7 @@ #include <linux/timer.h> #include <linux/interrupt.h> #include <linux/platform_device.h> -#include <linux/i2c.h> +#include <linux/gpio.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/soc.h> @@ -25,34 +26,161 @@ #include <sound/tlv.h> #include <asm/mach-types.h> -#include <asm/hardware/scoop.h> -#include <mach/regs-clock.h> -#include <mach/regs-gpio.h> -#include <mach/hardware.h> -#include <plat/audio.h> -#include <linux/io.h> -#include <mach/spi-gpio.h> #include <plat/regs-iis.h> +#include <mach/regs-clock.h> +#include <mach/gta02.h> #include "../codecs/wm8753.h" -#include "lm4857.h" #include "s3c24xx-pcm.h" #include "s3c24xx-i2s.h" -/* define the scenarios */ -#define NEO_AUDIO_OFF 0 -#define NEO_GSM_CALL_AUDIO_HANDSET 1 -#define NEO_GSM_CALL_AUDIO_HEADSET 2 -#define NEO_GSM_CALL_AUDIO_BLUETOOTH 3 -#define NEO_STEREO_TO_SPEAKERS 4 -#define NEO_STEREO_TO_HEADPHONES 5 -#define NEO_CAPTURE_HANDSET 6 -#define NEO_CAPTURE_HEADSET 7 -#define NEO_CAPTURE_BLUETOOTH 8 +#include "lm4857.h" +#include <linux/i2c.h> + +#ifdef CONFIG_MACH_NEO1973_GTA01 + +static struct lm4857 { + struct i2c_client *i2c; + uint8_t regs[4]; + uint8_t state; +} lm4857 = { + .regs = {0x00, 0x40, 0x80, 0xC0}, +}; + +static void lm4857_write_regs(void) +{ + if (!lm4857.i2c) + return; + + if (i2c_master_send(lm4857.i2c, lm4857.regs, 4) != 4) + printk(KERN_ERR "lm4857: i2c write failed\n"); +} -static struct snd_soc_card neo1973; -static struct i2c_client *i2c; +static int lm4857_get_reg(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int reg = mc->reg; + int shift = mc->shift; + int mask = mc->max; + + ucontrol->value.integer.value[0] = (lm4857.regs[reg] >> shift) & mask; + return 0; +} + +static int lm4857_set_reg(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int reg = mc->reg; + int shift = mc->shift; + int mask = mc->max; + + if (((lm4857.regs[reg] >> shift) & mask) == + ucontrol->value.integer.value[0]) + return 0; + + lm4857.regs[reg] &= ~(mask << shift); + lm4857.regs[reg] |= ucontrol->value.integer.value[0] << shift; + lm4857_write_regs(); + return 1; +} + +static int lm4857_get_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + uint8_t value = lm4857.regs[LM4857_CTRL] & 0x0F; + + if (value) + value -= 5; + + ucontrol->value.integer.value[0] = value; + return 0; +} + +static int lm4857_set_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + uint8_t value = ucontrol->value.integer.value[0]; + + if (value) + value += 5; + + if ((lm4857.regs[LM4857_CTRL] & 0x0F) == value) + return 0; + + lm4857.regs[LM4857_CTRL] &= 0xF0; + lm4857.regs[LM4857_CTRL] |= value; + lm4857_write_regs(); + return 1; +} + +static int lm4857_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + if (lm4857.i2c) + return -EBUSY; + + lm4857.i2c = client; + + lm4857_write_regs(); + return 0; +} + +static int lm4857_i2c_remove(struct i2c_client *client) +{ + lm4857.i2c = NULL; + + return 0; +} + +static int lm4857_suspend(struct i2c_client *dev, pm_message_t state) +{ + lm4857.state = lm4857.regs[LM4857_CTRL] & 0xf; + if (lm4857.state) { + lm4857.regs[LM4857_CTRL] &= 0xf0; + lm4857_write_regs(); + } + return 0; +} + +static int lm4857_resume(struct i2c_client *dev) +{ + if (lm4857.state) { + lm4857.regs[LM4857_CTRL] |= (lm4857.state & 0x0f); + lm4857_write_regs(); + } + return 0; +} + +static void lm4857_shutdown(struct i2c_client *dev) +{ + lm4857.regs[LM4857_CTRL] &= 0xf0; + lm4857_write_regs(); +} + +static const struct i2c_device_id lm4857_i2c_id[] = { + { "neo1973_lm4857", 0 }, + { } +}; + +static struct i2c_driver lm4857_i2c_driver = { + .driver = { + .name = "LM4857 I2C Amp", + .owner = THIS_MODULE, + }, + .suspend = lm4857_suspend, + .resume = lm4857_resume, + .shutdown = lm4857_shutdown, + .probe = lm4857_i2c_probe, + .remove = lm4857_i2c_remove, + .id_table = lm4857_i2c_id, +}; + +#endif static int neo1973_hifi_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) @@ -64,8 +192,6 @@ static int neo1973_hifi_hw_params(struct snd_pcm_substream *substream, int ret = 0; unsigned long iis_clkrate; - pr_debug("Entered %s\n", __func__); - iis_clkrate = s3c24xx_i2s_get_clockrate(); switch (params_rate(params)) { @@ -126,7 +252,8 @@ static int neo1973_hifi_hw_params(struct snd_pcm_substream *substream, return ret; /* set codec BCLK division for sample rate */ - ret = snd_soc_dai_set_clkdiv(codec_dai, WM8753_BCLKDIV, bclk); + ret = snd_soc_dai_set_clkdiv(codec_dai, + WM8753_BCLKDIV, bclk); if (ret < 0) return ret; @@ -150,8 +277,6 @@ static int neo1973_hifi_hw_free(struct snd_pcm_substream *substream) struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; - pr_debug("Entered %s\n", __func__); - /* disable the PLL */ return snd_soc_dai_set_pll(codec_dai, WM8753_PLL1, 0, 0); } @@ -164,7 +289,8 @@ static struct snd_soc_ops neo1973_hifi_ops = { .hw_free = neo1973_hifi_hw_free, }; -static int neo1973_voice_hw_params(struct snd_pcm_substream *substream, +static int neo1973_voice_hw_params( + struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { struct snd_soc_pcm_runtime *rtd = substream->private_data; @@ -173,8 +299,6 @@ static int neo1973_voice_hw_params(struct snd_pcm_substream *substream, int ret = 0; unsigned long iis_clkrate; - pr_debug("Entered %s\n", __func__); - iis_clkrate = s3c24xx_i2s_get_clockrate(); if (params_rate(params) != 8000) @@ -192,13 +316,14 @@ static int neo1973_voice_hw_params(struct snd_pcm_substream *substream, return ret; /* set the codec system clock for DAC and ADC */ - ret = snd_soc_dai_set_sysclk(codec_dai, WM8753_PCMCLK, 12288000, - SND_SOC_CLOCK_IN); + ret = snd_soc_dai_set_sysclk(codec_dai, WM8753_PCMCLK, + 12288000, SND_SOC_CLOCK_IN); if (ret < 0) return ret; /* set codec PCM division for sample rate */ - ret = snd_soc_dai_set_clkdiv(codec_dai, WM8753_PCMDIV, pcmdiv); + ret = snd_soc_dai_set_clkdiv(codec_dai, WM8753_PCMDIV, + pcmdiv); if (ret < 0) return ret; @@ -216,8 +341,6 @@ static int neo1973_voice_hw_free(struct snd_pcm_substream *substream) struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; - pr_debug("Entered %s\n", __func__); - /* disable the PLL */ return snd_soc_dai_set_pll(codec_dai, WM8753_PLL2, 0, 0); } @@ -227,202 +350,20 @@ static struct snd_soc_ops neo1973_voice_ops = { .hw_free = neo1973_voice_hw_free, }; -static int neo1973_scenario; - -static int neo1973_get_scenario(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - ucontrol->value.integer.value[0] = neo1973_scenario; - return 0; -} - -static int set_scenario_endpoints(struct snd_soc_codec *codec, int scenario) -{ - pr_debug("Entered %s\n", __func__); - - switch (neo1973_scenario) { - case NEO_AUDIO_OFF: - snd_soc_dapm_disable_pin(codec, "Audio Out"); - snd_soc_dapm_disable_pin(codec, "GSM Line Out"); - snd_soc_dapm_disable_pin(codec, "GSM Line In"); - snd_soc_dapm_disable_pin(codec, "Headset Mic"); - snd_soc_dapm_disable_pin(codec, "Call Mic"); - break; - case NEO_GSM_CALL_AUDIO_HANDSET: - snd_soc_dapm_enable_pin(codec, "Audio Out"); - snd_soc_dapm_enable_pin(codec, "GSM Line Out"); - snd_soc_dapm_enable_pin(codec, "GSM Line In"); - snd_soc_dapm_disable_pin(codec, "Headset Mic"); - snd_soc_dapm_enable_pin(codec, "Call Mic"); - break; - case NEO_GSM_CALL_AUDIO_HEADSET: - snd_soc_dapm_enable_pin(codec, "Audio Out"); - snd_soc_dapm_enable_pin(codec, "GSM Line Out"); - snd_soc_dapm_enable_pin(codec, "GSM Line In"); - snd_soc_dapm_enable_pin(codec, "Headset Mic"); - snd_soc_dapm_disable_pin(codec, "Call Mic"); - break; - case NEO_GSM_CALL_AUDIO_BLUETOOTH: - snd_soc_dapm_disable_pin(codec, "Audio Out"); - snd_soc_dapm_enable_pin(codec, "GSM Line Out"); - snd_soc_dapm_enable_pin(codec, "GSM Line In"); - snd_soc_dapm_disable_pin(codec, "Headset Mic"); - snd_soc_dapm_disable_pin(codec, "Call Mic"); - break; - case NEO_STEREO_TO_SPEAKERS: - snd_soc_dapm_enable_pin(codec, "Audio Out"); - snd_soc_dapm_disable_pin(codec, "GSM Line Out"); - snd_soc_dapm_disable_pin(codec, "GSM Line In"); - snd_soc_dapm_disable_pin(codec, "Headset Mic"); - snd_soc_dapm_disable_pin(codec, "Call Mic"); - break; - case NEO_STEREO_TO_HEADPHONES: - snd_soc_dapm_enable_pin(codec, "Audio Out"); - snd_soc_dapm_disable_pin(codec, "GSM Line Out"); - snd_soc_dapm_disable_pin(codec, "GSM Line In"); - snd_soc_dapm_disable_pin(codec, "Headset Mic"); - snd_soc_dapm_disable_pin(codec, "Call Mic"); - break; - case NEO_CAPTURE_HANDSET: - snd_soc_dapm_disable_pin(codec, "Audio Out"); - snd_soc_dapm_disable_pin(codec, "GSM Line Out"); - snd_soc_dapm_disable_pin(codec, "GSM Line In"); - snd_soc_dapm_disable_pin(codec, "Headset Mic"); - snd_soc_dapm_enable_pin(codec, "Call Mic"); - break; - case NEO_CAPTURE_HEADSET: - snd_soc_dapm_disable_pin(codec, "Audio Out"); - snd_soc_dapm_disable_pin(codec, "GSM Line Out"); - snd_soc_dapm_disable_pin(codec, "GSM Line In"); - snd_soc_dapm_enable_pin(codec, "Headset Mic"); - snd_soc_dapm_disable_pin(codec, "Call Mic"); - break; - case NEO_CAPTURE_BLUETOOTH: - snd_soc_dapm_disable_pin(codec, "Audio Out"); - snd_soc_dapm_disable_pin(codec, "GSM Line Out"); - snd_soc_dapm_disable_pin(codec, "GSM Line In"); - snd_soc_dapm_disable_pin(codec, "Headset Mic"); - snd_soc_dapm_disable_pin(codec, "Call Mic"); - break; - default: - snd_soc_dapm_disable_pin(codec, "Audio Out"); - snd_soc_dapm_disable_pin(codec, "GSM Line Out"); - snd_soc_dapm_disable_pin(codec, "GSM Line In"); - snd_soc_dapm_disable_pin(codec, "Headset Mic"); - snd_soc_dapm_disable_pin(codec, "Call Mic"); - } - - snd_soc_dapm_sync(codec); - - return 0; -} - -static int neo1973_set_scenario(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - - pr_debug("Entered %s\n", __func__); - - if (neo1973_scenario == ucontrol->value.integer.value[0]) - return 0; - - neo1973_scenario = ucontrol->value.integer.value[0]; - set_scenario_endpoints(codec, neo1973_scenario); - return 1; -} - -static u8 lm4857_regs[4] = {0x00, 0x40, 0x80, 0xC0}; - -static void lm4857_write_regs(void) -{ - pr_debug("Entered %s\n", __func__); - - if (i2c_master_send(i2c, lm4857_regs, 4) != 4) - printk(KERN_ERR "lm4857: i2c write failed\n"); -} - -static int lm4857_get_reg(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct soc_mixer_control *mc = - (struct soc_mixer_control *)kcontrol->private_value; - int reg = mc->reg; - int shift = mc->shift; - int mask = mc->max; - - pr_debug("Entered %s\n", __func__); - - ucontrol->value.integer.value[0] = (lm4857_regs[reg] >> shift) & mask; - return 0; -} - -static int lm4857_set_reg(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct soc_mixer_control *mc = - (struct soc_mixer_control *)kcontrol->private_value; - int reg = mc->reg; - int shift = mc->shift; - int mask = mc->max; - - if (((lm4857_regs[reg] >> shift) & mask) == - ucontrol->value.integer.value[0]) - return 0; - - lm4857_regs[reg] &= ~(mask << shift); - lm4857_regs[reg] |= ucontrol->value.integer.value[0] << shift; - lm4857_write_regs(); - return 1; -} - -static int lm4857_get_mode(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - u8 value = lm4857_regs[LM4857_CTRL] & 0x0F; - - pr_debug("Entered %s\n", __func__); - - if (value) - value -= 5; - - ucontrol->value.integer.value[0] = value; - return 0; -} - -static int lm4857_set_mode(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - u8 value = ucontrol->value.integer.value[0]; - - pr_debug("Entered %s\n", __func__); - - if (value) - value += 5; - - if ((lm4857_regs[LM4857_CTRL] & 0x0F) == value) - return 0; - - lm4857_regs[LM4857_CTRL] &= 0xF0; - lm4857_regs[LM4857_CTRL] |= value; - lm4857_write_regs(); - return 1; -} +/* Shared routes and controls */ static const struct snd_soc_dapm_widget wm8753_dapm_widgets[] = { - SND_SOC_DAPM_LINE("Audio Out", NULL), SND_SOC_DAPM_LINE("GSM Line Out", NULL), SND_SOC_DAPM_LINE("GSM Line In", NULL), SND_SOC_DAPM_MIC("Headset Mic", NULL), - SND_SOC_DAPM_MIC("Call Mic", NULL), + SND_SOC_DAPM_MIC("Handset Mic", NULL), }; +static const struct snd_soc_dapm_route audio_map[] = { -static const struct snd_soc_dapm_route dapm_routes[] = { - - /* Connections to the lm4857 amp */ - {"Audio Out", NULL, "LOUT1"}, - {"Audio Out", NULL, "ROUT1"}, + /* Connections to the lm4853 amp */ + {"Stereo Out", NULL, "LOUT1"}, + {"Stereo Out", NULL, "ROUT1"}, /* Connections to the GSM Module */ {"GSM Line Out", NULL, "MONO1"}, @@ -437,12 +378,24 @@ static const struct snd_soc_dapm_route dapm_routes[] = { /* Call Mic */ {"MIC2", NULL, "Mic Bias"}, {"MIC2N", NULL, "Mic Bias"}, - {"Mic Bias", NULL, "Call Mic"}, + {"Mic Bias", NULL, "Handset Mic"}, /* Connect the ALC pins */ {"ACIN", NULL, "ACOP"}, }; +static const struct snd_kcontrol_new wm8753_neo1973_controls[] = { + SOC_DAPM_PIN_SWITCH("Stereo Out"), + SOC_DAPM_PIN_SWITCH("GSM Line Out"), + SOC_DAPM_PIN_SWITCH("GSM Line In"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Handset Mic"), +}; + +/* GTA01 specific controlls */ + +#ifdef CONFIG_MACH_NEO1973_GTA01 + static const char *lm4857_mode[] = { "Off", "Call Speaker", @@ -455,26 +408,10 @@ static const struct soc_enum lm4857_mode_enum[] = { SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(lm4857_mode), lm4857_mode), }; -static const char *neo_scenarios[] = { - "Off", - "GSM Handset", - "GSM Headset", - "GSM Bluetooth", - "Speakers", - "Headphones", - "Capture Handset", - "Capture Headset", - "Capture Bluetooth" -}; - -static const struct soc_enum neo_scenario_enum[] = { - SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(neo_scenarios), neo_scenarios), -}; - static const DECLARE_TLV_DB_SCALE(stereo_tlv, -4050, 150, 0); static const DECLARE_TLV_DB_SCALE(mono_tlv, -3450, 150, 0); -static const struct snd_kcontrol_new wm8753_neo1973_controls[] = { +static const struct snd_kcontrol_new wm8753_neo1973_gta01_controls[] = { SOC_SINGLE_EXT_TLV("Amp Left Playback Volume", LM4857_LVOL, 0, 31, 0, lm4857_get_reg, lm4857_set_reg, stereo_tlv), SOC_SINGLE_EXT_TLV("Amp Right Playback Volume", LM4857_RVOL, 0, 31, 0, @@ -483,8 +420,6 @@ static const struct snd_kcontrol_new wm8753_neo1973_controls[] = { lm4857_get_reg, lm4857_set_reg, mono_tlv), SOC_ENUM_EXT("Amp Mode", lm4857_mode_enum[0], lm4857_get_mode, lm4857_set_mode), - SOC_ENUM_EXT("Neo Mode", neo_scenario_enum[0], - neo1973_get_scenario, neo1973_set_scenario), SOC_SINGLE_EXT("Amp Spk 3D Playback Switch", LM4857_LVOL, 5, 1, 0, lm4857_get_reg, lm4857_set_reg), SOC_SINGLE_EXT("Amp HP 3d Playback Switch", LM4857_RVOL, 5, 1, 0, @@ -495,20 +430,98 @@ static const struct snd_kcontrol_new wm8753_neo1973_controls[] = { lm4857_get_reg, lm4857_set_reg), }; -/* - * This is an example machine initialisation for a wm8753 connected to a - * neo1973 II. It is missing logic to detect hp/mic insertions and logic - * to re-route the audio in such an event. - */ +static const struct snd_soc_dapm_widget wm8753_dapm_widgets_gta01[] = { + SND_SOC_DAPM_SPK("Stereo Out", NULL), +}; + +#else +static const struct snd_kcontrol_new wm8753_neo1973_gta01_controls[] = {}; +static const struct snd_soc_dapm_widget wm8753_dapm_widgets_gta01[] = {}; +#endif + +/* GTA02 specific routes and controlls */ + +#ifdef CONFIG_MACH_NEO1973_GTA02 + +static int lm4853_set_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int val = ucontrol->value.integer.value[0]; + + gpio_set_value(GTA02_GPIO_HP_IN, !val); + + return 0; +} + +static int lm4853_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = !gpio_get_value(GTA02_GPIO_HP_IN); + + return 0; +} + +static int lm4853_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, + int event) +{ + gpio_set_value(GTA02_GPIO_AMP_SHUT, !SND_SOC_DAPM_EVENT_ON(event)); + + return 0; +} + +static const struct snd_soc_dapm_route audio_map_gta02[] = { + /* Call Speaker */ + {"Handset Spk", NULL, "LOUT2"}, + {"Handset Spk", NULL, "ROUT2"}, +}; + +static const struct snd_kcontrol_new wm8753_neo1973_gta02_controls[] = { + SOC_DAPM_PIN_SWITCH("Handset Spk"), + + SOC_SINGLE_BOOL_EXT("Amp Spk Switch", 0, + lm4853_get_spk, + lm4853_set_spk), +}; + +static const struct snd_soc_dapm_widget wm8753_dapm_widgets_gta02[] = { + SND_SOC_DAPM_SPK("Stereo Out", lm4853_event), + SND_SOC_DAPM_SPK("Handset Spk", NULL), +}; + +#else +static const struct snd_soc_dapm_route audio_map_gta02[]= {}; +static const struct snd_kcontrol_new wm8753_neo1973_gta02_controls[] = {}; +static const struct snd_soc_dapm_widget wm8753_dapm_widgets_gta02[] = {}; +#endif + static int neo1973_wm8753_init(struct snd_soc_codec *codec) { int err; - - pr_debug("Entered %s\n", __func__); + const struct snd_soc_dapm_widget *machine_widgets; + size_t num_machine_widgets; + const struct snd_kcontrol_new *machine_controls; + size_t num_machine_controls; + + if (machine_is_neo1973_gta01()) { + machine_widgets = wm8753_dapm_widgets_gta01; + num_machine_widgets = ARRAY_SIZE(wm8753_dapm_widgets_gta01); + + machine_controls = wm8753_neo1973_gta01_controls; + num_machine_controls = ARRAY_SIZE(wm8753_neo1973_gta01_controls); + } else { + machine_widgets = wm8753_dapm_widgets_gta02; + num_machine_widgets = ARRAY_SIZE(wm8753_dapm_widgets_gta02); + + machine_controls = wm8753_neo1973_gta02_controls; + num_machine_controls = ARRAY_SIZE(wm8753_neo1973_gta02_controls); + } /* set up NC codec pins */ - snd_soc_dapm_nc_pin(codec, "LOUT2"); - snd_soc_dapm_nc_pin(codec, "ROUT2"); + if (!machine_is_neo1973_gta02()) { + snd_soc_dapm_nc_pin(codec, "LOUT2"); + snd_soc_dapm_nc_pin(codec, "ROUT2"); + } snd_soc_dapm_nc_pin(codec, "OUT3"); snd_soc_dapm_nc_pin(codec, "OUT4"); snd_soc_dapm_nc_pin(codec, "LINE1"); @@ -518,20 +531,41 @@ static int neo1973_wm8753_init(struct snd_soc_codec *codec) snd_soc_dapm_new_controls(codec, wm8753_dapm_widgets, ARRAY_SIZE(wm8753_dapm_widgets)); - /* set endpoints to default mode */ - set_scenario_endpoints(codec, NEO_AUDIO_OFF); + snd_soc_dapm_new_controls(codec, machine_widgets, num_machine_widgets); + /* add neo1973 specific controls */ err = snd_soc_add_controls(codec, wm8753_neo1973_controls, - ARRAY_SIZE(8753_neo1973_controls)); + ARRAY_SIZE(wm8753_neo1973_controls)); + if (err < 0) return err; - /* set up neo1973 specific audio routes */ - err = snd_soc_dapm_add_routes(codec, dapm_routes, - ARRAY_SIZE(dapm_routes)); + err = snd_soc_add_controls(codec, machine_controls, + num_machine_controls); + + if (err < 0) + return err; + + /* set up neo1973 gta02 specific audio path audio_map */ + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + if (machine_is_neo1973_gta02()) { + snd_soc_dapm_add_routes(codec, audio_map_gta02, + ARRAY_SIZE(audio_map_gta02)); + } + + /* set endpoints to default off mode */ + snd_soc_dapm_disable_pin(codec, "Stereo Out"); + snd_soc_dapm_disable_pin(codec, "GSM Line Out"); + snd_soc_dapm_disable_pin(codec, "GSM Line In"); + snd_soc_dapm_disable_pin(codec, "Headset Mic"); + snd_soc_dapm_disable_pin(codec, "Handset Mic"); + if (machine_is_neo1973_gta02()) + snd_soc_dapm_disable_pin(codec, "Handset Spk"); snd_soc_dapm_sync(codec); + return 0; } @@ -555,126 +589,111 @@ static struct snd_soc_dai bt_dai = { static struct snd_soc_dai_link neo1973_dai[] = { { /* Hifi Playback - for similatious use with voice below */ - .name = "WM8753", - .stream_name = "WM8753 HiFi", - .cpu_dai = &s3c24xx_i2s_dai, - .codec_dai = &wm8753_dai[WM8753_DAI_HIFI], - .init = neo1973_wm8753_init, - .ops = &neo1973_hifi_ops, + .name = "WM8753", + .stream_name = "WM8753 HiFi", + .cpu_dai = &s3c24xx_i2s_dai, + .codec_dai = &wm8753_dai[WM8753_DAI_HIFI], + .init = neo1973_wm8753_init, + .ops = &neo1973_hifi_ops, }, { /* Voice via BT */ - .name = "Bluetooth", - .stream_name = "Voice", - .cpu_dai = &bt_dai, - .codec_dai = &wm8753_dai[WM8753_DAI_VOICE], - .ops = &neo1973_voice_ops, + .name = "Bluetooth", + .stream_name = "Voice", + .cpu_dai = &bt_dai, + .codec_dai = &wm8753_dai[WM8753_DAI_VOICE], + .ops = &neo1973_voice_ops, }, }; static struct snd_soc_card neo1973 = { - .name = "neo1973", - .platform = &s3c24xx_soc_platform, - .dai_link = neo1973_dai, - .num_links = ARRAY_SIZE(neo1973_dai), + .name = "neo1973", + .platform = &s3c24xx_soc_platform, + .dai_link = neo1973_dai, + .num_links = ARRAY_SIZE(neo1973_dai), }; static struct snd_soc_device neo1973_snd_devdata = { - .card = &neo1973, - .codec_dev = &soc_codec_dev_wm8753, + .card = &neo1973, + .codec_dev = &soc_codec_dev_wm8753, }; -static int lm4857_i2c_probe(struct i2c_client *client, - const struct i2c_device_id *id) -{ - pr_debug("Entered %s\n", __func__); - - i2c = client; - - lm4857_write_regs(); - return 0; -} +static struct platform_device *neo1973_snd_device; -static int lm4857_i2c_remove(struct i2c_client *client) +#ifdef CONFIG_MACH_NEO1973_GTA01 +static int __init neo1973_gta01_init(void) { - pr_debug("Entered %s\n", __func__); - - i2c = NULL; - - return 0; + return i2c_add_driver(&lm4857_i2c_driver); } +#else +static inline int neo1973_gta01_init(void) { return 0; } +#endif -static u8 lm4857_state; - -static int lm4857_suspend(struct i2c_client *dev, pm_message_t state) +#ifdef CONFIG_MACH_NEO1973_GTA02 +static int __init neo1973_gta02_init(void) { - pr_debug("Entered %s\n", __func__); + int ret; - dev_dbg(&dev->dev, "lm4857_suspend\n"); - lm4857_state = lm4857_regs[LM4857_CTRL] & 0xf; - if (lm4857_state) { - lm4857_regs[LM4857_CTRL] &= 0xf0; - lm4857_write_regs(); + /* Initialise GPIOs used by amp */ + ret = gpio_request(GTA02_GPIO_HP_IN, "GTA02_HP_IN"); + if (ret) { + pr_err("gta02_wm8753: Failed to register GPIO %d\n", GTA02_GPIO_HP_IN); + goto err; } - return 0; -} - -static int lm4857_resume(struct i2c_client *dev) -{ - pr_debug("Entered %s\n", __func__); - if (lm4857_state) { - lm4857_regs[LM4857_CTRL] |= (lm4857_state & 0x0f); - lm4857_write_regs(); + ret = gpio_direction_output(GTA02_GPIO_HP_IN, 1); + if (ret) { + pr_err("gta02_wm8753: Failed to configure GPIO %d\n", GTA02_GPIO_HP_IN); + goto err_free_gpio_hp_in; } - return 0; -} -static void lm4857_shutdown(struct i2c_client *dev) -{ - pr_debug("Entered %s\n", __func__); - - dev_dbg(&dev->dev, "lm4857_shutdown\n"); - lm4857_regs[LM4857_CTRL] &= 0xf0; - lm4857_write_regs(); -} + ret = gpio_request(GTA02_GPIO_AMP_SHUT, "GTA02_AMP_SHUT"); + if (ret) { + pr_err("gta02_wm8753: Failed to register GPIO %d\n", GTA02_GPIO_AMP_SHUT); + goto err_free_gpio_hp_in; + } -static const struct i2c_device_id lm4857_i2c_id[] = { - { "neo1973_lm4857", 0 }, - { } -}; + ret = gpio_direction_output(GTA02_GPIO_AMP_SHUT, 1); + if (ret) { + pr_err("gta02_wm8753: Failed to configure GPIO %d\n", GTA02_GPIO_AMP_SHUT); + goto err_free_gpio_amp_shut; + } -static struct i2c_driver lm4857_i2c_driver = { - .driver = { - .name = "LM4857 I2C Amp", - .owner = THIS_MODULE, - }, - .suspend = lm4857_suspend, - .resume = lm4857_resume, - .shutdown = lm4857_shutdown, - .probe = lm4857_i2c_probe, - .remove = lm4857_i2c_remove, - .id_table = lm4857_i2c_id, -}; + return 0; -static struct platform_device *neo1973_snd_device; +err_free_gpio_amp_shut: + gpio_free(GTA02_GPIO_AMP_SHUT); +err_free_gpio_hp_in: + gpio_free(GTA02_GPIO_HP_IN); +err: + return ret; +} +#else +static inline int neo1973_gta02_init(void) { return 0; } +#endif static int __init neo1973_init(void) { int ret; - pr_debug("Entered %s\n", __func__); - - if (!machine_is_neo1973_gta01()) { - printk(KERN_INFO - "Only GTA01 hardware supported by ASoC driver\n"); + if (!machine_is_neo1973_gta01() && !machine_is_neo1973_gta02()) { return -ENODEV; } + if (machine_is_neo1973_gta02()) { + neo1973_snd_devdata.card->name = "neo1973gta02"; + } + + /* register bluetooth DAI here */ + ret = snd_soc_register_dai(&bt_dai); + if (ret) + return ret; + neo1973_snd_device = platform_device_alloc("soc-audio", -1); if (!neo1973_snd_device) return -ENOMEM; - platform_set_drvdata(neo1973_snd_device, &neo1973_snd_devdata); + platform_set_drvdata(neo1973_snd_device, + &neo1973_snd_devdata); neo1973_snd_devdata.dev = &neo1973_snd_device->dev; ret = platform_device_add(neo1973_snd_device); @@ -683,26 +702,54 @@ static int __init neo1973_init(void) return ret; } - ret = i2c_add_driver(&lm4857_i2c_driver); + if (machine_is_neo1973_gta01()) + ret = neo1973_gta01_init(); + else + ret = neo1973_gta02_init(); - if (ret != 0) - platform_device_unregister(neo1973_snd_device); + if (ret) + goto err_unregister_device; + return 0; + +err_unregister_device: + platform_device_unregister(neo1973_snd_device); return ret; } +module_init(neo1973_init); -static void __exit neo1973_exit(void) +#ifdef CONFIG_MACH_NEO1973_GTA01 +static void __exit neo1973_gta01_exit(void) { - pr_debug("Entered %s\n", __func__); - i2c_del_driver(&lm4857_i2c_driver); - platform_device_unregister(neo1973_snd_device); } +#else +static inline void neo1973_gta01_exit(void) {} +#endif -module_init(neo1973_init); +#ifdef CONFIG_MACH_NEO1973_GTA02 +static void __exit neo1973_gta02_exit(void) +{ + gpio_free(GTA02_GPIO_HP_IN); + gpio_free(GTA02_GPIO_AMP_SHUT); +} +#else +static inline void neo1973_gta02_exit(void) {} +#endif + +static void __exit neo1973_exit(void) +{ + snd_soc_unregister_dai(&bt_dai); + platform_device_unregister(neo1973_snd_device); + + if (machine_is_neo1973_gta01()) + neo1973_gta01_exit(); + else + neo1973_gta02_exit(); +} module_exit(neo1973_exit); /* Module information */ -MODULE_AUTHOR("Graeme Gregory, graeme@openmoko.org, www.openmoko.org"); -MODULE_DESCRIPTION("ALSA SoC WM8753 Neo1973"); +MODULE_AUTHOR("Graeme Gregory, graeme@openmoko.org"); +MODULE_DESCRIPTION("ALSA SoC WM8753 Neo1973 devices"); MODULE_LICENSE("GPL"); |