diff options
Diffstat (limited to 'arch')
25 files changed, 1659 insertions, 78 deletions
diff --git a/arch/arm/boot/compressed/head.S b/arch/arm/boot/compressed/head.S index fa6fbf45cf3..4f0708b83ed 100644 --- a/arch/arm/boot/compressed/head.S +++ b/arch/arm/boot/compressed/head.S @@ -58,7 +58,7 @@ add \rb, \rb, #0x00010000 @ Ser1 #endif .endm -#elif defined(CONFIG_ARCH_S3C2410) +#elif defined(CONFIG_ARCH_S3C2410) && CONFIG_S3C_LOWLEVEL_UART_PORT >= 0 .macro loadsp, rb mov \rb, #0x50000000 add \rb, \rb, #0x4000 * CONFIG_S3C_LOWLEVEL_UART_PORT diff --git a/arch/arm/include/asm/fiq.h b/arch/arm/include/asm/fiq.h index 2242ce22ec6..7ade2b8445d 100644 --- a/arch/arm/include/asm/fiq.h +++ b/arch/arm/include/asm/fiq.h @@ -29,8 +29,9 @@ struct fiq_handler { extern int claim_fiq(struct fiq_handler *f); extern void release_fiq(struct fiq_handler *f); extern void set_fiq_handler(void *start, unsigned int length); -extern void set_fiq_regs(struct pt_regs *regs); -extern void get_fiq_regs(struct pt_regs *regs); +extern void set_fiq_c_handler(void (*handler)(void)); +extern void __attribute__((naked)) set_fiq_regs(struct pt_regs *regs); +extern void __attribute__((naked)) get_fiq_regs(struct pt_regs *regs); extern void enable_fiq(int fiq); extern void disable_fiq(int fiq); diff --git a/arch/arm/kernel/fiq.c b/arch/arm/kernel/fiq.c index 6ff7919613d..c07691ef4c0 100644 --- a/arch/arm/kernel/fiq.c +++ b/arch/arm/kernel/fiq.c @@ -8,6 +8,8 @@ * * FIQ support re-written by Russell King to be more generic * + * FIQ handler in C supoprt written by Andy Green <andy@openmoko.com> + * * We now properly support a method by which the FIQ handlers can * be stacked onto the vector. We still do not support sharing * the FIQ vector itself. @@ -124,6 +126,83 @@ void __naked get_fiq_regs(struct pt_regs *regs) : "r" (®s->ARM_r8), "I" (PSR_I_BIT | PSR_F_BIT | FIQ_MODE)); } +/* -------- FIQ handler in C --------- + * + * Major Caveats for using this + * --------------------------- + * * + * * 1) it CANNOT touch any vmalloc()'d memory, only memory + * that was kmalloc()'d. Static allocations in the monolithic kernel + * are kmalloc()'d so they are okay. You can touch memory-mapped IO, but + * the pointer for it has to have been stored in kmalloc'd memory. The + * reason for this is simple: every now and then Linux turns off interrupts + * and reorders the paging tables. If a FIQ happens during this time, the + * virtual memory space can be partly or entirely disordered or missing. + * + * 2) Because vmalloc() is used when a module is inserted, THIS FIQ + * ISR HAS TO BE IN THE MONOLITHIC KERNEL, not a module. But the way + * it is set up, you can all to enable and disable it from your module + * and intercommunicate with it through struct fiq_ipc + * fiq_ipc which you can define in + * asm/archfiq_ipc_type.h. The reason is the same as above, a + * FIQ could happen while even the ISR is not present in virtual memory + * space due to pagetables being changed at the time. + * + * 3) You can't call any Linux API code except simple macros + * - understand that FIQ can come in at any time, no matter what + * state of undress the kernel may privately be in, thinking it + * locked the door by turning off interrupts... FIQ is an + * unstoppable monster force (which is its value) + * - they are not vmalloc()'d memory safe + * - they might do crazy stuff like sleep: FIQ pisses fire and + * is not interested in 'sleep' that the weak seem to need + * - calling APIs from FIQ can re-enter un-renterable things + * - summary: you cannot interoperate with linux APIs directly in the FIQ ISR + * + * If you follow these rules, it is fantastic, an extremely powerful, solid, + * genuine hard realtime feature. + */ + +static void (*current_fiq_c_isr)(void); +#define FIQ_C_ISR_STACK_SIZE 256 + +static void __attribute__((naked)) __jump_to_isr(void) +{ + asm __volatile__ ("mov pc, r8"); +} + + +static void __attribute__((naked)) __actual_isr(void) +{ + asm __volatile__ ( + "stmdb sp!, {r0-r12, lr};" + "mov fp, sp;" + ); + + current_fiq_c_isr(); + + asm __volatile__ ( + "ldmia sp!, {r0-r12, lr};" + "subs pc, lr, #4;" + ); +} + +void set_fiq_c_handler(void (*isr)(void)) +{ + struct pt_regs regs; + + memset(®s, 0, sizeof(regs)); + regs.ARM_r8 = (unsigned long) __actual_isr; + regs.ARM_sp = 0xffff001c + FIQ_C_ISR_STACK_SIZE; + + set_fiq_handler(__jump_to_isr, 4); + + current_fiq_c_isr = isr; + + set_fiq_regs(®s); +} +/* -------- FIQ handler in C ---------*/ + int claim_fiq(struct fiq_handler *f) { int ret = 0; diff --git a/arch/arm/mach-s3c2410/include/mach/gpio.h b/arch/arm/mach-s3c2410/include/mach/gpio.h index 15f0b3e7ce6..0b53cad80fe 100644 --- a/arch/arm/mach-s3c2410/include/mach/gpio.h +++ b/arch/arm/mach-s3c2410/include/mach/gpio.h @@ -20,10 +20,11 @@ * devices that need GPIO. */ -#define ARCH_NR_GPIOS (256 + CONFIG_S3C24XX_GPIO_EXTRA) +#define ARCH_NR_GPIOS (512 + CONFIG_S3C24XX_GPIO_EXTRA) #include <asm-generic/gpio.h> #include <mach/gpio-nrs.h> #include <mach/gpio-fns.h> +#include <mach/regs-gpioj.h> -#define S3C_GPIO_END (S3C2410_GPIO_BANKH + 32) +#define S3C_GPIO_END (S3C2440_GPIO_BANKJ + 32) diff --git a/arch/arm/mach-s3c2410/include/mach/irqs.h b/arch/arm/mach-s3c2410/include/mach/irqs.h index 6c12c6312ad..6986d02fd4d 100644 --- a/arch/arm/mach-s3c2410/include/mach/irqs.h +++ b/arch/arm/mach-s3c2410/include/mach/irqs.h @@ -153,9 +153,9 @@ #define IRQ_S3C2443_AC97 S3C2410_IRQSUB(28) #ifdef CONFIG_CPU_S3C2443 -#define NR_IRQS (IRQ_S3C2443_AC97+1) +#define S3C2410_NR_INTERNAL_IRQS (IRQ_S3C2443_AC97+1) #else -#define NR_IRQS (IRQ_S3C2440_AC97+1) +#define S3C2410_NR_INTERNAL_IRQS (IRQ_S3C2440_AC97+1) #endif /* compatibility define. */ @@ -173,4 +173,22 @@ /* Our FIQs are routable from IRQ_EINT0 to IRQ_ADCPARENT */ #define FIQ_START IRQ_EINT0 +/* Board specific IRQs + * If your board needs a extra set of IRQ numbers add it to the list here. + * Make sure that the numbers are kept in descending, so if multiple boards are + * selected the maximum will be used and there are enough IRQ numbers available + * for each board. + */ + +#if defined(CONFIG_MACH_NEO1973_GTA02) +#define S3C2410_NR_BOARD_IRQS 9 +#else +#define S3C2410_NR_BOARD_IRQS 0 +#endif + +#define S3C2410_BOARD_IRQ_START S3C2410_NR_INTERNAL_IRQS +#define S3C2410_BOARD_IRQ_END (S3C2410_BOARD_IRQ_START + S3C2410_NR_BOARD_IRQS) + +#define NR_IRQS S3C2410_BOARD_IRQ_END + #endif /* __ASM_ARCH_IRQ_H */ diff --git a/arch/arm/mach-s3c2410/include/mach/ts.h b/arch/arm/mach-s3c2410/include/mach/ts.h new file mode 100644 index 00000000000..ac0c727d155 --- /dev/null +++ b/arch/arm/mach-s3c2410/include/mach/ts.h @@ -0,0 +1,35 @@ +/* arch/arm/mach-s3c2410/include/mach/ts.h + * + * Copyright (c) 2005 Arnaud Patard <arnaud.patard@rtp-net.org> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * + * Changelog: + * 24-Mar-2005 RTP Created file + * 03-Aug-2005 RTP Renamed to ts.h + */ + +#ifndef __ASM_ARM_TS_H +#define __ASM_ARM_TS_H + +#include <linux/input/touchscreen/ts_filter.h> + +struct s3c2410_ts_mach_info { + /* Touchscreen delay. */ + int delay; + /* Prescaler value. */ + int presc; + /* + * Null-terminated array of pointers to filter APIs and configurations + * we want to use. In the same order they will be applied. + */ + const struct ts_filter_chain_configuration *filter_config; +}; + +void set_s3c2410ts_info(const struct s3c2410_ts_mach_info *hard_s3c2410ts_info); + +#endif /* __ASM_ARM_TS_H */ diff --git a/arch/arm/mach-s3c2442/Kconfig b/arch/arm/mach-s3c2442/Kconfig index 8e37b9f42ab..bdb37dd567d 100644 --- a/arch/arm/mach-s3c2442/Kconfig +++ b/arch/arm/mach-s3c2442/Kconfig @@ -34,6 +34,7 @@ config MACH_NEO1973_GTA02 select MACH_NEO1973 select S3C2410_PWM select S3C_DEV_USB_HOST + select S3C_DEV_NAND select FIQ select S3C_PWM select S3C24XX_GPIO_EXTRA64 diff --git a/arch/arm/mach-s3c2442/Makefile b/arch/arm/mach-s3c2442/Makefile index 87338d9e139..cf39481cb5c 100644 --- a/arch/arm/mach-s3c2442/Makefile +++ b/arch/arm/mach-s3c2442/Makefile @@ -13,6 +13,10 @@ obj-$(CONFIG_CPU_S3C2442) += s3c2442.o obj-$(CONFIG_CPU_S3C2442) += clock.o obj-$(CONFIG_MACH_NEO1973_GTA02) += mach-gta02.o \ + gta02-pm-bt.o \ + gta02-pm-gps.o \ + gta02-pm-gsm.o \ + gta02-pm-wlan.o \ gta02-fiq.o # Machine support diff --git a/arch/arm/mach-s3c2442/gta02-pm-bt.c b/arch/arm/mach-s3c2442/gta02-pm-bt.c new file mode 100644 index 00000000000..76671ce96d0 --- /dev/null +++ b/arch/arm/mach-s3c2442/gta02-pm-bt.c @@ -0,0 +1,257 @@ +/* + * Bluetooth PM code for the Openmoko Freerunner GSM Phone + * + * (C) 2007 by Openmoko Inc. + * Author: Harald Welte <laforge@openmoko.org> + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/rfkill.h> +#include <linux/err.h> +#include <mach/gpio-fns.h> + +#include <mach/hardware.h> +#include <asm/mach-types.h> + +#include <mach/gta02.h> +#include <linux/mfd/pcf50633/gpio.h> + +#include <linux/regulator/consumer.h> + +#define DRVMSG "Openmoko Freerunner Bluetooth Power Management" + +struct gta02_pm_bt_data { + struct regulator *regulator; + struct rfkill *rfkill; + int pre_resume_state; +}; + +static ssize_t bt_read(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int ret = 0; + if (!strcmp(attr->attr.name, "power_on")) { + if (s3c2410_gpio_getpin(GTA02_GPIO_BT_EN)) + ret = 1; + } else if (!strcmp(attr->attr.name, "reset")) { + if (s3c2410_gpio_getpin(GTA02_GPIO_BT_EN) == 0) + ret = 1; + } + + if (!ret) { + return strlcpy(buf, "0\n", 3); + } else { + return strlcpy(buf, "1\n", 3); + } +} + +static void __gta02_pm_bt_toggle_radio(struct device *dev, unsigned int on) +{ + struct gta02_pm_bt_data *bt_data = dev_get_drvdata(dev); + + dev_info(dev, "__gta02_pm_bt_toggle_radio %d\n", on); + + bt_data = dev_get_drvdata(dev); + + s3c2410_gpio_setpin(GTA02_GPIO_BT_EN, !on); + + if (on) { + if (!regulator_is_enabled(bt_data->regulator)) + regulator_enable(bt_data->regulator); + } else { + if (regulator_is_enabled(bt_data->regulator)) + regulator_disable(bt_data->regulator); + } + + s3c2410_gpio_setpin(GTA02_GPIO_BT_EN, on); +} + + +static int bt_rfkill_set_block(void *data, bool blocked) +{ + struct device *dev = data; + + __gta02_pm_bt_toggle_radio(dev, !blocked); + + return 0; +} + +static const struct rfkill_ops gta02_bt_rfkill_ops = { + .set_block = bt_rfkill_set_block, +}; + + +static ssize_t bt_write(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long on = simple_strtoul(buf, NULL, 10); + struct gta02_pm_bt_data *bt_data = dev_get_drvdata(dev); + + if (!strcmp(attr->attr.name, "power_on")) { + rfkill_set_sw_state(bt_data->rfkill, on ? 1 : 0); + + __gta02_pm_bt_toggle_radio(dev, on); + } else if (!strcmp(attr->attr.name, "reset")) { + /* reset is low-active, so we need to invert */ + s3c2410_gpio_setpin(GTA02_GPIO_BT_EN, on ? 0 : 1); + } + + return count; +} + +static DEVICE_ATTR(power_on, 0644, bt_read, bt_write); +static DEVICE_ATTR(reset, 0644, bt_read, bt_write); + +#ifdef CONFIG_PM +static int gta02_bt_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct gta02_pm_bt_data *bt_data = dev_get_drvdata(&pdev->dev); + + dev_dbg(&pdev->dev, DRVMSG ": suspending\n"); + + bt_data->pre_resume_state = s3c2410_gpio_getpin(GTA02_GPIO_BT_EN); + __gta02_pm_bt_toggle_radio(&pdev->dev, 0); + + return 0; +} + +static int gta02_bt_resume(struct platform_device *pdev) +{ + struct gta02_pm_bt_data *bt_data = dev_get_drvdata(&pdev->dev); + dev_dbg(&pdev->dev, DRVMSG ": resuming\n"); + + __gta02_pm_bt_toggle_radio(&pdev->dev, bt_data->pre_resume_state); + return 0; +} +#else +#define gta02_bt_suspend NULL +#define gta02_bt_resume NULL +#endif + +static struct attribute *gta02_bt_sysfs_entries[] = { + &dev_attr_power_on.attr, + &dev_attr_reset.attr, + NULL +}; + +static struct attribute_group gta02_bt_attr_group = { + .name = NULL, + .attrs = gta02_bt_sysfs_entries, +}; + +static int __init gta02_bt_probe(struct platform_device *pdev) +{ + struct rfkill *rfkill; + struct regulator *regulator; + struct gta02_pm_bt_data *bt_data; + int ret; + + dev_info(&pdev->dev, DRVMSG ": starting\n"); + + bt_data = kzalloc(sizeof(*bt_data), GFP_KERNEL); + dev_set_drvdata(&pdev->dev, bt_data); + + regulator = regulator_get(&pdev->dev, "BT_3V2"); + if (IS_ERR(regulator)) + return -ENODEV; + + bt_data->regulator = regulator; + + /* this tests the true physical state of the regulator... */ + if (regulator_is_enabled(regulator)) { + /* + * but these only operate on the logical state of the + * regulator... so we need to logicaly "adopt" it on + * to turn it off + */ + regulator_enable(regulator); + regulator_disable(regulator); + } + + /* we pull reset to low to make sure that the chip doesn't + * drain power through the reset line */ + s3c2410_gpio_setpin(GTA02_GPIO_BT_EN, 0); + + rfkill = rfkill_alloc(pdev->name, &pdev->dev, RFKILL_TYPE_BLUETOOTH, + >a02_bt_rfkill_ops, &pdev->dev); + + if (!rfkill) { + dev_err(&pdev->dev, "Failed to allocate rfkill\n"); + return -ENOMEM; + } + + rfkill_init_sw_state(rfkill, 0); + + ret = rfkill_register(rfkill); + if (ret) { + rfkill_destroy(rfkill); + dev_err(&pdev->dev, "Failed to register rfkill\n"); + return ret; + } + + bt_data->rfkill = rfkill; + + return sysfs_create_group(&pdev->dev.kobj, >a02_bt_attr_group); +} + +static int gta02_bt_remove(struct platform_device *pdev) +{ + struct gta02_pm_bt_data *bt_data = dev_get_drvdata(&pdev->dev); + struct regulator *regulator; + + sysfs_remove_group(&pdev->dev.kobj, >a02_bt_attr_group); + + if (bt_data->rfkill) { + rfkill_destroy(bt_data->rfkill); + } + + if (!bt_data || !bt_data->regulator) + return 0; + + regulator = bt_data->regulator; + + /* Make sure regulator is disabled before calling regulator_put */ + if (regulator_is_enabled(regulator)) + regulator_disable(regulator); + + regulator_put(regulator); + + kfree(bt_data); + + return 0; +} + +static struct platform_driver gta02_bt_driver = { + .probe = gta02_bt_probe, + .remove = gta02_bt_remove, + .suspend = gta02_bt_suspend, + .resume = gta02_bt_resume, + .driver = { + .name = "gta02-pm-bt", + }, +}; + +static int __devinit gta02_bt_init(void) +{ + return platform_driver_register(>a02_bt_driver); +} +module_init(gta02_bt_init); + +static void gta02_bt_exit(void) +{ + platform_driver_unregister(>a02_bt_driver); +} +module_exit(gta02_bt_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Harald Welte <laforge@openmoko.org>"); +MODULE_DESCRIPTION(DRVMSG); diff --git a/arch/arm/mach-s3c2442/gta02-pm-gps.c b/arch/arm/mach-s3c2442/gta02-pm-gps.c new file mode 100644 index 00000000000..2b71b7e4811 --- /dev/null +++ b/arch/arm/mach-s3c2442/gta02-pm-gps.c @@ -0,0 +1,246 @@ +/* + * GPS Power Management code for the Openmoko Freerunner GSM Phone + * + * (C) 2007-2009 by Openmoko Inc. + * Author: Harald Welte <laforge@openmoko.org> + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/platform_device.h> + +#include <mach/hardware.h> +#include <mach/gpio-fns.h> + +#include <asm/mach-types.h> + +#include <mach/gta02.h> +#include <linux/mfd/pcf50633/core.h> +#include <linux/mfd/pcf50633/pmic.h> + +#include <linux/regulator/consumer.h> +#include <linux/err.h> + +struct gta02_pm_gps_data { +#ifdef CONFIG_PM + int keep_on_in_suspend; +#endif + int power_was_on; + struct regulator *regulator; +}; + +static struct gta02_pm_gps_data gta02_gps; + +int gta02_pm_gps_is_on(void) +{ + return gta02_gps.power_was_on; +} +EXPORT_SYMBOL_GPL(gta02_pm_gps_is_on); + +/* This is the POWERON pin */ +static void gps_pwron_set(int on) +{ + if (on) { + /* return UART pins to being UART pins */ + s3c2410_gpio_cfgpin(S3C2410_GPH(4), S3C2410_GPH4_TXD1); + /* remove pulldown now it won't be floating any more */ + s3c2410_gpio_pullup(S3C2410_GPH(5), 0); + + if (!gta02_gps.power_was_on) + regulator_enable(gta02_gps.regulator); + } else { + /* + * take care not to power unpowered GPS from UART TX + * return them to GPIO and force low + */ + s3c2410_gpio_cfgpin(S3C2410_GPH(4), S3C2410_GPIO_OUTPUT); + s3c2410_gpio_setpin(S3C2410_GPH(4), 0); + /* don't let RX from unpowered GPS float */ + s3c2410_gpio_pullup(S3C2410_GPH(5), 1); + if (gta02_gps.power_was_on) + regulator_disable(gta02_gps.regulator); + } +} + +static int gps_pwron_get(void) +{ + return regulator_is_enabled(gta02_gps.regulator); +} + +#ifdef CONFIG_PM +/* This is the flag for keeping gps ON during suspend */ +static void gps_keep_on_in_suspend_set(int on) +{ + gta02_gps.keep_on_in_suspend = on; +} + +static int gps_keep_on_in_suspend_get(void) +{ + return gta02_gps.keep_on_in_suspend; +} +#endif + +static ssize_t power_gps_read(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret = 0; + + if (!strcmp(attr->attr.name, "power_on") || + !strcmp(attr->attr.name, "pwron")) { + ret = gps_pwron_get(); +#ifdef CONFIG_PM + } else if (!strcmp(attr->attr.name, "keep_on_in_suspend")) { + ret = gps_keep_on_in_suspend_get(); +#endif + } + if (ret) + return strlcpy(buf, "1\n", 3); + else + return strlcpy(buf, "0\n", 3); +} + +static ssize_t power_gps_write(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + unsigned long on = simple_strtoul(buf, NULL, 10); + + if (!strcmp(attr->attr.name, "power_on") || + !strcmp(attr->attr.name, "pwron")) { + gps_pwron_set(on); + gta02_gps.power_was_on = !!on; +#ifdef CONFIG_PM + } else if (!strcmp(attr->attr.name, "keep_on_in_suspend")) { + gps_keep_on_in_suspend_set(on); +#endif + } + return count; +} + +#ifdef CONFIG_PM +static int gta02_pm_gps_suspend(struct device *dev) +{ + if (!gta02_gps.keep_on_in_suspend || + !gta02_gps.power_was_on) + gps_pwron_set(0); + else + dev_warn(dev, "GTA02: keeping gps ON " + "during suspend\n"); + return 0; +} + +static int gta02_pm_gps_resume(struct device *dev) +{ + if (!gta02_gps.keep_on_in_suspend && gta02_gps.power_was_on) + gps_pwron_set(1); + + return 0; +} + +static DEVICE_ATTR(keep_on_in_suspend, 0644, power_gps_read, power_gps_write); + +static struct dev_pm_ops gta02_gps_pm_ops = { + .suspend = gta02_pm_gps_suspend, + .resume = gta02_pm_gps_resume, +}; + +#define GTA02_GPS_PM_OPS (>a02_gps_pm_ops) + +#else +#define GTA02_GPS_PM_OPS NULL +#endif + +static DEVICE_ATTR(power_on, 0644, power_gps_read, power_gps_write); + +static struct attribute *gta02_gps_sysfs_entries[] = { + &dev_attr_power_on.attr, +#ifdef CONFIG_PM + &dev_attr_keep_on_in_suspend.attr, +#endif + NULL +}; + +static struct attribute_group gta02_gps_attr_group = { + .name = NULL, + .attrs = gta02_gps_sysfs_entries, +}; + +static int __init gta02_pm_gps_probe(struct platform_device *pdev) +{ + gta02_gps.regulator = regulator_get(&pdev->dev, "RF_3V"); + if (IS_ERR(gta02_gps.regulator)) { + dev_err(&pdev->dev, "probe failed %ld\n", + PTR_ERR(gta02_gps.regulator)); + + return PTR_ERR(gta02_gps.regulator); + } + + dev_info(&pdev->dev, "starting\n"); + + /* + * Here we should call the code that handles the set GPS power + * off action. But, the regulator API does not allow us to + * reassert regulator state, and when we read the regulator API + * logical state, it can differ from the actual state, So + * a workaround for this is to just set the regulator off in the + * PMU directly. Because that's different from normal flow, we + * have to reproduce other things from the OFF action here too. + */ + + /* + * u-boot enables LDO5 (GPS), which doesn't make sense and + * causes confusion. We therefore disable the regulator here. + */ + pcf50633_reg_write(gta02_pcf, PCF50633_REG_LDO5ENA, 0); + + /* + * take care not to power unpowered GPS from UART TX + * return them to GPIO and force low + */ + s3c2410_gpio_cfgpin(S3C2410_GPH(4), S3C2410_GPIO_OUTPUT); + s3c2410_gpio_setpin(S3C2410_GPH(4), 0); + /* don't let RX from unpowered GPS float */ + s3c2410_gpio_pullup(S3C2410_GPH(5), 1); + + return sysfs_create_group(&pdev->dev.kobj, + >a02_gps_attr_group); +} + +static int gta02_pm_gps_remove(struct platform_device *pdev) +{ + regulator_put(gta02_gps.regulator); + sysfs_remove_group(&pdev->dev.kobj, >a02_gps_attr_group); + return 0; +} + +static struct platform_driver gta02_pm_gps_driver = { + .probe = gta02_pm_gps_probe, + .remove = gta02_pm_gps_remove, + .driver = { + .name = "gta02-pm-gps", + .pm = GTA02_GPS_PM_OPS, + }, +}; + +static int __devinit gta02_pm_gps_init(void) +{ + return platform_driver_register(>a02_pm_gps_driver); +} +module_init(gta02_pm_gps_init); + +static void gta02_pm_gps_exit(void) +{ + platform_driver_unregister(>a02_pm_gps_driver); +} +module_exit(gta02_pm_gps_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Harald Welte <laforge@openmoko.org>"); diff --git a/arch/arm/mach-s3c2442/gta02-pm-gsm.c b/arch/arm/mach-s3c2442/gta02-pm-gsm.c new file mode 100644 index 00000000000..4b6cad806fe --- /dev/null +++ b/arch/arm/mach-s3c2442/gta02-pm-gsm.c @@ -0,0 +1,303 @@ +/* + * GSM Management code for the Openmoko Freerunner GSM Phone + * + * (C) 2007 by Openmoko Inc. + * Author: Harald Welte <laforge@openmoko.org> + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/console.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/delay.h> + +#include <mach/gpio.h> +#include <asm/mach-types.h> + +#include <mach/hardware.h> + +#include <mach/gta02.h> +#include <linux/mfd/pcf50633/gpio.h> +#include <mach/regs-gpio.h> +#include <mach/regs-gpioj.h> + +/* FIXME: Add interrupt handler counting irqs or something */ +static int gta_gsm_interrupts; + +extern void s3c24xx_serial_console_set_silence(int); + +struct gta02pm_priv { + int gpio_ndl_gsm; + struct console *con; +}; + +static struct gta02pm_priv gta02_gsm; + +static struct console *find_s3c24xx_console(void) +{ + struct console *con; + + acquire_console_sem(); + + for (con = console_drivers; con; con = con->next) { + if (!strcmp(con->name, "ttySAC")) + break; + } + + release_console_sem(); + + return con; +} + +static ssize_t gsm_read(struct device *dev, struct device_attribute *attr, + char *buf) +{ + if (!strcmp(attr->attr.name, "power_on")) { + if (pcf50633_gpio_get(gta02_pcf, PCF50633_GPIO2)) + goto out_1; + } else if (!strcmp(attr->attr.name, "download")) { + if (!s3c2410_gpio_getpin(GTA02_GPIO_nDL_GSM)) + goto out_1; + } else if (!strcmp(attr->attr.name, "flowcontrolled")) { + if (s3c2410_gpio_getcfg(S3C2410_GPH(1)) == S3C2410_GPIO_OUTPUT) + goto out_1; + } + + return strlcpy(buf, "0\n", 3); +out_1: + return strlcpy(buf, "1\n", 3); +} + +static void gsm_on_off(struct device *dev, int on) +{ + if (!on) { + s3c2410_gpio_cfgpin(S3C2410_GPH(1), S3C2410_GPIO_INPUT); + s3c2410_gpio_cfgpin(S3C2410_GPH(2), S3C2410_GPIO_INPUT); + + pcf50633_gpio_set(gta02_pcf, PCF50633_GPIO2, 0); + + if (gta02_gsm.con) { + s3c24xx_serial_console_set_silence(0); + console_start(gta02_gsm.con); + + dev_dbg(dev, "powered down gta02 GSM, enabling " + "serial console\n"); + } + + return; + } + + if (gta02_gsm.con) { + dev_dbg(dev, "powering up GSM, thus " + "disconnecting serial console\n"); + + console_stop(gta02_gsm.con); + s3c24xx_serial_console_set_silence(1); + } + + /* allow UART to talk to GSM side now we will power it */ + s3c2410_gpio_cfgpin(S3C2410_GPH(1), S3C2410_GPH1_nRTS0); + s3c2410_gpio_cfgpin(S3C2410_GPH(2), S3C2410_GPH2_TXD0); + + pcf50633_gpio_set(gta02_pcf, PCF50633_GPIO2, 7); + + msleep(100); + + s3c2410_gpio_setpin(GTA02_GPIO_MODEM_ON, 1); + msleep(500); + s3c2410_gpio_setpin(GTA02_GPIO_MODEM_ON, 0); + + /* + * workaround for calypso firmware moko10 and earlier, + * without this it will leave IRQ line high after + * booting + */ + s3c2410_gpio_setpin(S3C2410_GPH(1), 1); + s3c2410_gpio_cfgpin(S3C2410_GPH(1), S3C2410_GPIO_OUTPUT); + msleep(1000); + s3c2410_gpio_cfgpin(S3C2410_GPH(1), S3C2410_GPH1_nRTS0); + +} + +static ssize_t gsm_write(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long on = simple_strtoul(buf, NULL, 10); + + if (!strcmp(attr->attr.name, "power_on")) { + gsm_on_off(dev, on); + + return count; + } + + if (!strcmp(attr->attr.name, "download")) { + /* + * the keyboard / buttons driver requests and enables + * the JACK_INSERT IRQ. We have to take care about + * not enabling and disabling the IRQ when it was + * already in that state or we get "unblanaced IRQ" + * kernel warnings and stack dumps. So we use the + * copy of the ndl_gsm state to figure out if we should + * enable or disable the jack interrupt + */ + if (on) { + if (gta02_gsm.gpio_ndl_gsm) + disable_irq(gpio_to_irq( + GTA02_GPIO_JACK_INSERT)); + } else { + if (!gta02_gsm.gpio_ndl_gsm) + enable_irq(gpio_to_irq( + GTA02_GPIO_JACK_INSERT)); + } + + gta02_gsm.gpio_ndl_gsm = !on; + s3c2410_gpio_setpin(GTA02_GPIO_nDL_GSM, !on); + + return count; + } + + if (!strcmp(attr->attr.name, "flowcontrolled")) { + if (on) { + gta_gsm_interrupts = 0; + s3c2410_gpio_setpin(S3C2410_GPH(1), 1); + s3c2410_gpio_cfgpin(S3C2410_GPH(1), S3C2410_GPIO_OUTPUT); + } else + s3c2410_gpio_cfgpin(S3C2410_GPH(1), S3C2410_GPH1_nRTS0); + } + + return count; +} + +static DEVICE_ATTR(power_on, 0644, gsm_read, gsm_write); +static DEVICE_ATTR(reset, 0644, gsm_read, gsm_write); +static DEVICE_ATTR(download, 0644, gsm_read, gsm_write); +static DEVICE_ATTR(flowcontrolled, 0644, gsm_read, gsm_write); + +#ifdef CONFIG_PM + +static int gta02_gsm_suspend(struct device *dev) +{ + /* GPIO state is saved/restored by S3C2410 core GPIO driver, so we + * don't need to do much here. */ + + /* If flowcontrol asserted, abort if GSM already interrupted */ + if (s3c2410_gpio_getcfg(S3C2410_GPH(1)) == S3C2410_GPIO_OUTPUT) { + if (gta_gsm_interrupts) + goto busy; + } + + /* disable DL GSM to prevent jack_insert becoming 'floating' */ + s3c2410_gpio_setpin(GTA02_GPIO_nDL_GSM, 1); + return 0; + +busy: + return -EBUSY; +} + +static int gta02_gsm_suspend_late(struct device *dev) +{ + /* Last chance: abort if GSM already interrupted */ + if (s3c2410_gpio_getcfg(S3C2410_GPH(1)) == S3C2410_GPIO_OUTPUT) { + if (gta_gsm_interrupts) + return -EBUSY; + } + return 0; +} + +static int gta02_gsm_resume(struct device *dev) +{ + /* GPIO state is saved/restored by S3C2410 core GPIO driver, so we + * don't need to do much here. */ + + /* Make sure that the kernel console on the serial port is still + * disabled. FIXME: resume ordering race with serial driver! */ + if (gta02_gsm.con && s3c2410_gpio_getpin(GTA02_GPIO_MODEM_ON)) + console_stop(gta02_gsm.con); + + s3c2410_gpio_setpin(GTA02_GPIO_nDL_GSM, gta02_gsm.gpio_ndl_gsm); + + return 0; +} + +static struct dev_pm_ops gta02_gsm_pm_ops = { + .suspend = gta02_gsm_suspend, + .suspend_noirq = gta02_gsm_suspend_late, + .resume = gta02_gsm_resume, +}; + +#define GTA02_GSM_PM_OPS (>a02_gsm_pm_ops) + +#else +#define GTA02_GSM_PM_OPS NULL +#endif /* CONFIG_PM */ + +static struct attribute *gta02_gsm_sysfs_entries[] = { + &dev_attr_power_on.attr, + &dev_attr_reset.attr, + &dev_attr_download.attr, + &dev_attr_flowcontrolled.attr, + NULL +}; + +static struct attribute_group gta02_gsm_attr_group = { + .name = NULL, + .attrs = gta02_gsm_sysfs_entries, +}; + +static int __init gta02_gsm_probe(struct platform_device *pdev) +{ + gta02_gsm.con = find_s3c24xx_console(); + if (!gta02_gsm.con) + dev_warn(&pdev->dev, + "cannot find S3C24xx console driver\n"); + + /* note that download initially disabled, and enforce that */ + gta02_gsm.gpio_ndl_gsm = 1; + s3c2410_gpio_setpin(GTA02_GPIO_nDL_GSM, 1); + + /* GSM is to be initially off (at boot, or if this module inserted) */ + gsm_on_off(&pdev->dev, 0); + + return sysfs_create_group(&pdev->dev.kobj, >a02_gsm_attr_group); +} + +static int gta02_gsm_remove(struct platform_device *pdev) +{ + sysfs_remove_group(&pdev->dev.kobj, >a02_gsm_attr_group); + + return 0; +} + +static struct platform_driver gta02_gsm_driver = { + .probe = gta02_gsm_probe, + .remove = gta02_gsm_remove, + .driver = { + .name = "gta02-pm-gsm", + .pm = GTA02_GSM_PM_OPS, + }, +}; + +static int __devinit gta02_gsm_init(void) +{ + return platform_driver_register(>a02_gsm_driver); +} +module_init(gta02_gsm_init); + +static void gta02_gsm_exit(void) +{ + platform_driver_unregister(>a02_gsm_driver); +} +module_exit(gta02_gsm_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Harald Welte <laforge@openmoko.org>"); +MODULE_DESCRIPTION("Openmoko Freerunner GSM Power Management"); diff --git a/arch/arm/mach-s3c2442/gta02-pm-wlan.c b/arch/arm/mach-s3c2442/gta02-pm-wlan.c new file mode 100644 index 00000000000..6e0226600b1 --- /dev/null +++ b/arch/arm/mach-s3c2442/gta02-pm-wlan.c @@ -0,0 +1,201 @@ +/* + * GTA02 WLAN power management + * + * (C) 2008, 2009 by Openmoko Inc. + * Author: Andy Green <andy@openmoko.com> + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> + +#include <mach/hardware.h> +#include <asm/mach-types.h> + +#include <mach/gta02.h> +#include <mach/gta02-pm-wlan.h> +#include <mach/regs-gpio.h> +#include <mach/regs-gpioj.h> +#include <mach/gpio-fns.h> + +#include <linux/delay.h> +#include <linux/rfkill.h> + + +/* ----- Module hardware reset ("power") ----------------------------------- */ + + +void gta02_wlan_reset(int assert_reset) +{ + if (assert_reset) { + s3c2410_gpio_setpin(GTA02_GPIO_nWLAN_RESET, 0); + msleep(200); /* probably excessive but we don't have specs */ + } else { + s3c2410_gpio_setpin(GTA02_GPIO_nWLAN_RESET, 1); + } +} + +/* ----- rfkill ------------------------------------------------------------ */ + +/* + * S3C MCI handles suspend/resume through device removal/insertion. In order to + * preserve rfkill state, as required in clause 7 of section 3.1 in rfkill.txt, + * we therefore need to maintain rfkill state outside the driver. + * + * This platform driver is as good a place as any other. + */ + +static int (*gta02_wlan_rfkill_cb)(void *user, int on); +static void *gta02_wlan_rfkill_user; +static DEFINE_MUTEX(gta02_wlan_rfkill_lock); +static int gta02_wlan_rfkill_on; + +/* + * gta02_wlan_query_rfkill_lock is used to obtain the rfkill state before the + * driver is ready to process rfkill callbacks. To prevent the state from + * changing until the driver has completed its initialization, we grab and hold + * the rfkill lock. + * + * A call to gta02_wlan_query_rfkill_lock must be followed by either + * - a call to gta02_wlan_set_rfkill_cb, to complete the setup, or + * - a call to gta02_wlan_query_rfkill_unlock to abort the setup process. + */ + +int gta02_wlan_query_rfkill_lock(void) +{ + mutex_lock(>a02_wlan_rfkill_lock); + return gta02_wlan_rfkill_on; +} +EXPORT_SYMBOL_GPL(gta02_wlan_query_rfkill_lock); + +void gta02_wlan_query_rfkill_unlock(void) +{ + mutex_unlock(>a02_wlan_rfkill_lock); +} +EXPORT_SYMBOL_GPL(gta02_wlan_query_rfkill_unlock); + +void gta02_wlan_set_rfkill_cb(int (*cb)(void *user, int on), void *user) +{ + BUG_ON(!mutex_is_locked(>a02_wlan_rfkill_lock)); + BUG_ON(gta02_wlan_rfkill_cb); + gta02_wlan_rfkill_cb = cb; + gta02_wlan_rfkill_user = user; + mutex_unlock(>a02_wlan_rfkill_lock); +} +EXPORT_SYMBOL_GPL(gta02_wlan_set_rfkill_cb); + +void gta02_wlan_clear_rfkill_cb(void) +{ + mutex_lock(>a02_wlan_rfkill_lock); + BUG_ON(!gta02_wlan_rfkill_cb); + gta02_wlan_rfkill_cb = NULL; + mutex_unlock(>a02_wlan_rfkill_lock); +} +EXPORT_SYMBOL_GPL(gta02_wlan_clear_rfkill_cb); + +static int gta02_wlan_set_radio_block(void *data, bool blocked) +{ + struct device *dev = data; + int res = 0; + + dev_dbg(dev, "gta02_wlan_toggle_radio: blocked %d (%p)\n", + blocked, gta02_wlan_rfkill_cb); + mutex_lock(>a02_wlan_rfkill_lock); + if (gta02_wlan_rfkill_cb) + res = gta02_wlan_rfkill_cb(gta02_wlan_rfkill_user, !blocked); + if (!res) + gta02_wlan_rfkill_on = !blocked; + mutex_unlock(>a02_wlan_rfkill_lock); + return res; +} + +static const struct rfkill_ops gta02_wlan_rfkill_ops = { + .set_block = gta02_wlan_set_radio_block, +}; + +/* ----- Initialization/removal -------------------------------------------- */ + + +static int __init gta02_wlan_probe(struct platform_device *pdev) +{ + /* default-on for now */ + const int default_state = 1; + struct rfkill *rfkill; + int ret; + + dev_info(&pdev->dev, "starting\n"); + + s3c2410_gpio_cfgpin(GTA02_GPIO_nWLAN_RESET, S3C2410_GPIO_OUTPUT); + gta02_wlan_reset(1); + gta02_wlan_reset(0); + + rfkill = rfkill_alloc("ar6000", &pdev->dev, RFKILL_TYPE_WLAN, + >a02_wlan_rfkill_ops, &pdev->dev); + + + if (!rfkill) { + dev_err(&pdev->dev, "Failed to allocate rfkill\n"); + return -ENOMEM; + } + + rfkill_init_sw_state(rfkill, default_state); + /* + * If the WLAN driver somehow managed to get activated before we're + * ready, the driver is now in an unknown state, which isn't something + * we're prepared to handle. This can't happen, so just fail hard. + */ + BUG_ON(gta02_wlan_rfkill_cb); + gta02_wlan_rfkill_on = default_state; + + ret = rfkill_register(rfkill); + if (ret) { + rfkill_destroy(rfkill); + dev_err(&pdev->dev, "Failed to register rfkill\n"); + return ret; + } + + dev_set_drvdata(&pdev->dev, rfkill); + + return 0; +} + +static int gta02_wlan_remove(struct platform_device *pdev) +{ + struct rfkill *rfkill = dev_get_drvdata(&pdev->dev); + + rfkill_destroy(rfkill); + + return 0; +} + +static struct platform_driver gta02_wlan_driver = { + .probe = gta02_wlan_probe, + .remove = gta02_wlan_remove, + .driver = { + .name = "gta02-pm-wlan", + }, +}; + +static int __devinit gta02_wlan_init(void) +{ + return platform_driver_register(>a02_wlan_driver); +} +module_init(gta02_wlan_init); + +static void gta02_wlan_exit(void) +{ + platform_driver_unregister(>a02_wlan_driver); +} +module_exit(gta02_wlan_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Andy Green <andy@openmoko.com>"); +MODULE_DESCRIPTION("Openmoko GTA02 WLAN power management"); diff --git a/arch/arm/mach-s3c2442/include/mach/gta02-pm-gps.h b/arch/arm/mach-s3c2442/include/mach/gta02-pm-gps.h new file mode 100644 index 00000000000..f15180ac079 --- /dev/null +++ b/arch/arm/mach-s3c2442/include/mach/gta02-pm-gps.h @@ -0,0 +1 @@ +extern int gta02_pm_gps_is_on(void); diff --git a/arch/arm/mach-s3c2442/include/mach/gta02-pm-wlan.h b/arch/arm/mach-s3c2442/include/mach/gta02-pm-wlan.h new file mode 100644 index 00000000000..48369789709 --- /dev/null +++ b/arch/arm/mach-s3c2442/include/mach/gta02-pm-wlan.h @@ -0,0 +1,10 @@ +#ifndef __MACH_GTA02_PM_WLAN_H +#define __MACH_GTA02_PM_WLAN_H + +void gta02_wlan_reset(int assert_reset); +int gta02_wlan_query_rfkill_lock(void); +void gta02_wlan_query_rfkill_unlock(void); +void gta02_wlan_set_rfkill_cb(int (*cb)(void *user, int on), void *user); +void gta02_wlan_clear_rfkill_cb(void); + +#endif /* __MACH_GTA02_PM_WLAN_H */ diff --git a/arch/arm/mach-s3c2442/include/mach/gta02.h b/arch/arm/mach-s3c2442/include/mach/gta02.h index 43310a3c199..b67e5ec2919 100644 --- a/arch/arm/mach-s3c2442/include/mach/gta02.h +++ b/arch/arm/mach-s3c2442/include/mach/gta02.h @@ -2,6 +2,7 @@ #define _GTA02_H #include <mach/regs-gpio.h> +#include <mach/regs-gpioj.h> /* Different hardware revisions, passed in ATAG_REVISION by u-boot */ #define GTA02v1_SYSTEM_REV 0x00000310 @@ -84,4 +85,6 @@ int gta02_get_pcb_revision(void); +extern struct pcf50633 *gta02_pcf; + #endif /* _GTA02_H */ diff --git a/arch/arm/mach-s3c2442/mach-gta02.c b/arch/arm/mach-s3c2442/mach-gta02.c index 888e52eb81a..6afd4bd2c68 100644 --- a/arch/arm/mach-s3c2442/mach-gta02.c +++ b/arch/arm/mach-s3c2442/mach-gta02.c @@ -59,6 +59,12 @@ #include <linux/mfd/pcf50633/pmic.h> #include <linux/mfd/pcf50633/backlight.h> +#include <linux/input.h> +#include <linux/gpio_keys.h> + +#include <linux/leds.h> +#include <linux/leds_pwm.h> + #include <asm/mach/arch.h> #include <asm/mach/map.h> #include <asm/mach/irq.h> @@ -86,8 +92,12 @@ #include <plat/pm.h> #include <plat/udc.h> #include <plat/gpio-cfg.h> +#include <plat/gpio-core.h> #include <plat/iic.h> +#include <mach/gta02-pm-gps.h> +#include <mach/gta02-pm-wlan.h> + #include <linux/jbt6k74.h> #include <linux/glamofb.h> #include <linux/mfd/glamo.h> @@ -482,6 +492,7 @@ struct pcf50633_platform_data gta02_pcf_pdata = { .min_uV = 3300000, .max_uV = 3300000, .valid_modes_mask = REGULATOR_MODE_NORMAL, + .valid_ops_mask = REGULATOR_CHANGE_STATUS, .always_on = 1, .apply_uV = 1, .state_mem = { @@ -494,6 +505,7 @@ struct pcf50633_platform_data gta02_pcf_pdata = { .min_uV = 1300000, .max_uV = 1600000, .valid_modes_mask = REGULATOR_MODE_NORMAL, + .valid_ops_mask = REGULATOR_CHANGE_STATUS, .always_on = 1, .apply_uV = 1, }, @@ -515,7 +527,8 @@ struct pcf50633_platform_data gta02_pcf_pdata = { .min_uV = 2000000, .max_uV = 3300000, .valid_modes_mask = REGULATOR_MODE_NORMAL, - .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | + REGULATOR_CHANGE_VOLTAGE, .always_on = 1, }, .num_consumer_supplies = ARRAY_SIZE(hcldo_consumers), @@ -526,6 +539,7 @@ struct pcf50633_platform_data gta02_pcf_pdata = { .min_uV = 3300000, .max_uV = 3300000, .valid_modes_mask = REGULATOR_MODE_NORMAL, + .valid_ops_mask = REGULATOR_CHANGE_STATUS, .apply_uV = 1, .state_mem = { .enabled = 0, @@ -537,6 +551,7 @@ struct pcf50633_platform_data gta02_pcf_pdata = { .min_uV = 3300000, .max_uV = 3300000, .valid_modes_mask = REGULATOR_MODE_NORMAL, + .valid_ops_mask = REGULATOR_CHANGE_STATUS, .apply_uV = 1, }, }, @@ -545,6 +560,7 @@ struct pcf50633_platform_data gta02_pcf_pdata = { .min_uV = 3000000, .max_uV = 3000000, .valid_modes_mask = REGULATOR_MODE_NORMAL, + .valid_ops_mask = REGULATOR_CHANGE_STATUS, .apply_uV = 1, }, }, @@ -553,6 +569,7 @@ struct pcf50633_platform_data gta02_pcf_pdata = { .min_uV = 3200000, .max_uV = 3200000, .valid_modes_mask = REGULATOR_MODE_NORMAL, + .valid_ops_mask = REGULATOR_CHANGE_STATUS, .apply_uV = 1, }, .num_consumer_supplies = ARRAY_SIZE(ldo4_consumers), @@ -563,6 +580,7 @@ struct pcf50633_platform_data gta02_pcf_pdata = { .min_uV = 3000000, .max_uV = 3000000, .valid_modes_mask = REGULATOR_MODE_NORMAL, + .valid_ops_mask = REGULATOR_CHANGE_STATUS, .apply_uV = 1, .state_mem = { .enabled = 1, @@ -576,6 +594,7 @@ struct pcf50633_platform_data gta02_pcf_pdata = { .min_uV = 3000000, .max_uV = 3000000, .valid_modes_mask = REGULATOR_MODE_NORMAL, + .valid_ops_mask = REGULATOR_CHANGE_STATUS, }, .num_consumer_supplies = ARRAY_SIZE(ldo6_consumers), .consumer_supplies = ldo6_consumers, @@ -585,6 +604,7 @@ struct pcf50633_platform_data gta02_pcf_pdata = { .min_uV = 1800000, .max_uV = 1800000, .valid_modes_mask = REGULATOR_MODE_NORMAL, + .valid_ops_mask = REGULATOR_CHANGE_STATUS, .state_mem = { .enabled = 1, }, @@ -658,6 +678,7 @@ static struct s3c2410_platform_nand gta02_nand_info = { .twrph1 = 15, .nr_sets = ARRAY_SIZE(gta02_nand_sets), .sets = gta02_nand_sets, + .software_ecc = 1, }; @@ -666,11 +687,11 @@ static void gta02_udc_command(enum s3c2410_udc_cmd_e cmd) switch (cmd) { case S3C2410_UDC_P_ENABLE: pr_debug("%s S3C2410_UDC_P_ENABLE\n", __func__); - gpio_direction_output(GTA02_GPIO_USB_PULLUP, 1); + gpio_set_value(GTA02_GPIO_USB_PULLUP, 1); break; case S3C2410_UDC_P_DISABLE: pr_debug("%s S3C2410_UDC_P_DISABLE\n", __func__); - gpio_direction_output(GTA02_GPIO_USB_PULLUP, 0); + gpio_set_value(GTA02_GPIO_USB_PULLUP, 0); break; case S3C2410_UDC_P_RESET: pr_debug("%s S3C2410_UDC_P_RESET\n", __func__); @@ -685,52 +706,6 @@ static struct s3c2410_udc_mach_info gta02_udc_cfg = { }; -/* Touchscreen configuration. */ - -#ifdef CONFIG_TOUCHSCREEN_FILTER -const static struct ts_filter_group_configuration gta02_ts_group = { - .length = 12, - .close_enough = 10, - .threshold = 6, /* At least half of the points in a group. */ - .attempts = 10, -}; - -const static struct ts_filter_median_configuration gta02_ts_median = { - .extent = 20, - .decimation_below = 3, - .decimation_threshold = 8 * 3, - .decimation_above = 4, -}; - -const static struct ts_filter_mean_configuration gta02_ts_mean = { - .length = 4, -}; - -const static struct ts_filter_linear_configuration gta02_ts_linear = { - .constants = {1, 0, 0, 0, 1, 0, 1}, /* Don't modify coords. */ - .coord0 = 0, - .coord1 = 1, -}; -#endif - -const static struct ts_filter_chain_configuration gta02_filter_configuration[] = -{ -#ifdef CONFIG_TOUCHSCREEN_FILTER - {&ts_filter_group_api, >a02_ts_group.config}, - {&ts_filter_median_api, >a02_ts_median.config}, - {&ts_filter_mean_api, >a02_ts_mean.config}, - {&ts_filter_linear_api, >a02_ts_linear.config}, -#endif - {NULL, NULL}, -}; - -const static struct s3c2410_ts_mach_info gta02_ts_cfg = { - .delay = 10000, - .presc = 0xff, /* slow as we can go */ - .filter_config = gta02_filter_configuration, -}; - - /* USB */ static struct s3c2410_hcd_info gta02_usb_info = { .port[0] = { @@ -930,6 +905,137 @@ static struct resource gta02_hdq_resources[] = { }, }; +/* Buttons */ +static struct gpio_keys_button gta02_buttons[] = { + { + .gpio = GTA02_GPIO_AUX_KEY, + .code = KEY_PHONE, + .desc = "Aux", + .type = EV_KEY, + .debounce_interval = 100, + }, + { + .gpio = GTA02_GPIO_HOLD_KEY, + .code = KEY_PAUSE, + .desc = "Hold", + .type = EV_KEY, + .debounce_interval = 100, + }, +}; + +static struct gpio_keys_platform_data gta02_buttons_pdata = { + .buttons = gta02_buttons, + .nbuttons = ARRAY_SIZE(gta02_buttons), +}; + +static struct platform_device gta02_buttons_device = { + .name = "gpio-keys", + .id = -1, + .dev = { + .platform_data = >a02_buttons_pdata, + }, +}; + +/* LEDs */ +static struct gpio_led gta02_gpio_leds[] = { + { + .name = "gta02:red:aux", + .gpio = GTA02_GPIO_AUX_LED, + }, +}; + +static struct gpio_led_platform_data gta02_gpio_leds_pdata = { + .leds = gta02_gpio_leds, + .num_leds = ARRAY_SIZE(gta02_gpio_leds), +}; + +static struct platform_device gta02_leds_device = { + .name = "leds-gpio", + .id = -1, + .dev = { + .platform_data = >a02_gpio_leds_pdata, + }, +}; + +static struct led_pwm gta02_pwm_leds[] = { + { + .name = "gta02:orange:power", + .max_brightness = 0xff, + .pwm_period_ns = 1000000, + .pwm_id = 0, + }, + { + .name = "gta02:blue:power", + .max_brightness = 0xff, + .pwm_period_ns = 1000000, + .pwm_id = 1, + }, + { + .name = "gta02::vibrator", + .max_brightness = 0x3f, + .pwm_period_ns = 60000000, + .pwm_id = 3, + } +}; + +static struct led_pwm_platform_data gta02_pwm_leds_pdata = { + .num_leds = ARRAY_SIZE(gta02_pwm_leds), + .leds = gta02_pwm_leds, +}; + +static struct platform_device gta02_pwm_leds_device = { + .name = "leds_pwm", + .id = -1, + .dev = { + .platform_data = >a02_pwm_leds_pdata, + } +}; + +/* Touchscreen configuration. */ + +#ifdef CONFIG_TOUCHSCREEN_FILTER +const static struct ts_filter_group_configuration gta02_ts_group = { + .length = 12, + .close_enough = 10, + .threshold = 6, /* At least half of the points in a group. */ + .attempts = 10, +}; + +const static struct ts_filter_median_configuration gta02_ts_median = { + .extent = 20, + .decimation_below = 3, + .decimation_threshold = 8 * 3, + .decimation_above = 4, +}; + +const static struct ts_filter_mean_configuration gta02_ts_mean = { + .length = 4, +}; + +const static struct ts_filter_linear_configuration gta02_ts_linear = { + .constants = {1, 0, 0, 0, 1, 0, 1}, /* Don't modify coords. */ + .coord0 = 0, + .coord1 = 1, +}; +#endif + +const static struct ts_filter_chain_configuration gta02_filter_configuration[] = +{ +#ifdef CONFIG_TOUCHSCREEN_FILTER + {&ts_filter_group_api, >a02_ts_group.config}, + {&ts_filter_median_api, >a02_ts_median.config}, + {&ts_filter_mean_api, >a02_ts_mean.config}, + {&ts_filter_linear_api, >a02_ts_linear.config}, +#endif + {NULL, NULL}, +}; + +const static struct s3c2410_ts_mach_info gta02_ts_cfg = { + .delay = 10000, + .presc = 0xff, /* slow as we can go */ + .filter_config = gta02_filter_configuration, +}; + struct hdq_platform_data gta02_hdq_platform_data = { .attach_child_devices = gta02_hdq_attach_child_devices, .gpio_dir_out = gta02_hdq_gpio_direction_out, @@ -970,8 +1076,18 @@ static struct platform_device *gta02_devices[] __initdata = { &s3c_device_usbgadget, &s3c_device_nand, >a02_nor_flash, + &s3c_device_timer[0], + &s3c_device_timer[1], + &s3c_device_timer[3], &s3c_device_iis, &s3c_device_i2c0, + >a02_buttons_device, + >a02_leds_device, + >a02_pwm_leds_device, + >a02_pm_gps_dev, + >a02_pm_bt_dev, + >a02_pm_gsm_dev, + >a02_pm_wlan_dev, &s3c_device_adc, }; @@ -1014,11 +1130,104 @@ static void gta02_poweroff(void) pcf50633_reg_set_bit_mask(gta02_pcf, PCF50633_REG_OOCSHDWN, 1, 1); } +/* On hardware rev 5 and earlier the leds are missing a resistor and reading + * from their gpio pins will always return 0, so we have to shadow the + * led states software */ +static unsigned long gpb_shadow; +extern struct s3c_gpio_chip s3c24xx_gpios[]; + +static void gta02_gpb_set(struct gpio_chip *chip, + unsigned offset, int value) +{ + void __iomem *base = S3C24XX_GPIO_BASE(S3C2410_GPB(0)); + unsigned long flags; + unsigned long dat; + + local_irq_save(flags); + + dat = __raw_readl(base + 0x04) | gpb_shadow; + dat &= ~(1 << offset); + gpb_shadow &= ~(1 << offset); + if (value) { + dat |= 1 << offset; + switch (offset) { + case 0 ... 2: + gpb_shadow |= 1 << offset; + break; + default: + break; + } + } + __raw_writel(dat, base + 0x04); + + local_irq_restore(flags); +} + +static int gta02_gpb_get(struct gpio_chip *chip, unsigned offset) +{ + void __iomem *base = S3C24XX_GPIO_BASE(S3C2410_GPB(0)); + unsigned long val; + + val = __raw_readl(base + 0x04) | gpb_shadow; + val >>= offset; + val &= 1; + + return val; +} + +static void gta02_hijack_gpb(void) +{ +/* Uncomment this, once support for S3C_SYSTEM_REV_ATAG has been merged + * upstream. + if (S3C_SYSTEM_REV_ATAG > GTA02v5_SYSTEM_REV) + return; +*/ + + s3c24xx_gpios[1].chip.set = gta02_gpb_set; + s3c24xx_gpios[1].chip.get = gta02_gpb_get; +} + +/* + * Allow the bootloader to enable hw ecc + * hardware_ecc=1|0 + */ +static int __init hardware_ecc_setup(char *str) +{ + if (str && str[0] == '1') + gta02_nand_info.software_ecc = 0; + return 1; +} +__setup("hardware_ecc=", hardware_ecc_setup); + +static void gta02_request_gpios(void) +{ + int ret; + ret = gpio_request(GTA02_GPIO_USB_PULLUP, "USB pullup"); + if (ret) { + printk(KERN_ERR "Failed to request USB pullup gpio pin: %d\n", ret); + } else { + ret = gpio_direction_output(GTA02_GPIO_USB_PULLUP, 0); + if (ret) + printk(KERN_ERR "Failed to set USB pullup gpio direction: %d\n", ret); + } +} + +static void gta02_enable_pwm_pins(void) +{ + s3c2410_gpio_cfgpin(GTA02_GPIO_PWR_LED1, S3C2410_GPB0_TOUT0); + s3c2410_gpio_cfgpin(GTA02_GPIO_PWR_LED2, S3C2410_GPB1_TOUT1); + s3c2410_gpio_cfgpin(GTA02_GPIO_VIBRATOR_ON, S3C2410_GPB3_TOUT3); +} + static void __init gta02_machine_init(void) { /* Set the panic callback to make AUX LED blink at ~5Hz. */ panic_blink = gta02_panic_blink; + gta02_hijack_gpb(); + + gta02_request_gpios(); + s3c_pm_init(); #ifdef CONFIG_CHARGER_PCF50633 @@ -1037,6 +1246,8 @@ static void __init gta02_machine_init(void) ARRAY_SIZE(gta02_spi_board_info)); platform_add_devices(gta02_devices, ARRAY_SIZE(gta02_devices)); + gta02_enable_pwm_pins(); + pm_power_off = gta02_poweroff; } diff --git a/arch/arm/plat-s3c/include/plat/devs.h b/arch/arm/plat-s3c/include/plat/devs.h index 0f540ea1e99..50d50a2f086 100644 --- a/arch/arm/plat-s3c/include/plat/devs.h +++ b/arch/arm/plat-s3c/include/plat/devs.h @@ -53,6 +53,8 @@ extern struct platform_device s3c_device_nand; extern struct platform_device s3c_device_usbgadget; extern struct platform_device s3c_device_usb_hsotg; +extern struct platform_device s3c_device_ts; + /* s3c2440 specific devices */ #ifdef CONFIG_CPU_S3C2440 diff --git a/arch/arm/plat-s3c/include/plat/nand.h b/arch/arm/plat-s3c/include/plat/nand.h index 18f958801e6..723ab032059 100644 --- a/arch/arm/plat-s3c/include/plat/nand.h +++ b/arch/arm/plat-s3c/include/plat/nand.h @@ -47,6 +47,7 @@ struct s3c2410_platform_nand { int twrph1; /* time for release CLE/ALE from nWE/nOE inactive */ unsigned int ignore_unset_ecc:1; + unsigned int software_ecc:1; /* force software ecc at runtime */ int nr_sets; struct s3c2410_nand_set *sets; diff --git a/arch/arm/plat-s3c/include/plat/uncompress.h b/arch/arm/plat-s3c/include/plat/uncompress.h index dc66a477f62..8e02073e307 100644 --- a/arch/arm/plat-s3c/include/plat/uncompress.h +++ b/arch/arm/plat-s3c/include/plat/uncompress.h @@ -37,6 +37,8 @@ static void arch_detect_cpu(void); /* how many bytes we allow into the FIFO at a time in FIFO mode */ #define FIFO_MAX (14) +#if CONFIG_S3C_LOWLEVEL_UART_PORT >= 0 + #define uart_base S3C_PA_UART + (S3C_UART_OFFSET * CONFIG_S3C_LOWLEVEL_UART_PORT) static __inline__ void @@ -86,10 +88,6 @@ static void putc(int ch) uart_wr(S3C2410_UTXH, ch); } -static inline void flush(void) -{ -} - #define __raw_writel(d, ad) \ do { \ *((volatile unsigned int __force *)(ad)) = (d); \ @@ -163,6 +161,19 @@ static inline void arch_enable_uart_fifo(void) #define arch_enable_uart_fifo() do { } while(0) #endif +#else +static inline void putc(int ch) +{ +} + +#define arch_enable_uart_fifo() do { } while(0) +#define arch_decomp_wdog_start() +#define arch_decomp_wdog() +#endif + +static inline void flush(void) +{ +} static void arch_decomp_setup(void) diff --git a/arch/arm/plat-s3c/pwm.c b/arch/arm/plat-s3c/pwm.c index 4fdc5b307fd..2a5275d98ca 100644 --- a/arch/arm/plat-s3c/pwm.c +++ b/arch/arm/plat-s3c/pwm.c @@ -21,6 +21,8 @@ #include <mach/irqs.h> #include <mach/map.h> +#include <mach/gpio.h> +#include <mach/regs-gpio.h> #include <plat/devs.h> #include <plat/regs-timer.h> @@ -40,6 +42,10 @@ struct pwm_device { unsigned char running; unsigned char use_count; unsigned char pwm_id; + + /* Used for saving state during suspend */ + unsigned long tcon; + unsigned char routed_to_gpio:1; }; #define pwm_dbg(_pwm, msg...) dev_dbg(&(_pwm)->pdev->dev, msg) @@ -130,6 +136,7 @@ void pwm_free(struct pwm_device *pwm) EXPORT_SYMBOL(pwm_free); +#define pwm_tcon_mask(pwm) (0xf << (pwm->tcon_base)) #define pwm_tcon_start(pwm) (1 << (pwm->tcon_base + 0)) #define pwm_tcon_invert(pwm) (1 << (pwm->tcon_base + 2)) #define pwm_tcon_autoreload(pwm) (1 << (pwm->tcon_base + 3)) @@ -379,10 +386,75 @@ static int s3c_pwm_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM + +static int s3c_pwm_suspend(struct device *dev) +{ + struct pwm_device *pwm = dev_get_drvdata(dev); + unsigned int gpio_cfg = s3c2410_gpio_getcfg(S3C2410_GPB(pwm->pwm_id)); + + /* During suspend the pwm registers are reseted. If the invert bit is not + * set the output of TOUTn is high when the pwm is not active. In order to + * prevent spurious high output on the gpio at resume time, we stop routing + * the pwm signal to the gpio until tcon has been restored. */ + if (gpio_cfg == S3C2410_GPIO_SFN2) { + s3c2410_gpio_cfgpin(S3C2410_GPB(pwm->pwm_id), S3C2410_GPIO_INPUT); + pwm->routed_to_gpio = 1; + } else { + pwm->routed_to_gpio = 0; + } + + pwm->tcon = __raw_readl(S3C2410_TCON); + + return 0; +} + +static int s3c_pwm_resume(struct device *dev) +{ + struct pwm_device *pwm = dev_get_drvdata(dev); + unsigned long flags; + unsigned long tcon; + int duty_ns, period_ns; + + duty_ns = pwm->duty_ns; + period_ns = pwm->period_ns; + pwm->duty_ns = -1; + pwm->period_ns = -1; + pwm_config(pwm, duty_ns, period_ns); + + local_irq_save(flags); + tcon = __raw_readl(S3C2410_TCON); + tcon = __raw_readl(S3C2410_TCON); + tcon &= ~pwm_tcon_mask(pwm); + tcon |= pwm->tcon; + __raw_writel(tcon, S3C2410_TCON); + local_irq_restore(flags); + + if (pwm->routed_to_gpio) + s3c2410_gpio_cfgpin(S3C2410_GPB(pwm->pwm_id), S3C2410_GPIO_SFN2); + + return 0; +} + +struct dev_pm_ops s3c_pwm_pm_ops = { + .suspend = s3c_pwm_suspend, + .resume = s3c_pwm_resume, + .freeze = s3c_pwm_suspend, + .thaw = s3c_pwm_resume, +}; + +#define S3C_PWM_PM_OPS (&s3c_pwm_pm_ops) + +#else +#define S3C_PWM_PM_OPS NULL +#endif + + static struct platform_driver s3c_pwm_driver = { .driver = { .name = "s3c24xx-pwm", .owner = THIS_MODULE, + .pm = S3C_PWM_PM_OPS, }, .probe = s3c_pwm_probe, .remove = __devexit_p(s3c_pwm_remove), diff --git a/arch/arm/plat-s3c24xx/adc.c b/arch/arm/plat-s3c24xx/adc.c index df47322492d..8c443153d05 100644 --- a/arch/arm/plat-s3c24xx/adc.c +++ b/arch/arm/plat-s3c24xx/adc.c @@ -46,6 +46,7 @@ struct s3c_adc_client { int result; unsigned char is_ts; unsigned char channel; + unsigned int selected; void (*select_cb)(struct s3c_adc_client *c, unsigned selected); void (*convert_cb)(struct s3c_adc_client *c, @@ -62,20 +63,31 @@ struct adc_device { void __iomem *regs; unsigned int prescale; + unsigned int delay; int irq; }; static struct adc_device *adc_dev; +static struct work_struct resume_work; + static LIST_HEAD(adc_pending); #define adc_dbg(_adc, msg...) dev_dbg(&(_adc)->pdev->dev, msg) +#define AUTOPST (S3C2410_ADCTSC_YM_SEN | S3C2410_ADCTSC_YP_SEN | \ + S3C2410_ADCTSC_XP_SEN | S3C2410_ADCTSC_AUTO_PST | \ + S3C2410_ADCTSC_XY_PST(0)) + static inline void s3c_adc_convert(struct adc_device *adc) { unsigned con = readl(adc->regs + S3C2410_ADCCON); + if (adc->cur->is_ts) + writel(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, + adc->regs + S3C2410_ADCTSC); + con |= S3C2410_ADCCON_ENABLE_START; writel(con, adc->regs + S3C2410_ADCCON); } @@ -85,7 +97,10 @@ static inline void s3c_adc_select(struct adc_device *adc, { unsigned con = readl(adc->regs + S3C2410_ADCCON); - client->select_cb(client, 1); + if (!client->selected) { + client->selected = 1; + client->select_cb(client, 1); + } con &= ~S3C2410_ADCCON_MUXMASK; con &= ~S3C2410_ADCCON_STDBM; @@ -109,12 +124,9 @@ static void s3c_adc_try(struct adc_device *adc) { struct s3c_adc_client *next = adc->ts_pend; - if (!next && !list_empty(&adc_pending)) { + if (!next && !list_empty(&adc_pending)) next = list_first_entry(&adc_pending, struct s3c_adc_client, pend); - list_del(&next->pend); - } else - adc->ts_pend = NULL; if (next) { adc_dbg(adc, "new client is %p\n", next); @@ -277,14 +289,20 @@ static irqreturn_t s3c_adc_irq(int irq, void *pw) if (client->nr_samples > 0) { /* fire another conversion for this */ - + client->selected = 1; client->select_cb(client, 1); s3c_adc_convert(adc); } else { local_irq_save(flags); - (client->select_cb)(client, 0); + client->selected = 0; + if (!adc->cur->is_ts) + list_del(&adc->cur->pend); + else + adc->ts_pend = NULL; adc->cur = NULL; + (client->select_cb)(client, 0); + s3c_adc_try(adc); local_irq_restore(flags); } @@ -307,6 +325,7 @@ static int s3c_adc_probe(struct platform_device *pdev) adc->pdev = pdev; adc->prescale = S3C2410_ADCCON_PRSCVL(49); + adc->delay = 0x2710; adc->irq = platform_get_irq(pdev, 1); if (adc->irq <= 0) { @@ -346,6 +365,7 @@ static int s3c_adc_probe(struct platform_device *pdev) writel(adc->prescale | S3C2410_ADCCON_PRSCEN, adc->regs + S3C2410_ADCCON); + writel(adc->delay, adc->regs + S3C2410_ADCDLY); dev_info(dev, "attached adc driver\n"); @@ -389,18 +409,42 @@ static int s3c_adc_suspend(struct platform_device *pdev, pm_message_t state) writel(con, adc->regs + S3C2410_ADCCON); clk_disable(adc->clk); + disable_irq(IRQ_ADC); + if (!list_empty(&adc_pending) || adc->ts_pend) + dev_info(&pdev->dev, "We still have adc clients pending\n"); return 0; } +/* It seems this is not needed. This is under upstream review now. */ +static void adc_resume_work(struct work_struct *work) +{ + if (!adc_dev) /* Have no ADC here */ + return; + + if (!list_empty(&adc_pending) || adc_dev->ts_pend) + /* We still have adc clients pending */ + s3c_adc_try(adc_dev); +} + static int s3c_adc_resume(struct platform_device *pdev) { struct adc_device *adc = platform_get_drvdata(pdev); + enable_irq(IRQ_ADC); clk_enable(adc->clk); writel(adc->prescale | S3C2410_ADCCON_PRSCEN, adc->regs + S3C2410_ADCCON); + writel(adc->delay, adc->regs + S3C2410_ADCDLY); + + /* Schedule task if there are clients pending. */ + if (!list_empty(&adc_pending) || adc_dev->ts_pend) { + INIT_WORK(&resume_work, adc_resume_work); + if (!schedule_work(&resume_work)) + dev_err(&pdev->dev, + "Failed to schedule adc_resume work!\n"); + } return 0; } diff --git a/arch/arm/plat-s3c24xx/devs.c b/arch/arm/plat-s3c24xx/devs.c index f52a92ce8dd..9ef0e97d56e 100644 --- a/arch/arm/plat-s3c24xx/devs.c +++ b/arch/arm/plat-s3c24xx/devs.c @@ -30,6 +30,8 @@ #include <mach/irqs.h> #include <asm/irq.h> +#include <mach/ts.h> + #include <plat/regs-serial.h> #include <plat/udc.h> @@ -182,6 +184,24 @@ void __init s3c24xx_fb_set_platdata(struct s3c2410fb_mach_info *pd) } } +/* Touchscreen */ + +struct platform_device s3c_device_ts = { + .name = "s3c2410-ts", + .id = -1, + .dev.parent = &s3c_device_adc.dev, +}; + +static struct s3c2410_ts_mach_info s3c2410ts_info; + +void set_s3c2410ts_info(const struct s3c2410_ts_mach_info *hard_s3c2410ts_info) +{ + memcpy(&s3c2410ts_info, hard_s3c2410ts_info, + sizeof(struct s3c2410_ts_mach_info)); + s3c_device_ts.dev.platform_data = &s3c2410ts_info; +} +EXPORT_SYMBOL(set_s3c2410ts_info); + /* USB Device (Gadget)*/ static struct resource s3c_usbgadget_resource[] = { diff --git a/arch/arm/plat-s3c24xx/gpiolib.c b/arch/arm/plat-s3c24xx/gpiolib.c index 6d7a961d326..c2dd900660b 100644 --- a/arch/arm/plat-s3c24xx/gpiolib.c +++ b/arch/arm/plat-s3c24xx/gpiolib.c @@ -26,6 +26,7 @@ #include <plat/pm.h> #include <mach/regs-gpio.h> +#include <mach/regs-gpioj.h> static int s3c24xx_gpiolib_banka_input(struct gpio_chip *chip, unsigned offset) { @@ -160,8 +161,16 @@ struct s3c_gpio_chip s3c24xx_gpios[] = { .label = "GPIOH", .ngpio = 11, }, - }, -}; + }, { + .base = S3C2440_GPJCON, + .pm = __gpio_pm(&s3c_gpio_pm_2bit), + .chip = { + .base = S3C2440_GPJ0, + .owner = THIS_MODULE, + .label = "GPIOJ", + .ngpio = 11, + }, + },}; static __init int s3c24xx_gpiolib_init(void) { diff --git a/arch/arm/plat-s3c24xx/include/plat/irq.h b/arch/arm/plat-s3c24xx/include/plat/irq.h index 69e1be8bec3..11a866466d8 100644 --- a/arch/arm/plat-s3c24xx/include/plat/irq.h +++ b/arch/arm/plat-s3c24xx/include/plat/irq.h @@ -12,6 +12,7 @@ #include <linux/io.h> +#include <mach/irqs.h> #include <mach/hardware.h> #include <mach/regs-irq.h> #include <mach/regs-gpio.h> @@ -31,8 +32,15 @@ s3c_irqsub_mask(unsigned int irqno, unsigned int parentbit, { unsigned long mask; unsigned long submask; +#ifdef CONFIG_S3C2440_C_FIQ + unsigned long flags; +#endif submask = __raw_readl(S3C2410_INTSUBMSK); +#ifdef CONFIG_S3C2440_C_FIQ + local_save_flags(flags); + local_fiq_disable(); +#endif mask = __raw_readl(S3C2410_INTMSK); submask |= (1UL << (irqno - IRQ_S3CUART_RX0)); @@ -45,6 +53,9 @@ s3c_irqsub_mask(unsigned int irqno, unsigned int parentbit, /* write back masks */ __raw_writel(submask, S3C2410_INTSUBMSK); +#ifdef CONFIG_S3C2440_C_FIQ + local_irq_restore(flags); +#endif } @@ -53,8 +64,15 @@ s3c_irqsub_unmask(unsigned int irqno, unsigned int parentbit) { unsigned long mask; unsigned long submask; +#ifdef CONFIG_S3C2440_C_FIQ + unsigned long flags; +#endif submask = __raw_readl(S3C2410_INTSUBMSK); +#ifdef CONFIG_S3C2440_C_FIQ + local_save_flags(flags); + local_fiq_disable(); +#endif mask = __raw_readl(S3C2410_INTMSK); submask &= ~(1UL << (irqno - IRQ_S3CUART_RX0)); @@ -63,6 +81,9 @@ s3c_irqsub_unmask(unsigned int irqno, unsigned int parentbit) /* write back masks */ __raw_writel(submask, S3C2410_INTSUBMSK); __raw_writel(mask, S3C2410_INTMSK); +#ifdef CONFIG_S3C2440_C_FIQ + local_irq_restore(flags); +#endif } diff --git a/arch/arm/plat-s3c24xx/irq.c b/arch/arm/plat-s3c24xx/irq.c index d02f5f02045..14aa528d985 100644 --- a/arch/arm/plat-s3c24xx/irq.c +++ b/arch/arm/plat-s3c24xx/irq.c @@ -28,6 +28,8 @@ #include <asm/mach/irq.h> #include <plat/regs-irqtype.h> +#include <mach/regs-irq.h> +#include <mach/regs-gpio.h> #include <plat/cpu.h> #include <plat/pm.h> @@ -37,12 +39,20 @@ static void s3c_irq_mask(unsigned int irqno) { unsigned long mask; - +#ifdef CONFIG_S3C2440_C_FIQ + unsigned long flags; +#endif irqno -= IRQ_EINT0; - +#ifdef CONFIG_S3C2440_C_FIQ + local_save_flags(flags); + local_fiq_disable(); +#endif mask = __raw_readl(S3C2410_INTMSK); mask |= 1UL << irqno; __raw_writel(mask, S3C2410_INTMSK); +#ifdef CONFIG_S3C2440_C_FIQ + local_irq_restore(flags); +#endif } static inline void @@ -59,9 +69,19 @@ s3c_irq_maskack(unsigned int irqno) { unsigned long bitval = 1UL << (irqno - IRQ_EINT0); unsigned long mask; +#ifdef CONFIG_S3C2440_C_FIQ + unsigned long flags; +#endif +#ifdef CONFIG_S3C2440_C_FIQ + local_save_flags(flags); + local_fiq_disable(); +#endif mask = __raw_readl(S3C2410_INTMSK); __raw_writel(mask|bitval, S3C2410_INTMSK); +#ifdef CONFIG_S3C2440_C_FIQ + local_irq_restore(flags); +#endif __raw_writel(bitval, S3C2410_SRCPND); __raw_writel(bitval, S3C2410_INTPND); @@ -72,15 +92,25 @@ static void s3c_irq_unmask(unsigned int irqno) { unsigned long mask; +#ifdef CONFIG_S3C2440_C_FIQ + unsigned long flags; +#endif if (irqno != IRQ_TIMER4 && irqno != IRQ_EINT8t23) irqdbf2("s3c_irq_unmask %d\n", irqno); irqno -= IRQ_EINT0; +#ifdef CONFIG_S3C2440_C_FIQ + local_save_flags(flags); + local_fiq_disable(); +#endif mask = __raw_readl(S3C2410_INTMSK); mask &= ~(1UL << irqno); __raw_writel(mask, S3C2410_INTMSK); +#ifdef CONFIG_S3C2440_C_FIQ + local_irq_restore(flags); +#endif } struct irq_chip s3c_irq_level_chip = { @@ -559,26 +589,26 @@ void __init s3c24xx_init_irq(void) last = 0; for (i = 0; i < 4; i++) { - pend = __raw_readl(S3C2410_INTPND); + pend = __raw_readl(S3C2410_SUBSRCPND); if (pend == 0 || pend == last) break; - __raw_writel(pend, S3C2410_SRCPND); - __raw_writel(pend, S3C2410_INTPND); - printk("irq: clearing pending status %08x\n", (int)pend); + printk("irq: clearing subpending status %08x\n", (int)pend); + __raw_writel(pend, S3C2410_SUBSRCPND); last = pend; } last = 0; for (i = 0; i < 4; i++) { - pend = __raw_readl(S3C2410_SUBSRCPND); + pend = __raw_readl(S3C2410_INTPND); if (pend == 0 || pend == last) break; - printk("irq: clearing subpending status %08x\n", (int)pend); - __raw_writel(pend, S3C2410_SUBSRCPND); + __raw_writel(pend, S3C2410_SRCPND); + __raw_writel(pend, S3C2410_INTPND); + printk("irq: clearing pending status %08x\n", (int)pend); last = pend; } |