xref: /linux/drivers/power/supply/s2m-charger.c (revision 3d5e48944e824bddc20d7b874e784f7b279636fe)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Battery Charger Driver for Samsung S2M series PMICs.
4  *
5  * Copyright (c) 2015 Samsung Electronics Co., Ltd
6  * Copyright (c) 2026 Kaustabh Chakraborty <kauschluss@disroot.org>
7  * Copyright (c) 2026 Łukasz Lebiedziński <kernel@lvkasz.us>
8  */
9 
10 #include <linux/devm-helpers.h>
11 #include <linux/extcon.h>
12 #include <linux/mfd/samsung/core.h>
13 #include <linux/mfd/samsung/s2mu005.h>
14 #include <linux/module.h>
15 #include <linux/of.h>
16 #include <linux/of_graph.h>
17 #include <linux/platform_device.h>
18 #include <linux/power_supply.h>
19 #include <linux/regmap.h>
20 
21 struct s2m_chgr {
22 	struct device *dev;
23 	struct regmap *regmap;
24 	struct power_supply *psy;
25 	struct extcon_dev *extcon;
26 	struct work_struct extcon_work;
27 	struct notifier_block extcon_nb;
28 };
29 
30 static int s2mu005_chgr_get_online(struct s2m_chgr *priv, int *value)
31 {
32 	u32 val;
33 	int ret;
34 
35 	ret = regmap_read(priv->regmap, S2MU005_REG_CHGR_STATUS0, &val);
36 	if (ret) {
37 		dev_err(priv->dev, "failed to read register (%d)\n", ret);
38 		return ret;
39 	}
40 
41 	*value = !!(val & S2MU005_CHGR_CHG);
42 
43 	return 0;
44 }
45 
46 static void s2mu005_chgr_get_usb_type(struct s2m_chgr *priv, int *value)
47 {
48 	if (extcon_get_state(priv->extcon, EXTCON_CHG_USB_CDP) > 0)
49 		*value = POWER_SUPPLY_USB_TYPE_CDP;
50 	else if (extcon_get_state(priv->extcon, EXTCON_CHG_USB_SDP) > 0)
51 		*value = POWER_SUPPLY_USB_TYPE_SDP;
52 	else if (extcon_get_state(priv->extcon, EXTCON_CHG_USB_DCP) > 0)
53 		*value = POWER_SUPPLY_USB_TYPE_DCP;
54 	else
55 		*value = POWER_SUPPLY_USB_TYPE_UNKNOWN;
56 }
57 
58 static int s2mu005_chgr_get_property(struct power_supply *psy,
59 				     enum power_supply_property psp,
60 				     union power_supply_propval *val)
61 {
62 	struct s2m_chgr *priv = power_supply_get_drvdata(psy);
63 	int ret;
64 
65 	switch (psp) {
66 	case POWER_SUPPLY_PROP_ONLINE:
67 		ret = s2mu005_chgr_get_online(priv, &val->intval);
68 		if (ret)
69 			return ret;
70 		break;
71 	case POWER_SUPPLY_PROP_USB_TYPE:
72 		s2mu005_chgr_get_usb_type(priv, &val->intval);
73 		break;
74 	default:
75 		return -EINVAL;
76 	}
77 
78 	return 0;
79 }
80 
81 static int s2mu005_chgr_mode_set_host(struct s2m_chgr *priv)
82 {
83 	int ret;
84 
85 	/* set mode to OTG */
86 	ret = regmap_update_bits(priv->regmap, S2MU005_REG_CHGR_CTRL0,
87 				 S2MU005_CHGR_OP_MODE,
88 				 FIELD_PREP(S2MU005_CHGR_OP_MODE,
89 					    S2MU005_CHGR_OP_MODE_OTG));
90 	if (ret) {
91 		dev_err(priv->dev, "failed to set OTG mode (%d)\n", ret);
92 		return ret;
93 	}
94 
95 	/* set boost frequency to 2MHz */
96 	ret = regmap_update_bits(priv->regmap, S2MU005_REG_CHGR_CTRL11,
97 				 S2MU005_CHGR_OSC_BOOST,
98 				 FIELD_PREP(S2MU005_CHGR_OSC_BOOST,
99 					    S2MU005_CHGR_OSC_BOOST_2MHZ));
100 	if (ret) {
101 		dev_err(priv->dev, "failed to set boost frequency (%d)\n", ret);
102 		return ret;
103 	}
104 
105 	/* set OTG current limit to 1.5 A */
106 	ret = regmap_update_bits(priv->regmap, S2MU005_REG_CHGR_CTRL4,
107 				 S2MU005_CHGR_OTG_OCP,
108 				 FIELD_PREP(S2MU005_CHGR_OTG_OCP,
109 					    S2MU005_CHGR_OTG_OCP_1P5A));
110 	if (ret) {
111 		dev_err(priv->dev, "failed to set OTG current limit (%d)\n", ret);
112 		return ret;
113 	}
114 
115 	/* VBUS switches are OFF when OTG over-current happens */
116 	ret = regmap_set_bits(priv->regmap, S2MU005_REG_CHGR_CTRL4,
117 			      S2MU005_CHGR_OTG_OCP_OFF);
118 	if (ret) {
119 		dev_err(priv->dev, "failed to set OTG OCP switch (%d)\n", ret);
120 		return ret;
121 	}
122 
123 	/* set OTG voltage to 5.1 V */
124 	ret = regmap_update_bits(priv->regmap, S2MU005_REG_CHGR_CTRL5,
125 				 S2MU005_CHGR_VMID_BOOST,
126 				 FIELD_PREP(S2MU005_CHGR_VMID_BOOST,
127 					    S2MU005_CHGR_VMID_BOOST_5P1V));
128 	if (ret) {
129 		dev_err(priv->dev, "failed to set OTG voltage (%d)\n", ret);
130 		return ret;
131 	}
132 
133 	/* turn on OTG */
134 	ret = regmap_update_bits(priv->regmap, S2MU005_REG_CHGR_CTRL15,
135 				 S2MU005_CHGR_OTG_EN,
136 				 FIELD_PREP(S2MU005_CHGR_OTG_EN,
137 					    S2MU005_CHGR_OTG_EN_ON));
138 	if (ret) {
139 		dev_err(priv->dev, "failed to turn on OTG (%d)\n", ret);
140 		return ret;
141 	}
142 
143 	return 0;
144 }
145 
146 static int s2mu005_chgr_mode_set_charger(struct s2m_chgr *priv)
147 {
148 	int ret;
149 
150 	/* first reset to mode 0 */
151 	ret = regmap_clear_bits(priv->regmap, S2MU005_REG_CHGR_CTRL0,
152 				S2MU005_CHGR_OP_MODE);
153 	if (ret) {
154 		dev_err(priv->dev, "failed to reset opmode (%d)\n", ret);
155 		return ret;
156 	}
157 
158 	/* wait for the charger to settle before switching to charging mode */
159 	msleep(50);
160 	/* then set to charging mode */
161 	ret = regmap_update_bits(priv->regmap, S2MU005_REG_CHGR_CTRL0,
162 				 S2MU005_CHGR_OP_MODE,
163 				 FIELD_PREP(S2MU005_CHGR_OP_MODE,
164 					    S2MU005_CHGR_OP_MODE_CHG));
165 	if (ret) {
166 		dev_err(priv->dev, "failed to set opmode to charging (%d)\n", ret);
167 		return ret;
168 	}
169 
170 	return 0;
171 }
172 
173 static int s2mu005_chgr_mode_unset(struct s2m_chgr *priv)
174 {
175 	int ret;
176 
177 	/* turn off OTG */
178 	ret = regmap_clear_bits(priv->regmap, S2MU005_REG_CHGR_CTRL15,
179 				S2MU005_CHGR_OTG_EN);
180 	if (ret) {
181 		dev_err(priv->dev, "failed to turn off OTG (%d)\n", ret);
182 		return ret;
183 	}
184 
185 	/* reset operation mode */
186 	ret = regmap_clear_bits(priv->regmap, S2MU005_REG_CHGR_CTRL0,
187 				S2MU005_CHGR_OP_MODE);
188 	if (ret) {
189 		dev_err(priv->dev, "failed to reset opmode (%d)\n", ret);
190 		return ret;
191 	}
192 
193 	return 0;
194 }
195 
196 static void s2mu005_chgr_extcon_work(struct work_struct *work)
197 {
198 	struct s2m_chgr *priv = container_of(work, struct s2m_chgr, extcon_work);
199 
200 	if (extcon_get_state(priv->extcon, EXTCON_USB_HOST) > 0)
201 		s2mu005_chgr_mode_set_host(priv);
202 	else if (extcon_get_state(priv->extcon, EXTCON_USB) > 0)
203 		s2mu005_chgr_mode_set_charger(priv);
204 	else
205 		s2mu005_chgr_mode_unset(priv);
206 
207 	power_supply_changed(priv->psy);
208 }
209 
210 static const enum power_supply_property s2mu005_chgr_properties[] = {
211 	POWER_SUPPLY_PROP_ONLINE,
212 	POWER_SUPPLY_PROP_USB_TYPE,
213 };
214 
215 static const struct power_supply_desc s2mu005_chgr_psy_desc = {
216 	.name = "s2mu005-charger",
217 	.type = POWER_SUPPLY_TYPE_USB,
218 	.properties = s2mu005_chgr_properties,
219 	.num_properties = ARRAY_SIZE(s2mu005_chgr_properties),
220 	.get_property = s2mu005_chgr_get_property,
221 	.usb_types = BIT(POWER_SUPPLY_USB_TYPE_CDP) |
222 		     BIT(POWER_SUPPLY_USB_TYPE_SDP) |
223 		     BIT(POWER_SUPPLY_USB_TYPE_DCP) |
224 		     BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN),
225 };
226 
227 static int s2m_chgr_extcon_notifier(struct notifier_block *nb,
228 					unsigned long event, void *param)
229 {
230 	struct s2m_chgr *priv = container_of(nb, struct s2m_chgr, extcon_nb);
231 
232 	schedule_work(&priv->extcon_work);
233 
234 	return NOTIFY_OK;
235 }
236 
237 static int s2m_chgr_probe(struct platform_device *pdev)
238 {
239 	struct device *dev = &pdev->dev;
240 	struct sec_pmic_dev *pmic_drvdata = dev_get_drvdata(dev->parent);
241 	struct s2m_chgr *priv;
242 	struct device_node *extcon_node __free(device_node) = NULL;
243 	struct power_supply_config psy_cfg = {};
244 	const struct power_supply_desc *psy_desc;
245 	work_func_t extcon_work_func;
246 	int ret;
247 
248 	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
249 	if (!priv)
250 		return -ENOMEM;
251 
252 	platform_set_drvdata(pdev, priv);
253 	priv->dev = dev;
254 	priv->regmap = pmic_drvdata->regmap_pmic;
255 
256 	switch (platform_get_device_id(pdev)->driver_data) {
257 	case S2MU005:
258 		psy_desc = &s2mu005_chgr_psy_desc;
259 		extcon_work_func = s2mu005_chgr_extcon_work;
260 		break;
261 	default:
262 		return dev_err_probe(dev, -ENODEV,
263 				     "device type %d is not supported by driver\n",
264 				     pmic_drvdata->device_type);
265 	}
266 
267 	/* MUIC is mandatory. If unavailable, request probe deferral */
268 	extcon_node = of_get_child_by_name(dev->parent->of_node, "muic");
269 	if (!extcon_node)
270 		return dev_err_probe(dev, -ENODEV, "MUIC node required but not found\n");
271 
272 	priv->extcon = extcon_find_edev_by_node(extcon_node);
273 	if (IS_ERR(priv->extcon))
274 		return -EPROBE_DEFER;
275 
276 	psy_cfg.drv_data = priv;
277 	psy_cfg.fwnode = dev_fwnode(dev->parent);
278 	priv->psy = devm_power_supply_register(dev, psy_desc, &psy_cfg);
279 	if (IS_ERR(priv->psy))
280 		return dev_err_probe(dev, PTR_ERR(priv->psy),
281 				     "failed to register power supply subsystem\n");
282 
283 	ret = devm_work_autocancel(dev, &priv->extcon_work, extcon_work_func);
284 	if (ret)
285 		return dev_err_probe(dev, ret, "failed to initialize extcon work\n");
286 
287 	priv->extcon_nb.notifier_call = s2m_chgr_extcon_notifier;
288 	ret = devm_extcon_register_notifier_all(dev, priv->extcon, &priv->extcon_nb);
289 	if (ret)
290 		return dev_err_probe(dev, ret, "failed to register extcon notifier\n");
291 
292 	return 0;
293 }
294 
295 static const struct platform_device_id s2m_chgr_id_table[] = {
296 	{ "s2mu005-charger", S2MU005 },
297 	{ /* sentinel */ },
298 };
299 MODULE_DEVICE_TABLE(platform, s2m_chgr_id_table);
300 
301 static struct platform_driver s2m_chgr_driver = {
302 	.driver = {
303 		.name = "s2m-charger",
304 	},
305 	.probe = s2m_chgr_probe,
306 	.id_table = s2m_chgr_id_table,
307 };
308 module_platform_driver(s2m_chgr_driver);
309 
310 MODULE_DESCRIPTION("Battery Charger Driver For Samsung S2M Series PMICs");
311 MODULE_AUTHOR("Kaustabh Chakraborty <kauschluss@disroot.org>");
312 MODULE_AUTHOR("Łukasz Lebiedziński <kernel@lvkasz.us>");
313 MODULE_LICENSE("GPL");
314