extcon: mrfld: Introduce extcon driver for Basin Cove PMIC
authorAndy Shevchenko <andriy.shevchenko@linux.intel.com>
Tue, 19 Mar 2019 14:30:42 +0000 (16:30 +0200)
committerChanwoo Choi <cw00.choi@samsung.com>
Fri, 5 Apr 2019 01:21:41 +0000 (10:21 +0900)
On Intel Merrifield the Basin Cove PMIC provides a feature to detect
the USB connection type. This driver utilizes the feature in order
to support the USB dual role detection.

Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Chanwoo Choi <cw00.choi@samsung.com>
drivers/extcon/Kconfig
drivers/extcon/Makefile
drivers/extcon/extcon-intel-mrfld.c [new file with mode: 0644]

index 540e8cd..1ed4b45 100644 (file)
@@ -60,6 +60,13 @@ config EXTCON_INTEL_CHT_WC
          Say Y here to enable extcon support for charger detection / control
          on the Intel Cherrytrail Whiskey Cove PMIC.
 
+config EXTCON_INTEL_MRFLD
+       tristate "Intel Merrifield Basin Cove PMIC extcon driver"
+       depends on INTEL_SOC_PMIC_MRFLD
+       help
+         Say Y here to enable extcon support for charger detection / control
+         on the Intel Merrifield Basin Cove PMIC.
+
 config EXTCON_MAX14577
        tristate "Maxim MAX14577/77836 EXTCON Support"
        depends on MFD_MAX14577
index 261ce4c..d3941a7 100644 (file)
@@ -11,6 +11,7 @@ obj-$(CONFIG_EXTCON_AXP288)   += extcon-axp288.o
 obj-$(CONFIG_EXTCON_GPIO)      += extcon-gpio.o
 obj-$(CONFIG_EXTCON_INTEL_INT3496) += extcon-intel-int3496.o
 obj-$(CONFIG_EXTCON_INTEL_CHT_WC) += extcon-intel-cht-wc.o
+obj-$(CONFIG_EXTCON_INTEL_MRFLD) += extcon-intel-mrfld.o
 obj-$(CONFIG_EXTCON_MAX14577)  += extcon-max14577.o
 obj-$(CONFIG_EXTCON_MAX3355)   += extcon-max3355.o
 obj-$(CONFIG_EXTCON_MAX77693)  += extcon-max77693.o
diff --git a/drivers/extcon/extcon-intel-mrfld.c b/drivers/extcon/extcon-intel-mrfld.c
new file mode 100644 (file)
index 0000000..f47016f
--- /dev/null
@@ -0,0 +1,284 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * extcon driver for Basin Cove PMIC
+ *
+ * Copyright (c) 2019, Intel Corporation.
+ * Author: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
+ */
+
+#include <linux/extcon-provider.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/intel_soc_pmic.h>
+#include <linux/mfd/intel_soc_pmic_mrfld.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include "extcon-intel.h"
+
+#define BCOVE_USBIDCTRL                        0x19
+#define BCOVE_USBIDCTRL_ID             BIT(0)
+#define BCOVE_USBIDCTRL_ACA            BIT(1)
+#define BCOVE_USBIDCTRL_ALL    (BCOVE_USBIDCTRL_ID | BCOVE_USBIDCTRL_ACA)
+
+#define BCOVE_USBIDSTS                 0x1a
+#define BCOVE_USBIDSTS_GND             BIT(0)
+#define BCOVE_USBIDSTS_RARBRC_MASK     GENMASK(2, 1)
+#define BCOVE_USBIDSTS_RARBRC_SHIFT    1
+#define BCOVE_USBIDSTS_NO_ACA          0
+#define BCOVE_USBIDSTS_R_ID_A          1
+#define BCOVE_USBIDSTS_R_ID_B          2
+#define BCOVE_USBIDSTS_R_ID_C          3
+#define BCOVE_USBIDSTS_FLOAT           BIT(3)
+#define BCOVE_USBIDSTS_SHORT           BIT(4)
+
+#define BCOVE_CHGRIRQ_ALL      (BCOVE_CHGRIRQ_VBUSDET | BCOVE_CHGRIRQ_DCDET | \
+                                BCOVE_CHGRIRQ_BATTDET | BCOVE_CHGRIRQ_USBIDDET)
+
+#define BCOVE_CHGRCTRL0                        0x4b
+#define BCOVE_CHGRCTRL0_CHGRRESET      BIT(0)
+#define BCOVE_CHGRCTRL0_EMRGCHREN      BIT(1)
+#define BCOVE_CHGRCTRL0_EXTCHRDIS      BIT(2)
+#define BCOVE_CHGRCTRL0_SWCONTROL      BIT(3)
+#define BCOVE_CHGRCTRL0_TTLCK          BIT(4)
+#define BCOVE_CHGRCTRL0_BIT_5          BIT(5)
+#define BCOVE_CHGRCTRL0_BIT_6          BIT(6)
+#define BCOVE_CHGRCTRL0_CHR_WDT_NOKICK BIT(7)
+
+struct mrfld_extcon_data {
+       struct device *dev;
+       struct regmap *regmap;
+       struct extcon_dev *edev;
+       unsigned int status;
+       unsigned int id;
+};
+
+static const unsigned int mrfld_extcon_cable[] = {
+       EXTCON_USB,
+       EXTCON_USB_HOST,
+       EXTCON_CHG_USB_SDP,
+       EXTCON_CHG_USB_CDP,
+       EXTCON_CHG_USB_DCP,
+       EXTCON_CHG_USB_ACA,
+       EXTCON_NONE,
+};
+
+static int mrfld_extcon_clear(struct mrfld_extcon_data *data, unsigned int reg,
+                             unsigned int mask)
+{
+       return regmap_update_bits(data->regmap, reg, mask, 0x00);
+}
+
+static int mrfld_extcon_set(struct mrfld_extcon_data *data, unsigned int reg,
+                           unsigned int mask)
+{
+       return regmap_update_bits(data->regmap, reg, mask, 0xff);
+}
+
+static int mrfld_extcon_sw_control(struct mrfld_extcon_data *data, bool enable)
+{
+       unsigned int mask = BCOVE_CHGRCTRL0_SWCONTROL;
+       struct device *dev = data->dev;
+       int ret;
+
+       if (enable)
+               ret = mrfld_extcon_set(data, BCOVE_CHGRCTRL0, mask);
+       else
+               ret = mrfld_extcon_clear(data, BCOVE_CHGRCTRL0, mask);
+       if (ret)
+               dev_err(dev, "can't set SW control: %d\n", ret);
+       return ret;
+}
+
+static int mrfld_extcon_get_id(struct mrfld_extcon_data *data)
+{
+       struct regmap *regmap = data->regmap;
+       unsigned int id;
+       bool ground;
+       int ret;
+
+       ret = regmap_read(regmap, BCOVE_USBIDSTS, &id);
+       if (ret)
+               return ret;
+
+       if (id & BCOVE_USBIDSTS_FLOAT)
+               return INTEL_USB_ID_FLOAT;
+
+       switch ((id & BCOVE_USBIDSTS_RARBRC_MASK) >> BCOVE_USBIDSTS_RARBRC_SHIFT) {
+       case BCOVE_USBIDSTS_R_ID_A:
+               return INTEL_USB_RID_A;
+       case BCOVE_USBIDSTS_R_ID_B:
+               return INTEL_USB_RID_B;
+       case BCOVE_USBIDSTS_R_ID_C:
+               return INTEL_USB_RID_C;
+       }
+
+       /*
+        * PMIC A0 reports USBIDSTS_GND = 1 for ID_GND,
+        * but PMIC B0 reports USBIDSTS_GND = 0 for ID_GND.
+        * Thus we must check this bit at last.
+        */
+       ground = id & BCOVE_USBIDSTS_GND;
+       switch ('A' + BCOVE_MAJOR(data->id)) {
+       case 'A':
+               return ground ? INTEL_USB_ID_GND : INTEL_USB_ID_FLOAT;
+       case 'B':
+               return ground ? INTEL_USB_ID_FLOAT : INTEL_USB_ID_GND;
+       }
+
+       /* Unknown or unsupported type */
+       return INTEL_USB_ID_FLOAT;
+}
+
+static int mrfld_extcon_role_detect(struct mrfld_extcon_data *data)
+{
+       unsigned int id;
+       bool usb_host;
+       int ret;
+
+       ret = mrfld_extcon_get_id(data);
+       if (ret < 0)
+               return ret;
+
+       id = ret;
+
+       usb_host = (id == INTEL_USB_ID_GND) || (id == INTEL_USB_RID_A);
+       extcon_set_state_sync(data->edev, EXTCON_USB_HOST, usb_host);
+
+       return 0;
+}
+
+static int mrfld_extcon_cable_detect(struct mrfld_extcon_data *data)
+{
+       struct regmap *regmap = data->regmap;
+       unsigned int status, change;
+       int ret;
+
+       /*
+        * It seems SCU firmware clears the content of BCOVE_CHGRIRQ1
+        * and makes it useless for OS. Instead we compare a previously
+        * stored status to the current one, provided by BCOVE_SCHGRIRQ1.
+        */
+       ret = regmap_read(regmap, BCOVE_SCHGRIRQ1, &status);
+       if (ret)
+               return ret;
+
+       change = status ^ data->status;
+       if (!change)
+               return -ENODATA;
+
+       if (change & BCOVE_CHGRIRQ_USBIDDET) {
+               ret = mrfld_extcon_role_detect(data);
+               if (ret)
+                       return ret;
+       }
+
+       data->status = status;
+
+       return 0;
+}
+
+static irqreturn_t mrfld_extcon_interrupt(int irq, void *dev_id)
+{
+       struct mrfld_extcon_data *data = dev_id;
+       int ret;
+
+       ret = mrfld_extcon_cable_detect(data);
+
+       mrfld_extcon_clear(data, BCOVE_MIRQLVL1, BCOVE_LVL1_CHGR);
+
+       return ret ? IRQ_NONE: IRQ_HANDLED;
+}
+
+static int mrfld_extcon_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct intel_soc_pmic *pmic = dev_get_drvdata(dev->parent);
+       struct regmap *regmap = pmic->regmap;
+       struct mrfld_extcon_data *data;
+       unsigned int id;
+       int irq, ret;
+
+       irq = platform_get_irq(pdev, 0);
+       if (irq < 0)
+               return irq;
+
+       data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+       if (!data)
+               return -ENOMEM;
+
+       data->dev = dev;
+       data->regmap = regmap;
+
+       data->edev = devm_extcon_dev_allocate(dev, mrfld_extcon_cable);
+       if (IS_ERR(data->edev))
+               return -ENOMEM;
+
+       ret = devm_extcon_dev_register(dev, data->edev);
+       if (ret < 0) {
+               dev_err(dev, "can't register extcon device: %d\n", ret);
+               return ret;
+       }
+
+       ret = devm_request_threaded_irq(dev, irq, NULL, mrfld_extcon_interrupt,
+                                       IRQF_ONESHOT | IRQF_SHARED, pdev->name,
+                                       data);
+       if (ret) {
+               dev_err(dev, "can't register IRQ handler: %d\n", ret);
+               return ret;
+       }
+
+       ret = regmap_read(regmap, BCOVE_ID, &id);
+       if (ret) {
+               dev_err(dev, "can't read PMIC ID: %d\n", ret);
+               return ret;
+       }
+
+       data->id = id;
+
+       ret = mrfld_extcon_sw_control(data, true);
+       if (ret)
+               return ret;
+
+       /* Get initial state */
+       mrfld_extcon_role_detect(data);
+
+       mrfld_extcon_clear(data, BCOVE_MIRQLVL1, BCOVE_LVL1_CHGR);
+       mrfld_extcon_clear(data, BCOVE_MCHGRIRQ1, BCOVE_CHGRIRQ_ALL);
+
+       mrfld_extcon_set(data, BCOVE_USBIDCTRL, BCOVE_USBIDCTRL_ALL);
+
+       platform_set_drvdata(pdev, data);
+
+       return 0;
+}
+
+static int mrfld_extcon_remove(struct platform_device *pdev)
+{
+       struct mrfld_extcon_data *data = platform_get_drvdata(pdev);
+
+       mrfld_extcon_sw_control(data, false);
+
+       return 0;
+}
+
+static const struct platform_device_id mrfld_extcon_id_table[] = {
+       { .name = "mrfld_bcove_pwrsrc" },
+       {}
+};
+MODULE_DEVICE_TABLE(platform, mrfld_extcon_id_table);
+
+static struct platform_driver mrfld_extcon_driver = {
+       .driver = {
+               .name   = "mrfld_bcove_pwrsrc",
+       },
+       .probe          = mrfld_extcon_probe,
+       .remove         = mrfld_extcon_remove,
+       .id_table       = mrfld_extcon_id_table,
+};
+module_platform_driver(mrfld_extcon_driver);
+
+MODULE_AUTHOR("Andy Shevchenko <andriy.shevchenko@linux.intel.com>");
+MODULE_DESCRIPTION("extcon driver for Intel Merrifield Basin Cove PMIC");
+MODULE_LICENSE("GPL v2");