diff --git a/Documentation/devicetree/bindings/mfd/arizona.txt b/Documentation/devicetree/bindings/mfd/arizona.txt index 18be0cb..4b3510a 100644 --- a/Documentation/devicetree/bindings/mfd/arizona.txt +++ b/Documentation/devicetree/bindings/mfd/arizona.txt @@ -44,6 +44,23 @@ Required properties: Optional properties: - wlf,reset : GPIO specifier for the GPIO controlling /RESET + - 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 . If @@ -51,6 +68,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. @@ -87,6 +108,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 @@ -94,4 +128,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 3d8de47..156c29d 100644 --- a/arch/arm/boot/dts/overlays/Makefile +++ b/arch/arm/boot/dts/overlays/Makefile @@ -61,6 +61,7 @@ dtbo-$(RPI_DT_OVERLAYS) += pwm-2chan.dtbo dtbo-$(RPI_DT_OVERLAYS) += qca7000.dtbo dtbo-$(RPI_DT_OVERLAYS) += raspidac3.dtbo dtbo-$(RPI_DT_OVERLAYS) += rpi-backlight.dtbo +dtbo-$(RPI_DT_OVERLAYS) += rpi-cirrus-wm5102.dtbo dtbo-$(RPI_DT_OVERLAYS) += rpi-dac.dtbo dtbo-$(RPI_DT_OVERLAYS) += rpi-display.dtbo dtbo-$(RPI_DT_OVERLAYS) += rpi-ft5406.dtbo 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..3cb63a5 --- /dev/null +++ b/arch/arm/boot/dts/overlays/rpi-cirrus-wm5102-overlay.dts @@ -0,0 +1,138 @@ +// 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 8>; + brcm,function = <1 1 0 1>; + }; + }; + }; + + 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; + }; + }; + }; + + 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@3b { + #sound-dai-cells = <0>; + compatible = "wlf,wm8804"; + reg = <0x3b>; + status = "okay"; + PVDD-supply = <&ldo0>; + DVDD-supply = <&ldo0>; + wlf,reset-gpio = <&gpio 8 0>; + }; + }; + }; +}; diff --git a/arch/arm/configs/bcm2709_defconfig b/arch/arm/configs/bcm2709_defconfig index 0bdc631..4de55c8 100644 --- a/arch/arm/configs/bcm2709_defconfig +++ b/arch/arm/configs/bcm2709_defconfig @@ -650,6 +650,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 @@ -863,6 +866,7 @@ CONFIG_SND_BCM2708_SOC_BOOMBERRY_DIGI=m CONFIG_SND_BCM2708_SOC_IQAUDIO_DAC=m CONFIG_SND_BCM2708_SOC_RASPIDAC3=m CONFIG_SND_BCM2708_SOC_ADAU1977_ADC=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 57393b5..55ef0a5 100644 --- a/arch/arm/configs/bcmrpi_defconfig +++ b/arch/arm/configs/bcmrpi_defconfig @@ -642,6 +642,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 @@ -855,6 +858,7 @@ CONFIG_SND_BCM2708_SOC_BOOMBERRY_DIGI=m CONFIG_SND_BCM2708_SOC_IQAUDIO_DAC=m CONFIG_SND_BCM2708_SOC_RASPIDAC3=m CONFIG_SND_BCM2708_SOC_ADAU1977_ADC=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/drivers/dma/bcm2835-dma.c b/drivers/dma/bcm2835-dma.c index d26b6bd..dad4f48 100644 --- a/drivers/dma/bcm2835-dma.c +++ b/drivers/dma/bcm2835-dma.c @@ -144,12 +144,6 @@ struct bcm2835_desc { */ #define MAX_LITE_TRANSFER (SZ_64K - 4) -/* - * Transfers larger than 32k cause issues with the bcm2708-i2s driver, - * so limit transfer size to 32k as bcm2708-dmaengine did. - */ -#define MAX_CYCLIC_LITE_TRANSFER SZ_32K - static inline struct bcm2835_dmadev *to_bcm2835_dma_dev(struct dma_device *d) { return container_of(d, struct bcm2835_dmadev, ddev); @@ -385,6 +379,15 @@ static struct dma_async_tx_descriptor *bcm2835_dma_prep_dma_cyclic( unsigned int frame, max_size; int i; + if (!buf_len || !period_len) + return NULL; + + if (buf_len % period_len) { + dev_err(chan->device->dev, + "Buffer length should be a multiple of period\n"); + return NULL; + } + /* Grab configuration */ if (!is_slave_direction(direction)) { dev_err(chan->device->dev, "%s: bad direction?\n", __func__); @@ -410,6 +413,18 @@ static struct dma_async_tx_descriptor *bcm2835_dma_prep_dma_cyclic( return NULL; } + if (c->ch >= 8) /* LITE channel */ + max_size = MAX_LITE_TRANSFER; + else + max_size = MAX_NORMAL_TRANSFER; + + if (period_len > max_size) { + dev_err(chan->device->dev, + "Period length %d larger than maximum %d\n", + period_len, max_size); + return NULL; + } + /* Now allocate and setup the descriptor. */ d = kzalloc(sizeof(*d), GFP_NOWAIT); if (!d) @@ -417,12 +432,8 @@ static struct dma_async_tx_descriptor *bcm2835_dma_prep_dma_cyclic( d->c = c; d->dir = direction; - if (c->ch >= 8) /* LITE channel */ - max_size = MAX_CYCLIC_LITE_TRANSFER; - else - max_size = MAX_NORMAL_TRANSFER; - period_len = min(period_len, max_size); - d->frames = (buf_len - 1) / (period_len + 1); + d->frames = buf_len / period_len; + d->size = buf_len; d->cb_list = kcalloc(d->frames, sizeof(*d->cb_list), GFP_KERNEL); if (!d->cb_list) { @@ -470,12 +481,7 @@ static struct dma_async_tx_descriptor *bcm2835_dma_prep_dma_cyclic( BCM2835_DMA_PER_MAP(c->dreq); /* Length of a frame */ - if (frame != d->frames - 1) - control_block->length = period_len; - else - control_block->length = buf_len - (d->frames - 1) * - period_len; - d->size += control_block->length; + control_block->length = period_len; /* * Next block is the next frame. diff --git a/drivers/mfd/arizona-core.c b/drivers/mfd/arizona-core.c index d474732..a899b57 100644 --- a/drivers/mfd/arizona-core.c +++ b/drivers/mfd/arizona-core.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -460,6 +461,20 @@ static int wm5102_clear_write_sequencer(struct arizona *arizona) return 0; } +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_isolate_dcvdd(struct arizona *arizona) { @@ -800,6 +815,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; @@ -833,6 +996,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(pdata->inmode)) @@ -852,6 +1024,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; } @@ -1029,6 +1204,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 = devm_gpio_request_one(arizona->dev, arizona->pdata.reset, @@ -1036,16 +1219,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); @@ -1420,6 +1606,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: @@ -1433,6 +1621,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 f7c88ff..19fdcd4 100644 --- a/drivers/regulator/arizona-ldo1.c +++ b/drivers/regulator/arizona-ldo1.c @@ -288,6 +288,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 79e607e..39a23eb 100644 --- a/include/linux/mfd/arizona/core.h +++ b/include/linux/mfd/arizona/core.h @@ -119,6 +119,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; bool has_fully_powered_off; struct arizona_pdata pdata; diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index 95a937e..8262719 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -383,10 +383,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); @@ -553,7 +554,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 fb955e6..bbdc05d 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -989,7 +989,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 c05e656..0fb2384 100644 --- a/sound/soc/bcm/Kconfig +++ b/sound/soc/bcm/Kconfig @@ -85,3 +85,12 @@ config SND_BCM2708_SOC_ADAU1977_ADC select SND_SOC_ADAU1977_I2C help Say Y or M if you want to add support for ADAU1977 ADC. + +config SND_BCM2708_SOC_RPI_CODEC_WSP + tristate "Support for Cirrus sound pi" + depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S + select SND_SOC_WM5102 + select SND_SOC_WM8804 + 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 7de2ef1..4d6c910 100644 --- a/sound/soc/bcm/Makefile +++ b/sound/soc/bcm/Makefile @@ -15,6 +15,7 @@ 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 obj-$(CONFIG_SND_BCM2708_SOC_ADAU1977_ADC) += snd-soc-adau1977-adc.o obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_DAC) += snd-soc-hifiberry-dac.o @@ -27,3 +28,4 @@ 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 diff --git a/sound/soc/bcm/bcm2835-i2s.c b/sound/soc/bcm/bcm2835-i2s.c index aedb01f..c413973 100644 --- a/sound/soc/bcm/bcm2835-i2s.c +++ b/sound/soc/bcm/bcm2835-i2s.c @@ -813,16 +813,16 @@ static struct snd_pcm_hardware bcm2835_pcm_hardware = { SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, .period_bytes_min = 32, - .period_bytes_max = 64 * PAGE_SIZE, + .period_bytes_max = SZ_64K - 4, .periods_min = 2, .periods_max = 255, - .buffer_bytes_max = 128 * PAGE_SIZE, + .buffer_bytes_max = SZ_512K, }; static const struct snd_dmaengine_pcm_config bcm2835_dmaengine_pcm_config = { .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, .pcm_hardware = &bcm2835_pcm_hardware, - .prealloc_buffer_size = 256 * PAGE_SIZE, + .prealloc_buffer_size = SZ_1M, }; static int bcm2835_i2s_probe(struct platform_device *pdev) 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..3abfff1 --- /dev/null +++ b/sound/soc/bcm/rpi-cirrus-sound-pi.c @@ -0,0 +1,638 @@ +/* + * 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, + * 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 +#include +#include +#include + +#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*/ + +#define DAI_WM5102 0 +#define DAI_WM8804 1 + +static struct snd_soc_card snd_rpi_wsp; + +struct wm5102_machine_priv { + int wm8804_sr; + int wm5102_sr; + int sync_path_enable; + int fll1_freq; /* negative means RefClock in spdif rx case */ + /* mutex for synchronzing FLL1 access with DAPM */ + struct mutex fll1_mutex; +}; + +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_pcm_runtime *wm5102_rtd; + struct snd_soc_codec *wm5102_codec; + int ret = 0; + int clk_freq; + int sr = priv->wm8804_sr; + + dev_dbg(card->dev, "spdif_rx event %d\n", event); + + wm5102_rtd = snd_soc_get_pcm_runtime(card, card->dai_link[DAI_WM5102].name); + if (!wm5102_rtd) { + dev_warn(card->dev, "spdif_rx_enable_event: couldn't get WM5102 rtd\n"); + return -EFAULT; + } + wm5102_codec = wm5102_rtd->codec; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + mutex_lock(&priv->fll1_mutex); + + dev_dbg(wm5102_codec->dev, + "spdif_rx: changing FLL1 to use Ref Clock\n"); + + /* 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); + mutex_unlock(&priv->fll1_mutex); + return ret; + } + + ret = snd_soc_codec_set_pll(wm5102_codec, WM5102_FLL1, + ARIZONA_CLK_SRC_AIF2BCLK, + sr * 64, clk_freq); + if (!ret) + /* set to negative to indicate we're doing spdif rx */ + priv->fll1_freq = -clk_freq; + + mutex_unlock(&priv->fll1_mutex); + + 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_pcm_runtime *wm5102_rtd; + struct snd_soc_codec *wm5102_codec; + struct snd_soc_dai *wm5102_codec_dai; + 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; + + wm5102_rtd = snd_soc_get_pcm_runtime(card, card->dai_link[DAI_WM5102].name); + if (!wm5102_rtd) { + dev_warn(card->dev, "rpi_set_bias_level: couldn't get WM5102 rtd\n"); + return -EFAULT; + } + wm5102_codec = wm5102_rtd->codec; + wm5102_codec_dai = wm5102_rtd->codec_dai; + + if (dapm->dev != wm5102_codec_dai->dev) + return 0; + + dev_dbg(wm5102_codec->dev, "change bias level from %d to %d, sync=%d\n", + dapm->bias_level, level, priv->sync_path_enable); + + switch (level) { + case SND_SOC_BIAS_ON: + /* no need to check current level, it can only be PREPARE */ + if (!priv->sync_path_enable) { + mutex_lock(&priv->fll1_mutex); + + dev_dbg(wm5102_codec->dev, + "bias_on: changing FLL1 from %d to %d\n", + priv->fll1_freq, clk_freq); + + ret = snd_soc_codec_set_pll(wm5102_codec, WM5102_FLL1, + ARIZONA_CLK_SRC_MCLK1, + WM8804_CLKOUT_HZ, + clk_freq); + if (!ret) + priv->fll1_freq = clk_freq; + + mutex_unlock(&priv->fll1_mutex); + + if (ret != 0) { + dev_err(wm5102_codec->dev, "Failed to enable FLL1: %d\n", ret); + return ret; + } + } + break; + case SND_SOC_BIAS_STANDBY: + if (dapm->bias_level != SND_SOC_BIAS_PREPARE) + break; + + mutex_lock(&priv->fll1_mutex); + + dev_dbg(wm5102_codec->dev, + "bias_standby: changing FLL1 from %d to off\n", + priv->fll1_freq); + + ret = snd_soc_codec_set_pll(wm5102_codec, WM5102_FLL1, + ARIZONA_FLL_SRC_NONE, 0, 0); + if (ret) + dev_warn(wm5102_codec->dev, "set_bias_level: Failed to stop FLL1: %d\n", ret); + + ret = snd_soc_codec_set_pll(wm5102_codec, WM5102_FLL1_REFCLK, + ARIZONA_FLL_SRC_NONE, 0, 0); + if (ret) + dev_warn(wm5102_codec->dev, "set_bias_level: Failed to stop FLL1_REFCLK: %d\n", ret); + + priv->fll1_freq = 0; + + mutex_unlock(&priv->fll1_mutex); + + break; + default: + break; + } + + return 0; +} + +static int snd_rpi_wsp_config_5102_clks( + struct wm5102_machine_priv *priv, + struct snd_soc_codec *wm5102_codec, int sr) +{ + int ret; + int clk_freq = (sr % 4000 == 0) ? WM5102_MAX_SYSCLK_1 : WM5102_MAX_SYSCLK_2; + + /* + * Manually set up FLL1 if it's configured to another rate but only + * if we are not using spdif rx (fll1_freq negative). + * This is necessary if delayed DAPM powerdown hasn't stopped + * the FLL before. + */ + if ((priv->fll1_freq > 0) && (priv->fll1_freq != clk_freq)) { + mutex_lock(&priv->fll1_mutex); + + dev_dbg(wm5102_codec->dev, + "config_5102_clks: changing FLL1 from %d to %d\n", + priv->fll1_freq, clk_freq); + + /*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) + priv->fll1_freq = clk_freq; + + mutex_unlock(&priv->fll1_mutex); + + if (ret) { + 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_pcm_runtime *wm5102_rtd, *wm8804_rtd; + struct snd_soc_codec *wm5102_codec, *wm8804_codec; + struct snd_soc_dai *wm8804_codec_dai, *bcm_i2s_dai = rtd->cpu_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; + + wm5102_rtd = snd_soc_get_pcm_runtime(card, card->dai_link[DAI_WM5102].name); + if (!wm5102_rtd) { + dev_warn(card->dev, "snd_rpi_wsp_hw_params: couldn't get WM5102 rtd\n"); + return -EFAULT; + } + wm8804_rtd = snd_soc_get_pcm_runtime(card, card->dai_link[DAI_WM8804].name); + if (!wm8804_rtd) { + dev_warn(card->dev, "snd_rpi_wsp_hw_params: couldn't get WM8804 rtd\n"); + return -EFAULT; + } + wm5102_codec = wm5102_rtd->codec; + wm8804_codec = wm8804_rtd->codec; + wm8804_codec_dai = wm8804_rtd->codec_dai; + + 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(priv, 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-003b", + .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); + struct snd_soc_pcm_runtime *wm5102_rtd, *wm8804_rtd; + struct snd_soc_codec *wm5102_codec, *wm8804_codec; + struct snd_soc_dai *wm5102_codec_dai, *wm8804_codec_dai, *wm8804_cpu_dai; + int ret; + + wm5102_rtd = snd_soc_get_pcm_runtime(card, card->dai_link[DAI_WM5102].name); + if (!wm5102_rtd) { + dev_warn(card->dev, "snd_rpi_wsp_late_probe: couldn't get WM5102 rtd\n"); + return -EFAULT; + } + wm8804_rtd = snd_soc_get_pcm_runtime(card, card->dai_link[DAI_WM8804].name); + if (!wm8804_rtd) { + dev_warn(card->dev, "snd_rpi_wsp_late_probe: couldn't get WM8804 rtd\n"); + return -EFAULT; + } + wm5102_codec = wm5102_rtd->codec; + wm5102_codec_dai = wm5102_rtd->codec_dai; + wm8804_codec = wm8804_rtd->codec; + wm8804_codec_dai = wm8804_rtd->codec_dai; + wm8804_cpu_dai = wm8804_rtd->cpu_dai; + + priv->wm8804_sr = RPI_WLF_SR; + priv->wm5102_sr = RPI_WLF_SR; + priv->sync_path_enable = 0; + + 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; + } + + ret = snd_rpi_wsp_config_8804_clks(wm8804_codec, wm8804_codec_dai, RPI_WLF_SR); + + if (ret != 0) { + dev_err(wm8804_codec->dev, "snd_rpi_wsp_config_8804_clks failed: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(wm5102_codec_dai, ARIZONA_CLK_SYSCLK, 0, 0); + if (ret != 0) { + dev_err(wm5102_codec_dai->dev, "Failed to set codec dai clk domain: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(wm8804_cpu_dai, ARIZONA_CLK_SYSCLK, 0, 0); + if (ret != 0) { + dev_err(wm8804_cpu_dai->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", + .owner = THIS_MODULE, + .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, +}; + +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; + + wm5102->fll1_freq = 0; + mutex_init(&wm5102->fll1_mutex); + + 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[DAI_WM5102]; + 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) { + if (ret == -EPROBE_DEFER) + dev_dbg(&pdev->dev, "register card requested probe deferral\n"); + else + 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_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 93b4008..1be8d2e 100644 --- a/sound/soc/codecs/arizona.c +++ b/sound/soc/codecs/arizona.c @@ -1095,7 +1095,7 @@ int arizona_set_sysclk(struct snd_soc_codec *codec, int clk_id, unsigned int reg; unsigned int mask = ARIZONA_SYSCLK_FREQ_MASK | ARIZONA_SYSCLK_SRC_MASK; unsigned int val = source << ARIZONA_SYSCLK_SRC_SHIFT; - unsigned int *clk; + int *clk; switch (clk_id) { case ARIZONA_CLK_SYSCLK: @@ -1375,6 +1375,9 @@ static int arizona_startup(struct snd_pcm_substream *substream, const struct snd_pcm_hw_constraint_list *constraint; unsigned int base_rate; + if (!substream->runtime) + return 0; + switch (dai_priv->clk) { case ARIZONA_CLK_SYSCLK: base_rate = priv->sysclk; @@ -2093,9 +2096,9 @@ static int arizona_enable_fll(struct arizona_fll *fll) /* Facilitate smooth refclk across the transition */ regmap_update_bits_async(fll->arizona->regmap, fll->base + 0x9, ARIZONA_FLL1_GAIN_MASK, 0); - regmap_update_bits_async(fll->arizona->regmap, fll->base + 1, - ARIZONA_FLL1_FREERUN, - ARIZONA_FLL1_FREERUN); + regmap_update_bits(fll->arizona->regmap, fll->base + 1, + ARIZONA_FLL1_FREERUN, ARIZONA_FLL1_FREERUN); + udelay(32); } /* @@ -2180,6 +2183,8 @@ static void arizona_disable_fll(struct arizona_fll *fll) { struct arizona *arizona = fll->arizona; bool change; + int i; + unsigned int val; regmap_update_bits_async(arizona->regmap, fll->base + 1, ARIZONA_FLL1_FREERUN, ARIZONA_FLL1_FREERUN); @@ -2190,6 +2195,25 @@ static void arizona_disable_fll(struct arizona_fll *fll) regmap_update_bits_async(arizona->regmap, fll->base + 1, ARIZONA_FLL1_FREERUN, 0); + arizona_fll_dbg(fll, "Waiting for FLL disable...\n"); + val = 0; + for (i = 0; i < 15; i++) { + if (i < 5) + usleep_range(200, 400); + else + msleep(20); + + regmap_read(arizona->regmap, + ARIZONA_INTERRUPT_RAW_STATUS_5, + &val); + if (!(val & (ARIZONA_FLL1_CLOCK_OK_STS << (fll->id - 1)))) + break; + } + if (i == 15) + arizona_fll_warn(fll, "Timed out waiting for disable\n"); + else + arizona_fll_dbg(fll, "FLL disabled (%d polls)\n", i); + if (change) pm_runtime_put_autosuspend(arizona->dev); } diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index a1305f8..31f36e6 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1295,7 +1295,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); @@ -1308,7 +1308,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 afb70a5..104888d 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -3450,11 +3450,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->edges[SND_SOC_DAPM_DIR_OUT]) || @@ -3472,6 +3473,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; @@ -3601,10 +3612,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; @@ -3706,6 +3718,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)