xref: /openbmc/linux/drivers/extcon/extcon-intel-mrfld.c (revision 762f99f4f3cb41a775b5157dd761217beba65873)
1492929c5SAndy Shevchenko // SPDX-License-Identifier: GPL-2.0
2492929c5SAndy Shevchenko /*
3492929c5SAndy Shevchenko  * extcon driver for Basin Cove PMIC
4492929c5SAndy Shevchenko  *
5492929c5SAndy Shevchenko  * Copyright (c) 2019, Intel Corporation.
6492929c5SAndy Shevchenko  * Author: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
7492929c5SAndy Shevchenko  */
8492929c5SAndy Shevchenko 
9492929c5SAndy Shevchenko #include <linux/extcon-provider.h>
10492929c5SAndy Shevchenko #include <linux/interrupt.h>
11492929c5SAndy Shevchenko #include <linux/mfd/intel_soc_pmic.h>
12492929c5SAndy Shevchenko #include <linux/mfd/intel_soc_pmic_mrfld.h>
13492929c5SAndy Shevchenko #include <linux/mod_devicetable.h>
14492929c5SAndy Shevchenko #include <linux/module.h>
15492929c5SAndy Shevchenko #include <linux/platform_device.h>
16492929c5SAndy Shevchenko #include <linux/regmap.h>
17492929c5SAndy Shevchenko 
18492929c5SAndy Shevchenko #include "extcon-intel.h"
19492929c5SAndy Shevchenko 
20492929c5SAndy Shevchenko #define BCOVE_USBIDCTRL			0x19
21492929c5SAndy Shevchenko #define BCOVE_USBIDCTRL_ID		BIT(0)
22492929c5SAndy Shevchenko #define BCOVE_USBIDCTRL_ACA		BIT(1)
23492929c5SAndy Shevchenko #define BCOVE_USBIDCTRL_ALL	(BCOVE_USBIDCTRL_ID | BCOVE_USBIDCTRL_ACA)
24492929c5SAndy Shevchenko 
25492929c5SAndy Shevchenko #define BCOVE_USBIDSTS			0x1a
26492929c5SAndy Shevchenko #define BCOVE_USBIDSTS_GND		BIT(0)
27492929c5SAndy Shevchenko #define BCOVE_USBIDSTS_RARBRC_MASK	GENMASK(2, 1)
28492929c5SAndy Shevchenko #define BCOVE_USBIDSTS_RARBRC_SHIFT	1
29492929c5SAndy Shevchenko #define BCOVE_USBIDSTS_NO_ACA		0
30492929c5SAndy Shevchenko #define BCOVE_USBIDSTS_R_ID_A		1
31492929c5SAndy Shevchenko #define BCOVE_USBIDSTS_R_ID_B		2
32492929c5SAndy Shevchenko #define BCOVE_USBIDSTS_R_ID_C		3
33492929c5SAndy Shevchenko #define BCOVE_USBIDSTS_FLOAT		BIT(3)
34492929c5SAndy Shevchenko #define BCOVE_USBIDSTS_SHORT		BIT(4)
35492929c5SAndy Shevchenko 
36492929c5SAndy Shevchenko #define BCOVE_CHGRIRQ_ALL	(BCOVE_CHGRIRQ_VBUSDET | BCOVE_CHGRIRQ_DCDET | \
37492929c5SAndy Shevchenko 				 BCOVE_CHGRIRQ_BATTDET | BCOVE_CHGRIRQ_USBIDDET)
38492929c5SAndy Shevchenko 
39492929c5SAndy Shevchenko #define BCOVE_CHGRCTRL0			0x4b
40492929c5SAndy Shevchenko #define BCOVE_CHGRCTRL0_CHGRRESET	BIT(0)
41492929c5SAndy Shevchenko #define BCOVE_CHGRCTRL0_EMRGCHREN	BIT(1)
42492929c5SAndy Shevchenko #define BCOVE_CHGRCTRL0_EXTCHRDIS	BIT(2)
43492929c5SAndy Shevchenko #define BCOVE_CHGRCTRL0_SWCONTROL	BIT(3)
44492929c5SAndy Shevchenko #define BCOVE_CHGRCTRL0_TTLCK		BIT(4)
45492929c5SAndy Shevchenko #define BCOVE_CHGRCTRL0_BIT_5		BIT(5)
46492929c5SAndy Shevchenko #define BCOVE_CHGRCTRL0_BIT_6		BIT(6)
47492929c5SAndy Shevchenko #define BCOVE_CHGRCTRL0_CHR_WDT_NOKICK	BIT(7)
48492929c5SAndy Shevchenko 
49492929c5SAndy Shevchenko struct mrfld_extcon_data {
50492929c5SAndy Shevchenko 	struct device *dev;
51492929c5SAndy Shevchenko 	struct regmap *regmap;
52492929c5SAndy Shevchenko 	struct extcon_dev *edev;
53492929c5SAndy Shevchenko 	unsigned int status;
54492929c5SAndy Shevchenko 	unsigned int id;
55492929c5SAndy Shevchenko };
56492929c5SAndy Shevchenko 
57492929c5SAndy Shevchenko static const unsigned int mrfld_extcon_cable[] = {
58492929c5SAndy Shevchenko 	EXTCON_USB,
59492929c5SAndy Shevchenko 	EXTCON_USB_HOST,
60492929c5SAndy Shevchenko 	EXTCON_CHG_USB_SDP,
61492929c5SAndy Shevchenko 	EXTCON_CHG_USB_CDP,
62492929c5SAndy Shevchenko 	EXTCON_CHG_USB_DCP,
63492929c5SAndy Shevchenko 	EXTCON_CHG_USB_ACA,
64492929c5SAndy Shevchenko 	EXTCON_NONE,
65492929c5SAndy Shevchenko };
66492929c5SAndy Shevchenko 
mrfld_extcon_clear(struct mrfld_extcon_data * data,unsigned int reg,unsigned int mask)67492929c5SAndy Shevchenko static int mrfld_extcon_clear(struct mrfld_extcon_data *data, unsigned int reg,
68492929c5SAndy Shevchenko 			      unsigned int mask)
69492929c5SAndy Shevchenko {
70492929c5SAndy Shevchenko 	return regmap_update_bits(data->regmap, reg, mask, 0x00);
71492929c5SAndy Shevchenko }
72492929c5SAndy Shevchenko 
mrfld_extcon_set(struct mrfld_extcon_data * data,unsigned int reg,unsigned int mask)73492929c5SAndy Shevchenko static int mrfld_extcon_set(struct mrfld_extcon_data *data, unsigned int reg,
74492929c5SAndy Shevchenko 			    unsigned int mask)
75492929c5SAndy Shevchenko {
76492929c5SAndy Shevchenko 	return regmap_update_bits(data->regmap, reg, mask, 0xff);
77492929c5SAndy Shevchenko }
78492929c5SAndy Shevchenko 
mrfld_extcon_sw_control(struct mrfld_extcon_data * data,bool enable)79492929c5SAndy Shevchenko static int mrfld_extcon_sw_control(struct mrfld_extcon_data *data, bool enable)
80492929c5SAndy Shevchenko {
81492929c5SAndy Shevchenko 	unsigned int mask = BCOVE_CHGRCTRL0_SWCONTROL;
82492929c5SAndy Shevchenko 	struct device *dev = data->dev;
83492929c5SAndy Shevchenko 	int ret;
84492929c5SAndy Shevchenko 
85492929c5SAndy Shevchenko 	if (enable)
86492929c5SAndy Shevchenko 		ret = mrfld_extcon_set(data, BCOVE_CHGRCTRL0, mask);
87492929c5SAndy Shevchenko 	else
88492929c5SAndy Shevchenko 		ret = mrfld_extcon_clear(data, BCOVE_CHGRCTRL0, mask);
89492929c5SAndy Shevchenko 	if (ret)
90492929c5SAndy Shevchenko 		dev_err(dev, "can't set SW control: %d\n", ret);
91492929c5SAndy Shevchenko 	return ret;
92492929c5SAndy Shevchenko }
93492929c5SAndy Shevchenko 
mrfld_extcon_get_id(struct mrfld_extcon_data * data)94492929c5SAndy Shevchenko static int mrfld_extcon_get_id(struct mrfld_extcon_data *data)
95492929c5SAndy Shevchenko {
96492929c5SAndy Shevchenko 	struct regmap *regmap = data->regmap;
97492929c5SAndy Shevchenko 	unsigned int id;
98492929c5SAndy Shevchenko 	bool ground;
99492929c5SAndy Shevchenko 	int ret;
100492929c5SAndy Shevchenko 
101492929c5SAndy Shevchenko 	ret = regmap_read(regmap, BCOVE_USBIDSTS, &id);
102492929c5SAndy Shevchenko 	if (ret)
103492929c5SAndy Shevchenko 		return ret;
104492929c5SAndy Shevchenko 
105492929c5SAndy Shevchenko 	if (id & BCOVE_USBIDSTS_FLOAT)
106492929c5SAndy Shevchenko 		return INTEL_USB_ID_FLOAT;
107492929c5SAndy Shevchenko 
108492929c5SAndy Shevchenko 	switch ((id & BCOVE_USBIDSTS_RARBRC_MASK) >> BCOVE_USBIDSTS_RARBRC_SHIFT) {
109492929c5SAndy Shevchenko 	case BCOVE_USBIDSTS_R_ID_A:
110492929c5SAndy Shevchenko 		return INTEL_USB_RID_A;
111492929c5SAndy Shevchenko 	case BCOVE_USBIDSTS_R_ID_B:
112492929c5SAndy Shevchenko 		return INTEL_USB_RID_B;
113492929c5SAndy Shevchenko 	case BCOVE_USBIDSTS_R_ID_C:
114492929c5SAndy Shevchenko 		return INTEL_USB_RID_C;
115492929c5SAndy Shevchenko 	}
116492929c5SAndy Shevchenko 
117492929c5SAndy Shevchenko 	/*
118492929c5SAndy Shevchenko 	 * PMIC A0 reports USBIDSTS_GND = 1 for ID_GND,
119492929c5SAndy Shevchenko 	 * but PMIC B0 reports USBIDSTS_GND = 0 for ID_GND.
120492929c5SAndy Shevchenko 	 * Thus we must check this bit at last.
121492929c5SAndy Shevchenko 	 */
122492929c5SAndy Shevchenko 	ground = id & BCOVE_USBIDSTS_GND;
123492929c5SAndy Shevchenko 	switch ('A' + BCOVE_MAJOR(data->id)) {
124492929c5SAndy Shevchenko 	case 'A':
125492929c5SAndy Shevchenko 		return ground ? INTEL_USB_ID_GND : INTEL_USB_ID_FLOAT;
126492929c5SAndy Shevchenko 	case 'B':
127492929c5SAndy Shevchenko 		return ground ? INTEL_USB_ID_FLOAT : INTEL_USB_ID_GND;
128492929c5SAndy Shevchenko 	}
129492929c5SAndy Shevchenko 
130492929c5SAndy Shevchenko 	/* Unknown or unsupported type */
131492929c5SAndy Shevchenko 	return INTEL_USB_ID_FLOAT;
132492929c5SAndy Shevchenko }
133492929c5SAndy Shevchenko 
mrfld_extcon_role_detect(struct mrfld_extcon_data * data)134492929c5SAndy Shevchenko static int mrfld_extcon_role_detect(struct mrfld_extcon_data *data)
135492929c5SAndy Shevchenko {
136492929c5SAndy Shevchenko 	unsigned int id;
137492929c5SAndy Shevchenko 	bool usb_host;
138492929c5SAndy Shevchenko 	int ret;
139492929c5SAndy Shevchenko 
140492929c5SAndy Shevchenko 	ret = mrfld_extcon_get_id(data);
141492929c5SAndy Shevchenko 	if (ret < 0)
142492929c5SAndy Shevchenko 		return ret;
143492929c5SAndy Shevchenko 
144492929c5SAndy Shevchenko 	id = ret;
145492929c5SAndy Shevchenko 
146492929c5SAndy Shevchenko 	usb_host = (id == INTEL_USB_ID_GND) || (id == INTEL_USB_RID_A);
147492929c5SAndy Shevchenko 	extcon_set_state_sync(data->edev, EXTCON_USB_HOST, usb_host);
148492929c5SAndy Shevchenko 
149492929c5SAndy Shevchenko 	return 0;
150492929c5SAndy Shevchenko }
151492929c5SAndy Shevchenko 
mrfld_extcon_cable_detect(struct mrfld_extcon_data * data)152492929c5SAndy Shevchenko static int mrfld_extcon_cable_detect(struct mrfld_extcon_data *data)
153492929c5SAndy Shevchenko {
154492929c5SAndy Shevchenko 	struct regmap *regmap = data->regmap;
155492929c5SAndy Shevchenko 	unsigned int status, change;
156492929c5SAndy Shevchenko 	int ret;
157492929c5SAndy Shevchenko 
158492929c5SAndy Shevchenko 	/*
159492929c5SAndy Shevchenko 	 * It seems SCU firmware clears the content of BCOVE_CHGRIRQ1
160492929c5SAndy Shevchenko 	 * and makes it useless for OS. Instead we compare a previously
161492929c5SAndy Shevchenko 	 * stored status to the current one, provided by BCOVE_SCHGRIRQ1.
162492929c5SAndy Shevchenko 	 */
163492929c5SAndy Shevchenko 	ret = regmap_read(regmap, BCOVE_SCHGRIRQ1, &status);
164492929c5SAndy Shevchenko 	if (ret)
165492929c5SAndy Shevchenko 		return ret;
166492929c5SAndy Shevchenko 
167492929c5SAndy Shevchenko 	change = status ^ data->status;
168492929c5SAndy Shevchenko 	if (!change)
169492929c5SAndy Shevchenko 		return -ENODATA;
170492929c5SAndy Shevchenko 
171492929c5SAndy Shevchenko 	if (change & BCOVE_CHGRIRQ_USBIDDET) {
172492929c5SAndy Shevchenko 		ret = mrfld_extcon_role_detect(data);
173492929c5SAndy Shevchenko 		if (ret)
174492929c5SAndy Shevchenko 			return ret;
175492929c5SAndy Shevchenko 	}
176492929c5SAndy Shevchenko 
177492929c5SAndy Shevchenko 	data->status = status;
178492929c5SAndy Shevchenko 
179492929c5SAndy Shevchenko 	return 0;
180492929c5SAndy Shevchenko }
181492929c5SAndy Shevchenko 
mrfld_extcon_interrupt(int irq,void * dev_id)182492929c5SAndy Shevchenko static irqreturn_t mrfld_extcon_interrupt(int irq, void *dev_id)
183492929c5SAndy Shevchenko {
184492929c5SAndy Shevchenko 	struct mrfld_extcon_data *data = dev_id;
185492929c5SAndy Shevchenko 	int ret;
186492929c5SAndy Shevchenko 
187492929c5SAndy Shevchenko 	ret = mrfld_extcon_cable_detect(data);
188492929c5SAndy Shevchenko 
189492929c5SAndy Shevchenko 	mrfld_extcon_clear(data, BCOVE_MIRQLVL1, BCOVE_LVL1_CHGR);
190492929c5SAndy Shevchenko 
191492929c5SAndy Shevchenko 	return ret ? IRQ_NONE: IRQ_HANDLED;
192492929c5SAndy Shevchenko }
193492929c5SAndy Shevchenko 
mrfld_extcon_probe(struct platform_device * pdev)194492929c5SAndy Shevchenko static int mrfld_extcon_probe(struct platform_device *pdev)
195492929c5SAndy Shevchenko {
196492929c5SAndy Shevchenko 	struct device *dev = &pdev->dev;
197492929c5SAndy Shevchenko 	struct intel_soc_pmic *pmic = dev_get_drvdata(dev->parent);
198492929c5SAndy Shevchenko 	struct regmap *regmap = pmic->regmap;
199492929c5SAndy Shevchenko 	struct mrfld_extcon_data *data;
200*ecb5bdffSFerry Toth 	unsigned int status;
201492929c5SAndy Shevchenko 	unsigned int id;
202492929c5SAndy Shevchenko 	int irq, ret;
203492929c5SAndy Shevchenko 
204492929c5SAndy Shevchenko 	irq = platform_get_irq(pdev, 0);
205492929c5SAndy Shevchenko 	if (irq < 0)
206492929c5SAndy Shevchenko 		return irq;
207492929c5SAndy Shevchenko 
208492929c5SAndy Shevchenko 	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
209492929c5SAndy Shevchenko 	if (!data)
210492929c5SAndy Shevchenko 		return -ENOMEM;
211492929c5SAndy Shevchenko 
212492929c5SAndy Shevchenko 	data->dev = dev;
213492929c5SAndy Shevchenko 	data->regmap = regmap;
214492929c5SAndy Shevchenko 
215492929c5SAndy Shevchenko 	data->edev = devm_extcon_dev_allocate(dev, mrfld_extcon_cable);
216492929c5SAndy Shevchenko 	if (IS_ERR(data->edev))
217492929c5SAndy Shevchenko 		return -ENOMEM;
218492929c5SAndy Shevchenko 
219492929c5SAndy Shevchenko 	ret = devm_extcon_dev_register(dev, data->edev);
220492929c5SAndy Shevchenko 	if (ret < 0) {
221492929c5SAndy Shevchenko 		dev_err(dev, "can't register extcon device: %d\n", ret);
222492929c5SAndy Shevchenko 		return ret;
223492929c5SAndy Shevchenko 	}
224492929c5SAndy Shevchenko 
225492929c5SAndy Shevchenko 	ret = devm_request_threaded_irq(dev, irq, NULL, mrfld_extcon_interrupt,
226492929c5SAndy Shevchenko 					IRQF_ONESHOT | IRQF_SHARED, pdev->name,
227492929c5SAndy Shevchenko 					data);
228492929c5SAndy Shevchenko 	if (ret) {
229492929c5SAndy Shevchenko 		dev_err(dev, "can't register IRQ handler: %d\n", ret);
230492929c5SAndy Shevchenko 		return ret;
231492929c5SAndy Shevchenko 	}
232492929c5SAndy Shevchenko 
233492929c5SAndy Shevchenko 	ret = regmap_read(regmap, BCOVE_ID, &id);
234492929c5SAndy Shevchenko 	if (ret) {
235492929c5SAndy Shevchenko 		dev_err(dev, "can't read PMIC ID: %d\n", ret);
236492929c5SAndy Shevchenko 		return ret;
237492929c5SAndy Shevchenko 	}
238492929c5SAndy Shevchenko 
239492929c5SAndy Shevchenko 	data->id = id;
240492929c5SAndy Shevchenko 
241492929c5SAndy Shevchenko 	ret = mrfld_extcon_sw_control(data, true);
242492929c5SAndy Shevchenko 	if (ret)
243492929c5SAndy Shevchenko 		return ret;
244492929c5SAndy Shevchenko 
245492929c5SAndy Shevchenko 	/* Get initial state */
246492929c5SAndy Shevchenko 	mrfld_extcon_role_detect(data);
247492929c5SAndy Shevchenko 
248*ecb5bdffSFerry Toth 	/*
249*ecb5bdffSFerry Toth 	 * Cached status value is used for cable detection, see comments
250*ecb5bdffSFerry Toth 	 * in mrfld_extcon_cable_detect(), we need to sync cached value
251*ecb5bdffSFerry Toth 	 * with a real state of the hardware.
252*ecb5bdffSFerry Toth 	 */
253*ecb5bdffSFerry Toth 	regmap_read(regmap, BCOVE_SCHGRIRQ1, &status);
254*ecb5bdffSFerry Toth 	data->status = status;
255*ecb5bdffSFerry Toth 
256492929c5SAndy Shevchenko 	mrfld_extcon_clear(data, BCOVE_MIRQLVL1, BCOVE_LVL1_CHGR);
257492929c5SAndy Shevchenko 	mrfld_extcon_clear(data, BCOVE_MCHGRIRQ1, BCOVE_CHGRIRQ_ALL);
258492929c5SAndy Shevchenko 
259492929c5SAndy Shevchenko 	mrfld_extcon_set(data, BCOVE_USBIDCTRL, BCOVE_USBIDCTRL_ALL);
260492929c5SAndy Shevchenko 
261492929c5SAndy Shevchenko 	platform_set_drvdata(pdev, data);
262492929c5SAndy Shevchenko 
263492929c5SAndy Shevchenko 	return 0;
264492929c5SAndy Shevchenko }
265492929c5SAndy Shevchenko 
mrfld_extcon_remove(struct platform_device * pdev)266492929c5SAndy Shevchenko static int mrfld_extcon_remove(struct platform_device *pdev)
267492929c5SAndy Shevchenko {
268492929c5SAndy Shevchenko 	struct mrfld_extcon_data *data = platform_get_drvdata(pdev);
269492929c5SAndy Shevchenko 
270492929c5SAndy Shevchenko 	mrfld_extcon_sw_control(data, false);
271492929c5SAndy Shevchenko 
272492929c5SAndy Shevchenko 	return 0;
273492929c5SAndy Shevchenko }
274492929c5SAndy Shevchenko 
275492929c5SAndy Shevchenko static const struct platform_device_id mrfld_extcon_id_table[] = {
276492929c5SAndy Shevchenko 	{ .name = "mrfld_bcove_pwrsrc" },
277492929c5SAndy Shevchenko 	{}
278492929c5SAndy Shevchenko };
279492929c5SAndy Shevchenko MODULE_DEVICE_TABLE(platform, mrfld_extcon_id_table);
280492929c5SAndy Shevchenko 
281492929c5SAndy Shevchenko static struct platform_driver mrfld_extcon_driver = {
282492929c5SAndy Shevchenko 	.driver = {
283492929c5SAndy Shevchenko 		.name	= "mrfld_bcove_pwrsrc",
284492929c5SAndy Shevchenko 	},
285492929c5SAndy Shevchenko 	.probe		= mrfld_extcon_probe,
286492929c5SAndy Shevchenko 	.remove		= mrfld_extcon_remove,
287492929c5SAndy Shevchenko 	.id_table	= mrfld_extcon_id_table,
288492929c5SAndy Shevchenko };
289492929c5SAndy Shevchenko module_platform_driver(mrfld_extcon_driver);
290492929c5SAndy Shevchenko 
291492929c5SAndy Shevchenko MODULE_AUTHOR("Andy Shevchenko <andriy.shevchenko@linux.intel.com>");
292492929c5SAndy Shevchenko MODULE_DESCRIPTION("extcon driver for Intel Merrifield Basin Cove PMIC");
293492929c5SAndy Shevchenko MODULE_LICENSE("GPL v2");
294