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