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