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