diff options
-rw-r--r-- | arch/arm/mach-s3c2442/Kconfig | 1 | ||||
-rw-r--r-- | arch/arm/mach-s3c2442/Makefile | 6 | ||||
-rw-r--r-- | arch/arm/mach-s3c2442/gta02-pm-bt.c | 257 | ||||
-rw-r--r-- | arch/arm/mach-s3c2442/gta02-pm-gps.c | 246 | ||||
-rw-r--r-- | arch/arm/mach-s3c2442/gta02-pm-gsm.c | 303 | ||||
-rw-r--r-- | arch/arm/mach-s3c2442/gta02-pm-wlan.c | 201 | ||||
-rw-r--r-- | arch/arm/mach-s3c2442/include/mach/gta02-pm-gps.h | 1 | ||||
-rw-r--r-- | arch/arm/mach-s3c2442/include/mach/gta02-pm-wlan.h | 10 | ||||
-rw-r--r-- | arch/arm/mach-s3c2442/include/mach/gta02.h | 3 | ||||
-rw-r--r-- | arch/arm/mach-s3c2442/mach-gta02.c | 309 |
10 files changed, 1287 insertions, 50 deletions
diff --git a/arch/arm/mach-s3c2442/Kconfig b/arch/arm/mach-s3c2442/Kconfig index 8e37b9f42ab..194d128c695 100644 --- a/arch/arm/mach-s3c2442/Kconfig +++ b/arch/arm/mach-s3c2442/Kconfig @@ -37,6 +37,7 @@ config MACH_NEO1973_GTA02 select FIQ select S3C_PWM select S3C24XX_GPIO_EXTRA64 + select S3C_DEV_NAND help Say Y here if you are using the Openmoko GTA02 / Freerunner GSM Phone diff --git a/arch/arm/mach-s3c2442/Makefile b/arch/arm/mach-s3c2442/Makefile index 87338d9e139..6247ca03eae 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-fiq.o + gta02-fiq.o \ + gta02-pm-bt.o \ + gta02-pm-gps.o \ + gta02-pm-gsm.o \ + gta02-pm-wlan.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 dc0aaeefc88..1480881268c 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,6 +1130,95 @@ 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. */ @@ -1021,6 +1226,10 @@ static void __init gta02_machine_init(void) s3c_device_ts.name = "s3c2440-ts"; + gta02_hijack_gpb(); + + gta02_request_gpios(); + s3c_pm_init(); #ifdef CONFIG_CHARGER_PCF50633 @@ -1039,6 +1248,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; } |