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