| // SPDX-License-Identifier: GPL-2.0 | |
| // | |
| // Lochnagar regulator driver | |
| // | |
| // Copyright (c) 2017-2018 Cirrus Logic, Inc. and | |
| // Cirrus Logic International Semiconductor Ltd. | |
| // | |
| // Author: Charles Keepax <ckeepax@opensource.cirrus.com> | |
| #include <linux/bitops.h> | |
| #include <linux/device.h> | |
| #include <linux/err.h> | |
| #include <linux/module.h> | |
| #include <linux/mutex.h> | |
| #include <linux/of.h> | |
| #include <linux/of_device.h> | |
| #include <linux/platform_device.h> | |
| #include <linux/regmap.h> | |
| #include <linux/regulator/driver.h> | |
| #include <linux/regulator/machine.h> | |
| #include <linux/regulator/of_regulator.h> | |
| #include <linux/mfd/lochnagar.h> | |
| #include <linux/mfd/lochnagar1_regs.h> | |
| #include <linux/mfd/lochnagar2_regs.h> | |
| static const struct regulator_ops lochnagar_micvdd_ops = { | |
| .enable = regulator_enable_regmap, | |
| .disable = regulator_disable_regmap, | |
| .is_enabled = regulator_is_enabled_regmap, | |
| .list_voltage = regulator_list_voltage_linear_range, | |
| .map_voltage = regulator_map_voltage_linear_range, | |
| .get_voltage_sel = regulator_get_voltage_sel_regmap, | |
| .set_voltage_sel = regulator_set_voltage_sel_regmap, | |
| }; | |
| static const struct linear_range lochnagar_micvdd_ranges[] = { | |
| REGULATOR_LINEAR_RANGE(1000000, 0, 0xC, 50000), | |
| REGULATOR_LINEAR_RANGE(1700000, 0xD, 0x1F, 100000), | |
| }; | |
| static int lochnagar_micbias_enable(struct regulator_dev *rdev) | |
| { | |
| struct lochnagar *lochnagar = rdev_get_drvdata(rdev); | |
| int ret; | |
| mutex_lock(&lochnagar->analogue_config_lock); | |
| ret = regulator_enable_regmap(rdev); | |
| if (ret < 0) | |
| goto err; | |
| ret = lochnagar_update_config(lochnagar); | |
| err: | |
| mutex_unlock(&lochnagar->analogue_config_lock); | |
| return ret; | |
| } | |
| static int lochnagar_micbias_disable(struct regulator_dev *rdev) | |
| { | |
| struct lochnagar *lochnagar = rdev_get_drvdata(rdev); | |
| int ret; | |
| mutex_lock(&lochnagar->analogue_config_lock); | |
| ret = regulator_disable_regmap(rdev); | |
| if (ret < 0) | |
| goto err; | |
| ret = lochnagar_update_config(lochnagar); | |
| err: | |
| mutex_unlock(&lochnagar->analogue_config_lock); | |
| return ret; | |
| } | |
| static const struct regulator_ops lochnagar_micbias_ops = { | |
| .enable = lochnagar_micbias_enable, | |
| .disable = lochnagar_micbias_disable, | |
| .is_enabled = regulator_is_enabled_regmap, | |
| }; | |
| static const struct regulator_ops lochnagar_vddcore_ops = { | |
| .enable = regulator_enable_regmap, | |
| .disable = regulator_disable_regmap, | |
| .is_enabled = regulator_is_enabled_regmap, | |
| .list_voltage = regulator_list_voltage_linear_range, | |
| .map_voltage = regulator_map_voltage_linear_range, | |
| .get_voltage_sel = regulator_get_voltage_sel_regmap, | |
| .set_voltage_sel = regulator_set_voltage_sel_regmap, | |
| }; | |
| static const struct linear_range lochnagar_vddcore_ranges[] = { | |
| REGULATOR_LINEAR_RANGE(600000, 0, 0x7, 0), | |
| REGULATOR_LINEAR_RANGE(600000, 0x8, 0x41, 12500), | |
| }; | |
| enum lochnagar_regulators { | |
| LOCHNAGAR_MICVDD, | |
| LOCHNAGAR_MIC1VDD, | |
| LOCHNAGAR_MIC2VDD, | |
| LOCHNAGAR_VDDCORE, | |
| }; | |
| static int lochnagar_micbias_of_parse(struct device_node *np, | |
| const struct regulator_desc *desc, | |
| struct regulator_config *config) | |
| { | |
| struct lochnagar *lochnagar = config->driver_data; | |
| int shift = (desc->id - LOCHNAGAR_MIC1VDD) * | |
| LOCHNAGAR2_P2_MICBIAS_SRC_SHIFT; | |
| int mask = LOCHNAGAR2_P1_MICBIAS_SRC_MASK << shift; | |
| unsigned int val; | |
| int ret; | |
| ret = of_property_read_u32(np, "cirrus,micbias-input", &val); | |
| if (ret >= 0) { | |
| mutex_lock(&lochnagar->analogue_config_lock); | |
| ret = regmap_update_bits(lochnagar->regmap, | |
| LOCHNAGAR2_ANALOGUE_PATH_CTRL2, | |
| mask, val << shift); | |
| mutex_unlock(&lochnagar->analogue_config_lock); | |
| if (ret < 0) { | |
| dev_err(lochnagar->dev, | |
| "Failed to update micbias source: %d\n", ret); | |
| return ret; | |
| } | |
| } | |
| return 0; | |
| } | |
| static const struct regulator_desc lochnagar_regulators[] = { | |
| [LOCHNAGAR_MICVDD] = { | |
| .name = "MICVDD", | |
| .supply_name = "SYSVDD", | |
| .type = REGULATOR_VOLTAGE, | |
| .n_voltages = 32, | |
| .ops = &lochnagar_micvdd_ops, | |
| .id = LOCHNAGAR_MICVDD, | |
| .of_match = of_match_ptr("MICVDD"), | |
| .enable_reg = LOCHNAGAR2_MICVDD_CTRL1, | |
| .enable_mask = LOCHNAGAR2_MICVDD_REG_ENA_MASK, | |
| .vsel_reg = LOCHNAGAR2_MICVDD_CTRL2, | |
| .vsel_mask = LOCHNAGAR2_MICVDD_VSEL_MASK, | |
| .linear_ranges = lochnagar_micvdd_ranges, | |
| .n_linear_ranges = ARRAY_SIZE(lochnagar_micvdd_ranges), | |
| .enable_time = 3000, | |
| .ramp_delay = 1000, | |
| .owner = THIS_MODULE, | |
| }, | |
| [LOCHNAGAR_MIC1VDD] = { | |
| .name = "MIC1VDD", | |
| .supply_name = "MICBIAS1", | |
| .type = REGULATOR_VOLTAGE, | |
| .ops = &lochnagar_micbias_ops, | |
| .id = LOCHNAGAR_MIC1VDD, | |
| .of_match = of_match_ptr("MIC1VDD"), | |
| .of_parse_cb = lochnagar_micbias_of_parse, | |
| .enable_reg = LOCHNAGAR2_ANALOGUE_PATH_CTRL2, | |
| .enable_mask = LOCHNAGAR2_P1_INPUT_BIAS_ENA_MASK, | |
| .owner = THIS_MODULE, | |
| }, | |
| [LOCHNAGAR_MIC2VDD] = { | |
| .name = "MIC2VDD", | |
| .supply_name = "MICBIAS2", | |
| .type = REGULATOR_VOLTAGE, | |
| .ops = &lochnagar_micbias_ops, | |
| .id = LOCHNAGAR_MIC2VDD, | |
| .of_match = of_match_ptr("MIC2VDD"), | |
| .of_parse_cb = lochnagar_micbias_of_parse, | |
| .enable_reg = LOCHNAGAR2_ANALOGUE_PATH_CTRL2, | |
| .enable_mask = LOCHNAGAR2_P2_INPUT_BIAS_ENA_MASK, | |
| .owner = THIS_MODULE, | |
| }, | |
| [LOCHNAGAR_VDDCORE] = { | |
| .name = "VDDCORE", | |
| .supply_name = "SYSVDD", | |
| .type = REGULATOR_VOLTAGE, | |
| .n_voltages = 66, | |
| .ops = &lochnagar_vddcore_ops, | |
| .id = LOCHNAGAR_VDDCORE, | |
| .of_match = of_match_ptr("VDDCORE"), | |
| .enable_reg = LOCHNAGAR2_VDDCORE_CDC_CTRL1, | |
| .enable_mask = LOCHNAGAR2_VDDCORE_CDC_REG_ENA_MASK, | |
| .vsel_reg = LOCHNAGAR2_VDDCORE_CDC_CTRL2, | |
| .vsel_mask = LOCHNAGAR2_VDDCORE_CDC_VSEL_MASK, | |
| .linear_ranges = lochnagar_vddcore_ranges, | |
| .n_linear_ranges = ARRAY_SIZE(lochnagar_vddcore_ranges), | |
| .enable_time = 3000, | |
| .ramp_delay = 1000, | |
| .off_on_delay = 15000, | |
| .owner = THIS_MODULE, | |
| }, | |
| }; | |
| static const struct of_device_id lochnagar_of_match[] = { | |
| { | |
| .compatible = "cirrus,lochnagar2-micvdd", | |
| .data = &lochnagar_regulators[LOCHNAGAR_MICVDD], | |
| }, | |
| { | |
| .compatible = "cirrus,lochnagar2-mic1vdd", | |
| .data = &lochnagar_regulators[LOCHNAGAR_MIC1VDD], | |
| }, | |
| { | |
| .compatible = "cirrus,lochnagar2-mic2vdd", | |
| .data = &lochnagar_regulators[LOCHNAGAR_MIC2VDD], | |
| }, | |
| { | |
| .compatible = "cirrus,lochnagar2-vddcore", | |
| .data = &lochnagar_regulators[LOCHNAGAR_VDDCORE], | |
| }, | |
| {} | |
| }; | |
| MODULE_DEVICE_TABLE(of, lochnagar_of_match); | |
| static int lochnagar_regulator_probe(struct platform_device *pdev) | |
| { | |
| struct device *dev = &pdev->dev; | |
| struct lochnagar *lochnagar = dev_get_drvdata(dev->parent); | |
| struct regulator_config config = { }; | |
| const struct of_device_id *of_id; | |
| const struct regulator_desc *desc; | |
| struct regulator_dev *rdev; | |
| int ret; | |
| config.dev = dev; | |
| config.regmap = lochnagar->regmap; | |
| config.driver_data = lochnagar; | |
| of_id = of_match_device(lochnagar_of_match, dev); | |
| if (!of_id) | |
| return -EINVAL; | |
| desc = of_id->data; | |
| rdev = devm_regulator_register(dev, desc, &config); | |
| if (IS_ERR(rdev)) { | |
| ret = PTR_ERR(rdev); | |
| dev_err(dev, "Failed to register %s regulator: %d\n", | |
| desc->name, ret); | |
| return ret; | |
| } | |
| return 0; | |
| } | |
| static struct platform_driver lochnagar_regulator_driver = { | |
| .driver = { | |
| .name = "lochnagar-regulator", | |
| .of_match_table = of_match_ptr(lochnagar_of_match), | |
| }, | |
| .probe = lochnagar_regulator_probe, | |
| }; | |
| module_platform_driver(lochnagar_regulator_driver); | |
| MODULE_AUTHOR("Charles Keepax <ckeepax@opensource.cirrus.com>"); | |
| MODULE_DESCRIPTION("Regulator driver for Cirrus Logic Lochnagar Board"); | |
| MODULE_LICENSE("GPL v2"); | |
| MODULE_ALIAS("platform:lochnagar-regulator"); |