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