Marek Vasut | 5d9b83d | 2022-05-20 05:10:17 +0200 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 2 | /* |
| 3 | * NXP PCA9450 regulator driver |
| 4 | * Copyright (C) 2022 Marek Vasut <marex@denx.de> |
| 5 | * |
| 6 | * Largely based on: |
| 7 | * ROHM BD71837 regulator driver |
| 8 | */ |
| 9 | |
Marek Vasut | 5d9b83d | 2022-05-20 05:10:17 +0200 | [diff] [blame] | 10 | #include <dm.h> |
| 11 | #include <log.h> |
| 12 | #include <linux/bitops.h> |
| 13 | #include <power/pca9450.h> |
| 14 | #include <power/pmic.h> |
| 15 | #include <power/regulator.h> |
| 16 | |
| 17 | #define HW_STATE_CONTROL 0 |
| 18 | #define DEBUG |
| 19 | |
| 20 | /** |
| 21 | * struct pca9450_vrange - describe linear range of voltages |
| 22 | * |
| 23 | * @min_volt: smallest voltage in range |
| 24 | * @step: how much voltage changes at each selector step |
| 25 | * @min_sel: smallest selector in the range |
| 26 | * @max_sel: maximum selector in the range |
| 27 | */ |
| 28 | struct pca9450_vrange { |
| 29 | unsigned int min_volt; |
| 30 | unsigned int step; |
| 31 | u8 min_sel; |
| 32 | u8 max_sel; |
| 33 | }; |
| 34 | |
| 35 | /** |
| 36 | * struct pca9450_plat - describe regulator control registers |
| 37 | * |
| 38 | * @name: name of the regulator. Used for matching the dt-entry |
| 39 | * @enable_reg: register address used to enable/disable regulator |
| 40 | * @enablemask: register mask used to enable/disable regulator |
| 41 | * @volt_reg: register address used to configure regulator voltage |
| 42 | * @volt_mask: register mask used to configure regulator voltage |
| 43 | * @ranges: pointer to ranges of regulator voltages and matching register |
| 44 | * values |
| 45 | * @numranges: number of voltage ranges pointed by ranges |
Marek Vasut | 5d9b83d | 2022-05-20 05:10:17 +0200 | [diff] [blame] | 46 | */ |
| 47 | struct pca9450_plat { |
| 48 | const char *name; |
| 49 | u8 enable_reg; |
| 50 | u8 enablemask; |
| 51 | u8 volt_reg; |
| 52 | u8 volt_mask; |
| 53 | struct pca9450_vrange *ranges; |
| 54 | unsigned int numranges; |
Marek Vasut | 5d9b83d | 2022-05-20 05:10:17 +0200 | [diff] [blame] | 55 | }; |
| 56 | |
| 57 | #define PCA_RANGE(_min, _vstep, _sel_low, _sel_hi) \ |
| 58 | { \ |
| 59 | .min_volt = (_min), .step = (_vstep), \ |
| 60 | .min_sel = (_sel_low), .max_sel = (_sel_hi), \ |
| 61 | } |
| 62 | |
Heiko Thiery | 91c89d3 | 2022-06-20 05:49:49 +0200 | [diff] [blame] | 63 | #define PCA_DATA(_name, enreg, enmask, vreg, vmask, _range) \ |
Marek Vasut | 5d9b83d | 2022-05-20 05:10:17 +0200 | [diff] [blame] | 64 | { \ |
| 65 | .name = (_name), .enable_reg = (enreg), .enablemask = (enmask), \ |
| 66 | .volt_reg = (vreg), .volt_mask = (vmask), .ranges = (_range), \ |
Heiko Thiery | 91c89d3 | 2022-06-20 05:49:49 +0200 | [diff] [blame] | 67 | .numranges = ARRAY_SIZE(_range) \ |
Marek Vasut | 5d9b83d | 2022-05-20 05:10:17 +0200 | [diff] [blame] | 68 | } |
| 69 | |
| 70 | static struct pca9450_vrange pca9450_buck123_vranges[] = { |
| 71 | PCA_RANGE(600000, 12500, 0, 0x7f), |
| 72 | }; |
| 73 | |
| 74 | static struct pca9450_vrange pca9450_buck456_vranges[] = { |
| 75 | PCA_RANGE(600000, 25000, 0, 0x70), |
| 76 | PCA_RANGE(3400000, 0, 0x71, 0x7f), |
| 77 | }; |
| 78 | |
| 79 | static struct pca9450_vrange pca9450_ldo1_vranges[] = { |
| 80 | PCA_RANGE(1600000, 100000, 0x0, 0x3), |
| 81 | PCA_RANGE(3000000, 100000, 0x4, 0x7), |
| 82 | }; |
| 83 | |
| 84 | static struct pca9450_vrange pca9450_ldo2_vranges[] = { |
| 85 | PCA_RANGE(800000, 50000, 0x0, 0x7), |
| 86 | }; |
| 87 | |
| 88 | static struct pca9450_vrange pca9450_ldo34_vranges[] = { |
| 89 | PCA_RANGE(800000, 100000, 0x0, 0x19), |
| 90 | PCA_RANGE(3300000, 0, 0x1a, 0x1f), |
| 91 | }; |
| 92 | |
| 93 | static struct pca9450_vrange pca9450_ldo5_vranges[] = { |
| 94 | PCA_RANGE(1800000, 100000, 0x0, 0xf), |
| 95 | }; |
| 96 | |
| 97 | /* |
| 98 | * We use enable mask 'HW_STATE_CONTROL' to indicate that this regulator |
| 99 | * must not be enabled or disabled by SW. The typical use-case for PCA9450 |
| 100 | * is powering NXP i.MX8. In this use-case we (for now) only allow control |
| 101 | * for BUCK4, BUCK5, BUCK6 which are not boot critical. |
| 102 | */ |
| 103 | static struct pca9450_plat pca9450_reg_data[] = { |
| 104 | /* Bucks 1-3 which support dynamic voltage scaling */ |
| 105 | PCA_DATA("BUCK1", PCA9450_BUCK1CTRL, HW_STATE_CONTROL, |
| 106 | PCA9450_BUCK1OUT_DVS0, PCA9450_DVS_BUCK_RUN_MASK, |
Heiko Thiery | 91c89d3 | 2022-06-20 05:49:49 +0200 | [diff] [blame] | 107 | pca9450_buck123_vranges), |
Marek Vasut | 5d9b83d | 2022-05-20 05:10:17 +0200 | [diff] [blame] | 108 | PCA_DATA("BUCK2", PCA9450_BUCK2CTRL, HW_STATE_CONTROL, |
| 109 | PCA9450_BUCK2OUT_DVS0, PCA9450_DVS_BUCK_RUN_MASK, |
Heiko Thiery | 91c89d3 | 2022-06-20 05:49:49 +0200 | [diff] [blame] | 110 | pca9450_buck123_vranges), |
Marek Vasut | 5d9b83d | 2022-05-20 05:10:17 +0200 | [diff] [blame] | 111 | PCA_DATA("BUCK3", PCA9450_BUCK3CTRL, HW_STATE_CONTROL, |
| 112 | PCA9450_BUCK3OUT_DVS0, PCA9450_DVS_BUCK_RUN_MASK, |
Heiko Thiery | 91c89d3 | 2022-06-20 05:49:49 +0200 | [diff] [blame] | 113 | pca9450_buck123_vranges), |
Marek Vasut | 5d9b83d | 2022-05-20 05:10:17 +0200 | [diff] [blame] | 114 | /* Bucks 4-6 which do not support dynamic voltage scaling */ |
| 115 | PCA_DATA("BUCK4", PCA9450_BUCK4CTRL, HW_STATE_CONTROL, |
| 116 | PCA9450_BUCK4OUT, PCA9450_DVS_BUCK_RUN_MASK, |
Heiko Thiery | 91c89d3 | 2022-06-20 05:49:49 +0200 | [diff] [blame] | 117 | pca9450_buck456_vranges), |
Marek Vasut | 5d9b83d | 2022-05-20 05:10:17 +0200 | [diff] [blame] | 118 | PCA_DATA("BUCK5", PCA9450_BUCK5CTRL, HW_STATE_CONTROL, |
| 119 | PCA9450_BUCK5OUT, PCA9450_DVS_BUCK_RUN_MASK, |
Heiko Thiery | 91c89d3 | 2022-06-20 05:49:49 +0200 | [diff] [blame] | 120 | pca9450_buck456_vranges), |
Marek Vasut | 5d9b83d | 2022-05-20 05:10:17 +0200 | [diff] [blame] | 121 | PCA_DATA("BUCK6", PCA9450_BUCK6CTRL, HW_STATE_CONTROL, |
| 122 | PCA9450_BUCK6OUT, PCA9450_DVS_BUCK_RUN_MASK, |
Heiko Thiery | 91c89d3 | 2022-06-20 05:49:49 +0200 | [diff] [blame] | 123 | pca9450_buck456_vranges), |
Marek Vasut | 5d9b83d | 2022-05-20 05:10:17 +0200 | [diff] [blame] | 124 | /* LDOs */ |
| 125 | PCA_DATA("LDO1", PCA9450_LDO1CTRL, HW_STATE_CONTROL, |
| 126 | PCA9450_LDO1CTRL, PCA9450_LDO12_MASK, |
Heiko Thiery | 91c89d3 | 2022-06-20 05:49:49 +0200 | [diff] [blame] | 127 | pca9450_ldo1_vranges), |
Marek Vasut | 5d9b83d | 2022-05-20 05:10:17 +0200 | [diff] [blame] | 128 | PCA_DATA("LDO2", PCA9450_LDO2CTRL, HW_STATE_CONTROL, |
| 129 | PCA9450_LDO2CTRL, PCA9450_LDO12_MASK, |
Heiko Thiery | 91c89d3 | 2022-06-20 05:49:49 +0200 | [diff] [blame] | 130 | pca9450_ldo2_vranges), |
Marek Vasut | 5d9b83d | 2022-05-20 05:10:17 +0200 | [diff] [blame] | 131 | PCA_DATA("LDO3", PCA9450_LDO3CTRL, HW_STATE_CONTROL, |
| 132 | PCA9450_LDO3CTRL, PCA9450_LDO34_MASK, |
Heiko Thiery | 91c89d3 | 2022-06-20 05:49:49 +0200 | [diff] [blame] | 133 | pca9450_ldo34_vranges), |
Marek Vasut | 5d9b83d | 2022-05-20 05:10:17 +0200 | [diff] [blame] | 134 | PCA_DATA("LDO4", PCA9450_LDO4CTRL, HW_STATE_CONTROL, |
| 135 | PCA9450_LDO4CTRL, PCA9450_LDO34_MASK, |
Heiko Thiery | 91c89d3 | 2022-06-20 05:49:49 +0200 | [diff] [blame] | 136 | pca9450_ldo34_vranges), |
Marek Vasut | 5d9b83d | 2022-05-20 05:10:17 +0200 | [diff] [blame] | 137 | PCA_DATA("LDO5", PCA9450_LDO5CTRL_H, HW_STATE_CONTROL, |
| 138 | PCA9450_LDO5CTRL_H, PCA9450_LDO5_MASK, |
Heiko Thiery | 91c89d3 | 2022-06-20 05:49:49 +0200 | [diff] [blame] | 139 | pca9450_ldo5_vranges), |
Marek Vasut | 5d9b83d | 2022-05-20 05:10:17 +0200 | [diff] [blame] | 140 | }; |
| 141 | |
| 142 | static int vrange_find_value(struct pca9450_vrange *r, unsigned int sel, |
| 143 | unsigned int *val) |
| 144 | { |
| 145 | if (!val || sel < r->min_sel || sel > r->max_sel) |
| 146 | return -EINVAL; |
| 147 | |
| 148 | *val = r->min_volt + r->step * (sel - r->min_sel); |
| 149 | return 0; |
| 150 | } |
| 151 | |
| 152 | static int vrange_find_selector(struct pca9450_vrange *r, int val, |
| 153 | unsigned int *sel) |
| 154 | { |
| 155 | int ret = -EINVAL; |
| 156 | int num_vals = r->max_sel - r->min_sel + 1; |
| 157 | |
| 158 | if (val >= r->min_volt && |
| 159 | val <= r->min_volt + r->step * (num_vals - 1)) { |
| 160 | if (r->step) { |
| 161 | *sel = r->min_sel + ((val - r->min_volt) / r->step); |
| 162 | ret = 0; |
| 163 | } else { |
| 164 | *sel = r->min_sel; |
| 165 | ret = 0; |
| 166 | } |
| 167 | } |
| 168 | return ret; |
| 169 | } |
| 170 | |
| 171 | static int pca9450_get_enable(struct udevice *dev) |
| 172 | { |
| 173 | struct pca9450_plat *plat = dev_get_plat(dev); |
| 174 | int val; |
| 175 | |
| 176 | /* |
| 177 | * boot critical regulators on pca9450 must not be controlled by sw |
| 178 | * due to the 'feature' which leaves power rails down if pca9450 is |
| 179 | * reseted to snvs state. hence we can't get the state here. |
| 180 | * |
| 181 | * if we are alive it means we probably are on run state and |
| 182 | * if the regulator can't be controlled we can assume it is |
| 183 | * enabled. |
| 184 | */ |
| 185 | if (plat->enablemask == HW_STATE_CONTROL) |
| 186 | return 1; |
| 187 | |
| 188 | val = pmic_reg_read(dev->parent, plat->enable_reg); |
| 189 | if (val < 0) |
| 190 | return val; |
| 191 | |
| 192 | return (val & plat->enablemask); |
| 193 | } |
| 194 | |
| 195 | static int pca9450_set_enable(struct udevice *dev, bool enable) |
| 196 | { |
| 197 | int val = 0; |
| 198 | struct pca9450_plat *plat = dev_get_plat(dev); |
| 199 | |
| 200 | /* |
| 201 | * boot critical regulators on pca9450 must not be controlled by sw |
| 202 | * due to the 'feature' which leaves power rails down if pca9450 is |
| 203 | * reseted to snvs state. Hence we can't set the state here. |
| 204 | */ |
| 205 | if (plat->enablemask == HW_STATE_CONTROL) |
| 206 | return enable ? 0 : -EINVAL; |
| 207 | |
| 208 | if (enable) |
| 209 | val = plat->enablemask; |
| 210 | |
| 211 | return pmic_clrsetbits(dev->parent, plat->enable_reg, plat->enablemask, |
| 212 | val); |
| 213 | } |
| 214 | |
| 215 | static int pca9450_get_value(struct udevice *dev) |
| 216 | { |
| 217 | struct pca9450_plat *plat = dev_get_plat(dev); |
| 218 | unsigned int reg, tmp; |
| 219 | int i, ret; |
| 220 | |
| 221 | ret = pmic_reg_read(dev->parent, plat->volt_reg); |
| 222 | if (ret < 0) |
| 223 | return ret; |
| 224 | |
| 225 | reg = ret; |
| 226 | reg &= plat->volt_mask; |
| 227 | |
| 228 | for (i = 0; i < plat->numranges; i++) { |
| 229 | struct pca9450_vrange *r = &plat->ranges[i]; |
| 230 | |
| 231 | if (!vrange_find_value(r, reg, &tmp)) |
| 232 | return tmp; |
| 233 | } |
| 234 | |
| 235 | pr_err("Unknown voltage value read from pmic\n"); |
| 236 | |
| 237 | return -EINVAL; |
| 238 | } |
| 239 | |
| 240 | static int pca9450_set_value(struct udevice *dev, int uvolt) |
| 241 | { |
| 242 | struct pca9450_plat *plat = dev_get_plat(dev); |
| 243 | unsigned int sel; |
| 244 | int i, found = 0; |
| 245 | |
Marek Vasut | 5d9b83d | 2022-05-20 05:10:17 +0200 | [diff] [blame] | 246 | for (i = 0; i < plat->numranges; i++) { |
| 247 | struct pca9450_vrange *r = &plat->ranges[i]; |
| 248 | |
| 249 | found = !vrange_find_selector(r, uvolt, &sel); |
| 250 | if (found) { |
| 251 | unsigned int tmp; |
| 252 | |
| 253 | /* |
| 254 | * We require exactly the requested value to be |
| 255 | * supported - this can be changed later if needed |
| 256 | */ |
| 257 | found = !vrange_find_value(r, sel, &tmp); |
| 258 | if (found && tmp == uvolt) |
| 259 | break; |
| 260 | found = 0; |
| 261 | } |
| 262 | } |
| 263 | |
| 264 | if (!found) |
| 265 | return -EINVAL; |
| 266 | |
| 267 | return pmic_clrsetbits(dev->parent, plat->volt_reg, |
| 268 | plat->volt_mask, sel); |
| 269 | } |
| 270 | |
| 271 | static int pca9450_regulator_probe(struct udevice *dev) |
| 272 | { |
| 273 | struct pca9450_plat *plat = dev_get_plat(dev); |
| 274 | int i, type; |
| 275 | |
| 276 | type = dev_get_driver_data(dev_get_parent(dev)); |
| 277 | |
Ye Li | b2ced04 | 2023-02-03 18:24:36 +0800 | [diff] [blame] | 278 | if (type != NXP_CHIP_TYPE_PCA9450A && type != NXP_CHIP_TYPE_PCA9450BC && |
| 279 | type != NXP_CHIP_TYPE_PCA9451A) { |
Marek Vasut | 5d9b83d | 2022-05-20 05:10:17 +0200 | [diff] [blame] | 280 | debug("Unknown PMIC type\n"); |
| 281 | return -EINVAL; |
| 282 | } |
| 283 | |
| 284 | for (i = 0; i < ARRAY_SIZE(pca9450_reg_data); i++) { |
| 285 | if (strcmp(dev->name, pca9450_reg_data[i].name)) |
| 286 | continue; |
| 287 | |
| 288 | /* PCA9450B/PCA9450C uses BUCK1 and BUCK3 in dual-phase */ |
| 289 | if (type == NXP_CHIP_TYPE_PCA9450BC && |
| 290 | !strcmp(pca9450_reg_data[i].name, "BUCK3")) { |
| 291 | continue; |
| 292 | } |
| 293 | |
Ye Li | b2ced04 | 2023-02-03 18:24:36 +0800 | [diff] [blame] | 294 | /* PCA9451A uses BUCK3 in dual-phase and don't have LDO2 and LDO3 */ |
| 295 | if (type == NXP_CHIP_TYPE_PCA9451A && |
| 296 | (!strcmp(pca9450_reg_data[i].name, "BUCK3") || |
| 297 | !strcmp(pca9450_reg_data[i].name, "LDO2") || |
| 298 | !strcmp(pca9450_reg_data[i].name, "LDO3"))) { |
| 299 | continue; |
| 300 | } |
| 301 | |
Marek Vasut | 5d9b83d | 2022-05-20 05:10:17 +0200 | [diff] [blame] | 302 | *plat = pca9450_reg_data[i]; |
| 303 | |
| 304 | return 0; |
| 305 | } |
| 306 | |
| 307 | pr_err("Unknown regulator '%s'\n", dev->name); |
| 308 | |
| 309 | return -ENOENT; |
| 310 | } |
| 311 | |
| 312 | static const struct dm_regulator_ops pca9450_regulator_ops = { |
| 313 | .get_value = pca9450_get_value, |
| 314 | .set_value = pca9450_set_value, |
| 315 | .get_enable = pca9450_get_enable, |
| 316 | .set_enable = pca9450_set_enable, |
| 317 | }; |
| 318 | |
| 319 | U_BOOT_DRIVER(pca9450_regulator) = { |
| 320 | .name = PCA9450_REGULATOR_DRIVER, |
| 321 | .id = UCLASS_REGULATOR, |
| 322 | .ops = &pca9450_regulator_ops, |
| 323 | .probe = pca9450_regulator_probe, |
| 324 | .plat_auto = sizeof(struct pca9450_plat), |
| 325 | }; |