aboutsummaryrefslogtreecommitdiffstats
path: root/main/linux-rpi/rpi-cirrus-4.1.x-20151006.patch
diff options
context:
space:
mode:
Diffstat (limited to 'main/linux-rpi/rpi-cirrus-4.1.x-20151006.patch')
-rw-r--r--main/linux-rpi/rpi-cirrus-4.1.x-20151006.patch2046
1 files changed, 2046 insertions, 0 deletions
diff --git a/main/linux-rpi/rpi-cirrus-4.1.x-20151006.patch b/main/linux-rpi/rpi-cirrus-4.1.x-20151006.patch
new file mode 100644
index 0000000000..c9c5b8dce3
--- /dev/null
+++ b/main/linux-rpi/rpi-cirrus-4.1.x-20151006.patch
@@ -0,0 +1,2046 @@
+https://github.com/HiassofT/rpi-linux/commits/cirrus-4.1.y
+
+diff --git a/Documentation/devicetree/bindings/mfd/arizona.txt b/Documentation/devicetree/bindings/mfd/arizona.txt
+index 7665aa9..ec7979e 100644
+--- a/Documentation/devicetree/bindings/mfd/arizona.txt
++++ b/Documentation/devicetree/bindings/mfd/arizona.txt
+@@ -42,6 +42,23 @@ Optional properties:
+
+ - wlf,reset : GPIO specifier for the GPIO controlling /RESET
+ - wlf,ldoena : GPIO specifier for the GPIO controlling LDOENA
++ - wlf,clk32k-src : set input source for codec 32kHz clock.
++ 0 = default, 1 = MCLK1, 2 = MCLK2, 3 = None
++
++ - wlf,micd-ranges : Microphone detection level and key configuration, this
++ field can be of variable length but should always be a multiple of 2 cells
++ long, each two cell group represents one button configuration
++ The first cell is the maximum impedance for this button in ohms
++ The second cell the key that should be reported to the input layer
++ - wlf,micd-configs : Headset polarity configurations, the field can be of
++ variable length but should always be a multiple of 3 cells long, each two
++ cell group represents one polarity configration
++ The first cell is the accessory detection source as per the ACCDET_SRC bits
++ in the ACCESSORY_DETECT_MODE_1 register
++ The second cell represents the MICBIAS to be used as per the MICD_BIAS_SRC
++ bits in the MIC_DETECT_1 register
++ The third cell represents the value of the micd-pol-gpio pin, a non-zero
++ value indicates this should be on
+
+ - wlf,gpio-defaults : A list of GPIO configuration register values. Defines
+ for the appropriate values can found in <dt-bindings/mfd/arizona.txt>. If
+@@ -49,6 +66,10 @@ Optional properties:
+ a value that is out of range for a 16 bit register then the chip default
+ will be used. If present exactly five values must be specified.
+
++ - wlf,dmic-ref : DMIC reference for each input, must contain four cells if
++ specified. 0 indicates MICVDD and is the default, 1,2,3 indicate the
++ respective MICBIAS.
++
+ - wlf,inmode : A list of INn_MODE register values, where n is the number
+ of input signals. Valid values are 0 (Differential), 1 (Single-ended) and
+ 2 (Digital Microphone). If absent, INn_MODE registers set to 0 by default.
+@@ -85,6 +106,19 @@ codec: wm5102@1a {
+ gpio-controller;
+ #gpio-cells = <2>;
+
++ wlf,micd-ranges = <
++ 11 0x100
++ 28 0x101
++ 54 0x102
++ 100 0x103
++ 186 0x104
++ 430 0x105
++ >;
++ wlf,micd-configs = <
++ 0x1 1 0
++ 0x0 2 1
++ >;
++
+ wlf,gpio-defaults = <
+ ARIZONA_GP_FN_TXLRCLK
+ ARIZONA_GP_DEFAULT
+@@ -92,4 +126,6 @@ codec: wm5102@1a {
+ ARIZONA_GP_DEFAULT
+ ARIZONA_GP_DEFAULT
+ >;
++
++ wlf,dmic-ref = <0 0 1 0>;
+ };
+diff --git a/arch/arm/boot/dts/overlays/Makefile b/arch/arm/boot/dts/overlays/Makefile
+index 192bda7..6046f13 100644
+--- a/arch/arm/boot/dts/overlays/Makefile
++++ b/arch/arm/boot/dts/overlays/Makefile
+@@ -38,6 +38,7 @@ dtb-$(RPI_DT_OVERLAYS) += pps-gpio-overlay.dtb
+ dtb-$(RPI_DT_OVERLAYS) += pwm-overlay.dtb
+ dtb-$(RPI_DT_OVERLAYS) += pwm-2chan-overlay.dtb
+ dtb-$(RPI_DT_OVERLAYS) += raspidac3-overlay.dtb
++dtb-$(RPI_DT_OVERLAYS) += rpi-cirrus-wm5102-overlay.dtb
+ dtb-$(RPI_DT_OVERLAYS) += rpi-dac-overlay.dtb
+ dtb-$(RPI_DT_OVERLAYS) += rpi-display-overlay.dtb
+ dtb-$(RPI_DT_OVERLAYS) += rpi-ft5406-overlay.dtb
+diff --git a/arch/arm/boot/dts/overlays/rpi-cirrus-wm5102-overlay.dts b/arch/arm/boot/dts/overlays/rpi-cirrus-wm5102-overlay.dts
+new file mode 100644
+index 0000000..85b3d15
+--- /dev/null
++++ b/arch/arm/boot/dts/overlays/rpi-cirrus-wm5102-overlay.dts
+@@ -0,0 +1,141 @@
++// Definitions for Cirrus audio card
++/dts-v1/;
++/plugin/;
++
++/ {
++ compatible = "brcm,bcm2708";
++
++ fragment@0 {
++ target-path = "/";
++ __overlay__ {
++ aliases {
++ ldo0 = &ldo0;
++ ldo1 = &ldo1;
++ };
++ };
++ };
++
++ fragment@1 {
++ target = <&sound>;
++ __overlay__ {
++ compatible = "wlf,rpi-wm5102";
++ i2s-controller = <&i2s>;
++ status = "okay";
++ };
++ };
++
++ fragment@2 {
++ target = <&i2s>;
++ __overlay__ {
++ status = "okay";
++ };
++ };
++
++ fragment@3 {
++ target = <&gpio>;
++ __overlay__ {
++ wlf_pins: wlf_pins {
++ brcm,pins = <17 22 27>;
++ brcm,function = <1 1 0>;
++ };
++ };
++ };
++
++ fragment@4 {
++ target-path = "/soc";
++ __overlay__ {
++
++ ldo1: ldo1 {
++ compatible = "regulator-fixed";
++ regulator-name = "DC_5V";
++ regulator-min-microvolt = <5000000>;
++ regulator-max-microvolt = <5000000>;
++ enable-active-high;
++ regulator-always-on;
++ };
++
++ ldo0: ldo0 {
++ compatible = "regulator-fixed";
++ regulator-name = "DC_1V8";
++ regulator-min-microvolt = <1800000>;
++ regulator-max-microvolt = <1800000>;
++ enable-active-high;
++ regulator-always-on;
++ };
++
++ wsp_preinit: wsp_preinit {
++ compatible = "wlf,rpi-wm5102-preinit";
++ };
++ };
++ };
++
++ fragment@5 {
++ target = <&spi0>;
++ __overlay__ {
++ #address-cells = <1>;
++ #size-cells = <0>;
++ status = "okay";
++
++ spidev@0{
++ status = "disabled";
++ };
++
++ spidev@1{
++ status = "disabled";
++ };
++
++ wm5102@1{
++ compatible = "wlf,wm5102";
++ reg = <1>;
++ #address-cells = <1>;
++ #size-cells = <0>;
++ spi-max-frequency = <500000>;
++
++ interrupt-parent = <&gpio>;
++ interrupts = <27 8>;
++
++ LDOVDD-supply = <&ldo0>;
++ AVDD-supply = <&ldo0>;
++ DBVDD1-supply = <&ldo0>;
++ DBVDD2-supply = <&ldo0>;
++ DBVDD3-supply = <&ldo0>;
++ CPVDD-supply = <&ldo0>;
++ SPKVDDL-supply = <&ldo1>;
++ SPKVDDR-supply = <&ldo1>;
++
++ wlf,reset = <&gpio 17 0>;
++ wlf,ldoena = <&gpio 22 0>;
++ wlf,gpio-defaults = <
++ 0x000fffff
++ 0x000fffff
++ 0x000fffff
++ 0x000fffff
++ 0x000fffff
++ >;
++ wlf,micd-configs = <0 1 0>;
++ wlf,dmic-ref = <0 2 0 0>;
++ wlf,clk32k-src = <3>;
++ wlf,inmode = <0 2 1 0>;
++ status = "okay";
++ };
++ };
++ };
++
++ fragment@6 {
++ target = <&i2c1>;
++ __overlay__ {
++ status = "okay";
++ #address-cells = <1>;
++ #size-cells = <0>;
++
++ wm8804@3a {
++ #sound-dai-cells = <0>;
++ compatible = "wlf,wm8804";
++ reg = <0x3a>;
++ status = "okay";
++ PVDD-supply = <&ldo0>;
++ DVDD-supply = <&ldo0>;
++ };
++ };
++ };
++};
+diff --git a/arch/arm/configs/bcm2709_defconfig b/arch/arm/configs/bcm2709_defconfig
+index 630da1b..b4e2906 100644
+--- a/arch/arm/configs/bcm2709_defconfig
++++ b/arch/arm/configs/bcm2709_defconfig
+@@ -639,6 +639,9 @@ CONFIG_STMPE_SPI=y
+ CONFIG_MFD_ARIZONA_I2C=m
+ CONFIG_MFD_ARIZONA_SPI=m
+ CONFIG_MFD_WM5102=y
++CONFIG_REGULATOR=y
++CONFIG_REGULATOR_FIXED_VOLTAGE=m
++CONFIG_REGULATOR_ARIZONA=m
+ CONFIG_MEDIA_SUPPORT=m
+ CONFIG_MEDIA_CAMERA_SUPPORT=y
+ CONFIG_MEDIA_ANALOG_TV_SUPPORT=y
+@@ -843,6 +846,7 @@ CONFIG_SND_BCM2708_SOC_RPI_DAC=m
+ CONFIG_SND_BCM2708_SOC_RPI_PROTO=m
+ CONFIG_SND_BCM2708_SOC_IQAUDIO_DAC=m
+ CONFIG_SND_BCM2708_SOC_RASPIDAC3=m
++CONFIG_SND_BCM2708_SOC_RPI_CODEC_WSP=m
+ CONFIG_SND_SOC_ADAU1701=m
+ CONFIG_SND_SOC_WM8804_I2C=m
+ CONFIG_SND_SIMPLE_CARD=m
+diff --git a/arch/arm/configs/bcmrpi_defconfig b/arch/arm/configs/bcmrpi_defconfig
+index df4cebc..bfe13a3 100644
+--- a/arch/arm/configs/bcmrpi_defconfig
++++ b/arch/arm/configs/bcmrpi_defconfig
+@@ -632,6 +632,9 @@ CONFIG_STMPE_SPI=y
+ CONFIG_MFD_ARIZONA_I2C=m
+ CONFIG_MFD_ARIZONA_SPI=m
+ CONFIG_MFD_WM5102=y
++CONFIG_REGULATOR=y
++CONFIG_REGULATOR_FIXED_VOLTAGE=m
++CONFIG_REGULATOR_ARIZONA=m
+ CONFIG_MEDIA_SUPPORT=m
+ CONFIG_MEDIA_CAMERA_SUPPORT=y
+ CONFIG_MEDIA_ANALOG_TV_SUPPORT=y
+@@ -836,6 +839,7 @@ CONFIG_SND_BCM2708_SOC_RPI_DAC=m
+ CONFIG_SND_BCM2708_SOC_RPI_PROTO=m
+ CONFIG_SND_BCM2708_SOC_IQAUDIO_DAC=m
+ CONFIG_SND_BCM2708_SOC_RASPIDAC3=m
++CONFIG_SND_BCM2708_SOC_RPI_CODEC_WSP=m
+ CONFIG_SND_SOC_ADAU1701=m
+ CONFIG_SND_SOC_WM8804_I2C=m
+ CONFIG_SND_SIMPLE_CARD=m
+diff --git a/arch/arm/mach-bcm2708/bcm2708.c b/arch/arm/mach-bcm2708/bcm2708.c
+index 1b9c3be..39b6698 100644
+--- a/arch/arm/mach-bcm2708/bcm2708.c
++++ b/arch/arm/mach-bcm2708/bcm2708.c
+@@ -62,10 +62,17 @@
+ #include "bcm2708.h"
+ #include "armctrl.h"
+
++#include <linux/mfd/arizona/pdata.h>
++#include <linux/regulator/machine.h>
++#include <linux/regulator/fixed.h>
++
+ #ifdef CONFIG_BCM_VC_CMA
+ #include <linux/broadcom/vc_cma.h>
+ #endif
+
++#define GPIO_WM5102_IRQ 27
++#define GPIO_WM5102_RST 17
++#define GPIO_WM5102_LDOEN 22
+
+ /* Effectively we have an IOMMU (ARM<->VideoCore map) that is set up to
+ * give us IO access only to 64Mbytes of physical memory (26 bits). We could
+@@ -587,7 +594,9 @@ static struct spi_board_info bcm2708_spi_devices[] = {
+ .bus_num = 0,
+ .chip_select = 0,
+ .mode = SPI_MODE_0,
+- }, {
++ },
++#if !defined (CONFIG_SND_BCM2708_SOC_RPI_CODEC_WSP_MODULE) && !defined (CONFIG_SND_BCM2708_SOC_RPI_CODEC_WSP)
++ {
+ .modalias = "spidev",
+ .max_speed_hz = 500000,
+ .bus_num = 0,
+@@ -595,6 +604,7 @@ static struct spi_board_info bcm2708_spi_devices[] = {
+ .mode = SPI_MODE_0,
+ }
+ #endif
++#endif
+ };
+ #endif
+
+@@ -663,6 +673,123 @@ static struct platform_device bcm2708_i2s_device = {
+ };
+ #endif
+
++#if defined(CONFIG_REGULATOR_FIXED_VOLTAGE_MODULE) || defined(CONFIG_REGULATOR_FIXED_VOLTAGE)
++static struct regulator_consumer_supply dc1v8_consumers[] = {
++ REGULATOR_SUPPLY("LDOVDD", "spi0.1"),
++ REGULATOR_SUPPLY("AVDD", "spi0.1"),
++ REGULATOR_SUPPLY("DBVDD1", "spi0.1"),
++ REGULATOR_SUPPLY("CPVDD", "spi0.1"),
++ REGULATOR_SUPPLY("DBVDD2", "spi0.1"),
++ REGULATOR_SUPPLY("DBVDD3", "spi0.1"),
++ REGULATOR_SUPPLY("PVDD", "1-003a"),
++ REGULATOR_SUPPLY("DVDD", "1-003a"),
++};
++
++static struct regulator_init_data dc1v8_data = {
++ .constraints = {
++ .always_on = 1,
++ },
++ .num_consumer_supplies = ARRAY_SIZE(dc1v8_consumers),
++ .consumer_supplies = dc1v8_consumers,
++};
++
++static struct fixed_voltage_config dc1v8vdd_pdata = {
++ .supply_name = "DC_1V8",
++ .microvolts = 1800000,
++ .init_data = &dc1v8_data,
++ .gpio = -1,
++};
++
++static struct platform_device dc1v8_device = {
++ .name = "reg-fixed-voltage",
++ .id = 0,
++ .dev = {
++ .platform_data = &dc1v8vdd_pdata,
++ },
++};
++
++static struct regulator_consumer_supply dc5v_consumers[] = {
++ REGULATOR_SUPPLY("SPKVDDL", "spi0.1"),
++ REGULATOR_SUPPLY("SPKVDDR", "spi0.1"),
++};
++
++static struct regulator_init_data dc5v_data = {
++ .constraints = {
++ .always_on = 1,
++ },
++ .num_consumer_supplies = ARRAY_SIZE(dc5v_consumers),
++ .consumer_supplies = dc5v_consumers,
++};
++
++static struct fixed_voltage_config dc5vvdd_pdata = {
++ .supply_name = "DC_5V",
++ .microvolts = 5000000,
++ .init_data = &dc5v_data,
++ .gpio = -1,
++};
++
++static struct platform_device dc5v_device = {
++ .name = "reg-fixed-voltage",
++ .id = 1,
++ .dev = {
++ .platform_data = &dc5vvdd_pdata,
++ },
++};
++#endif
++
++#if defined(CONFIG_SND_BCM2708_SOC_RPI_CODEC_WSP_MODULE) || defined(CONFIG_SND_BCM2708_SOC_RPI_CODEC_WSP)
++#include <linux/mfd/arizona/registers.h>
++
++static struct platform_device snd_rpi_wsp_device = {
++ .name = "snd-rpi-wsp",
++ .id = 0,
++ .num_resources = 0,
++};
++
++static struct arizona_micd_config wm5102_micd[] = {
++ { 0, 1, 0 },
++};
++
++static struct arizona_pdata snd_rpi_wsp_spi_platform_data = {
++ .reset = GPIO_WM5102_RST,
++ .ldoena = GPIO_WM5102_LDOEN,
++ .irq_flags = IRQF_TRIGGER_RISING,
++ .gpio_defaults = {
++ [2] = 0x04, /* OPCLK */
++ [3] = 0x3d, /* ASYNC OPCLK */
++ },
++ .micd_configs = wm5102_micd,
++ .num_micd_configs = ARRAY_SIZE(wm5102_micd),
++ .dmic_ref = {
++ [1] = ARIZONA_DMIC_MICBIAS2,
++ },
++ .inmode = {
++ [1] = ARIZONA_INMODE_DMIC,
++ [2] = ARIZONA_INMODE_SE,
++ },
++ .clk32k_src = ARIZONA_32KZ_NONE,
++ .irq_gpio = GPIO_WM5102_IRQ,
++};
++
++static struct spi_board_info __initdata snd_rpi_wsp_spi_devices[] = {
++ {
++ .modalias = "wm5102",
++ .platform_data = &snd_rpi_wsp_spi_platform_data,
++ .max_speed_hz = 500000,
++ .bus_num = 0,
++ .chip_select = 1,
++ .mode = SPI_MODE_0,
++ }
++};
++
++static struct i2c_board_info __initdata snd_rpi_wsp_i2c_devices[] = {
++ {
++ I2C_BOARD_INFO("wm8804", 0x3A),
++ },
++};
++
++#endif
++
+ #if defined(CONFIG_SND_BCM2708_SOC_HIFIBERRY_DAC) || defined(CONFIG_SND_BCM2708_SOC_HIFIBERRY_DAC_MODULE)
+ static struct platform_device snd_hifiberry_dac_device = {
+ .name = "snd-hifiberry-dac",
+@@ -772,6 +899,9 @@ int __init bcm_register_device(struct platform_device *pdev)
+ #define i2c_register_board_info_dt(busnum, info, n) \
+ if (!use_dt) i2c_register_board_info(busnum, info, n)
+
++#define spi_register_board_info_dt(info, n) \
++ if (!use_dt) spi_register_board_info(info, n)
++
+ int calc_rsts(int partition)
+ {
+ return PM_PASSWORD |
+@@ -965,6 +1095,18 @@ void __init bcm2708_init(void)
+ i2c_register_board_info_dt(1, snd_pcm512x_i2c_devices, ARRAY_SIZE(snd_pcm512x_i2c_devices));
+ #endif
+
++#if defined(CONFIG_REGULATOR_FIXED_VOLTAGE_MODULE) || defined(CONFIG_REGULATOR_FIXED_VOLTAGE)
++ bcm_register_device_dt(&dc1v8_device);
++ bcm_register_device_dt(&dc5v_device);
++#endif
++
++#if defined(CONFIG_SND_BCM2708_SOC_RPI_CODEC_WSP_MODULE) || defined(CONFIG_SND_BCM2708_SOC_RPI_CODEC_WSP)
++ bcm_register_device_dt(&snd_rpi_wsp_device);
++ spi_register_board_info_dt(snd_rpi_wsp_spi_devices, ARRAY_SIZE(snd_rpi_wsp_spi_devices));
++ i2c_register_board_info_dt(1, snd_rpi_wsp_i2c_devices,
++ ARRAY_SIZE(snd_rpi_wsp_i2c_devices));
++#endif
++
+ if (!use_dt) {
+ for (i = 0; i < ARRAY_SIZE(amba_devs); i++) {
+ struct amba_device *d = amba_devs[i];
+diff --git a/drivers/mfd/arizona-core.c b/drivers/mfd/arizona-core.c
+index 6523903..4211644 100644
+--- a/drivers/mfd/arizona-core.c
++++ b/drivers/mfd/arizona-core.c
+@@ -16,6 +16,7 @@
+ #include <linux/interrupt.h>
+ #include <linux/mfd/core.h>
+ #include <linux/module.h>
++#include <linux/notifier.h>
+ #include <linux/of.h>
+ #include <linux/of_device.h>
+ #include <linux/of_gpio.h>
+@@ -94,6 +95,91 @@ int arizona_clk32k_disable(struct arizona *arizona)
+ }
+ EXPORT_SYMBOL_GPL(arizona_clk32k_disable);
+
++int arizona_dvfs_up(struct arizona *arizona, unsigned int flags)
++{
++ unsigned int new_flags;
++ int ret = 0;
++
++ mutex_lock(&arizona->subsys_max_lock);
++
++ new_flags = arizona->subsys_max_rq | flags;
++
++ if (arizona->subsys_max_rq != new_flags) {
++ switch (arizona->type) {
++ case WM5102:
++ case WM8997:
++ ret = regulator_set_voltage(arizona->dcvdd,
++ 1800000, 1800000);
++ if (ret != 0) {
++ dev_err(arizona->dev,
++ "Failed to set DCVDD (DVFS up): %d\n",
++ ret);
++ goto err;
++ }
++
++ ret = regmap_update_bits(arizona->regmap,
++ ARIZONA_DYNAMIC_FREQUENCY_SCALING_1,
++ ARIZONA_SUBSYS_MAX_FREQ, 1);
++ if (ret != 0) {
++ dev_err(arizona->dev,
++ "Failed to enable subsys max: %d\n",
++ ret);
++ regulator_set_voltage(arizona->dcvdd,
++ 1200000, 1800000);
++ goto err;
++ }
++ break;
++
++ default:
++ break;
++ }
++
++ arizona->subsys_max_rq = new_flags;
++ }
++err:
++ mutex_unlock(&arizona->subsys_max_lock);
++ return ret;
++}
++EXPORT_SYMBOL_GPL(arizona_dvfs_up);
++
++int arizona_dvfs_down(struct arizona *arizona, unsigned int flags)
++{
++ int ret = 0;
++
++ mutex_lock(&arizona->subsys_max_lock);
++
++ arizona->subsys_max_rq &= ~flags;
++
++ if (arizona->subsys_max_rq == 0) {
++ switch (arizona->type) {
++ case WM5102:
++ case WM8997:
++ ret = regmap_update_bits(arizona->regmap,
++ ARIZONA_DYNAMIC_FREQUENCY_SCALING_1,
++ ARIZONA_SUBSYS_MAX_FREQ, 0);
++ if (ret != 0)
++ dev_err(arizona->dev,
++ "Failed to disable subsys max: %d\n",
++ ret);
++
++ ret = regulator_set_voltage(arizona->dcvdd,
++ 1200000, 1800000);
++ if (ret != 0)
++ dev_err(arizona->dev,
++ "Failed to set DCVDD (DVFS down): %d\n",
++ ret);
++ break;
++
++ default:
++ break;
++ }
++ }
++
++ mutex_unlock(&arizona->subsys_max_lock);
++ return ret;
++}
++EXPORT_SYMBOL_GPL(arizona_dvfs_down);
++
+ static irqreturn_t arizona_clkgen_err(int irq, void *data)
+ {
+ struct arizona *arizona = data;
+@@ -330,6 +416,20 @@ err_fll:
+ return err;
+ }
+
++static int arizona_dcvdd_notify(struct notifier_block *nb,
++ unsigned long action, void *data)
++{
++ struct arizona *arizona = container_of(nb, struct arizona,
++ dcvdd_notifier);
++
++ dev_dbg(arizona->dev, "DCVDD notify %lx\n", action);
++
++ if (action & REGULATOR_EVENT_DISABLE)
++ msleep(20);
++
++ return NOTIFY_DONE;
++}
++
+ #ifdef CONFIG_PM
+ static int arizona_runtime_resume(struct device *dev)
+ {
+@@ -519,6 +619,154 @@ int arizona_of_get_named_gpio(struct arizona *arizona, const char *prop,
+ }
+ EXPORT_SYMBOL_GPL(arizona_of_get_named_gpio);
+
++static int arizona_of_get_u32_num_groups(struct arizona *arizona,
++ const char *prop,
++ int group_size)
++{
++ int len_prop;
++ int num_groups;
++
++ if (!of_get_property(arizona->dev->of_node, prop, &len_prop))
++ return -EINVAL;
++
++ num_groups = len_prop / (group_size * sizeof(u32));
++
++ if (num_groups * group_size * sizeof(u32) != len_prop) {
++ dev_err(arizona->dev,
++ "DT property %s is malformed: %d\n",
++ prop, -EOVERFLOW);
++ return -EOVERFLOW;
++ }
++
++ return num_groups;
++}
++
++static int arizona_of_get_micd_ranges(struct arizona *arizona,
++ const char *prop)
++{
++ int nranges;
++ int i, j;
++ int ret = 0;
++ u32 value;
++ struct arizona_micd_range *micd_ranges;
++
++ nranges = arizona_of_get_u32_num_groups(arizona, prop, 2);
++ if (nranges < 0)
++ return nranges;
++
++ micd_ranges = devm_kzalloc(arizona->dev,
++ nranges * sizeof(struct arizona_micd_range),
++ GFP_KERNEL);
++
++ for (i = 0, j = 0; i < nranges; ++i) {
++ ret = of_property_read_u32_index(arizona->dev->of_node,
++ prop, j++, &value);
++ if (ret < 0)
++ goto error;
++ micd_ranges[i].max = value;
++
++ ret = of_property_read_u32_index(arizona->dev->of_node,
++ prop, j++, &value);
++ if (ret < 0)
++ goto error;
++ micd_ranges[i].key = value;
++ }
++
++ arizona->pdata.micd_ranges = micd_ranges;
++ arizona->pdata.num_micd_ranges = nranges;
++
++ return ret;
++
++error:
++ devm_kfree(arizona->dev, micd_ranges);
++ dev_err(arizona->dev, "DT property %s is malformed: %d\n", prop, ret);
++ return ret;
++}
++
++static int arizona_of_get_micd_configs(struct arizona *arizona,
++ const char *prop)
++{
++ int nconfigs;
++ int i, j;
++ int ret = 0;
++ u32 value;
++ struct arizona_micd_config *micd_configs;
++
++ nconfigs = arizona_of_get_u32_num_groups(arizona, prop, 3);
++ if (nconfigs < 0)
++ return nconfigs;
++
++ micd_configs = devm_kzalloc(arizona->dev,
++ nconfigs *
++ sizeof(struct arizona_micd_config),
++ GFP_KERNEL);
++
++ for (i = 0, j = 0; i < nconfigs; ++i) {
++ ret = of_property_read_u32_index(arizona->dev->of_node,
++ prop, j++, &value);
++ if (ret < 0)
++ goto error;
++ micd_configs[i].src = value;
++
++ ret = of_property_read_u32_index(arizona->dev->of_node,
++ prop, j++, &value);
++ if (ret < 0)
++ goto error;
++ micd_configs[i].bias = value;
++
++ ret = of_property_read_u32_index(arizona->dev->of_node,
++ prop, j++, &value);
++ if (ret < 0)
++ goto error;
++ micd_configs[i].gpio = value;
++ }
++
++ arizona->pdata.micd_configs = micd_configs;
++ arizona->pdata.num_micd_configs = nconfigs;
++
++ return ret;
++
++error:
++ devm_kfree(arizona->dev, micd_configs);
++ dev_err(arizona->dev, "DT property %s is malformed: %d\n", prop, ret);
++ return ret;
++}
++
++static int arizona_of_read_u32_array(struct arizona *arizona,
++ const char *prop, bool mandatory,
++ u32 *data, size_t num)
++{
++ int ret;
++
++ ret = of_property_read_u32_array(arizona->dev->of_node, prop,
++ data, num);
++
++ if (ret >= 0)
++ return 0;
++
++ switch (ret) {
++ case -EINVAL:
++ if (mandatory)
++ dev_err(arizona->dev,
++ "Mandatory DT property %s is missing\n",
++ prop);
++ break;
++ default:
++ dev_err(arizona->dev,
++ "DT property %s is malformed: %d\n",
++ prop, ret);
++ }
++
++ return ret;
++}
++
++static int arizona_of_read_u32(struct arizona *arizona,
++ const char* prop, bool mandatory,
++ u32 *data)
++{
++ return arizona_of_read_u32_array(arizona, prop, mandatory, data, 1);
++}
++
+ static int arizona_of_get_core_pdata(struct arizona *arizona)
+ {
+ struct arizona_pdata *pdata = &arizona->pdata;
+@@ -552,6 +800,15 @@ static int arizona_of_get_core_pdata(struct arizona *arizona)
+ ret);
+ }
+
++ arizona_of_read_u32(arizona, "wlf,clk32k-src", false,
++ &pdata->clk32k_src);
++
++ arizona_of_get_micd_ranges(arizona, "wlf,micd-ranges");
++ arizona_of_get_micd_configs(arizona, "wlf,micd-configs");
++
++ arizona_of_read_u32_array(arizona, "wlf,dmic-ref", false,
++ pdata->dmic_ref, ARRAY_SIZE(pdata->dmic_ref));
++
+ of_property_for_each_u32(arizona->dev->of_node, "wlf,inmode", prop,
+ cur, val) {
+ if (count == ARRAY_SIZE(arizona->pdata.inmode))
+@@ -571,6 +828,9 @@ static int arizona_of_get_core_pdata(struct arizona *arizona)
+ count++;
+ }
+
++ arizona_of_read_u32(arizona, "wlf,irq_gpio", false,
++ &pdata->irq_gpio);
++
+ return 0;
+ }
+
+@@ -670,6 +930,7 @@ int arizona_dev_init(struct arizona *arizona)
+
+ dev_set_drvdata(arizona->dev, arizona);
+ mutex_init(&arizona->clk_lock);
++ mutex_init(&arizona->subsys_max_lock);
+
+ if (dev_get_platdata(arizona->dev))
+ memcpy(&arizona->pdata, dev_get_platdata(arizona->dev),
+@@ -726,6 +987,14 @@ int arizona_dev_init(struct arizona *arizona)
+ goto err_early;
+ }
+
++ arizona->dcvdd_notifier.notifier_call = arizona_dcvdd_notify;
++ ret = regulator_register_notifier(arizona->dcvdd,
++ &arizona->dcvdd_notifier);
++ if (ret < 0) {
++ dev_err(dev, "Failed to register DCVDD notifier %d\n", ret);
++ goto err_dcvdd;
++ }
++
+ if (arizona->pdata.reset) {
+ /* Start out with /RESET low to put the chip into reset */
+ ret = gpio_request_one(arizona->pdata.reset,
+@@ -733,16 +1002,19 @@ int arizona_dev_init(struct arizona *arizona)
+ "arizona /RESET");
+ if (ret != 0) {
+ dev_err(dev, "Failed to request /RESET: %d\n", ret);
+- goto err_dcvdd;
++ goto err_notifier;
+ }
+ }
+
++ /* Ensure period of reset asserted before we apply the supplies */
++ msleep(20);
++
+ ret = regulator_bulk_enable(arizona->num_core_supplies,
+ arizona->core_supplies);
+ if (ret != 0) {
+ dev_err(dev, "Failed to enable core supplies: %d\n",
+ ret);
+- goto err_dcvdd;
++ goto err_notifier;
+ }
+
+ ret = regulator_enable(arizona->dcvdd);
+@@ -1060,6 +1332,8 @@ err_reset:
+ err_enable:
+ regulator_bulk_disable(arizona->num_core_supplies,
+ arizona->core_supplies);
++err_notifier:
++ regulator_unregister_notifier(arizona->dcvdd, &arizona->dcvdd_notifier);
+ err_dcvdd:
+ regulator_put(arizona->dcvdd);
+ err_early:
+@@ -1073,6 +1347,7 @@ int arizona_dev_exit(struct arizona *arizona)
+ pm_runtime_disable(arizona->dev);
+
+ regulator_disable(arizona->dcvdd);
++ regulator_unregister_notifier(arizona->dcvdd, &arizona->dcvdd_notifier);
+ regulator_put(arizona->dcvdd);
+
+ mfd_remove_devices(arizona->dev);
+diff --git a/drivers/regulator/arizona-ldo1.c b/drivers/regulator/arizona-ldo1.c
+index a1d07d3..353b930 100644
+--- a/drivers/regulator/arizona-ldo1.c
++++ b/drivers/regulator/arizona-ldo1.c
+@@ -78,11 +78,6 @@ static int arizona_ldo1_hc_set_voltage_sel(struct regulator_dev *rdev,
+ if (ret != 0)
+ return ret;
+
+- ret = regmap_update_bits(regmap, ARIZONA_DYNAMIC_FREQUENCY_SCALING_1,
+- ARIZONA_SUBSYS_MAX_FREQ, val);
+- if (ret != 0)
+- return ret;
+-
+ if (val)
+ return 0;
+
+@@ -268,6 +263,7 @@ static int arizona_ldo1_probe(struct platform_device *pdev)
+ }
+
+ config.ena_gpio = arizona->pdata.ldoena;
++ config.ena_gpio_flags = GPIOF_OUT_INIT_LOW;
+
+ if (arizona->pdata.ldo1)
+ config.init_data = arizona->pdata.ldo1;
+diff --git a/include/linux/mfd/arizona/core.h b/include/linux/mfd/arizona/core.h
+index 16a498f..e54676c 100644
+--- a/include/linux/mfd/arizona/core.h
++++ b/include/linux/mfd/arizona/core.h
+@@ -117,6 +117,7 @@ struct arizona {
+ int num_core_supplies;
+ struct regulator_bulk_data core_supplies[ARIZONA_MAX_CORE_SUPPLIES];
+ struct regulator *dcvdd;
++ struct notifier_block dcvdd_notifier;
+
+ struct arizona_pdata pdata;
+
+@@ -135,6 +136,9 @@ struct arizona {
+
+ bool ctrlif_error;
+
++ struct mutex subsys_max_lock;
++ unsigned int subsys_max_rq;
++
+ struct snd_soc_dapm_context *dapm;
+
+ int tdm_width[ARIZONA_MAX_AIF];
+@@ -145,8 +149,17 @@ struct arizona {
+ struct mutex dac_comp_lock;
+ };
+
++#define ARIZONA_DVFS_SR1_RQ 0x00000001
++#define ARIZONA_DVFS_SR2_RQ 0x00000002
++#define ARIZONA_DVFS_SR3_RQ 0x00000004
++#define ARIZONA_DVFS_ASR1_RQ 0x00000010
++#define ARIZONA_DVFS_ASR2_RQ 0x00000020
++#define ARIZONA_DVFS_ADSP1_RQ 0x00010000
++
+ int arizona_clk32k_enable(struct arizona *arizona);
+ int arizona_clk32k_disable(struct arizona *arizona);
++int arizona_dvfs_up(struct arizona *arizona, unsigned int mask);
++int arizona_dvfs_down(struct arizona *arizona, unsigned int mask);
+
+ int arizona_request_irq(struct arizona *arizona, int irq, char *name,
+ irq_handler_t handler, void *data);
+diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h
+index 1065095..edd011a 100644
+--- a/include/sound/soc-dapm.h
++++ b/include/sound/soc-dapm.h
+@@ -377,10 +377,11 @@ int snd_soc_dapm_new_dai_widgets(struct snd_soc_dapm_context *dapm,
+ int snd_soc_dapm_link_dai_widgets(struct snd_soc_card *card);
+ void snd_soc_dapm_connect_dai_link_widgets(struct snd_soc_card *card);
+ int snd_soc_dapm_new_pcm(struct snd_soc_card *card,
+- const struct snd_soc_pcm_stream *params,
++ struct snd_soc_pcm_stream *params,
+ unsigned int num_params,
+ struct snd_soc_dapm_widget *source,
+- struct snd_soc_dapm_widget *sink);
++ struct snd_soc_dapm_widget *sink,
++ void *priv);
+
+ /* dapm path setup */
+ int snd_soc_dapm_new_widgets(struct snd_soc_card *card);
+@@ -530,7 +531,7 @@ struct snd_soc_dapm_widget {
+
+ void *priv; /* widget specific data */
+ struct regulator *regulator; /* attached regulator */
+- const struct snd_soc_pcm_stream *params; /* params for dai links */
++ struct snd_soc_pcm_stream *params; /* params for dai links */
+ unsigned int num_params; /* number of params for dai links */
+ unsigned int params_select; /* currently selected param for dai link */
+
+diff --git a/include/sound/soc.h b/include/sound/soc.h
+index f6226914..a9389cf 100644
+--- a/include/sound/soc.h
++++ b/include/sound/soc.h
+@@ -954,7 +954,9 @@ struct snd_soc_dai_link {
+ struct device_node *platform_of_node;
+ int be_id; /* optional ID for machine driver BE identification */
+
+- const struct snd_soc_pcm_stream *params;
++ struct snd_soc_pcm_stream *params;
++ /* optional params re-writing for dai links */
++ int (*params_fixup)(struct snd_soc_dapm_widget *w, int event);
+ unsigned int num_params;
+
+ unsigned int dai_fmt; /* format to set on init */
+diff --git a/sound/soc/bcm/Kconfig b/sound/soc/bcm/Kconfig
+index 3db2852..7c5699a 100644
+--- a/sound/soc/bcm/Kconfig
++++ b/sound/soc/bcm/Kconfig
+@@ -75,3 +75,15 @@ config SND_BCM2708_SOC_RASPIDAC3
+ select SND_SOC_TPA6130A2
+ help
+ Say Y or M if you want to add support for RaspiDAC Rev.3x.
++
++config SND_BCM2708_SOC_RPI_CODEC_WSP_PREINIT
++ tristate
++
++config SND_BCM2708_SOC_RPI_CODEC_WSP
++ tristate "Support for Cirrus sound pi"
++ depends on SND_BCM2708_SOC_I2S
++ select SND_SOC_WM5102
++ select SND_SOC_WM8804
++ select SND_BCM2708_SOC_RPI_CODEC_WSP_PREINIT
++ help
++ Say Y or M if you want to add support for Cirrus sound pi
+diff --git a/sound/soc/bcm/Makefile b/sound/soc/bcm/Makefile
+index 621358b..65e120d 100644
+--- a/sound/soc/bcm/Makefile
++++ b/sound/soc/bcm/Makefile
+@@ -17,6 +17,8 @@ snd-soc-rpi-dac-objs := rpi-dac.o
+ snd-soc-rpi-proto-objs := rpi-proto.o
+ snd-soc-iqaudio-dac-objs := iqaudio-dac.o
+ snd-soc-raspidac3-objs := raspidac3.o
++snd-soc-rpi-wsp-objs := rpi-cirrus-sound-pi.o
++snd-soc-rpi-wsp-preinit-objs := rpi-cirrus-sound-pi-preinit.o
+
+ obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_DAC) += snd-soc-hifiberry-dac.o
+ obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUS) += snd-soc-hifiberry-dacplus.o
+@@ -26,3 +28,5 @@ obj-$(CONFIG_SND_BCM2708_SOC_RPI_DAC) += snd-soc-rpi-dac.o
+ obj-$(CONFIG_SND_BCM2708_SOC_RPI_PROTO) += snd-soc-rpi-proto.o
+ obj-$(CONFIG_SND_BCM2708_SOC_IQAUDIO_DAC) += snd-soc-iqaudio-dac.o
+ obj-$(CONFIG_SND_BCM2708_SOC_RASPIDAC3) += snd-soc-raspidac3.o
++obj-$(CONFIG_SND_BCM2708_SOC_RPI_CODEC_WSP) += snd-soc-rpi-wsp.o
++obj-$(CONFIG_SND_BCM2708_SOC_RPI_CODEC_WSP_PREINIT) += snd-soc-rpi-wsp-preinit.o
+diff --git a/sound/soc/bcm/rpi-cirrus-sound-pi-preinit.c b/sound/soc/bcm/rpi-cirrus-sound-pi-preinit.c
+new file mode 100644
+index 0000000..a2f17e4
+--- /dev/null
++++ b/sound/soc/bcm/rpi-cirrus-sound-pi-preinit.c
+@@ -0,0 +1,188 @@
++/*
++ * Cirrus Audio Card wm8804 preinit code
++ *
++ * Refactored out of the Cirrus Audio Card code to work around
++ * init dependecy issues.
++ *
++ * Copyright 2015 Cirrus Logic Inc.
++ *
++ * Author: Nikesh Oswal, <Nikesh.Oswal@wolfsonmicro.com>
++ * Author: Matthias Reichl, <hias@horus.com>
++ * Partly based on sound/soc/bcm/iqaudio-dac.c
++ *
++ * 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/module.h>
++#include <linux/platform_device.h>
++#include <linux/gpio.h>
++#include <linux/delay.h>
++#include <linux/io.h>
++
++#include <asm/system_info.h>
++
++/*TODO: Shift this to platform data*/
++#define GPIO_WM8804_RST 8
++#define GPIO_WM8804_MODE 2
++#define GPIO_WM8804_SW_MODE 23
++#define GPIO_WM8804_I2C_ADDR_B 18
++#define GPIO_WM8804_I2C_ADDR_B_PLUS 13
++
++static void bcm2708_set_gpio_alt(int pin, int alt)
++{
++ /*
++ * This is the common way to handle the GPIO pins for
++ * the Raspberry Pi.
++ * TODO This is a hack. Use pinmux / pinctrl.
++ */
++#define INP_GPIO(g) *(gpio+((g)/10)) &= ~(7<<(((g)%10)*3))
++#define SET_GPIO_ALT(g,a) *(gpio+(((g)/10))) |= (((a)<=3?(a)+4:(a)==4?3:2)<<(((g)%10)*3))
++ unsigned int *gpio;
++ gpio = ioremap(GPIO_BASE, SZ_16K);
++ INP_GPIO(pin);
++ SET_GPIO_ALT(pin, alt);
++ iounmap(gpio);
++#undef INP_GPIO
++#undef SET_GPIO_ALT
++}
++
++static int wm8804_reset(void)
++ {
++ int ret;
++ unsigned int gpio_wm8804_i2c_addr;
++
++ if ((system_rev & 0xffffff) >= 0x10) {
++ /* Model B+ or later */
++ gpio_wm8804_i2c_addr = GPIO_WM8804_I2C_ADDR_B_PLUS;
++ } else {
++ gpio_wm8804_i2c_addr = GPIO_WM8804_I2C_ADDR_B;
++ }
++
++ if (!gpio_is_valid(GPIO_WM8804_RST)) {
++ pr_err("Skipping unavailable gpio %d (%s)\n", GPIO_WM8804_RST, "wm8804_rst");
++ return -ENOMEM;
++ }
++
++ if (!gpio_is_valid(GPIO_WM8804_MODE)) {
++ pr_err("Skipping unavailable gpio %d (%s)\n", GPIO_WM8804_MODE, "wm8804_mode");
++ return -ENOMEM;
++ }
++
++ if (!gpio_is_valid(GPIO_WM8804_SW_MODE)) {
++ pr_err("Skipping unavailable gpio %d (%s)\n", GPIO_WM8804_SW_MODE, "wm8804_sw_mode");
++ return -ENOMEM;
++ }
++
++ if (!gpio_is_valid(gpio_wm8804_i2c_addr)) {
++ pr_err("Skipping unavailable gpio %d (%s)\n", gpio_wm8804_i2c_addr, "wm8804_i2c_addr");
++ return -ENOMEM;
++ }
++
++ ret = gpio_request(GPIO_WM8804_RST, "wm8804_rst");
++ if (ret < 0) {
++ pr_err("gpio_request wm8804_rst failed\n");
++ return ret;
++ }
++
++ ret = gpio_request(GPIO_WM8804_MODE, "wm8804_mode");
++ if (ret < 0) {
++ pr_err("gpio_request wm8804_mode failed\n");
++ return ret;
++ }
++
++ ret = gpio_request(GPIO_WM8804_SW_MODE, "wm8804_sw_mode");
++ if (ret < 0) {
++ pr_err("gpio_request wm8804_sw_mode failed\n");
++ return ret;
++ }
++
++ ret = gpio_request(gpio_wm8804_i2c_addr, "wm8804_i2c_addr");
++ if (ret < 0) {
++ pr_err("gpio_request wm8804_i2c_addr failed\n");
++ return ret;
++ }
++
++ /*GPIO2 is used for SW/HW Mode Select and after Reset the same pin is used as
++ I2C data line, so initially it is configured as GPIO OUT from BCM perspective*/
++ /*Set SW Mode*/
++ ret = gpio_direction_output(GPIO_WM8804_MODE, 1);
++ if (ret < 0) {
++ pr_err("gpio_direction_output wm8804_mode failed\n");
++ }
++
++ /*Set 2 Wire (I2C) Mode*/
++ ret = gpio_direction_output(GPIO_WM8804_SW_MODE, 0);
++ if (ret < 0) {
++ pr_err("gpio_direction_output wm8804_sw_mode failed\n");
++ }
++
++ /*Set 2 Wire (I2C) Addr to 0x3A, writing 1 will make the Addr as 0x3B*/
++ ret = gpio_direction_output(gpio_wm8804_i2c_addr, 0);
++ if (ret < 0) {
++ pr_err("gpio_direction_output wm8804_i2c_addr failed\n");
++ }
++
++ /*Take WM8804 out of reset*/
++ ret = gpio_direction_output(GPIO_WM8804_RST, 1);
++ if (ret < 0) {
++ pr_err("gpio_direction_output wm8804_rst failed\n");
++ }
++
++ /*Put WM8804 in reset*/
++ gpio_set_value(GPIO_WM8804_RST, 0);
++ mdelay(500);
++ /*Take WM8804 out of reset*/
++ gpio_set_value(GPIO_WM8804_RST, 1);
++ mdelay(500);
++
++ gpio_free(GPIO_WM8804_RST);
++ gpio_free(GPIO_WM8804_MODE);
++ gpio_free(GPIO_WM8804_SW_MODE);
++ gpio_free(gpio_wm8804_i2c_addr);
++
++ /*GPIO2 is used for SW/HW Mode Select and after Reset the same pin is used as
++ I2C data line, so after reset it is configured as I2C data line i.e ALT0 function*/
++ bcm2708_set_gpio_alt(GPIO_WM8804_MODE, 0);
++
++ return ret;
++}
++
++static int snd_rpi_wsp_preinit_probe(struct platform_device *pdev)
++{
++ int ret;
++ struct device *dev = &pdev->dev;
++
++ dev_info(dev, "initializing wm8804 on Cirrus audio card\n");
++ ret = wm8804_reset();
++ if (ret)
++ dev_err(dev, "wm8804_reset returned %d\n", ret);
++
++ return ret;
++}
++
++#ifdef CONFIG_OF
++static const struct of_device_id snd_rpi_wsp_preinit_of_match[] = {
++ { .compatible = "wlf,rpi-wm5102-preinit", },
++ {},
++};
++MODULE_DEVICE_TABLE(of, snd_rpi_wsp_preinit_of_match);
++#endif /* CONFIG_OF */
++
++static struct platform_driver snd_rpi_wsp_preinit_driver = {
++ .driver = {
++ .name = "snd-rpi-wsp-preinit",
++ .owner = THIS_MODULE,
++ .of_match_table = of_match_ptr(snd_rpi_wsp_preinit_of_match),
++ },
++ .probe = snd_rpi_wsp_preinit_probe,
++};
++
++module_platform_driver(snd_rpi_wsp_preinit_driver);
++
++MODULE_AUTHOR("Nikesh Oswal");
++MODULE_AUTHOR("Liu Xin");
++MODULE_AUTHOR("Matthias Reichl");
++MODULE_DESCRIPTION("Cirrus sound pi wm8804 one-time initialisation code");
++MODULE_LICENSE("GPL");
+diff --git a/sound/soc/bcm/rpi-cirrus-sound-pi.c b/sound/soc/bcm/rpi-cirrus-sound-pi.c
+new file mode 100644
+index 0000000..f7b8839
+--- /dev/null
++++ b/sound/soc/bcm/rpi-cirrus-sound-pi.c
+@@ -0,0 +1,530 @@
++/*
++ * ASoC machine driver for Cirrus Audio Card (with a WM5102 and WM8804 codecs )
++ * connected to a Raspberry Pi
++ *
++ * Copyright 2015 Cirrus Logic Inc.
++ *
++ * Author: Nikesh Oswal, <Nikesh.Oswal@wolfsonmicro.com>
++ * Partly based on sound/soc/bcm/iqaudio-dac.c
++ *
++ * 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/module.h>
++#include <linux/slab.h>
++#include <sound/pcm_params.h>
++
++#include "../codecs/wm5102.h"
++#include "../codecs/wm8804.h"
++
++#define WM8804_CLKOUT_HZ 12000000
++
++#define RPI_WLF_SR 44100
++#define WM5102_MAX_SYSCLK_1 49152000 /*max sysclk for 4K family*/
++#define WM5102_MAX_SYSCLK_2 45158400 /*max sysclk for 11.025K family*/
++
++static struct snd_soc_card snd_rpi_wsp;
++
++struct wm5102_machine_priv {
++ int wm8804_sr;
++ int wm5102_sr;
++ int sync_path_enable;
++};
++
++enum {
++ GPIO_FSEL_INPUT, GPIO_FSEL_OUTPUT,
++ GPIO_FSEL_ALT5, GPIO_FSEL_ALT_4,
++ GPIO_FSEL_ALT0, GPIO_FSEL_ALT1,
++ GPIO_FSEL_ALT2, GPIO_FSEL_ALT3,
++};
++
++int spdif_rx_enable_event(struct snd_soc_dapm_widget *w,
++ struct snd_kcontrol *kcontrol, int event)
++{
++ struct snd_soc_card *card = &snd_rpi_wsp;
++ struct wm5102_machine_priv *priv = snd_soc_card_get_drvdata(card);
++ struct snd_soc_codec *wm5102_codec = card->rtd[0].codec;
++ int ret = 0;
++ int clk_freq;
++ int sr = priv->wm8804_sr;
++
++ switch (event) {
++ case SND_SOC_DAPM_POST_PMU:
++ /* Enable sync path in case of SPDIF capture use case */
++ clk_freq = (sr % 4000 == 0) ? WM5102_MAX_SYSCLK_1 : WM5102_MAX_SYSCLK_2;
++
++ /*reset FLL1*/
++ snd_soc_codec_set_pll(wm5102_codec, WM5102_FLL1_REFCLK,
++ ARIZONA_FLL_SRC_NONE, 0, 0);
++ snd_soc_codec_set_pll(wm5102_codec, WM5102_FLL1,
++ ARIZONA_FLL_SRC_NONE, 0, 0);
++
++ ret = snd_soc_codec_set_pll(wm5102_codec, WM5102_FLL1_REFCLK,
++ ARIZONA_CLK_SRC_MCLK1,
++ WM8804_CLKOUT_HZ,
++ clk_freq);
++ if (ret != 0) {
++ dev_err(wm5102_codec->dev, "Failed to enable FLL1 with Ref Clock Loop: %d\n", ret);
++ return ret;
++ }
++
++ ret = snd_soc_codec_set_pll(wm5102_codec, WM5102_FLL1,
++ ARIZONA_CLK_SRC_AIF2BCLK,
++ sr * 64, clk_freq);
++ if (ret != 0) {
++ dev_err(wm5102_codec->dev, "Failed to enable FLL1 Sync Clock Loop: %d\n", ret);
++ return ret;
++ }
++ priv->sync_path_enable = 1;
++ break;
++ case SND_SOC_DAPM_POST_PMD:
++ priv->sync_path_enable = 0;
++ break;
++ }
++
++ return ret;
++}
++
++static const struct snd_kcontrol_new rpi_wsp_controls[] = {
++ SOC_DAPM_PIN_SWITCH("DMIC"),
++ SOC_DAPM_PIN_SWITCH("Headset Mic"),
++ SOC_DAPM_PIN_SWITCH("SPDIF Out"),
++ SOC_DAPM_PIN_SWITCH("SPDIF In"),
++ SOC_DAPM_PIN_SWITCH("Line Input"),
++};
++
++const struct snd_soc_dapm_widget rpi_wsp_dapm_widgets[] = {
++ SND_SOC_DAPM_MIC("DMIC", NULL),
++ SND_SOC_DAPM_MIC("Headset Mic", NULL),
++ SND_SOC_DAPM_MIC("Line Input", NULL),
++ SND_SOC_DAPM_INPUT("dummy SPDIF in"),
++ SND_SOC_DAPM_PGA_E("dummy SPDIFRX", SND_SOC_NOPM, 0, 0,NULL, 0,
++ spdif_rx_enable_event,
++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
++};
++
++const struct snd_soc_dapm_route rpi_wsp_dapm_routes[] = {
++ { "IN1L", NULL, "Headset Mic" },
++ { "IN1R", NULL, "Headset Mic" },
++ { "Headset Mic", NULL, "MICBIAS1" },
++
++ { "IN2L", NULL, "DMIC" },
++ { "IN2R", NULL, "DMIC" },
++ { "DMIC", NULL, "MICBIAS2" },
++
++ { "IN3L", NULL, "Line Input" },
++ { "IN3R", NULL, "Line Input" },
++ { "Line Input", NULL, "MICBIAS3" },
++
++ /* Dummy routes to check whether SPDIF RX is enabled or not */
++ {"dummy SPDIFRX", NULL, "dummy SPDIF in"},
++ {"AIFTX", NULL, "dummy SPDIFRX"},
++};
++
++static int rpi_set_bias_level(struct snd_soc_card *card,
++ struct snd_soc_dapm_context *dapm,
++ enum snd_soc_bias_level level)
++{
++ struct snd_soc_codec *wm5102_codec = card->rtd[0].codec;
++ struct wm5102_machine_priv *priv = snd_soc_card_get_drvdata(card);
++
++ int ret;
++ int sr = priv->wm5102_sr;
++ int clk_freq = (sr % 4000 == 0) ? WM5102_MAX_SYSCLK_1 : WM5102_MAX_SYSCLK_2;
++
++ switch (level) {
++ case SND_SOC_BIAS_OFF:
++ break;
++ case SND_SOC_BIAS_ON:
++ if (!priv->sync_path_enable) {
++ ret = snd_soc_codec_set_pll(wm5102_codec, WM5102_FLL1,
++ ARIZONA_CLK_SRC_MCLK1,
++ WM8804_CLKOUT_HZ,
++ clk_freq);
++ if (ret != 0) {
++ dev_err(wm5102_codec->dev, "Failed to enable FLL1 with Ref Clock Loop: %d\n", ret);
++ return ret;
++ }
++ }
++ break;
++ default:
++ break;
++ }
++
++ dapm->bias_level = level;
++
++ return 0;
++}
++
++static int rpi_set_bias_level_post(struct snd_soc_card *card,
++ struct snd_soc_dapm_context *dapm,
++ enum snd_soc_bias_level level)
++{
++ struct snd_soc_codec *wm5102_codec = card->rtd[0].codec;
++
++ switch (level) {
++ case SND_SOC_BIAS_STANDBY:
++ snd_soc_codec_set_pll(wm5102_codec, WM5102_FLL1,
++ ARIZONA_FLL_SRC_NONE, 0, 0);
++ snd_soc_codec_set_pll(wm5102_codec, WM5102_FLL1_REFCLK,
++ ARIZONA_FLL_SRC_NONE, 0, 0);
++ break;
++ default:
++ break;
++ }
++
++ dapm->bias_level = level;
++
++ return 0;
++}
++
++static int snd_rpi_wsp_config_5102_clks(struct snd_soc_codec *wm5102_codec, int sr)
++{
++ int ret;
++ int clk_freq = (sr % 4000 == 0) ? WM5102_MAX_SYSCLK_1 : WM5102_MAX_SYSCLK_2;
++
++ /*reset FLL1*/
++ snd_soc_codec_set_pll(wm5102_codec, WM5102_FLL1,
++ ARIZONA_FLL_SRC_NONE, 0, 0);
++
++ ret = snd_soc_codec_set_pll(wm5102_codec, WM5102_FLL1,
++ ARIZONA_CLK_SRC_MCLK1,
++ WM8804_CLKOUT_HZ,
++ clk_freq);
++
++ if (ret != 0) {
++ dev_err(wm5102_codec->dev, "Failed to set FLL1: %d\n", ret);
++ return ret;
++ }
++
++ ret = snd_soc_codec_set_sysclk(wm5102_codec,
++ ARIZONA_CLK_SYSCLK,
++ ARIZONA_CLK_SRC_FLL1,
++ clk_freq,
++ SND_SOC_CLOCK_IN);
++ if (ret != 0) {
++ dev_err(wm5102_codec->dev, "Failed to set AYNCCLK: %d\n", ret);
++ return ret;
++ }
++
++ return 0;
++ }
++
++static int snd_rpi_wsp_config_8804_clks(struct snd_soc_codec *wm8804_codec,
++ struct snd_soc_dai *wm8804_dai, int sr)
++ {
++ int ret;
++
++ /*Set OSC(12MHz) to CLK2 freq*/
++ /*Based on MCLKDIV it will be 128fs (MCLKDIV=1) or 256fs mode (MCLKDIV=0)*/
++ /*BCLK will be MCLK/2 (MCLKDIV=1) or MCLK/4 (MCLKDIV=0) so BCLK is 64fs always*/
++ ret = snd_soc_dai_set_pll(wm8804_dai, 0, 0, WM8804_CLKOUT_HZ, sr * 256);
++ if (ret != 0) {
++ dev_err(wm8804_codec->dev, "Failed to set OSC to CLK2 frequency: %d\n", ret);
++ return ret;
++ }
++
++ /*Set MCLK as PLL Output*/
++ ret = snd_soc_dai_set_sysclk(wm8804_dai, WM8804_TX_CLKSRC_PLL, sr * 256, 0);
++ if (ret != 0) {
++ dev_err(wm8804_codec->dev, "Failed to set MCLK as PLL Output: %d\n", ret);
++ return ret;
++ }
++
++ /*Fix MCLKDIV=0 for 256fs to avoid any issues switching between TX and RX. RX always expects 256fs*/
++ ret = snd_soc_dai_set_clkdiv(wm8804_dai, WM8804_MCLK_DIV, 0 );
++ if (ret != 0) {
++ dev_err(wm8804_codec->dev, "Failed to set MCLK_DIV to 256fs: %d\n", ret);
++ return ret;
++ }
++
++ /*Set CLKOUT as OSC Frequency*/
++ ret = snd_soc_dai_set_sysclk(wm8804_dai, WM8804_CLKOUT_SRC_OSCCLK, WM8804_CLKOUT_HZ, 0);
++ if (ret != 0) {
++ dev_err(wm8804_codec->dev, "Failed to set CLKOUT as OSC Frequency: %d\n", ret);
++ return ret;
++ }
++
++ return 0;
++}
++
++static int snd_rpi_wsp_hw_params(struct snd_pcm_substream *substream,
++ struct snd_pcm_hw_params *params)
++{
++ struct snd_soc_pcm_runtime *rtd = substream->private_data;
++ struct snd_soc_card *card = rtd->card;
++ struct snd_soc_codec *wm5102_codec = rtd->codec;
++ struct snd_soc_dai *bcm_i2s_dai = rtd->cpu_dai;
++ struct snd_soc_codec *wm8804_codec = card->rtd[1].codec;
++ struct snd_soc_dai *wm8804_codec_dai = card->rtd[1].codec_dai;
++ struct wm5102_machine_priv *priv = snd_soc_card_get_drvdata(card);
++ int ret, capture_stream_opened,playback_stream_opened;
++ unsigned int bclkratio, tx_mask, rx_mask;
++ int width, num_slots=1;
++
++ bclkratio = 2 * snd_pcm_format_physical_width(params_format(params));
++
++ ret = snd_soc_dai_set_bclk_ratio(bcm_i2s_dai, bclkratio);
++ if (ret < 0) {
++ dev_err(wm5102_codec->dev, "set_bclk_ratio failed: %d\n", ret);
++ return ret;
++ }
++
++ /*8804 supports sample rates from 32k only*/
++ /*Setting <32k raises error from 8804 driver while setting the clock*/
++ if(params_rate(params) >= 32000)
++ {
++ ret = snd_rpi_wsp_config_8804_clks(wm8804_codec, wm8804_codec_dai,
++ params_rate(params));
++
++ if (ret != 0) {
++ dev_err(wm8804_codec->dev, "snd_rpi_wsp_config_8804_clks failed: %d\n",
++ ret);
++ return ret;
++ }
++ }
++
++ capture_stream_opened =
++ substream->pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream_opened;
++ playback_stream_opened =
++ substream->pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream_opened;
++
++ priv->wm5102_sr = params_rate(params);
++
++ ret = snd_rpi_wsp_config_5102_clks(wm5102_codec, params_rate(params));
++ if (ret != 0) {
++ dev_err(wm5102_codec->dev, "snd_rpi_wsp_config_5102_clks failed: %d\n", ret);
++ return ret;
++ }
++
++ width = snd_pcm_format_physical_width(params_format(params));
++
++ if (capture_stream_opened) {
++ tx_mask = 0;
++ rx_mask = 1;
++ }
++ if (playback_stream_opened) {
++ tx_mask = 1;
++ rx_mask = 0;
++ }
++ ret = snd_soc_dai_set_tdm_slot(rtd->codec_dai, tx_mask, rx_mask, num_slots, width);
++ if (ret < 0)
++ return ret;
++
++ priv->wm8804_sr = params_rate(params);
++
++ return 0;
++}
++
++static int dai_link2_params_fixup(struct snd_soc_dapm_widget *w, int event)
++{
++ struct snd_soc_card *card = &snd_rpi_wsp;
++ struct wm5102_machine_priv *priv = snd_soc_card_get_drvdata(card);
++ struct snd_soc_pcm_stream *config = w->params;
++
++ if (event == SND_SOC_DAPM_PRE_PMU) {
++ config->rate_min = priv->wm8804_sr;
++ config->rate_max = priv->wm8804_sr;
++ } else if (event == SND_SOC_DAPM_PRE_PMD) {
++ config->rate_min = RPI_WLF_SR;
++ config->rate_max = RPI_WLF_SR;
++ }
++
++ return 0;
++}
++
++static int snd_rpi_wsp_hw_free(struct snd_pcm_substream *substream)
++{
++ struct snd_soc_pcm_runtime *rtd = substream->private_data;
++ struct snd_soc_codec *wm5102_codec = rtd->codec;
++ int ret,playback_stream_opened,capture_stream_opened;
++
++ playback_stream_opened = substream->pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream_opened;
++
++ capture_stream_opened = substream->pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream_opened;
++
++ if((playback_stream_opened + capture_stream_opened) == 1){
++
++ ret = snd_soc_codec_set_sysclk(wm5102_codec,
++ ARIZONA_CLK_SYSCLK,
++ ARIZONA_CLK_SRC_FLL1,
++ 0,
++ SND_SOC_CLOCK_IN);
++
++ if (ret != 0) {
++ dev_err(wm5102_codec->dev, "Failed to set SYSCLK to Zero: %d\n", ret);
++ return ret;
++ }
++ }
++
++ return 0;
++}
++
++static struct snd_soc_ops snd_rpi_wsp_ops = {
++ .hw_params = snd_rpi_wsp_hw_params,
++ .hw_free = snd_rpi_wsp_hw_free,
++};
++
++static struct snd_soc_pcm_stream dai_link2_params = {
++ .formats = SNDRV_PCM_FMTBIT_S24_LE,
++ .rate_min = RPI_WLF_SR,
++ .rate_max = RPI_WLF_SR,
++ .channels_min = 2,
++ .channels_max = 2,
++};
++
++static struct snd_soc_dai_link snd_rpi_wsp_dai[] = {
++ {
++ .name = "WM5102",
++ .stream_name = "WM5102 AiFi",
++ .cpu_dai_name = "bcm2708-i2s.0",
++ .codec_dai_name = "wm5102-aif1",
++ .platform_name = "bcm2708-i2s.0",
++ .codec_name = "wm5102-codec",
++ .dai_fmt = SND_SOC_DAIFMT_I2S
++ | SND_SOC_DAIFMT_NB_NF
++ | SND_SOC_DAIFMT_CBM_CFM,
++ .ops = &snd_rpi_wsp_ops,
++ },
++ {
++ .name = "WM5102 SPDIF",
++ .stream_name = "SPDIF Tx/Rx",
++ .cpu_dai_name = "wm5102-aif2",
++ .codec_dai_name = "wm8804-spdif",
++ .codec_name = "wm8804.1-003a",
++ .dai_fmt = SND_SOC_DAIFMT_I2S
++ | SND_SOC_DAIFMT_NB_NF
++ | SND_SOC_DAIFMT_CBM_CFM,
++ .ignore_suspend = 1,
++ .params = &dai_link2_params,
++ .params_fixup = dai_link2_params_fixup,
++ },
++};
++
++static int snd_rpi_wsp_late_probe(struct snd_soc_card *card)
++{
++ struct wm5102_machine_priv *priv = snd_soc_card_get_drvdata(card);
++ int ret;
++
++ priv->wm8804_sr = RPI_WLF_SR;
++ priv->wm5102_sr = RPI_WLF_SR;
++ priv->sync_path_enable = 0;
++
++ ret = snd_soc_codec_set_sysclk(card->rtd[0].codec, ARIZONA_CLK_SYSCLK, ARIZONA_CLK_SRC_FLL1,
++ 0, SND_SOC_CLOCK_IN);
++ if (ret != 0) {
++ dev_err(card->rtd[0].codec->dev, "Failed to set SYSCLK to Zero: %d\n", ret);
++ return ret;
++ }
++
++ ret = snd_rpi_wsp_config_8804_clks(card->rtd[1].codec, card->rtd[1].codec_dai, RPI_WLF_SR);
++
++ if (ret != 0) {
++ dev_err(card->rtd[1].codec->dev, "snd_rpi_wsp_config_8804_clks failed: %d\n", ret);
++ return ret;
++ }
++
++ ret = snd_soc_dai_set_sysclk(card->rtd[0].codec_dai, ARIZONA_CLK_SYSCLK, 0, 0);
++ if (ret != 0) {
++ dev_err(card->rtd[0].codec->dev, "Failed to set codec dai clk domain: %d\n", ret);
++ return ret;
++ }
++
++ ret = snd_soc_dai_set_sysclk(card->rtd[1].cpu_dai, ARIZONA_CLK_SYSCLK, 0, 0);
++ if (ret != 0) {
++ dev_err(card->rtd[0].codec->dev, "Failed to set codec dai clk domain: %d\n", ret);
++ return ret;
++ }
++
++ return 0;
++}
++
++/* audio machine driver */
++static struct snd_soc_card snd_rpi_wsp = {
++ .name = "snd_rpi_wsp",
++ .dai_link = snd_rpi_wsp_dai,
++ .num_links = ARRAY_SIZE(snd_rpi_wsp_dai),
++ .late_probe = snd_rpi_wsp_late_probe,
++ .controls = rpi_wsp_controls,
++ .num_controls = ARRAY_SIZE(rpi_wsp_controls),
++ .dapm_widgets = rpi_wsp_dapm_widgets,
++ .num_dapm_widgets = ARRAY_SIZE(rpi_wsp_dapm_widgets),
++ .dapm_routes = rpi_wsp_dapm_routes,
++ .num_dapm_routes = ARRAY_SIZE(rpi_wsp_dapm_routes),
++ .set_bias_level = rpi_set_bias_level,
++ .set_bias_level_post = rpi_set_bias_level_post,
++};
++
++static int snd_rpi_wsp_probe(struct platform_device *pdev)
++{
++ int ret = 0;
++ struct wm5102_machine_priv *wm5102;
++
++ wm5102 = kzalloc(sizeof *wm5102, GFP_KERNEL);
++ if (!wm5102)
++ return -ENOMEM;
++
++ snd_soc_card_set_drvdata(&snd_rpi_wsp, wm5102);
++
++ if (pdev->dev.of_node) {
++ struct device_node *i2s_node;
++ struct snd_soc_dai_link *dai = &snd_rpi_wsp_dai[0];
++ i2s_node = of_parse_phandle(pdev->dev.of_node,
++ "i2s-controller", 0);
++
++ if (i2s_node) {
++ dai->cpu_dai_name = NULL;
++ dai->cpu_of_node = i2s_node;
++ dai->platform_name = NULL;
++ dai->platform_of_node = i2s_node;
++ }
++ }
++
++ snd_rpi_wsp.dev = &pdev->dev;
++ ret = snd_soc_register_card(&snd_rpi_wsp);
++ if (ret) {
++ dev_err(&pdev->dev, "Failed to register card: %d\n", ret);
++ kfree(wm5102);
++ }
++
++ return ret;
++}
++
++static int snd_rpi_wsp_remove(struct platform_device *pdev)
++{
++ struct snd_soc_card *card = &snd_rpi_wsp;
++ struct wm5102_machine_priv *wm5102 = snd_soc_card_get_drvdata(card);
++
++ snd_soc_unregister_card(&snd_rpi_wsp);
++ kfree(wm5102);
++
++ return 0;
++}
++
++#ifdef CONFIG_OF
++static const struct of_device_id snd_rpi_wsp_of_match[] = {
++ { .compatible = "wlf,rpi-wm5102", },
++ {},
++};
++MODULE_DEVICE_TABLE(of, snd_rpi_wsp_of_match);
++#endif /* CONFIG_OF */
++
++static struct platform_driver snd_rpi_wsp_driver = {
++ .driver = {
++ .name = "snd-rpi-wsp",
++ .owner = THIS_MODULE,
++ .of_match_table = of_match_ptr(snd_rpi_wsp_of_match),
++ },
++ .probe = snd_rpi_wsp_probe,
++ .remove = snd_rpi_wsp_remove,
++};
++
++module_platform_driver(snd_rpi_wsp_driver);
++
++MODULE_SOFTDEP("pre: snd_soc_wm8804_i2c");
++MODULE_AUTHOR("Nikesh Oswal");
++MODULE_AUTHOR("Liu Xin");
++MODULE_DESCRIPTION("ASoC Driver for Raspberry Pi connected to Cirrus sound pi");
++MODULE_LICENSE("GPL");
+diff --git a/sound/soc/codecs/arizona.c b/sound/soc/codecs/arizona.c
+index ee91edc..6f19255 100644
+--- a/sound/soc/codecs/arizona.c
++++ b/sound/soc/codecs/arizona.c
+@@ -1266,7 +1266,7 @@ static int arizona_hw_params_rate(struct snd_pcm_substream *substream,
+ struct arizona_priv *priv = snd_soc_codec_get_drvdata(codec);
+ struct arizona_dai_priv *dai_priv = &priv->dai[dai->id - 1];
+ int base = dai->driver->base;
+- int i, sr_val;
++ int i, sr_val, ret;
+
+ /*
+ * We will need to be more flexible than this in future,
+@@ -1282,6 +1282,26 @@ static int arizona_hw_params_rate(struct snd_pcm_substream *substream,
+ }
+ sr_val = i;
+
++ switch (priv->arizona->type) {
++ case WM5102:
++ case WM8997:
++ if (arizona_sr_vals[sr_val] >= 88200)
++ ret = arizona_dvfs_up(priv->arizona,
++ ARIZONA_DVFS_SR1_RQ);
++ else
++ ret = arizona_dvfs_down(priv->arizona,
++ ARIZONA_DVFS_SR1_RQ);
++
++ if (ret != 0) {
++ arizona_aif_err(dai, "Failed to change DVFS %d\n", ret);
++ return ret;
++ }
++ break;
++
++ default:
++ break;
++ }
++
+ switch (dai_priv->clk) {
+ case ARIZONA_CLK_SYSCLK:
+ switch (priv->arizona->type) {
+diff --git a/sound/soc/codecs/wm5102.c b/sound/soc/codecs/wm5102.c
+index d476221..fc50c6d 100644
+--- a/sound/soc/codecs/wm5102.c
++++ b/sound/soc/codecs/wm5102.c
+@@ -613,6 +613,49 @@ static int wm5102_sysclk_ev(struct snd_soc_dapm_widget *w,
+ return 0;
+ }
+
++static int wm5102_adsp_power_ev(struct snd_soc_dapm_widget *w,
++ struct snd_kcontrol *kcontrol, int event)
++{
++ struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm);
++ struct arizona *arizona = dev_get_drvdata(codec->dev->parent);
++ unsigned int v;
++ int ret;
++
++ switch (event) {
++ case SND_SOC_DAPM_PRE_PMU:
++ ret = regmap_read(arizona->regmap, ARIZONA_SYSTEM_CLOCK_1, &v);
++ if (ret != 0) {
++ dev_err(codec->dev,
++ "Failed to read SYSCLK state: %d\n", ret);
++ return -EIO;
++ }
++
++ v = (v & ARIZONA_SYSCLK_FREQ_MASK) >> ARIZONA_SYSCLK_FREQ_SHIFT;
++
++ if (v >= 3) {
++ ret = arizona_dvfs_up(arizona, ARIZONA_DVFS_ADSP1_RQ);
++ if (ret != 0) {
++ dev_err(codec->dev,
++ "Failed to raise DVFS: %d\n", ret);
++ return ret;
++ }
++ }
++ break;
++
++ case SND_SOC_DAPM_POST_PMD:
++ ret = arizona_dvfs_down(arizona, ARIZONA_DVFS_ADSP1_RQ);
++ if (ret != 0)
++ dev_warn(codec->dev,
++ "Failed to lower DVFS: %d\n", ret);
++ break;
++
++ default:
++ break;
++ }
++
++ return wm_adsp2_early_event(w, kcontrol, event);
++}
++
+ static int wm5102_out_comp_coeff_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+ {
+@@ -1367,7 +1410,7 @@ ARIZONA_MUX_WIDGETS(ISRC2DEC2, "ISRC2DEC2"),
+ ARIZONA_MUX_WIDGETS(ISRC2INT1, "ISRC2INT1"),
+ ARIZONA_MUX_WIDGETS(ISRC2INT2, "ISRC2INT2"),
+
+-WM_ADSP2("DSP1", 0),
++WM_ADSP2_E("DSP1", 0, wm5102_adsp_power_ev),
+
+ SND_SOC_DAPM_OUTPUT("HPOUT1L"),
+ SND_SOC_DAPM_OUTPUT("HPOUT1R"),
+@@ -1918,7 +1961,7 @@ static int wm5102_probe(struct platform_device *pdev)
+ wm5102->core.adsp[0].mem = wm5102_dsp1_regions;
+ wm5102->core.adsp[0].num_mems = ARRAY_SIZE(wm5102_dsp1_regions);
+
+- ret = wm_adsp2_init(&wm5102->core.adsp[0], true);
++ ret = wm_adsp2_init(&wm5102->core.adsp[0]);
+ if (ret != 0)
+ return ret;
+
+diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c
+index d01c209..bbbc5c2 100644
+--- a/sound/soc/codecs/wm_adsp.c
++++ b/sound/soc/codecs/wm_adsp.c
+@@ -1531,35 +1531,6 @@ static void wm_adsp2_boot_work(struct work_struct *work)
+ return;
+ }
+
+- if (dsp->dvfs) {
+- ret = regmap_read(dsp->regmap,
+- dsp->base + ADSP2_CLOCKING, &val);
+- if (ret != 0) {
+- adsp_err(dsp, "Failed to read clocking: %d\n", ret);
+- return;
+- }
+-
+- if ((val & ADSP2_CLK_SEL_MASK) >= 3) {
+- ret = regulator_enable(dsp->dvfs);
+- if (ret != 0) {
+- adsp_err(dsp,
+- "Failed to enable supply: %d\n",
+- ret);
+- return;
+- }
+-
+- ret = regulator_set_voltage(dsp->dvfs,
+- 1800000,
+- 1800000);
+- if (ret != 0) {
+- adsp_err(dsp,
+- "Failed to raise supply: %d\n",
+- ret);
+- return;
+- }
+- }
+- }
+-
+ ret = wm_adsp2_ena(dsp);
+ if (ret != 0)
+ return;
+@@ -1653,21 +1624,6 @@ int wm_adsp2_event(struct snd_soc_dapm_widget *w,
+ regmap_write(dsp->regmap, dsp->base + ADSP2_WDMA_CONFIG_2, 0);
+ regmap_write(dsp->regmap, dsp->base + ADSP2_RDMA_CONFIG_1, 0);
+
+- if (dsp->dvfs) {
+- ret = regulator_set_voltage(dsp->dvfs, 1200000,
+- 1800000);
+- if (ret != 0)
+- adsp_warn(dsp,
+- "Failed to lower supply: %d\n",
+- ret);
+-
+- ret = regulator_disable(dsp->dvfs);
+- if (ret != 0)
+- adsp_err(dsp,
+- "Failed to enable supply: %d\n",
+- ret);
+- }
+-
+ list_for_each_entry(ctl, &dsp->ctl_list, list)
+ ctl->enabled = 0;
+
+@@ -1694,7 +1650,7 @@ err:
+ }
+ EXPORT_SYMBOL_GPL(wm_adsp2_event);
+
+-int wm_adsp2_init(struct wm_adsp *adsp, bool dvfs)
++int wm_adsp2_init(struct wm_adsp *adsp)
+ {
+ int ret;
+
+@@ -1713,33 +1669,6 @@ int wm_adsp2_init(struct wm_adsp *adsp, bool dvfs)
+ INIT_LIST_HEAD(&adsp->ctl_list);
+ INIT_WORK(&adsp->boot_work, wm_adsp2_boot_work);
+
+- if (dvfs) {
+- adsp->dvfs = devm_regulator_get(adsp->dev, "DCVDD");
+- if (IS_ERR(adsp->dvfs)) {
+- ret = PTR_ERR(adsp->dvfs);
+- adsp_err(adsp, "Failed to get DCVDD: %d\n", ret);
+- return ret;
+- }
+-
+- ret = regulator_enable(adsp->dvfs);
+- if (ret != 0) {
+- adsp_err(adsp, "Failed to enable DCVDD: %d\n", ret);
+- return ret;
+- }
+-
+- ret = regulator_set_voltage(adsp->dvfs, 1200000, 1800000);
+- if (ret != 0) {
+- adsp_err(adsp, "Failed to initialise DVFS: %d\n", ret);
+- return ret;
+- }
+-
+- ret = regulator_disable(adsp->dvfs);
+- if (ret != 0) {
+- adsp_err(adsp, "Failed to disable DCVDD: %d\n", ret);
+- return ret;
+- }
+- }
+-
+ return 0;
+ }
+ EXPORT_SYMBOL_GPL(wm_adsp2_init);
+diff --git a/sound/soc/codecs/wm_adsp.h b/sound/soc/codecs/wm_adsp.h
+index a4f6b64..a86e531 100644
+--- a/sound/soc/codecs/wm_adsp.h
++++ b/sound/soc/codecs/wm_adsp.h
+@@ -56,8 +56,6 @@ struct wm_adsp {
+ int fw;
+ bool running;
+
+- struct regulator *dvfs;
+-
+ struct list_head ctl_list;
+
+ struct work_struct boot_work;
+@@ -67,19 +65,22 @@ struct wm_adsp {
+ SND_SOC_DAPM_PGA_E(wname, SND_SOC_NOPM, num, 0, NULL, 0, \
+ wm_adsp1_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD)
+
+-#define WM_ADSP2(wname, num) \
++#define WM_ADSP2_E(wname, num, event_fn) \
+ { .id = snd_soc_dapm_dai_link, .name = wname " Preloader", \
+- .reg = SND_SOC_NOPM, .shift = num, .event = wm_adsp2_early_event, \
+- .event_flags = SND_SOC_DAPM_PRE_PMU }, \
++ .reg = SND_SOC_NOPM, .shift = num, .event = event_fn, \
++ .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD }, \
+ { .id = snd_soc_dapm_out_drv, .name = wname, \
+ .reg = SND_SOC_NOPM, .shift = num, .event = wm_adsp2_event, \
+ .event_flags = SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD }
+
++#define WM_ADSP2(wname, num) \
++ WM_ADSP2_E(wname, num, wm_adsp2_early_event)
++
+ extern const struct snd_kcontrol_new wm_adsp1_fw_controls[];
+ extern const struct snd_kcontrol_new wm_adsp2_fw_controls[];
+
+ int wm_adsp1_init(struct wm_adsp *adsp);
+-int wm_adsp2_init(struct wm_adsp *adsp, bool dvfs);
++int wm_adsp2_init(struct wm_adsp *adsp);
+ int wm_adsp1_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event);
+ int wm_adsp2_early_event(struct snd_soc_dapm_widget *w,
+diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c
+index 2373252..ee92ec7 100644
+--- a/sound/soc/soc-core.c
++++ b/sound/soc/soc-core.c
+@@ -1299,7 +1299,7 @@ static int soc_link_dai_widgets(struct snd_soc_card *card,
+ if (play_w && capture_w) {
+ ret = snd_soc_dapm_new_pcm(card, dai_link->params,
+ dai_link->num_params, capture_w,
+- play_w);
++ play_w, dai_link);
+ if (ret != 0) {
+ dev_err(card->dev, "ASoC: Can't link %s to %s: %d\n",
+ play_w->name, capture_w->name, ret);
+@@ -1312,7 +1312,7 @@ static int soc_link_dai_widgets(struct snd_soc_card *card,
+ if (play_w && capture_w) {
+ ret = snd_soc_dapm_new_pcm(card, dai_link->params,
+ dai_link->num_params, capture_w,
+- play_w);
++ play_w, dai_link);
+ if (ret != 0) {
+ dev_err(card->dev, "ASoC: Can't link %s to %s: %d\n",
+ play_w->name, capture_w->name, ret);
+diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c
+index b6c12dc..e767365 100644
+--- a/sound/soc/soc-dapm.c
++++ b/sound/soc/soc-dapm.c
+@@ -3227,11 +3227,12 @@ static int snd_soc_dai_link_event(struct snd_soc_dapm_widget *w,
+ {
+ struct snd_soc_dapm_path *source_p, *sink_p;
+ struct snd_soc_dai *source, *sink;
+- const struct snd_soc_pcm_stream *config = w->params + w->params_select;
++ struct snd_soc_pcm_stream *config = w->params + w->params_select;
++ struct snd_soc_dai_link *dai_link = w->priv;
+ struct snd_pcm_substream substream;
+ struct snd_pcm_hw_params *params = NULL;
+ u64 fmt;
+- int ret;
++ int ret = 0;
+
+ if (WARN_ON(!config) ||
+ WARN_ON(list_empty(&w->sources) || list_empty(&w->sinks)))
+@@ -3251,6 +3252,16 @@ static int snd_soc_dai_link_event(struct snd_soc_dapm_widget *w,
+ source = source_p->source->priv;
+ sink = sink_p->sink->priv;
+
++ if (dai_link && dai_link->params_fixup) {
++ ret = dai_link->params_fixup(w, event);
++ if (ret < 0) {
++ dev_err(w->dapm->dev,
++ "ASoC: params_fixup for dai link widget failed %d\n",
++ ret);
++ goto out;
++ }
++ }
++
+ /* Be a little careful as we don't want to overflow the mask array */
+ if (config->formats) {
+ fmt = ffs(config->formats) - 1;
+@@ -3350,10 +3361,11 @@ static int snd_soc_dapm_dai_link_put(struct snd_kcontrol *kcontrol,
+ }
+
+ int snd_soc_dapm_new_pcm(struct snd_soc_card *card,
+- const struct snd_soc_pcm_stream *params,
++ struct snd_soc_pcm_stream *params,
+ unsigned int num_params,
+ struct snd_soc_dapm_widget *source,
+- struct snd_soc_dapm_widget *sink)
++ struct snd_soc_dapm_widget *sink,
++ void *priv)
+ {
+ struct snd_soc_dapm_widget template;
+ struct snd_soc_dapm_widget *w;
+@@ -3455,6 +3467,7 @@ int snd_soc_dapm_new_pcm(struct snd_soc_card *card,
+
+ w->params = params;
+ w->num_params = num_params;
++ w->priv = priv;
+
+ ret = snd_soc_dapm_add_path(&card->dapm, source, w, NULL, NULL);
+ if (ret)
+