diff options
author | Lars-Peter Clausen <lars@metafoo.de> | 2010-05-17 20:19:36 +0200 |
---|---|---|
committer | Lars-Peter Clausen <lars@metafoo.de> | 2010-05-17 20:59:38 +0200 |
commit | caae218ff71120df2a724cca20c357a4658f41fa (patch) | |
tree | b928588abece4dde590fb1d6c611ebc2ed267f0e | |
parent | b27e3d74ea6afd91cd4763bca94cd19f4bedb5fc (diff) |
gta02: Add bt power management device
-rw-r--r-- | arch/arm/mach-s3c2440/Makefile | 3 | ||||
-rw-r--r-- | arch/arm/mach-s3c2440/gta02-pm-bt.c | 259 | ||||
-rw-r--r-- | arch/arm/mach-s3c2440/mach-gta02.c | 14 |
3 files changed, 275 insertions, 1 deletions
diff --git a/arch/arm/mach-s3c2440/Makefile b/arch/arm/mach-s3c2440/Makefile index c85ba32d895..dc4665a6d18 100644 --- a/arch/arm/mach-s3c2440/Makefile +++ b/arch/arm/mach-s3c2440/Makefile @@ -33,7 +33,8 @@ 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_NEO1973_GTA02) += mach-gta02.o +obj-$(CONFIG_MACH_NEO1973_GTA02) += mach-gta02.o \ + gta02-pm-bt.o \ # extra machine support diff --git a/arch/arm/mach-s3c2440/gta02-pm-bt.c b/arch/arm/mach-s3c2440/gta02-pm-bt.c new file mode 100644 index 00000000000..5cf35d8917e --- /dev/null +++ b/arch/arm/mach-s3c2440/gta02-pm-bt.c @@ -0,0 +1,259 @@ +/* + * 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/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/rfkill.h> +#include <linux/slab.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/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-s3c2440/mach-gta02.c b/arch/arm/mach-s3c2440/mach-gta02.c index f28fa8f3b15..8eae7d8599e 100644 --- a/arch/arm/mach-s3c2440/mach-gta02.c +++ b/arch/arm/mach-s3c2440/mach-gta02.c @@ -156,6 +156,10 @@ static struct s3c2410_uartcfg gta02_uartcfgs[] = { }, }; +static struct platform_device gta02_pm_bt_dev = { + .name = "gta02-pm-bt", +}; + #ifdef CONFIG_CHARGER_PCF50633 /* * On GTA02 the 1A charger features a 48K resistor to 0V on the ID pin. @@ -261,6 +265,13 @@ static char *gta02_batteries[] = { "battery", }; +static struct regulator_consumer_supply ldo4_consumers[] = { + { + .dev = >a02_pm_bt_dev.dev, + .supply = "BT_3V2", + }, +}; + struct pcf50633_platform_data gta02_pcf_pdata = { .resumers = { [0] = PCF50633_INT1_USBINS | @@ -359,6 +370,8 @@ struct pcf50633_platform_data gta02_pcf_pdata = { .valid_ops_mask = REGULATOR_CHANGE_STATUS, .apply_uV = 1, }, + .num_consumer_supplies = ARRAY_SIZE(ldo4_consumers), + .consumer_supplies = ldo4_consumers, }, [PCF50633_REGULATOR_LDO5] = { .constraints = { @@ -716,6 +729,7 @@ static struct platform_device *gta02_devices[] __initdata = { >a02_buttons_device, >a02_leds_device, >a02_pwm_leds_device, + >a02_pm_bt_dev, }; /* These guys DO need to be children of PMU. */ |