| 1 | |
| 2 | |
| 3 | * Copyright 2012 Samsung Electronics Co., Ltd
|
| 4 | * http://www.samsung.com
|
| 5 | * Copyright 2025 Linaro Ltd.
|
| 6 | *
|
| 7 | * Samsung SxM core driver
|
| 8 |
|
| 9 |
|
| 10 | #include <linux/device.h>
|
| 11 | #include <linux/err.h>
|
| 12 | #include <linux/export.h>
|
| 13 | #include <linux/interrupt.h>
|
| 14 | #include <linux/mfd/core.h>
|
| 15 | #include <linux/mfd/samsung/core.h>
|
| 16 | #include <linux/mfd/samsung/irq.h>
|
| 17 | #include <linux/mfd/samsung/s2mps11.h>
|
| 18 | #include <linux/mfd/samsung/s2mps13.h>
|
| 19 | #include <linux/mfd/samsung/s2mu005.h>
|
| 20 | #include <linux/module.h>
|
| 21 | #include <linux/of.h>
|
| 22 | #include <linux/pm.h>
|
| 23 | #include <linux/pm_runtime.h>
|
| 24 | #include <linux/regmap.h>
|
| 25 | #include "sec-core.h"
|
| 26 |
|
| 27 | static const struct resource s5m8767_rtc_resources[] = {
|
| 28 | DEFINE_RES_IRQ_NAMED(S5M8767_IRQ_RTCA1, "alarm"),
|
| 29 | };
|
| 30 |
|
| 31 | static const struct mfd_cell s5m8767_devs[] = {
|
| 32 | MFD_CELL_NAME("s5m8767-pmic"),
|
| 33 | MFD_CELL_RES("s5m-rtc", s5m8767_rtc_resources),
|
| 34 | MFD_CELL_OF("s5m8767-clk", NULL, NULL, 0, 0, "samsung,s5m8767-clk"),
|
| 35 | };
|
| 36 |
|
| 37 | static const struct mfd_cell s2dos05_devs[] = {
|
| 38 | MFD_CELL_NAME("s2dos05-regulator"),
|
| 39 | };
|
| 40 |
|
| 41 | static const struct resource s2mpg10_rtc_resources[] = {
|
| 42 | DEFINE_RES_IRQ_NAMED(S2MPG10_IRQ_RTCA0, "alarm"),
|
| 43 | };
|
| 44 |
|
| 45 | static const struct mfd_cell s2mpg10_devs[] = {
|
| 46 | MFD_CELL_NAME("s2mpg10-meter"),
|
| 47 | MFD_CELL_NAME("s2mpg10-regulator"),
|
| 48 | MFD_CELL_RES("s2mpg10-rtc", s2mpg10_rtc_resources),
|
| 49 | MFD_CELL_OF("s2mpg10-clk", NULL, NULL, 0, 0, "samsung,s2mpg10-clk"),
|
| 50 | MFD_CELL_OF("s2mpg10-gpio", NULL, NULL, 0, 0, "samsung,s2mpg10-gpio"),
|
| 51 | };
|
| 52 |
|
| 53 | static const struct mfd_cell s2mpg11_devs[] = {
|
| 54 | MFD_CELL_NAME("s2mpg11-meter"),
|
| 55 | MFD_CELL_NAME("s2mpg11-regulator"),
|
| 56 | MFD_CELL_OF("s2mpg11-gpio", NULL, NULL, 0, 0, "samsung,s2mpg11-gpio"),
|
| 57 | };
|
| 58 |
|
| 59 | static const struct resource s2mps11_rtc_resources[] = {
|
| 60 | DEFINE_RES_IRQ_NAMED(S2MPS11_IRQ_RTCA0, "alarm"),
|
| 61 | };
|
| 62 |
|
| 63 | static const struct mfd_cell s2mps11_devs[] = {
|
| 64 | MFD_CELL_NAME("s2mps11-regulator"),
|
| 65 | MFD_CELL_RES("s2mps14-rtc", s2mps11_rtc_resources),
|
| 66 | MFD_CELL_OF("s2mps11-clk", NULL, NULL, 0, 0, "samsung,s2mps11-clk"),
|
| 67 | };
|
| 68 |
|
| 69 | static const struct resource s2mps14_rtc_resources[] = {
|
| 70 | DEFINE_RES_IRQ_NAMED(S2MPS14_IRQ_RTCA0, "alarm"),
|
| 71 | };
|
| 72 |
|
| 73 | static const struct mfd_cell s2mps13_devs[] = {
|
| 74 | MFD_CELL_NAME("s2mps13-regulator"),
|
| 75 | MFD_CELL_RES("s2mps13-rtc", s2mps14_rtc_resources),
|
| 76 | MFD_CELL_OF("s2mps13-clk", NULL, NULL, 0, 0, "samsung,s2mps13-clk"),
|
| 77 | };
|
| 78 |
|
| 79 | static const struct mfd_cell s2mps14_devs[] = {
|
| 80 | MFD_CELL_NAME("s2mps14-regulator"),
|
| 81 | MFD_CELL_RES("s2mps14-rtc", s2mps14_rtc_resources),
|
| 82 | MFD_CELL_OF("s2mps14-clk", NULL, NULL, 0, 0, "samsung,s2mps14-clk"),
|
| 83 | };
|
| 84 |
|
| 85 | static const struct mfd_cell s2mps15_devs[] = {
|
| 86 | MFD_CELL_NAME("s2mps15-regulator"),
|
| 87 | MFD_CELL_RES("s2mps15-rtc", s2mps14_rtc_resources),
|
| 88 | MFD_CELL_OF("s2mps13-clk", NULL, NULL, 0, 0, "samsung,s2mps13-clk"),
|
| 89 | };
|
| 90 |
|
| 91 | static const struct mfd_cell s2mpa01_devs[] = {
|
| 92 | MFD_CELL_NAME("s2mpa01-pmic"),
|
| 93 | MFD_CELL_RES("s2mps14-rtc", s2mps14_rtc_resources),
|
| 94 | };
|
| 95 |
|
| 96 | static const struct mfd_cell s2mpu02_devs[] = {
|
| 97 | MFD_CELL_NAME("s2mpu02-regulator"),
|
| 98 | };
|
| 99 |
|
| 100 | static const struct resource s2mpu05_rtc_resources[] = {
|
| 101 | DEFINE_RES_IRQ_NAMED(S2MPU05_IRQ_RTCA0, "alarm"),
|
| 102 | };
|
| 103 |
|
| 104 | static const struct mfd_cell s2mpu05_devs[] = {
|
| 105 | MFD_CELL_NAME("s2mpu05-regulator"),
|
| 106 | MFD_CELL_RES("s2mps15-rtc", s2mpu05_rtc_resources),
|
| 107 | };
|
| 108 |
|
| 109 | static const struct resource s2mu005_muic_resources[] = {
|
| 110 | DEFINE_RES_IRQ_NAMED(S2MU005_IRQ_MUIC_ATTACH, "attach"),
|
| 111 | DEFINE_RES_IRQ_NAMED(S2MU005_IRQ_MUIC_DETACH, "detach"),
|
| 112 | };
|
| 113 |
|
| 114 | static const struct mfd_cell s2mu005_devs[] = {
|
| 115 | MFD_CELL_NAME("s2mu005-charger"),
|
| 116 | MFD_CELL_OF("s2mu005-flash", NULL, NULL, 0, 0, "samsung,s2mu005-flash"),
|
| 117 | MFD_CELL_OF("s2mu005-muic", s2mu005_muic_resources, NULL, 0, 0, "samsung,s2mu005-muic"),
|
| 118 | MFD_CELL_OF("s2mu005-rgb", NULL, NULL, 0, 0, "samsung,s2mu005-rgb"),
|
| 119 | };
|
| 120 |
|
| 121 | static void sec_pmic_dump_rev(struct sec_pmic_dev *sec_pmic)
|
| 122 | {
|
| 123 | unsigned int reg, mask, val;
|
| 124 |
|
| 125 | switch (sec_pmic->device_type) {
|
| 126 | case S2MPG10:
|
| 127 | case S2MPG11:
|
| 128 |
|
| 129 | return;
|
| 130 | case S2MU005:
|
| 131 | reg = S2MU005_REG_ID;
|
| 132 | mask = S2MU005_ID_MASK;
|
| 133 | break;
|
| 134 | default:
|
| 135 |
|
| 136 | reg = S2MPS11_REG_ID;
|
| 137 | mask = ~0;
|
| 138 | }
|
| 139 |
|
| 140 | if (!regmap_read(sec_pmic->regmap_pmic, reg, &val))
|
| 141 | dev_dbg(sec_pmic->dev, "Revision: 0x%x\n", field_get(mask, val));
|
| 142 | }
|
| 143 |
|
| 144 | static void sec_pmic_configure(struct sec_pmic_dev *sec_pmic)
|
| 145 | {
|
| 146 | int err;
|
| 147 |
|
| 148 | if (sec_pmic->device_type != S2MPS13X)
|
| 149 | return;
|
| 150 |
|
| 151 | if (sec_pmic->pdata->disable_wrstbi) {
|
| 152 | |
| 153 | * If WRSTBI pin is pulled down this feature must be disabled
|
| 154 | * because each Suspend to RAM will trigger buck voltage reset
|
| 155 | * to default values.
|
| 156 |
|
| 157 | err = regmap_update_bits(sec_pmic->regmap_pmic,
|
| 158 | S2MPS13_REG_WRSTBI,
|
| 159 | S2MPS13_REG_WRSTBI_MASK, 0x0);
|
| 160 | if (err)
|
| 161 | dev_warn(sec_pmic->dev,
|
| 162 | "Cannot initialize WRSTBI config: %d\n",
|
| 163 | err);
|
| 164 | }
|
| 165 | }
|
| 166 |
|
| 167 | |
| 168 | * Only the common platform data elements for s5m8767 are parsed here from the
|
| 169 | * device tree. Other sub-modules of s5m8767 such as pmic, rtc , charger and
|
| 170 | * others have to parse their own platform data elements from device tree.
|
| 171 | *
|
| 172 | * The s5m8767 platform data structure is instantiated here and the drivers for
|
| 173 | * the sub-modules need not instantiate another instance while parsing their
|
| 174 | * platform data.
|
| 175 |
|
| 176 | static struct sec_platform_data *
|
| 177 | sec_pmic_parse_dt_pdata(struct device *dev)
|
| 178 | {
|
| 179 | struct sec_platform_data *pd;
|
| 180 |
|
| 181 | pd = devm_kzalloc(dev, sizeof(*pd), GFP_KERNEL);
|
| 182 | if (!pd)
|
| 183 | return ERR_PTR(-ENOMEM);
|
| 184 |
|
| 185 | pd->manual_poweroff = of_property_read_bool(dev->of_node,
|
| 186 | "samsung,s2mps11-acokb-ground");
|
| 187 | pd->disable_wrstbi = of_property_read_bool(dev->of_node,
|
| 188 | "samsung,s2mps11-wrstbi-ground");
|
| 189 | return pd;
|
| 190 | }
|
| 191 |
|
| 192 | int sec_pmic_probe(struct device *dev, int device_type, unsigned int irq,
|
| 193 | struct regmap *regmap, struct i2c_client *client)
|
| 194 | {
|
| 195 | struct regmap_irq_chip_data *irq_data;
|
| 196 | struct sec_platform_data *pdata;
|
| 197 | const struct mfd_cell *sec_devs;
|
| 198 | struct sec_pmic_dev *sec_pmic;
|
| 199 | int ret, num_sec_devs;
|
| 200 |
|
| 201 | sec_pmic = devm_kzalloc(dev, sizeof(*sec_pmic), GFP_KERNEL);
|
| 202 | if (!sec_pmic)
|
| 203 | return -ENOMEM;
|
| 204 |
|
| 205 | dev_set_drvdata(dev, sec_pmic);
|
| 206 | sec_pmic->dev = dev;
|
| 207 | sec_pmic->device_type = device_type;
|
| 208 | sec_pmic->i2c = client;
|
| 209 | sec_pmic->irq = irq;
|
| 210 | sec_pmic->regmap_pmic = regmap;
|
| 211 |
|
| 212 | pdata = sec_pmic_parse_dt_pdata(sec_pmic->dev);
|
| 213 | if (IS_ERR(pdata)) {
|
| 214 | ret = PTR_ERR(pdata);
|
| 215 | return ret;
|
| 216 | }
|
| 217 |
|
| 218 | sec_pmic->pdata = pdata;
|
| 219 |
|
| 220 | irq_data = sec_irq_init(sec_pmic);
|
| 221 | if (IS_ERR(irq_data))
|
| 222 | return PTR_ERR(irq_data);
|
| 223 |
|
| 224 | dev->coherent_dma_mask = 0;
|
| 225 | dev->dma_mask = &dev->coherent_dma_mask;
|
| 226 |
|
| 227 | pm_runtime_set_active(sec_pmic->dev);
|
| 228 |
|
| 229 | switch (sec_pmic->device_type) {
|
| 230 | case S5M8767X:
|
| 231 | sec_devs = s5m8767_devs;
|
| 232 | num_sec_devs = ARRAY_SIZE(s5m8767_devs);
|
| 233 | break;
|
| 234 | case S2DOS05:
|
| 235 | sec_devs = s2dos05_devs;
|
| 236 | num_sec_devs = ARRAY_SIZE(s2dos05_devs);
|
| 237 | break;
|
| 238 | case S2MPA01:
|
| 239 | sec_devs = s2mpa01_devs;
|
| 240 | num_sec_devs = ARRAY_SIZE(s2mpa01_devs);
|
| 241 | break;
|
| 242 | case S2MPG10:
|
| 243 | sec_devs = s2mpg10_devs;
|
| 244 | num_sec_devs = ARRAY_SIZE(s2mpg10_devs);
|
| 245 | break;
|
| 246 | case S2MPG11:
|
| 247 | sec_devs = s2mpg11_devs;
|
| 248 | num_sec_devs = ARRAY_SIZE(s2mpg11_devs);
|
| 249 | break;
|
| 250 | case S2MPS11X:
|
| 251 | sec_devs = s2mps11_devs;
|
| 252 | num_sec_devs = ARRAY_SIZE(s2mps11_devs);
|
| 253 | break;
|
| 254 | case S2MPS13X:
|
| 255 | sec_devs = s2mps13_devs;
|
| 256 | num_sec_devs = ARRAY_SIZE(s2mps13_devs);
|
| 257 | break;
|
| 258 | case S2MPS14X:
|
| 259 | sec_devs = s2mps14_devs;
|
| 260 | num_sec_devs = ARRAY_SIZE(s2mps14_devs);
|
| 261 | break;
|
| 262 | case S2MPS15X:
|
| 263 | sec_devs = s2mps15_devs;
|
| 264 | num_sec_devs = ARRAY_SIZE(s2mps15_devs);
|
| 265 | break;
|
| 266 | case S2MPU02:
|
| 267 | sec_devs = s2mpu02_devs;
|
| 268 | num_sec_devs = ARRAY_SIZE(s2mpu02_devs);
|
| 269 | break;
|
| 270 | case S2MPU05:
|
| 271 | sec_devs = s2mpu05_devs;
|
| 272 | num_sec_devs = ARRAY_SIZE(s2mpu05_devs);
|
| 273 | break;
|
| 274 | case S2MU005:
|
| 275 | sec_devs = s2mu005_devs;
|
| 276 | num_sec_devs = ARRAY_SIZE(s2mu005_devs);
|
| 277 | break;
|
| 278 | default:
|
| 279 | return dev_err_probe(sec_pmic->dev, -EINVAL,
|
| 280 | "Unsupported device type %d\n",
|
| 281 | sec_pmic->device_type);
|
| 282 | }
|
| 283 | ret = devm_mfd_add_devices(sec_pmic->dev, -1, sec_devs, num_sec_devs,
|
| 284 | NULL, 0, regmap_irq_get_domain(irq_data));
|
| 285 | if (ret)
|
| 286 | return ret;
|
| 287 |
|
| 288 | sec_pmic_configure(sec_pmic);
|
| 289 | sec_pmic_dump_rev(sec_pmic);
|
| 290 |
|
| 291 | return ret;
|
| 292 | }
|
| 293 | EXPORT_SYMBOL_GPL(sec_pmic_probe);
|
| 294 |
|
| 295 | void sec_pmic_shutdown(struct device *dev)
|
| 296 | {
|
| 297 | struct sec_pmic_dev *sec_pmic = dev_get_drvdata(dev);
|
| 298 | unsigned int reg, mask;
|
| 299 |
|
| 300 | if (!sec_pmic->pdata->manual_poweroff)
|
| 301 | return;
|
| 302 |
|
| 303 | switch (sec_pmic->device_type) {
|
| 304 | case S2MPS11X:
|
| 305 | reg = S2MPS11_REG_CTRL1;
|
| 306 | mask = S2MPS11_CTRL1_PWRHOLD_MASK;
|
| 307 | break;
|
| 308 | default:
|
| 309 | |
| 310 | * Currently only one board with S2MPS11 needs this, so just
|
| 311 | * ignore the rest.
|
| 312 |
|
| 313 | dev_warn(sec_pmic->dev,
|
| 314 | "Unsupported device %d for manual power off\n",
|
| 315 | sec_pmic->device_type);
|
| 316 | return;
|
| 317 | }
|
| 318 |
|
| 319 | regmap_update_bits(sec_pmic->regmap_pmic, reg, mask, 0);
|
| 320 | }
|
| 321 | EXPORT_SYMBOL_GPL(sec_pmic_shutdown);
|
| 322 |
|
| 323 | static int sec_pmic_suspend(struct device *dev)
|
| 324 | {
|
| 325 | struct sec_pmic_dev *sec_pmic = dev_get_drvdata(dev);
|
| 326 |
|
| 327 | if (device_may_wakeup(dev))
|
| 328 | enable_irq_wake(sec_pmic->irq);
|
| 329 | |
| 330 | * PMIC IRQ must be disabled during suspend for RTC alarm
|
| 331 | * to work properly.
|
| 332 | * When device is woken up from suspend, an
|
| 333 | * interrupt occurs before resuming I2C bus controller.
|
| 334 | * The interrupt is handled by regmap_irq_thread which tries
|
| 335 | * to read RTC registers. This read fails (I2C is still
|
| 336 | * suspended) and RTC Alarm interrupt is disabled.
|
| 337 |
|
| 338 | disable_irq(sec_pmic->irq);
|
| 339 |
|
| 340 | return 0;
|
| 341 | }
|
| 342 |
|
| 343 | static int sec_pmic_resume(struct device *dev)
|
| 344 | {
|
| 345 | struct sec_pmic_dev *sec_pmic = dev_get_drvdata(dev);
|
| 346 |
|
| 347 | if (device_may_wakeup(dev))
|
| 348 | disable_irq_wake(sec_pmic->irq);
|
| 349 | enable_irq(sec_pmic->irq);
|
| 350 |
|
| 351 | return 0;
|
| 352 | }
|
| 353 |
|
| 354 | DEFINE_SIMPLE_DEV_PM_OPS(sec_pmic_pm_ops, sec_pmic_suspend, sec_pmic_resume);
|
| 355 | EXPORT_SYMBOL_GPL(sec_pmic_pm_ops);
|
| 356 |
|
| 357 | MODULE_AUTHOR("Chanwoo Choi <[email protected]>");
|
| 358 | MODULE_AUTHOR("Krzysztof Kozlowski <[email protected]>");
|
| 359 | MODULE_AUTHOR("Sangbeom Kim <[email protected]>");
|
| 360 | MODULE_AUTHOR("André Draszik <[email protected]>");
|
| 361 | MODULE_DESCRIPTION("Core driver for the Samsung S5M");
|
| 362 | MODULE_LICENSE("GPL");
|