From c2742ff04d32db3d287c0bc4f7ab090fc957ee49 Mon Sep 17 00:00:00 2001
From: Lars-Peter Clausen <lars@metafoo.de>
Date: Mon, 17 May 2010 20:25:20 +0200
Subject: [PATCH 24/69] gta02: Add gsm power management device

---
 arch/arm/mach-s3c2440/Makefile       |    3 +-
 arch/arm/mach-s3c2440/gta02-pm-gsm.c |  368 ++++++++++++++++++++++++++++++++++
 arch/arm/mach-s3c2440/mach-gta02.c   |   56 +++++
 3 files changed, 426 insertions(+), 1 deletions(-)
 create mode 100644 arch/arm/mach-s3c2440/gta02-pm-gsm.c

diff --git a/arch/arm/mach-s3c2440/Makefile b/arch/arm/mach-s3c2440/Makefile
index 062c339..8264c36 100644
--- a/arch/arm/mach-s3c2440/Makefile
+++ b/arch/arm/mach-s3c2440/Makefile
@@ -33,10 +33,11 @@ obj-$(CONFIG_ARCH_S3C2440)	+= mach-smdk2440.o
 obj-$(CONFIG_MACH_NEXCODER_2440) += mach-nexcoder.o
 obj-$(CONFIG_MACH_AT2440EVB) += mach-at2440evb.o
 obj-$(CONFIG_MACH_MINI2440) += mach-mini2440.o
+obj-$(CONFIG_MACH_RX1950) += mach-rx1950.o
 obj-$(CONFIG_MACH_NEO1973_GTA02) += mach-gta02.o \
 	gta02-pm-bt.o \
 	gta02-pm-gps.o \
-obj-$(CONFIG_MACH_RX1950) += mach-rx1950.o
+	gta02-pm-gsm.o \
 
 # extra machine support
 
diff --git a/arch/arm/mach-s3c2440/gta02-pm-gsm.c b/arch/arm/mach-s3c2440/gta02-pm-gsm.c
new file mode 100644
index 0000000..a2b4e91
--- /dev/null
+++ b/arch/arm/mach-s3c2440/gta02-pm-gsm.c
@@ -0,0 +1,368 @@
+/*
+ * 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 <linux/err.h>
+#include <linux/regulator/consumer.h>
+
+#include <linux/gpio.h>
+#include <mach/gpio.h>
+#include <asm/mach-types.h>
+
+#include <mach/hardware.h>
+
+#include <mach/gta02.h>
+#include <mach/regs-gpio.h>
+#include <mach/regs-gpioj.h>
+
+static int gta02_gsm_interrupts;
+
+extern void s3c24xx_serial_console_set_silence(int);
+
+struct gta02pm_priv {
+	int gpio_ndl_gsm;
+	struct console *con;
+	struct regulator *regulator;
+};
+
+static struct gta02pm_priv gta02_gsm;
+
+static struct console *find_s3c24xx_console(void)
+{
+	struct console *con;
+
+	console_lock();
+
+	for (con = console_drivers; con; con = con->next) {
+		if (!strcmp(con->name, "ttySAC"))
+			break;
+	}
+
+	console_unlock();
+
+	return con;
+}
+
+static ssize_t gsm_read(struct device *dev, struct device_attribute *attr,
+			char *buf)
+{
+	if (!strcmp(attr->attr.name, "power_on")) {
+		if (regulator_is_enabled(gta02_gsm.regulator))
+			goto out_1;
+	} else if (!strcmp(attr->attr.name, "download")) {
+		if (!gpio_get_value(GTA02_GPIO_nDL_GSM))
+			goto out_1;
+	} else if (!strcmp(attr->attr.name, "flowcontrolled")) {
+		if (s3c_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, bool on)
+{
+	if (on == regulator_is_enabled(gta02_gsm.regulator))
+		return;
+
+	if (!on) {
+		s3c_gpio_cfgpin(S3C2410_GPH(1), S3C2410_GPIO_INPUT);
+		s3c_gpio_cfgpin(S3C2410_GPH(2), S3C2410_GPIO_INPUT);
+
+		regulator_disable(gta02_gsm.regulator);
+
+		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 */
+	s3c_gpio_cfgpin(S3C2410_GPH(1), S3C2410_GPH1_nRTS0);
+	s3c_gpio_cfgpin(S3C2410_GPH(2), S3C2410_GPH2_TXD0);
+
+	regulator_enable(gta02_gsm.regulator);
+
+	msleep(100);
+
+	gpio_set_value(GTA02_GPIO_MODEM_ON, 1);
+	msleep(500);
+	gpio_set_value(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);
+	s3c_gpio_cfgpin(S3C2410_GPH(1), S3C2410_GPIO_OUTPUT);
+	msleep(1000);
+	s3c_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)
+{
+	int ret;
+	unsigned long on;
+
+	ret = strict_strtoul(buf, 10, &on);
+	if (ret)
+		return ret;
+
+	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;
+		gpio_set_value(GTA02_GPIO_nDL_GSM, !on);
+
+		return count;
+	}
+
+	if (!strcmp(attr->attr.name, "flowcontrolled")) {
+		if (on) {
+			gta02_gsm_interrupts = 0;
+			gpio_set_value(S3C2410_GPH(1), 1);
+			s3c_gpio_cfgpin(S3C2410_GPH(1), S3C2410_GPIO_OUTPUT);
+		} else
+			s3c_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 (s3c_gpio_getcfg(S3C2410_GPH(1)) == S3C2410_GPIO_OUTPUT) {
+		if (gta02_gsm_interrupts)
+			goto busy;
+	}
+
+	/* disable DL GSM to prevent jack_insert becoming 'floating' */
+	gpio_set_value(GTA02_GPIO_nDL_GSM, 1);
+
+	if (device_may_wakeup(dev))
+		enable_irq_wake(GTA02_IRQ_MODEM);
+
+	return 0;
+
+busy:
+	return -EBUSY;
+}
+
+static int gta02_gsm_suspend_late(struct device *dev)
+{
+	/* Last chance: abort if GSM already interrupted */
+	if (s3c_gpio_getcfg(S3C2410_GPH(1)) == S3C2410_GPIO_OUTPUT) {
+		if (gta02_gsm_interrupts)
+			return -EBUSY;
+	}
+	return 0;
+}
+
+static int gta02_gsm_resume(struct device *dev)
+{
+	if (device_may_wakeup(dev))
+		disable_irq_wake(GTA02_IRQ_MODEM);
+
+	/* 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 && gpio_get_value(GTA02_GPIO_MODEM_ON))
+		console_stop(gta02_gsm.con);
+
+	gpio_set_value(GTA02_GPIO_nDL_GSM, gta02_gsm.gpio_ndl_gsm);
+
+	return 0;
+}
+
+static const 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 (&gta02_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 irqreturn_t gta02_gsm_irq(int irq, void *devid)
+{
+	gta02_gsm_interrupts++;
+	return IRQ_HANDLED;
+}
+
+static int __devinit gta02_gsm_probe(struct platform_device *pdev)
+{
+	int ret;
+
+	gta02_gsm.con = find_s3c24xx_console();
+	if (!gta02_gsm.con)
+		dev_warn(&pdev->dev,
+			 "cannot find S3C24xx console driver\n");
+
+	gta02_gsm.regulator = regulator_get_exclusive(&pdev->dev, "GSM");
+
+	if (IS_ERR(gta02_gsm.regulator)) {
+		ret = PTR_ERR(gta02_gsm.regulator);
+		dev_err(&pdev->dev, "Failed to get regulator: %d\n", ret);
+		return ret;
+	}
+
+	ret = request_irq(GTA02_IRQ_MODEM, gta02_gsm_irq,
+			IRQF_DISABLED | IRQF_TRIGGER_RISING, "modem", NULL);
+
+	if (ret) {
+		regulator_put(gta02_gsm.regulator);
+		dev_err(&pdev->dev, "Failed to get modem irq: %d\n", ret);
+		goto err_regulator_put;
+	}
+
+	ret = sysfs_create_group(&pdev->dev.kobj, &gta02_gsm_attr_group);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to create sysfs entries: %d\n",
+			ret);
+		goto err_free_irq;
+	}
+
+	device_init_wakeup(&pdev->dev, 1);
+
+	/* note that download initially disabled, and enforce that */
+	gta02_gsm.gpio_ndl_gsm = 1;
+	gpio_request(GTA02_GPIO_nDL_GSM, "GSM nDL");
+	gpio_direction_output(GTA02_GPIO_nDL_GSM, 1);
+	gpio_request(GTA02_GPIO_MODEM_ON, "GSM modem enable");
+	gpio_direction_output(GTA02_GPIO_MODEM_ON, 0);
+
+	/* GSM is to be initially off (at boot, or if this module inserted) */
+	gsm_on_off(&pdev->dev, 0);
+
+	return 0;
+err_free_irq:
+	free_irq(GTA02_IRQ_MODEM, NULL);
+err_regulator_put:
+	regulator_put(gta02_gsm.regulator);
+
+	return ret;
+}
+
+static int __devexit gta02_gsm_remove(struct platform_device *pdev)
+{
+	gsm_on_off(&pdev->dev, 0);
+
+	sysfs_remove_group(&pdev->dev.kobj, &gta02_gsm_attr_group);
+	free_irq(GTA02_IRQ_MODEM, NULL);
+	regulator_put(gta02_gsm.regulator);
+
+	return 0;
+}
+
+static struct platform_driver gta02_gsm_driver = {
+	.probe		= gta02_gsm_probe,
+	.remove		= __devexit_p(gta02_gsm_remove),
+	.driver		= {
+		.name	= "gta02-pm-gsm",
+		.pm	= GTA02_GSM_PM_OPS,
+	},
+};
+
+static int __init gta02_gsm_init(void)
+{
+	return platform_driver_register(&gta02_gsm_driver);
+}
+module_init(gta02_gsm_init);
+
+static void __exit gta02_gsm_exit(void)
+{
+	platform_driver_unregister(&gta02_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-s3c2440/mach-gta02.c b/arch/arm/mach-s3c2440/mach-gta02.c
index 5a0c57c..fa61963 100644
--- a/arch/arm/mach-s3c2440/mach-gta02.c
+++ b/arch/arm/mach-s3c2440/mach-gta02.c
@@ -50,6 +50,7 @@
 
 #include <linux/i2c.h>
 #include <linux/regulator/machine.h>
+#include <linux/regulator/fixed.h>
 
 #include <linux/mfd/pcf50633/core.h>
 #include <linux/mfd/pcf50633/mbc.h>
@@ -160,6 +161,43 @@ static struct platform_device gta02_pm_gps_dev = {
 	.name = "gta02-pm-gps",
 };
 
+static struct platform_device gta02_pm_gsm_dev = {
+	.name = "gta02-pm-gsm",
+};
+
+static struct regulator_consumer_supply gsm_supply_consumer = {
+	.dev = &gta02_pm_gsm_dev.dev,
+	.supply = "GSM",
+};
+
+static struct regulator_init_data gsm_supply_init_data = {
+	.constraints = {
+		.min_uV = 3700000,
+		.max_uV = 3700000,
+		.valid_modes_mask = REGULATOR_MODE_NORMAL,
+		.valid_ops_mask = REGULATOR_CHANGE_STATUS,
+	},
+	.num_consumer_supplies = 1,
+	.consumer_supplies = &gsm_supply_consumer,
+};
+
+static struct fixed_voltage_config gsm_supply_config = {
+	.supply_name = "GSM",
+	.microvolts = 3700000,
+	.gpio = GTA02_GPIO_PCF(PCF50633_GPIO2),
+	.enable_high = 1,
+	.init_data = &gsm_supply_init_data,
+};
+
+static struct platform_device gta02_gsm_supply_device = {
+	.name = "reg-fixed-voltage",
+	.id = 1,
+	.dev = {
+		.platform_data = &gsm_supply_config,
+	},
+};
+
+
 #ifdef CONFIG_CHARGER_PCF50633
 /*
  * On GTA02 the 1A charger features a 48K resistor to 0V on the ID pin.
@@ -705,7 +743,25 @@ struct gta02_device_children {
 	void (*probed_callback)(struct device *dev);
 };
 
+static struct platform_device *gta02_pcf50633_gpio_children[] = {
+	&gta02_gsm_supply_device,
+};
+
+static struct platform_device *gta02_gsm_supply_children[] = {
+	&gta02_pm_gsm_dev,
+};
+
 static struct gta02_device_children gta02_device_children[] = {
+	{
+		.dev_name = "pcf50633-gpio",
+		.num_children = 1,
+		.children = gta02_pcf50633_gpio_children,
+	},
+	{
+		.dev_name = "reg-fixed-voltage.1",
+		.num_children = 1,
+		.children = gta02_gsm_supply_children,
+	}
 };
 
 static int gta02_add_child_devices(struct device *parent,
-- 
1.7.2.5

