Hsin-Hsiung Wang | 7aa382c | 2021-02-07 14:14:16 +0800 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | // |
| 3 | // Copyright (c) 2021 MediaTek Inc. |
| 4 | |
| 5 | #include <linux/module.h> |
| 6 | #include <linux/of_device.h> |
| 7 | #include <linux/regmap.h> |
| 8 | #include <linux/regulator/driver.h> |
| 9 | #include <linux/regulator/machine.h> |
| 10 | #include <linux/regulator/mt6315-regulator.h> |
| 11 | #include <linux/regulator/of_regulator.h> |
| 12 | #include <linux/spmi.h> |
| 13 | |
| 14 | #define MT6315_BUCK_MODE_AUTO 0 |
| 15 | #define MT6315_BUCK_MODE_FORCE_PWM 1 |
| 16 | #define MT6315_BUCK_MODE_LP 2 |
| 17 | |
| 18 | struct mt6315_regulator_info { |
| 19 | struct regulator_desc desc; |
| 20 | u32 status_reg; |
| 21 | u32 lp_mode_mask; |
| 22 | u32 lp_mode_shift; |
| 23 | }; |
| 24 | |
| 25 | struct mt_regulator_init_data { |
| 26 | u32 modeset_mask[MT6315_VBUCK_MAX]; |
| 27 | }; |
| 28 | |
| 29 | struct mt6315_chip { |
| 30 | struct device *dev; |
| 31 | struct regmap *regmap; |
| 32 | }; |
| 33 | |
| 34 | #define MT_BUCK(_name, _bid, _vsel) \ |
| 35 | [_bid] = { \ |
| 36 | .desc = { \ |
| 37 | .name = _name, \ |
| 38 | .of_match = of_match_ptr(_name), \ |
| 39 | .regulators_node = "regulators", \ |
| 40 | .ops = &mt6315_volt_range_ops, \ |
| 41 | .type = REGULATOR_VOLTAGE, \ |
| 42 | .id = _bid, \ |
| 43 | .owner = THIS_MODULE, \ |
Axel Lin | d450293 | 2021-03-11 10:05:58 +0800 | [diff] [blame] | 44 | .n_voltages = 0xc0, \ |
Hsin-Hsiung Wang | 7aa382c | 2021-02-07 14:14:16 +0800 | [diff] [blame] | 45 | .linear_ranges = mt_volt_range1, \ |
| 46 | .n_linear_ranges = ARRAY_SIZE(mt_volt_range1), \ |
| 47 | .vsel_reg = _vsel, \ |
| 48 | .vsel_mask = 0xff, \ |
| 49 | .enable_reg = MT6315_BUCK_TOP_CON0, \ |
| 50 | .enable_mask = BIT(_bid), \ |
| 51 | .of_map_mode = mt6315_map_mode, \ |
| 52 | }, \ |
| 53 | .status_reg = _bid##_DBG4, \ |
| 54 | .lp_mode_mask = BIT(_bid), \ |
| 55 | .lp_mode_shift = _bid, \ |
| 56 | } |
| 57 | |
| 58 | static const struct linear_range mt_volt_range1[] = { |
| 59 | REGULATOR_LINEAR_RANGE(0, 0, 0xbf, 6250), |
| 60 | }; |
| 61 | |
Axel Lin | 8908217 | 2021-05-30 10:21:09 +0800 | [diff] [blame] | 62 | static unsigned int mt6315_map_mode(unsigned int mode) |
Hsin-Hsiung Wang | 7aa382c | 2021-02-07 14:14:16 +0800 | [diff] [blame] | 63 | { |
| 64 | switch (mode) { |
| 65 | case MT6315_BUCK_MODE_AUTO: |
| 66 | return REGULATOR_MODE_NORMAL; |
| 67 | case MT6315_BUCK_MODE_FORCE_PWM: |
| 68 | return REGULATOR_MODE_FAST; |
| 69 | case MT6315_BUCK_MODE_LP: |
| 70 | return REGULATOR_MODE_IDLE; |
| 71 | default: |
Axel Lin | fbc102f | 2021-02-15 11:48:13 +0800 | [diff] [blame] | 72 | return REGULATOR_MODE_INVALID; |
Hsin-Hsiung Wang | 7aa382c | 2021-02-07 14:14:16 +0800 | [diff] [blame] | 73 | } |
| 74 | } |
| 75 | |
| 76 | static unsigned int mt6315_regulator_get_mode(struct regulator_dev *rdev) |
| 77 | { |
| 78 | struct mt_regulator_init_data *init = rdev_get_drvdata(rdev); |
| 79 | const struct mt6315_regulator_info *info; |
| 80 | int ret, regval; |
| 81 | u32 modeset_mask; |
| 82 | |
| 83 | info = container_of(rdev->desc, struct mt6315_regulator_info, desc); |
| 84 | modeset_mask = init->modeset_mask[rdev_get_id(rdev)]; |
| 85 | ret = regmap_read(rdev->regmap, MT6315_BUCK_TOP_4PHASE_ANA_CON42, ®val); |
| 86 | if (ret != 0) { |
Axel Lin | 7f8c839 | 2021-05-30 10:05:43 +0800 | [diff] [blame] | 87 | dev_err(&rdev->dev, "Failed to get mode: %d\n", ret); |
Hsin-Hsiung Wang | 7aa382c | 2021-02-07 14:14:16 +0800 | [diff] [blame] | 88 | return ret; |
| 89 | } |
| 90 | |
| 91 | if ((regval & modeset_mask) == modeset_mask) |
| 92 | return REGULATOR_MODE_FAST; |
| 93 | |
| 94 | ret = regmap_read(rdev->regmap, MT6315_BUCK_TOP_CON1, ®val); |
| 95 | if (ret != 0) { |
Axel Lin | 7f8c839 | 2021-05-30 10:05:43 +0800 | [diff] [blame] | 96 | dev_err(&rdev->dev, "Failed to get lp mode: %d\n", ret); |
Hsin-Hsiung Wang | 7aa382c | 2021-02-07 14:14:16 +0800 | [diff] [blame] | 97 | return ret; |
| 98 | } |
| 99 | |
| 100 | if (regval & info->lp_mode_mask) |
| 101 | return REGULATOR_MODE_IDLE; |
| 102 | else |
| 103 | return REGULATOR_MODE_NORMAL; |
| 104 | } |
| 105 | |
| 106 | static int mt6315_regulator_set_mode(struct regulator_dev *rdev, |
| 107 | u32 mode) |
| 108 | { |
| 109 | struct mt_regulator_init_data *init = rdev_get_drvdata(rdev); |
| 110 | const struct mt6315_regulator_info *info; |
| 111 | int ret, val, curr_mode; |
| 112 | u32 modeset_mask; |
| 113 | |
| 114 | info = container_of(rdev->desc, struct mt6315_regulator_info, desc); |
| 115 | modeset_mask = init->modeset_mask[rdev_get_id(rdev)]; |
| 116 | curr_mode = mt6315_regulator_get_mode(rdev); |
| 117 | switch (mode) { |
| 118 | case REGULATOR_MODE_FAST: |
| 119 | ret = regmap_update_bits(rdev->regmap, |
| 120 | MT6315_BUCK_TOP_4PHASE_ANA_CON42, |
| 121 | modeset_mask, |
| 122 | modeset_mask); |
| 123 | break; |
| 124 | case REGULATOR_MODE_NORMAL: |
| 125 | if (curr_mode == REGULATOR_MODE_FAST) { |
| 126 | ret = regmap_update_bits(rdev->regmap, |
| 127 | MT6315_BUCK_TOP_4PHASE_ANA_CON42, |
| 128 | modeset_mask, |
| 129 | 0); |
| 130 | } else if (curr_mode == REGULATOR_MODE_IDLE) { |
| 131 | ret = regmap_update_bits(rdev->regmap, |
| 132 | MT6315_BUCK_TOP_CON1, |
| 133 | info->lp_mode_mask, |
| 134 | 0); |
| 135 | usleep_range(100, 110); |
| 136 | } else { |
| 137 | ret = -EINVAL; |
| 138 | } |
| 139 | break; |
| 140 | case REGULATOR_MODE_IDLE: |
| 141 | val = MT6315_BUCK_MODE_LP >> 1; |
| 142 | val <<= info->lp_mode_shift; |
| 143 | ret = regmap_update_bits(rdev->regmap, |
| 144 | MT6315_BUCK_TOP_CON1, |
| 145 | info->lp_mode_mask, |
| 146 | val); |
| 147 | break; |
| 148 | default: |
| 149 | ret = -EINVAL; |
Axel Lin | 7f8c839 | 2021-05-30 10:05:43 +0800 | [diff] [blame] | 150 | dev_err(&rdev->dev, "Unsupported mode: %d\n", mode); |
Hsin-Hsiung Wang | 7aa382c | 2021-02-07 14:14:16 +0800 | [diff] [blame] | 151 | break; |
| 152 | } |
| 153 | |
| 154 | if (ret != 0) { |
Axel Lin | 7f8c839 | 2021-05-30 10:05:43 +0800 | [diff] [blame] | 155 | dev_err(&rdev->dev, "Failed to set mode: %d\n", ret); |
Hsin-Hsiung Wang | 7aa382c | 2021-02-07 14:14:16 +0800 | [diff] [blame] | 156 | return ret; |
| 157 | } |
| 158 | |
| 159 | return 0; |
| 160 | } |
| 161 | |
| 162 | static int mt6315_get_status(struct regulator_dev *rdev) |
| 163 | { |
| 164 | const struct mt6315_regulator_info *info; |
| 165 | int ret; |
| 166 | u32 regval; |
| 167 | |
| 168 | info = container_of(rdev->desc, struct mt6315_regulator_info, desc); |
| 169 | ret = regmap_read(rdev->regmap, info->status_reg, ®val); |
| 170 | if (ret < 0) { |
Axel Lin | 7f8c839 | 2021-05-30 10:05:43 +0800 | [diff] [blame] | 171 | dev_err(&rdev->dev, "Failed to get enable reg: %d\n", ret); |
Hsin-Hsiung Wang | 7aa382c | 2021-02-07 14:14:16 +0800 | [diff] [blame] | 172 | return ret; |
| 173 | } |
| 174 | |
| 175 | return (regval & BIT(0)) ? REGULATOR_STATUS_ON : REGULATOR_STATUS_OFF; |
| 176 | } |
| 177 | |
| 178 | static const struct regulator_ops mt6315_volt_range_ops = { |
| 179 | .list_voltage = regulator_list_voltage_linear_range, |
| 180 | .map_voltage = regulator_map_voltage_linear_range, |
| 181 | .set_voltage_sel = regulator_set_voltage_sel_regmap, |
| 182 | .get_voltage_sel = regulator_get_voltage_sel_regmap, |
| 183 | .set_voltage_time_sel = regulator_set_voltage_time_sel, |
| 184 | .enable = regulator_enable_regmap, |
| 185 | .disable = regulator_disable_regmap, |
| 186 | .is_enabled = regulator_is_enabled_regmap, |
| 187 | .get_status = mt6315_get_status, |
| 188 | .set_mode = mt6315_regulator_set_mode, |
| 189 | .get_mode = mt6315_regulator_get_mode, |
| 190 | }; |
| 191 | |
| 192 | static const struct mt6315_regulator_info mt6315_regulators[MT6315_VBUCK_MAX] = { |
| 193 | MT_BUCK("vbuck1", MT6315_VBUCK1, MT6315_BUCK_TOP_ELR0), |
| 194 | MT_BUCK("vbuck2", MT6315_VBUCK2, MT6315_BUCK_TOP_ELR2), |
| 195 | MT_BUCK("vbuck3", MT6315_VBUCK3, MT6315_BUCK_TOP_ELR4), |
| 196 | MT_BUCK("vbuck4", MT6315_VBUCK4, MT6315_BUCK_TOP_ELR6), |
| 197 | }; |
| 198 | |
| 199 | static const struct regmap_config mt6315_regmap_config = { |
| 200 | .reg_bits = 16, |
| 201 | .val_bits = 8, |
| 202 | .max_register = 0x16d0, |
| 203 | .fast_io = true, |
| 204 | }; |
| 205 | |
| 206 | static const struct of_device_id mt6315_of_match[] = { |
| 207 | { |
| 208 | .compatible = "mediatek,mt6315-regulator", |
| 209 | }, { |
| 210 | /* sentinel */ |
| 211 | }, |
| 212 | }; |
| 213 | MODULE_DEVICE_TABLE(of, mt6315_of_match); |
| 214 | |
| 215 | static int mt6315_regulator_probe(struct spmi_device *pdev) |
| 216 | { |
| 217 | struct device *dev = &pdev->dev; |
| 218 | struct regmap *regmap; |
| 219 | struct mt6315_chip *chip; |
| 220 | struct mt_regulator_init_data *init_data; |
| 221 | struct regulator_config config = {}; |
| 222 | struct regulator_dev *rdev; |
| 223 | int i; |
| 224 | |
| 225 | regmap = devm_regmap_init_spmi_ext(pdev, &mt6315_regmap_config); |
Axel Lin | 70d654e | 2021-06-15 21:29:34 +0800 | [diff] [blame] | 226 | if (IS_ERR(regmap)) |
| 227 | return PTR_ERR(regmap); |
Hsin-Hsiung Wang | 7aa382c | 2021-02-07 14:14:16 +0800 | [diff] [blame] | 228 | |
| 229 | chip = devm_kzalloc(dev, sizeof(struct mt6315_chip), GFP_KERNEL); |
| 230 | if (!chip) |
| 231 | return -ENOMEM; |
| 232 | |
| 233 | init_data = devm_kzalloc(dev, sizeof(struct mt_regulator_init_data), GFP_KERNEL); |
| 234 | if (!init_data) |
| 235 | return -ENOMEM; |
| 236 | |
| 237 | switch (pdev->usid) { |
| 238 | case MT6315_PP: |
| 239 | init_data->modeset_mask[MT6315_VBUCK1] = BIT(MT6315_VBUCK1) | BIT(MT6315_VBUCK2) | |
| 240 | BIT(MT6315_VBUCK4); |
| 241 | break; |
| 242 | case MT6315_SP: |
| 243 | case MT6315_RP: |
| 244 | init_data->modeset_mask[MT6315_VBUCK1] = BIT(MT6315_VBUCK1) | BIT(MT6315_VBUCK2); |
| 245 | break; |
| 246 | default: |
| 247 | init_data->modeset_mask[MT6315_VBUCK1] = BIT(MT6315_VBUCK1); |
| 248 | break; |
| 249 | } |
| 250 | for (i = MT6315_VBUCK2; i < MT6315_VBUCK_MAX; i++) |
| 251 | init_data->modeset_mask[i] = BIT(i); |
| 252 | |
| 253 | chip->dev = dev; |
| 254 | chip->regmap = regmap; |
| 255 | dev_set_drvdata(dev, chip); |
| 256 | |
| 257 | config.dev = dev; |
| 258 | config.regmap = regmap; |
| 259 | for (i = MT6315_VBUCK1; i < MT6315_VBUCK_MAX; i++) { |
| 260 | config.driver_data = init_data; |
| 261 | rdev = devm_regulator_register(dev, &mt6315_regulators[i].desc, &config); |
| 262 | if (IS_ERR(rdev)) { |
Axel Lin | 7f8c839 | 2021-05-30 10:05:43 +0800 | [diff] [blame] | 263 | dev_err(dev, "Failed to register %s\n", |
| 264 | mt6315_regulators[i].desc.name); |
| 265 | return PTR_ERR(rdev); |
Hsin-Hsiung Wang | 7aa382c | 2021-02-07 14:14:16 +0800 | [diff] [blame] | 266 | } |
| 267 | } |
| 268 | |
| 269 | return 0; |
| 270 | } |
| 271 | |
| 272 | static void mt6315_regulator_shutdown(struct spmi_device *pdev) |
| 273 | { |
| 274 | struct mt6315_chip *chip = dev_get_drvdata(&pdev->dev); |
| 275 | int ret = 0; |
| 276 | |
| 277 | ret |= regmap_write(chip->regmap, MT6315_TOP_TMA_KEY_H, PROTECTION_KEY_H); |
| 278 | ret |= regmap_write(chip->regmap, MT6315_TOP_TMA_KEY, PROTECTION_KEY); |
| 279 | ret |= regmap_update_bits(chip->regmap, MT6315_TOP2_ELR7, 1, 1); |
| 280 | ret |= regmap_write(chip->regmap, MT6315_TOP_TMA_KEY, 0); |
| 281 | ret |= regmap_write(chip->regmap, MT6315_TOP_TMA_KEY_H, 0); |
| 282 | if (ret < 0) |
Axel Lin | 7f8c839 | 2021-05-30 10:05:43 +0800 | [diff] [blame] | 283 | dev_err(&pdev->dev, "[%#x] Failed to enable power off sequence. %d\n", |
Hsin-Hsiung Wang | 7aa382c | 2021-02-07 14:14:16 +0800 | [diff] [blame] | 284 | pdev->usid, ret); |
| 285 | } |
| 286 | |
| 287 | static struct spmi_driver mt6315_regulator_driver = { |
| 288 | .driver = { |
| 289 | .name = "mt6315-regulator", |
| 290 | .of_match_table = mt6315_of_match, |
| 291 | }, |
| 292 | .probe = mt6315_regulator_probe, |
| 293 | .shutdown = mt6315_regulator_shutdown, |
| 294 | }; |
| 295 | |
| 296 | module_spmi_driver(mt6315_regulator_driver); |
| 297 | |
| 298 | MODULE_AUTHOR("Hsin-Hsiung Wang <hsin-hsiung.wang@mediatek.com>"); |
| 299 | MODULE_DESCRIPTION("Regulator Driver for MediaTek MT6315 PMIC"); |
| 300 | MODULE_LICENSE("GPL"); |