123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369 |
- /*
- * extcon-axp288.c - X-Power AXP288 PMIC extcon cable detection driver
- *
- * Copyright (C) 2015 Intel Corporation
- * Author: Ramakrishna Pallala <ramakrishna.pallala@intel.com>
- *
- * 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.
- *
- * 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.
- */
- #include <linux/module.h>
- #include <linux/kernel.h>
- #include <linux/io.h>
- #include <linux/slab.h>
- #include <linux/interrupt.h>
- #include <linux/platform_device.h>
- #include <linux/property.h>
- #include <linux/usb/phy.h>
- #include <linux/notifier.h>
- #include <linux/extcon.h>
- #include <linux/regmap.h>
- #include <linux/gpio.h>
- #include <linux/gpio/consumer.h>
- #include <linux/mfd/axp20x.h>
- /* Power source status register */
- #define PS_STAT_VBUS_TRIGGER BIT(0)
- #define PS_STAT_BAT_CHRG_DIR BIT(2)
- #define PS_STAT_VBUS_ABOVE_VHOLD BIT(3)
- #define PS_STAT_VBUS_VALID BIT(4)
- #define PS_STAT_VBUS_PRESENT BIT(5)
- /* BC module global register */
- #define BC_GLOBAL_RUN BIT(0)
- #define BC_GLOBAL_DET_STAT BIT(2)
- #define BC_GLOBAL_DBP_TOUT BIT(3)
- #define BC_GLOBAL_VLGC_COM_SEL BIT(4)
- #define BC_GLOBAL_DCD_TOUT_MASK (BIT(6)|BIT(5))
- #define BC_GLOBAL_DCD_TOUT_300MS 0
- #define BC_GLOBAL_DCD_TOUT_100MS 1
- #define BC_GLOBAL_DCD_TOUT_500MS 2
- #define BC_GLOBAL_DCD_TOUT_900MS 3
- #define BC_GLOBAL_DCD_DET_SEL BIT(7)
- /* BC module vbus control and status register */
- #define VBUS_CNTL_DPDM_PD_EN BIT(4)
- #define VBUS_CNTL_DPDM_FD_EN BIT(5)
- #define VBUS_CNTL_FIRST_PO_STAT BIT(6)
- /* BC USB status register */
- #define USB_STAT_BUS_STAT_MASK (BIT(3)|BIT(2)|BIT(1)|BIT(0))
- #define USB_STAT_BUS_STAT_SHIFT 0
- #define USB_STAT_BUS_STAT_ATHD 0
- #define USB_STAT_BUS_STAT_CONN 1
- #define USB_STAT_BUS_STAT_SUSP 2
- #define USB_STAT_BUS_STAT_CONF 3
- #define USB_STAT_USB_SS_MODE BIT(4)
- #define USB_STAT_DEAD_BAT_DET BIT(6)
- #define USB_STAT_DBP_UNCFG BIT(7)
- /* BC detect status register */
- #define DET_STAT_MASK (BIT(7)|BIT(6)|BIT(5))
- #define DET_STAT_SHIFT 5
- #define DET_STAT_SDP 1
- #define DET_STAT_CDP 2
- #define DET_STAT_DCP 3
- /* IRQ enable-1 register */
- #define PWRSRC_IRQ_CFG_MASK (BIT(4)|BIT(3)|BIT(2))
- /* IRQ enable-6 register */
- #define BC12_IRQ_CFG_MASK BIT(1)
- enum axp288_extcon_reg {
- AXP288_PS_STAT_REG = 0x00,
- AXP288_PS_BOOT_REASON_REG = 0x02,
- AXP288_BC_GLOBAL_REG = 0x2c,
- AXP288_BC_VBUS_CNTL_REG = 0x2d,
- AXP288_BC_USB_STAT_REG = 0x2e,
- AXP288_BC_DET_STAT_REG = 0x2f,
- AXP288_PWRSRC_IRQ_CFG_REG = 0x40,
- AXP288_BC12_IRQ_CFG_REG = 0x45,
- };
- enum axp288_mux_select {
- EXTCON_GPIO_MUX_SEL_PMIC = 0,
- EXTCON_GPIO_MUX_SEL_SOC,
- };
- enum axp288_extcon_irq {
- VBUS_FALLING_IRQ = 0,
- VBUS_RISING_IRQ,
- MV_CHNG_IRQ,
- BC_USB_CHNG_IRQ,
- EXTCON_IRQ_END,
- };
- static const unsigned int axp288_extcon_cables[] = {
- EXTCON_CHG_USB_SDP,
- EXTCON_CHG_USB_CDP,
- EXTCON_CHG_USB_DCP,
- EXTCON_NONE,
- };
- struct axp288_extcon_info {
- struct device *dev;
- struct regmap *regmap;
- struct regmap_irq_chip_data *regmap_irqc;
- struct axp288_extcon_pdata *pdata;
- int irq[EXTCON_IRQ_END];
- struct extcon_dev *edev;
- struct notifier_block extcon_nb;
- struct usb_phy *otg;
- };
- /* Power up/down reason string array */
- static char *axp288_pwr_up_down_info[] = {
- "Last wake caused by user pressing the power button",
- "Last wake caused by a charger insertion",
- "Last wake caused by a battery insertion",
- "Last wake caused by SOC initiated global reset",
- "Last wake caused by cold reset",
- "Last shutdown caused by PMIC UVLO threshold",
- "Last shutdown caused by SOC initiated cold off",
- "Last shutdown caused by user pressing the power button",
- NULL,
- };
- /*
- * Decode and log the given "reset source indicator" (rsi)
- * register and then clear it.
- */
- static void axp288_extcon_log_rsi(struct axp288_extcon_info *info)
- {
- char **rsi;
- unsigned int val, i, clear_mask = 0;
- int ret;
- ret = regmap_read(info->regmap, AXP288_PS_BOOT_REASON_REG, &val);
- for (i = 0, rsi = axp288_pwr_up_down_info; *rsi; rsi++, i++) {
- if (val & BIT(i)) {
- dev_dbg(info->dev, "%s\n", *rsi);
- clear_mask |= BIT(i);
- }
- }
- /* Clear the register value for next reboot (write 1 to clear bit) */
- regmap_write(info->regmap, AXP288_PS_BOOT_REASON_REG, clear_mask);
- }
- static int axp288_handle_chrg_det_event(struct axp288_extcon_info *info)
- {
- static bool notify_otg, notify_charger;
- static unsigned int cable;
- int ret, stat, cfg, pwr_stat;
- u8 chrg_type;
- bool vbus_attach = false;
- ret = regmap_read(info->regmap, AXP288_PS_STAT_REG, &pwr_stat);
- if (ret < 0) {
- dev_err(info->dev, "failed to read vbus status\n");
- return ret;
- }
- vbus_attach = (pwr_stat & PS_STAT_VBUS_VALID);
- if (!vbus_attach)
- goto notify_otg;
- /* Check charger detection completion status */
- ret = regmap_read(info->regmap, AXP288_BC_GLOBAL_REG, &cfg);
- if (ret < 0)
- goto dev_det_ret;
- if (cfg & BC_GLOBAL_DET_STAT) {
- dev_dbg(info->dev, "can't complete the charger detection\n");
- goto dev_det_ret;
- }
- ret = regmap_read(info->regmap, AXP288_BC_DET_STAT_REG, &stat);
- if (ret < 0)
- goto dev_det_ret;
- chrg_type = (stat & DET_STAT_MASK) >> DET_STAT_SHIFT;
- switch (chrg_type) {
- case DET_STAT_SDP:
- dev_dbg(info->dev, "sdp cable is connected\n");
- notify_otg = true;
- notify_charger = true;
- cable = EXTCON_CHG_USB_SDP;
- break;
- case DET_STAT_CDP:
- dev_dbg(info->dev, "cdp cable is connected\n");
- notify_otg = true;
- notify_charger = true;
- cable = EXTCON_CHG_USB_CDP;
- break;
- case DET_STAT_DCP:
- dev_dbg(info->dev, "dcp cable is connected\n");
- notify_charger = true;
- cable = EXTCON_CHG_USB_DCP;
- break;
- default:
- dev_warn(info->dev,
- "disconnect or unknown or ID event\n");
- }
- notify_otg:
- if (notify_otg) {
- /*
- * If VBUS is absent Connect D+/D- lines to PMIC for BC
- * detection. Else connect them to SOC for USB communication.
- */
- if (info->pdata->gpio_mux_cntl)
- gpiod_set_value(info->pdata->gpio_mux_cntl,
- vbus_attach ? EXTCON_GPIO_MUX_SEL_SOC
- : EXTCON_GPIO_MUX_SEL_PMIC);
- atomic_notifier_call_chain(&info->otg->notifier,
- vbus_attach ? USB_EVENT_VBUS : USB_EVENT_NONE, NULL);
- }
- if (notify_charger)
- extcon_set_state_sync(info->edev, cable, vbus_attach);
- /* Clear the flags on disconnect event */
- if (!vbus_attach)
- notify_otg = notify_charger = false;
- return 0;
- dev_det_ret:
- if (ret < 0)
- dev_err(info->dev, "failed to detect BC Mod\n");
- return ret;
- }
- static irqreturn_t axp288_extcon_isr(int irq, void *data)
- {
- struct axp288_extcon_info *info = data;
- int ret;
- ret = axp288_handle_chrg_det_event(info);
- if (ret < 0)
- dev_err(info->dev, "failed to handle the interrupt\n");
- return IRQ_HANDLED;
- }
- static void axp288_extcon_enable_irq(struct axp288_extcon_info *info)
- {
- /* Unmask VBUS interrupt */
- regmap_write(info->regmap, AXP288_PWRSRC_IRQ_CFG_REG,
- PWRSRC_IRQ_CFG_MASK);
- regmap_update_bits(info->regmap, AXP288_BC_GLOBAL_REG,
- BC_GLOBAL_RUN, 0);
- /* Unmask the BC1.2 complete interrupts */
- regmap_write(info->regmap, AXP288_BC12_IRQ_CFG_REG, BC12_IRQ_CFG_MASK);
- /* Enable the charger detection logic */
- regmap_update_bits(info->regmap, AXP288_BC_GLOBAL_REG,
- BC_GLOBAL_RUN, BC_GLOBAL_RUN);
- }
- static int axp288_extcon_probe(struct platform_device *pdev)
- {
- struct axp288_extcon_info *info;
- struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
- int ret, i, pirq, gpio;
- info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
- if (!info)
- return -ENOMEM;
- info->dev = &pdev->dev;
- info->regmap = axp20x->regmap;
- info->regmap_irqc = axp20x->regmap_irqc;
- info->pdata = pdev->dev.platform_data;
- if (!info->pdata) {
- /* Try ACPI provided pdata via device properties */
- if (!device_property_present(&pdev->dev,
- "axp288_extcon_data\n"))
- dev_err(&pdev->dev, "failed to get platform data\n");
- return -ENODEV;
- }
- platform_set_drvdata(pdev, info);
- axp288_extcon_log_rsi(info);
- /* Initialize extcon device */
- info->edev = devm_extcon_dev_allocate(&pdev->dev,
- axp288_extcon_cables);
- if (IS_ERR(info->edev)) {
- dev_err(&pdev->dev, "failed to allocate memory for extcon\n");
- return PTR_ERR(info->edev);
- }
- /* Register extcon device */
- ret = devm_extcon_dev_register(&pdev->dev, info->edev);
- if (ret) {
- dev_err(&pdev->dev, "failed to register extcon device\n");
- return ret;
- }
- /* Get otg transceiver phy */
- info->otg = devm_usb_get_phy(&pdev->dev, USB_PHY_TYPE_USB2);
- if (IS_ERR(info->otg)) {
- dev_err(&pdev->dev, "failed to get otg transceiver\n");
- return PTR_ERR(info->otg);
- }
- /* Set up gpio control for USB Mux */
- if (info->pdata->gpio_mux_cntl) {
- gpio = desc_to_gpio(info->pdata->gpio_mux_cntl);
- ret = devm_gpio_request(&pdev->dev, gpio, "USB_MUX");
- if (ret < 0) {
- dev_err(&pdev->dev,
- "failed to request the gpio=%d\n", gpio);
- return ret;
- }
- gpiod_direction_output(info->pdata->gpio_mux_cntl,
- EXTCON_GPIO_MUX_SEL_PMIC);
- }
- for (i = 0; i < EXTCON_IRQ_END; i++) {
- pirq = platform_get_irq(pdev, i);
- info->irq[i] = regmap_irq_get_virq(info->regmap_irqc, pirq);
- if (info->irq[i] < 0) {
- dev_err(&pdev->dev,
- "failed to get virtual interrupt=%d\n", pirq);
- ret = info->irq[i];
- return ret;
- }
- ret = devm_request_threaded_irq(&pdev->dev, info->irq[i],
- NULL, axp288_extcon_isr,
- IRQF_ONESHOT | IRQF_NO_SUSPEND,
- pdev->name, info);
- if (ret) {
- dev_err(&pdev->dev, "failed to request interrupt=%d\n",
- info->irq[i]);
- return ret;
- }
- }
- /* Enable interrupts */
- axp288_extcon_enable_irq(info);
- return 0;
- }
- static struct platform_driver axp288_extcon_driver = {
- .probe = axp288_extcon_probe,
- .driver = {
- .name = "axp288_extcon",
- },
- };
- module_platform_driver(axp288_extcon_driver);
- MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>");
- MODULE_DESCRIPTION("X-Powers AXP288 extcon driver");
- MODULE_LICENSE("GPL v2");
|