1 // SPDX-License-Identifier: GPL-2.0 2 // 3 // Lochnagar regulator driver 4 // 5 // Copyright (c) 2017-2018 Cirrus Logic, Inc. and 6 // Cirrus Logic International Semiconductor Ltd. 7 // 8 // Author: Charles Keepax <ckeepax@opensource.cirrus.com> 9 10 #include <linux/bitops.h> 11 #include <linux/device.h> 12 #include <linux/err.h> 13 #include <linux/module.h> 14 #include <linux/mutex.h> 15 #include <linux/of.h> 16 #include <linux/of_device.h> 17 #include <linux/platform_device.h> 18 #include <linux/regmap.h> 19 #include <linux/regulator/driver.h> 20 #include <linux/regulator/machine.h> 21 #include <linux/regulator/of_regulator.h> 22 23 #include <linux/mfd/lochnagar.h> 24 #include <linux/mfd/lochnagar1_regs.h> 25 #include <linux/mfd/lochnagar2_regs.h> 26 27 static const struct regulator_ops lochnagar_micvdd_ops = { 28 .enable = regulator_enable_regmap, 29 .disable = regulator_disable_regmap, 30 .is_enabled = regulator_is_enabled_regmap, 31 32 .list_voltage = regulator_list_voltage_linear_range, 33 .map_voltage = regulator_map_voltage_linear_range, 34 35 .get_voltage_sel = regulator_get_voltage_sel_regmap, 36 .set_voltage_sel = regulator_set_voltage_sel_regmap, 37 }; 38 39 static const struct linear_range lochnagar_micvdd_ranges[] = { 40 REGULATOR_LINEAR_RANGE(1000000, 0, 0xC, 50000), 41 REGULATOR_LINEAR_RANGE(1700000, 0xD, 0x1F, 100000), 42 }; 43 44 static int lochnagar_micbias_enable(struct regulator_dev *rdev) 45 { 46 struct lochnagar *lochnagar = rdev_get_drvdata(rdev); 47 int ret; 48 49 mutex_lock(&lochnagar->analogue_config_lock); 50 51 ret = regulator_enable_regmap(rdev); 52 if (ret < 0) 53 goto err; 54 55 ret = lochnagar_update_config(lochnagar); 56 57 err: 58 mutex_unlock(&lochnagar->analogue_config_lock); 59 60 return ret; 61 } 62 63 static int lochnagar_micbias_disable(struct regulator_dev *rdev) 64 { 65 struct lochnagar *lochnagar = rdev_get_drvdata(rdev); 66 int ret; 67 68 mutex_lock(&lochnagar->analogue_config_lock); 69 70 ret = regulator_disable_regmap(rdev); 71 if (ret < 0) 72 goto err; 73 74 ret = lochnagar_update_config(lochnagar); 75 76 err: 77 mutex_unlock(&lochnagar->analogue_config_lock); 78 79 return ret; 80 } 81 82 static const struct regulator_ops lochnagar_micbias_ops = { 83 .enable = lochnagar_micbias_enable, 84 .disable = lochnagar_micbias_disable, 85 .is_enabled = regulator_is_enabled_regmap, 86 }; 87 88 static const struct regulator_ops lochnagar_vddcore_ops = { 89 .enable = regulator_enable_regmap, 90 .disable = regulator_disable_regmap, 91 .is_enabled = regulator_is_enabled_regmap, 92 93 .list_voltage = regulator_list_voltage_linear_range, 94 .map_voltage = regulator_map_voltage_linear_range, 95 96 .get_voltage_sel = regulator_get_voltage_sel_regmap, 97 .set_voltage_sel = regulator_set_voltage_sel_regmap, 98 }; 99 100 static const struct linear_range lochnagar_vddcore_ranges[] = { 101 REGULATOR_LINEAR_RANGE(600000, 0, 0x7, 0), 102 REGULATOR_LINEAR_RANGE(600000, 0x8, 0x41, 12500), 103 }; 104 105 enum lochnagar_regulators { 106 LOCHNAGAR_MICVDD, 107 LOCHNAGAR_MIC1VDD, 108 LOCHNAGAR_MIC2VDD, 109 LOCHNAGAR_VDDCORE, 110 }; 111 112 static int lochnagar_micbias_of_parse(struct device_node *np, 113 const struct regulator_desc *desc, 114 struct regulator_config *config) 115 { 116 struct lochnagar *lochnagar = config->driver_data; 117 int shift = (desc->id - LOCHNAGAR_MIC1VDD) * 118 LOCHNAGAR2_P2_MICBIAS_SRC_SHIFT; 119 int mask = LOCHNAGAR2_P1_MICBIAS_SRC_MASK << shift; 120 unsigned int val; 121 int ret; 122 123 ret = of_property_read_u32(np, "cirrus,micbias-input", &val); 124 if (ret >= 0) { 125 mutex_lock(&lochnagar->analogue_config_lock); 126 ret = regmap_update_bits(lochnagar->regmap, 127 LOCHNAGAR2_ANALOGUE_PATH_CTRL2, 128 mask, val << shift); 129 mutex_unlock(&lochnagar->analogue_config_lock); 130 if (ret < 0) { 131 dev_err(lochnagar->dev, 132 "Failed to update micbias source: %d\n", ret); 133 return ret; 134 } 135 } 136 137 return 0; 138 } 139 140 static const struct regulator_desc lochnagar_regulators[] = { 141 [LOCHNAGAR_MICVDD] = { 142 .name = "MICVDD", 143 .supply_name = "SYSVDD", 144 .type = REGULATOR_VOLTAGE, 145 .n_voltages = 32, 146 .ops = &lochnagar_micvdd_ops, 147 148 .id = LOCHNAGAR_MICVDD, 149 .of_match = of_match_ptr("MICVDD"), 150 151 .enable_reg = LOCHNAGAR2_MICVDD_CTRL1, 152 .enable_mask = LOCHNAGAR2_MICVDD_REG_ENA_MASK, 153 .vsel_reg = LOCHNAGAR2_MICVDD_CTRL2, 154 .vsel_mask = LOCHNAGAR2_MICVDD_VSEL_MASK, 155 156 .linear_ranges = lochnagar_micvdd_ranges, 157 .n_linear_ranges = ARRAY_SIZE(lochnagar_micvdd_ranges), 158 159 .enable_time = 3000, 160 .ramp_delay = 1000, 161 162 .owner = THIS_MODULE, 163 }, 164 [LOCHNAGAR_MIC1VDD] = { 165 .name = "MIC1VDD", 166 .supply_name = "MICBIAS1", 167 .type = REGULATOR_VOLTAGE, 168 .ops = &lochnagar_micbias_ops, 169 170 .id = LOCHNAGAR_MIC1VDD, 171 .of_match = of_match_ptr("MIC1VDD"), 172 .of_parse_cb = lochnagar_micbias_of_parse, 173 174 .enable_reg = LOCHNAGAR2_ANALOGUE_PATH_CTRL2, 175 .enable_mask = LOCHNAGAR2_P1_INPUT_BIAS_ENA_MASK, 176 177 .owner = THIS_MODULE, 178 }, 179 [LOCHNAGAR_MIC2VDD] = { 180 .name = "MIC2VDD", 181 .supply_name = "MICBIAS2", 182 .type = REGULATOR_VOLTAGE, 183 .ops = &lochnagar_micbias_ops, 184 185 .id = LOCHNAGAR_MIC2VDD, 186 .of_match = of_match_ptr("MIC2VDD"), 187 .of_parse_cb = lochnagar_micbias_of_parse, 188 189 .enable_reg = LOCHNAGAR2_ANALOGUE_PATH_CTRL2, 190 .enable_mask = LOCHNAGAR2_P2_INPUT_BIAS_ENA_MASK, 191 192 .owner = THIS_MODULE, 193 }, 194 [LOCHNAGAR_VDDCORE] = { 195 .name = "VDDCORE", 196 .supply_name = "SYSVDD", 197 .type = REGULATOR_VOLTAGE, 198 .n_voltages = 66, 199 .ops = &lochnagar_vddcore_ops, 200 201 .id = LOCHNAGAR_VDDCORE, 202 .of_match = of_match_ptr("VDDCORE"), 203 204 .enable_reg = LOCHNAGAR2_VDDCORE_CDC_CTRL1, 205 .enable_mask = LOCHNAGAR2_VDDCORE_CDC_REG_ENA_MASK, 206 .vsel_reg = LOCHNAGAR2_VDDCORE_CDC_CTRL2, 207 .vsel_mask = LOCHNAGAR2_VDDCORE_CDC_VSEL_MASK, 208 209 .linear_ranges = lochnagar_vddcore_ranges, 210 .n_linear_ranges = ARRAY_SIZE(lochnagar_vddcore_ranges), 211 212 .enable_time = 3000, 213 .ramp_delay = 1000, 214 .off_on_delay = 15000, 215 216 .owner = THIS_MODULE, 217 }, 218 }; 219 220 static const struct of_device_id lochnagar_of_match[] = { 221 { 222 .compatible = "cirrus,lochnagar2-micvdd", 223 .data = &lochnagar_regulators[LOCHNAGAR_MICVDD], 224 }, 225 { 226 .compatible = "cirrus,lochnagar2-mic1vdd", 227 .data = &lochnagar_regulators[LOCHNAGAR_MIC1VDD], 228 }, 229 { 230 .compatible = "cirrus,lochnagar2-mic2vdd", 231 .data = &lochnagar_regulators[LOCHNAGAR_MIC2VDD], 232 }, 233 { 234 .compatible = "cirrus,lochnagar2-vddcore", 235 .data = &lochnagar_regulators[LOCHNAGAR_VDDCORE], 236 }, 237 {} 238 }; 239 MODULE_DEVICE_TABLE(of, lochnagar_of_match); 240 241 static int lochnagar_regulator_probe(struct platform_device *pdev) 242 { 243 struct device *dev = &pdev->dev; 244 struct lochnagar *lochnagar = dev_get_drvdata(dev->parent); 245 struct regulator_config config = { }; 246 const struct of_device_id *of_id; 247 const struct regulator_desc *desc; 248 struct regulator_dev *rdev; 249 int ret; 250 251 config.dev = dev; 252 config.regmap = lochnagar->regmap; 253 config.driver_data = lochnagar; 254 255 of_id = of_match_device(lochnagar_of_match, dev); 256 if (!of_id) 257 return -EINVAL; 258 259 desc = of_id->data; 260 261 rdev = devm_regulator_register(dev, desc, &config); 262 if (IS_ERR(rdev)) { 263 ret = PTR_ERR(rdev); 264 dev_err(dev, "Failed to register %s regulator: %d\n", 265 desc->name, ret); 266 return ret; 267 } 268 269 return 0; 270 } 271 272 static struct platform_driver lochnagar_regulator_driver = { 273 .driver = { 274 .name = "lochnagar-regulator", 275 .probe_type = PROBE_PREFER_ASYNCHRONOUS, 276 .of_match_table = of_match_ptr(lochnagar_of_match), 277 }, 278 279 .probe = lochnagar_regulator_probe, 280 }; 281 module_platform_driver(lochnagar_regulator_driver); 282 283 MODULE_AUTHOR("Charles Keepax <ckeepax@opensource.cirrus.com>"); 284 MODULE_DESCRIPTION("Regulator driver for Cirrus Logic Lochnagar Board"); 285 MODULE_LICENSE("GPL v2"); 286 MODULE_ALIAS("platform:lochnagar-regulator"); 287