diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 0108c2af005b92..94dad4a1dc40b8 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -75,6 +75,12 @@ config BATTERY_88PM860X help Say Y here to enable battery monitor for Marvell 88PM860x chip. +config RPI_POWER_SWITCH + tristate "Raspberry Pi GPIO power switch" + depends on MACH_BCM2708 || MACH_BCM2709 || ARCH_BCM2835 + help + Use a GPIO as a pseudo power switch. + config BATTERY_DS2760 tristate "DS2760 battery driver (HP iPAQ & others)" depends on W1 && W1_SLAVE_DS2760 diff --git a/drivers/power/Makefile b/drivers/power/Makefile index dfa89427392636..d18fdd3f18a712 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -14,6 +14,7 @@ obj-$(CONFIG_WM831X_BACKUP) += wm831x_backup.o obj-$(CONFIG_WM831X_POWER) += wm831x_power.o obj-$(CONFIG_WM8350_POWER) += wm8350_power.o obj-$(CONFIG_TEST_POWER) += test_power.o +obj-$(CONFIG_RPI_POWER_SWITCH) += rpi_power_switch.o obj-$(CONFIG_BATTERY_88PM860X) += 88pm860x_battery.o obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o diff --git a/drivers/power/rpi_power_switch.c b/drivers/power/rpi_power_switch.c new file mode 100644 index 00000000000000..bacf1de0dd6071 --- /dev/null +++ b/drivers/power/rpi_power_switch.c @@ -0,0 +1,439 @@ +/* + * Adafruit power switch driver for Raspberry Pi + * + * Written by Sean Cross for Adafruit Industries + * + * Copyright (C) 2014 Adafruit Industries (www.adafruit.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#define RPI_POWER_SWITCH_VERSION "1.7" +#define POWER_SWITCH_CLASS_NAME "rpi-power-switch" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* the BCM2709 redefines this for us right! +#define BCM2708_PERI_BASE 0x20000000 +*/ +#define GPIO_BASE (BCM2708_PERI_BASE + 0x200000) + +#define GPPUD (gpio_reg+0x94) +#define GPPUDCLK0 (gpio_reg+0x98) +#define GPPUDCLK1 (gpio_reg+0x9C) +#define GPSET0 (gpio_reg+0x1c) +#define GPSET1 (gpio_reg+0x20) +#define GPCLR0 (gpio_reg+0x28) +#define GPCLR1 (gpio_reg+0x2c) + +#define GPIO_REG(g) (gpio_reg+((g/10)*4)) +#define SET_GPIO_OUTPUT(g) \ + __raw_writel( \ + (1<<(((g)%10)*3)) \ + | (__raw_readl(GPIO_REG(g)) & (~(7<<(((g)%10)*3)))), \ + GPIO_REG(g)) +#define SET_GPIO_INPUT(g) \ + __raw_writel( \ + 0 \ + | (__raw_readl(GPIO_REG(g)) & (~(7<<(((g)%10)*3)))), \ + GPIO_REG(g)) +#define SET_GPIO_ALT(g,a) \ + __raw_writel( \ + (((a)<=3?(a)+4:(a)==4?3:2)<<(((g)%10)*3)) \ + | (__raw_readl(GPIO_REG(g)) & (~(7<<(((g)%10)*3)))), \ + GPIO_REG(g)) + +enum button_mode { + MODE_BUTTON = 0, + MODE_SWITCH = 1, +}; + +enum gpio_pull_direction { + GPIO_PULL_NONE = 0, + GPIO_PULL_DOWN = 1, + GPIO_PULL_UP = 2, +}; + +/* Module Parameters */ +static int gpio_pin = 22; +static int mode = MODE_SWITCH; +static int led_pin = 16; + +/* This is the base state. When this changes, do a shutdown. */ +static int gpio_pol; + +static void __iomem *gpio_reg; +static void (*old_pm_power_off)(void); +static struct device *switch_dev; +static int raw_gpio = 0; + +/* + * Attach either a pull up or pull down to the specified GPIO pin. Or + * clear any pull on the pin, if requested. + */ +static int set_gpio_pull(int gpio, enum gpio_pull_direction direction) +{ + long *bank; + int pin; + + bank = ((gpio&(~31))?GPPUDCLK1:GPPUDCLK0); + pin = gpio & 31; + + /* Set the direction (involves two writes and a clock wait) */ + __raw_writel(direction, GPPUD); + udelay(20); + __raw_writel(1< 63) + return -1; + else if (gpio < 32) + __raw_writel(1< 100) + duty = 100; + low = duty; + high = 100-duty; + + if (raw_gpio) + raw_gpio_set(gpio, 0); + else + gpio_set_value(gpio, 0); + udelay(RATE*low); + + if (raw_gpio) + raw_gpio_set(gpio, 1); + else + gpio_set_value(gpio, 1); + udelay(RATE*high); + + return (RATE*low)+(RATE*high); +} + +/* + * Give an indication that it's safe to turn off the board. Pulse the LED + * in a kind of "breathing" pattern, so the user knows that it's + * "powered down". + */ +static int do_breathing_forever(int gpio) +{ + int err; + err = gpio_request(gpio, "LED light"); + if (err < 0) { + pr_err("Unable to request GPIO, switching to raw access"); + raw_gpio = 1; + } + SET_GPIO_OUTPUT(gpio); + + while (1) { + int usecs; + /* We want four seconds: + * - One second of ramp-up + * - One second of ramp-down + * - Two seconds of low + */ + for (usecs=0; usecs < 800000; ) + usecs += gpio_pulse(gpio, ((usecs*9)/80000)+10); + + for (usecs=0; usecs < 800000; ) + usecs += gpio_pulse(gpio, 100-((usecs*9)/80000)); + + for (usecs=0; usecs < 800000; ) + usecs += gpio_pulse(gpio, 10); + + for (usecs=0; usecs < 800000; ) + usecs += gpio_pulse(gpio, 10); + } + return 0; +} + +/* + * Our shutdown function. Execution will stay here until the switch is + * flipped. + * + * NOTE: The default power_off function sends a message to the GPU via + * a mailbox message to shut down most parts of the core. Since we don't + * have any documentation on the mailbox message formats, we will leave + * the CPU powered up here but not executing any code in order to simulate + * an "off" state. + */ +static void rpi_power_switch_power_off(void) +{ + int ret; + pr_info("Waiting for the switch to be flipped back...\n"); + if (mode == MODE_SWITCH) + gpio_pol = !gpio_pol; + ret = request_irq(gpio_to_irq(gpio_pin), reboot_isr, + gpio_pol?IRQF_TRIGGER_RISING:IRQF_TRIGGER_FALLING, + "Reboot ISR", NULL); + + /* + * If it's taken us so long to reboot that the switch was flipped, + * immediately reboot. + */ + if (gpio_pol == gpio_get_value(gpio_pin)) + reboot_isr(0, NULL); + + do_breathing_forever(led_pin); + return; +} + +static irqreturn_t power_isr(int irqno, void *param) +{ + schedule_delayed_work(&initiate_shutdown_work, msecs_to_jiffies(100)); + return IRQ_HANDLED; +} + +/* Sysfs entry */ + +static ssize_t do_shutdown_show(struct device *d, + struct device_attribute *attr, char *buf) +{ + ssize_t ret; + ret = sprintf(buf, "Write into this file to initiate a shutdown\n"); + return ret; +} + +static ssize_t do_shutdown_store(struct device *d, + struct device_attribute *attr, const char *buf, size_t count) +{ + if (mode == MODE_SWITCH) + gpio_pol = !gpio_pol; + schedule_delayed_work(&initiate_shutdown_work, msecs_to_jiffies(10)); + return count; +} +static DEVICE_ATTR(do_shutdown, 0660, do_shutdown_show, do_shutdown_store); + +static struct attribute *rpi_power_switch_sysfs_entries[] = { + &dev_attr_do_shutdown.attr, + NULL, +}; + +static struct attribute_group rpi_power_switch_attribute_group = { + .name = NULL, + .attrs = rpi_power_switch_sysfs_entries, +}; + +static struct class power_switch_class = { + .name = POWER_SWITCH_CLASS_NAME, + .owner = THIS_MODULE, +}; + +/* Main module entry point */ +int __init rpi_power_switch_init(void) +{ + int ret = 0; + + old_pm_power_off = pm_power_off; + pm_power_off = rpi_power_switch_power_off; + + pr_info("Adafruit Industries' power switch driver v%s\n", + RPI_POWER_SWITCH_VERSION); + + INIT_DELAYED_WORK(&initiate_shutdown_work, initiate_shutdown); + + /* Register our own class for the power switch */ + ret = class_register(&power_switch_class); + if (ret < 0) { + pr_err("%s: Unable to register class\n", power_switch_class.name); + goto out0; + } + + /* Create devices for each PWM present */ + switch_dev = device_create(&power_switch_class, &platform_bus, + MKDEV(0, 0), NULL, "pswitch%u", 0); + if (IS_ERR(switch_dev)) { + pr_err("%s: device_create failed\n", power_switch_class.name); + ret = PTR_ERR(switch_dev); + goto out1; + } + + ret = sysfs_create_group(&switch_dev->kobj, + &rpi_power_switch_attribute_group); + if (ret < 0) { + pr_err("%s: create_group failed\n", power_switch_class.name); + goto out2; + } + + /* + * GPIO register memory must be mapped before doing any direct + * accesses such as changing GPIO alt functions or changing GPIO + * pull ups or pull downs. + */ + gpio_reg = ioremap(GPIO_BASE, 1024); + + /* Set the specified pin as a GPIO input */ + SET_GPIO_INPUT(gpio_pin); + + /* + * Set the pin as a pulldown. Most pins should default to having + * pulldowns, and this seems most intuitive. + */ + set_gpio_pull(gpio_pin, GPIO_PULL_UP); + + ret = gpio_request(gpio_pin, "Power switch"); + if (ret) { + printk(KERN_ALERT "GPIO request failure: %d\n", ret); + goto out3; + } + + gpio_direction_input(gpio_pin); + + /* + * The targeted polarity should be the opposite of the current value. + * I.e. we want the pin to transition to this state in order to + * initiate a shutdown. + */ + gpio_pol = !gpio_get_value(gpio_pin); + + /* + * Request an interrupt to fire when the pin transitions to our + * desired state. + */ + ret = request_irq(__gpio_to_irq(gpio_pin), power_isr, + gpio_pol?IRQF_TRIGGER_RISING:IRQF_TRIGGER_FALLING, + "Power button", NULL); + if (ret) { + pr_err("Unable to request IRQ\n"); + goto out3; + } + + return 0; + + /* Error handling */ +out3: + sysfs_remove_group(&switch_dev->kobj,&rpi_power_switch_attribute_group); +out2: + device_unregister(switch_dev); +out1: + class_unregister(&power_switch_class); +out0: + iounmap(gpio_reg); + pm_power_off = old_pm_power_off; + return ret; +} + +/* Main module exit point (called at unload) */ +void __exit rpi_power_switch_cleanup(void) +{ + sysfs_remove_group(&switch_dev->kobj,&rpi_power_switch_attribute_group); + device_unregister(switch_dev); + free_irq(__gpio_to_irq(gpio_pin), NULL); + gpio_free(gpio_pin); + pm_power_off = old_pm_power_off; + class_unregister(&power_switch_class); + iounmap(gpio_reg); +} + +module_init(rpi_power_switch_init); +module_exit(rpi_power_switch_cleanup); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Sean Cross for Adafruit Industries "); +MODULE_ALIAS("platform:bcm2708_power_switch"); +module_param(gpio_pin, int, 0); +MODULE_PARM_DESC(gpio_pin, "GPIO pin number of the BCM processor for shutdown switch (default 23)"); +module_param(led_pin, int, 0); +MODULE_PARM_DESC(led_pin, "Pin for LED to pulse after shutdown (default 16)"); +module_param(mode, int, 0); +MODULE_PARM_DESC(mode, "Shutdown switch mode (0 for button, 1 for switch)");