new file mode 100644
@@ -0,0 +1,27 @@
+Cirrus Logic Madera class audio codecs mic supply regulator driver
+
+Only required if the codec has an internal mic supply regulator.
+This is a subnode of the parent mfd node.
+
+See also the core bindings for the parent MFD driver:
+See Documentation/devicetree/bindings/mfd/madera.txt
+
+Required properties:
+ - compatible : must be "cirrus,madera-micsupp"
+
+Optional subnodes:
+ Standard regulator bindings as described in bindings/regulator/regulator.txt
+
+Example:
+
+codec: cs47l85@0 {
+ compatible = "cirrus,cs47l85";
+
+ micvdd {
+ compatible = "cirrus,madera-micsupp";
+ regulator-min-microvolt = <900000>;
+ regulator-max-microvolt = <3300000>;
+ regulator-allow-bypass = <1>;
+ };
+};
+
@@ -376,6 +376,14 @@ config REGULATOR_MADERA_LDO1
to supply DCVDD you must include this driver. If you are using an
external DCVDD regulator you do not need this driver.
+config REGULATOR_MADERA_MICSUPP
+ tristate "Cirrus Logic Madera codecs mic supply regulator"
+ depends on MFD_MADERA
+ depends on SND_SOC
+ help
+ Support for the microphone supply regulator on Cirrus Logic
+ Madera class codecs
+
config REGULATOR_MAX14577
tristate "Maxim 14577/77836 regulator"
depends on MFD_MAX14577
@@ -50,6 +50,7 @@ obj-$(CONFIG_REGULATOR_LP8755) += lp8755.o
obj-$(CONFIG_REGULATOR_LTC3589) += ltc3589.o
obj-$(CONFIG_REGULATOR_LTC3676) += ltc3676.o
obj-$(CONFIG_REGULATOR_MADERA_LDO1) += madera-ldo1.o
+obj-$(CONFIG_REGULATOR_MADERA_MICSUPP) += madera-micsupp.o
obj-$(CONFIG_REGULATOR_MAX14577) += max14577-regulator.o
obj-$(CONFIG_REGULATOR_MAX1586) += max1586.o
obj-$(CONFIG_REGULATOR_MAX77620) += max77620-regulator.o
new file mode 100644
@@ -0,0 +1,260 @@
+/*
+ * madera-micsupp.c -- Driver for the mic supply regulator on Madera codecs
+ *
+ * Copyright 2015-2017 Cirrus Logic
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/workqueue.h>
+#include <sound/soc.h>
+
+#include <linux/regulator/madera-micsupp.h>
+
+#include <linux/mfd/madera/core.h>
+#include <linux/mfd/madera/pdata.h>
+#include <linux/mfd/madera/registers.h>
+
+struct madera_micsupp {
+ struct regulator_dev *regulator;
+ struct madera *madera;
+
+ struct regulator_consumer_supply supply;
+ struct regulator_init_data init_data;
+
+ struct work_struct check_cp_work;
+};
+
+static void madera_micsupp_check_cp(struct work_struct *work)
+{
+ struct madera_micsupp *micsupp =
+ container_of(work, struct madera_micsupp, check_cp_work);
+ struct snd_soc_dapm_context *dapm = micsupp->madera->dapm;
+ struct snd_soc_component *component = snd_soc_dapm_to_component(dapm);
+ struct madera *madera = micsupp->madera;
+ struct regmap *regmap = madera->regmap;
+ unsigned int reg;
+ int ret;
+
+ if (dapm) {
+ ret = regmap_read(regmap, MADERA_MIC_CHARGE_PUMP_1, ®);
+ if (ret) {
+ dev_err(madera->dev,
+ "Failed to read CP state: %d\n", ret);
+ return;
+ }
+
+ reg &= MADERA_CPMIC_ENA | MADERA_CPMIC_BYPASS;
+ if (reg == MADERA_CPMIC_ENA)
+ snd_soc_component_force_enable_pin(component,
+ "MICSUPP");
+ else
+ snd_soc_component_disable_pin(component, "MICSUPP");
+
+ snd_soc_dapm_sync(dapm);
+ }
+}
+
+static int madera_micsupp_enable(struct regulator_dev *rdev)
+{
+ struct madera_micsupp *micsupp = rdev_get_drvdata(rdev);
+ int ret;
+
+ ret = regulator_enable_regmap(rdev);
+ if (ret == 0)
+ schedule_work(&micsupp->check_cp_work);
+
+ return ret;
+}
+
+static int madera_micsupp_disable(struct regulator_dev *rdev)
+{
+ struct madera_micsupp *micsupp = rdev_get_drvdata(rdev);
+ int ret;
+
+ ret = regulator_disable_regmap(rdev);
+ if (ret == 0)
+ schedule_work(&micsupp->check_cp_work);
+
+ return ret;
+}
+
+static int madera_micsupp_set_bypass(struct regulator_dev *rdev, bool ena)
+{
+ struct madera_micsupp *micsupp = rdev_get_drvdata(rdev);
+ int ret;
+
+ ret = regulator_set_bypass_regmap(rdev, ena);
+ if (ret == 0)
+ schedule_work(&micsupp->check_cp_work);
+
+ return ret;
+}
+
+static const struct regulator_ops madera_micsupp_ops = {
+ .enable = madera_micsupp_enable,
+ .disable = madera_micsupp_disable,
+ .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,
+
+ .get_bypass = regulator_get_bypass_regmap,
+ .set_bypass = madera_micsupp_set_bypass,
+};
+
+static const struct regulator_linear_range madera_micsupp_ranges[] = {
+ REGULATOR_LINEAR_RANGE(900000, 0, 0x14, 25000),
+ REGULATOR_LINEAR_RANGE(1500000, 0x15, 0x27, 100000),
+};
+
+static const struct regulator_desc madera_micsupp = {
+ .name = "MICVDD",
+ .supply_name = "CPVDD1",
+ .type = REGULATOR_VOLTAGE,
+ .n_voltages = 40,
+ .ops = &madera_micsupp_ops,
+
+ .vsel_reg = MADERA_LDO2_CONTROL_1,
+ .vsel_mask = MADERA_LDO2_VSEL_MASK,
+ .enable_reg = MADERA_MIC_CHARGE_PUMP_1,
+ .enable_mask = MADERA_CPMIC_ENA,
+ .bypass_reg = MADERA_MIC_CHARGE_PUMP_1,
+ .bypass_mask = MADERA_CPMIC_BYPASS,
+
+ .linear_ranges = madera_micsupp_ranges,
+ .n_linear_ranges = ARRAY_SIZE(madera_micsupp_ranges),
+
+ .enable_time = 3000,
+
+ .owner = THIS_MODULE,
+};
+
+static const struct regulator_init_data madera_micsupp_default = {
+ .constraints = {
+ .valid_ops_mask = REGULATOR_CHANGE_STATUS |
+ REGULATOR_CHANGE_VOLTAGE |
+ REGULATOR_CHANGE_BYPASS,
+ .min_uV = 900000,
+ .max_uV = 3300000,
+ },
+
+ .num_consumer_supplies = 1,
+};
+
+static int madera_micsupp_of_get_pdata(struct madera *madera,
+ struct regulator_config *config,
+ const struct regulator_desc *desc)
+{
+ struct madera_pdata *pdata = &madera->pdata;
+ struct madera_micsupp *micsupp = config->driver_data;
+ struct regulator_init_data *init_data;
+
+ init_data = of_get_regulator_init_data(config->dev, config->of_node,
+ desc);
+
+ if (init_data) {
+ init_data->consumer_supplies = &micsupp->supply;
+ init_data->num_consumer_supplies = 1;
+ pdata->micsupp.init_data = init_data;
+ }
+
+ return 0;
+}
+
+static int madera_micsupp_probe(struct platform_device *pdev)
+{
+ struct madera *madera = dev_get_drvdata(pdev->dev.parent);
+ const struct regulator_desc *desc;
+ struct regulator_config config = { };
+ struct madera_micsupp *micsupp;
+ int ret;
+
+ micsupp = devm_kzalloc(&pdev->dev, sizeof(*micsupp), GFP_KERNEL);
+ if (!micsupp)
+ return -ENOMEM;
+
+ micsupp->madera = madera;
+ INIT_WORK(&micsupp->check_cp_work, madera_micsupp_check_cp);
+
+ /*
+ * Since the chip usually supplies itself we provide some
+ * default init_data for it. This will be overridden with
+ * platform data if provided.
+ */
+ desc = &madera_micsupp;
+ micsupp->init_data = madera_micsupp_default;
+
+ micsupp->init_data.consumer_supplies = &micsupp->supply;
+ micsupp->supply.supply = "MICVDD";
+ micsupp->supply.dev_name = dev_name(madera->dev);
+
+ config.dev = madera->dev;
+ config.of_node = config.dev->of_node;
+ config.driver_data = micsupp;
+ config.regmap = madera->regmap;
+
+ if (IS_ENABLED(CONFIG_OF)) {
+ if (!dev_get_platdata(madera->dev)) {
+ ret = madera_micsupp_of_get_pdata(madera, &config,
+ desc);
+ if (ret < 0)
+ return ret;
+ }
+ }
+
+ if (madera->pdata.micsupp.init_data)
+ config.init_data = madera->pdata.micsupp.init_data;
+ else
+ config.init_data = &micsupp->init_data;
+
+ /* Default to regulated mode, in case bypass is not in constraints */
+ regmap_update_bits(madera->regmap, MADERA_MIC_CHARGE_PUMP_1,
+ MADERA_CPMIC_BYPASS, 0);
+
+ micsupp->regulator = devm_regulator_register(&pdev->dev, desc,
+ &config);
+
+ of_node_put(config.of_node);
+
+ if (IS_ERR(micsupp->regulator)) {
+ ret = PTR_ERR(micsupp->regulator);
+ dev_err(madera->dev, "Failed to register mic supply: %d\n",
+ ret);
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, micsupp);
+
+ return 0;
+}
+
+static struct platform_driver madera_micsupp_driver = {
+ .probe = madera_micsupp_probe,
+ .driver = {
+ .name = "madera-micsupp",
+ },
+};
+
+module_platform_driver(madera_micsupp_driver);
+
+/* Module information */
+MODULE_DESCRIPTION("Madera microphone supply driver");
+MODULE_AUTHOR("Charles Keepax <ckeepax@opensource.wolfsonmicro.com>");
+MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:madera-micsupp");
new file mode 100644
@@ -0,0 +1,21 @@
+/*
+ * Platform data for Madera codecs MICSUPP regulator
+ *
+ * Copyright 2016-2017 Cirrus Logic
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef MADERA_MICSUPP_H
+#define MADERA_MICSUPP_H
+
+struct regulator_init_data;
+
+struct madera_micsupp_pdata {
+ /** Regulator configuration for MICSUPP */
+ const struct regulator_init_data *init_data;
+};
+
+#endif