brintos

brintos / linux-shallow public Read only

0
0
Text · 7.0 KiB · a1f737f Raw
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