286 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * extcon driver for Basin Cove PMIC4 *5 * Copyright (c) 2019, Intel Corporation.6 * Author: Andy Shevchenko <andriy.shevchenko@linux.intel.com>7 */8 9#include <linux/extcon-provider.h>10#include <linux/interrupt.h>11#include <linux/mfd/intel_soc_pmic.h>12#include <linux/mfd/intel_soc_pmic_mrfld.h>13#include <linux/mod_devicetable.h>14#include <linux/module.h>15#include <linux/platform_device.h>16#include <linux/regmap.h>17 18#include "extcon-intel.h"19 20#define BCOVE_USBIDCTRL 0x1921#define BCOVE_USBIDCTRL_ID BIT(0)22#define BCOVE_USBIDCTRL_ACA BIT(1)23#define BCOVE_USBIDCTRL_ALL (BCOVE_USBIDCTRL_ID | BCOVE_USBIDCTRL_ACA)24 25#define BCOVE_USBIDSTS 0x1a26#define BCOVE_USBIDSTS_GND BIT(0)27#define BCOVE_USBIDSTS_RARBRC_MASK GENMASK(2, 1)28#define BCOVE_USBIDSTS_RARBRC_SHIFT 129#define BCOVE_USBIDSTS_NO_ACA 030#define BCOVE_USBIDSTS_R_ID_A 131#define BCOVE_USBIDSTS_R_ID_B 232#define BCOVE_USBIDSTS_R_ID_C 333#define BCOVE_USBIDSTS_FLOAT BIT(3)34#define BCOVE_USBIDSTS_SHORT BIT(4)35 36#define BCOVE_CHGRIRQ_ALL (BCOVE_CHGRIRQ_VBUSDET | BCOVE_CHGRIRQ_DCDET | \37 BCOVE_CHGRIRQ_BATTDET | BCOVE_CHGRIRQ_USBIDDET)38 39#define BCOVE_CHGRCTRL0 0x4b40#define BCOVE_CHGRCTRL0_CHGRRESET BIT(0)41#define BCOVE_CHGRCTRL0_EMRGCHREN BIT(1)42#define BCOVE_CHGRCTRL0_EXTCHRDIS BIT(2)43#define BCOVE_CHGRCTRL0_SWCONTROL BIT(3)44#define BCOVE_CHGRCTRL0_TTLCK BIT(4)45#define BCOVE_CHGRCTRL0_BIT_5 BIT(5)46#define BCOVE_CHGRCTRL0_BIT_6 BIT(6)47#define BCOVE_CHGRCTRL0_CHR_WDT_NOKICK BIT(7)48 49struct mrfld_extcon_data {50 struct device *dev;51 struct regmap *regmap;52 struct extcon_dev *edev;53 unsigned int status;54 unsigned int id;55};56 57static const unsigned int mrfld_extcon_cable[] = {58 EXTCON_USB,59 EXTCON_USB_HOST,60 EXTCON_CHG_USB_SDP,61 EXTCON_CHG_USB_CDP,62 EXTCON_CHG_USB_DCP,63 EXTCON_CHG_USB_ACA,64 EXTCON_NONE,65};66 67static int mrfld_extcon_clear(struct mrfld_extcon_data *data, unsigned int reg,68 unsigned int mask)69{70 return regmap_update_bits(data->regmap, reg, mask, 0x00);71}72 73static int mrfld_extcon_set(struct mrfld_extcon_data *data, unsigned int reg,74 unsigned int mask)75{76 return regmap_update_bits(data->regmap, reg, mask, 0xff);77}78 79static int mrfld_extcon_sw_control(struct mrfld_extcon_data *data, bool enable)80{81 unsigned int mask = BCOVE_CHGRCTRL0_SWCONTROL;82 struct device *dev = data->dev;83 int ret;84 85 if (enable)86 ret = mrfld_extcon_set(data, BCOVE_CHGRCTRL0, mask);87 else88 ret = mrfld_extcon_clear(data, BCOVE_CHGRCTRL0, mask);89 if (ret)90 dev_err(dev, "can't set SW control: %d\n", ret);91 return ret;92}93 94static int mrfld_extcon_get_id(struct mrfld_extcon_data *data)95{96 struct regmap *regmap = data->regmap;97 unsigned int id;98 bool ground;99 int ret;100 101 ret = regmap_read(regmap, BCOVE_USBIDSTS, &id);102 if (ret)103 return ret;104 105 if (id & BCOVE_USBIDSTS_FLOAT)106 return INTEL_USB_ID_FLOAT;107 108 switch ((id & BCOVE_USBIDSTS_RARBRC_MASK) >> BCOVE_USBIDSTS_RARBRC_SHIFT) {109 case BCOVE_USBIDSTS_R_ID_A:110 return INTEL_USB_RID_A;111 case BCOVE_USBIDSTS_R_ID_B:112 return INTEL_USB_RID_B;113 case BCOVE_USBIDSTS_R_ID_C:114 return INTEL_USB_RID_C;115 }116 117 /*118 * PMIC A0 reports USBIDSTS_GND = 1 for ID_GND,119 * but PMIC B0 reports USBIDSTS_GND = 0 for ID_GND.120 * Thus we must check this bit at last.121 */122 ground = id & BCOVE_USBIDSTS_GND;123 switch ('A' + BCOVE_MAJOR(data->id)) {124 case 'A':125 return ground ? INTEL_USB_ID_GND : INTEL_USB_ID_FLOAT;126 case 'B':127 return ground ? INTEL_USB_ID_FLOAT : INTEL_USB_ID_GND;128 }129 130 /* Unknown or unsupported type */131 return INTEL_USB_ID_FLOAT;132}133 134static int mrfld_extcon_role_detect(struct mrfld_extcon_data *data)135{136 unsigned int id;137 bool usb_host;138 int ret;139 140 ret = mrfld_extcon_get_id(data);141 if (ret < 0)142 return ret;143 144 id = ret;145 146 usb_host = (id == INTEL_USB_ID_GND) || (id == INTEL_USB_RID_A);147 extcon_set_state_sync(data->edev, EXTCON_USB_HOST, usb_host);148 149 return 0;150}151 152static int mrfld_extcon_cable_detect(struct mrfld_extcon_data *data)153{154 struct regmap *regmap = data->regmap;155 unsigned int status, change;156 int ret;157 158 /*159 * It seems SCU firmware clears the content of BCOVE_CHGRIRQ1160 * and makes it useless for OS. Instead we compare a previously161 * stored status to the current one, provided by BCOVE_SCHGRIRQ1.162 */163 ret = regmap_read(regmap, BCOVE_SCHGRIRQ1, &status);164 if (ret)165 return ret;166 167 change = status ^ data->status;168 if (!change)169 return -ENODATA;170 171 if (change & BCOVE_CHGRIRQ_USBIDDET) {172 ret = mrfld_extcon_role_detect(data);173 if (ret)174 return ret;175 }176 177 data->status = status;178 179 return 0;180}181 182static irqreturn_t mrfld_extcon_interrupt(int irq, void *dev_id)183{184 struct mrfld_extcon_data *data = dev_id;185 int ret;186 187 ret = mrfld_extcon_cable_detect(data);188 189 mrfld_extcon_clear(data, BCOVE_MIRQLVL1, BCOVE_LVL1_CHGR);190 191 return ret ? IRQ_NONE: IRQ_HANDLED;192}193 194static int mrfld_extcon_probe(struct platform_device *pdev)195{196 struct device *dev = &pdev->dev;197 struct intel_soc_pmic *pmic = dev_get_drvdata(dev->parent);198 struct regmap *regmap = pmic->regmap;199 struct mrfld_extcon_data *data;200 unsigned int status;201 unsigned int id;202 int irq, ret;203 204 irq = platform_get_irq(pdev, 0);205 if (irq < 0)206 return irq;207 208 data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);209 if (!data)210 return -ENOMEM;211 212 data->dev = dev;213 data->regmap = regmap;214 215 data->edev = devm_extcon_dev_allocate(dev, mrfld_extcon_cable);216 if (IS_ERR(data->edev))217 return PTR_ERR(data->edev);218 219 ret = devm_extcon_dev_register(dev, data->edev);220 if (ret < 0)221 return dev_err_probe(dev, ret, "can't register extcon device\n");222 223 ret = devm_request_threaded_irq(dev, irq, NULL, mrfld_extcon_interrupt,224 IRQF_ONESHOT | IRQF_SHARED, pdev->name,225 data);226 if (ret)227 return dev_err_probe(dev, ret, "can't register IRQ handler\n");228 229 ret = regmap_read(regmap, BCOVE_ID, &id);230 if (ret)231 return dev_err_probe(dev, ret, "can't read PMIC ID\n");232 233 data->id = id;234 235 ret = mrfld_extcon_sw_control(data, true);236 if (ret)237 return ret;238 239 /* Get initial state */240 mrfld_extcon_role_detect(data);241 242 /*243 * Cached status value is used for cable detection, see comments244 * in mrfld_extcon_cable_detect(), we need to sync cached value245 * with a real state of the hardware.246 */247 regmap_read(regmap, BCOVE_SCHGRIRQ1, &status);248 data->status = status;249 250 mrfld_extcon_clear(data, BCOVE_MIRQLVL1, BCOVE_LVL1_CHGR);251 mrfld_extcon_clear(data, BCOVE_MCHGRIRQ1, BCOVE_CHGRIRQ_ALL);252 253 mrfld_extcon_set(data, BCOVE_USBIDCTRL, BCOVE_USBIDCTRL_ALL);254 255 platform_set_drvdata(pdev, data);256 257 return 0;258}259 260static void mrfld_extcon_remove(struct platform_device *pdev)261{262 struct mrfld_extcon_data *data = platform_get_drvdata(pdev);263 264 mrfld_extcon_sw_control(data, false);265}266 267static const struct platform_device_id mrfld_extcon_id_table[] = {268 { .name = "mrfld_bcove_pwrsrc" },269 {}270};271MODULE_DEVICE_TABLE(platform, mrfld_extcon_id_table);272 273static struct platform_driver mrfld_extcon_driver = {274 .driver = {275 .name = "mrfld_bcove_pwrsrc",276 },277 .probe = mrfld_extcon_probe,278 .remove_new = mrfld_extcon_remove,279 .id_table = mrfld_extcon_id_table,280};281module_platform_driver(mrfld_extcon_driver);282 283MODULE_AUTHOR("Andy Shevchenko <andriy.shevchenko@linux.intel.com>");284MODULE_DESCRIPTION("extcon driver for Intel Merrifield Basin Cove PMIC");285MODULE_LICENSE("GPL v2");286