From 0739383d85822b93831803abb207794c16eedd79 Mon Sep 17 00:00:00 2001 From: mokopatches Date: Wed, 16 Jul 2008 14:44:48 +0100 Subject: [PATCH] gta01-power_control.patch [PATCH] Neo1973 GPS / GSM / Bluetooth power control via sysfs Signed-off-by: Harald Welte --- arch/arm/plat-s3c24xx/Makefile | 1 + arch/arm/plat-s3c24xx/neo1973_pm_bt.c | 152 +++++++++ arch/arm/plat-s3c24xx/neo1973_pm_gps.c | 560 ++++++++++++++++++++++++++++++++ arch/arm/plat-s3c24xx/neo1973_pm_gsm.c | 217 ++++++++++++ 4 files changed, 930 insertions(+), 0 deletions(-) create mode 100644 arch/arm/plat-s3c24xx/neo1973_pm_bt.c create mode 100644 arch/arm/plat-s3c24xx/neo1973_pm_gps.c create mode 100644 arch/arm/plat-s3c24xx/neo1973_pm_gsm.c diff --git a/arch/arm/plat-s3c24xx/Makefile b/arch/arm/plat-s3c24xx/Makefile index 131d202..6f43aca 100644 --- a/arch/arm/plat-s3c24xx/Makefile +++ b/arch/arm/plat-s3c24xx/Makefile @@ -29,3 +29,4 @@ obj-$(CONFIG_PM) += pm.o obj-$(CONFIG_PM) += sleep.o obj-$(CONFIG_S3C2410_DMA) += dma.o obj-$(CONFIG_MACH_SMDK) += common-smdk.o +obj-$(CONFIG_MACH_NEO1973) += neo1973_pm_gsm.o neo1973_pm_gps.o neo1973_pm_bt.o diff --git a/arch/arm/plat-s3c24xx/neo1973_pm_bt.c b/arch/arm/plat-s3c24xx/neo1973_pm_bt.c new file mode 100644 index 0000000..b1af441 --- /dev/null +++ b/arch/arm/plat-s3c24xx/neo1973_pm_bt.c @@ -0,0 +1,152 @@ +/* + * Bluetooth PM code for the FIC Neo1973 GSM Phone + * + * (C) 2007 by OpenMoko Inc. + * Author: Harald Welte + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation + * + */ + +#include +#include +#include +#include + +#include + +#include +#include + +#define DRVMSG "FIC Neo1973 Bluetooth Power Management" + +static ssize_t bt_read(struct device *dev, struct device_attribute *attr, + char *buf) +{ + if (!strcmp(attr->attr.name, "power_on")) { + if (pcf50606_onoff_get(pcf50606_global, + PCF50606_REGULATOR_D1REG) && + pcf50606_voltage_get(pcf50606_global, + PCF50606_REGULATOR_D1REG) == 3100) + goto out_1; + } else if (!strcmp(attr->attr.name, "reset")) { + if (s3c2410_gpio_getpin(GTA01_GPIO_BT_EN) == 0) + goto out_1; + } + + return strlcpy(buf, "0\n", 3); +out_1: + return strlcpy(buf, "1\n", 3); +} + +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); + + if (!strcmp(attr->attr.name, "power_on")) { + /* if we are powering up, assert reset, then power, then + * release reset */ + if (on) { + s3c2410_gpio_setpin(GTA01_GPIO_BT_EN, 0); + pcf50606_voltage_set(pcf50606_global, + PCF50606_REGULATOR_D1REG, + 3100); + } + pcf50606_onoff_set(pcf50606_global, + PCF50606_REGULATOR_D1REG, on); + s3c2410_gpio_setpin(GTA01_GPIO_BT_EN, on); + } else if (!strcmp(attr->attr.name, "reset")) { + /* reset is low-active, so we need to invert */ + s3c2410_gpio_setpin(GTA01_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 gta01_bt_suspend(struct platform_device *pdev, pm_message_t state) +{ + dev_dbg(&pdev->dev, DRVMSG ": suspending\n"); + /* FIXME: The PMU should save the PMU status, and the GPIO code should + * preserve the GPIO level, so there shouldn't be anything left to do + * for us, should there? */ + + return 0; +} + +static int gta01_bt_resume(struct platform_device *pdev) +{ + dev_dbg(&pdev->dev, DRVMSG ": resuming\n"); + + return 0; +} +#else +#define gta01_bt_suspend NULL +#define gta01_bt_resume NULL +#endif + +static struct attribute *gta01_bt_sysfs_entries[] = { + &dev_attr_power_on.attr, + &dev_attr_reset.attr, + NULL +}; + +static struct attribute_group gta01_bt_attr_group = { + .name = NULL, + .attrs = gta01_bt_sysfs_entries, +}; + +static int __init gta01_bt_probe(struct platform_device *pdev) +{ + dev_info(&pdev->dev, DRVMSG ": starting\n"); + + /* we make sure that the voltage is off */ + pcf50606_onoff_set(pcf50606_global, + PCF50606_REGULATOR_D1REG, 0); + /* we pull reset to low to make sure that the chip doesn't + * drain power through the reset line */ + s3c2410_gpio_setpin(GTA01_GPIO_BT_EN, 0); + + return sysfs_create_group(&pdev->dev.kobj, >a01_bt_attr_group); +} + +static int gta01_bt_remove(struct platform_device *pdev) +{ + sysfs_remove_group(&pdev->dev.kobj, >a01_bt_attr_group); + + return 0; +} + +static struct platform_driver gta01_bt_driver = { + .probe = gta01_bt_probe, + .remove = gta01_bt_remove, + .suspend = gta01_bt_suspend, + .resume = gta01_bt_resume, + .driver = { + .name = "neo1973-pm-bt", + }, +}; + +static int __devinit gta01_bt_init(void) +{ + return platform_driver_register(>a01_bt_driver); +} + +static void gta01_bt_exit(void) +{ + platform_driver_unregister(>a01_bt_driver); +} + +module_init(gta01_bt_init); +module_exit(gta01_bt_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Harald Welte "); +MODULE_DESCRIPTION(DRVMSG); diff --git a/arch/arm/plat-s3c24xx/neo1973_pm_gps.c b/arch/arm/plat-s3c24xx/neo1973_pm_gps.c new file mode 100644 index 0000000..f8cf719 --- /dev/null +++ b/arch/arm/plat-s3c24xx/neo1973_pm_gps.c @@ -0,0 +1,560 @@ +/* + * GPS Power Management code for the FIC Neo1973 GSM Phone + * + * (C) 2007 by OpenMoko Inc. + * Author: Harald Welte + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation + * + */ + +#include +#include +#include +#include +#include + +#include + +#include +#include + +/* This is the 2.8V supply for the RTC crystal, the mail clock crystal and + * the input to VDD_RF */ +static void gps_power_2v8_set(int on) +{ + switch (system_rev) { + case GTA01v3_SYSTEM_REV: + case GTA01v4_SYSTEM_REV: + if (on) + pcf50606_voltage_set(pcf50606_global, + PCF50606_REGULATOR_IOREG, 2800); + pcf50606_onoff_set(pcf50606_global, + PCF50606_REGULATOR_IOREG, on); + break; + case GTA01Bv2_SYSTEM_REV: + s3c2410_gpio_setpin(GTA01_GPIO_GPS_EN_2V8, on); + break; + case GTA01Bv3_SYSTEM_REV: + case GTA01Bv4_SYSTEM_REV: + break; + } +} + +static int gps_power_2v8_get(void) +{ + int ret = 0; + + switch (system_rev) { + case GTA01v3_SYSTEM_REV: + case GTA01v4_SYSTEM_REV: + if (pcf50606_onoff_get(pcf50606_global, + PCF50606_REGULATOR_IOREG) && + pcf50606_voltage_get(pcf50606_global, + PCF50606_REGULATOR_IOREG) == 2800) + ret = 1; + break; + case GTA01Bv2_SYSTEM_REV: + if (s3c2410_gpio_getpin(GTA01_GPIO_GPS_EN_2V8)) + ret = 1; + break; + case GTA01Bv3_SYSTEM_REV: + case GTA01Bv4_SYSTEM_REV: + break; + } + + return ret; +} + +/* This is the 3V supply (AVDD) for the external RF frontend (LNA bias) */ +static void gps_power_3v_set(int on) +{ + switch (system_rev) { + case GTA01v3_SYSTEM_REV: + case GTA01v4_SYSTEM_REV: + if (on) + pcf50606_voltage_set(pcf50606_global, + PCF50606_REGULATOR_D1REG, 3000); + pcf50606_onoff_set(pcf50606_global, + PCF50606_REGULATOR_D1REG, on); + break; + case GTA01Bv2_SYSTEM_REV: + case GTA01Bv3_SYSTEM_REV: + case GTA01Bv4_SYSTEM_REV: + s3c2410_gpio_setpin(GTA01_GPIO_GPS_EN_3V, on); + break; + } +} + +static int gps_power_3v_get(void) +{ + int ret = 0; + + switch (system_rev) { + case GTA01v3_SYSTEM_REV: + case GTA01v4_SYSTEM_REV: + if (pcf50606_onoff_get(pcf50606_global, + PCF50606_REGULATOR_D1REG) && + pcf50606_voltage_get(pcf50606_global, + PCF50606_REGULATOR_D1REG) == 3000) + ret = 1; + break; + case GTA01Bv2_SYSTEM_REV: + case GTA01Bv3_SYSTEM_REV: + case GTA01Bv4_SYSTEM_REV: + if (s3c2410_gpio_getpin(GTA01_GPIO_GPS_EN_3V)) + ret = 1; + break; + } + + return ret; +} + +/* This is the 3.3V supply for VDD_IO and VDD_LPREG input */ +static void gps_power_3v3_set(int on) +{ + switch (system_rev) { + case GTA01v3_SYSTEM_REV: + case GTA01v4_SYSTEM_REV: + case GTA01Bv2_SYSTEM_REV: + if (on) + pcf50606_voltage_set(pcf50606_global, + PCF50606_REGULATOR_DCD, 3300); + pcf50606_onoff_set(pcf50606_global, + PCF50606_REGULATOR_DCD, on); + break; + case GTA01Bv3_SYSTEM_REV: + case GTA01Bv4_SYSTEM_REV: + s3c2410_gpio_setpin(GTA01_GPIO_GPS_EN_3V3, on); + break; + } +} + +static int gps_power_3v3_get(void) +{ + int ret = 0; + + switch (system_rev) { + case GTA01v3_SYSTEM_REV: + case GTA01v4_SYSTEM_REV: + case GTA01Bv2_SYSTEM_REV: + if (pcf50606_onoff_get(pcf50606_global, + PCF50606_REGULATOR_DCD) && + pcf50606_voltage_get(pcf50606_global, + PCF50606_REGULATOR_DCD) == 3300) + ret = 1; + break; + case GTA01Bv3_SYSTEM_REV: + case GTA01Bv4_SYSTEM_REV: + if (s3c2410_gpio_getpin(GTA01_GPIO_GPS_EN_3V3)) + ret = 1; + break; + } + + return ret; +} + +/* This is the 2.5V supply for VDD_PLLREG and VDD_COREREG input */ +static void gps_power_2v5_set(int on) +{ + switch (system_rev) { + case GTA01v3_SYSTEM_REV: + /* This is CORE_1V8 and cannot be disabled */ + break; + case GTA01v4_SYSTEM_REV: + case GTA01Bv2_SYSTEM_REV: + case GTA01Bv3_SYSTEM_REV: + case GTA01Bv4_SYSTEM_REV: + if (on) + pcf50606_voltage_set(pcf50606_global, + PCF50606_REGULATOR_D2REG, 2500); + pcf50606_onoff_set(pcf50606_global, + PCF50606_REGULATOR_D2REG, on); + break; + } +} + +static int gps_power_2v5_get(void) +{ + int ret = 0; + + switch (system_rev) { + case GTA01v3_SYSTEM_REV: + /* This is CORE_1V8 and cannot be disabled */ + ret = 1; + break; + case GTA01v4_SYSTEM_REV: + case GTA01Bv2_SYSTEM_REV: + case GTA01Bv3_SYSTEM_REV: + case GTA01Bv4_SYSTEM_REV: + if (pcf50606_onoff_get(pcf50606_global, + PCF50606_REGULATOR_D2REG) && + pcf50606_voltage_get(pcf50606_global, + PCF50606_REGULATOR_D2REG) == 2500) + ret = 1; + break; + } + + return ret; +} + +/* This is the 1.5V supply for VDD_CORE */ +static void gps_power_1v5_set(int on) +{ + switch (system_rev) { + case GTA01v3_SYSTEM_REV: + case GTA01v4_SYSTEM_REV: + case GTA01Bv2_SYSTEM_REV: + /* This is switched via 2v5 */ + break; + case GTA01Bv3_SYSTEM_REV: + case GTA01Bv4_SYSTEM_REV: + if (on) + pcf50606_voltage_set(pcf50606_global, + PCF50606_REGULATOR_DCD, 1500); + pcf50606_onoff_set(pcf50606_global, + PCF50606_REGULATOR_DCD, on); + break; + } +} + +static int gps_power_1v5_get(void) +{ + int ret = 0; + + switch (system_rev) { + case GTA01v3_SYSTEM_REV: + case GTA01v4_SYSTEM_REV: + case GTA01Bv2_SYSTEM_REV: + /* This is switched via 2v5 */ + ret = 1; + break; + case GTA01Bv3_SYSTEM_REV: + case GTA01Bv4_SYSTEM_REV: + if (pcf50606_onoff_get(pcf50606_global, + PCF50606_REGULATOR_DCD) && + pcf50606_voltage_get(pcf50606_global, + PCF50606_REGULATOR_DCD) == 1500) + ret = 1; + break; + } + + return ret; +} + +/* This is the POWERON pin */ +static void gps_pwron_set(int on) +{ + s3c2410_gpio_setpin(GTA01_GPIO_GPS_PWRON, on); +} + +static int gps_pwron_get(void) +{ + if (s3c2410_gpio_getpin(GTA01_GPIO_GPS_PWRON)) + return 1; + else + return 0; +} + +/* This is the nRESET pin */ +static void gps_rst_set(int on) +{ + switch (system_rev) { + case GTA01v3_SYSTEM_REV: + pcf50606_gpo0_set(pcf50606_global, on); + break; + case GTA01v4_SYSTEM_REV: + case GTA01Bv2_SYSTEM_REV: + case GTA01Bv3_SYSTEM_REV: + case GTA01Bv4_SYSTEM_REV: + s3c2410_gpio_setpin(GTA01_GPIO_GPS_RESET, on); + break; + } +} + +static int gps_rst_get(void) +{ + switch (system_rev) { + case GTA01v3_SYSTEM_REV: + if (pcf50606_gpo0_get(pcf50606_global)) + return 1; + break; + case GTA01v4_SYSTEM_REV: + case GTA01Bv2_SYSTEM_REV: + case GTA01Bv3_SYSTEM_REV: + case GTA01Bv4_SYSTEM_REV: + if (s3c2410_gpio_getpin(GTA01_GPIO_GPS_RESET)) + return 1; + break; + } + + return 0; +} + +static ssize_t power_gps_read(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret = 0; + + if (!strcmp(attr->attr.name, "power_tcxo_2v8")) { + ret = gps_power_2v8_get(); + } else if (!strcmp(attr->attr.name, "power_avdd_3v")) { + ret = gps_power_3v_get(); + } else if (!strcmp(attr->attr.name, "pwron")) { + ret = gps_pwron_get(); + } else if (!strcmp(attr->attr.name, "reset")) { + ret = gps_rst_get(); + } else if (!strcmp(attr->attr.name, "power_lp_io_3v3")) { + ret = gps_power_3v3_get(); + } else if (!strcmp(attr->attr.name, "power_pll_core_2v5")) { + ret = gps_power_2v5_get(); + } else if (!strcmp(attr->attr.name, "power_core_1v5") || + !strcmp(attr->attr.name, "power_vdd_core_1v5")) { + ret = gps_power_1v5_get(); + } + + 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_tcxo_2v8")) { + gps_power_2v8_set(on); + } else if (!strcmp(attr->attr.name, "power_avdd_3v")) { + gps_power_3v_set(on); + } else if (!strcmp(attr->attr.name, "pwron")) { + gps_pwron_set(on); + } else if (!strcmp(attr->attr.name, "reset")) { + gps_rst_set(on); + } else if (!strcmp(attr->attr.name, "power_lp_io_3v3")) { + gps_power_3v3_set(on); + } else if (!strcmp(attr->attr.name, "power_pll_core_2v5")) { + gps_power_2v5_set(on); + } else if (!strcmp(attr->attr.name, "power_core_1v5") || + !strcmp(attr->attr.name, "power_vdd_core_1v5")) { + gps_power_1v5_set(on); + } + + return count; +} + +static void gps_power_sequence_up(void) +{ + /* According to PMB2520 Data Sheet, Rev. 2006-06-05, + * Chapter 4.2.2 */ + + /* nRESET must be asserted low */ + gps_rst_set(0); + + /* POWERON must be de-asserted (low) */ + gps_pwron_set(0); + + /* Apply VDD_IO and VDD_LPREG_IN */ + gps_power_3v3_set(1); + + /* VDD_COREREG_IN, VDD_PLLREG_IN */ + gps_power_1v5_set(1); + gps_power_2v5_set(1); + + /* and VDD_RF may be applied */ + gps_power_2v8_set(1); + + /* We need to enable AVDD, since in GTA01Bv3 it is + * shared with RFREG_IN */ + gps_power_3v_set(1); + + msleep(3); /* Is 3ms enough? */ + + /* De-asert nRESET */ + gps_rst_set(1); + + /* Switch power on */ + gps_pwron_set(1); + +} + +static void gps_power_sequence_down(void) +{ + /* According to PMB2520 Data Sheet, Rev. 2006-06-05, + * Chapter 4.2.3.1 */ + gps_pwron_set(0); + + /* Don't disable AVDD before PWRON is cleared, since + * in GTA01Bv3, AVDD and RFREG_IN are shared */ + gps_power_3v_set(0); + + /* Remove VDD_COREREG_IN, VDD_PLLREG_IN and VDD_REFREG_IN */ + gps_power_1v5_set(0); + gps_power_2v5_set(0); + gps_power_2v8_set(0); + + /* Remove VDD_LPREG_IN and VDD_IO */ + gps_power_3v3_set(0); +} + + +static ssize_t power_sequence_read(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return strlcpy(buf, "power_up power_down\n", PAGE_SIZE); +} + +static ssize_t power_sequence_write(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + dev_dbg(dev, "wrote: '%s'\n", buf); + + if (!strncmp(buf, "power_up", 8)) + gps_power_sequence_up(); + else if (!strncmp(buf, "power_down", 10)) + gps_power_sequence_down(); + else + return -EINVAL; + + return count; +} + +static DEVICE_ATTR(power_tcxo_2v8, 0644, power_gps_read, power_gps_write); +static DEVICE_ATTR(power_avdd_3v, 0644, power_gps_read, power_gps_write); +static DEVICE_ATTR(pwron, 0644, power_gps_read, power_gps_write); +static DEVICE_ATTR(reset, 0644, power_gps_read, power_gps_write); +static DEVICE_ATTR(power_lp_io_3v3, 0644, power_gps_read, power_gps_write); +static DEVICE_ATTR(power_pll_core_2v5, 0644, power_gps_read, power_gps_write); +static DEVICE_ATTR(power_core_1v5, 0644, power_gps_read, power_gps_write); +static DEVICE_ATTR(power_vdd_core_1v5, 0644, power_gps_read, power_gps_write); +static DEVICE_ATTR(power_sequence, 0644, power_sequence_read, + power_sequence_write); + +#ifdef CONFIG_PM +static int gta01_pm_gps_suspend(struct platform_device *pdev, + pm_message_t state) +{ + /* FIXME */ + gps_power_sequence_down(); + + return 0; +} + +static int gta01_pm_gps_resume(struct platform_device *pdev) +{ + /* FIXME */ + gps_power_sequence_up(); + + return 0; +} +#else +#define gta01_pm_gps_suspend NULL +#define gta01_pm_gps_resume NULL +#endif + +static struct attribute *gta01_gps_sysfs_entries[] = { + &dev_attr_power_avdd_3v.attr, + &dev_attr_pwron.attr, + &dev_attr_reset.attr, + &dev_attr_power_lp_io_3v3.attr, + &dev_attr_power_pll_core_2v5.attr, + &dev_attr_power_sequence.attr, + NULL, /* power_core_1v5 */ + NULL, /* power_vdd_core_1v5 */ + NULL /* terminating entry */ +}; + +static struct attribute_group gta01_gps_attr_group = { + .name = NULL, + .attrs = gta01_gps_sysfs_entries, +}; + +static int __init gta01_pm_gps_probe(struct platform_device *pdev) +{ + s3c2410_gpio_cfgpin(GTA01_GPIO_GPS_PWRON, S3C2410_GPIO_OUTPUT); + + switch (system_rev) { + case GTA01v3_SYSTEM_REV: + break; + case GTA01v4_SYSTEM_REV: + s3c2410_gpio_cfgpin(GTA01_GPIO_GPS_RESET, S3C2410_GPIO_OUTPUT); + break; + case GTA01Bv3_SYSTEM_REV: + case GTA01Bv4_SYSTEM_REV: + s3c2410_gpio_cfgpin(GTA01_GPIO_GPS_EN_3V3, S3C2410_GPIO_OUTPUT); + /* fallthrough */ + case GTA01Bv2_SYSTEM_REV: + s3c2410_gpio_cfgpin(GTA01_GPIO_GPS_EN_2V8, S3C2410_GPIO_OUTPUT); + s3c2410_gpio_cfgpin(GTA01_GPIO_GPS_EN_3V, S3C2410_GPIO_OUTPUT); + s3c2410_gpio_cfgpin(GTA01_GPIO_GPS_RESET, S3C2410_GPIO_OUTPUT); + break; + default: + dev_warn(&pdev->dev, "Unknown GTA01 Revision 0x%x, " + "AGPS PM features not available!!!\n", + system_rev); + return -1; + break; + } + + gps_power_sequence_down(); + + switch (system_rev) { + case GTA01v3_SYSTEM_REV: + case GTA01v4_SYSTEM_REV: + case GTA01Bv2_SYSTEM_REV: + gta01_gps_sysfs_entries[ARRAY_SIZE(gta01_gps_sysfs_entries)-3] = + &dev_attr_power_tcxo_2v8.attr; + break; + case GTA01Bv3_SYSTEM_REV: + case GTA01Bv4_SYSTEM_REV: + gta01_gps_sysfs_entries[ARRAY_SIZE(gta01_gps_sysfs_entries)-3] = + &dev_attr_power_core_1v5.attr; + gta01_gps_sysfs_entries[ARRAY_SIZE(gta01_gps_sysfs_entries)-2] = + &dev_attr_power_vdd_core_1v5.attr; + break; + } + + return sysfs_create_group(&pdev->dev.kobj, >a01_gps_attr_group); +} + +static int gta01_pm_gps_remove(struct platform_device *pdev) +{ + gps_power_sequence_down(); + sysfs_remove_group(&pdev->dev.kobj, >a01_gps_attr_group); + + return 0; +} + +static struct platform_driver gta01_pm_gps_driver = { + .probe = gta01_pm_gps_probe, + .remove = gta01_pm_gps_remove, + .suspend = gta01_pm_gps_suspend, + .resume = gta01_pm_gps_resume, + .driver = { + .name = "neo1973-pm-gps", + }, +}; + +static int __devinit gta01_pm_gps_init(void) +{ + return platform_driver_register(>a01_pm_gps_driver); +} + +static void gta01_pm_gps_exit(void) +{ + platform_driver_unregister(>a01_pm_gps_driver); +} + +module_init(gta01_pm_gps_init); +module_exit(gta01_pm_gps_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Harald Welte "); +MODULE_DESCRIPTION("FIC Neo1973 GPS Power Management"); diff --git a/arch/arm/plat-s3c24xx/neo1973_pm_gsm.c b/arch/arm/plat-s3c24xx/neo1973_pm_gsm.c new file mode 100644 index 0000000..a1615f8 --- /dev/null +++ b/arch/arm/plat-s3c24xx/neo1973_pm_gsm.c @@ -0,0 +1,217 @@ +/* + * GSM Management code for the FIC Neo1973 GSM Phone + * + * (C) 2007 by OpenMoko Inc. + * Author: Harald Welte + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +struct gta01pm_priv { + int gpio_ngsm_en; + struct console *con; +}; + +static struct gta01pm_priv gta01_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 (s3c2410_gpio_getpin(GTA01_GPIO_MODEM_ON)) + goto out_1; + } else if (!strcmp(attr->attr.name, "reset")) { + if (s3c2410_gpio_getpin(GTA01_GPIO_MODEM_RST)) + goto out_1; + } else if (!strcmp(attr->attr.name, "download")) { + if (s3c2410_gpio_getpin(GTA01_GPIO_MODEM_DNLOAD)) + goto out_1; + } + + return strlcpy(buf, "0\n", 3); +out_1: + return strlcpy(buf, "1\n", 3); +} + +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")) { + if (on) { + dev_info(dev, "powering up GSM, thus disconnecting " + "serial console\n"); + + if (gta01_gsm.con) + console_stop(gta01_gsm.con); + + if (gta01_gsm.gpio_ngsm_en) + s3c2410_gpio_setpin(gta01_gsm.gpio_ngsm_en, 0); + + s3c2410_gpio_setpin(GTA01_GPIO_MODEM_ON, 1); + } else { + s3c2410_gpio_setpin(GTA01_GPIO_MODEM_ON, 0); + + if (gta01_gsm.gpio_ngsm_en) + s3c2410_gpio_setpin(gta01_gsm.gpio_ngsm_en, 1); + + if (gta01_gsm.con) + console_start(gta01_gsm.con); + + dev_info(dev, "powered down GSM, thus enabling " + "serial console\n"); + } + } else if (!strcmp(attr->attr.name, "reset")) { + s3c2410_gpio_setpin(GTA01_GPIO_MODEM_RST, on); + } else if (!strcmp(attr->attr.name, "download")) { + s3c2410_gpio_setpin(GTA01_GPIO_MODEM_DNLOAD, on); + } + + 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); + +#ifdef CONFIG_PM +static int gta01_gsm_suspend(struct platform_device *pdev, pm_message_t state) +{ + /* GPIO state is saved/restored by S3C2410 core GPIO driver, so we + * don't need to do anything here */ + + return 0; +} + +static int gta01_gsm_resume(struct platform_device *pdev) +{ + /* GPIO state is saved/restored by S3C2410 core GPIO driver, so we + * don't need to do anything here */ + + /* Make sure that the kernel console on the serial port is still + * disabled. FIXME: resume ordering race with serial driver! */ + if (s3c2410_gpio_getpin(GTA01_GPIO_MODEM_ON) && gta01_gsm.con) + console_stop(gta01_gsm.con); + + return 0; +} +#else +#define gta01_gsm_suspend NULL +#define gta01_gsm_resume NULL +#endif + +static struct attribute *gta01_gsm_sysfs_entries[] = { + &dev_attr_power_on.attr, + &dev_attr_reset.attr, + NULL, + NULL +}; + +static struct attribute_group gta01_gsm_attr_group = { + .name = NULL, + .attrs = gta01_gsm_sysfs_entries, +}; + +static int __init gta01_gsm_probe(struct platform_device *pdev) +{ + switch (system_rev) { + case GTA01v3_SYSTEM_REV: + gta01_gsm.gpio_ngsm_en = GTA01v3_GPIO_nGSM_EN; + break; + case GTA01v4_SYSTEM_REV: + gta01_gsm.gpio_ngsm_en = 0; + break; + case GTA01Bv2_SYSTEM_REV: + case GTA01Bv3_SYSTEM_REV: + case GTA01Bv4_SYSTEM_REV: + gta01_gsm.gpio_ngsm_en = GTA01Bv2_GPIO_nGSM_EN; + s3c2410_gpio_setpin(GTA01v3_GPIO_nGSM_EN, 0); + break; + default: + dev_warn(&pdev->dev, "Unknown GTA01 Revision 0x%x, " + "some PM features not available!!!\n", + system_rev); + break; + } + + switch (system_rev) { + case GTA01v4_SYSTEM_REV: + case GTA01Bv2_SYSTEM_REV: + gta01_gsm_sysfs_entries[ARRAY_SIZE(gta01_gsm_sysfs_entries)-2] = + &dev_attr_download.attr; + break; + default: + break; + } + + gta01_gsm.con = find_s3c24xx_console(); + if (!gta01_gsm.con) + dev_warn(&pdev->dev, "cannot find S3C24xx console driver\n"); + + return sysfs_create_group(&pdev->dev.kobj, >a01_gsm_attr_group); +} + +static int gta01_gsm_remove(struct platform_device *pdev) +{ + sysfs_remove_group(&pdev->dev.kobj, >a01_gsm_attr_group); + + return 0; +} + +static struct platform_driver gta01_gsm_driver = { + .probe = gta01_gsm_probe, + .remove = gta01_gsm_remove, + .suspend = gta01_gsm_suspend, + .resume = gta01_gsm_resume, + .driver = { + .name = "neo1973-pm-gsm", + }, +}; + +static int __devinit gta01_gsm_init(void) +{ + return platform_driver_register(>a01_gsm_driver); +} + +static void gta01_gsm_exit(void) +{ + platform_driver_unregister(>a01_gsm_driver); +} + +module_init(gta01_gsm_init); +module_exit(gta01_gsm_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Harald Welte "); +MODULE_DESCRIPTION("FIC Neo1973 GSM Power Management"); -- 1.5.6.3