From 470379585be3e2e116e9412e114698debb02eb9e Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sat, 24 Oct 2009 18:48:17 +0200 Subject: MFD: pcf50633: Fix bitfield logic in interrupt handler Those constants are alreay bitfields. --- drivers/mfd/pcf50633-core.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/mfd/pcf50633-core.c b/drivers/mfd/pcf50633-core.c index 63a614d696c..64d490ccd64 100644 --- a/drivers/mfd/pcf50633-core.c +++ b/drivers/mfd/pcf50633-core.c @@ -354,18 +354,18 @@ static void pcf50633_irq_worker(struct work_struct *work) 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 " -- cgit v1.2.3 From 0c254d7564232d0a9f2427d1ff2e3736db28c567 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Mon, 5 Oct 2009 13:39:17 +0200 Subject: Add c fiq handler. --- arch/arm/include/asm/fiq.h | 5 +- arch/arm/kernel/fiq.c | 79 ++++++++++++++++++++++++++++++++ arch/arm/plat-s3c24xx/include/plat/irq.h | 21 +++++++++ arch/arm/plat-s3c24xx/irq.c | 48 +++++++++++++++---- 4 files changed, 142 insertions(+), 11 deletions(-) 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 + * * 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/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 +#include #include #include #include @@ -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 ad0d44ef1f9..3334f560e13 100644 --- a/arch/arm/plat-s3c24xx/irq.c +++ b/arch/arm/plat-s3c24xx/irq.c @@ -28,6 +28,8 @@ #include #include +#include +#include #include #include @@ -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 = { @@ -561,26 +591,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; } -- cgit v1.2.3 From 3a821bff143548dd67dbc7292fa9dce24f5b864e Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Thu, 13 May 2010 23:20:53 +0200 Subject: LEDS: leds-pwm: Add init, notfiy and exit callbacks This patch adds init, notify and exit callbacks to the leds-pwm driver. One usecase for these callbacks is that on certain platforms it is neccessary to configure a gpio pin as pwm pin. On these platforms it is usefull to have the added callbacks, so that the gpio pin can be configured at the same time as the pwm device. Signed-off-by: Lars-Peter Clausen --- drivers/leds/leds-pwm.c | 29 ++++++++++++++++++++++++----- include/linux/leds_pwm.h | 20 ++++++++++++++++++++ 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/drivers/leds/leds-pwm.c b/drivers/leds/leds-pwm.c index da3fa8dcdf5..ee9c7085664 100644 --- a/drivers/leds/leds-pwm.c +++ b/drivers/leds/leds-pwm.c @@ -26,8 +26,8 @@ struct led_pwm_data { struct led_classdev cdev; struct pwm_device *pwm; - unsigned int active_low; - unsigned int period; + struct led_pwm *led; + struct device *parent; }; static void led_pwm_set(struct led_classdev *led_cdev, @@ -35,8 +35,13 @@ static void led_pwm_set(struct led_classdev *led_cdev, { struct led_pwm_data *led_dat = container_of(led_cdev, struct led_pwm_data, cdev); + struct device *parent = led_dat->parent; + struct led_pwm_platform_data *pdata = parent->platform_data; unsigned int max = led_dat->cdev.max_brightness; - unsigned int period = led_dat->period; + unsigned int period = led_dat->led->pwm_period_ns; + + if (pdata->notify) + brightness = pdata->notify(parent, led_dat->led, brightness); if (brightness == 0) { pwm_config(led_dat->pwm, 0, period); @@ -76,18 +81,28 @@ static int led_pwm_probe(struct platform_device *pdev) led_dat->cdev.name = cur_led->name; led_dat->cdev.default_trigger = cur_led->default_trigger; - led_dat->active_low = cur_led->active_low; - led_dat->period = cur_led->pwm_period_ns; led_dat->cdev.brightness_set = led_pwm_set; led_dat->cdev.brightness = LED_OFF; led_dat->cdev.max_brightness = cur_led->max_brightness; led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME; + led_dat->led = cur_led; + led_dat->parent = &pdev->dev; + ret = led_classdev_register(&pdev->dev, &led_dat->cdev); if (ret < 0) { pwm_free(led_dat->pwm); goto err; } + + if (pdata->init) { + ret = pdata->init(&pdev->dev, cur_led); + if (ret < 0) { + led_classdev_unregister(&led_dat->cdev); + pwm_free(led_dat->pwm); + goto err; + } + } } platform_set_drvdata(pdev, leds_data); @@ -97,6 +112,8 @@ static int led_pwm_probe(struct platform_device *pdev) err: if (i > 0) { for (i = i - 1; i >= 0; i--) { + if (pdata->exit) + pdata->exit(&pdev->dev, &pdata->leds[i]); led_classdev_unregister(&leds_data[i].cdev); pwm_free(leds_data[i].pwm); } @@ -116,6 +133,8 @@ static int __devexit led_pwm_remove(struct platform_device *pdev) leds_data = platform_get_drvdata(pdev); for (i = 0; i < pdata->num_leds; i++) { + if (pdata->exit) + pdata->exit(&pdev->dev, &pdata->leds[i]); led_classdev_unregister(&leds_data[i].cdev); pwm_free(leds_data[i].pwm); } diff --git a/include/linux/leds_pwm.h b/include/linux/leds_pwm.h index 33a07116748..42d49694cbd 100644 --- a/include/linux/leds_pwm.h +++ b/include/linux/leds_pwm.h @@ -16,6 +16,26 @@ struct led_pwm { struct led_pwm_platform_data { int num_leds; struct led_pwm *leds; + + /* @init: The init callback is called after the pwm device for a led has + * been successfully configured. If the return value is negative it will be + * seen as an error and initzalisation of the leds-pwm device will fail. + */ + int (*init)(struct device *dev, struct led_pwm *led); + + /* @notify: The notify callback is called whenever the brightness of a led + * is changed. + * The return value of the callback will be the brightness which is used to + * configure the pwm device. + */ + enum led_brightness (*notify)(struct device *dev, struct led_pwm *led, + enum led_brightness brightness); + + /* @exit: The exit callback is called, whenever a led device registered by + * the leds-pwm device is unregistered. It will be called prior to freeing + * the pwm device. + */ + void (*exit)(struct device *dev, struct led_pwm *led); }; #endif -- cgit v1.2.3 From e8e8a0e59979f12e48af0ed86437c8b924a6863f Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sun, 4 Oct 2009 13:37:37 +0200 Subject: Add jbt6k74 display driver. --- drivers/video/backlight/Kconfig | 14 + drivers/video/backlight/Makefile | 1 + drivers/video/backlight/jbt6k74.c | 896 ++++++++++++++++++++++++++++++++++++++ include/linux/jbt6k74.h | 21 + 4 files changed, 932 insertions(+) create mode 100644 drivers/video/backlight/jbt6k74.c create mode 100644 include/linux/jbt6k74.h diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig index c025c84601b..88db009f3b3 100644 --- a/drivers/video/backlight/Kconfig +++ b/drivers/video/backlight/Kconfig @@ -107,6 +107,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 # diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile index 09d1f14d625..54c2cdac583 100644 --- a/drivers/video/backlight/Makefile +++ b/drivers/video/backlight/Makefile @@ -11,6 +11,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 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 , + * Stefan Schmidt + * Copyright (C) 2008 by Harald Welte + * Copyright (C) 2009 by Lars-Peter Clausen + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 "); +MODULE_LICENSE("GPL"); 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 + +/* + * 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 -- cgit v1.2.3 From 2536d500962c564120a1e617d536da7af970f1d6 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sun, 11 Oct 2009 00:33:51 +0200 Subject: s3c24xx: Add support for board IRQs Some boards have a need for a set of extra IRQ numbers, for example some multifunction devices which do irq demultiplexing require them. This patch adds S3C2410_BOARD_NR_IRQS which specifies the number of extra board IRQs. Board specific code would use S3C2410_BOARD_IRQ_START to get the number of it's first irq. Since it is possible to support a multiple boards with a single kernel and there is no easy way to set a CONFIG option to multiple values and only use the maximum each board which needs board IRQs has do be explicitly added to irqs.h Signed-off-by: Lars-Peter Clausen --- arch/arm/mach-s3c2410/include/mach/irqs.h | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/arch/arm/mach-s3c2410/include/mach/irqs.h b/arch/arm/mach-s3c2410/include/mach/irqs.h index 6c12c6312ad..04c59ab489f 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 order, so if support for + * multiple boards is compiled in the maximum will be used and there are enough + * IRQ numbers available for each board. + */ + +#if 0 +/* Number of board specific irqs go here */ +#else +#define IRQ_BOARD_NR 0 +#endif + +#define IRQ_BOARD_START S3C2410_NR_INTERNAL_IRQS +#define IRQ_BOARD_END (IRQ_BOARD_START + IRQ_BOARD_NR) + +#define NR_IRQS IRQ_BOARD_END + #endif /* __ASM_ARCH_IRQ_H */ -- cgit v1.2.3 From 8dd3b11c092dff5aa3892c1e12cbda799fd37891 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sun, 11 Oct 2009 01:23:35 +0200 Subject: s3c24xx: Add number of board irqs for the gta02 The gta02 uses the glamo mfd driver which requires 9 board irqs and the pcf50633 mfd driver which requires 40 irqs. Signed-off-by: Lars-Peter Clausen --- arch/arm/mach-s3c2410/include/mach/irqs.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm/mach-s3c2410/include/mach/irqs.h b/arch/arm/mach-s3c2410/include/mach/irqs.h index 04c59ab489f..fc0e3c93fb7 100644 --- a/arch/arm/mach-s3c2410/include/mach/irqs.h +++ b/arch/arm/mach-s3c2410/include/mach/irqs.h @@ -180,8 +180,8 @@ * IRQ numbers available for each board. */ -#if 0 -/* Number of board specific irqs go here */ +#if defined(CONFIG_MACH_NEO1973_GTA02) +#define IRQ_BOARD_NR (9 + 40) #else #define IRQ_BOARD_NR 0 #endif -- cgit v1.2.3 From 200ef71c3f94b4c152bdb02954297cd0672bc07e Mon Sep 17 00:00:00 2001 From: mokopatches Date: Wed, 19 Nov 2008 17:03:14 +0000 Subject: s3c2410_udc-2440_dual_packet-workaround.patch This is a patch that seems to make the USB hangs on the S3C2440 go away. At least a good amount of ping torture didn't make them come back so far. The issue is that, if there are several back-to-back packets, sometimes no interrupt is generated for one of them. This seems to be caused by the mysterious dual packet mode, which the USB hardware enters automatically if the endpoint size is half that of the FIFO. (On the 2440, this is the normal situation for bulk data endpoints.) There is also a timing factor in this. I think what happens is that the USB hardware automatically sends an acknowledgement if there is only one packet in the FIFO (the FIFO has space for two). If another packet arrives before the host has retrieved and acknowledged the previous one, no interrupt is generated for that second one. However, there may be an indication. There is one undocumented bit (none of the 244x manuals document it), OUT_CRS1_REG[1], that seems to be set suspiciously often when this condition occurs. There is also CLR_DATA_TOGGLE, OUT_CRS1_REG[7], which may have a function related to this. (The Samsung manual is rather terse on that, as usual.) This needs to be examined further. For now, the patch seems to do the trick. Note that this is not a clean solution by any means, because we might potentially get stuck in that interrupt for quite a while. --- drivers/usb/gadget/s3c2410_udc.c | 3 +++ 1 file changed, 3 insertions(+) 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; } } } -- cgit v1.2.3 From 4b5493b4c126b54a730d57a619ede97424ad0e7c Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Tue, 3 Nov 2009 00:29:53 +0100 Subject: s3c: Register gpio bank J with gpiolib This is nonportable and wastes a lot of memory and thus should be replaced with a prober solution asap. --- arch/arm/mach-s3c2410/include/mach/gpio.h | 5 +++-- arch/arm/plat-s3c24xx/gpiolib.c | 13 +++++++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) 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 #include #include +#include -#define S3C_GPIO_END (S3C2410_GPIO_BANKH + 32) +#define S3C_GPIO_END (S3C2440_GPIO_BANKJ + 32) diff --git a/arch/arm/plat-s3c24xx/gpiolib.c b/arch/arm/plat-s3c24xx/gpiolib.c index 4f0f11a6a67..c0b03663e40 100644 --- a/arch/arm/plat-s3c24xx/gpiolib.c +++ b/arch/arm/plat-s3c24xx/gpiolib.c @@ -26,6 +26,7 @@ #include #include +#include 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) { -- cgit v1.2.3 From 6e279858214c5929aaf45e7eb454720ebd482294 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Thu, 5 Nov 2009 23:04:42 +0100 Subject: S3C: Allow to completly disable low-level messages In some cases it is desirable to completly disable low-level messages. For example if you have no uart available for a serial console. With this patch it is possible to disable low-level messages by setting CONFIG_S3C_LOWLEVEL_UART_PORT to less then zero. Signed-off-by: Lars-Peter Clausen --- arch/arm/boot/compressed/head.S | 2 +- arch/arm/plat-samsung/include/plat/uncompress.h | 19 +++++++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/arch/arm/boot/compressed/head.S b/arch/arm/boot/compressed/head.S index c5191b1532e..7e5e8933b26 100644 --- a/arch/arm/boot/compressed/head.S +++ b/arch/arm/boot/compressed/head.S @@ -66,7 +66,7 @@ wait: mrc p14, 0, pc, c0, c1, 0 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, tmp mov \rb, #0x50000000 add \rb, \rb, #0x4000 * CONFIG_S3C_LOWLEVEL_UART_PORT diff --git a/arch/arm/plat-samsung/include/plat/uncompress.h b/arch/arm/plat-samsung/include/plat/uncompress.h index 7d6ed7263d5..152c496d1c2 100644 --- a/arch/arm/plat-samsung/include/plat/uncompress.h +++ b/arch/arm/plat-samsung/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); \ @@ -161,6 +159,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) -- cgit v1.2.3 From 6e4982b08e0f112bbf0648e377ae53f14e9560a6 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sat, 8 May 2010 14:32:09 +0200 Subject: s3c24xx: Fix level irqs on external interrupts. Although the external interrupts support level and edge triggered irqs their handler is currently always set to handle_edge_irq(). While being technically wrong for a level triggered irq to be handled by handle_edge_irq() it will cause serious problems in combination with a oneshot irq. handle_edge_irq() will unmask the irq immediately and as a result the irq will be triggered again before the threaded irq handler had a chance to run and clear the irq source. Thus level triggered irqs should be handled by handle_level_irq. According to the specs the irq controller will remember if an irq has been triggered while it had been masked and will issue it when the irq gets unmasked. Thus it is sufficient to use handle_level_irq() for edge triggered irqs as well. Hence handle_level_irq() can always be used for external interrupts. Signed-off-by: Lars-Peter Clausen --- arch/arm/plat-s3c24xx/irq.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/arch/arm/plat-s3c24xx/irq.c b/arch/arm/plat-s3c24xx/irq.c index 3334f560e13..0a8553b402a 100644 --- a/arch/arm/plat-s3c24xx/irq.c +++ b/arch/arm/plat-s3c24xx/irq.c @@ -661,15 +661,13 @@ void __init s3c24xx_init_irq(void) for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++) { irqdbf("registering irq %d (ext int)\n", irqno); - set_irq_chip(irqno, &s3c_irq_eint0t4); - set_irq_handler(irqno, handle_edge_irq); + set_irq_chip_and_handler(irqno, &s3c_irq_eint0t4, handle_level_irq); set_irq_flags(irqno, IRQF_VALID); } for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23; irqno++) { irqdbf("registering irq %d (extended s3c irq)\n", irqno); - set_irq_chip(irqno, &s3c_irqext_chip); - set_irq_handler(irqno, handle_edge_irq); + set_irq_chip_and_handler(irqno, &s3c_irqext_chip, handle_level_irq); set_irq_flags(irqno, IRQF_VALID); } -- cgit v1.2.3 From 5e8d52635470a7345d0cfa6912bd6ff68793236c Mon Sep 17 00:00:00 2001 From: Holger Freyther Date: Wed, 19 Nov 2008 17:10:51 +0000 Subject: mtd: s3c2410_nand: Add config option to disable hw ecc at runtime This patch adds a flag to the s3c2410_nand platform data, which configures whether hardware ecc is used for that chip. Currently hardware ecc is used if it was compiled into the kernel. But if you want to build a kernel which runs on multiple devices you might have a configuration where you have devices which require hw ecc as well as devices which want software ecc. Signed-off-by: Lars-Peter Clausen --- arch/arm/plat-samsung/include/plat/nand.h | 1 + drivers/mtd/nand/s3c2410.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/arm/plat-samsung/include/plat/nand.h b/arch/arm/plat-samsung/include/plat/nand.h index b64115fa93a..cbdc8f0d318 100644 --- a/arch/arm/plat-samsung/include/plat/nand.h +++ b/arch/arm/plat-samsung/include/plat/nand.h @@ -49,6 +49,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/drivers/mtd/nand/s3c2410.c b/drivers/mtd/nand/s3c2410.c index fa6e9c7fe51..ec4ac0934d2 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; -- cgit v1.2.3 From e74c2624244993b98a2e350383796d14a2eb2708 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Mon, 5 Oct 2009 14:27:40 +0200 Subject: Add s3c24xx_serial_console_set_silence --- drivers/serial/samsung.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/drivers/serial/samsung.c b/drivers/serial/samsung.c index a9d6c5626a0..f7b80ae91d5 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 -- cgit v1.2.3 From 501a67c8d153ad5849fc47720598fa2c4c05144b Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sat, 8 May 2010 19:19:51 +0200 Subject: MFD: pcf50633-adc: Fix potential race pcf50633_adc_sync_read Currently it's not guaranteed that request struct is not already freed when reading from it. Fix this by moving synced request related fields from the pcf50633_adc_request struct to its own struct and store it on the functions stack. Signed-off-by: Lars-Peter Clausen --- drivers/mfd/pcf50633-adc.c | 39 +++++++++++++++------------------------ 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/drivers/mfd/pcf50633-adc.c b/drivers/mfd/pcf50633-adc.c index fe8f922f665..aed0d2a9b03 100644 --- a/drivers/mfd/pcf50633-adc.c +++ b/drivers/mfd/pcf50633-adc.c @@ -30,13 +30,13 @@ struct pcf50633_adc_request { int mux; int avg; - int result; void (*callback)(struct pcf50633 *, void *, int); void *callback_param; +}; - /* Used in case of sync requests */ +struct pcf50633_adc_sync_request { + int result; struct completion completion; - }; #define PCF50633_MAX_ADC_FIFO_DEPTH 8 @@ -109,10 +109,10 @@ adc_enqueue_request(struct pcf50633 *pcf, struct pcf50633_adc_request *req) return 0; } -static void -pcf50633_adc_sync_read_callback(struct pcf50633 *pcf, void *param, int result) +static void pcf50633_adc_sync_read_callback(struct pcf50633 *pcf, void *param, + int result) { - struct pcf50633_adc_request *req = param; + struct pcf50633_adc_sync_request *req = param; req->result = result; complete(&req->completion); @@ -120,28 +120,19 @@ pcf50633_adc_sync_read_callback(struct pcf50633 *pcf, void *param, int result) int pcf50633_adc_sync_read(struct pcf50633 *pcf, int mux, int avg) { - struct pcf50633_adc_request *req; - int err; + struct pcf50633_adc_sync_request req; + int ret; - /* req is freed when the result is ready, in interrupt handler */ - req = kzalloc(sizeof(*req), GFP_KERNEL); - if (!req) - return -ENOMEM; - - req->mux = mux; - req->avg = avg; - req->callback = pcf50633_adc_sync_read_callback; - req->callback_param = req; + init_completion(&req.completion); - init_completion(&req->completion); - err = adc_enqueue_request(pcf, req); - if (err) - return err; + ret = pcf50633_adc_async_read(pcf, mux, avg, + pcf50633_adc_sync_read_callback, &req); + if (ret) + return ret; - wait_for_completion(&req->completion); + wait_for_completion(&req.completion); - /* FIXME by this time req might be already freed */ - return req->result; + return req.result; } EXPORT_SYMBOL_GPL(pcf50633_adc_sync_read); -- cgit v1.2.3 From c93252fe4964991bc6d2b200f740fe59995f3342 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Mon, 2 Nov 2009 19:54:35 +0100 Subject: backlight/mfd: Add pcf50633 backlight driver This patch adds a backlight driver controling the pcf50633 led converter. Signed-off-by: Lars-Peter Clausen --- drivers/mfd/pcf50633-core.c | 3 + drivers/video/backlight/Kconfig | 6 + drivers/video/backlight/Makefile | 1 + drivers/video/backlight/pcf50633-backlight.c | 189 +++++++++++++++++++++++++++ include/linux/mfd/pcf50633/backlight.h | 44 +++++++ include/linux/mfd/pcf50633/core.h | 4 + 6 files changed, 247 insertions(+) create mode 100644 drivers/video/backlight/pcf50633-backlight.c create mode 100644 include/linux/mfd/pcf50633/backlight.h diff --git a/drivers/mfd/pcf50633-core.c b/drivers/mfd/pcf50633-core.c index 64d490ccd64..977a4227508 100644 --- a/drivers/mfd/pcf50633-core.c +++ b/drivers/mfd/pcf50633-core.c @@ -620,6 +620,9 @@ 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; diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig index c025c84601b..6aadc00be1f 100644 --- a/drivers/video/backlight/Kconfig +++ b/drivers/video/backlight/Kconfig @@ -282,3 +282,9 @@ config BACKLIGHT_88PM860X help Say Y to enable the backlight driver for Marvell 88PM8606. +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 09d1f14d625..8b714c1a91a 100644 --- a/drivers/video/backlight/Makefile +++ b/drivers/video/backlight/Makefile @@ -31,4 +31,5 @@ obj-$(CONFIG_BACKLIGHT_WM831X) += wm831x_bl.o obj-$(CONFIG_BACKLIGHT_ADX) += adx_bl.o obj-$(CONFIG_BACKLIGHT_ADP5520) += adp5520_bl.o obj-$(CONFIG_BACKLIGHT_88PM860X) += 88pm860x_bl.o +obj-$(CONFIG_BACKLIGHT_PCF50633) += pcf50633-backlight.o diff --git a/drivers/video/backlight/pcf50633-backlight.c b/drivers/video/backlight/pcf50633-backlight.c new file mode 100644 index 00000000000..5b72089c9c3 --- /dev/null +++ b/drivers/video/backlight/pcf50633-backlight.c @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2009, Lars-Peter Clausen + * 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 +#include +#include +#include + +#include +#include + +#include +#include + +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; + struct backlight_properties bl_props; + + pcf_bl = kzalloc(sizeof(*pcf_bl), GFP_KERNEL); + if (!pcf_bl) + return -ENOMEM; + + bl_props.max_brightness = 0x3f; + bl_props.power = FB_BLANK_UNBLANK; + + if (pdata) { + bl_props.brightness = pdata->default_brightness; + pcf_bl->brightness_limit = pdata->default_brightness_limit; + } else { + bl_props.brightness = 0x3f; + pcf_bl->brightness_limit = 0x3f; + } + + pcf_bl->pcf = dev_to_pcf50633(pdev->dev.parent); + + pcf_bl->bl = backlight_device_register(pdev->name, &pdev->dev, pcf_bl, + &pcf50633_bl_ops, &bl_props); + + if (IS_ERR(pcf_bl->bl)) { + ret = PTR_ERR(pcf_bl->bl); + goto err_free; + } + + platform_set_drvdata(pdev, pcf_bl); + + pcf50633_reg_write(pcf_bl->pcf, PCF50633_REG_LEDDIM, pdata->ramp_time); + + /* Should be different from bl_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 "); +MODULE_DESCRIPTION("PCF50633 backlight driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:pcf50633-backlight"); 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 + * 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 3398bd9aab1..ad411a78870 100644 --- a/include/linux/mfd/pcf50633/core.h +++ b/include/linux/mfd/pcf50633/core.h @@ -18,6 +18,7 @@ #include #include #include +#include struct pcf50633; @@ -43,6 +44,8 @@ struct pcf50633_platform_data { void (*force_shutdown)(struct pcf50633 *); u8 resumers[5]; + + struct pcf50633_bl_platform_data *backlight_data; }; struct pcf50633_irq { @@ -152,6 +155,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]; }; -- cgit v1.2.3 From 1608707cca9c9eac88f34a48346435dc0bd75259 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Fri, 22 Jan 2010 12:47:01 +0100 Subject: pcf50633-gpio: Add gpiolib support. --- drivers/mfd/pcf50633-core.c | 6 +- drivers/mfd/pcf50633-gpio.c | 157 ++++++++++++++++++++++++++++++++------ include/linux/mfd/pcf50633/core.h | 4 + include/linux/mfd/pcf50633/gpio.h | 59 +++++++------- 4 files changed, 171 insertions(+), 55 deletions(-) diff --git a/drivers/mfd/pcf50633-core.c b/drivers/mfd/pcf50633-core.c index 977a4227508..0a2c9aa3e23 100644 --- a/drivers/mfd/pcf50633-core.c +++ b/drivers/mfd/pcf50633-core.c @@ -455,7 +455,7 @@ static irqreturn_t pcf50633_irq(int irq, void *data) static void pcf50633_client_dev_register(struct pcf50633 *pcf, const char *name, - struct platform_device **pdev) + struct platform_device **pdev) { int ret; @@ -622,7 +622,8 @@ static int __devinit pcf50633_probe(struct i2c_client *client, &pcf->adc_pdev); pcf50633_client_dev_register(pcf, "pcf50633-backlight", &pcf->bl_pdev); - + pcf50633_client_dev_register(pcf, "pcf50633-gpio", + &pcf->gpio_pdev); for (i = 0; i < PCF50633_NUM_REGULATORS; i++) { struct platform_device *pdev; @@ -671,6 +672,7 @@ static int __devexit pcf50633_remove(struct i2c_client *client) free_irq(pcf->irq, pcf); destroy_workqueue(pcf->work_queue); + platform_device_unregister(pcf->gpio_pdev); platform_device_unregister(pcf->input_pdev); platform_device_unregister(pcf->rtc_pdev); platform_device_unregister(pcf->mbc_pdev); diff --git a/drivers/mfd/pcf50633-gpio.c b/drivers/mfd/pcf50633-gpio.c index 9ab19a8f669..eb044e805bf 100644 --- a/drivers/mfd/pcf50633-gpio.c +++ b/drivers/mfd/pcf50633-gpio.c @@ -2,6 +2,7 @@ * * (C) 2006-2008 by Openmoko, Inc. * Author: Balaji Rao + * Copyright 2010, Lars-Peter Clausen * All rights reserved. * * Broken down from monstrous PCF50633 driver mainly by @@ -16,9 +17,15 @@ #include #include +#include +#include #include #include +#include + +#define PCF50633_REG_GPIOCTL 0x13 +#define PCF50633_REG_GPIOCFG(x) (0x14 + (x)) enum pcf50633_regulator_id { PCF50633_REGULATOR_AUTO, @@ -60,54 +67,89 @@ static const u8 pcf50633_regulator_registers[PCF50633_NUM_REGULATORS] = { [PCF50633_REGULATOR_HCLDO] = PCF50633_REG_HCLDOOUT, }; -int pcf50633_gpio_set(struct pcf50633 *pcf, int gpio, u8 val) +struct pcf50633_gpio { + struct pcf50633 *pcf; + struct gpio_chip chip; +}; + +static inline struct pcf50633 *gpio_chip_to_pcf50633(struct gpio_chip *chip) +{ + struct pcf50633 *pcf = dev_to_pcf50633(chip->dev->parent); + return pcf; +} + +static void pcf50633_gpio_set_value(struct gpio_chip *chip, unsigned gpio, int value) { + struct pcf50633 *pcf = gpio_chip_to_pcf50633(chip); u8 reg; - reg = gpio - PCF50633_GPIO1 + PCF50633_REG_GPIO1CFG; + reg = PCF50633_REG_GPIOCFG(gpio); - return pcf50633_reg_set_bit_mask(pcf, reg, 0x07, val); + pcf50633_reg_set_bit_mask(pcf, reg, 0x07, value ? 0x7 : 0x0); } -EXPORT_SYMBOL_GPL(pcf50633_gpio_set); -u8 pcf50633_gpio_get(struct pcf50633 *pcf, int gpio) +static int pcf50633_gpio_get_value(struct gpio_chip *chip, unsigned gpio) { - u8 reg, val; - - reg = gpio - PCF50633_GPIO1 + PCF50633_REG_GPIO1CFG; - val = pcf50633_reg_read(pcf, reg) & 0x07; - - return val; + struct pcf50633 *pcf = gpio_chip_to_pcf50633(chip); + return pcf50633_reg_read(pcf, PCF50633_REG_GPIOCFG(gpio)) >> 3; } -EXPORT_SYMBOL_GPL(pcf50633_gpio_get); -int pcf50633_gpio_invert_set(struct pcf50633 *pcf, int gpio, int invert) + +static int pcf50633_gpio_direction_output(struct gpio_chip *chip, unsigned gpio, + int value) { - u8 val, reg; + struct pcf50633 *pcf = gpio_chip_to_pcf50633(chip); + int ret; - reg = gpio - PCF50633_GPIO1 + PCF50633_REG_GPIO1CFG; - val = !!invert << 3; + ret = pcf50633_gpio_set_config(pcf, pcf->pdata->gpio_base + gpio, + PCF50633_GPIO_CONFIG_OUTPUT); + if (!ret) + pcf50633_gpio_set_value(chip, gpio, value); - return pcf50633_reg_set_bit_mask(pcf, reg, 1 << 3, val); + return ret; } -EXPORT_SYMBOL_GPL(pcf50633_gpio_invert_set); -int pcf50633_gpio_invert_get(struct pcf50633 *pcf, int gpio) +static int pcf50633_gpio_direction_input(struct gpio_chip *chip, unsigned gpio) { - u8 reg, val; - - reg = gpio - PCF50633_GPIO1 + PCF50633_REG_GPIO1CFG; - val = pcf50633_reg_read(pcf, reg); + return -ENOSYS; +} - return val & (1 << 3); +int pcf50633_gpio_set_config(struct pcf50633 *pcf, unsigned gpio, + enum pcf50633_gpio_config config) +{ + u8 reg; + u8 direction; + int ret; + + gpio -= pcf->pdata->gpio_base; + + if (gpio < 3) { + direction = (config == PCF50633_GPIO_CONFIG_INPUT) ? (1 << gpio) : 0; + ret = pcf50633_reg_set_bit_mask(pcf, PCF50633_REG_GPIOCTL, (1 << gpio), + direction); + if (ret) { + return ret; + } + } else if (gpio > 3 || config == PCF50633_GPIO_CONFIG_INPUT) { + return -EINVAL; + } + + if (config != PCF50633_GPIO_CONFIG_INPUT) { + reg = PCF50633_REG_GPIOCFG(gpio); + ret = pcf50633_reg_set_bit_mask(pcf, reg, 0x0f, config); + } + + return ret; } -EXPORT_SYMBOL_GPL(pcf50633_gpio_invert_get); +EXPORT_SYMBOL_GPL(pcf50633_gpio_set_config); int pcf50633_gpio_power_supply_set(struct pcf50633 *pcf, int gpio, int regulator, int on) { u8 reg, val, mask; + gpio -= pcf->pdata->gpio_base; + /* the *ENA register is always one after the *OUT register */ reg = pcf50633_regulator_registers[regulator] + 1; @@ -118,4 +160,69 @@ int pcf50633_gpio_power_supply_set(struct pcf50633 *pcf, } EXPORT_SYMBOL_GPL(pcf50633_gpio_power_supply_set); + +static int __devinit pcf50633_gpio_probe(struct platform_device *pdev) +{ + struct pcf50633 *pcf = dev_to_pcf50633(pdev->dev.parent); + struct pcf50633_gpio *pcf_gpio; + + pcf_gpio = kzalloc(sizeof(*pcf_gpio), GFP_KERNEL); + + if (!pcf_gpio) + return -ENOMEM; + + pcf_gpio->pcf = pcf; + + pcf_gpio->chip.direction_input = pcf50633_gpio_direction_input; + pcf_gpio->chip.direction_output = pcf50633_gpio_direction_output; + pcf_gpio->chip.get = pcf50633_gpio_get_value; + pcf_gpio->chip.set = pcf50633_gpio_set_value; + + pcf_gpio->chip.base = pcf->pdata->gpio_base; + pcf_gpio->chip.ngpio = 4; + pcf_gpio->chip.label = dev_name(pcf->dev); + pcf_gpio->chip.can_sleep = 1; + pcf_gpio->chip.owner = THIS_MODULE; + pcf_gpio->chip.dev = &pdev->dev; + + platform_set_drvdata(pdev, pcf_gpio); + + return gpiochip_add(&pcf_gpio->chip); +} + +static int __devexit pcf50633_gpio_remove(struct platform_device *pdev) +{ + struct pcf50633_gpio *pcf_gpio = platform_get_drvdata(pdev); + + gpiochip_remove(&pcf_gpio->chip); + + platform_set_drvdata(pdev, NULL); + kfree(pcf_gpio); + + return 0; +} + +static struct platform_driver pcf50633_gpio_driver = { + .probe = pcf50633_gpio_probe, + .remove = __devexit_p(pcf50633_gpio_remove), + .driver = { + .name = "pcf50633-gpio", + .owner = THIS_MODULE, + }, +}; + +int __init pcf50633_gpio_init(void) +{ + return platform_driver_register(&pcf50633_gpio_driver); +} +module_init(pcf50633_gpio_init); + +void __exit pcf50633_gpio_exit(void) +{ + platform_driver_unregister(&pcf50633_gpio_driver); +} +module_exit(pcf50633_gpio_exit); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("GPIO driver for the PCF50633"); MODULE_LICENSE("GPL"); diff --git a/include/linux/mfd/pcf50633/core.h b/include/linux/mfd/pcf50633/core.h index ad411a78870..9953fa14ec9 100644 --- a/include/linux/mfd/pcf50633/core.h +++ b/include/linux/mfd/pcf50633/core.h @@ -19,6 +19,7 @@ #include #include #include +#include struct pcf50633; @@ -46,6 +47,8 @@ struct pcf50633_platform_data { u8 resumers[5]; struct pcf50633_bl_platform_data *backlight_data; + + int gpio_base; }; struct pcf50633_irq { @@ -151,6 +154,7 @@ struct pcf50633 { int onkey1s_held; + struct platform_device *gpio_pdev; struct platform_device *rtc_pdev; struct platform_device *mbc_pdev; struct platform_device *adc_pdev; diff --git a/include/linux/mfd/pcf50633/gpio.h b/include/linux/mfd/pcf50633/gpio.h index a42b845efc5..af2c341d5f6 100644 --- a/include/linux/mfd/pcf50633/gpio.h +++ b/include/linux/mfd/pcf50633/gpio.h @@ -15,37 +15,40 @@ #include -#define PCF50633_GPIO1 1 -#define PCF50633_GPIO2 2 -#define PCF50633_GPIO3 3 -#define PCF50633_GPO 4 - -#define PCF50633_REG_GPIO1CFG 0x14 -#define PCF50633_REG_GPIO2CFG 0x15 -#define PCF50633_REG_GPIO3CFG 0x16 -#define PCF50633_REG_GPOCFG 0x17 - -#define PCF50633_GPOCFG_GPOSEL_MASK 0x07 - -enum pcf50633_reg_gpocfg { - PCF50633_GPOCFG_GPOSEL_0 = 0x00, - PCF50633_GPOCFG_GPOSEL_LED_NFET = 0x01, - PCF50633_GPOCFG_GPOSEL_SYSxOK = 0x02, - PCF50633_GPOCFG_GPOSEL_CLK32K = 0x03, - PCF50633_GPOCFG_GPOSEL_ADAPUSB = 0x04, - PCF50633_GPOCFG_GPOSEL_USBxOK = 0x05, - PCF50633_GPOCFG_GPOSEL_ACTPH4 = 0x06, - PCF50633_GPOCFG_GPOSEL_1 = 0x07, - PCF50633_GPOCFG_GPOSEL_INVERSE = 0x08, +#define PCF50633_GPIO1 0 +#define PCF50633_GPIO2 1 +#define PCF50633_GPIO3 2 +#define PCF50633_GPO 3 + +#define PCF50633_REG_GPIOCFG(x) (0x14 + (x)) + +enum pcf50633_gpio_config { + PCF50633_GPIO_CONFIG_OUTPUT = 0x0, + PCF50633_GPIO_CONFIG_SYSxOK = 0x2, + PCF50633_GPIO_CONFIG_CHARGING = 0x3, + PCF50633_GPIO_CONFIG_MOBILE_MODE = 0x4, + PCF50633_GPIO_CONFIG_USBxOK = 0x5, + PCF50633_GPIO_CONFIG_ACTPH = 0x6, + PCF50633_GPIO_CONFIG_INPUT = 0x7, + + PCF50633_GPIO_CONFIG_INVERT = 0x8, + + PCF50633_GPO_CONFIG_OUTPUT = 0x0, + PCF50633_GPO_CONFIG_LED_NFET = 0x1, + PCF50633_GPO_CONFIG_SYSxOK = 0x2, + PCF50633_GPO_CONFIG_CLK32K = 0x3, + PCF50633_GPO_CONFIG_MOBILE_MODE = 0x4, + PCF50633_GPO_CONFIG_USBxOK = 0x5, + PCF50633_GPO_CONFIG_ACTPH = 0x6, + PCF50633_GPO_CONFIG_INPUT = 0x7, + + PCF50633_GPO_CONFIG_INVERT = 0x8, }; -int pcf50633_gpio_set(struct pcf50633 *pcf, int gpio, u8 val); -u8 pcf50633_gpio_get(struct pcf50633 *pcf, int gpio); +int pcf50633_gpio_set_config(struct pcf50633 *pcf, unsigned gpio, + enum pcf50633_gpio_config config); -int pcf50633_gpio_invert_set(struct pcf50633 *, int gpio, int invert); -int pcf50633_gpio_invert_get(struct pcf50633 *pcf, int gpio); - -int pcf50633_gpio_power_supply_set(struct pcf50633 *, +int pcf50633_gpio_power_supply_set(struct pcf50633 *pcf, int gpio, int regulator, int on); #endif /* __LINUX_MFD_PCF50633_GPIO_H */ -- cgit v1.2.3 From 337651144022b10d023cecd6a5a0083f2e9f0b96 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Fri, 22 Jan 2010 12:48:41 +0100 Subject: pcf50633: Move pcf50633-gpio driver to the gpio drivers folder --- drivers/gpio/Kconfig | 7 ++ drivers/gpio/Makefile | 3 +- drivers/gpio/pcf50633-gpio.c | 228 +++++++++++++++++++++++++++++++++++++++++++ drivers/mfd/Kconfig | 7 -- drivers/mfd/Makefile | 3 +- drivers/mfd/pcf50633-gpio.c | 228 ------------------------------------------- 6 files changed, 238 insertions(+), 238 deletions(-) create mode 100644 drivers/gpio/pcf50633-gpio.c delete mode 100644 drivers/mfd/pcf50633-gpio.c diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index fee678f74a1..c6b9a40bffa 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -235,6 +235,13 @@ config GPIO_ADP5588 To compile this driver as a module, choose M here: the module will be called adp5588-gpio. +config GPIO_PCF50633 + tristate "GPIO support for NXP PCF50633" + depends on MFD_PCF50633 + help + Say yes here if you want to include support GPIO for pins on + the PCF50633 chip. + comment "PCI GPIO expanders:" config GPIO_CS5535 diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 10f3f8d958b..4fc4675a67b 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -27,4 +27,5 @@ obj-$(CONFIG_GPIO_VR41XX) += vr41xx_giu.o obj-$(CONFIG_GPIO_WM831X) += wm831x-gpio.o obj-$(CONFIG_GPIO_WM8350) += wm8350-gpiolib.o obj-$(CONFIG_GPIO_WM8994) += wm8994-gpio.o -obj-$(CONFIG_GPIO_SCH) += sch_gpio.o \ No newline at end of file +obj-$(CONFIG_GPIO_SCH) += sch_gpio.o +obj-$(CONFIG_GPIO_PCF50633) += pcf50633-gpio.o diff --git a/drivers/gpio/pcf50633-gpio.c b/drivers/gpio/pcf50633-gpio.c new file mode 100644 index 00000000000..eb044e805bf --- /dev/null +++ b/drivers/gpio/pcf50633-gpio.c @@ -0,0 +1,228 @@ +/* NXP PCF50633 GPIO Driver + * + * (C) 2006-2008 by Openmoko, Inc. + * Author: Balaji Rao + * Copyright 2010, Lars-Peter Clausen + * All rights reserved. + * + * Broken down from monstrous PCF50633 driver mainly by + * Harald Welte, Andy Green and Werner Almesberger + * + * 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 +#include +#include +#include + +#include +#include +#include + +#define PCF50633_REG_GPIOCTL 0x13 +#define PCF50633_REG_GPIOCFG(x) (0x14 + (x)) + +enum pcf50633_regulator_id { + PCF50633_REGULATOR_AUTO, + PCF50633_REGULATOR_DOWN1, + PCF50633_REGULATOR_DOWN2, + PCF50633_REGULATOR_LDO1, + PCF50633_REGULATOR_LDO2, + PCF50633_REGULATOR_LDO3, + PCF50633_REGULATOR_LDO4, + PCF50633_REGULATOR_LDO5, + PCF50633_REGULATOR_LDO6, + PCF50633_REGULATOR_HCLDO, + PCF50633_REGULATOR_MEMLDO, +}; + +#define PCF50633_REG_AUTOOUT 0x1a +#define PCF50633_REG_DOWN1OUT 0x1e +#define PCF50633_REG_DOWN2OUT 0x22 +#define PCF50633_REG_MEMLDOOUT 0x26 +#define PCF50633_REG_LDO1OUT 0x2d +#define PCF50633_REG_LDO2OUT 0x2f +#define PCF50633_REG_LDO3OUT 0x31 +#define PCF50633_REG_LDO4OUT 0x33 +#define PCF50633_REG_LDO5OUT 0x35 +#define PCF50633_REG_LDO6OUT 0x37 +#define PCF50633_REG_HCLDOOUT 0x39 + +static const u8 pcf50633_regulator_registers[PCF50633_NUM_REGULATORS] = { + [PCF50633_REGULATOR_AUTO] = PCF50633_REG_AUTOOUT, + [PCF50633_REGULATOR_DOWN1] = PCF50633_REG_DOWN1OUT, + [PCF50633_REGULATOR_DOWN2] = PCF50633_REG_DOWN2OUT, + [PCF50633_REGULATOR_MEMLDO] = PCF50633_REG_MEMLDOOUT, + [PCF50633_REGULATOR_LDO1] = PCF50633_REG_LDO1OUT, + [PCF50633_REGULATOR_LDO2] = PCF50633_REG_LDO2OUT, + [PCF50633_REGULATOR_LDO3] = PCF50633_REG_LDO3OUT, + [PCF50633_REGULATOR_LDO4] = PCF50633_REG_LDO4OUT, + [PCF50633_REGULATOR_LDO5] = PCF50633_REG_LDO5OUT, + [PCF50633_REGULATOR_LDO6] = PCF50633_REG_LDO6OUT, + [PCF50633_REGULATOR_HCLDO] = PCF50633_REG_HCLDOOUT, +}; + +struct pcf50633_gpio { + struct pcf50633 *pcf; + struct gpio_chip chip; +}; + +static inline struct pcf50633 *gpio_chip_to_pcf50633(struct gpio_chip *chip) +{ + struct pcf50633 *pcf = dev_to_pcf50633(chip->dev->parent); + return pcf; +} + +static void pcf50633_gpio_set_value(struct gpio_chip *chip, unsigned gpio, int value) +{ + struct pcf50633 *pcf = gpio_chip_to_pcf50633(chip); + u8 reg; + + reg = PCF50633_REG_GPIOCFG(gpio); + + pcf50633_reg_set_bit_mask(pcf, reg, 0x07, value ? 0x7 : 0x0); +} + +static int pcf50633_gpio_get_value(struct gpio_chip *chip, unsigned gpio) +{ + struct pcf50633 *pcf = gpio_chip_to_pcf50633(chip); + return pcf50633_reg_read(pcf, PCF50633_REG_GPIOCFG(gpio)) >> 3; +} + + +static int pcf50633_gpio_direction_output(struct gpio_chip *chip, unsigned gpio, + int value) +{ + struct pcf50633 *pcf = gpio_chip_to_pcf50633(chip); + int ret; + + ret = pcf50633_gpio_set_config(pcf, pcf->pdata->gpio_base + gpio, + PCF50633_GPIO_CONFIG_OUTPUT); + if (!ret) + pcf50633_gpio_set_value(chip, gpio, value); + + return ret; +} + +static int pcf50633_gpio_direction_input(struct gpio_chip *chip, unsigned gpio) +{ + return -ENOSYS; +} + +int pcf50633_gpio_set_config(struct pcf50633 *pcf, unsigned gpio, + enum pcf50633_gpio_config config) +{ + u8 reg; + u8 direction; + int ret; + + gpio -= pcf->pdata->gpio_base; + + if (gpio < 3) { + direction = (config == PCF50633_GPIO_CONFIG_INPUT) ? (1 << gpio) : 0; + ret = pcf50633_reg_set_bit_mask(pcf, PCF50633_REG_GPIOCTL, (1 << gpio), + direction); + if (ret) { + return ret; + } + } else if (gpio > 3 || config == PCF50633_GPIO_CONFIG_INPUT) { + return -EINVAL; + } + + if (config != PCF50633_GPIO_CONFIG_INPUT) { + reg = PCF50633_REG_GPIOCFG(gpio); + ret = pcf50633_reg_set_bit_mask(pcf, reg, 0x0f, config); + } + + return ret; +} +EXPORT_SYMBOL_GPL(pcf50633_gpio_set_config); + +int pcf50633_gpio_power_supply_set(struct pcf50633 *pcf, + int gpio, int regulator, int on) +{ + u8 reg, val, mask; + + gpio -= pcf->pdata->gpio_base; + + /* the *ENA register is always one after the *OUT register */ + reg = pcf50633_regulator_registers[regulator] + 1; + + val = !!on << (gpio - PCF50633_GPIO1); + mask = 1 << (gpio - PCF50633_GPIO1); + + return pcf50633_reg_set_bit_mask(pcf, reg, mask, val); +} +EXPORT_SYMBOL_GPL(pcf50633_gpio_power_supply_set); + + +static int __devinit pcf50633_gpio_probe(struct platform_device *pdev) +{ + struct pcf50633 *pcf = dev_to_pcf50633(pdev->dev.parent); + struct pcf50633_gpio *pcf_gpio; + + pcf_gpio = kzalloc(sizeof(*pcf_gpio), GFP_KERNEL); + + if (!pcf_gpio) + return -ENOMEM; + + pcf_gpio->pcf = pcf; + + pcf_gpio->chip.direction_input = pcf50633_gpio_direction_input; + pcf_gpio->chip.direction_output = pcf50633_gpio_direction_output; + pcf_gpio->chip.get = pcf50633_gpio_get_value; + pcf_gpio->chip.set = pcf50633_gpio_set_value; + + pcf_gpio->chip.base = pcf->pdata->gpio_base; + pcf_gpio->chip.ngpio = 4; + pcf_gpio->chip.label = dev_name(pcf->dev); + pcf_gpio->chip.can_sleep = 1; + pcf_gpio->chip.owner = THIS_MODULE; + pcf_gpio->chip.dev = &pdev->dev; + + platform_set_drvdata(pdev, pcf_gpio); + + return gpiochip_add(&pcf_gpio->chip); +} + +static int __devexit pcf50633_gpio_remove(struct platform_device *pdev) +{ + struct pcf50633_gpio *pcf_gpio = platform_get_drvdata(pdev); + + gpiochip_remove(&pcf_gpio->chip); + + platform_set_drvdata(pdev, NULL); + kfree(pcf_gpio); + + return 0; +} + +static struct platform_driver pcf50633_gpio_driver = { + .probe = pcf50633_gpio_probe, + .remove = __devexit_p(pcf50633_gpio_remove), + .driver = { + .name = "pcf50633-gpio", + .owner = THIS_MODULE, + }, +}; + +int __init pcf50633_gpio_init(void) +{ + return platform_driver_register(&pcf50633_gpio_driver); +} +module_init(pcf50633_gpio_init); + +void __exit pcf50633_gpio_exit(void) +{ + platform_driver_unregister(&pcf50633_gpio_driver); +} +module_exit(pcf50633_gpio_exit); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("GPIO driver for the PCF50633"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 2a5a0b78f84..64fd21ef442 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -334,13 +334,6 @@ config PCF50633_ADC Say yes here if you want to include support for ADC in the NXP PCF50633 chip. -config PCF50633_GPIO - tristate "Support for NXP PCF50633 GPIO" - depends on MFD_PCF50633 - help - Say yes here if you want to include support GPIO for pins on - the PCF50633 chip. - config AB3100_CORE bool "ST-Ericsson AB3100 Mixed Signal Circuit core functions" depends on I2C=y diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 22715add99a..b11883f600b 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -56,10 +56,9 @@ obj-$(CONFIG_MFD_MAX8925) += max8925.o obj-$(CONFIG_MFD_PCF50633) += pcf50633-core.o obj-$(CONFIG_PCF50633_ADC) += pcf50633-adc.o -obj-$(CONFIG_PCF50633_GPIO) += pcf50633-gpio.o obj-$(CONFIG_AB3100_CORE) += ab3100-core.o obj-$(CONFIG_AB3100_OTP) += ab3100-otp.o obj-$(CONFIG_AB4500_CORE) += ab4500-core.o obj-$(CONFIG_MFD_TIMBERDALE) += timberdale.o obj-$(CONFIG_PMIC_ADP5520) += adp5520.o -obj-$(CONFIG_LPC_SCH) += lpc_sch.o \ No newline at end of file +obj-$(CONFIG_LPC_SCH) += lpc_sch.o diff --git a/drivers/mfd/pcf50633-gpio.c b/drivers/mfd/pcf50633-gpio.c deleted file mode 100644 index eb044e805bf..00000000000 --- a/drivers/mfd/pcf50633-gpio.c +++ /dev/null @@ -1,228 +0,0 @@ -/* NXP PCF50633 GPIO Driver - * - * (C) 2006-2008 by Openmoko, Inc. - * Author: Balaji Rao - * Copyright 2010, Lars-Peter Clausen - * All rights reserved. - * - * Broken down from monstrous PCF50633 driver mainly by - * Harald Welte, Andy Green and Werner Almesberger - * - * 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 -#include -#include -#include - -#include -#include -#include - -#define PCF50633_REG_GPIOCTL 0x13 -#define PCF50633_REG_GPIOCFG(x) (0x14 + (x)) - -enum pcf50633_regulator_id { - PCF50633_REGULATOR_AUTO, - PCF50633_REGULATOR_DOWN1, - PCF50633_REGULATOR_DOWN2, - PCF50633_REGULATOR_LDO1, - PCF50633_REGULATOR_LDO2, - PCF50633_REGULATOR_LDO3, - PCF50633_REGULATOR_LDO4, - PCF50633_REGULATOR_LDO5, - PCF50633_REGULATOR_LDO6, - PCF50633_REGULATOR_HCLDO, - PCF50633_REGULATOR_MEMLDO, -}; - -#define PCF50633_REG_AUTOOUT 0x1a -#define PCF50633_REG_DOWN1OUT 0x1e -#define PCF50633_REG_DOWN2OUT 0x22 -#define PCF50633_REG_MEMLDOOUT 0x26 -#define PCF50633_REG_LDO1OUT 0x2d -#define PCF50633_REG_LDO2OUT 0x2f -#define PCF50633_REG_LDO3OUT 0x31 -#define PCF50633_REG_LDO4OUT 0x33 -#define PCF50633_REG_LDO5OUT 0x35 -#define PCF50633_REG_LDO6OUT 0x37 -#define PCF50633_REG_HCLDOOUT 0x39 - -static const u8 pcf50633_regulator_registers[PCF50633_NUM_REGULATORS] = { - [PCF50633_REGULATOR_AUTO] = PCF50633_REG_AUTOOUT, - [PCF50633_REGULATOR_DOWN1] = PCF50633_REG_DOWN1OUT, - [PCF50633_REGULATOR_DOWN2] = PCF50633_REG_DOWN2OUT, - [PCF50633_REGULATOR_MEMLDO] = PCF50633_REG_MEMLDOOUT, - [PCF50633_REGULATOR_LDO1] = PCF50633_REG_LDO1OUT, - [PCF50633_REGULATOR_LDO2] = PCF50633_REG_LDO2OUT, - [PCF50633_REGULATOR_LDO3] = PCF50633_REG_LDO3OUT, - [PCF50633_REGULATOR_LDO4] = PCF50633_REG_LDO4OUT, - [PCF50633_REGULATOR_LDO5] = PCF50633_REG_LDO5OUT, - [PCF50633_REGULATOR_LDO6] = PCF50633_REG_LDO6OUT, - [PCF50633_REGULATOR_HCLDO] = PCF50633_REG_HCLDOOUT, -}; - -struct pcf50633_gpio { - struct pcf50633 *pcf; - struct gpio_chip chip; -}; - -static inline struct pcf50633 *gpio_chip_to_pcf50633(struct gpio_chip *chip) -{ - struct pcf50633 *pcf = dev_to_pcf50633(chip->dev->parent); - return pcf; -} - -static void pcf50633_gpio_set_value(struct gpio_chip *chip, unsigned gpio, int value) -{ - struct pcf50633 *pcf = gpio_chip_to_pcf50633(chip); - u8 reg; - - reg = PCF50633_REG_GPIOCFG(gpio); - - pcf50633_reg_set_bit_mask(pcf, reg, 0x07, value ? 0x7 : 0x0); -} - -static int pcf50633_gpio_get_value(struct gpio_chip *chip, unsigned gpio) -{ - struct pcf50633 *pcf = gpio_chip_to_pcf50633(chip); - return pcf50633_reg_read(pcf, PCF50633_REG_GPIOCFG(gpio)) >> 3; -} - - -static int pcf50633_gpio_direction_output(struct gpio_chip *chip, unsigned gpio, - int value) -{ - struct pcf50633 *pcf = gpio_chip_to_pcf50633(chip); - int ret; - - ret = pcf50633_gpio_set_config(pcf, pcf->pdata->gpio_base + gpio, - PCF50633_GPIO_CONFIG_OUTPUT); - if (!ret) - pcf50633_gpio_set_value(chip, gpio, value); - - return ret; -} - -static int pcf50633_gpio_direction_input(struct gpio_chip *chip, unsigned gpio) -{ - return -ENOSYS; -} - -int pcf50633_gpio_set_config(struct pcf50633 *pcf, unsigned gpio, - enum pcf50633_gpio_config config) -{ - u8 reg; - u8 direction; - int ret; - - gpio -= pcf->pdata->gpio_base; - - if (gpio < 3) { - direction = (config == PCF50633_GPIO_CONFIG_INPUT) ? (1 << gpio) : 0; - ret = pcf50633_reg_set_bit_mask(pcf, PCF50633_REG_GPIOCTL, (1 << gpio), - direction); - if (ret) { - return ret; - } - } else if (gpio > 3 || config == PCF50633_GPIO_CONFIG_INPUT) { - return -EINVAL; - } - - if (config != PCF50633_GPIO_CONFIG_INPUT) { - reg = PCF50633_REG_GPIOCFG(gpio); - ret = pcf50633_reg_set_bit_mask(pcf, reg, 0x0f, config); - } - - return ret; -} -EXPORT_SYMBOL_GPL(pcf50633_gpio_set_config); - -int pcf50633_gpio_power_supply_set(struct pcf50633 *pcf, - int gpio, int regulator, int on) -{ - u8 reg, val, mask; - - gpio -= pcf->pdata->gpio_base; - - /* the *ENA register is always one after the *OUT register */ - reg = pcf50633_regulator_registers[regulator] + 1; - - val = !!on << (gpio - PCF50633_GPIO1); - mask = 1 << (gpio - PCF50633_GPIO1); - - return pcf50633_reg_set_bit_mask(pcf, reg, mask, val); -} -EXPORT_SYMBOL_GPL(pcf50633_gpio_power_supply_set); - - -static int __devinit pcf50633_gpio_probe(struct platform_device *pdev) -{ - struct pcf50633 *pcf = dev_to_pcf50633(pdev->dev.parent); - struct pcf50633_gpio *pcf_gpio; - - pcf_gpio = kzalloc(sizeof(*pcf_gpio), GFP_KERNEL); - - if (!pcf_gpio) - return -ENOMEM; - - pcf_gpio->pcf = pcf; - - pcf_gpio->chip.direction_input = pcf50633_gpio_direction_input; - pcf_gpio->chip.direction_output = pcf50633_gpio_direction_output; - pcf_gpio->chip.get = pcf50633_gpio_get_value; - pcf_gpio->chip.set = pcf50633_gpio_set_value; - - pcf_gpio->chip.base = pcf->pdata->gpio_base; - pcf_gpio->chip.ngpio = 4; - pcf_gpio->chip.label = dev_name(pcf->dev); - pcf_gpio->chip.can_sleep = 1; - pcf_gpio->chip.owner = THIS_MODULE; - pcf_gpio->chip.dev = &pdev->dev; - - platform_set_drvdata(pdev, pcf_gpio); - - return gpiochip_add(&pcf_gpio->chip); -} - -static int __devexit pcf50633_gpio_remove(struct platform_device *pdev) -{ - struct pcf50633_gpio *pcf_gpio = platform_get_drvdata(pdev); - - gpiochip_remove(&pcf_gpio->chip); - - platform_set_drvdata(pdev, NULL); - kfree(pcf_gpio); - - return 0; -} - -static struct platform_driver pcf50633_gpio_driver = { - .probe = pcf50633_gpio_probe, - .remove = __devexit_p(pcf50633_gpio_remove), - .driver = { - .name = "pcf50633-gpio", - .owner = THIS_MODULE, - }, -}; - -int __init pcf50633_gpio_init(void) -{ - return platform_driver_register(&pcf50633_gpio_driver); -} -module_init(pcf50633_gpio_init); - -void __exit pcf50633_gpio_exit(void) -{ - platform_driver_unregister(&pcf50633_gpio_driver); -} -module_exit(pcf50633_gpio_exit); - -MODULE_AUTHOR("Lars-Peter Clausen "); -MODULE_DESCRIPTION("GPIO driver for the PCF50633"); -MODULE_LICENSE("GPL"); -- cgit v1.2.3 From d48a90d8d65023564a5d52f2c50c78b4ce06a61c Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Fri, 7 May 2010 20:07:29 +0200 Subject: pcf50633: Use threaded irq Use a threaded irq instead of normal irq and a workqueue. --- drivers/mfd/Makefile | 2 +- drivers/mfd/pcf50633-core.c | 57 +++++++-------------------------------------- 2 files changed, 10 insertions(+), 49 deletions(-) diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index b11883f600b..6741cd0fc6c 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -54,7 +54,7 @@ obj-$(CONFIG_PMIC_DA903X) += da903x.o max8925-objs := max8925-core.o max8925-i2c.o obj-$(CONFIG_MFD_MAX8925) += max8925.o -obj-$(CONFIG_MFD_PCF50633) += pcf50633-core.o +obj-$(CONFIG_MFD_PCF50633) += pcf50633-core.o pcf50633-irq.o obj-$(CONFIG_PCF50633_ADC) += pcf50633-adc.o obj-$(CONFIG_AB3100_CORE) += ab3100-core.o obj-$(CONFIG_AB3100_OTP) += ab3100-otp.o diff --git a/drivers/mfd/pcf50633-core.c b/drivers/mfd/pcf50633-core.c index 0a2c9aa3e23..30058126abc 100644 --- a/drivers/mfd/pcf50633-core.c +++ b/drivers/mfd/pcf50633-core.c @@ -325,14 +325,12 @@ static void pcf50633_irq_call_handler(struct pcf50633 *pcf, int irq) /* Maximum amount of time ONKEY is held before emergency action is taken */ #define PCF50633_ONKEY1S_TIMEOUT 8 -static void pcf50633_irq_worker(struct work_struct *work) +static irqreturn_t pcf50633_irq(int irq, void *data) { - struct pcf50633 *pcf; + struct pcf50633 *pcf = data; int ret, i, j; u8 pcf_int[5], chgstat; - pcf = container_of(work, struct pcf50633, irq_work); - /* Read the 5 INT regs in one transaction */ ret = pcf50633_read_block(pcf, PCF50633_REG_INT1, ARRAY_SIZE(pcf_int), pcf_int); @@ -436,21 +434,7 @@ static void pcf50633_irq_worker(struct work_struct *work) } out: - put_device(pcf->dev); - enable_irq(pcf->irq); -} - -static irqreturn_t pcf50633_irq(int irq, void *data) -{ - struct pcf50633 *pcf = data; - - dev_dbg(pcf->dev, "pcf50633_irq\n"); - - get_device(pcf->dev); - disable_irq_nosync(pcf->irq); - queue_work(pcf->work_queue, &pcf->irq_work); - - return IRQ_HANDLED; + return IRQ_HANDLED } static void @@ -488,9 +472,6 @@ static int pcf50633_suspend(struct i2c_client *client, pm_message_t state) * henceforth */ disable_irq(pcf->irq); - /* Make sure that any running IRQ worker has quit */ - cancel_work_sync(&pcf->irq_work); - /* Save the masks */ ret = pcf50633_read_block(pcf, PCF50633_REG_INT1M, ARRAY_SIZE(pcf->suspend_irq_masks), @@ -531,16 +512,7 @@ static int pcf50633_resume(struct i2c_client *client) if (ret < 0) dev_err(pcf->dev, "Error restoring saved suspend masks\n"); - /* Restore regulators' state */ - - - get_device(pcf->dev); - - /* - * Clear any pending interrupts and set resume reason if any. - * This will leave with enable_irq() - */ - pcf50633_irq_worker(&pcf->irq_work); + enable_irq(pcf->irq); return 0; } @@ -574,22 +546,13 @@ static int __devinit pcf50633_probe(struct i2c_client *client, pcf->dev = &client->dev; pcf->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"); - ret = -ENOMEM; - goto err_free; - } - - INIT_WORK(&pcf->irq_work, pcf50633_irq_worker); version = pcf50633_reg_read(pcf, 0); variant = pcf50633_reg_read(pcf, 1); if (version < 0 || variant < 0) { dev_err(pcf->dev, "Unable to probe pcf50633\n"); ret = -ENODEV; - goto err_destroy_workqueue; + goto err_free; } dev_info(pcf->dev, "Probed device version %d variant %d\n", @@ -603,12 +566,13 @@ 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); - ret = request_irq(client->irq, pcf50633_irq, - IRQF_TRIGGER_LOW, "pcf50633", pcf); + ret = request_threaded_irq(client->irq, NULL, pcf50633_irq, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "pcf50633", pcf); if (ret) { dev_err(pcf->dev, "Failed to request IRQ %d\n", ret); - goto err_destroy_workqueue; + goto err_free; } /* Create sub devices */ @@ -655,8 +619,6 @@ static int __devinit pcf50633_probe(struct i2c_client *client, return 0; -err_destroy_workqueue: - destroy_workqueue(pcf->work_queue); err_free: i2c_set_clientdata(client, NULL); kfree(pcf); @@ -670,7 +632,6 @@ static int __devexit pcf50633_remove(struct i2c_client *client) int i; free_irq(pcf->irq, pcf); - destroy_workqueue(pcf->work_queue); platform_device_unregister(pcf->gpio_pdev); platform_device_unregister(pcf->input_pdev); -- cgit v1.2.3 From 7c3f9799aa31d974507bf73e8c29172ecc672ecf Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Fri, 7 May 2010 20:28:00 +0200 Subject: pcf50633: Move irq related functions to it's own file. This reduces the code clutter a bit and will ease an the migration to genirq. --- drivers/mfd/Makefile | 3 +- drivers/mfd/pcf50633-core.c | 303 ++--------------------------------------- drivers/mfd/pcf50633-irq.c | 319 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 331 insertions(+), 294 deletions(-) create mode 100644 drivers/mfd/pcf50633-irq.c diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 6741cd0fc6c..ea52d37a76b 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -54,7 +54,8 @@ obj-$(CONFIG_PMIC_DA903X) += da903x.o max8925-objs := max8925-core.o max8925-i2c.o obj-$(CONFIG_MFD_MAX8925) += max8925.o -obj-$(CONFIG_MFD_PCF50633) += pcf50633-core.o pcf50633-irq.o +pcf50633-objs := pcf50633-core.o pcf50633-irq.o +obj-$(CONFIG_MFD_PCF50633) += pcf50633.o obj-$(CONFIG_PCF50633_ADC) += pcf50633-adc.o obj-$(CONFIG_AB3100_CORE) += ab3100-core.o obj-$(CONFIG_AB3100_OTP) += ab3100-otp.o diff --git a/drivers/mfd/pcf50633-core.c b/drivers/mfd/pcf50633-core.c index 30058126abc..9c14dbf02b0 100644 --- a/drivers/mfd/pcf50633-core.c +++ b/drivers/mfd/pcf50633-core.c @@ -21,16 +21,16 @@ #include #include #include -#include #include #include -/* Two MBCS registers used during cold start */ -#define PCF50633_REG_MBCS1 0x4b -#define PCF50633_REG_MBCS2 0x4c -#define PCF50633_MBCS1_USBPRES 0x01 -#define PCF50633_MBCS1_ADAPTPRES 0x01 +int pcf50633_irq_init(struct pcf50633 *pcf, int irq); +void pcf50633_irq_free(struct pcf50633 *pcf); +#ifdef CONFIG_PM +int pcf50633_irq_suspend(struct pcf50633 *pcf); +int pcf50633_irq_resume(struct pcf50633 *pcf); +#endif static int __pcf50633_read(struct pcf50633 *pcf, u8 reg, int num, u8 *data) { @@ -215,228 +215,6 @@ static struct attribute_group pcf_attr_group = { .attrs = pcf_sysfs_entries, }; -int pcf50633_register_irq(struct pcf50633 *pcf, int irq, - void (*handler) (int, void *), void *data) -{ - if (irq < 0 || irq > PCF50633_NUM_IRQ || !handler) - return -EINVAL; - - if (WARN_ON(pcf->irq_handler[irq].handler)) - return -EBUSY; - - mutex_lock(&pcf->lock); - pcf->irq_handler[irq].handler = handler; - pcf->irq_handler[irq].data = data; - mutex_unlock(&pcf->lock); - - return 0; -} -EXPORT_SYMBOL_GPL(pcf50633_register_irq); - -int pcf50633_free_irq(struct pcf50633 *pcf, int irq) -{ - if (irq < 0 || irq > PCF50633_NUM_IRQ) - return -EINVAL; - - mutex_lock(&pcf->lock); - pcf->irq_handler[irq].handler = NULL; - mutex_unlock(&pcf->lock); - - return 0; -} -EXPORT_SYMBOL_GPL(pcf50633_free_irq); - -static int __pcf50633_irq_mask_set(struct pcf50633 *pcf, int irq, u8 mask) -{ - u8 reg, bits, tmp; - int ret = 0, idx; - - idx = irq >> 3; - reg = PCF50633_REG_INT1M + idx; - bits = 1 << (irq & 0x07); - - mutex_lock(&pcf->lock); - - if (mask) { - ret = __pcf50633_read(pcf, reg, 1, &tmp); - if (ret < 0) - goto out; - - tmp |= bits; - - ret = __pcf50633_write(pcf, reg, 1, &tmp); - if (ret < 0) - goto out; - - pcf->mask_regs[idx] &= ~bits; - pcf->mask_regs[idx] |= bits; - } else { - ret = __pcf50633_read(pcf, reg, 1, &tmp); - if (ret < 0) - goto out; - - tmp &= ~bits; - - ret = __pcf50633_write(pcf, reg, 1, &tmp); - if (ret < 0) - goto out; - - pcf->mask_regs[idx] &= ~bits; - } -out: - mutex_unlock(&pcf->lock); - - return ret; -} - -int pcf50633_irq_mask(struct pcf50633 *pcf, int irq) -{ - dev_dbg(pcf->dev, "Masking IRQ %d\n", irq); - - return __pcf50633_irq_mask_set(pcf, irq, 1); -} -EXPORT_SYMBOL_GPL(pcf50633_irq_mask); - -int pcf50633_irq_unmask(struct pcf50633 *pcf, int irq) -{ - dev_dbg(pcf->dev, "Unmasking IRQ %d\n", irq); - - return __pcf50633_irq_mask_set(pcf, irq, 0); -} -EXPORT_SYMBOL_GPL(pcf50633_irq_unmask); - -int pcf50633_irq_mask_get(struct pcf50633 *pcf, int irq) -{ - u8 reg, bits; - - reg = irq >> 3; - bits = 1 << (irq & 0x07); - - return pcf->mask_regs[reg] & bits; -} -EXPORT_SYMBOL_GPL(pcf50633_irq_mask_get); - -static void pcf50633_irq_call_handler(struct pcf50633 *pcf, int irq) -{ - if (pcf->irq_handler[irq].handler) - pcf->irq_handler[irq].handler(irq, pcf->irq_handler[irq].data); -} - -/* Maximum amount of time ONKEY is held before emergency action is taken */ -#define PCF50633_ONKEY1S_TIMEOUT 8 - -static irqreturn_t pcf50633_irq(int irq, void *data) -{ - struct pcf50633 *pcf = data; - int ret, i, j; - u8 pcf_int[5], chgstat; - - /* Read the 5 INT regs in one transaction */ - ret = pcf50633_read_block(pcf, PCF50633_REG_INT1, - ARRAY_SIZE(pcf_int), pcf_int); - if (ret != ARRAY_SIZE(pcf_int)) { - dev_err(pcf->dev, "Error reading INT registers\n"); - - /* - * If this doesn't ACK the interrupt to the chip, we'll be - * called once again as we're level triggered. - */ - 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] &= ~PCF50633_INT1_USBREM; - else - 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] &= ~PCF50633_INT1_ADPREM; - else - pcf_int[0] &= ~PCF50633_INT1_ADPINS; - } - - dev_dbg(pcf->dev, "INT1=0x%02x INT2=0x%02x INT3=0x%02x " - "INT4=0x%02x INT5=0x%02x\n", pcf_int[0], - pcf_int[1], pcf_int[2], pcf_int[3], pcf_int[4]); - - /* Some revisions of the chip don't have a 8s standby mode on - * ONKEY1S press. We try to manually do it in such cases. */ - if ((pcf_int[0] & PCF50633_INT1_SECOND) && pcf->onkey1s_held) { - dev_info(pcf->dev, "ONKEY1S held for %d secs\n", - pcf->onkey1s_held); - if (pcf->onkey1s_held++ == PCF50633_ONKEY1S_TIMEOUT) - if (pcf->pdata->force_shutdown) - pcf->pdata->force_shutdown(pcf); - } - - if (pcf_int[2] & PCF50633_INT3_ONKEY1S) { - dev_info(pcf->dev, "ONKEY1S held\n"); - pcf->onkey1s_held = 1 ; - - /* Unmask IRQ_SECOND */ - pcf50633_reg_clear_bits(pcf, PCF50633_REG_INT1M, - PCF50633_INT1_SECOND); - - /* Unmask IRQ_ONKEYR */ - pcf50633_reg_clear_bits(pcf, PCF50633_REG_INT2M, - PCF50633_INT2_ONKEYR); - } - - if ((pcf_int[1] & PCF50633_INT2_ONKEYR) && pcf->onkey1s_held) { - pcf->onkey1s_held = 0; - - /* Mask SECOND and ONKEYR interrupts */ - if (pcf->mask_regs[0] & PCF50633_INT1_SECOND) - pcf50633_reg_set_bit_mask(pcf, - PCF50633_REG_INT1M, - PCF50633_INT1_SECOND, - PCF50633_INT1_SECOND); - - if (pcf->mask_regs[1] & PCF50633_INT2_ONKEYR) - pcf50633_reg_set_bit_mask(pcf, - PCF50633_REG_INT2M, - PCF50633_INT2_ONKEYR, - PCF50633_INT2_ONKEYR); - } - - /* Have we just resumed ? */ - if (pcf->is_suspended) { - pcf->is_suspended = 0; - - /* Set the resume reason filtering out non resumers */ - for (i = 0; i < ARRAY_SIZE(pcf_int); i++) - pcf->resume_reason[i] = pcf_int[i] & - pcf->pdata->resumers[i]; - - /* Make sure we don't pass on any ONKEY events to - * userspace now */ - pcf_int[1] &= ~(PCF50633_INT2_ONKEYR | PCF50633_INT2_ONKEYF); - } - - for (i = 0; i < ARRAY_SIZE(pcf_int); i++) { - /* Unset masked interrupts */ - pcf_int[i] &= ~pcf->mask_regs[i]; - - for (j = 0; j < 8 ; j++) - if (pcf_int[i] & (1 << j)) - pcf50633_irq_call_handler(pcf, (i * 8) + j); - } - -out: - return IRQ_HANDLED -} - static void pcf50633_client_dev_register(struct pcf50633 *pcf, const char *name, struct platform_device **pdev) @@ -463,58 +241,17 @@ pcf50633_client_dev_register(struct pcf50633 *pcf, const char *name, static int pcf50633_suspend(struct i2c_client *client, pm_message_t state) { struct pcf50633 *pcf; - int ret = 0, i; - u8 res[5]; - pcf = i2c_get_clientdata(client); - /* Make sure our interrupt handlers are not called - * henceforth */ - disable_irq(pcf->irq); - - /* Save the masks */ - ret = pcf50633_read_block(pcf, PCF50633_REG_INT1M, - ARRAY_SIZE(pcf->suspend_irq_masks), - pcf->suspend_irq_masks); - if (ret < 0) { - dev_err(pcf->dev, "error saving irq masks\n"); - goto out; - } - - /* Write wakeup irq masks */ - for (i = 0; i < ARRAY_SIZE(res); i++) - res[i] = ~pcf->pdata->resumers[i]; - - ret = pcf50633_write_block(pcf, PCF50633_REG_INT1M, - ARRAY_SIZE(res), &res[0]); - if (ret < 0) { - dev_err(pcf->dev, "error writing wakeup irq masks\n"); - goto out; - } - - pcf->is_suspended = 1; - -out: - return ret; + return pcf50633_irq_suspend(pcf); } static int pcf50633_resume(struct i2c_client *client) { struct pcf50633 *pcf; - int ret; - pcf = i2c_get_clientdata(client); - /* Write the saved mask registers */ - ret = pcf50633_write_block(pcf, PCF50633_REG_INT1M, - ARRAY_SIZE(pcf->suspend_irq_masks), - pcf->suspend_irq_masks); - if (ret < 0) - dev_err(pcf->dev, "Error restoring saved suspend masks\n"); - - enable_irq(pcf->irq); - - return 0; + return pcf50633_irq_resume(pcf); } #else #define pcf50633_suspend NULL @@ -545,7 +282,6 @@ static int __devinit pcf50633_probe(struct i2c_client *client, i2c_set_clientdata(client, pcf); pcf->dev = &client->dev; pcf->i2c_client = client; - pcf->irq = client->irq; version = pcf50633_reg_read(pcf, 0); variant = pcf50633_reg_read(pcf, 1); @@ -558,22 +294,7 @@ static int __devinit pcf50633_probe(struct i2c_client *client, dev_info(pcf->dev, "Probed device version %d variant %d\n", version, variant); - /* Enable all interrupts except RTC SECOND */ - pcf->mask_regs[0] = 0x80; - pcf50633_reg_write(pcf, PCF50633_REG_INT1M, pcf->mask_regs[0]); - pcf50633_reg_write(pcf, PCF50633_REG_INT2M, 0x00); - pcf50633_reg_write(pcf, PCF50633_REG_INT3M, 0x00); - pcf50633_reg_write(pcf, PCF50633_REG_INT4M, 0x00); - pcf50633_reg_write(pcf, PCF50633_REG_INT5M, 0x00); - - ret = request_threaded_irq(client->irq, NULL, pcf50633_irq, - IRQF_TRIGGER_LOW | IRQF_ONESHOT, - "pcf50633", pcf); - - if (ret) { - dev_err(pcf->dev, "Failed to request IRQ %d\n", ret); - goto err_free; - } + pcf50633_irq_init(pcf, client->irq); /* Create sub devices */ pcf50633_client_dev_register(pcf, "pcf50633-input", @@ -606,10 +327,6 @@ static int __devinit pcf50633_probe(struct i2c_client *client, platform_device_add(pdev); } - if (enable_irq_wake(client->irq) < 0) - dev_err(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"); @@ -631,7 +348,7 @@ static int __devexit pcf50633_remove(struct i2c_client *client) struct pcf50633 *pcf = i2c_get_clientdata(client); int i; - free_irq(pcf->irq, pcf); + pcf50633_irq_free(pcf); platform_device_unregister(pcf->gpio_pdev); platform_device_unregister(pcf->input_pdev); diff --git a/drivers/mfd/pcf50633-irq.c b/drivers/mfd/pcf50633-irq.c new file mode 100644 index 00000000000..c1923cf1b06 --- /dev/null +++ b/drivers/mfd/pcf50633-irq.c @@ -0,0 +1,319 @@ +/* NXP PCF50633 Power Management Unit (PMU) driver + * + * (C) 2006-2008 by Openmoko, Inc. + * Author: Harald Welte + * Balaji Rao + * 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. + * + */ + +#include +#include +#include +#include + +#include + +/* Two MBCS registers used during cold start */ +#define PCF50633_REG_MBCS1 0x4b +#define PCF50633_REG_MBCS2 0x4c +#define PCF50633_MBCS1_USBPRES 0x01 +#define PCF50633_MBCS1_ADAPTPRES 0x01 + +int pcf50633_register_irq(struct pcf50633 *pcf, int irq, + void (*handler) (int, void *), void *data) +{ + if (irq < 0 || irq > PCF50633_NUM_IRQ || !handler) + return -EINVAL; + + if (WARN_ON(pcf->irq_handler[irq].handler)) + return -EBUSY; + + mutex_lock(&pcf->lock); + pcf->irq_handler[irq].handler = handler; + pcf->irq_handler[irq].data = data; + mutex_unlock(&pcf->lock); + + return 0; +} +EXPORT_SYMBOL_GPL(pcf50633_register_irq); + +int pcf50633_free_irq(struct pcf50633 *pcf, int irq) +{ + if (irq < 0 || irq > PCF50633_NUM_IRQ) + return -EINVAL; + + mutex_lock(&pcf->lock); + pcf->irq_handler[irq].handler = NULL; + mutex_unlock(&pcf->lock); + + return 0; +} +EXPORT_SYMBOL_GPL(pcf50633_free_irq); + +static int __pcf50633_irq_mask_set(struct pcf50633 *pcf, int irq, u8 mask) +{ + u8 reg, bit; + int ret = 0, idx; + + idx = irq >> 3; + reg = PCF50633_REG_INT1M + idx; + bit = 1 << (irq & 0x07); + + pcf50633_reg_set_bit_mask(pcf, reg, bit, mask ? bit : 0); + + mutex_lock(&pcf->lock); + + if (mask) + pcf->mask_regs[idx] |= bit; + else + pcf->mask_regs[idx] &= ~bit; + + mutex_unlock(&pcf->lock); + + return ret; +} + +int pcf50633_irq_mask(struct pcf50633 *pcf, int irq) +{ + dev_dbg(pcf->dev, "Masking IRQ %d\n", irq); + + return __pcf50633_irq_mask_set(pcf, irq, 1); +} +EXPORT_SYMBOL_GPL(pcf50633_irq_mask); + +int pcf50633_irq_unmask(struct pcf50633 *pcf, int irq) +{ + dev_dbg(pcf->dev, "Unmasking IRQ %d\n", irq); + + return __pcf50633_irq_mask_set(pcf, irq, 0); +} +EXPORT_SYMBOL_GPL(pcf50633_irq_unmask); + +int pcf50633_irq_mask_get(struct pcf50633 *pcf, int irq) +{ + u8 reg, bits; + + reg = irq >> 3; + bits = 1 << (irq & 0x07); + + return pcf->mask_regs[reg] & bits; +} +EXPORT_SYMBOL_GPL(pcf50633_irq_mask_get); + +static void pcf50633_irq_call_handler(struct pcf50633 *pcf, int irq) +{ + if (pcf->irq_handler[irq].handler) + pcf->irq_handler[irq].handler(irq, pcf->irq_handler[irq].data); +} + +/* Maximum amount of time ONKEY is held before emergency action is taken */ +#define PCF50633_ONKEY1S_TIMEOUT 8 + +static irqreturn_t pcf50633_irq(int irq, void *data) +{ + struct pcf50633 *pcf = data; + int ret, i, j; + u8 pcf_int[5], chgstat; + + /* Read the 5 INT regs in one transaction */ + ret = pcf50633_read_block(pcf, PCF50633_REG_INT1, + ARRAY_SIZE(pcf_int), pcf_int); + if (ret != ARRAY_SIZE(pcf_int)) { + dev_err(pcf->dev, "Error reading INT registers\n"); + + /* + * If this doesn't ACK the interrupt to the chip, we'll be + * called once again as we're level triggered. + */ + 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] &= ~PCF50633_INT1_USBREM; + else + 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] &= ~PCF50633_INT1_ADPREM; + else + pcf_int[0] &= ~PCF50633_INT1_ADPINS; + } + + dev_dbg(pcf->dev, "INT1=0x%02x INT2=0x%02x INT3=0x%02x " + "INT4=0x%02x INT5=0x%02x\n", pcf_int[0], + pcf_int[1], pcf_int[2], pcf_int[3], pcf_int[4]); + + /* Some revisions of the chip don't have a 8s standby mode on + * ONKEY1S press. We try to manually do it in such cases. */ + if ((pcf_int[0] & PCF50633_INT1_SECOND) && pcf->onkey1s_held) { + dev_info(pcf->dev, "ONKEY1S held for %d secs\n", + pcf->onkey1s_held); + if (pcf->onkey1s_held++ == PCF50633_ONKEY1S_TIMEOUT) + if (pcf->pdata->force_shutdown) + pcf->pdata->force_shutdown(pcf); + } + + if (pcf_int[2] & PCF50633_INT3_ONKEY1S) { + dev_info(pcf->dev, "ONKEY1S held\n"); + pcf->onkey1s_held = 1 ; + + /* Unmask IRQ_SECOND */ + pcf50633_reg_clear_bits(pcf, PCF50633_REG_INT1M, + PCF50633_INT1_SECOND); + + /* Unmask IRQ_ONKEYR */ + pcf50633_reg_clear_bits(pcf, PCF50633_REG_INT2M, + PCF50633_INT2_ONKEYR); + } + + if ((pcf_int[1] & PCF50633_INT2_ONKEYR) && pcf->onkey1s_held) { + pcf->onkey1s_held = 0; + + /* Mask SECOND and ONKEYR interrupts */ + if (pcf->mask_regs[0] & PCF50633_INT1_SECOND) + pcf50633_reg_set_bit_mask(pcf, + PCF50633_REG_INT1M, + PCF50633_INT1_SECOND, + PCF50633_INT1_SECOND); + + if (pcf->mask_regs[1] & PCF50633_INT2_ONKEYR) + pcf50633_reg_set_bit_mask(pcf, + PCF50633_REG_INT2M, + PCF50633_INT2_ONKEYR, + PCF50633_INT2_ONKEYR); + } + + /* Have we just resumed ? */ + if (pcf->is_suspended) { + pcf->is_suspended = 0; + + /* Set the resume reason filtering out non resumers */ + for (i = 0; i < ARRAY_SIZE(pcf_int); i++) + pcf->resume_reason[i] = pcf_int[i] & + pcf->pdata->resumers[i]; + + /* Make sure we don't pass on any ONKEY events to + * userspace now */ + pcf_int[1] &= ~(PCF50633_INT2_ONKEYR | PCF50633_INT2_ONKEYF); + } + + for (i = 0; i < ARRAY_SIZE(pcf_int); i++) { + /* Unset masked interrupts */ + pcf_int[i] &= ~pcf->mask_regs[i]; + + for (j = 0; j < 8 ; j++) + if (pcf_int[i] & (1 << j)) + pcf50633_irq_call_handler(pcf, (i * 8) + j); + } + +out: + return IRQ_HANDLED; +} + +#ifdef CONFIG_PM + +int pcf50633_irq_suspend(struct pcf50633 *pcf) +{ + int ret; + int i; + u8 res[5]; + + + /* Make sure our interrupt handlers are not called + * henceforth */ + disable_irq(pcf->irq); + + /* Save the masks */ + ret = pcf50633_read_block(pcf, PCF50633_REG_INT1M, + ARRAY_SIZE(pcf->suspend_irq_masks), + pcf->suspend_irq_masks); + if (ret < 0) { + dev_err(pcf->dev, "error saving irq masks\n"); + goto out; + } + + /* Write wakeup irq masks */ + for (i = 0; i < ARRAY_SIZE(res); i++) + res[i] = ~pcf->pdata->resumers[i]; + + ret = pcf50633_write_block(pcf, PCF50633_REG_INT1M, + ARRAY_SIZE(res), &res[0]); + if (ret < 0) { + dev_err(pcf->dev, "error writing wakeup irq masks\n"); + goto out; + } + + pcf->is_suspended = 1; + +out: + return ret; +} + +int pcf50633_irq_resume(struct pcf50633 *pcf) +{ + int ret; + + /* Write the saved mask registers */ + ret = pcf50633_write_block(pcf, PCF50633_REG_INT1M, + ARRAY_SIZE(pcf->suspend_irq_masks), + pcf->suspend_irq_masks); + if (ret < 0) + dev_err(pcf->dev, "Error restoring saved suspend masks\n"); + + enable_irq(pcf->irq); + + return ret; +} + +#endif + +int pcf50633_irq_init(struct pcf50633 *pcf, int irq) +{ + int ret; + + pcf->irq = irq; + + /* Enable all interrupts except RTC SECOND */ + pcf->mask_regs[0] = 0x80; + pcf50633_reg_write(pcf, PCF50633_REG_INT1M, pcf->mask_regs[0]); + pcf50633_reg_write(pcf, PCF50633_REG_INT2M, 0x00); + pcf50633_reg_write(pcf, PCF50633_REG_INT3M, 0x00); + pcf50633_reg_write(pcf, PCF50633_REG_INT4M, 0x00); + pcf50633_reg_write(pcf, PCF50633_REG_INT5M, 0x00); + + ret = request_threaded_irq(irq, NULL, pcf50633_irq, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "pcf50633", pcf); + + if (ret) { + dev_err(pcf->dev, "Failed to request IRQ %d\n", ret); + } + + if (enable_irq_wake(irq) < 0) + dev_err(pcf->dev, "IRQ %u cannot be enabled as wake-up source" + "in this hardware revision", irq); + + return ret; +} + +void pcf50633_irq_free(struct pcf50633 *pcf) +{ + free_irq(pcf->irq, pcf); +} -- cgit v1.2.3 From 09cb945ad762e9a7f258287942918dd4a3badae4 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sun, 4 Oct 2009 13:47:44 +0200 Subject: Add glamo driver. --- drivers/mfd/Kconfig | 1 + drivers/mfd/Makefile | 3 +- drivers/mfd/glamo/Kconfig | 42 ++ drivers/mfd/glamo/Makefile | 11 + drivers/mfd/glamo/glamo-core.c | 1289 ++++++++++++++++++++++++++++++++++++++++ drivers/mfd/glamo/glamo-core.h | 61 ++ drivers/mfd/glamo/glamo-fb.c | 982 ++++++++++++++++++++++++++++++ drivers/mfd/glamo/glamo-gpio.c | 285 +++++++++ drivers/mfd/glamo/glamo-mci.c | 1023 +++++++++++++++++++++++++++++++ drivers/mfd/glamo/glamo-regs.h | 630 ++++++++++++++++++++ include/linux/fb.h | 1 + include/linux/glamofb.h | 35 ++ include/linux/mfd/glamo.h | 52 ++ 13 files changed, 4414 insertions(+), 1 deletion(-) create mode 100644 drivers/mfd/glamo/Kconfig create mode 100644 drivers/mfd/glamo/Makefile create mode 100644 drivers/mfd/glamo/glamo-core.c create mode 100644 drivers/mfd/glamo/glamo-core.h create mode 100644 drivers/mfd/glamo/glamo-fb.c create mode 100644 drivers/mfd/glamo/glamo-gpio.c create mode 100644 drivers/mfd/glamo/glamo-mci.c create mode 100644 drivers/mfd/glamo/glamo-regs.h create mode 100644 include/linux/glamofb.h create mode 100644 include/linux/mfd/glamo.h diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 2a5a0b78f84..d72b9853290 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -398,6 +398,7 @@ config LPC_SCH help LPC bridge function of the Intel SCH provides support for System Management Bus and General Purpose I/O. +source "drivers/mfd/glamo/Kconfig" endmenu diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 22715add99a..b0c59712244 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -7,6 +7,7 @@ obj-$(CONFIG_MFD_88PM860X) += 88pm860x.o obj-$(CONFIG_MFD_SM501) += sm501.o obj-$(CONFIG_MFD_ASIC3) += asic3.o tmio_core.o obj-$(CONFIG_MFD_SH_MOBILE_SDHI) += sh_mobile_sdhi.o +obj-$(CONFIG_MFD_GLAMO) += glamo/ obj-$(CONFIG_HTC_EGPIO) += htc-egpio.o obj-$(CONFIG_HTC_PASIC3) += htc-pasic3.o @@ -62,4 +63,4 @@ obj-$(CONFIG_AB3100_OTP) += ab3100-otp.o obj-$(CONFIG_AB4500_CORE) += ab4500-core.o obj-$(CONFIG_MFD_TIMBERDALE) += timberdale.o obj-$(CONFIG_PMIC_ADP5520) += adp5520.o -obj-$(CONFIG_LPC_SCH) += lpc_sch.o \ No newline at end of file +obj-$(CONFIG_LPC_SCH) += lpc_sch.o diff --git a/drivers/mfd/glamo/Kconfig b/drivers/mfd/glamo/Kconfig new file mode 100644 index 00000000000..3aa4831a4ff --- /dev/null +++ b/drivers/mfd/glamo/Kconfig @@ -0,0 +1,42 @@ +config MFD_GLAMO + bool "Smedia Glamo 336x/337x support" + select MFD_CORE + help + This enables the core driver for the Smedia Glamo 336x/337x + multi-function device. It includes irq_chip demultiplex as + well as clock / power management and GPIO support. + +config MFD_GLAMO_FB + tristate "Smedia Glamo 336x/337x framebuffer support" + depends on FB && MFD_GLAMO + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + Frame buffer driver for the LCD controller in the Smedia Glamo + 336x/337x. + + This driver is also available as a module ( = code which can be + inserted and removed from the running kernel whenever you want). The + module will be called glamofb. If you want to compile it as a module, + say M here and read . + + If unsure, say N. + +config MFD_GLAMO_GPIO + tristate "Glamo GPIO support" + depends on MFD_GLAMO + + help + Enable a bitbanging SPI adapter driver for the Smedia Glamo. + +config MFD_GLAMO_MCI + tristate "Glamo S3C SD/MMC Card Interface support" + depends on MFD_GLAMO && MMC && REGULATOR + select CRC7 + help + This selects a driver for the MCI interface found in + the S-Media GLAMO chip, as used in Openmoko + neo1973 GTA-02. + + If unsure, say N. diff --git a/drivers/mfd/glamo/Makefile b/drivers/mfd/glamo/Makefile new file mode 100644 index 00000000000..ebf26f7e473 --- /dev/null +++ b/drivers/mfd/glamo/Makefile @@ -0,0 +1,11 @@ +# +# Makefile for the Smedia Glamo framebuffer driver +# + +obj-$(CONFIG_MFD_GLAMO) += glamo-core.o +obj-$(CONFIG_MFD_GLAMO_GPIO) += glamo-gpio.o +obj-$(CONFIG_MFD_GLAMO_SPI) += glamo-spi.o + +obj-$(CONFIG_MFD_GLAMO_FB) += glamo-fb.o +obj-$(CONFIG_MFD_GLAMO_MCI) += glamo-mci.o + diff --git a/drivers/mfd/glamo/glamo-core.c b/drivers/mfd/glamo/glamo-core.c new file mode 100644 index 00000000000..23073fedea2 --- /dev/null +++ b/drivers/mfd/glamo/glamo-core.c @@ -0,0 +1,1289 @@ +/* Smedia Glamo 3362 driver + * + * (C) 2007 by Openmoko, Inc. + * Author: Harald Welte + * (C) 2009, Lars-Peter Clausen + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "glamo-regs.h" +#include "glamo-core.h" + +#define GLAMO_MEM_REFRESH_COUNT 0x100 + +#define GLAMO_NR_IRQS 9 + +#define GLAMO_IRQ_HOSTBUS 0 +#define GLAMO_IRQ_JPEG 1 +#define GLAMO_IRQ_MPEG 2 +#define GLAMO_IRQ_MPROC1 3 +#define GLAMO_IRQ_MPROC0 4 +#define GLAMO_IRQ_CMDQUEUE 5 +#define GLAMO_IRQ_2D 6 +#define GLAMO_IRQ_MMC 7 +#define GLAMO_IRQ_RISC 8 + +/* + * Glamo internal settings + * + * We run the memory interface from the faster PLLB on 2.6.28 kernels and + * above. Couple of GTA02 users report trouble with memory bus when they + * upgraded from 2.6.24. So this parameter allows reversion to 2.6.24 + * scheme if their Glamo chip needs it. + * + * you can override the faster default on kernel commandline using + * + * glamo3362.slow_memory=1 + * + * for example + */ + +static int slow_memory; +module_param(slow_memory, int, 0644); + +struct reg_range { + int start; + int count; + char *name; + unsigned dump:1; +}; + +static const struct reg_range reg_range[] = { + { 0x0000, 0x76, "General", 1 }, + { 0x0200, 0x18, "Host Bus", 1 }, + { 0x0300, 0x38, "Memory", 1 }, +/* { 0x0400, 0x100, "Sensor", 0 }, */ +/* { 0x0500, 0x300, "ISP", 0 }, */ +/* { 0x0800, 0x400, "JPEG", 0 }, */ +/* { 0x0c00, 0xcc, "MPEG", 0 }, */ + { 0x1100, 0xb2, "LCD 1", 1 }, + { 0x1200, 0x64, "LCD 2", 1 }, + { 0x1400, 0x42, "MMC", 1 }, +/* { 0x1500, 0x080, "MPU 0", 0 }, + { 0x1580, 0x080, "MPU 1", 0 }, + { 0x1600, 0x080, "Cmd Queue", 0 }, + { 0x1680, 0x080, "RISC CPU", 0 },*/ + { 0x1700, 0x400, "2D Unit", 1 }, +/* { 0x1b00, 0x900, "3D Unit", 0 }, */ +}; + +static inline void __reg_write(struct glamo_core *glamo, + uint16_t reg, uint16_t val) +{ + writew(val, glamo->base + reg); +} + +void glamo_reg_write(struct glamo_core *glamo, + uint16_t reg, uint16_t val) +{ + spin_lock(&glamo->lock); + __reg_write(glamo, reg, val); + spin_unlock(&glamo->lock); +} +EXPORT_SYMBOL_GPL(glamo_reg_write); + + +static inline uint16_t __reg_read(struct glamo_core *glamo, + uint16_t reg) +{ + return readw(glamo->base + reg); +} + +uint16_t glamo_reg_read(struct glamo_core *glamo, uint16_t reg) +{ + uint16_t val; + spin_lock(&glamo->lock); + val = __reg_read(glamo, reg); + spin_unlock(&glamo->lock); + + return val; +} +EXPORT_SYMBOL_GPL(glamo_reg_read); + +static void __reg_set_bit_mask(struct glamo_core *glamo, + uint16_t reg, uint16_t mask, + uint16_t val) +{ + uint16_t tmp; + + val &= mask; + + tmp = __reg_read(glamo, reg); + tmp &= ~mask; + tmp |= val; + __reg_write(glamo, reg, tmp); +} + +static void reg_set_bit_mask(struct glamo_core *glamo, + uint16_t reg, uint16_t mask, + uint16_t val) +{ + spin_lock(&glamo->lock); + __reg_set_bit_mask(glamo, reg, mask, val); + spin_unlock(&glamo->lock); +} + +static inline void __reg_set_bit(struct glamo_core *glamo, + uint16_t reg, uint16_t bit) +{ + uint16_t tmp; + tmp = __reg_read(glamo, reg); + tmp |= bit; + __reg_write(glamo, reg, tmp); +} + +static inline void __reg_clear_bit(struct glamo_core *glamo, + uint16_t reg, uint16_t bit) +{ + uint16_t tmp; + tmp = __reg_read(glamo, reg); + tmp &= ~bit; + __reg_write(glamo, reg, tmp); +} + +static void __reg_write_batch(struct glamo_core *glamo, uint16_t reg, + uint16_t count, uint16_t *values) +{ + uint16_t end; + for (end = reg + count * 2; reg < end; reg += 2, ++values) + __reg_write(glamo, reg, *values); +} + +static void __reg_read_batch(struct glamo_core *glamo, uint16_t reg, + uint16_t count, uint16_t *values) +{ + uint16_t end; + for (end = reg + count * 2; reg < end; reg += 2, ++values) + *values = __reg_read(glamo, reg); +} + +void glamo_reg_write_batch(struct glamo_core *glamo, uint16_t reg, + uint16_t count, uint16_t *values) +{ + spin_lock(&glamo->lock); + __reg_write_batch(glamo, reg, count, values); + spin_unlock(&glamo->lock); +} +EXPORT_SYMBOL(glamo_reg_write_batch); + +void glamo_reg_read_batch(struct glamo_core *glamo, uint16_t reg, + uint16_t count, uint16_t *values) +{ + spin_lock(&glamo->lock); + __reg_read_batch(glamo, reg, count, values); + spin_unlock(&glamo->lock); +} +EXPORT_SYMBOL(glamo_reg_read_batch); + +/*********************************************************************** + * resources of sibling devices + ***********************************************************************/ + +static struct resource glamo_fb_resources[] = { + { + .name = "glamo-fb-regs", + .start = GLAMO_REGOFS_LCD, + .end = GLAMO_REGOFS_MMC - 1, + .flags = IORESOURCE_MEM, + }, { + .name = "glamo-fb-mem", + .start = GLAMO_OFFSET_FB, + .end = GLAMO_OFFSET_FB + GLAMO_FB_SIZE - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct resource glamo_mmc_resources[] = { + { + .name = "glamo-mmc-regs", + .start = GLAMO_REGOFS_MMC, + .end = GLAMO_REGOFS_MPROC0 - 1, + .flags = IORESOURCE_MEM + }, { + .name = "glamo-mmc-mem", + .start = GLAMO_OFFSET_FB + GLAMO_FB_SIZE, + .end = GLAMO_OFFSET_FB + GLAMO_FB_SIZE + + GLAMO_MMC_BUFFER_SIZE - 1, + .flags = IORESOURCE_MEM + }, { + .start = GLAMO_IRQ_MMC, + .end = GLAMO_IRQ_MMC, + .flags = IORESOURCE_IRQ, + }, +}; + +enum glamo_cells { + GLAMO_CELL_FB, + GLAMO_CELL_MMC, + GLAMO_CELL_GPIO, +}; + +static const struct mfd_cell glamo_cells[] = { + [GLAMO_CELL_FB] = { + .name = "glamo-fb", + .num_resources = ARRAY_SIZE(glamo_fb_resources), + .resources = glamo_fb_resources, + }, + [GLAMO_CELL_MMC] = { + .name = "glamo-mci", + .num_resources = ARRAY_SIZE(glamo_mmc_resources), + .resources = glamo_mmc_resources, + }, + [GLAMO_CELL_GPIO] = { + .name = "glamo-gpio", + }, +}; + +/*********************************************************************** + * IRQ demultiplexer + ***********************************************************************/ +#define glamo_irq_bit(glamo, x) BIT(x - glamo->irq_base) + +static inline struct glamo_core *irq_to_glamo(unsigned int irq) +{ + return (struct glamo_core *)get_irq_chip_data(irq); +} + +static void glamo_ack_irq(unsigned int irq) +{ + struct glamo_core *glamo = irq_to_glamo(irq); + /* clear interrupt source */ + __reg_write(glamo, GLAMO_REG_IRQ_CLEAR, glamo_irq_bit(glamo, irq)); +} + +static void glamo_mask_irq(unsigned int irq) +{ + struct glamo_core *glamo = irq_to_glamo(irq); + + /* clear bit in enable register */ + __reg_clear_bit(glamo, GLAMO_REG_IRQ_ENABLE, glamo_irq_bit(glamo, irq)); +} + +static void glamo_unmask_irq(unsigned int irq) +{ + struct glamo_core *glamo = irq_to_glamo(irq); + + /* set bit in enable register */ + __reg_set_bit(glamo, GLAMO_REG_IRQ_ENABLE, glamo_irq_bit(glamo, irq)); +} + +static struct irq_chip glamo_irq_chip = { + .name = "glamo", + .ack = glamo_ack_irq, + .mask = glamo_mask_irq, + .unmask = glamo_unmask_irq, +}; + +static void glamo_irq_demux_handler(unsigned int irq, struct irq_desc *desc) +{ + struct glamo_core *glamo = get_irq_desc_data(desc); + desc->status &= ~(IRQ_REPLAY | IRQ_WAITING); + + if (unlikely(desc->status & IRQ_INPROGRESS)) { + desc->status |= (IRQ_PENDING | IRQ_MASKED); + desc->chip->mask(irq); + desc->chip->ack(irq); + return; + } + kstat_incr_irqs_this_cpu(irq, desc); + + desc->chip->ack(irq); + desc->status |= IRQ_INPROGRESS; + + do { + uint16_t irqstatus; + int i; + + if (unlikely((desc->status & + (IRQ_PENDING | IRQ_MASKED | IRQ_DISABLED)) == + (IRQ_PENDING | IRQ_MASKED))) { + /* dealing with pending IRQ, unmasking */ + desc->chip->unmask(irq); + desc->status &= ~IRQ_MASKED; + } + + desc->status &= ~IRQ_PENDING; + + /* read IRQ status register */ + irqstatus = __reg_read(glamo, GLAMO_REG_IRQ_STATUS); + for (i = 0; i < 9; ++i) { + if (irqstatus & BIT(i)) + generic_handle_irq(glamo->irq_base + i); + } + + } while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING); + + desc->status &= ~IRQ_INPROGRESS; +} + +/* +sysfs +*/ + +static ssize_t regs_write(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct glamo_core *glamo = dev_get_drvdata(dev); + unsigned int reg; + unsigned int val; + + sscanf(buf, "%u %u", ®, &val); + printk(KERN_INFO"reg 0x%02x <-- 0x%04x\n", + reg, val); + + glamo_reg_write(glamo, reg, val); + + return count; +} + +static ssize_t regs_read(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct glamo_core *glamo = dev_get_drvdata(dev); + int i, n; + char *end = buf; + const struct reg_range *rr = reg_range; + + spin_lock(&glamo->lock); + + for (i = 0; i < ARRAY_SIZE(reg_range); ++i, ++rr) { + if (!rr->dump) + continue; + end += sprintf(end, "\n%s\n", rr->name); + for (n = rr->start; n < rr->start + rr->count; n += 2) { + if ((n & 15) == 0) + end += sprintf(end, "\n%04X: ", n); + end += sprintf(end, "%04x ", __reg_read(glamo, n)); + } + end += sprintf(end, "\n"); + } + spin_unlock(&glamo->lock); + + return end - buf; +} + +static DEVICE_ATTR(regs, 0644, regs_read, regs_write); + +struct glamo_engine_reg_set { + uint16_t reg; + uint16_t mask_suspended; + uint16_t mask_enabled; +}; + +struct glamo_engine_desc { + const char *name; + uint16_t hostbus; + const struct glamo_engine_reg_set *regs; + int num_regs; +}; + +static const struct glamo_engine_reg_set glamo_lcd_regs[] = { + { GLAMO_REG_CLOCK_LCD, + GLAMO_CLOCK_LCD_EN_M5CLK | + GLAMO_CLOCK_LCD_DG_M5CLK | + GLAMO_CLOCK_LCD_EN_DMCLK, + + GLAMO_CLOCK_LCD_EN_DHCLK | + GLAMO_CLOCK_LCD_EN_DCLK + }, + { GLAMO_REG_CLOCK_GEN5_1, + GLAMO_CLOCK_GEN51_EN_DIV_DMCLK, + + GLAMO_CLOCK_GEN51_EN_DIV_DHCLK | + GLAMO_CLOCK_GEN51_EN_DIV_DCLK + } +}; + +static const struct glamo_engine_reg_set glamo_mmc_regs[] = { + { GLAMO_REG_CLOCK_MMC, + GLAMO_CLOCK_MMC_EN_M9CLK | + GLAMO_CLOCK_MMC_DG_M9CLK, + + GLAMO_CLOCK_MMC_EN_TCLK | + GLAMO_CLOCK_MMC_DG_TCLK + }, + { GLAMO_REG_CLOCK_GEN5_1, + 0, + GLAMO_CLOCK_GEN51_EN_DIV_TCLK + } +}; + +static const struct glamo_engine_reg_set glamo_2d_regs[] = { + { GLAMO_REG_CLOCK_2D, + GLAMO_CLOCK_2D_EN_M7CLK | + GLAMO_CLOCK_2D_DG_M7CLK, + + GLAMO_CLOCK_2D_EN_GCLK | + GLAMO_CLOCK_2D_DG_GCLK + }, + { GLAMO_REG_CLOCK_GEN5_1, + 0, + GLAMO_CLOCK_GEN51_EN_DIV_GCLK, + } +}; + +static const struct glamo_engine_reg_set glamo_cmdq_regs[] = { + { GLAMO_REG_CLOCK_2D, + GLAMO_CLOCK_2D_EN_M6CLK, + 0 + }, +}; + +#define GLAMO_ENGINE(xname, xhostbus, xregs) { \ + .name = xname, \ + .hostbus = xhostbus, \ + .num_regs = ARRAY_SIZE(xregs), \ + .regs = xregs, \ +} + +static const struct glamo_engine_desc glamo_engines[] = { + [GLAMO_ENGINE_LCD] = GLAMO_ENGINE("LCD", GLAMO_HOSTBUS2_MMIO_EN_LCD, + glamo_lcd_regs), + [GLAMO_ENGINE_MMC] = GLAMO_ENGINE("MMC", GLAMO_HOSTBUS2_MMIO_EN_MMC, + glamo_mmc_regs), + [GLAMO_ENGINE_2D] = GLAMO_ENGINE("2D", GLAMO_HOSTBUS2_MMIO_EN_2D, + glamo_2d_regs), + [GLAMO_ENGINE_CMDQ] = GLAMO_ENGINE("CMDQ", GLAMO_HOSTBUS2_MMIO_EN_CQ, + glamo_cmdq_regs), +}; + +static inline const char *glamo_engine_name(enum glamo_engine engine) +{ + return glamo_engines[engine].name; +} + +/*********************************************************************** + * 'engine' support + ***********************************************************************/ + +int __glamo_engine_enable(struct glamo_core *glamo, enum glamo_engine engine) +{ + int i; + const struct glamo_engine_desc *engine_desc = &glamo_engines[engine]; + const struct glamo_engine_reg_set *reg; + + switch (engine) { + case GLAMO_ENGINE_LCD: + case GLAMO_ENGINE_MMC: + case GLAMO_ENGINE_2D: + case GLAMO_ENGINE_CMDQ: + break; + default: + return -EINVAL; + } + + reg = engine_desc->regs; + + __reg_set_bit(glamo, GLAMO_REG_HOSTBUS(2), + engine_desc->hostbus); + for (i = engine_desc->num_regs; i; --i, ++reg) + __reg_set_bit(glamo, reg->reg, + reg->mask_suspended | reg->mask_enabled); + + return 0; +} + +int glamo_engine_enable(struct glamo_core *glamo, enum glamo_engine engine) +{ + int ret = 0; + + spin_lock(&glamo->lock); + + if (glamo->engine_state[engine] != GLAMO_ENGINE_ENABLED) { + ret = __glamo_engine_enable(glamo, engine); + if (!ret) + glamo->engine_state[engine] = GLAMO_ENGINE_ENABLED; + } + + spin_unlock(&glamo->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(glamo_engine_enable); + +int __glamo_engine_disable(struct glamo_core *glamo, enum glamo_engine engine) +{ + int i; + const struct glamo_engine_desc *engine_desc = &glamo_engines[engine]; + const struct glamo_engine_reg_set *reg; + + switch (engine) { + case GLAMO_ENGINE_LCD: + case GLAMO_ENGINE_MMC: + case GLAMO_ENGINE_2D: + case GLAMO_ENGINE_CMDQ: + break; + default: + return -EINVAL; + } + + reg = engine_desc->regs; + + __reg_clear_bit(glamo, GLAMO_REG_HOSTBUS(2), + engine_desc->hostbus); + for (i = engine_desc->num_regs; i; --i, ++reg) + __reg_clear_bit(glamo, reg->reg, + reg->mask_suspended | reg->mask_enabled); + + return 0; +} +int glamo_engine_disable(struct glamo_core *glamo, enum glamo_engine engine) +{ + int ret = 0; + + spin_lock(&glamo->lock); + + if (glamo->engine_state[engine] != GLAMO_ENGINE_DISABLED) { + ret = __glamo_engine_disable(glamo, engine); + if (!ret) + glamo->engine_state[engine] = GLAMO_ENGINE_DISABLED; + } + + spin_unlock(&glamo->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(glamo_engine_disable); + +int __glamo_engine_suspend(struct glamo_core *glamo, enum glamo_engine engine) +{ + int i; + const struct glamo_engine_desc *engine_desc = &glamo_engines[engine]; + const struct glamo_engine_reg_set *reg; + + switch (engine) { + case GLAMO_ENGINE_LCD: + case GLAMO_ENGINE_MMC: + case GLAMO_ENGINE_2D: + case GLAMO_ENGINE_CMDQ: + break; + default: + return -EINVAL; + } + + reg = engine_desc->regs; + + __reg_set_bit(glamo, GLAMO_REG_HOSTBUS(2), + engine_desc->hostbus); + for (i = engine_desc->num_regs; i; --i, ++reg) { + __reg_set_bit(glamo, reg->reg, reg->mask_suspended); + __reg_clear_bit(glamo, reg->reg, reg->mask_enabled); + } + + return 0; +} + +int glamo_engine_suspend(struct glamo_core *glamo, enum glamo_engine engine) +{ + int ret = 0; + + spin_lock(&glamo->lock); + + if (glamo->engine_state[engine] != GLAMO_ENGINE_SUSPENDED) { + ret = __glamo_engine_suspend(glamo, engine); + if (!ret) + glamo->engine_state[engine] = GLAMO_ENGINE_SUSPENDED; + } + + spin_unlock(&glamo->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(glamo_engine_suspend); + +static const struct glamo_script reset_regs[] = { + [GLAMO_ENGINE_LCD] = { + GLAMO_REG_CLOCK_LCD, GLAMO_CLOCK_LCD_RESET + }, + [GLAMO_ENGINE_MMC] = { + GLAMO_REG_CLOCK_MMC, GLAMO_CLOCK_MMC_RESET + }, + [GLAMO_ENGINE_CMDQ] = { + GLAMO_REG_CLOCK_2D, GLAMO_CLOCK_2D_CQ_RESET + }, + [GLAMO_ENGINE_2D] = { + GLAMO_REG_CLOCK_2D, GLAMO_CLOCK_2D_RESET + }, + [GLAMO_ENGINE_JPEG] = { + GLAMO_REG_CLOCK_JPEG, GLAMO_CLOCK_JPEG_RESET + }, +}; + +void glamo_engine_reset(struct glamo_core *glamo, enum glamo_engine engine) +{ + uint16_t reg = reset_regs[engine].reg; + uint16_t val = reset_regs[engine].val; + + if (engine >= ARRAY_SIZE(reset_regs)) { + dev_warn(&glamo->pdev->dev, "unknown engine %u ", engine); + return; + } + + spin_lock(&glamo->lock); + __reg_set_bit(glamo, reg, val); + __reg_clear_bit(glamo, reg, val); + spin_unlock(&glamo->lock); +} +EXPORT_SYMBOL_GPL(glamo_engine_reset); + +int glamo_pll_rate(struct glamo_core *glamo, + enum glamo_pll pll) +{ + uint16_t reg; + unsigned int osci = glamo->pdata->osci_clock_rate; + + switch (pll) { + case GLAMO_PLL1: + reg = __reg_read(glamo, GLAMO_REG_PLL_GEN1); + break; + case GLAMO_PLL2: + reg = __reg_read(glamo, GLAMO_REG_PLL_GEN3); + break; + default: + return -EINVAL; + } + + return (int)osci * (int)reg; +} +EXPORT_SYMBOL_GPL(glamo_pll_rate); + +int glamo_engine_reclock(struct glamo_core *glamo, + enum glamo_engine engine, + int hz) +{ + int pll; + uint16_t reg, mask, div; + + if (!hz) + return -EINVAL; + + switch (engine) { + case GLAMO_ENGINE_LCD: + pll = GLAMO_PLL1; + reg = GLAMO_REG_CLOCK_GEN7; + mask = 0xff; + break; + case GLAMO_ENGINE_MMC: + pll = GLAMO_PLL1; + reg = GLAMO_REG_CLOCK_GEN8; + mask = 0xff; + break; + default: + dev_warn(&glamo->pdev->dev, + "reclock of engine 0x%x not supported\n", engine); + return -EINVAL; + break; + } + + pll = glamo_pll_rate(glamo, pll); + + div = pll / hz; + + if (div != 0 && pll / div <= hz) + --div; + + if (div > mask) + div = mask; + + dev_dbg(&glamo->pdev->dev, + "PLL %d, kHZ %d, div %d\n", pll, hz / 1000, div); + + reg_set_bit_mask(glamo, reg, mask, div); + mdelay(5); /* wait some time to stabilize */ + + return pll / (div + 1); +} +EXPORT_SYMBOL_GPL(glamo_engine_reclock); + +/*********************************************************************** + * script support + ***********************************************************************/ + +#define GLAMO_SCRIPT_END 0xffff +#define GLAMO_SCRIPT_WAIT 0xfffe +#define GLAMO_SCRIPT_LOCK_PLL 0xfffd + +/* + * couple of people reported artefacts with 2.6.28 changes, this + * allows reversion to 2.6.24 settings +*/ +static const uint16_t reg_0x200[] = { +0xe03, /* 0 waits on Async BB R & W, Use PLL 2 for mem bus */ +0xef0, /* 3 waits on Async BB R & W, Use PLL 1 for mem bus */ +0xea0, /* 2 waits on Async BB R & W, Use PLL 1 for mem bus */ +0xe50, /* 1 waits on Async BB R & W, Use PLL 1 for mem bus */ +0xe00, /* 0 waits on Async BB R & W, Use PLL 1 for mem bus */ +0xef3, /* 3 waits on Async BB R & W, Use PLL 2 for mem bus */ +0xea3, /* 2 waits on Async BB R & W, Use PLL 2 for mem bus */ +0xe53, /* 1 waits on Async BB R & W, Use PLL 2 for mem bus */ +}; + +static int glamo_run_script(struct glamo_core *glamo, + const struct glamo_script *script, int len, + int may_sleep) +{ + int i; + uint16_t status; + const struct glamo_script *line = script; + + for (i = 0; i < len; ++i, ++line) { + switch (line->reg) { + case GLAMO_SCRIPT_END: + return 0; + case GLAMO_SCRIPT_WAIT: + if (may_sleep) + msleep(line->val); + else + mdelay(line->val * 4); + break; + case GLAMO_SCRIPT_LOCK_PLL: + /* spin until PLLs lock */ + do { + status = __reg_read(glamo, GLAMO_REG_PLL_GEN5); + } while ((status & 3) != 3); + break; + case 0x200: + __reg_write(glamo, line->reg, + reg_0x200[slow_memory & 0x8]); + break; + default: + __reg_write(glamo, line->reg, line->val); + break; + } + } + + return 0; +} + +static const struct glamo_script glamo_init_script[] = { + { GLAMO_REG_CLOCK_HOST, 0x1000 }, + { GLAMO_SCRIPT_WAIT, 2 }, + { GLAMO_REG_CLOCK_MEMORY, 0x1000 }, + { GLAMO_REG_CLOCK_MEMORY, 0x2000 }, + { GLAMO_REG_CLOCK_LCD, 0x1000 }, + { GLAMO_REG_CLOCK_MMC, 0x1000 }, + { GLAMO_REG_CLOCK_ISP, 0x1000 }, + { GLAMO_REG_CLOCK_ISP, 0x3000 }, + { GLAMO_REG_CLOCK_JPEG, 0x1000 }, + { GLAMO_REG_CLOCK_3D, 0x1000 }, + { GLAMO_REG_CLOCK_3D, 0x3000 }, + { GLAMO_REG_CLOCK_2D, 0x1000 }, + { GLAMO_REG_CLOCK_2D, 0x3000 }, + { GLAMO_REG_CLOCK_RISC1, 0x1000 }, + { GLAMO_REG_CLOCK_MPEG, 0x1000 }, + { GLAMO_REG_CLOCK_MPEG, 0x3000 }, + { GLAMO_REG_CLOCK_MPROC, 0x1000 /*0x100f*/ }, + { GLAMO_SCRIPT_WAIT, 2 }, + { GLAMO_REG_CLOCK_HOST, 0x0000 }, + { GLAMO_REG_CLOCK_MEMORY, 0x0000 }, + { GLAMO_REG_CLOCK_LCD, 0x0000 }, + { GLAMO_REG_CLOCK_MMC, 0x0000 }, + { GLAMO_REG_PLL_GEN1, 0x05db }, /* 48MHz */ + { GLAMO_REG_PLL_GEN3, 0x0aba }, /* 90MHz */ + { GLAMO_SCRIPT_LOCK_PLL, 0 }, + /* + * b9 of this register MUST be zero to get any interrupts on INT# + * the other set bits enable all the engine interrupt sources + */ + { GLAMO_REG_IRQ_ENABLE, 0x0100 }, + { GLAMO_REG_CLOCK_GEN6, 0x2000 }, + { GLAMO_REG_CLOCK_GEN7, 0x0101 }, + { GLAMO_REG_CLOCK_GEN8, 0x0100 }, + { GLAMO_REG_CLOCK_HOST, 0x000d }, + /* + * b7..b4 = 0 = no wait states on read or write + * b0 = 1 select PLL2 for Host interface, b1 = enable it + */ + { GLAMO_REG_HOSTBUS(0), 0x0e03 /* this is replaced by script parser */ }, + { GLAMO_REG_HOSTBUS(1), 0x07ff }, /* TODO: Disable all */ + { GLAMO_REG_HOSTBUS(10), 0x0000 }, + { GLAMO_REG_HOSTBUS(11), 0x4000 }, + { GLAMO_REG_HOSTBUS(12), 0xf00e }, + + /* S-Media recommended "set tiling mode to 512 mode for memory access + * more efficiency when 640x480" */ + { GLAMO_REG_MEM_TYPE, 0x0c74 }, /* 8MB, 16 word pg wr+rd */ + { GLAMO_REG_MEM_GEN, 0xafaf }, /* 63 grants min + max */ + + { GLAMO_REG_MEM_TIMING1, 0x0108 }, + { GLAMO_REG_MEM_TIMING2, 0x0010 }, /* Taa = 3 MCLK */ + { GLAMO_REG_MEM_TIMING3, 0x0000 }, + { GLAMO_REG_MEM_TIMING4, 0x0000 }, /* CE1# delay fall/rise */ + { GLAMO_REG_MEM_TIMING5, 0x0000 }, /* UB# LB# */ + { GLAMO_REG_MEM_TIMING6, 0x0000 }, /* OE# */ + { GLAMO_REG_MEM_TIMING7, 0x0000 }, /* WE# */ + { GLAMO_REG_MEM_TIMING8, 0x1002 }, /* MCLK delay, was 0x1000 */ + { GLAMO_REG_MEM_TIMING9, 0x6006 }, + { GLAMO_REG_MEM_TIMING10, 0x00ff }, + { GLAMO_REG_MEM_TIMING11, 0x0001 }, + { GLAMO_REG_MEM_POWER1, 0x0020 }, + { GLAMO_REG_MEM_POWER2, 0x0000 }, + { GLAMO_REG_MEM_DRAM1, 0x0000 }, + { GLAMO_SCRIPT_WAIT, 1 }, + { GLAMO_REG_MEM_DRAM1, 0xc100 }, + { GLAMO_SCRIPT_WAIT, 1 }, + { GLAMO_REG_MEM_DRAM1, 0xe100 }, + { GLAMO_REG_MEM_DRAM2, 0x01d6 }, + { GLAMO_REG_CLOCK_MEMORY, 0x000b }, +}; + +/* Find out if we can support this version of the Glamo chip */ +static int __devinit glamo_supported(struct glamo_core *glamo) +{ + uint16_t dev_id, rev_id; + + dev_id = __reg_read(glamo, GLAMO_REG_DEVICE_ID); + rev_id = __reg_read(glamo, GLAMO_REG_REVISION_ID); + + switch (dev_id) { + case 0x3650: + switch (rev_id) { + case GLAMO_CORE_REV_A2: + break; + case GLAMO_CORE_REV_A0: + case GLAMO_CORE_REV_A1: + case GLAMO_CORE_REV_A3: + dev_warn(&glamo->pdev->dev, "untested core revision " + "%04x, your mileage may vary\n", rev_id); + break; + default: + dev_warn(&glamo->pdev->dev, "unknown glamo revision " + "%04x, your mileage may vary\n", rev_id); + } + break; + default: + dev_err(&glamo->pdev->dev, "unsupported Glamo device %04x\n", + dev_id); + return 0; + } + + dev_dbg(&glamo->pdev->dev, "Detected Glamo core %04x Revision %04x " + "(%uHz CPU / %uHz Memory)\n", dev_id, rev_id, + glamo_pll_rate(glamo, GLAMO_PLL1), + glamo_pll_rate(glamo, GLAMO_PLL2)); + + return 1; +} + +static int __devinit glamo_probe(struct platform_device *pdev) +{ + int ret = 0, irq, irq_base; + struct glamo_core *glamo; + struct resource *mem; + + glamo = kmalloc(GFP_KERNEL, sizeof(*glamo)); + if (!glamo) + return -ENOMEM; + + spin_lock_init(&glamo->lock); + + glamo->pdev = pdev; + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + glamo->irq = platform_get_irq(pdev, 0); + glamo->irq_base = irq_base = platform_get_irq(pdev, 1); + glamo->pdata = pdev->dev.platform_data; + + if (glamo->irq < 0) { + ret = glamo->irq; + dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret); + goto err_free; + } + + if (irq_base < 0) { + ret = glamo->irq; + dev_err(&pdev->dev, "Failed to get glamo irq base: %d\n", ret); + goto err_free; + } + + if (!mem) { + dev_err(&pdev->dev, "Failed to get platform memory\n"); + ret = -ENOENT; + goto err_free; + } + + if (!glamo->pdata) { + dev_err(&pdev->dev, "Missing platform data\n"); + ret = -ENOENT; + goto err_free; + } + + /* only request the generic, hostbus and memory controller registers */ + glamo->mem = request_mem_region(mem->start, GLAMO_REGOFS_VIDCAP, + pdev->name); + + if (!glamo->mem) { + dev_err(&pdev->dev, "Failed to request io memory region\n"); + ret = -ENOENT; + goto err_free; + } + + glamo->base = ioremap(glamo->mem->start, resource_size(glamo->mem)); + if (!glamo->base) { + dev_err(&pdev->dev, "Failed to ioremap() memory region\n"); + goto err_release_mem_region; + } + + /* confirm it isn't insane version */ + if (!glamo_supported(glamo)) { + dev_err(&pdev->dev, + "This version of the Glamo is not supported\n"); + goto err_iounmap; + } + + platform_set_drvdata(pdev, glamo); + + /* sysfs */ + ret = device_create_file(&pdev->dev, &dev_attr_regs); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to create sysfs file\n"); + goto err_iounmap; + } + + /* init the chip with canned register set */ + glamo_run_script(glamo, glamo_init_script, + ARRAY_SIZE(glamo_init_script), 1); + + /* + * finally set the mfd interrupts up + */ + for (irq = irq_base; irq < irq_base + GLAMO_NR_IRQS; ++irq) { +#ifdef CONFIG_ARM + set_irq_flags(irq, IRQF_VALID); +#else + set_irq_noprobe(irq); +#endif + set_irq_chip_data(irq, glamo); + set_irq_chip_and_handler(irq, &glamo_irq_chip, + handle_level_irq); + } + + set_irq_type(glamo->irq, IRQ_TYPE_EDGE_FALLING); + set_irq_data(glamo->irq, glamo); + set_irq_chained_handler(glamo->irq, glamo_irq_demux_handler); + glamo->irq_works = 1; + + ret = mfd_add_devices(&pdev->dev, pdev->id, glamo_cells, + ARRAY_SIZE(glamo_cells), mem, glamo->irq_base); + + if (ret) { + dev_err(&pdev->dev, "Failed to add child devices: %d\n", ret); + goto err_free_irqs; + } + + dev_info(&glamo->pdev->dev, "Glamo core PLL1: %uHz, PLL2: %uHz\n", + glamo_pll_rate(glamo, GLAMO_PLL1), + glamo_pll_rate(glamo, GLAMO_PLL2)); + + return 0; + +err_free_irqs: + disable_irq(glamo->irq); + set_irq_chained_handler(glamo->irq, NULL); + set_irq_chip_data(glamo->irq, NULL); + + for (irq = irq_base; irq < irq_base + GLAMO_NR_IRQS; ++irq) { + set_irq_chip(irq, NULL); +#ifdef CONFIG_ARM + set_irq_flags(irq, 0); +#else + set_irq_probe(irq); +#endif + set_irq_chip_data(irq, NULL); + } +err_iounmap: + iounmap(glamo->base); +err_release_mem_region: + release_mem_region(glamo->mem->start, resource_size(glamo->mem)); +err_free: + platform_set_drvdata(pdev, NULL); + kfree(glamo); + + return ret; +} + +static int __devexit glamo_remove(struct platform_device *pdev) +{ + struct glamo_core *glamo = platform_get_drvdata(pdev); + int irq; + int irq_base = glamo->irq_base; + + mfd_remove_devices(&pdev->dev); + + disable_irq(glamo->irq); + set_irq_chained_handler(glamo->irq, NULL); + set_irq_chip_data(glamo->irq, NULL); + + for (irq = irq_base; irq < irq_base + GLAMO_NR_IRQS; ++irq) { +#ifdef CONFIG_ARM + set_irq_flags(irq, 0); +#else + set_irq_noprobe(); +#endif + set_irq_chip(irq, NULL); + set_irq_chip_data(irq, NULL); + } + + platform_set_drvdata(pdev, NULL); + iounmap(glamo->base); + release_mem_region(glamo->mem->start, resource_size(glamo->mem)); + kfree(glamo); + + return 0; +} + +#ifdef CONFIG_PM +#if 0 +static struct glamo_script glamo_resume_script[] = { + + { GLAMO_REG_PLL_GEN1, 0x05db }, /* 48MHz */ + { GLAMO_REG_PLL_GEN3, 0x0aba }, /* 90MHz */ + { GLAMO_REG_DFT_GEN6, 1 }, + { 0xfffe, 100 }, + { 0xfffd, 0 }, + { 0x200, 0x0e03 }, + + /* + * b9 of this register MUST be zero to get any interrupts on INT# + * the other set bits enable all the engine interrupt sources + */ + { GLAMO_REG_IRQ_ENABLE, 0x01ff }, + { GLAMO_REG_CLOCK_HOST, 0x0018 }, + { GLAMO_REG_CLOCK_GEN5_1, 0x18b1 }, + + { GLAMO_REG_MEM_DRAM1, 0x0000 }, + { 0xfffe, 1 }, + { GLAMO_REG_MEM_DRAM1, 0xc100 }, + { 0xfffe, 1 }, + { GLAMO_REG_MEM_DRAM1, 0xe100 }, + { GLAMO_REG_MEM_DRAM2, 0x01d6 }, + { GLAMO_REG_CLOCK_MEMORY, 0x000b }, +}; +#endif + +#if 0 +static void glamo_power(struct glamo_core *glamo) +{ + unsigned long flags; + + spin_lock_irqsave(&glamo->lock, flags); + + /* +Power management +static const REG_VALUE_MASK_TYPE reg_powerOn[] = +{ + { REG_GEN_DFT6, REG_BIT_ALL, REG_DATA(1u << 0) }, + { REG_GEN_PLL3, 0u, REG_DATA(1u << 13) }, + { REG_GEN_MEM_CLK, REG_BIT_ALL, REG_BIT_EN_MOCACLK }, + { REG_MEM_DRAM2, 0u, REG_BIT_EN_DEEP_POWER_DOWN }, + { REG_MEM_DRAM1, 0u, REG_BIT_SELF_REFRESH } +}; + +static const REG_VALUE_MASK_TYPE reg_powerStandby[] = +{ + { REG_MEM_DRAM1, REG_BIT_ALL, REG_BIT_SELF_REFRESH }, + { REG_GEN_MEM_CLK, 0u, REG_BIT_EN_MOCACLK }, + { REG_GEN_PLL3, REG_BIT_ALL, REG_DATA(1u << 13) }, + { REG_GEN_DFT5, REG_BIT_ALL, REG_DATA(1u << 0) } +}; + +static const REG_VALUE_MASK_TYPE reg_powerSuspend[] = +{ + { REG_MEM_DRAM2, REG_BIT_ALL, REG_BIT_EN_DEEP_POWER_DOWN }, + { REG_GEN_MEM_CLK, 0u, REG_BIT_EN_MOCACLK }, + { REG_GEN_PLL3, REG_BIT_ALL, REG_DATA(1u << 13) }, + { REG_GEN_DFT5, REG_BIT_ALL, REG_DATA(1u << 0) } +}; +*/ + switch (new_state) { + case GLAMO_POWER_ON: + + /* + * glamo state on resume is nondeterministic in some + * fundamental way, it has also been observed that the + * Glamo reset pin can get asserted by, eg, touching it with + * a scope probe. So the only answer is to roll with it and + * force an external reset on the Glamo during resume. + */ + + + break; + + case GLAMO_POWER_SUSPEND: + + break; + } + spin_unlock_irqrestore(&glamo->lock, flags); +} +#endif + +static int glamo_suspend(struct device *dev) +{ + struct glamo_core *glamo = dev_get_drvdata(dev); + int n; + + spin_lock(&glamo->lock); + + glamo->saved_irq_mask = __reg_read(glamo, GLAMO_REG_IRQ_ENABLE); + + /* nuke interrupts */ + __reg_write(glamo, GLAMO_REG_IRQ_ENABLE, 0x200); + + /* take down each engine before we kill mem and pll */ + for (n = 0; n < __NUM_GLAMO_ENGINES; n++) { + if (glamo->engine_state != GLAMO_ENGINE_DISABLED) + __glamo_engine_disable(glamo, n); + } + + /* enable self-refresh */ + + __reg_write(glamo, GLAMO_REG_MEM_DRAM1, + GLAMO_MEM_DRAM1_EN_DRAM_REFRESH | + GLAMO_MEM_DRAM1_EN_GATE_CKE | + GLAMO_MEM_DRAM1_SELF_REFRESH | + GLAMO_MEM_REFRESH_COUNT); + __reg_write(glamo, GLAMO_REG_MEM_DRAM1, + GLAMO_MEM_DRAM1_EN_MODEREG_SET | + GLAMO_MEM_DRAM1_EN_DRAM_REFRESH | + GLAMO_MEM_DRAM1_EN_GATE_CKE | + GLAMO_MEM_DRAM1_SELF_REFRESH | + GLAMO_MEM_REFRESH_COUNT); + + /* force RAM into deep powerdown */ + __reg_write(glamo, GLAMO_REG_MEM_DRAM2, + GLAMO_MEM_DRAM2_DEEP_PWRDOWN | + (7 << 6) | /* tRC */ + (1 << 4) | /* tRP */ + (1 << 2) | /* tRCD */ + 2); /* CAS latency */ + + /* disable clocks to memory */ + __reg_write(glamo, GLAMO_REG_CLOCK_MEMORY, 0); + + /* all dividers from OSCI */ + __reg_set_bit_mask(glamo, GLAMO_REG_CLOCK_GEN5_1, 0x400, 0x400); + + /* PLL2 into bypass */ + __reg_set_bit_mask(glamo, GLAMO_REG_PLL_GEN3, 1 << 12, 1 << 12); + + __reg_write(glamo, GLAMO_BASIC_MMC_EN_TCLK_DLYA1, 0x0e00); + + /* kill PLLS 1 then 2 */ + __reg_write(glamo, GLAMO_REG_DFT_GEN5, 0x0001); + __reg_set_bit_mask(glamo, GLAMO_REG_PLL_GEN3, 1 << 13, 1 << 13); + + spin_unlock(&glamo->lock); + + return 0; +} + +static int glamo_resume(struct device *dev) +{ + struct glamo_core *glamo = dev_get_drvdata(dev); + int n; + + (glamo->pdata->glamo_external_reset)(0); + udelay(10); + (glamo->pdata->glamo_external_reset)(1); + mdelay(5); + + spin_lock(&glamo->lock); + + glamo_run_script(glamo, glamo_init_script, + ARRAY_SIZE(glamo_init_script), 0); + + + for (n = 0; n < __NUM_GLAMO_ENGINES; n++) { + switch (glamo->engine_state[n]) { + case GLAMO_ENGINE_SUSPENDED: + __glamo_engine_suspend(glamo, n); + break; + case GLAMO_ENGINE_ENABLED: + __glamo_engine_enable(glamo, n); + break; + default: + break; + } + } + + __reg_write(glamo, GLAMO_REG_IRQ_ENABLE, glamo->saved_irq_mask); + + spin_unlock(&glamo->lock); + + return 0; +} + +static struct dev_pm_ops glamo_pm_ops = { + .suspend = glamo_suspend, + .resume = glamo_resume, + .poweroff = glamo_suspend, + .restore = glamo_resume, +}; + +#define GLAMO_PM_OPS (&glamo_pm_ops) + +#else +#define GLAMO_PM_OPS NULL +#endif + +static struct platform_driver glamo_driver = { + .probe = glamo_probe, + .remove = __devexit_p(glamo_remove), + .driver = { + .name = "glamo3362", + .owner = THIS_MODULE, + .pm = GLAMO_PM_OPS, + }, +}; + +static int __devinit glamo_init(void) +{ + return platform_driver_register(&glamo_driver); +} +module_init(glamo_init); + +static void __exit glamo_exit(void) +{ + platform_driver_unregister(&glamo_driver); +} +module_exit(glamo_exit); + +MODULE_AUTHOR("Harald Welte "); +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("Smedia Glamo 3362 core/resource driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:glamo3362"); diff --git a/drivers/mfd/glamo/glamo-core.h b/drivers/mfd/glamo/glamo-core.h new file mode 100644 index 00000000000..17017b03fb5 --- /dev/null +++ b/drivers/mfd/glamo/glamo-core.h @@ -0,0 +1,61 @@ +#ifndef __GLAMO_CORE_H +#define __GLAMO_CORE_H + +#include + +/* for the time being, we put the on-screen framebuffer into the lowest + * VRAM space. This should make the code easily compatible with the various + * 2MB/4MB/8MB variants of the Smedia chips */ +#define GLAMO_OFFSET_VRAM 0x800000 +#define GLAMO_OFFSET_FB (GLAMO_OFFSET_VRAM) + +/* we only allocate the minimum possible size for the framebuffer to make + * sure we have sufficient memory for other functions of the chip */ +/*#define GLAMO_FB_SIZE (640*480*4) *//* == 0x12c000 */ +#define GLAMO_INTERNAL_RAM_SIZE 0x800000 +#define GLAMO_MMC_BUFFER_SIZE (64 * 1024) +#define GLAMO_FB_SIZE (GLAMO_INTERNAL_RAM_SIZE - GLAMO_MMC_BUFFER_SIZE) + +enum glamo_pll { + GLAMO_PLL1, + GLAMO_PLL2, +}; + +enum glamo_engine_state { + GLAMO_ENGINE_DISABLED, + GLAMO_ENGINE_SUSPENDED, + GLAMO_ENGINE_ENABLED, +}; + +struct glamo_core { + int irq; + int irq_base; + int irq_works; /* 0 means PCB does not support Glamo IRQ */ + struct resource *mem; + void __iomem *base; + struct platform_device *pdev; + struct glamo_platform_data *pdata; + enum glamo_engine_state engine_state[__NUM_GLAMO_ENGINES]; + spinlock_t lock; + uint16_t saved_irq_mask; +}; + +struct glamo_script { + uint16_t reg; + uint16_t val; +}; + +int glamo_pll_rate(struct glamo_core *glamo, enum glamo_pll pll); + +int glamo_engine_enable(struct glamo_core *glamo, enum glamo_engine engine); +int glamo_engine_suspend(struct glamo_core *glamo, enum glamo_engine engine); +int glamo_engine_disable(struct glamo_core *glamo, enum glamo_engine engine); +void glamo_engine_reset(struct glamo_core *glamo, enum glamo_engine engine); +int glamo_engine_reclock(struct glamo_core *glamo, + enum glamo_engine engine, int ps); + +void glamo_reg_read_batch(struct glamo_core *glamo, uint16_t reg, + uint16_t count, uint16_t *values); +void glamo_reg_write_batch(struct glamo_core *glamo, uint16_t reg, + uint16_t count, uint16_t *values); +#endif /* __GLAMO_CORE_H */ diff --git a/drivers/mfd/glamo/glamo-fb.c b/drivers/mfd/glamo/glamo-fb.c new file mode 100644 index 00000000000..0eac3ac442b --- /dev/null +++ b/drivers/mfd/glamo/glamo-fb.c @@ -0,0 +1,982 @@ +/* Smedia Glamo 336x/337x driver + * + * (C) 2007-2008 by Openmoko, Inc. + * Author: Harald Welte + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef CONFIG_PM +#include +#endif + +#include + +#include "glamo-regs.h" +#include "glamo-core.h" + +static void glamofb_program_mode(struct glamofb_handle *glamo); + +struct glamofb_handle { + struct glamo_core *core; + struct fb_info *fb; + struct device *dev; + struct resource *reg; + struct resource *fb_res; + char __iomem *base; + struct glamo_fb_platform_data *mach_info; + char __iomem *cursor_addr; + int cursor_on; + u_int32_t pseudo_pal[16]; + spinlock_t lock_cmd; + int blank_mode; + int mode_set; /* 0 if the current display mode hasn't been set on the glamo */ + int output_enabled; /* 0 if the video output is disabled */ +}; + +static void glamo_output_enable(struct glamofb_handle *gfb) +{ + struct glamo_core *gcore = gfb->core; + + if (gfb->output_enabled) + return; + + /* enable the pixel clock if off */ + glamo_engine_enable(gcore, GLAMO_ENGINE_LCD); + + gfb->output_enabled = 1; + if (!gfb->mode_set) + glamofb_program_mode(gfb); +} + +static void glamo_output_disable(struct glamofb_handle *gfb) +{ + struct glamo_core *gcore = gfb->core; + + if (!gfb->output_enabled) + return; + + /* enable the pixel clock if off */ + glamo_engine_suspend(gcore, GLAMO_ENGINE_LCD); + + gfb->output_enabled = 0; +} + + +static int reg_read(struct glamofb_handle *glamo, + u_int16_t reg) +{ + int i = 0; + + for (i = 0; i != 2; i++) + nop(); + + return readw(glamo->base + reg); +} + +static void reg_write(struct glamofb_handle *glamo, + uint16_t reg, uint16_t val) +{ + int i = 0; + + for (i = 0; i != 2; i++) + nop(); + + writew(val, glamo->base + reg); +} + +static struct glamo_script glamo_regs[] = { + { GLAMO_REG_LCD_MODE1, 0x0020 }, + /* no display rotation, no hardware cursor, no dither, no gamma, + * no retrace flip, vsync low-active, hsync low active, + * no TVCLK, no partial display, hw dest color from fb, + * no partial display mode, LCD1, software flip, */ + { GLAMO_REG_LCD_MODE2, 0x9020 }, + /* video flip, no ptr, no ptr, dhclk off, + * normal mode, no cpuif, + * res, serial msb first, single fb, no fr ctrl, + * cpu if bits all zero, no crc + * 0000 0000 0010 0000 */ + { GLAMO_REG_LCD_MODE3, 0x0b40 }, + /* src data rgb565, res, 18bit rgb666 + * 000 01 011 0100 0000 */ + { GLAMO_REG_LCD_POLARITY, 0x440c }, + /* DE high active, no cpu/lcd if, cs0 force low, a0 low active, + * np cpu if, 9bit serial data, sclk rising edge latch data + * 01 00 0 100 0 000 01 0 0 */ + /* The following values assume 640*480@16bpp */ + { GLAMO_REG_LCD_A_BASE1, 0x0000 }, /* display A base address 15:0 */ + { GLAMO_REG_LCD_A_BASE2, 0x0000 }, /* display A base address 22:16 */ + { GLAMO_REG_LCD_CURSOR_BASE1, 0xC000 }, /* cursor base address 15:0 */ + { GLAMO_REG_LCD_CURSOR_BASE2, 0x0012 }, /* cursor base address 22:16 */ + { GLAMO_REG_LCD_COMMAND2, 0x0000 }, /* display page A */ +}; + +static int glamofb_run_script(struct glamofb_handle *glamo, + struct glamo_script *script, int len) +{ + int i; + + for (i = 0; i < len; i++) { + struct glamo_script *line = &script[i]; + + if (line->reg == 0xffff) + return 0; + else if (line->reg == 0xfffe) + msleep(line->val); + else + reg_write(glamo, script[i].reg, script[i].val); + } + + return 0; +} + +static int glamofb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct glamofb_handle *glamo = info->par; + + if (var->bits_per_pixel != 16) + var->bits_per_pixel = 16; + + var->height = glamo->mach_info->height; + var->width = glamo->mach_info->width; + + /* FIXME: set rgb positions */ + switch (var->bits_per_pixel) { + case 16: + switch (reg_read(glamo, GLAMO_REG_LCD_MODE3) & 0xc000) { + case GLAMO_LCD_SRC_RGB565: + var->red.offset = 11; + var->green.offset = 5; + var->blue.offset = 0; + var->red.length = 5; + var->green.length = 6; + var->blue.length = 5; + var->transp.length = 0; + break; + case GLAMO_LCD_SRC_ARGB1555: + var->transp.offset = 15; + var->red.offset = 10; + var->green.offset = 5; + var->blue.offset = 0; + var->transp.length = 1; + var->red.length = 5; + var->green.length = 5; + var->blue.length = 5; + break; + case GLAMO_LCD_SRC_ARGB4444: + var->transp.offset = 12; + var->red.offset = 8; + var->green.offset = 4; + var->blue.offset = 0; + var->transp.length = 4; + var->red.length = 4; + var->green.length = 4; + var->blue.length = 4; + break; + } + break; + case 24: + case 32: + default: + /* The Smedia Glamo doesn't support anything but 16bit color */ + printk(KERN_ERR + "Smedia driver does not [yet?] support 24/32bpp\n"); + return -EINVAL; + } + + return 0; +} + +static void reg_set_bit_mask(struct glamofb_handle *glamo, + uint16_t reg, uint16_t mask, + uint16_t val) +{ + u_int16_t tmp; + + val &= mask; + + tmp = reg_read(glamo, reg); + tmp &= ~mask; + tmp |= val; + reg_write(glamo, reg, tmp); +} + +#define GLAMO_LCD_WIDTH_MASK 0x03FF +#define GLAMO_LCD_HEIGHT_MASK 0x03FF +#define GLAMO_LCD_PITCH_MASK 0x07FE +#define GLAMO_LCD_HV_TOTAL_MASK 0x03FF +#define GLAMO_LCD_HV_RETR_START_MASK 0x03FF +#define GLAMO_LCD_HV_RETR_END_MASK 0x03FF +#define GLAMO_LCD_HV_RETR_DISP_START_MASK 0x03FF +#define GLAMO_LCD_HV_RETR_DISP_END_MASK 0x03FF + +/* the caller has to enxure lock_cmd is held and we are in cmd mode */ +static void __rotate_lcd(struct glamofb_handle *glamo, __u32 rotation) +{ + int glamo_rot; + + switch (rotation) { + case FB_ROTATE_CW: + glamo_rot = GLAMO_LCD_ROT_MODE_90; + break; + case FB_ROTATE_UD: + glamo_rot = GLAMO_LCD_ROT_MODE_180; + break; + case FB_ROTATE_CCW: + glamo_rot = GLAMO_LCD_ROT_MODE_270; + break; + default: + glamo_rot = GLAMO_LCD_ROT_MODE_0; + break; + } + + reg_set_bit_mask(glamo, + GLAMO_REG_LCD_WIDTH, + GLAMO_LCD_ROT_MODE_MASK, + glamo_rot); + reg_set_bit_mask(glamo, + GLAMO_REG_LCD_MODE1, + GLAMO_LCD_MODE1_ROTATE_EN, + (glamo_rot != GLAMO_LCD_ROT_MODE_0) ? + GLAMO_LCD_MODE1_ROTATE_EN : 0); +} + +static void glamofb_program_mode(struct glamofb_handle *gfb) +{ + int sync, bp, disp, fp, total; + unsigned long flags; + struct glamo_core *gcore = gfb->core; + struct fb_var_screeninfo *var = &gfb->fb->var; + + dev_dbg(&gcore->pdev->dev, + "glamofb_program_mode spin_lock_irqsave\n"); + spin_lock_irqsave(&gfb->lock_cmd, flags); + + if (glamofb_cmd_mode(gfb, 1)) + goto out_unlock; + + if (var->pixclock) + glamo_engine_reclock(gcore, GLAMO_ENGINE_LCD, + (1000000000UL / gfb->fb->var.pixclock) * 1000); + + reg_set_bit_mask(gfb, + GLAMO_REG_LCD_WIDTH, + GLAMO_LCD_WIDTH_MASK, + var->xres); + reg_set_bit_mask(gfb, + GLAMO_REG_LCD_HEIGHT, + GLAMO_LCD_HEIGHT_MASK, + var->yres); + reg_set_bit_mask(gfb, + GLAMO_REG_LCD_PITCH, + GLAMO_LCD_PITCH_MASK, + gfb->fb->fix.line_length); + + /* honour the rotation request */ + __rotate_lcd(gfb, var->rotate); + + /* update scannout timings */ + sync = 0; + bp = sync + var->hsync_len; + disp = bp + var->left_margin; + fp = disp + var->xres; + total = fp + var->right_margin; + + reg_set_bit_mask(gfb, GLAMO_REG_LCD_HORIZ_TOTAL, + GLAMO_LCD_HV_TOTAL_MASK, total); + reg_set_bit_mask(gfb, GLAMO_REG_LCD_HORIZ_RETR_START, + GLAMO_LCD_HV_RETR_START_MASK, sync); + reg_set_bit_mask(gfb, GLAMO_REG_LCD_HORIZ_RETR_END, + GLAMO_LCD_HV_RETR_END_MASK, bp); + reg_set_bit_mask(gfb, GLAMO_REG_LCD_HORIZ_DISP_START, + GLAMO_LCD_HV_RETR_DISP_START_MASK, disp); + reg_set_bit_mask(gfb, GLAMO_REG_LCD_HORIZ_DISP_END, + GLAMO_LCD_HV_RETR_DISP_END_MASK, fp); + + sync = 0; + bp = sync + var->vsync_len; + disp = bp + var->upper_margin; + fp = disp + var->yres; + total = fp + var->lower_margin; + + reg_set_bit_mask(gfb, GLAMO_REG_LCD_VERT_TOTAL, + GLAMO_LCD_HV_TOTAL_MASK, total); + reg_set_bit_mask(gfb, GLAMO_REG_LCD_VERT_RETR_START, + GLAMO_LCD_HV_RETR_START_MASK, sync); + reg_set_bit_mask(gfb, GLAMO_REG_LCD_VERT_RETR_END, + GLAMO_LCD_HV_RETR_END_MASK, bp); + reg_set_bit_mask(gfb, GLAMO_REG_LCD_VERT_DISP_START, + GLAMO_LCD_HV_RETR_DISP_START_MASK, disp); + reg_set_bit_mask(gfb, GLAMO_REG_LCD_VERT_DISP_END, + GLAMO_LCD_HV_RETR_DISP_END_MASK, fp); + + glamofb_cmd_mode(gfb, 0); + + gfb->mode_set = 1; + +out_unlock: + dev_dbg(&gcore->pdev->dev, + "glamofb_program_mode spin_unlock_irqrestore\n"); + spin_unlock_irqrestore(&gfb->lock_cmd, flags); +} + + +static int glamofb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + return 0; +} + +static struct fb_videomode *glamofb_find_mode(struct fb_info *info, + struct fb_var_screeninfo *var) +{ + struct glamofb_handle *glamo = info->par; + struct glamo_fb_platform_data *pdata = glamo->mach_info; + struct fb_videomode *mode; + int i; + + for (i = pdata->num_modes, mode = pdata->modes; i; --i, ++mode) { + if (mode->xres == var->xres && + mode->yres == var->yres) + return mode; + } + + return NULL; +} + +static int glamofb_set_par(struct fb_info *info) +{ + struct glamofb_handle *glamo = info->par; + struct fb_var_screeninfo *var = &info->var; + struct fb_videomode *mode; + + mode = glamofb_find_mode(info, var); + if (!mode) + return -EINVAL; + + fb_videomode_to_var(var, mode); + + info->mode = mode; + + glamo->mode_set = 0; + + switch (var->rotate) { + case FB_ROTATE_CW: + case FB_ROTATE_CCW: + info->fix.line_length = (var->yres * var->bits_per_pixel) / 8; + /* FIXME: Limit pixelclock */ + var->pixclock *= 2; + break; + default: + info->fix.line_length = (var->xres * var->bits_per_pixel) / 8; + break; + } + + if (glamo->output_enabled) + glamofb_program_mode(glamo); + + return 0; +} + +static int glamofb_blank(int blank_mode, struct fb_info *info) +{ + struct glamofb_handle *gfb = info->par; + + dev_dbg(gfb->dev, "glamofb_blank(%u)\n", blank_mode); + + switch (blank_mode) { + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + /* FIXME: add pdata hook/flag to indicate whether + * we should already switch off pixel clock here */ + break; + case FB_BLANK_POWERDOWN: + /* disable the pixel clock */ + glamo_output_disable(gfb); + gfb->blank_mode = blank_mode; + break; + case FB_BLANK_UNBLANK: + case FB_BLANK_NORMAL: + glamo_output_enable(gfb); + gfb->blank_mode = blank_mode; + break; + } + + /* FIXME: once we have proper clock management in glamo-core, + * we can determine if other units need MCLK1 or the PLL, and + * disable it if not used. */ + return 0; +} + +static inline unsigned int chan_to_field(unsigned int chan, + struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +static int glamofb_setcolreg(unsigned regno, + unsigned red, unsigned green, unsigned blue, + unsigned transp, struct fb_info *info) +{ + struct glamofb_handle *glamo = info->par; + unsigned int val; + + switch (glamo->fb->fix.visual) { + case FB_VISUAL_TRUECOLOR: + case FB_VISUAL_DIRECTCOLOR: + /* true-colour, use pseuo-palette */ + + if (regno < 16) { + u32 *pal = glamo->fb->pseudo_palette; + + val = chan_to_field(red, &glamo->fb->var.red); + val |= chan_to_field(green, &glamo->fb->var.green); + val |= chan_to_field(blue, &glamo->fb->var.blue); + + pal[regno] = val; + }; + break; + default: + return 1; /* unknown type */ + } + + return 0; +} + +static int glamofb_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + struct glamofb_handle *gfb = (struct glamofb_handle *)info->par; + struct glamo_core *gcore = gfb->core; + int retval = -ENOTTY; + + switch (cmd) { + case GLAMOFB_ENGINE_ENABLE: + retval = glamo_engine_enable(gcore, arg); + break; + case GLAMOFB_ENGINE_DISABLE: + retval = glamo_engine_disable(gcore, arg); + break; + case GLAMOFB_ENGINE_RESET: + glamo_engine_reset(gcore, arg); + retval = 0; + break; + default: + break; + } + + return retval; +} + + +#ifdef CONFIG_MFD_GLAMO_HWACCEL +static inline void glamofb_vsync_wait(struct glamofb_handle *glamo, + int line, int size, int range) +{ + int count[2]; + + do { + count[0] = reg_read(glamo, GLAMO_REG_LCD_STATUS2) & 0x3ff; + count[1] = reg_read(glamo, GLAMO_REG_LCD_STATUS2) & 0x3ff; + } while (count[0] != count[1] || + (line < count[0] + range && + size > count[0] - range) || + count[0] < range * 2); +} + +/* + * Enable/disable the hardware cursor mode altogether + * (for blinking and such, use glamofb_cursor()). + */ +static void glamofb_cursor_onoff(struct glamofb_handle *glamo, int on) +{ + int y, size; + + if (glamo->cursor_on) { + y = reg_read(glamo, GLAMO_REG_LCD_CURSOR_Y_POS); + size = reg_read(glamo, GLAMO_REG_LCD_CURSOR_Y_SIZE); + + glamofb_vsync_wait(glamo, y, size, 30); + } + + reg_set_bit_mask(glamo, GLAMO_REG_LCD_MODE1, + GLAMO_LCD_MODE1_CURSOR_EN, + on ? GLAMO_LCD_MODE1_CURSOR_EN : 0); + glamo->cursor_on = on; + + /* Hide the cursor by default */ + reg_write(glamo, GLAMO_REG_LCD_CURSOR_X_SIZE, 0); +} + +static int glamofb_cursor(struct fb_info *info, struct fb_cursor *cursor) +{ + struct glamofb_handle *glamo = info->par; + unsigned long flags; + + spin_lock_irqsave(&glamo->lock_cmd, flags); + + reg_write(glamo, GLAMO_REG_LCD_CURSOR_X_SIZE, + cursor->enable ? cursor->image.width : 0); + + if (cursor->set & FB_CUR_SETPOS) { + reg_write(glamo, GLAMO_REG_LCD_CURSOR_X_POS, + cursor->image.dx); + reg_write(glamo, GLAMO_REG_LCD_CURSOR_Y_POS, + cursor->image.dy); + } + + if (cursor->set & FB_CUR_SETCMAP) { + uint16_t fg = glamo->pseudo_pal[cursor->image.fg_color]; + uint16_t bg = glamo->pseudo_pal[cursor->image.bg_color]; + + reg_write(glamo, GLAMO_REG_LCD_CURSOR_FG_COLOR, fg); + reg_write(glamo, GLAMO_REG_LCD_CURSOR_BG_COLOR, bg); + reg_write(glamo, GLAMO_REG_LCD_CURSOR_DST_COLOR, fg); + } + + if (cursor->set & FB_CUR_SETHOT) + reg_write(glamo, GLAMO_REG_LCD_CURSOR_PRESET, + (cursor->hot.x << 8) | cursor->hot.y); + + if ((cursor->set & FB_CUR_SETSIZE) || + (cursor->set & (FB_CUR_SETIMAGE | FB_CUR_SETSHAPE))) { + int x, y, pitch, op; + const uint8_t *pcol = cursor->image.data; + const uint8_t *pmsk = cursor->mask; + uint8_t __iomem *dst = glamo->cursor_addr; + uint8_t dcol = 0; + uint8_t dmsk = 0; + uint8_t byte = 0; + + if (cursor->image.depth > 1) { + spin_unlock_irqrestore(&glamo->lock_cmd, flags); + return -EINVAL; + } + + pitch = ((cursor->image.width + 7) >> 2) & ~1; + reg_write(glamo, GLAMO_REG_LCD_CURSOR_PITCH, + pitch); + reg_write(glamo, GLAMO_REG_LCD_CURSOR_Y_SIZE, + cursor->image.height); + + for (y = 0; y < cursor->image.height; y++) { + byte = 0; + for (x = 0; x < cursor->image.width; x++) { + if ((x % 8) == 0) { + dcol = *pcol++; + dmsk = *pmsk++; + } else { + dcol >>= 1; + dmsk >>= 1; + } + + if (cursor->rop == ROP_COPY) + op = (dmsk & 1) ? + (dcol & 1) ? 1 : 3 : 0; + else + op = ((dmsk & 1) << 1) | + ((dcol & 1) << 0); + byte |= op << ((x & 3) << 1); + + if (x % 4 == 3) { + writeb(byte, dst + x / 4); + byte = 0; + } + } + if (x % 4) { + writeb(byte, dst + x / 4); + byte = 0; + } + + dst += pitch; + } + } + + spin_unlock_irqrestore(&glamo->lock_cmd, flags); + + return 0; +} +#endif + +static inline int glamofb_cmdq_empty(struct glamofb_handle *gfb) +{ + /* DGCMdQempty -- 1 == command queue is empty */ + return reg_read(gfb, GLAMO_REG_LCD_STATUS1) & (1 << 15); +} + +/* call holding gfb->lock_cmd when locking, until you unlock */ +int glamofb_cmd_mode(struct glamofb_handle *gfb, int on) +{ + int timeout = 2000000; + + dev_dbg(gfb->dev, "glamofb_cmd_mode(gfb=%p, on=%d)\n", gfb, on); + if (on) { + dev_dbg(gfb->dev, "%s: waiting for cmdq empty: ", + __func__); + while (!glamofb_cmdq_empty(gfb) && (timeout--)) + cpu_relax(); + if (timeout < 0) { + printk(KERN_ERR "glamofb cmd_queue never got empty\n"); + return -EIO; + } + dev_dbg(gfb->dev, "empty!\n"); + + /* display the entire frame then switch to command */ + reg_write(gfb, GLAMO_REG_LCD_COMMAND1, + GLAMO_LCD_CMD_TYPE_DISP | + GLAMO_LCD_CMD_DATA_FIRE_VSYNC); + + /* wait until lcd idle */ + dev_dbg(gfb->dev, "waiting for lcd idle: "); + timeout = 2000000; + while (!(reg_read(gfb, GLAMO_REG_LCD_STATUS2) & (1 << 12)) && + (timeout--)) + cpu_relax(); + if (timeout < 0) { + printk(KERN_ERR"*************" + "glamofb lcd never idle" + "*************\n"); + return -EIO; + } + + mdelay(100); + + dev_dbg(gfb->dev, "cmd mode entered\n"); + + } else { + /* RGB interface needs vsync/hsync */ + if (reg_read(gfb, GLAMO_REG_LCD_MODE3) & GLAMO_LCD_MODE3_RGB) + reg_write(gfb, GLAMO_REG_LCD_COMMAND1, + GLAMO_LCD_CMD_TYPE_DISP | + GLAMO_LCD_CMD_DATA_DISP_SYNC); + + reg_write(gfb, GLAMO_REG_LCD_COMMAND1, + GLAMO_LCD_CMD_TYPE_DISP | + GLAMO_LCD_CMD_DATA_DISP_FIRE); + } + + return 0; +} +EXPORT_SYMBOL_GPL(glamofb_cmd_mode); + + +int glamofb_cmd_write(struct glamofb_handle *gfb, u_int16_t val) +{ + int timeout = 200000; + + dev_dbg(gfb->dev, "%s: waiting for cmdq empty\n", __func__); + while ((!glamofb_cmdq_empty(gfb)) && (timeout--)) + yield(); + if (timeout < 0) { + printk(KERN_ERR"*************" + "glamofb cmd_queue never got empty" + "*************\n"); + return 1; + } + dev_dbg(gfb->dev, "idle, writing 0x%04x\n", val); + + reg_write(gfb, GLAMO_REG_LCD_COMMAND1, val); + + return 0; +} +EXPORT_SYMBOL_GPL(glamofb_cmd_write); + +static struct fb_ops glamofb_ops = { + .owner = THIS_MODULE, + .fb_check_var = glamofb_check_var, + .fb_pan_display = glamofb_pan_display, + .fb_set_par = glamofb_set_par, + .fb_blank = glamofb_blank, + .fb_setcolreg = glamofb_setcolreg, + .fb_ioctl = glamofb_ioctl, +#ifdef CONFIG_MFD_GLAMO_HWACCEL + .fb_cursor = glamofb_cursor, +#endif + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +static int glamofb_init_regs(struct glamofb_handle *glamo) +{ + struct fb_info *info = glamo->fb; + + glamofb_check_var(&info->var, info); + glamofb_run_script(glamo, glamo_regs, ARRAY_SIZE(glamo_regs)); + glamofb_set_par(info); + + return 0; +} + +static int __init glamofb_probe(struct platform_device *pdev) +{ + int rc = -EIO; + struct fb_info *fbinfo; + struct glamofb_handle *glamofb; + struct glamo_core *core = dev_get_drvdata(pdev->dev.parent); + struct glamo_fb_platform_data *mach_info; + + printk(KERN_INFO "SMEDIA Glamo frame buffer driver (C) 2007 " + "Openmoko, Inc.\n"); + + if (!core->pdata || !core->pdata->fb_data) + return -ENOENT; + + + fbinfo = framebuffer_alloc(sizeof(struct glamofb_handle), &pdev->dev); + if (!fbinfo) + return -ENOMEM; + + + glamofb = fbinfo->par; + glamofb->fb = fbinfo; + glamofb->dev = &pdev->dev; + + glamofb->blank_mode = FB_BLANK_POWERDOWN; + + strcpy(fbinfo->fix.id, "SMedia Glamo"); + + glamofb->reg = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "glamo-fb-regs"); + if (!glamofb->reg) { + dev_err(&pdev->dev, "platform device with no registers?\n"); + rc = -ENOENT; + goto out_free; + } + + glamofb->fb_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "glamo-fb-mem"); + if (!glamofb->fb_res) { + dev_err(&pdev->dev, "platform device with no memory ?\n"); + rc = -ENOENT; + goto out_free; + } + + glamofb->reg = request_mem_region(glamofb->reg->start, + resource_size(glamofb->reg), + pdev->name); + if (!glamofb->reg) { + dev_err(&pdev->dev, "failed to request mmio region\n"); + goto out_free; + } + + glamofb->fb_res = request_mem_region(glamofb->fb_res->start, + resource_size(glamofb->fb_res), + pdev->name); + if (!glamofb->fb_res) { + dev_err(&pdev->dev, "failed to request vram region\n"); + goto out_release_reg; + } + + /* we want to remap only the registers required for this core + * driver. */ + glamofb->base = ioremap_nocache(glamofb->reg->start, + resource_size(glamofb->reg)); + if (!glamofb->base) { + dev_err(&pdev->dev, "failed to ioremap() mmio memory\n"); + goto out_release_fb; + } + + fbinfo->fix.smem_start = (unsigned long) glamofb->fb_res->start; + fbinfo->fix.smem_len = (__u32) resource_size(glamofb->fb_res); + + fbinfo->screen_base = ioremap(glamofb->fb_res->start, + resource_size(glamofb->fb_res)); + if (!fbinfo->screen_base) { + dev_err(&pdev->dev, "failed to ioremap() vram memory\n"); + goto out_release_fb; + } + glamofb->cursor_addr = fbinfo->screen_base + 0x12C000; + + platform_set_drvdata(pdev, glamofb); + + mach_info = core->pdata->fb_data; + glamofb->core = core; + glamofb->mach_info = mach_info; + + fbinfo->fix.visual = FB_VISUAL_TRUECOLOR; + fbinfo->fix.type = FB_TYPE_PACKED_PIXELS; + fbinfo->fix.type_aux = 0; + fbinfo->fix.xpanstep = 0; + fbinfo->fix.ypanstep = 0; + fbinfo->fix.ywrapstep = 0; + fbinfo->fix.accel = FB_ACCEL_GLAMO; + + + fbinfo->fbops = &glamofb_ops; + fbinfo->flags = FBINFO_FLAG_DEFAULT; + fbinfo->pseudo_palette = &glamofb->pseudo_pal; + + fbinfo->mode = mach_info->modes; + fb_videomode_to_var(&fbinfo->var, fbinfo->mode); + fbinfo->var.bits_per_pixel = 16; + fbinfo->var.nonstd = 0; + fbinfo->var.activate = FB_ACTIVATE_NOW; + fbinfo->var.height = mach_info->height; + fbinfo->var.width = mach_info->width; + fbinfo->var.accel_flags = 0; + fbinfo->var.vmode = FB_VMODE_NONINTERLACED; + + glamo_engine_enable(core, GLAMO_ENGINE_LCD); + glamo_engine_reset(core, GLAMO_ENGINE_LCD); + glamofb->output_enabled = 1; + glamofb->mode_set = 1; + + dev_info(&pdev->dev, "spin_lock_init\n"); + spin_lock_init(&glamofb->lock_cmd); + glamofb_init_regs(glamofb); +#ifdef CONFIG_MFD_GLAMO_HWACCEL + glamofb_cursor_onoff(glamofb, 1); +#endif + + fb_videomode_to_modelist(mach_info->modes, mach_info->num_modes, + &fbinfo->modelist); + + rc = register_framebuffer(fbinfo); + if (rc < 0) { + dev_err(&pdev->dev, "failed to register framebuffer\n"); + goto out_unmap_fb; + } + + printk(KERN_INFO "fb%d: %s frame buffer device\n", + fbinfo->node, fbinfo->fix.id); + + return 0; + +out_unmap_fb: + iounmap(fbinfo->screen_base); + iounmap(glamofb->base); +out_release_fb: + release_mem_region(glamofb->fb_res->start, + resource_size(glamofb->fb_res)); +out_release_reg: + release_mem_region(glamofb->reg->start, + resource_size(glamofb->reg)); +out_free: + framebuffer_release(fbinfo); + return rc; +} + +static int glamofb_remove(struct platform_device *pdev) +{ + struct glamofb_handle *glamofb = platform_get_drvdata(pdev); + + platform_set_drvdata(pdev, NULL); + iounmap(glamofb->base); + release_mem_region(glamofb->reg->start, resource_size(glamofb->reg)); + + framebuffer_release(glamofb->fb); + + return 0; +} + +#ifdef CONFIG_PM + +static int glamofb_suspend(struct device *dev) +{ + struct glamofb_handle *gfb = dev_get_drvdata(dev); + + acquire_console_sem(); + fb_set_suspend(gfb->fb, 1); + release_console_sem(); + + /* seriously -- nobody is allowed to touch glamo memory when we + * are suspended or we lock on nWAIT + */ + /* iounmap(gfb->fb->screen_base); */ + + return 0; +} + +static int glamofb_resume(struct device *dev) +{ + struct glamofb_handle *gfb = dev_get_drvdata(dev); + + /* OK let's allow framebuffer ops again */ + /* gfb->fb->screen_base = ioremap(gfb->fb_res->start, + resource_size(gfb->fb_res)); */ + glamo_engine_enable(gfb->core, GLAMO_ENGINE_LCD); + glamo_engine_reset(gfb->core, GLAMO_ENGINE_LCD); + + glamofb_init_regs(gfb); +#ifdef CONFIG_MFD_GLAMO_HWACCEL + glamofb_cursor_onoff(gfb, 1); +#endif + + acquire_console_sem(); + fb_set_suspend(gfb->fb, 0); + release_console_sem(); + + return 0; +} + +static struct dev_pm_ops glamofb_pm_ops = { + .suspend = glamofb_suspend, + .resume = glamofb_resume, +}; + +#define GLAMOFB_PM_OPS (&glamofb_pm_ops) + +#else +#define GLAMOFB_PM_OPS NULL +#endif + +static struct platform_driver glamofb_driver = { + .probe = glamofb_probe, + .remove = glamofb_remove, + .driver = { + .name = "glamo-fb", + .owner = THIS_MODULE, + .pm = GLAMOFB_PM_OPS + }, +}; + +static int __devinit glamofb_init(void) +{ + return platform_driver_register(&glamofb_driver); +} + +static void __exit glamofb_cleanup(void) +{ + platform_driver_unregister(&glamofb_driver); +} + +module_init(glamofb_init); +module_exit(glamofb_cleanup); + +MODULE_AUTHOR("Harald Welte "); +MODULE_DESCRIPTION("Smedia Glamo 336x/337x framebuffer driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/glamo/glamo-gpio.c b/drivers/mfd/glamo/glamo-gpio.c new file mode 100644 index 00000000000..30ee6ed1d9a --- /dev/null +++ b/drivers/mfd/glamo/glamo-gpio.c @@ -0,0 +1,285 @@ +/* Smedia Glamo 336x/337x gpio driver + * + * (C) 2009 Lars-Peter Clausen + * + * 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 +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "glamo-core.h" +#include "glamo-regs.h" + +#define GLAMO_NR_GPIO 21 +#define GLAMO_NR_GPIO_REGS DIV_ROUND_UP(GLAMO_NR_GPIO, 4) + +#define GLAMO_REG_GPIO(x) (((x) * 2) + GLAMO_REG_GPIO_GEN1) + +struct glamo_gpio { + struct glamo_core *glamo; + struct gpio_chip chip; + uint16_t saved_regs[GLAMO_NR_GPIO_REGS]; +}; + +#define REG_OF_GPIO(gpio) (GLAMO_REG_GPIO(gpio >> 2)) +#define NUM_OF_GPIO(gpio) (gpio & 0x3) +#define DIRECTION_BIT(gpio) (1 << (NUM_OF_GPIO(gpio) + 0)) +#define OUTPUT_BIT(gpio) (1 << (NUM_OF_GPIO(gpio) + 4)) +#define INPUT_BIT(gpio) (1 << (NUM_OF_GPIO(gpio) + 8)) +#define FUNC_BIT(gpio) (1 << (NUM_OF_GPIO(gpio) + 12)) + + +static inline struct glamo_core *chip_to_glamo(struct gpio_chip *chip) +{ + return container_of(chip, struct glamo_gpio, chip)->glamo; +} + +static void glamo_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + struct glamo_core *glamo = chip_to_glamo(chip); + unsigned int reg = REG_OF_GPIO(offset); + u_int16_t tmp; + + spin_lock(&glamo->lock); + tmp = readw(glamo->base + reg); + if (value) + tmp |= OUTPUT_BIT(offset); + else + tmp &= ~OUTPUT_BIT(offset); + writew(tmp, glamo->base + reg); + spin_unlock(&glamo->lock); +} + +static int glamo_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct glamo_core *glamo = chip_to_glamo(chip); + return !!(readw(glamo->base + REG_OF_GPIO(offset)) & INPUT_BIT(offset)); +} + +static int glamo_gpio_request(struct gpio_chip *chip, unsigned offset) +{ + struct glamo_core *glamo = chip_to_glamo(chip); + unsigned int reg = REG_OF_GPIO(offset); + u_int16_t tmp; + + spin_lock(&glamo->lock); + tmp = readw(glamo->base + reg); + if ((tmp & FUNC_BIT(offset)) == 0) { + tmp |= FUNC_BIT(offset); + writew(tmp, glamo->base + reg); + } + spin_unlock(&glamo->lock); + + return 0; +} + +static void glamo_gpio_free(struct gpio_chip *chip, unsigned offset) +{ + struct glamo_core *glamo = chip_to_glamo(chip); + unsigned int reg = REG_OF_GPIO(offset); + u_int16_t tmp; + + spin_lock(&glamo->lock); + tmp = readw(glamo->base + reg); + if ((tmp & FUNC_BIT(offset)) == 1) { + tmp &= ~FUNC_BIT(offset); + writew(tmp, glamo->base + reg); + } + spin_unlock(&glamo->lock); +} + +static int glamo_gpio_direction_output(struct gpio_chip *chip, unsigned offset, + int value) +{ + struct glamo_core *glamo = chip_to_glamo(chip); + unsigned int reg = REG_OF_GPIO(offset); + u_int16_t tmp; + + spin_lock(&glamo->lock); + tmp = readw(glamo->base + reg); + tmp &= ~DIRECTION_BIT(offset); + + if (value) + tmp |= OUTPUT_BIT(offset); + else + tmp &= ~OUTPUT_BIT(offset); + + writew(tmp, glamo->base + reg); + spin_unlock(&glamo->lock); + + return 0; +} + +static int glamo_gpio_direction_input(struct gpio_chip *chip, unsigned offset) +{ + struct glamo_core *glamo = chip_to_glamo(chip); + unsigned int reg = REG_OF_GPIO(offset); + u_int16_t tmp; + + spin_lock(&glamo->lock); + tmp = readw(glamo->base + reg); + if ((tmp & DIRECTION_BIT(offset)) == 0) { + tmp |= DIRECTION_BIT(offset); + writew(tmp, glamo->base + reg); + } + spin_unlock(&glamo->lock); + + return 0; +} + +static const struct __devinit gpio_chip glamo_gpio_chip = { + .label = "glamo", + .request = glamo_gpio_request, + .free = glamo_gpio_free, + .direction_input = glamo_gpio_direction_input, + .get = glamo_gpio_get, + .direction_output = glamo_gpio_direction_output, + .set = glamo_gpio_set, + .base = -1, + .ngpio = GLAMO_NR_GPIO, + .can_sleep = 0, + .owner = THIS_MODULE, +}; + +static int __devinit glamo_gpio_probe(struct platform_device *pdev) +{ + struct glamo_platform_data *pdata = pdev->dev.parent->platform_data; + struct glamo_gpio *glamo_gpio; + int ret; + + glamo_gpio = kzalloc(sizeof(struct glamo_gpio), GFP_KERNEL); + if (!glamo_gpio) + return -ENOMEM; + + glamo_gpio->glamo = dev_get_drvdata(pdev->dev.parent); + glamo_gpio->chip = glamo_gpio_chip; + glamo_gpio->chip.dev = &pdev->dev; + if (pdata && pdata->gpio_data) + glamo_gpio->chip.base = pdata->gpio_data->base; + + ret = gpiochip_add(&glamo_gpio->chip); + + if (ret) { + dev_err(&pdev->dev, "Could not register gpio chip: %d\n", ret); + goto err; + } + + platform_set_drvdata(pdev, glamo_gpio); + + if (pdata && pdata->gpio_data && pdata->gpio_data->registered) + pdata->gpio_data->registered(&pdev->dev); + + return 0; +err: + kfree(glamo_gpio); + return ret; +} + +static int __devexit glamo_gpio_remove(struct platform_device *pdev) +{ + struct glamo_gpio *glamo_gpio = platform_get_drvdata(pdev); + int ret; + + ret = gpiochip_remove(&glamo_gpio->chip); + if (!ret) + goto done; + + platform_set_drvdata(pdev, NULL); + kfree(glamo_gpio); + +done: + return ret; +} + +#ifdef CONFIG_PM + +static int glamo_gpio_suspend(struct device *dev) +{ + struct glamo_gpio *glamo_gpio = dev_get_drvdata(dev); + struct glamo_core *glamo = glamo_gpio->glamo; + uint16_t *saved_regs = glamo_gpio->saved_regs; + int i; + + spin_lock(&glamo->lock); + for (i = 0; i < GLAMO_NR_GPIO / 4; ++i) + saved_regs[i] = readw(glamo->base + GLAMO_REG_GPIO(i)); + spin_unlock(&glamo->lock); + + return 0; +} + +static int glamo_gpio_resume(struct device *dev) +{ + struct glamo_gpio *glamo_gpio = dev_get_drvdata(dev); + struct glamo_core *glamo = glamo_gpio->glamo; + uint16_t *saved_regs = glamo_gpio->saved_regs; + int i; + + spin_lock(&glamo->lock); + for (i = 0; i < GLAMO_NR_GPIO_REGS; ++i) + writew(saved_regs[i], glamo->base + GLAMO_REG_GPIO(i)); + spin_unlock(&glamo->lock); + return 0; +} + +static const struct dev_pm_ops glamo_pm_ops = { + .suspend = glamo_gpio_suspend, + .resume = glamo_gpio_resume, + .freeze = glamo_gpio_suspend, + .thaw = glamo_gpio_resume, +}; + +#define GLAMO_GPIO_PM_OPS (&glamo_pm_ops) + +#else +#define GLAMO_GPIO_PM_OPS NULL +#endif + +static struct platform_driver glamo_gpio_driver = { + .driver = { + .name = "glamo-gpio", + .owner = THIS_MODULE, + .pm = GLAMO_GPIO_PM_OPS, + }, + .probe = glamo_gpio_probe, + .remove = __devexit_p(glamo_gpio_remove), +}; + +static int __devinit glamo_gpio_init(void) +{ + return platform_driver_register(&glamo_gpio_driver); +} +module_init(glamo_gpio_init); + +static void __exit glamo_gpio_exit(void) +{ + platform_driver_unregister(&glamo_gpio_driver); +} +module_exit(glamo_gpio_exit); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("GPIO interface for the Glamo multimedia device"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:glamo-gpio"); diff --git a/drivers/mfd/glamo/glamo-mci.c b/drivers/mfd/glamo/glamo-mci.c new file mode 100644 index 00000000000..f49fa7167aa --- /dev/null +++ b/drivers/mfd/glamo/glamo-mci.c @@ -0,0 +1,1023 @@ +/* + * linux/drivers/mmc/host/glamo-mmc.c - Glamo MMC driver + * + * Copyright (C) 2007 Openmoko, Inc, Andy Green + * Copyright (C) 2009, Lars-Peter Clausen + * Based on S3C MMC driver that was: + * Copyright (C) 2004-2006 maintech GmbH, Thomas Kleffel + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "glamo-core.h" +#include "glamo-regs.h" + +struct glamo_mci_host { + struct glamo_mmc_platform_data *pdata; + struct platform_device *pdev; + struct glamo_core *core; + struct mmc_host *mmc; + struct resource *mmio_mem; + struct resource *data_mem; + void __iomem *mmio_base; + uint16_t __iomem *data_base; + + unsigned int irq; + + struct regulator *regulator; + struct mmc_request *mrq; + + unsigned int clk_rate; + + unsigned short vdd; + char power_mode; + + unsigned char request_counter; + + struct timer_list disable_timer; + + struct work_struct irq_work; + struct work_struct read_work; + + unsigned clk_enabled:1; +}; + +static void glamo_mci_send_request(struct mmc_host *mmc, + struct mmc_request *mrq); +static void glamo_mci_send_command(struct glamo_mci_host *host, + struct mmc_command *cmd); + +/* + * Max SD clock rate + * + * held at /(3 + 1) due to concerns of 100R recommended series resistor + * allows 16MHz @ 4-bit --> 8MBytes/sec raw + * + * you can override this on kernel commandline using + * + * glamo_mci.sd_max_clk=10000000 + * + * for example + */ + +static int sd_max_clk = 21000000; +module_param(sd_max_clk, int, 0644); + +/* + * Slow SD clock rate + * + * you can override this on kernel commandline using + * + * glamo_mci.sd_slow_ratio=8 + * + * for example + * + * platform callback is used to decide effective clock rate, if not + * defined then max is used, if defined and returns nonzero, rate is + * divided by this factor + */ + +static int sd_slow_ratio = 8; +module_param(sd_slow_ratio, int, 0644); + +/* + * Post-power SD clock rate + * + * you can override this on kernel commandline using + * + * glamo_mci.sd_post_power_clock=1000000 + * + * for example + * + * After changing power to card, clock is held at this rate until first bulk + * transfer completes + */ + +static int sd_post_power_clock = 1000000; +module_param(sd_post_power_clock, int, 0644); + + +static inline void glamo_reg_write(struct glamo_mci_host *glamo, + uint16_t reg, uint16_t val) +{ + writew(val, glamo->mmio_base + reg); +} + +static inline uint16_t glamo_reg_read(struct glamo_mci_host *glamo, + uint16_t reg) +{ + return readw(glamo->mmio_base + reg); +} + +static void glamo_reg_set_bit_mask(struct glamo_mci_host *glamo, + uint16_t reg, uint16_t mask, + uint16_t val) +{ + uint16_t tmp; + + val &= mask; + + tmp = glamo_reg_read(glamo, reg); + tmp &= ~mask; + tmp |= val; + glamo_reg_write(glamo, reg, tmp); +} + +static void glamo_mci_reset(struct glamo_mci_host *host) +{ + glamo_engine_reset(host->core, GLAMO_ENGINE_MMC); + + glamo_reg_write(host, GLAMO_REG_MMC_WDATADS1, + (uint16_t)(host->data_mem->start)); + glamo_reg_write(host, GLAMO_REG_MMC_WDATADS2, + (uint16_t)(host->data_mem->start >> 16)); + + glamo_reg_write(host, GLAMO_REG_MMC_RDATADS1, + (uint16_t)(host->data_mem->start)); + glamo_reg_write(host, GLAMO_REG_MMC_RDATADS2, + (uint16_t)(host->data_mem->start >> 16)); + +} + +static void glamo_mci_clock_disable(struct glamo_mci_host *host) +{ + glamo_engine_suspend(host->core, GLAMO_ENGINE_MMC); +} + +static void glamo_mci_clock_enable(struct glamo_mci_host *host) +{ + del_timer_sync(&host->disable_timer); + + glamo_engine_enable(host->core, GLAMO_ENGINE_MMC); +} + +static void glamo_mci_disable_timer(unsigned long data) +{ + struct glamo_mci_host *host = (struct glamo_mci_host *)data; + glamo_mci_clock_disable(host); +} + + +static void do_pio_read(struct glamo_mci_host *host, struct mmc_data *data) +{ + struct sg_mapping_iter miter; + uint16_t __iomem *from_ptr = host->data_base; + + dev_dbg(&host->pdev->dev, "pio_read():\n"); + + sg_miter_start(&miter, data->sg, data->sg_len, SG_MITER_TO_SG); + + while (sg_miter_next(&miter)) { + memcpy(miter.addr, from_ptr, miter.length); + from_ptr += miter.length >> 1; + + data->bytes_xfered += miter.length; + } + + sg_miter_stop(&miter); + + dev_dbg(&host->pdev->dev, "pio_read(): " + "complete (no more data).\n"); +} + +static void do_pio_write(struct glamo_mci_host *host, struct mmc_data *data) +{ + struct sg_mapping_iter miter; + uint16_t __iomem *to_ptr = host->data_base; + + dev_dbg(&host->pdev->dev, "pio_write():\n"); + sg_miter_start(&miter, data->sg, data->sg_len, SG_MITER_FROM_SG); + + while (sg_miter_next(&miter)) { + memcpy(to_ptr, miter.addr, miter.length); + to_ptr += miter.length >> 1; + + data->bytes_xfered += miter.length; + } + + sg_miter_stop(&miter); + dev_dbg(&host->pdev->dev, "pio_write(): complete\n"); +} + +static int glamo_mci_set_card_clock(struct glamo_mci_host *host, int freq) +{ + int real_rate = 0; + + if (freq) { + glamo_mci_clock_enable(host); + real_rate = glamo_engine_reclock(host->core, GLAMO_ENGINE_MMC, + freq); + } else { + glamo_mci_clock_disable(host); + } + + return real_rate; +} + +static int glamo_mci_wait_idle(struct glamo_mci_host *host, + unsigned long timeout) +{ + uint16_t status; + do { + status = glamo_reg_read(host, GLAMO_REG_MMC_RB_STAT1); + } while (!(status & GLAMO_STAT1_MMC_IDLE) && + time_is_after_jiffies(timeout)); + + if (time_is_before_eq_jiffies(timeout)) { + glamo_mci_reset(host); + return -ETIMEDOUT; + } + + return 0; +} + +static void glamo_mci_request_done(struct glamo_mci_host *host, struct +mmc_request *mrq) +{ + mod_timer(&host->disable_timer, jiffies + HZ / 16); + mmc_request_done(host->mmc, mrq); +} + + +static void glamo_mci_irq_worker(struct work_struct *work) +{ + struct glamo_mci_host *host = container_of(work, struct glamo_mci_host, + irq_work); + struct mmc_request *mrq; + struct mmc_command *cmd; + uint16_t status; + + if (!host->mrq || !host->mrq->cmd) + return; + + mrq = host->mrq; + cmd = mrq->cmd; + +#if 0 + if (cmd->data->flags & MMC_DATA_READ) + return; +#endif + + status = glamo_reg_read(host, GLAMO_REG_MMC_RB_STAT1); + dev_dbg(&host->pdev->dev, "status = 0x%04x\n", status); + + /* we ignore a data timeout report if we are also told the data came */ + if (status & GLAMO_STAT1_MMC_RB_DRDY) + status &= ~GLAMO_STAT1_MMC_DTOUT; + + if (status & (GLAMO_STAT1_MMC_RTOUT | GLAMO_STAT1_MMC_DTOUT)) + cmd->error = -ETIMEDOUT; + else if (status & (GLAMO_STAT1_MMC_BWERR | GLAMO_STAT1_MMC_BRERR)) + cmd->error = -EILSEQ; + + if (cmd->error) { + dev_info(&host->pdev->dev, "Error after cmd: 0x%x\n", status); + goto done; + } + + /* issue STOP if we have been given one to use */ + if (mrq->stop) + glamo_mci_send_command(host, mrq->stop); + + if (cmd->data->flags & MMC_DATA_READ) + do_pio_read(host, cmd->data); + + if (mrq->stop) + mrq->stop->error = glamo_mci_wait_idle(host, jiffies + HZ); + +done: + host->mrq = NULL; + glamo_mci_request_done(host, cmd->mrq); +} + +static void glamo_mci_read_worker(struct work_struct *work) +{ + struct glamo_mci_host *host = container_of(work, struct glamo_mci_host, + read_work); + struct mmc_command *cmd; + uint16_t status; + uint16_t blocks_ready; + size_t data_read = 0; + size_t data_ready; + struct scatterlist *sg; + uint16_t __iomem *from_ptr = host->data_base; + void *sg_pointer; + + + cmd = host->mrq->cmd; + sg = cmd->data->sg; + do { + status = glamo_reg_read(host, GLAMO_REG_MMC_RB_STAT1); + + if (status & (GLAMO_STAT1_MMC_RTOUT | GLAMO_STAT1_MMC_DTOUT)) + cmd->error = -ETIMEDOUT; + if (status & (GLAMO_STAT1_MMC_BWERR | GLAMO_STAT1_MMC_BRERR)) + cmd->error = -EILSEQ; + if (cmd->error) { + dev_info(&host->pdev->dev, "Error after cmd: 0x%x\n", + status); + goto done; + } + + blocks_ready = glamo_reg_read(host, GLAMO_REG_MMC_RB_BLKCNT); + data_ready = blocks_ready * cmd->data->blksz; + + if (data_ready == data_read) + yield(); + + while (sg && data_read + sg->length <= data_ready) { + sg_pointer = page_address(sg_page(sg)) + sg->offset; + memcpy(sg_pointer, from_ptr, sg->length); + from_ptr += sg->length >> 1; + + data_read += sg->length; + sg = sg_next(sg); + } + + } while (sg); + cmd->data->bytes_xfered = data_read; + + do { + status = glamo_reg_read(host, GLAMO_REG_MMC_RB_STAT1); + } while (!(status & GLAMO_STAT1_MMC_IDLE)); + + if (host->mrq->stop) + glamo_mci_send_command(host, host->mrq->stop); + + do { + status = glamo_reg_read(host, GLAMO_REG_MMC_RB_STAT1); + } while (!(status & GLAMO_STAT1_MMC_IDLE)); +done: + host->mrq = NULL; + glamo_mci_request_done(host, cmd->mrq); +} + +static irqreturn_t glamo_mci_irq(int irq, void *devid) +{ + struct glamo_mci_host *host = (struct glamo_mci_host *)devid; + schedule_work(&host->irq_work); + + return IRQ_HANDLED; +} + +static void glamo_mci_send_command(struct glamo_mci_host *host, + struct mmc_command *cmd) +{ + uint8_t u8a[6]; + uint16_t fire = 0; + unsigned int timeout = 1000000; + uint16_t *reg_resp = (uint16_t *)(host->mmio_base + GLAMO_REG_MMC_CMD_RSP1); + uint16_t status; + int triggers_int = 1; + + /* if we can't do it, reject as busy */ + if (!(glamo_reg_read(host, GLAMO_REG_MMC_RB_STAT1) & + GLAMO_STAT1_MMC_IDLE)) { + cmd->error = -EBUSY; + return; + } + + /* create an array in wire order for CRC computation */ + u8a[0] = 0x40 | (cmd->opcode & 0x3f); + u8a[1] = (uint8_t)(cmd->arg >> 24); + u8a[2] = (uint8_t)(cmd->arg >> 16); + u8a[3] = (uint8_t)(cmd->arg >> 8); + u8a[4] = (uint8_t)cmd->arg; + u8a[5] = (crc7(0, u8a, 5) << 1) | 0x01; + + /* issue the wire-order array including CRC in register order */ + glamo_reg_write(host, GLAMO_REG_MMC_CMD_REG1, ((u8a[4] << 8) | u8a[5])); + glamo_reg_write(host, GLAMO_REG_MMC_CMD_REG2, ((u8a[2] << 8) | u8a[3])); + glamo_reg_write(host, GLAMO_REG_MMC_CMD_REG3, ((u8a[0] << 8) | u8a[1])); + + /* command index toggle */ + fire |= (host->request_counter & 1) << 12; + + /* set type of command */ + switch (mmc_cmd_type(cmd)) { + case MMC_CMD_BC: + fire |= GLAMO_FIRE_MMC_CMDT_BNR; + break; + case MMC_CMD_BCR: + fire |= GLAMO_FIRE_MMC_CMDT_BR; + break; + case MMC_CMD_AC: + fire |= GLAMO_FIRE_MMC_CMDT_AND; + break; + case MMC_CMD_ADTC: + fire |= GLAMO_FIRE_MMC_CMDT_AD; + break; + } + /* + * if it expects a response, set the type expected + * + * R1, Length : 48bit, Normal response + * R1b, Length : 48bit, same R1, but added card busy status + * R2, Length : 136bit (really 128 bits with CRC snipped) + * R3, Length : 48bit (OCR register value) + * R4, Length : 48bit, SDIO_OP_CONDITION, Reverse SDIO Card + * R5, Length : 48bit, IO_RW_DIRECTION, Reverse SDIO Card + * R6, Length : 48bit (RCA register) + * R7, Length : 48bit (interface condition, VHS(voltage supplied), + * check pattern, CRC7) + */ + switch (mmc_resp_type(cmd)) { + case MMC_RSP_R1: /* same index as R6 and R7 */ + fire |= GLAMO_FIRE_MMC_RSPT_R1; + break; + case MMC_RSP_R1B: + fire |= GLAMO_FIRE_MMC_RSPT_R1b; + break; + case MMC_RSP_R2: + fire |= GLAMO_FIRE_MMC_RSPT_R2; + break; + case MMC_RSP_R3: + fire |= GLAMO_FIRE_MMC_RSPT_R3; + break; + /* R4 and R5 supported by chip not defined in linux/mmc/core.h (sdio) */ + } + /* + * From the command index, set up the command class in the host ctrllr + * + * missing guys present on chip but couldn't figure out how to use yet: + * 0x0 "stream read" + * 0x9 "cancel running command" + */ + switch (cmd->opcode) { + case MMC_READ_SINGLE_BLOCK: + fire |= GLAMO_FIRE_MMC_CC_SBR; /* single block read */ + break; + case MMC_SWITCH: /* 64 byte payload */ + case SD_APP_SEND_SCR: + case MMC_READ_MULTIPLE_BLOCK: + /* we will get an interrupt off this */ + if (!cmd->mrq->stop) { + /* multiblock no stop */ + fire |= GLAMO_FIRE_MMC_CC_MBRNS; + } else { + /* multiblock with stop */ + fire |= GLAMO_FIRE_MMC_CC_MBRS; + } + break; + case MMC_WRITE_BLOCK: + fire |= GLAMO_FIRE_MMC_CC_SBW; /* single block write */ + break; + case MMC_WRITE_MULTIPLE_BLOCK: + if (cmd->mrq->stop) { + /* multiblock with stop */ + fire |= GLAMO_FIRE_MMC_CC_MBWS; + } else { + /* multiblock NO stop-- 'RESERVED'? */ + fire |= GLAMO_FIRE_MMC_CC_MBWNS; + } + break; + case MMC_STOP_TRANSMISSION: + fire |= GLAMO_FIRE_MMC_CC_STOP; /* STOP */ + triggers_int = 0; + break; + default: + fire |= GLAMO_FIRE_MMC_CC_BASIC; /* "basic command" */ + triggers_int = 0; + break; + } + + if (cmd->data) + host->mrq = cmd->mrq; + + /* always largest timeout */ + glamo_reg_write(host, GLAMO_REG_MMC_TIMEOUT, 0xfff); + + /* Generate interrupt on txfer */ + glamo_reg_set_bit_mask(host, GLAMO_REG_MMC_BASIC, 0xff36, + 0x0800 | + GLAMO_BASIC_MMC_NO_CLK_RD_WAIT | + GLAMO_BASIC_MMC_EN_COMPL_INT | + GLAMO_BASIC_MMC_EN_DATA_PUPS | + GLAMO_BASIC_MMC_EN_CMD_PUP); + + /* send the command out on the wire */ + /* dev_info(&host->pdev->dev, "Using FIRE %04X\n", fire); */ + glamo_reg_write(host, GLAMO_REG_MMC_CMD_FIRE, fire); + + /* we are deselecting card? because it isn't going to ack then... */ + if ((cmd->opcode == 7) && (cmd->arg == 0)) + return; + + /* + * we must spin until response is ready or timed out + * -- we don't get interrupts unless there is a bulk rx + */ + do + status = glamo_reg_read(host, GLAMO_REG_MMC_RB_STAT1); + while (((((status >> 15) & 1) != (host->request_counter & 1)) || + (!(status & (GLAMO_STAT1_MMC_RB_RRDY | + GLAMO_STAT1_MMC_RTOUT | + GLAMO_STAT1_MMC_DTOUT | + GLAMO_STAT1_MMC_BWERR | + GLAMO_STAT1_MMC_BRERR)))) && (timeout--)); + + if ((status & (GLAMO_STAT1_MMC_RTOUT | + GLAMO_STAT1_MMC_DTOUT)) || + (timeout == 0)) { + cmd->error = -ETIMEDOUT; + } else if (status & (GLAMO_STAT1_MMC_BWERR | GLAMO_STAT1_MMC_BRERR)) { + cmd->error = -EILSEQ; + } + + if (cmd->flags & MMC_RSP_PRESENT) { + if (cmd->flags & MMC_RSP_136) { + cmd->resp[3] = readw(®_resp[0]) | + (readw(®_resp[1]) << 16); + cmd->resp[2] = readw(®_resp[2]) | + (readw(®_resp[3]) << 16); + cmd->resp[1] = readw(®_resp[4]) | + (readw(®_resp[5]) << 16); + cmd->resp[0] = readw(®_resp[6]) | + (readw(®_resp[7]) << 16); + } else { + cmd->resp[0] = (readw(®_resp[0]) >> 8) | + (readw(®_resp[1]) << 8) | + (readw(®_resp[2]) << 24); + } + } + +#if 0 + /* We'll only get an interrupt when all data has been transfered. + By starting to copy data when it's avaiable we can increase + throughput by up to 30%. */ + if (cmd->data && (cmd->data->flags & MMC_DATA_READ)) + schedule_work(&host->read_work); +#endif + +} + +static int glamo_mci_prepare_pio(struct glamo_mci_host *host, + struct mmc_data *data) +{ + /* set up the block info */ + glamo_reg_write(host, GLAMO_REG_MMC_DATBLKLEN, data->blksz); + glamo_reg_write(host, GLAMO_REG_MMC_DATBLKCNT, data->blocks); + + data->bytes_xfered = 0; + + /* if write, prep the write into the shared RAM before the command */ + if (data->flags & MMC_DATA_WRITE) + do_pio_write(host, data); + + dev_dbg(&host->pdev->dev, "(blksz=%d, count=%d)\n", + data->blksz, data->blocks); + return 0; +} + +static int glamo_mci_irq_poll(struct glamo_mci_host *host, + struct mmc_command *cmd) +{ + int timeout = 1000000; + uint16_t status; + /* + * if the glamo INT# line isn't wired (*cough* it can happen) + * I'm afraid we have to spin on the IRQ status bit and "be + * our own INT# line" + */ + /* + * we have faith we will get an "interrupt"... + * but something insane like suspend problems can mean + * we spin here forever, so we timeout after a LONG time + */ + do { + status = glamo_reg_read(host, GLAMO_REG_IRQ_STATUS); + } while ((--timeout) && !(status & GLAMO_IRQ_MMC)); + + if (timeout <= 0) { + if (cmd->data->error) + cmd->data->error = -ETIMEDOUT; + dev_err(&host->pdev->dev, "Payload timeout\n"); + return -ETIMEDOUT; + } + /* ack this interrupt source */ + writew(GLAMO_IRQ_MMC, host->core->base + + GLAMO_REG_IRQ_CLEAR); + + /* yay we are an interrupt controller! -- call the ISR + * it will stop clock to card + */ + glamo_mci_irq(host->irq, host); + + return 0; +} + +static void glamo_mci_send_request(struct mmc_host *mmc, + struct mmc_request *mrq) +{ + struct glamo_mci_host *host = mmc_priv(mmc); + struct mmc_command *cmd = mrq->cmd; + + glamo_mci_clock_enable(host); + host->request_counter++; + if (cmd->data) { + if (glamo_mci_prepare_pio(host, cmd->data)) { + cmd->error = -EIO; + cmd->data->error = -EIO; + goto done; + } + } + + dev_dbg(&host->pdev->dev, "cmd 0x%x, " + "arg 0x%x data=%p mrq->stop=%p flags 0x%x\n", + cmd->opcode, cmd->arg, cmd->data, cmd->mrq->stop, + cmd->flags); + + glamo_mci_send_command(host, cmd); + + /* + * if we don't have bulk data to take care of, we're done + */ + if (!cmd->data || cmd->error) + goto done; + + + if (!host->core->irq_works) { + if (glamo_mci_irq_poll(host, mrq->cmd)) + goto done; + } + + /* + * Otherwise can can use the interrupt as async completion -- + * if there is read data coming, or we wait for write data to complete, + * exit without mmc_request_done() as the payload interrupt + * will service it + */ + dev_dbg(&host->pdev->dev, "Waiting for payload data\n"); + return; +done: + if (!cmd->error) + cmd->error = glamo_mci_wait_idle(host, jiffies + 2 * HZ); + glamo_mci_request_done(host, mrq); +} + +static void glamo_mci_set_power_mode(struct glamo_mci_host *host, + unsigned char power_mode) +{ + int ret; + + if (power_mode == host->power_mode) + return; + + switch (power_mode) { + case MMC_POWER_UP: + if (host->power_mode == MMC_POWER_OFF) { + ret = regulator_enable(host->regulator); + if (ret) + dev_err(&host->pdev->dev, + "Failed to enable regulator: %d\n", + ret); + } + break; + case MMC_POWER_ON: + break; + case MMC_POWER_OFF: + default: + glamo_engine_disable(host->core, GLAMO_ENGINE_MMC); + + ret = regulator_disable(host->regulator); + if (ret) + dev_warn(&host->pdev->dev, + "Failed to disable regulator: %d\n", + ret); + break; + } + host->power_mode = power_mode; +} + +static void glamo_mci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct glamo_mci_host *host = mmc_priv(mmc); + int bus_width = 0; + int rate; + int sd_drive; + int ret; + + /* Set power */ + glamo_mci_set_power_mode(host, ios->power_mode); + + if (host->vdd != ios->vdd) { + ret = mmc_regulator_set_ocr(host->regulator, ios->vdd); + if (ret) + dev_err(&host->pdev->dev, + "Failed to set regulator voltage: %d\n", ret); + else + host->vdd = ios->vdd; + } + rate = glamo_mci_set_card_clock(host, ios->clock); + + if ((ios->power_mode == MMC_POWER_ON) || + (ios->power_mode == MMC_POWER_UP)) { + dev_info(&host->pdev->dev, + "powered (vdd = %hu) clk: %dkHz div=%hu (req: %ukHz). " + "Bus width=%d\n", ios->vdd, + rate / 1000, 0, + ios->clock / 1000, (int)ios->bus_width); + } else { + dev_info(&host->pdev->dev, "glamo_mci_set_ios: power down.\n"); + } + + /* set bus width */ + if (ios->bus_width == MMC_BUS_WIDTH_4) + bus_width = GLAMO_BASIC_MMC_EN_4BIT_DATA; + + sd_drive = (rate * 4) / host->clk_rate; + if (sd_drive > 3) + sd_drive = 3; + + glamo_reg_set_bit_mask(host, GLAMO_REG_MMC_BASIC, + GLAMO_BASIC_MMC_EN_4BIT_DATA | 0xb0, + bus_width | sd_drive << 6); +} + + +/* + * no physical write protect supported by us + */ +static int glamo_mci_get_ro(struct mmc_host *mmc) +{ + return 0; +} + +static struct mmc_host_ops glamo_mci_ops = { + .request = glamo_mci_send_request, + .set_ios = glamo_mci_set_ios, + .get_ro = glamo_mci_get_ro, +}; + +static int glamo_mci_probe(struct platform_device *pdev) +{ + struct mmc_host *mmc; + struct glamo_mci_host *host; + struct glamo_core *core = dev_get_drvdata(pdev->dev.parent); + int ret; + + dev_info(&pdev->dev, "glamo_mci driver (C)2007 Openmoko, Inc\n"); + + mmc = mmc_alloc_host(sizeof(struct glamo_mci_host), &pdev->dev); + if (!mmc) { + ret = -ENOMEM; + goto probe_out; + } + + host = mmc_priv(mmc); + host->mmc = mmc; + host->pdev = pdev; + if (core->pdata) + host->pdata = core->pdata->mmc_data; + host->power_mode = MMC_POWER_OFF; + host->clk_enabled = 0; + host->core = core; + host->irq = platform_get_irq(pdev, 0); + + INIT_WORK(&host->irq_work, glamo_mci_irq_worker); + INIT_WORK(&host->read_work, glamo_mci_read_worker); + + host->regulator = regulator_get(pdev->dev.parent, "SD_3V3"); + if (IS_ERR(host->regulator)) { + dev_err(&pdev->dev, "Cannot proceed without regulator.\n"); + ret = PTR_ERR(host->regulator); + goto probe_free_host; + } + + host->mmio_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!host->mmio_mem) { + dev_err(&pdev->dev, + "failed to get io memory region resouce.\n"); + ret = -ENOENT; + goto probe_regulator_put; + } + + host->mmio_mem = request_mem_region(host->mmio_mem->start, + resource_size(host->mmio_mem), + pdev->name); + + if (!host->mmio_mem) { + dev_err(&pdev->dev, "failed to request io memory region.\n"); + ret = -ENOENT; + goto probe_regulator_put; + } + + host->mmio_base = ioremap(host->mmio_mem->start, + resource_size(host->mmio_mem)); + if (!host->mmio_base) { + dev_err(&pdev->dev, "failed to ioremap() io memory region.\n"); + ret = -EINVAL; + goto probe_free_mem_region_mmio; + } + + + /* Get ahold of our data buffer we use for data in and out on MMC */ + host->data_mem = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!host->data_mem) { + dev_err(&pdev->dev, + "failed to get io memory region resource.\n"); + ret = -ENOENT; + goto probe_iounmap_mmio; + } + + host->data_mem = request_mem_region(host->data_mem->start, + resource_size(host->data_mem), + pdev->name); + + if (!host->data_mem) { + dev_err(&pdev->dev, "failed to request io memory region.\n"); + ret = -ENOENT; + goto probe_iounmap_mmio; + } + host->data_base = ioremap(host->data_mem->start, + resource_size(host->data_mem)); + + if (host->data_base == 0) { + dev_err(&pdev->dev, "failed to ioremap() io memory region.\n"); + ret = -EINVAL; + goto probe_free_mem_region_data; + } + + ret = request_irq(host->irq, glamo_mci_irq, IRQF_SHARED, + pdev->name, host); + if (ret) { + dev_err(&pdev->dev, "failed to register irq.\n"); + goto probe_iounmap_data; + } + + + host->vdd = 0; + host->clk_rate = glamo_pll_rate(host->core, GLAMO_PLL1); + + /* explain our host controller capabilities */ + mmc->ops = &glamo_mci_ops; + mmc->ocr_avail = mmc_regulator_get_ocrmask(host->regulator); + mmc->caps = MMC_CAP_4_BIT_DATA | + MMC_CAP_MMC_HIGHSPEED | + MMC_CAP_SD_HIGHSPEED; + mmc->f_min = host->clk_rate / 256; + mmc->f_max = sd_max_clk; + + mmc->max_blk_count = (1 << 16) - 1; /* GLAMO_REG_MMC_RB_BLKCNT */ + mmc->max_blk_size = (1 << 12) - 1; /* GLAMO_REG_MMC_RB_BLKLEN */ + mmc->max_req_size = resource_size(host->data_mem); + mmc->max_seg_size = mmc->max_req_size; + mmc->max_phys_segs = 128; + mmc->max_hw_segs = 128; + + if (mmc->ocr_avail < 0) { + dev_warn(&pdev->dev, + "Failed to get ocr list for regulator: %d.\n", + mmc->ocr_avail); + mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; + } + + platform_set_drvdata(pdev, mmc); + + glamo_engine_enable(host->core, GLAMO_ENGINE_MMC); + glamo_mci_reset(host); + glamo_engine_disable(host->core, GLAMO_ENGINE_MMC); + + setup_timer(&host->disable_timer, glamo_mci_disable_timer, + (unsigned long)host); + + ret = mmc_add_host(mmc); + if (ret) { + dev_err(&pdev->dev, "failed to add mmc host.\n"); + goto probe_freeirq; + } + + return 0; + +probe_freeirq: + free_irq(host->irq, host); +probe_iounmap_data: + iounmap(host->data_base); +probe_free_mem_region_data: + release_mem_region(host->data_mem->start, + resource_size(host->data_mem)); +probe_iounmap_mmio: + iounmap(host->mmio_base); +probe_free_mem_region_mmio: + release_mem_region(host->mmio_mem->start, + resource_size(host->mmio_mem)); +probe_regulator_put: + regulator_put(host->regulator); +probe_free_host: + mmc_free_host(mmc); +probe_out: + return ret; +} + +static int glamo_mci_remove(struct platform_device *pdev) +{ + struct mmc_host *mmc = platform_get_drvdata(pdev); + struct glamo_mci_host *host = mmc_priv(mmc); + + free_irq(host->irq, host); + + mmc_remove_host(mmc); + iounmap(host->mmio_base); + iounmap(host->data_base); + release_mem_region(host->mmio_mem->start, + resource_size(host->mmio_mem)); + release_mem_region(host->data_mem->start, + resource_size(host->data_mem)); + + regulator_put(host->regulator); + + mmc_free_host(mmc); + + glamo_engine_disable(host->core, GLAMO_ENGINE_MMC); + return 0; +} + + +#ifdef CONFIG_PM + +static int glamo_mci_suspend(struct device *dev) +{ + struct mmc_host *mmc = dev_get_drvdata(dev); + struct glamo_mci_host *host = mmc_priv(mmc); + int ret; + + cancel_work_sync(&host->irq_work); + + ret = mmc_suspend_host(mmc, PMSG_SUSPEND); + + return ret; +} + +static int glamo_mci_resume(struct device *dev) +{ + struct mmc_host *mmc = dev_get_drvdata(dev); + struct glamo_mci_host *host = mmc_priv(mmc); + int ret; + + + glamo_mci_reset(host); + glamo_engine_enable(host->core, GLAMO_ENGINE_MMC); + mdelay(10); + + ret = mmc_resume_host(host->mmc); + + return 0; +} + +static struct dev_pm_ops glamo_mci_pm_ops = { + .suspend = glamo_mci_suspend, + .resume = glamo_mci_resume, + .freeze = glamo_mci_suspend, + .thaw = glamo_mci_resume, +}; +#define GLAMO_MCI_PM_OPS (&glamo_mci_pm_ops) + +#else /* CONFIG_PM */ +#define GLAMO_MCI_PM_OPS NULL +#endif /* CONFIG_PM */ + + +static struct platform_driver glamo_mci_driver = { + .probe = glamo_mci_probe, + .remove = glamo_mci_remove, + .driver = { + .name = "glamo-mci", + .owner = THIS_MODULE, + .pm = GLAMO_MCI_PM_OPS, + }, +}; + +static int __init glamo_mci_init(void) +{ + platform_driver_register(&glamo_mci_driver); + return 0; +} +module_init(glamo_mci_init); + +static void __exit glamo_mci_exit(void) +{ + platform_driver_unregister(&glamo_mci_driver); +} +module_exit(glamo_mci_exit); + +MODULE_DESCRIPTION("Glamo MMC/SD Card Interface driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Andy Green "); diff --git a/drivers/mfd/glamo/glamo-regs.h b/drivers/mfd/glamo/glamo-regs.h new file mode 100644 index 00000000000..59848e1122e --- /dev/null +++ b/drivers/mfd/glamo/glamo-regs.h @@ -0,0 +1,630 @@ +#ifndef _GLAMO_REGS_H +#define _GLAMO_REGS_H + +/* Smedia Glamo 336x/337x driver + * + * (C) 2007 by Openmoko, Inc. + * Author: Harald Welte + * 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 + */ + +enum glamo_regster_offsets { + GLAMO_REGOFS_GENERIC = 0x0000, + GLAMO_REGOFS_HOSTBUS = 0x0200, + GLAMO_REGOFS_MEMORY = 0x0300, + GLAMO_REGOFS_VIDCAP = 0x0400, + GLAMO_REGOFS_ISP = 0x0500, + GLAMO_REGOFS_JPEG = 0x0800, + GLAMO_REGOFS_MPEG = 0x0c00, + GLAMO_REGOFS_LCD = 0x1100, + GLAMO_REGOFS_MMC = 0x1400, + GLAMO_REGOFS_MPROC0 = 0x1500, + GLAMO_REGOFS_MPROC1 = 0x1580, + GLAMO_REGOFS_CMDQUEUE = 0x1600, + GLAMO_REGOFS_RISC = 0x1680, + GLAMO_REGOFS_2D = 0x1700, + GLAMO_REGOFS_3D = 0x1b00, + GLAMO_REGOFS_END = 0x2400, +}; + + +enum glamo_register_generic { + GLAMO_REG_GCONF1 = 0x0000, + GLAMO_REG_GCONF2 = 0x0002, +#define GLAMO_REG_DEVICE_ID GLAMO_REG_GCONF2 + GLAMO_REG_GCONF3 = 0x0004, +#define GLAMO_REG_REVISION_ID GLAMO_REG_GCONF3 + GLAMO_REG_IRQ_GEN1 = 0x0006, +#define GLAMO_REG_IRQ_ENABLE GLAMO_REG_IRQ_GEN1 + GLAMO_REG_IRQ_GEN2 = 0x0008, +#define GLAMO_REG_IRQ_SET GLAMO_REG_IRQ_GEN2 + GLAMO_REG_IRQ_GEN3 = 0x000a, +#define GLAMO_REG_IRQ_CLEAR GLAMO_REG_IRQ_GEN3 + GLAMO_REG_IRQ_GEN4 = 0x000c, +#define GLAMO_REG_IRQ_STATUS GLAMO_REG_IRQ_GEN4 + GLAMO_REG_CLOCK_HOST = 0x0010, + GLAMO_REG_CLOCK_MEMORY = 0x0012, + GLAMO_REG_CLOCK_LCD = 0x0014, + GLAMO_REG_CLOCK_MMC = 0x0016, + GLAMO_REG_CLOCK_ISP = 0x0018, + GLAMO_REG_CLOCK_JPEG = 0x001a, + GLAMO_REG_CLOCK_3D = 0x001c, + GLAMO_REG_CLOCK_2D = 0x001e, + GLAMO_REG_CLOCK_RISC1 = 0x0020, /* 3365 only? */ + GLAMO_REG_CLOCK_RISC2 = 0x0022, /* 3365 only? */ + GLAMO_REG_CLOCK_MPEG = 0x0024, + GLAMO_REG_CLOCK_MPROC = 0x0026, + + GLAMO_REG_CLOCK_GEN5_1 = 0x0030, + GLAMO_REG_CLOCK_GEN5_2 = 0x0032, + GLAMO_REG_CLOCK_GEN6 = 0x0034, + GLAMO_REG_CLOCK_GEN7 = 0x0036, + GLAMO_REG_CLOCK_GEN8 = 0x0038, + GLAMO_REG_CLOCK_GEN9 = 0x003a, + GLAMO_REG_CLOCK_GEN10 = 0x003c, + GLAMO_REG_CLOCK_GEN11 = 0x003e, + GLAMO_REG_PLL_GEN1 = 0x0040, + GLAMO_REG_PLL_GEN2 = 0x0042, + GLAMO_REG_PLL_GEN3 = 0x0044, + GLAMO_REG_PLL_GEN4 = 0x0046, + GLAMO_REG_PLL_GEN5 = 0x0048, + GLAMO_REG_GPIO_GEN1 = 0x0050, + GLAMO_REG_GPIO_GEN2 = 0x0052, + GLAMO_REG_GPIO_GEN3 = 0x0054, + GLAMO_REG_GPIO_GEN4 = 0x0056, + GLAMO_REG_GPIO_GEN5 = 0x0058, + GLAMO_REG_GPIO_GEN6 = 0x005a, + GLAMO_REG_GPIO_GEN7 = 0x005c, + GLAMO_REG_GPIO_GEN8 = 0x005e, + GLAMO_REG_GPIO_GEN9 = 0x0060, + GLAMO_REG_GPIO_GEN10 = 0x0062, + GLAMO_REG_DFT_GEN1 = 0x0070, + GLAMO_REG_DFT_GEN2 = 0x0072, + GLAMO_REG_DFT_GEN3 = 0x0074, + GLAMO_REG_DFT_GEN4 = 0x0076, + + GLAMO_REG_DFT_GEN5 = 0x01e0, + GLAMO_REG_DFT_GEN6 = 0x01f0, +}; + +#define GLAMO_REG_HOSTBUS(x) (GLAMO_REGOFS_HOSTBUS-2+(x*2)) + +#define REG_MEM(x) (GLAMO_REGOFS_MEMORY+(x)) +#define GLAMO_REG_MEM_TIMING(x) (GLAMO_REG_MEM_TIMING1-2+(x*2)) + +enum glamo_register_mem { + GLAMO_REG_MEM_TYPE = REG_MEM(0x00), + GLAMO_REG_MEM_GEN = REG_MEM(0x02), + GLAMO_REG_MEM_TIMING1 = REG_MEM(0x04), + GLAMO_REG_MEM_TIMING2 = REG_MEM(0x06), + GLAMO_REG_MEM_TIMING3 = REG_MEM(0x08), + GLAMO_REG_MEM_TIMING4 = REG_MEM(0x0a), + GLAMO_REG_MEM_TIMING5 = REG_MEM(0x0c), + GLAMO_REG_MEM_TIMING6 = REG_MEM(0x0e), + GLAMO_REG_MEM_TIMING7 = REG_MEM(0x10), + GLAMO_REG_MEM_TIMING8 = REG_MEM(0x12), + GLAMO_REG_MEM_TIMING9 = REG_MEM(0x14), + GLAMO_REG_MEM_TIMING10 = REG_MEM(0x16), + GLAMO_REG_MEM_TIMING11 = REG_MEM(0x18), + GLAMO_REG_MEM_POWER1 = REG_MEM(0x1a), + GLAMO_REG_MEM_POWER2 = REG_MEM(0x1c), + GLAMO_REG_MEM_LCD_BUF1 = REG_MEM(0x1e), + GLAMO_REG_MEM_LCD_BUF2 = REG_MEM(0x20), + GLAMO_REG_MEM_LCD_BUF3 = REG_MEM(0x22), + GLAMO_REG_MEM_LCD_BUF4 = REG_MEM(0x24), + GLAMO_REG_MEM_BIST1 = REG_MEM(0x26), + GLAMO_REG_MEM_BIST2 = REG_MEM(0x28), + GLAMO_REG_MEM_BIST3 = REG_MEM(0x2a), + GLAMO_REG_MEM_BIST4 = REG_MEM(0x2c), + GLAMO_REG_MEM_BIST5 = REG_MEM(0x2e), + GLAMO_REG_MEM_MAH1 = REG_MEM(0x30), + GLAMO_REG_MEM_MAH2 = REG_MEM(0x32), + GLAMO_REG_MEM_DRAM1 = REG_MEM(0x34), + GLAMO_REG_MEM_DRAM2 = REG_MEM(0x36), + GLAMO_REG_MEM_CRC = REG_MEM(0x38), +}; + +#define GLAMO_MEM_TYPE_MASK 0x03 + +enum glamo_reg_mem_dram1 { + /* b0 - b10 == refresh period, 1 -> 2048 clocks */ + GLAMO_MEM_DRAM1_EN_GATE_CLK = (1 << 11), + GLAMO_MEM_DRAM1_SELF_REFRESH = (1 << 12), + GLAMO_MEM_DRAM1_EN_GATE_CKE = (1 << 13), + GLAMO_MEM_DRAM1_EN_DRAM_REFRESH = (1 << 14), + GLAMO_MEM_DRAM1_EN_MODEREG_SET = (1 << 15), +}; + +enum glamo_reg_mem_dram2 { + GLAMO_MEM_DRAM2_DEEP_PWRDOWN = (1 << 12), +}; + +enum glamo_irq_index { + GLAMO_IRQIDX_HOSTBUS = 0, + GLAMO_IRQIDX_JPEG = 1, + GLAMO_IRQIDX_MPEG = 2, + GLAMO_IRQIDX_MPROC1 = 3, + GLAMO_IRQIDX_MPROC0 = 4, + GLAMO_IRQIDX_CMDQUEUE = 5, + GLAMO_IRQIDX_2D = 6, + GLAMO_IRQIDX_MMC = 7, + GLAMO_IRQIDX_RISC = 8, +}; + +enum glamo_irq { + GLAMO_IRQ_HOSTBUS = (1 << GLAMO_IRQIDX_HOSTBUS), + GLAMO_IRQ_JPEG = (1 << GLAMO_IRQIDX_JPEG), + GLAMO_IRQ_MPEG = (1 << GLAMO_IRQIDX_MPEG), + GLAMO_IRQ_MPROC1 = (1 << GLAMO_IRQIDX_MPROC1), + GLAMO_IRQ_MPROC0 = (1 << GLAMO_IRQIDX_MPROC0), + GLAMO_IRQ_CMDQUEUE = (1 << GLAMO_IRQIDX_CMDQUEUE), + GLAMO_IRQ_2D = (1 << GLAMO_IRQIDX_2D), + GLAMO_IRQ_MMC = (1 << GLAMO_IRQIDX_MMC), + GLAMO_IRQ_RISC = (1 << GLAMO_IRQIDX_RISC), +}; + +enum glamo_reg_clock_host { + GLAMO_CLOCK_HOST_DG_BCLK = 0x0001, + GLAMO_CLOCK_HOST_DG_M0CLK = 0x0004, + GLAMO_CLOCK_HOST_RESET = 0x1000, +}; + +enum glamo_reg_clock_mem { + GLAMO_CLOCK_MEM_DG_M1CLK = 0x0001, + GLAMO_CLOCK_MEM_EN_M1CLK = 0x0002, + GLAMO_CLOCK_MEM_DG_MOCACLK = 0x0004, + GLAMO_CLOCK_MEM_EN_MOCACLK = 0x0008, + GLAMO_CLOCK_MEM_RESET = 0x1000, + GLAMO_CLOCK_MOCA_RESET = 0x2000, +}; + +enum glamo_reg_clock_lcd { + GLAMO_CLOCK_LCD_DG_DCLK = 0x0001, + GLAMO_CLOCK_LCD_EN_DCLK = 0x0002, + GLAMO_CLOCK_LCD_DG_DMCLK = 0x0004, + GLAMO_CLOCK_LCD_EN_DMCLK = 0x0008, + GLAMO_CLOCK_LCD_EN_DHCLK = 0x0020, + GLAMO_CLOCK_LCD_DG_M5CLK = 0x0040, + GLAMO_CLOCK_LCD_EN_M5CLK = 0x0080, + GLAMO_CLOCK_LCD_RESET = 0x1000, +}; + +enum glamo_reg_clock_mmc { + GLAMO_CLOCK_MMC_DG_TCLK = 0x0001, + GLAMO_CLOCK_MMC_EN_TCLK = 0x0002, + GLAMO_CLOCK_MMC_DG_M9CLK = 0x0004, + GLAMO_CLOCK_MMC_EN_M9CLK = 0x0008, + GLAMO_CLOCK_MMC_RESET = 0x1000, +}; + +enum glamo_reg_basic_mmc { + /* set to disable CRC error rejection */ + GLAMO_BASIC_MMC_DISABLE_CRC = 0x0001, + /* enable completion interrupt */ + GLAMO_BASIC_MMC_EN_COMPL_INT = 0x0002, + /* stop MMC clock while enforced idle waiting for data from card */ + GLAMO_BASIC_MMC_NO_CLK_RD_WAIT = 0x0004, + /* 0 = 1-bit bus to card, 1 = use 4-bit bus (has to be negotiated) */ + GLAMO_BASIC_MMC_EN_4BIT_DATA = 0x0008, + /* enable 75K pullups on D3..D0 */ + GLAMO_BASIC_MMC_EN_DATA_PUPS = 0x0010, + /* enable 75K pullup on CMD */ + GLAMO_BASIC_MMC_EN_CMD_PUP = 0x0020, + /* IO drive strength 00=weak -> 11=strongest */ + GLAMO_BASIC_MMC_EN_DR_STR0 = 0x0040, + GLAMO_BASIC_MMC_EN_DR_STR1 = 0x0080, + /* TCLK delay stage A, 0000 = 500ps --> 1111 = 8ns */ + GLAMO_BASIC_MMC_EN_TCLK_DLYA0 = 0x0100, + GLAMO_BASIC_MMC_EN_TCLK_DLYA1 = 0x0200, + GLAMO_BASIC_MMC_EN_TCLK_DLYA2 = 0x0400, + GLAMO_BASIC_MMC_EN_TCLK_DLYA3 = 0x0800, + /* TCLK delay stage B (cumulative), 0000 = 500ps --> 1111 = 8ns */ + GLAMO_BASIC_MMC_EN_TCLK_DLYB0 = 0x1000, + GLAMO_BASIC_MMC_EN_TCLK_DLYB1 = 0x2000, + GLAMO_BASIC_MMC_EN_TCLK_DLYB2 = 0x4000, + GLAMO_BASIC_MMC_EN_TCLK_DLYB3 = 0x8000, +}; + +enum glamo_reg_stat1_mmc { + /* command "counter" (really: toggle) */ + GLAMO_STAT1_MMC_CMD_CTR = 0x8000, + /* engine is idle */ + GLAMO_STAT1_MMC_IDLE = 0x4000, + /* readback response is ready */ + GLAMO_STAT1_MMC_RB_RRDY = 0x0200, + /* readback data is ready */ + GLAMO_STAT1_MMC_RB_DRDY = 0x0100, + /* no response timeout */ + GLAMO_STAT1_MMC_RTOUT = 0x0020, + /* no data timeout */ + GLAMO_STAT1_MMC_DTOUT = 0x0010, + /* CRC error on block write */ + GLAMO_STAT1_MMC_BWERR = 0x0004, + /* CRC error on block read */ + GLAMO_STAT1_MMC_BRERR = 0x0002 +}; + +enum glamo_reg_fire_mmc { + /* command "counter" (really: toggle) + * the STAT1 register reflects this so you can ensure you don't look + * at status for previous command + */ + GLAMO_FIRE_MMC_CMD_CTR = 0x8000, + /* sets kind of response expected */ + GLAMO_FIRE_MMC_RES_MASK = 0x0700, + /* sets command type */ + GLAMO_FIRE_MMC_TYP_MASK = 0x00C0, + /* sets command class */ + GLAMO_FIRE_MMC_CLS_MASK = 0x000F, +}; + +enum glamo_fire_mmc_response_types { + GLAMO_FIRE_MMC_RSPT_R1 = 0x0000, + GLAMO_FIRE_MMC_RSPT_R1b = 0x0100, + GLAMO_FIRE_MMC_RSPT_R2 = 0x0200, + GLAMO_FIRE_MMC_RSPT_R3 = 0x0300, + GLAMO_FIRE_MMC_RSPT_R4 = 0x0400, + GLAMO_FIRE_MMC_RSPT_R5 = 0x0500, +}; + +enum glamo_fire_mmc_command_types { + /* broadcast, no response */ + GLAMO_FIRE_MMC_CMDT_BNR = 0x0000, + /* broadcast, with response */ + GLAMO_FIRE_MMC_CMDT_BR = 0x0040, + /* addressed, no data */ + GLAMO_FIRE_MMC_CMDT_AND = 0x0080, + /* addressed, with data */ + GLAMO_FIRE_MMC_CMDT_AD = 0x00C0, +}; + +enum glamo_fire_mmc_command_class { + /* "Stream Read" */ + GLAMO_FIRE_MMC_CC_STRR = 0x0000, + /* Single Block Read */ + GLAMO_FIRE_MMC_CC_SBR = 0x0001, + /* Multiple Block Read With Stop */ + GLAMO_FIRE_MMC_CC_MBRS = 0x0002, + /* Multiple Block Read No Stop */ + GLAMO_FIRE_MMC_CC_MBRNS = 0x0003, + /* RESERVED for "Stream Write" */ + GLAMO_FIRE_MMC_CC_STRW = 0x0004, + /* "Stream Write" */ + GLAMO_FIRE_MMC_CC_SBW = 0x0005, + /* RESERVED for Multiple Block Write With Stop */ + GLAMO_FIRE_MMC_CC_MBWS = 0x0006, + /* Multiple Block Write No Stop */ + GLAMO_FIRE_MMC_CC_MBWNS = 0x0007, + /* STOP command */ + GLAMO_FIRE_MMC_CC_STOP = 0x0008, + /* Cancel on Running Command */ + GLAMO_FIRE_MMC_CC_CANCL = 0x0009, + /* "Basic Command" */ + GLAMO_FIRE_MMC_CC_BASIC = 0x000a, +}; + +/* these are offsets from the start of the MMC register region */ +enum glamo_register_mmc { + /* MMC command, b15..8 = cmd arg b7..0; b7..1 = CRC; b0 = end bit */ + GLAMO_REG_MMC_CMD_REG1 = 0x00, + /* MMC command, b15..0 = cmd arg b23 .. 8 */ + GLAMO_REG_MMC_CMD_REG2 = 0x02, + /* MMC command, b15=start, b14=transmission, + * b13..8=cmd idx, b7..0=cmd arg b31..24 + */ + GLAMO_REG_MMC_CMD_REG3 = 0x04, + GLAMO_REG_MMC_CMD_FIRE = 0x06, + GLAMO_REG_MMC_CMD_RSP1 = 0x10, + GLAMO_REG_MMC_CMD_RSP2 = 0x12, + GLAMO_REG_MMC_CMD_RSP3 = 0x14, + GLAMO_REG_MMC_CMD_RSP4 = 0x16, + GLAMO_REG_MMC_CMD_RSP5 = 0x18, + GLAMO_REG_MMC_CMD_RSP6 = 0x1a, + GLAMO_REG_MMC_CMD_RSP7 = 0x1c, + GLAMO_REG_MMC_CMD_RSP8 = 0x1e, + GLAMO_REG_MMC_RB_STAT1 = 0x20, + GLAMO_REG_MMC_RB_BLKCNT = 0x22, + GLAMO_REG_MMC_RB_BLKLEN = 0x24, + GLAMO_REG_MMC_BASIC = 0x30, + GLAMO_REG_MMC_RDATADS1 = 0x34, + GLAMO_REG_MMC_RDATADS2 = 0x36, + GLAMO_REG_MMC_WDATADS1 = 0x38, + GLAMO_REG_MMC_WDATADS2 = 0x3a, + GLAMO_REG_MMC_DATBLKCNT = 0x3c, + GLAMO_REG_MMC_DATBLKLEN = 0x3e, + GLAMO_REG_MMC_TIMEOUT = 0x40, + +}; + +enum glamo_reg_clock_isp { + GLAMO_CLOCK_ISP_DG_I1CLK = 0x0001, + GLAMO_CLOCK_ISP_EN_I1CLK = 0x0002, + GLAMO_CLOCK_ISP_DG_CCLK = 0x0004, + GLAMO_CLOCK_ISP_EN_CCLK = 0x0008, + GLAMO_CLOCK_ISP_EN_SCLK = 0x0020, + GLAMO_CLOCK_ISP_DG_M2CLK = 0x0040, + GLAMO_CLOCK_ISP_EN_M2CLK = 0x0080, + GLAMO_CLOCK_ISP_DG_M15CLK = 0x0100, + GLAMO_CLOCK_ISP_EN_M15CLK = 0x0200, + GLAMO_CLOCK_ISP1_RESET = 0x1000, + GLAMO_CLOCK_ISP2_RESET = 0x2000, +}; + +enum glamo_reg_clock_jpeg { + GLAMO_CLOCK_JPEG_DG_JCLK = 0x0001, + GLAMO_CLOCK_JPEG_EN_JCLK = 0x0002, + GLAMO_CLOCK_JPEG_DG_M3CLK = 0x0004, + GLAMO_CLOCK_JPEG_EN_M3CLK = 0x0008, + GLAMO_CLOCK_JPEG_RESET = 0x1000, +}; + +enum glamo_reg_clock_2d { + GLAMO_CLOCK_2D_DG_GCLK = 0x0001, + GLAMO_CLOCK_2D_EN_GCLK = 0x0002, + GLAMO_CLOCK_2D_DG_M7CLK = 0x0004, + GLAMO_CLOCK_2D_EN_M7CLK = 0x0008, + GLAMO_CLOCK_2D_DG_M6CLK = 0x0010, + GLAMO_CLOCK_2D_EN_M6CLK = 0x0020, + GLAMO_CLOCK_2D_RESET = 0x1000, + GLAMO_CLOCK_2D_CQ_RESET = 0x2000, +}; + +enum glamo_reg_clock_3d { + GLAMO_CLOCK_3D_DG_ECLK = 0x0001, + GLAMO_CLOCK_3D_EN_ECLK = 0x0002, + GLAMO_CLOCK_3D_DG_RCLK = 0x0004, + GLAMO_CLOCK_3D_EN_RCLK = 0x0008, + GLAMO_CLOCK_3D_DG_M8CLK = 0x0010, + GLAMO_CLOCK_3D_EN_M8CLK = 0x0020, + GLAMO_CLOCK_3D_BACK_RESET = 0x1000, + GLAMO_CLOCK_3D_FRONT_RESET = 0x2000, +}; + +enum glamo_reg_clock_mpeg { + GLAMO_CLOCK_MPEG_DG_X0CLK = 0x0001, + GLAMO_CLOCK_MPEG_EN_X0CLK = 0x0002, + GLAMO_CLOCK_MPEG_DG_X1CLK = 0x0004, + GLAMO_CLOCK_MPEG_EN_X1CLK = 0x0008, + GLAMO_CLOCK_MPEG_DG_X2CLK = 0x0010, + GLAMO_CLOCK_MPEG_EN_X2CLK = 0x0020, + GLAMO_CLOCK_MPEG_DG_X3CLK = 0x0040, + GLAMO_CLOCK_MPEG_EN_X3CLK = 0x0080, + GLAMO_CLOCK_MPEG_DG_X4CLK = 0x0100, + GLAMO_CLOCK_MPEG_EN_X4CLK = 0x0200, + GLAMO_CLOCK_MPEG_DG_X6CLK = 0x0400, + GLAMO_CLOCK_MPEG_EN_X6CLK = 0x0800, + GLAMO_CLOCK_MPEG_ENC_RESET = 0x1000, + GLAMO_CLOCK_MPEG_DEC_RESET = 0x2000, +}; + +enum glamo_reg_clock51 { + GLAMO_CLOCK_GEN51_EN_DIV_MCLK = 0x0001, + GLAMO_CLOCK_GEN51_EN_DIV_SCLK = 0x0002, + GLAMO_CLOCK_GEN51_EN_DIV_JCLK = 0x0004, + GLAMO_CLOCK_GEN51_EN_DIV_DCLK = 0x0008, + GLAMO_CLOCK_GEN51_EN_DIV_DMCLK = 0x0010, + GLAMO_CLOCK_GEN51_EN_DIV_DHCLK = 0x0020, + GLAMO_CLOCK_GEN51_EN_DIV_GCLK = 0x0040, + GLAMO_CLOCK_GEN51_EN_DIV_TCLK = 0x0080, + /* FIXME: higher bits */ +}; + +enum glamo_reg_hostbus2 { + GLAMO_HOSTBUS2_MMIO_EN_ISP = 0x0001, + GLAMO_HOSTBUS2_MMIO_EN_JPEG = 0x0002, + GLAMO_HOSTBUS2_MMIO_EN_MPEG = 0x0004, + GLAMO_HOSTBUS2_MMIO_EN_LCD = 0x0008, + GLAMO_HOSTBUS2_MMIO_EN_MMC = 0x0010, + GLAMO_HOSTBUS2_MMIO_EN_MICROP0 = 0x0020, + GLAMO_HOSTBUS2_MMIO_EN_MICROP1 = 0x0040, + GLAMO_HOSTBUS2_MMIO_EN_CQ = 0x0080, + GLAMO_HOSTBUS2_MMIO_EN_RISC = 0x0100, + GLAMO_HOSTBUS2_MMIO_EN_2D = 0x0200, + GLAMO_HOSTBUS2_MMIO_EN_3D = 0x0400, +}; + +/* LCD Controller */ + +#define REG_LCD(x) (x) +enum glamo_reg_lcd { + GLAMO_REG_LCD_MODE1 = REG_LCD(0x00), + GLAMO_REG_LCD_MODE2 = REG_LCD(0x02), + GLAMO_REG_LCD_MODE3 = REG_LCD(0x04), + GLAMO_REG_LCD_WIDTH = REG_LCD(0x06), + GLAMO_REG_LCD_HEIGHT = REG_LCD(0x08), + GLAMO_REG_LCD_POLARITY = REG_LCD(0x0a), + GLAMO_REG_LCD_A_BASE1 = REG_LCD(0x0c), + GLAMO_REG_LCD_A_BASE2 = REG_LCD(0x0e), + GLAMO_REG_LCD_B_BASE1 = REG_LCD(0x10), + GLAMO_REG_LCD_B_BASE2 = REG_LCD(0x12), + GLAMO_REG_LCD_C_BASE1 = REG_LCD(0x14), + GLAMO_REG_LCD_C_BASE2 = REG_LCD(0x16), + GLAMO_REG_LCD_PITCH = REG_LCD(0x18), + /* RES */ + GLAMO_REG_LCD_HORIZ_TOTAL = REG_LCD(0x1c), + /* RES */ + GLAMO_REG_LCD_HORIZ_RETR_START = REG_LCD(0x20), + /* RES */ + GLAMO_REG_LCD_HORIZ_RETR_END = REG_LCD(0x24), + /* RES */ + GLAMO_REG_LCD_HORIZ_DISP_START = REG_LCD(0x28), + /* RES */ + GLAMO_REG_LCD_HORIZ_DISP_END = REG_LCD(0x2c), + /* RES */ + GLAMO_REG_LCD_VERT_TOTAL = REG_LCD(0x30), + /* RES */ + GLAMO_REG_LCD_VERT_RETR_START = REG_LCD(0x34), + /* RES */ + GLAMO_REG_LCD_VERT_RETR_END = REG_LCD(0x38), + /* RES */ + GLAMO_REG_LCD_VERT_DISP_START = REG_LCD(0x3c), + /* RES */ + GLAMO_REG_LCD_VERT_DISP_END = REG_LCD(0x40), + /* RES */ + GLAMO_REG_LCD_POL = REG_LCD(0x44), + GLAMO_REG_LCD_DATA_START = REG_LCD(0x46), + GLAMO_REG_LCD_FRATE_CONTRO = REG_LCD(0x48), + GLAMO_REG_LCD_DATA_CMD_HDR = REG_LCD(0x4a), + GLAMO_REG_LCD_SP_START = REG_LCD(0x4c), + GLAMO_REG_LCD_SP_END = REG_LCD(0x4e), + GLAMO_REG_LCD_CURSOR_BASE1 = REG_LCD(0x50), + GLAMO_REG_LCD_CURSOR_BASE2 = REG_LCD(0x52), + GLAMO_REG_LCD_CURSOR_PITCH = REG_LCD(0x54), + GLAMO_REG_LCD_CURSOR_X_SIZE = REG_LCD(0x56), + GLAMO_REG_LCD_CURSOR_Y_SIZE = REG_LCD(0x58), + GLAMO_REG_LCD_CURSOR_X_POS = REG_LCD(0x5a), + GLAMO_REG_LCD_CURSOR_Y_POS = REG_LCD(0x5c), + GLAMO_REG_LCD_CURSOR_PRESET = REG_LCD(0x5e), + GLAMO_REG_LCD_CURSOR_FG_COLOR = REG_LCD(0x60), + /* RES */ + GLAMO_REG_LCD_CURSOR_BG_COLOR = REG_LCD(0x64), + /* RES */ + GLAMO_REG_LCD_CURSOR_DST_COLOR = REG_LCD(0x68), + /* RES */ + GLAMO_REG_LCD_STATUS1 = REG_LCD(0x80), + GLAMO_REG_LCD_STATUS2 = REG_LCD(0x82), + GLAMO_REG_LCD_STATUS3 = REG_LCD(0x84), + GLAMO_REG_LCD_STATUS4 = REG_LCD(0x86), + /* RES */ + GLAMO_REG_LCD_COMMAND1 = REG_LCD(0xa0), + GLAMO_REG_LCD_COMMAND2 = REG_LCD(0xa2), + /* RES */ + GLAMO_REG_LCD_WFORM_DELAY1 = REG_LCD(0xb0), + GLAMO_REG_LCD_WFORM_DELAY2 = REG_LCD(0xb2), + /* RES */ + GLAMO_REG_LCD_GAMMA_CORR = REG_LCD(0x100), + /* RES */ + GLAMO_REG_LCD_GAMMA_R_ENTRY01 = REG_LCD(0x110), + GLAMO_REG_LCD_GAMMA_R_ENTRY23 = REG_LCD(0x112), + GLAMO_REG_LCD_GAMMA_R_ENTRY45 = REG_LCD(0x114), + GLAMO_REG_LCD_GAMMA_R_ENTRY67 = REG_LCD(0x116), + GLAMO_REG_LCD_GAMMA_R_ENTRY8 = REG_LCD(0x118), + /* RES */ + GLAMO_REG_LCD_GAMMA_G_ENTRY01 = REG_LCD(0x130), + GLAMO_REG_LCD_GAMMA_G_ENTRY23 = REG_LCD(0x132), + GLAMO_REG_LCD_GAMMA_G_ENTRY45 = REG_LCD(0x134), + GLAMO_REG_LCD_GAMMA_G_ENTRY67 = REG_LCD(0x136), + GLAMO_REG_LCD_GAMMA_G_ENTRY8 = REG_LCD(0x138), + /* RES */ + GLAMO_REG_LCD_GAMMA_B_ENTRY01 = REG_LCD(0x150), + GLAMO_REG_LCD_GAMMA_B_ENTRY23 = REG_LCD(0x152), + GLAMO_REG_LCD_GAMMA_B_ENTRY45 = REG_LCD(0x154), + GLAMO_REG_LCD_GAMMA_B_ENTRY67 = REG_LCD(0x156), + GLAMO_REG_LCD_GAMMA_B_ENTRY8 = REG_LCD(0x158), + /* RES */ + GLAMO_REG_LCD_SRAM_DRIVING1 = REG_LCD(0x160), + GLAMO_REG_LCD_SRAM_DRIVING2 = REG_LCD(0x162), + GLAMO_REG_LCD_SRAM_DRIVING3 = REG_LCD(0x164), +}; + +enum glamo_reg_lcd_mode1 { + GLAMO_LCD_MODE1_PWRSAVE = 0x0001, + GLAMO_LCD_MODE1_PARTIAL_PRT = 0x0002, + GLAMO_LCD_MODE1_HWFLIP = 0x0004, + GLAMO_LCD_MODE1_LCD2 = 0x0008, + /* RES */ + GLAMO_LCD_MODE1_PARTIAL_MODE = 0x0020, + GLAMO_LCD_MODE1_CURSOR_DSTCOLOR = 0x0040, + GLAMO_LCD_MODE1_PARTIAL_ENABLE = 0x0080, + GLAMO_LCD_MODE1_TVCLK_IN_ENABLE = 0x0100, + GLAMO_LCD_MODE1_HSYNC_HIGH_ACT = 0x0200, + GLAMO_LCD_MODE1_VSYNC_HIGH_ACT = 0x0400, + GLAMO_LCD_MODE1_HSYNC_FLIP = 0x0800, + GLAMO_LCD_MODE1_GAMMA_COR_EN = 0x1000, + GLAMO_LCD_MODE1_DITHER_EN = 0x2000, + GLAMO_LCD_MODE1_CURSOR_EN = 0x4000, + GLAMO_LCD_MODE1_ROTATE_EN = 0x8000, +}; + +enum glamo_reg_lcd_mode2 { + GLAMO_LCD_MODE2_CRC_CHECK_EN = 0x0001, + GLAMO_LCD_MODE2_DCMD_PER_LINE = 0x0002, + GLAMO_LCD_MODE2_NOUSE_BDEF = 0x0004, + GLAMO_LCD_MODE2_OUT_POS_MODE = 0x0008, + GLAMO_LCD_MODE2_FRATE_CTRL_EN = 0x0010, + GLAMO_LCD_MODE2_SINGLE_BUFFER = 0x0020, + GLAMO_LCD_MODE2_SER_LSB_TO_MSB = 0x0040, + /* FIXME */ +}; + +enum glamo_reg_lcd_mode3 { + /* LCD color source data format */ + GLAMO_LCD_SRC_RGB565 = 0x0000, + GLAMO_LCD_SRC_ARGB1555 = 0x4000, + GLAMO_LCD_SRC_ARGB4444 = 0x8000, + /* interface type */ + GLAMO_LCD_MODE3_LCD = 0x1000, + GLAMO_LCD_MODE3_RGB = 0x0800, + GLAMO_LCD_MODE3_CPU = 0x0000, + /* mode */ + GLAMO_LCD_MODE3_RGB332 = 0x0000, + GLAMO_LCD_MODE3_RGB444 = 0x0100, + GLAMO_LCD_MODE3_RGB565 = 0x0200, + GLAMO_LCD_MODE3_RGB666 = 0x0300, + /* depth */ + GLAMO_LCD_MODE3_6BITS = 0x0000, + GLAMO_LCD_MODE3_8BITS = 0x0010, + GLAMO_LCD_MODE3_9BITS = 0x0020, + GLAMO_LCD_MODE3_16BITS = 0x0030, + GLAMO_LCD_MODE3_18BITS = 0x0040, +}; + +enum glamo_lcd_rot_mode { + GLAMO_LCD_ROT_MODE_0 = 0x0000, + GLAMO_LCD_ROT_MODE_180 = 0x2000, + GLAMO_LCD_ROT_MODE_MIRROR = 0x4000, + GLAMO_LCD_ROT_MODE_FLIP = 0x6000, + GLAMO_LCD_ROT_MODE_90 = 0x8000, + GLAMO_LCD_ROT_MODE_270 = 0xa000, +}; +#define GLAMO_LCD_ROT_MODE_MASK 0xe000 + +enum glamo_lcd_cmd_type { + GLAMO_LCD_CMD_TYPE_DISP = 0x0000, + GLAMO_LCD_CMD_TYPE_PARALLEL = 0x4000, + GLAMO_LCD_CMD_TYPE_SERIAL = 0x8000, + GLAMO_LCD_CMD_TYPE_SERIAL_DIRECT = 0xc000, +}; +#define GLAMO_LCD_CMD_TYPE_MASK 0xc000 + +enum glamo_lcd_cmds { + GLAMO_LCD_CMD_DATA_DISP_FIRE = 0x00, + GLAMO_LCD_CMD_DATA_DISP_SYNC = 0x01, /* RGB only */ + /* switch to command mode, no display */ + GLAMO_LCD_CMD_DATA_FIRE_NO_DISP = 0x02, + /* display until VSYNC, switch to command */ + GLAMO_LCD_CMD_DATA_FIRE_VSYNC = 0x11, + /* display until HSYNC, switch to command */ + GLAMO_LCD_CMD_DATA_FIRE_HSYNC = 0x12, + /* display until VSYNC, 1 black frame, VSYNC, switch to command */ + GLAMO_LCD_CMD_DATA_FIRE_VSYNC_B = 0x13, + /* don't care about display and switch to command */ + GLAMO_LCD_CMD_DATA_FIRE_FREE = 0x14, /* RGB only */ + /* don't care about display, keep data display but disable data, + * and switch to command */ + GLAMO_LCD_CMD_DATA_FIRE_FREE_D = 0x15, /* RGB only */ +}; + +enum glamo_core_revisions { + GLAMO_CORE_REV_A0 = 0x0000, + GLAMO_CORE_REV_A1 = 0x0001, + GLAMO_CORE_REV_A2 = 0x0002, + GLAMO_CORE_REV_A3 = 0x0003, +}; + +#endif /* _GLAMO_REGS_H */ diff --git a/include/linux/fb.h b/include/linux/fb.h index c10163b4c40..71f714a1da5 100644 --- a/include/linux/fb.h +++ b/include/linux/fb.h @@ -134,6 +134,7 @@ struct dentry; #define FB_ACCEL_NEOMAGIC_NM2360 97 /* NeoMagic NM2360 */ #define FB_ACCEL_NEOMAGIC_NM2380 98 /* NeoMagic NM2380 */ #define FB_ACCEL_PXA3XX 99 /* PXA3xx */ +#define FB_ACCEL_GLAMO 100 /* Smedia Glamo 3362 */ #define FB_ACCEL_SAVAGE4 0x80 /* S3 Savage4 */ #define FB_ACCEL_SAVAGE3D 0x81 /* S3 Savage3D */ diff --git a/include/linux/glamofb.h b/include/linux/glamofb.h new file mode 100644 index 00000000000..5f9fab5a487 --- /dev/null +++ b/include/linux/glamofb.h @@ -0,0 +1,35 @@ +#ifndef _LINUX_GLAMOFB_H +#define _LINUX_GLAMOFB_H + +#include + +#ifdef __KERNEL__ + +struct glamo_core; +struct glamofb_handle; + +struct glamo_fb_platform_data { + int width, height; + + int num_modes; + struct fb_videomode *modes; + + struct glamo_core *core; +}; + +int glamofb_cmd_mode(struct glamofb_handle *gfb, int on); +int glamofb_cmd_write(struct glamofb_handle *gfb, u_int16_t val); + +#ifdef CONFIG_MFD_GLAMO +void glamo_lcm_reset(struct platform_device *pdev, int level); +#else +#define glamo_lcm_reset(...) do {} while (0) +#endif + +#endif + +#define GLAMOFB_ENGINE_ENABLE _IOW('F', 0x1, __u32) +#define GLAMOFB_ENGINE_DISABLE _IOW('F', 0x2, __u32) +#define GLAMOFB_ENGINE_RESET _IOW('F', 0x3, __u32) + +#endif diff --git a/include/linux/mfd/glamo.h b/include/linux/mfd/glamo.h new file mode 100644 index 00000000000..529d4f07f03 --- /dev/null +++ b/include/linux/mfd/glamo.h @@ -0,0 +1,52 @@ +#ifndef __GLAMO_MFD_H +#define __GLAMO_MFD_H + +struct glamo_core; +struct glamo_spigpio_platform_data; +struct glamo_fb_platform_data; + +struct glamo_mmc_platform_data { + int (*glamo_mmc_use_slow)(void); + + struct glamo_core *core; +}; + +struct glamo_gpio_platform_data { + int base; + void (*registered)(struct device *dev); +}; + +struct glamo_platform_data { + struct glamo_fb_platform_data *fb_data; + struct glamo_mmc_platform_data *mmc_data; + struct glamo_gpio_platform_data *gpio_data; + + unsigned int osci_clock_rate; + + void (*glamo_external_reset)(int); +}; + +enum glamo_engine { + GLAMO_ENGINE_CAPTURE = 0, + GLAMO_ENGINE_ISP = 1, + GLAMO_ENGINE_JPEG = 2, + GLAMO_ENGINE_MPEG_ENC = 3, + GLAMO_ENGINE_MPEG_DEC = 4, + GLAMO_ENGINE_LCD = 5, + GLAMO_ENGINE_CMDQ = 6, + GLAMO_ENGINE_2D = 7, + GLAMO_ENGINE_3D = 8, + GLAMO_ENGINE_MMC = 9, + GLAMO_ENGINE_MICROP0 = 10, + GLAMO_ENGINE_RISC = 11, + GLAMO_ENGINE_MICROP1_MPEG_ENC = 12, + GLAMO_ENGINE_MICROP1_MPEG_DEC = 13, +#if 0 + GLAMO_ENGINE_H264_DEC = 14, + GLAMO_ENGINE_RISC1 = 15, + GLAMO_ENGINE_SPI = 16, +#endif + __NUM_GLAMO_ENGINES +}; + +#endif -- cgit v1.2.3