| /* |
| * Marvell 88PM80x ONKEY driver |
| * |
| * Copyright (C) 2012 Marvell International Ltd. |
| * Haojian Zhuang <haojian.zhuang@marvell.com> |
| * Qiao Zhou <zhouqiao@marvell.com> |
| * |
| * This file is subject to the terms and conditions of the GNU General |
| * Public License. See the file "COPYING" in the main directory of this |
| * archive for more details. |
| * |
| * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/input.h> |
| #include <linux/mfd/88pm80x.h> |
| #include <linux/regmap.h> |
| #include <linux/slab.h> |
| #include <linux/of.h> |
| #include <linux/irq.h> |
| |
| #define PM800_LONG_ONKEY_EN1 (1 << 0) |
| #define PM800_LONG_ONKEY_EN2 (1 << 1) |
| #define PM800_LONG_KEY_DELAY (12) /* 1 .. 16 seconds */ |
| #define PM800_LONKEY_PRESS_TIME ((PM800_LONG_KEY_DELAY-1) << 4) |
| #define PM860_LONKEY_RESTOUTN_PULSE_MASK (0x3) |
| |
| #define PM800_LONKEY_PRESS_TIME_MASK (0xF0) |
| #define PM800_LONKEY_RESET (1 << 3) |
| #define PM800_SW_PDOWN (1 << 5) |
| |
| #define PM860_ALWAYS_ON2 (0xe2) |
| #define PM860_HWRST_DB_MASK (0x1 << 7) |
| #define PM860_HWRST_DB_2S (0x0 << 7) |
| #define PM860_HWRST_DB_7S (0x1 << 7) |
| |
| struct pm80x_onkey_info { |
| struct input_dev *idev; |
| struct device *dev; |
| struct pm80x_chip *pm80x; |
| struct regmap *map; |
| int irq; |
| int gpio_number; |
| int hw_rst; |
| int long_onkey_rst; |
| int long_onkey_timeout; |
| int disable_long_onkey; |
| int disable_onkey_panic; |
| }; |
| |
| static int pm80x_config_gpio(struct pm80x_onkey_info *info) |
| { |
| u8 ctrl_reg, ctrl_mask, ctrl_val; |
| |
| if (!info || !info->map) { |
| pr_err("%s: No chip information!\n", __func__); |
| return -ENODEV; |
| } |
| |
| /* set debounce period of ONKEY as 7s, when used with GPIO */ |
| regmap_update_bits(info->map, PM860_ALWAYS_ON2, |
| PM860_HWRST_DB_MASK, PM860_HWRST_DB_7S); |
| |
| if (info->pm80x->chip_id == CHIP_PM86X_ID_Z3) { |
| switch (info->gpio_number) { |
| case 0: |
| /* HW_RST1_N and HW_RST2_N */ |
| ctrl_reg = PM800_GPIO_0_1_CNTRL; |
| ctrl_mask = PM860_GPIO0_MODE_MASK; |
| if (info->hw_rst == 1) |
| ctrl_val = PM800_GPIO0_GPIO_MODE(0x6); |
| else |
| ctrl_val = PM800_GPIO0_GPIO_MODE(0x7); |
| break; |
| case 1: |
| /* HW_RST1_N and HW_RST2_N */ |
| ctrl_reg = PM800_GPIO_0_1_CNTRL; |
| ctrl_mask = PM860_GPIO1_MODE_MASK; |
| if (info->hw_rst == 1) |
| ctrl_val = PM800_GPIO1_GPIO_MODE(0x6); |
| else |
| ctrl_val = PM800_GPIO1_GPIO_MODE(0x7); |
| break; |
| case 2: |
| /* HW_RST1 and HW_RST2 */ |
| ctrl_reg = PM800_GPIO_2_3_CNTRL; |
| ctrl_mask = PM860_GPIO2_MODE_MASK; |
| if (info->hw_rst == 1) |
| ctrl_val = PM800_GPIO2_GPIO_MODE(0x6); |
| else |
| ctrl_val = PM800_GPIO2_GPIO_MODE(0x7); |
| break; |
| case 5: |
| /* HW_RST1 and HW_RST2 */ |
| ctrl_reg = PM800_GPIO_4_5_CNTRL; |
| ctrl_mask = PM860_GPIO5_MODE_MASK; |
| if (info->hw_rst == 1) |
| ctrl_val = PM860_GPIO5_GPIO_MODE(0x6); |
| else |
| ctrl_val = PM860_GPIO5_GPIO_MODE(0x7); |
| break; |
| default: |
| return -EINVAL; |
| } |
| } else if (info->pm80x->chip_id == CHIP_PM86X_ID_A0) { |
| switch (info->gpio_number) { |
| case 0: |
| /* HW_RST1_N and HW_RST2_N */ |
| ctrl_reg = PM800_GPIO_0_1_CNTRL; |
| ctrl_mask = PM860_GPIO0_MODE_MASK; |
| if (info->hw_rst == 1) |
| ctrl_val = PM800_GPIO0_GPIO_MODE(0x6); |
| else |
| ctrl_val = PM800_GPIO0_GPIO_MODE(0x7); |
| break; |
| case 1: |
| /* HW_RST1_N and HW_RST2_N */ |
| ctrl_reg = PM800_GPIO_0_1_CNTRL; |
| ctrl_mask = PM860_GPIO1_MODE_MASK; |
| if (info->hw_rst == 1) |
| ctrl_val = PM800_GPIO1_GPIO_MODE(0x6); |
| else |
| ctrl_val = PM800_GPIO1_GPIO_MODE(0x7); |
| break; |
| case 2: |
| /* HW_RST1 and HW_RST2 */ |
| ctrl_reg = PM800_GPIO_2_3_CNTRL; |
| ctrl_mask = PM860_GPIO2_MODE_MASK; |
| if (info->hw_rst == 1) |
| ctrl_val = PM800_GPIO2_GPIO_MODE(0x6); |
| else |
| ctrl_val = PM800_GPIO2_GPIO_MODE(0x7); |
| break; |
| case 3: |
| /* HW_RST1 and HW_RST2 */ |
| ctrl_reg = PM800_GPIO_2_3_CNTRL; |
| ctrl_mask = PM860_GPIO3_MODE_MASK; |
| if (info->hw_rst == 1) |
| ctrl_val = PM800_GPIO3_GPIO_MODE(0x6); |
| else |
| ctrl_val = PM800_GPIO3_GPIO_MODE(0x7); |
| break; |
| default: |
| return -EINVAL; |
| } |
| } else |
| return -EINVAL; |
| |
| regmap_update_bits(info->map, ctrl_reg, ctrl_mask, ctrl_val); |
| |
| return 0; |
| } |
| |
| |
| #ifdef CONFIG_PXA_RAMDUMP |
| #define PANIC_UPON_FREQUENT_ONKEY |
| #endif |
| #ifdef PANIC_UPON_FREQUENT_ONKEY |
| #include <linux/jiffies.h> |
| #define PANIC_ONKEY_SEC 4 |
| #define PANIC_ONKEY_CNTR (8 * 2) /* press= 0 and 1 */ |
| |
| /* OnKey is helpfull HW to generate debug-event (quick PM-wakeup, panic...) |
| * Add "hidden global" callback for that. |
| * An implementation and bind-control is outside. |
| */ |
| #ifdef CONFIG_PXA_RAMDUMP |
| int (*pm80x_onkey_cb_ret0_to_continue)(unsigned int val); |
| #endif |
| static void pm80x_onkey_panic_handler(void) |
| { |
| static unsigned long jiff_expiry; |
| static int cntr; |
| |
| if (!cntr++) { |
| jiff_expiry = jiffies + msecs_to_jiffies(PANIC_ONKEY_SEC * 1000); |
| return; |
| } |
| if (time_after(jiffies, jiff_expiry)) { |
| cntr = 0; |
| } else if (cntr >= PANIC_ONKEY_CNTR) { |
| pr_err("Panic emulation by frequent OnKey\n"); |
| BUG_ON("Panic emulation by frequent OnKey\n"); |
| } |
| } |
| #else |
| #define pm80x_onkey_panic_handler() /* */ |
| #endif |
| |
| /* 88PM80x gives us an interrupt when ONKEY is held */ |
| static irqreturn_t pm80x_onkey_handler(int irq, void *data) |
| { |
| struct pm80x_onkey_info *info = data; |
| int ret = 0; |
| unsigned int val; |
| #ifdef CONFIG_PXA_RAMDUMP |
| if (pm80x_onkey_cb_ret0_to_continue) { |
| if (pm80x_onkey_cb_ret0_to_continue(0)) |
| return IRQ_HANDLED; |
| } |
| #endif |
| if (!info->disable_onkey_panic) |
| pm80x_onkey_panic_handler(); |
| |
| /* reset the LONGKEY reset time */ |
| if (CHIP_PM800 == info->pm80x->type || CHIP_PM801 == info->pm80x->type) { |
| regmap_update_bits(info->map, PM800_WAKEUP1, |
| PM800_LONKEY_RESET, PM800_LONKEY_RESET); |
| } |
| |
| ret = regmap_read(info->map, PM800_STATUS_1, &val); |
| if (ret < 0) { |
| dev_err(info->idev->dev.parent, "failed to read status: %d\n", ret); |
| return IRQ_NONE; |
| } |
| val &= PM800_ONKEY_STS1; |
| if (val) { |
| pm_stay_awake(info->dev); |
| } else { |
| pm_wakeup_event(info->dev, 8000); |
| } |
| |
| pr_info("pm80x_onkey press = %x\n", !!val); /* sys-sanity */ |
| |
| input_report_key(info->idev, KEY_POWER, val); |
| input_sync(info->idev); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static SIMPLE_DEV_PM_OPS(pm80x_onkey_pm_ops, pm80x_dev_suspend, |
| pm80x_dev_resume); |
| |
| static int pm80x_onkey_dt_init(struct device_node *np, |
| struct pm80x_onkey_info *info) |
| { |
| int ret; |
| |
| ret = of_property_read_u32(np, "long-onkey-timeout", |
| &info->long_onkey_timeout); |
| if (ret < 0) { |
| pr_warn("NO lonkey timeout!\n"); |
| info->long_onkey_timeout = -1; |
| } |
| |
| ret = of_property_read_u32(np, "onkey-gpio-number", |
| &info->gpio_number); |
| if (ret < 0) { |
| pr_warn("No GPIO number for long onkey!\n"); |
| info->gpio_number = -1; |
| } |
| |
| ret = of_property_read_u32(np, "hw-rst-type", &info->hw_rst); |
| if (ret < 0) { |
| pr_warn("No hw reset type for gpio + long onkey!\n"); |
| } |
| |
| ret = of_property_read_u32(np, "long-onkey-type", |
| &info->long_onkey_rst); |
| if (ret < 0) { |
| pr_warn("NO long onkey type!\n"); |
| } |
| |
| ret = of_property_read_u32(np, "disable-long-onkey", |
| &info->disable_long_onkey); |
| if (ret < 0) |
| info->disable_long_onkey = 0; |
| |
| ret = of_property_read_u32(np, "disable-onkey-panic", |
| &info->disable_onkey_panic); |
| if (ret < 0) |
| info->disable_onkey_panic = 0; |
| |
| return 0; |
| } |
| |
| static int pm80x_set_onkey_timeout(struct pm80x_chip *chip, u32 onkey_timeout) |
| { |
| int i, ret; |
| u32 update_bits, val; |
| |
| if (chip->type == CHIP_PM800 || chip->type == CHIP_PM801) { |
| regmap_update_bits(chip->regmap, PM800_RTC_MISC3, |
| PM800_LONKEY_PRESS_TIME_MASK, |
| onkey_timeout); |
| } else { |
| ret = regmap_read(chip->regmap, PM800_RTC_MISC3, &val); |
| if (ret < 0) { |
| pr_err("failed to read onkey timeout: %d\n", ret); |
| val = 0x0; |
| } |
| val &= PM800_LONKEY_PRESS_TIME_MASK; |
| pr_info("onkey timeout: 0x%x\n", val); |
| |
| update_bits = (val ^ onkey_timeout) & 0xf0; |
| /* update bits[7:4] one by one */ |
| for (i = 4; i < 8; i++) { |
| if (update_bits & (1 << i)) { |
| val = val ^ (update_bits & (1 << i)); |
| pr_info("onkey timeout: 0x%x\n", val); |
| regmap_update_bits(chip->regmap, PM800_RTC_MISC3, |
| PM800_LONKEY_PRESS_TIME_MASK, |
| val); |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int pm80x_onkey_probe(struct platform_device *pdev) |
| { |
| |
| struct pm80x_chip *chip = dev_get_drvdata(pdev->dev.parent); |
| struct device_node *node = pdev->dev.of_node; |
| struct pm80x_onkey_info *info; |
| int err; |
| u32 onkey_timeout; |
| |
| info = kzalloc(sizeof(struct pm80x_onkey_info), GFP_KERNEL); |
| if (!info) |
| return -ENOMEM; |
| |
| info->pm80x = chip; |
| |
| info->irq = platform_get_irq(pdev, 0); |
| if (info->irq < 0) { |
| dev_err(&pdev->dev, "No IRQ resource!\n"); |
| err = -EINVAL; |
| goto out; |
| } |
| |
| info->map = info->pm80x->regmap; |
| if (!info->map) { |
| dev_err(&pdev->dev, "no regmap!\n"); |
| err = -EINVAL; |
| goto out; |
| } |
| |
| info->idev = input_allocate_device(); |
| if (!info->idev) { |
| dev_err(&pdev->dev, "Failed to allocate input dev\n"); |
| err = -ENOMEM; |
| goto out; |
| } |
| |
| info->dev = &pdev->dev; |
| info->idev->name = "88pm80x_on"; |
| info->idev->phys = "88pm80x_on/input0"; |
| info->idev->id.bustype = BUS_I2C; |
| info->idev->dev.parent = &pdev->dev; |
| info->idev->evbit[0] = BIT_MASK(EV_KEY); |
| __set_bit(KEY_POWER, info->idev->keybit); |
| |
| /* give the gpio number as a default value */ |
| info->gpio_number = -1; |
| err = pm80x_onkey_dt_init(node, info); |
| if (err < 0) { |
| err = -ENODEV; |
| goto out_reg; |
| } |
| |
| irq_set_status_flags(info->irq, IRQ_NOAUTOEN); |
| err = pm80x_request_irq(info->pm80x, info->irq, pm80x_onkey_handler, |
| IRQF_ONESHOT | IRQF_NO_SUSPEND, |
| "onkey", info); |
| if (err < 0) { |
| dev_err(&pdev->dev, "Failed to request IRQ: #%d: %d\n", |
| info->irq, err); |
| goto out_reg; |
| } |
| |
| err = input_register_device(info->idev); |
| if (err) { |
| dev_err(&pdev->dev, "Can't register input device: %d\n", err); |
| goto out_irq; |
| } |
| |
| platform_set_drvdata(pdev, info); |
| |
| if (info->gpio_number != -1) { |
| err = pm80x_config_gpio(info); |
| if (err < 0) { |
| dev_err(&pdev->dev, "Cannot configure gpio: %d\n", err); |
| info->long_onkey_rst = 2; |
| } |
| } else |
| info->long_onkey_rst = 1; |
| |
| /* set duration of RESETOUTN pulse as 1s */ |
| if (info->pm80x->chip_id == CHIP_PM86X_ID_Z3 |
| || info->pm80x->chip_id == CHIP_PM86X_ID_A0) |
| regmap_update_bits(chip->regmap, PM800_RTC_MISC3, |
| PM860_LONKEY_RESTOUTN_PULSE_MASK, 1); |
| |
| if (info->disable_long_onkey) { |
| regmap_update_bits(chip->regmap, PM800_RTC_MISC4, |
| PM800_LONG_ONKEY_EN1, 0x0); |
| } else { |
| if (info->long_onkey_rst == 1) |
| /* enable LONG_ONKEY_DETECT1, power down the supply */ |
| regmap_update_bits(chip->regmap, PM800_RTC_MISC4, |
| PM800_LONG_ONKEY_EN2 | PM800_LONG_ONKEY_EN1, |
| PM800_LONG_ONKEY_EN1); |
| else |
| /* |
| * enable LONG_ONKEY_DETECT2, |
| * which doesn't force a supply power down |
| */ |
| regmap_update_bits(chip->regmap, PM800_RTC_MISC4, |
| PM800_LONG_ONKEY_EN2 | PM800_LONG_ONKEY_EN1, |
| PM800_LONG_ONKEY_EN2); |
| } |
| |
| if (info->long_onkey_timeout != -1) { |
| onkey_timeout = ((info->long_onkey_timeout & 0xf) << 4); |
| dev_info(&pdev->dev, "lonkey timeout: %d\n", info->long_onkey_timeout); |
| } else { |
| /* set debounce period of ONKEY as 12s */ |
| onkey_timeout = PM800_LONKEY_PRESS_TIME; |
| } |
| pm80x_set_onkey_timeout(chip, onkey_timeout); |
| |
| /* set FAULT_WU_EN */ |
| if (CHIP_PM803 == chip->type || |
| CHIP_PM813 == chip->type) |
| regmap_update_bits(chip->regmap, PM800_RTC_MISC5, PM803_FAULT_WAKEUP_EN, |
| PM803_FAULT_WAKEUP_EN); |
| else |
| regmap_update_bits(chip->regmap, PM800_RTC_MISC5, PM800_FAULT_WAKEUP_EN, |
| PM800_FAULT_WAKEUP_EN); |
| |
| /* need to set fault_wakeup for asr new pmics */ |
| if (CHIP_PM802 == chip->type || |
| CHIP_PM813_ID == chip->chip_id) |
| regmap_update_bits(chip->regmap, PM800_RTC_MISC5, PM800_FAULT_WAKEUP, |
| PM800_FAULT_WAKEUP); |
| else if (CHIP_PM803 == chip->type || |
| CHIP_PM813S_A0_ID == chip->chip_id || |
| CHIP_PM813S_A1_ID == chip->chip_id) |
| regmap_update_bits(chip->regmap, PM800_RTC_MISC5, PM803_FAULT_WAKEUP, |
| PM803_FAULT_WAKEUP); |
| |
| device_init_wakeup(info->dev, true); |
| |
| /* OnKey runs timer => need wake-constraint {Press ... Release+mSec} |
| * (especially to avoid false-panic-emulation). |
| * device_init_wakeup() creates "88pm80x-onkey" |
| * but pm_stay_awake(&info->idev->dev) doesn't as required |
| * Do NOT use the driver's name but the "onkey" instead. |
| */ |
| enable_irq(info->irq); |
| return 0; |
| |
| out_irq: |
| pm80x_free_irq(info->pm80x, info->irq, info); |
| out_reg: |
| input_free_device(info->idev); |
| out: |
| kfree(info); |
| return err; |
| } |
| |
| static int pm80x_onkey_remove(struct platform_device *pdev) |
| { |
| struct pm80x_onkey_info *info = platform_get_drvdata(pdev); |
| |
| disable_irq(info->irq); |
| pm80x_free_irq(info->pm80x, info->irq, info); |
| input_unregister_device(info->idev); |
| kfree(info); |
| return 0; |
| } |
| |
| static void pm80x_onkey_shutdown(struct platform_device *pdev) |
| { |
| struct pm80x_onkey_info *info = platform_get_drvdata(pdev); |
| disable_irq(info->irq); |
| pm80x_free_irq(info->pm80x, info->irq, info); |
| |
| /* clear long onkey event if any */ |
| if (!(CHIP_PM801 == info->pm80x->type |
| || CHIP_PM800 == info->pm80x->type)) |
| regmap_write(info->map, PM800_POWER_DOWN_LOG1, 0x20); |
| |
| return; |
| } |
| |
| static const struct of_device_id pm80x_onkey_dt_match[] = { |
| { .compatible = "marvell,88pm80x-onkey", }, |
| { }, |
| }; |
| MODULE_DEVICE_TABLE(of, pm80x_onkey_dt_match); |
| |
| static struct platform_driver pm80x_onkey_driver = { |
| .driver = { |
| .name = "88pm80x-onkey", |
| .owner = THIS_MODULE, |
| .pm = &pm80x_onkey_pm_ops, |
| .of_match_table = of_match_ptr(pm80x_onkey_dt_match), |
| }, |
| .probe = pm80x_onkey_probe, |
| .remove = pm80x_onkey_remove, |
| .shutdown = pm80x_onkey_shutdown, |
| }; |
| |
| module_platform_driver(pm80x_onkey_driver); |
| |
| MODULE_LICENSE("GPL"); |
| MODULE_DESCRIPTION("Marvell 88PM80x ONKEY driver"); |
| MODULE_AUTHOR("Qiao Zhou <zhouqiao@marvell.com>"); |
| MODULE_ALIAS("platform:88pm80x-onkey"); |