diff options
Diffstat (limited to 'sound')
78 files changed, 27390 insertions, 901 deletions
diff --git a/sound/core/pcm_dmaengine.c b/sound/core/pcm_dmaengine.c index 8eb58c709b14..99abc917427f 100644 --- a/sound/core/pcm_dmaengine.c +++ b/sound/core/pcm_dmaengine.c @@ -5,6 +5,7 @@ * Based on: * imx-pcm-dma-mx2.c, Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de> * mxs-pcm.c, Copyright (C) 2011 Freescale Semiconductor, Inc. + * imx-pcm-dma.c, Copyright (C) 2014-2015 Freescale Semiconductor, Inc. * ep93xx-pcm.c, Copyright (C) 2006 Lennert Buytenhek <buytenh@wantstofly.org> * Copyright (C) 2006 Applied Data Systems * @@ -28,13 +29,6 @@ #include <sound/dmaengine_pcm.h> -struct dmaengine_pcm_runtime_data { - struct dma_chan *dma_chan; - dma_cookie_t cookie; - - unsigned int pos; -}; - static inline struct dmaengine_pcm_runtime_data *substream_to_prtd( const struct snd_pcm_substream *substream) { @@ -171,7 +165,10 @@ static int dmaengine_pcm_prepare_and_submit(struct snd_pcm_substream *substream) if (!desc) return -ENOMEM; - desc->callback = dmaengine_pcm_dma_complete; + if (prtd->callback) + desc->callback = prtd->callback; + else + desc->callback = dmaengine_pcm_dma_complete; desc->callback_param = substream; prtd->cookie = dmaengine_submit(desc); diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index c67667bb970f..4f84c5c2542f 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -35,6 +35,10 @@ config SND_SOC_ALL_CODECS select SND_SOC_ADAU7002 select SND_SOC_ADS117X select SND_SOC_AK4104 if SPI_MASTER + select SND_SOC_AK4458 + select SND_SOC_AK4458_I2C if I2C + select SND_SOC_AK4458_SPI if SPI_MASTER + select SND_SOC_AK4497 if I2C select SND_SOC_AK4535 if I2C select SND_SOC_AK4554 select SND_SOC_AK4613 if I2C @@ -42,6 +46,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_AK4642 if I2C select SND_SOC_AK4671 if I2C select SND_SOC_AK5386 + select SND_SOC_AK5558 if I2C select SND_SOC_ALC5623 if I2C select SND_SOC_ALC5632 if I2C select SND_SOC_BT_SCO @@ -162,6 +167,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_WM8400 if MFD_WM8400 select SND_SOC_WM8510 if SND_SOC_I2C_AND_SPI select SND_SOC_WM8523 if I2C + select SND_SOC_WM8524 if GPIOLIB select SND_SOC_WM8580 if I2C select SND_SOC_WM8711 if SND_SOC_I2C_AND_SPI select SND_SOC_WM8727 @@ -203,6 +209,8 @@ config SND_SOC_ALL_CODECS select SND_SOC_WM9705 if SND_SOC_AC97_BUS select SND_SOC_WM9712 if SND_SOC_AC97_BUS select SND_SOC_WM9713 if SND_SOC_AC97_BUS + select SND_SOC_RPMSG_WM8960 + select SND_SOC_RPMSG_CS42XX8 help Normally ASoC codec drivers are only built if a machine driver which uses them is also built since they are only usable with a machine @@ -355,6 +363,26 @@ config SND_SOC_AK4104 tristate "AKM AK4104 CODEC" depends on SPI_MASTER +config SND_SOC_AK4458 + tristate + +config SND_SOC_AK4458_I2C + tristate "AKM AK4458 DAC I2c" + depends on I2C + select SND_SOC_AK4458 + select REGMAP_I2C + +config SND_SOC_AK4458_SPI + tristate "AKM AK4458 DAC SPI" + depends on SPI_MASTER + select SND_SOC_AK4458 + select REGMAP_SPI + +config SND_SOC_AK4497 + tristate "AKM AK4497 CODEC" + depends on I2C + select REGMAP_I2C + config SND_SOC_AK4535 tristate @@ -378,6 +406,11 @@ config SND_SOC_AK4671 config SND_SOC_AK5386 tristate "AKM AK5638 CODEC" +config SND_SOC_AK5558 + tristate "AKM AK5558 CODEC" + depends on I2C + select REGMAP_I2C + config SND_SOC_ALC5623 tristate "Realtek ALC5623 CODEC" depends on I2C @@ -523,6 +556,9 @@ config SND_SOC_ES8328_SPI tristate select SND_SOC_ES8328 +config SND_SOC_FSL_MQS + tristate + config SND_SOC_GTM601 tristate 'GTM601 UMTS modem audio codec' @@ -913,6 +949,10 @@ config SND_SOC_WM8523 tristate "Wolfson Microelectronics WM8523 DAC" depends on I2C +config SND_SOC_WM8524 + tristate "Wolfson Microelectronics WM8524 DAC" + depends on GPIOLIB + config SND_SOC_WM8580 tristate "Wolfson Microelectronics WM8523 CODEC" depends on I2C @@ -1062,6 +1102,12 @@ config SND_SOC_WM9713 tristate select REGMAP_AC97 +config SND_SOC_RPMSG_WM8960 + tristate + +config SND_SOC_RPMSG_CS42XX8 + tristate + # Amp config SND_SOC_LM4857 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 958cd4912fbc..070cccdabb9d 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -26,6 +26,10 @@ snd-soc-adav801-objs := adav801.o snd-soc-adav803-objs := adav803.o snd-soc-ads117x-objs := ads117x.o snd-soc-ak4104-objs := ak4104.o +snd-soc-ak4458-objs := ak4458.o +snd-soc-ak4458-i2c-objs := ak4458-i2c.o +snd-soc-ak4458-spi-objs := ak4458-spi.o +snd-soc-ak4497-objs := ak4497.o snd-soc-ak4535-objs := ak4535.o snd-soc-ak4554-objs := ak4554.o snd-soc-ak4613-objs := ak4613.o @@ -33,6 +37,7 @@ snd-soc-ak4641-objs := ak4641.o snd-soc-ak4642-objs := ak4642.o snd-soc-ak4671-objs := ak4671.o snd-soc-ak5386-objs := ak5386.o +snd-soc-ak5558-objs := ak5558.o snd-soc-arizona-objs := arizona.o snd-soc-bt-sco-objs := bt-sco.o snd-soc-cq93vc-objs := cq93vc.o @@ -64,6 +69,7 @@ snd-soc-dmic-objs := dmic.o snd-soc-es8328-objs := es8328.o snd-soc-es8328-i2c-objs := es8328-i2c.o snd-soc-es8328-spi-objs := es8328-spi.o +snd-soc-fsl-mqs-objs := fsl_mqs.o snd-soc-gtm601-objs := gtm601.o snd-soc-hdac-hdmi-objs := hdac_hdmi.o snd-soc-ics43432-objs := ics43432.o @@ -171,6 +177,7 @@ snd-soc-wm8350-objs := wm8350.o snd-soc-wm8400-objs := wm8400.o snd-soc-wm8510-objs := wm8510.o snd-soc-wm8523-objs := wm8523.o +snd-soc-wm8524-objs := wm8524.o snd-soc-wm8580-objs := wm8580.o snd-soc-wm8711-objs := wm8711.o snd-soc-wm8727-objs := wm8727.o @@ -214,6 +221,8 @@ snd-soc-wm9705-objs := wm9705.o snd-soc-wm9712-objs := wm9712.o snd-soc-wm9713-objs := wm9713.o snd-soc-wm-hubs-objs := wm_hubs.o +snd-soc-rpmsg-wm8960-objs := rpmsg_wm8960.o +snd-soc-rpmsg-cs42xx8-objs := rpmsg_cs42xx8.o # Amp snd-soc-max9877-objs := max9877.o @@ -249,6 +258,10 @@ obj-$(CONFIG_SND_SOC_ADAV801) += snd-soc-adav801.o obj-$(CONFIG_SND_SOC_ADAV803) += snd-soc-adav803.o obj-$(CONFIG_SND_SOC_ADS117X) += snd-soc-ads117x.o obj-$(CONFIG_SND_SOC_AK4104) += snd-soc-ak4104.o +obj-$(CONFIG_SND_SOC_AK4458) += snd-soc-ak4458.o +obj-$(CONFIG_SND_SOC_AK4458_I2C) += snd-soc-ak4458-i2c.o +obj-$(CONFIG_SND_SOC_AK4458_SPI) += snd-soc-ak4458-spi.o +obj-$(CONFIG_SND_SOC_AK4497) += snd-soc-ak4497.o obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o obj-$(CONFIG_SND_SOC_AK4554) += snd-soc-ak4554.o obj-$(CONFIG_SND_SOC_AK4613) += snd-soc-ak4613.o @@ -256,6 +269,7 @@ obj-$(CONFIG_SND_SOC_AK4641) += snd-soc-ak4641.o obj-$(CONFIG_SND_SOC_AK4642) += snd-soc-ak4642.o obj-$(CONFIG_SND_SOC_AK4671) += snd-soc-ak4671.o obj-$(CONFIG_SND_SOC_AK5386) += snd-soc-ak5386.o +obj-$(CONFIG_SND_SOC_AK5558) += snd-soc-ak5558.o obj-$(CONFIG_SND_SOC_ALC5623) += snd-soc-alc5623.o obj-$(CONFIG_SND_SOC_ALC5632) += snd-soc-alc5632.o obj-$(CONFIG_SND_SOC_ARIZONA) += snd-soc-arizona.o @@ -289,6 +303,7 @@ obj-$(CONFIG_SND_SOC_DMIC) += snd-soc-dmic.o obj-$(CONFIG_SND_SOC_ES8328) += snd-soc-es8328.o obj-$(CONFIG_SND_SOC_ES8328_I2C)+= snd-soc-es8328-i2c.o obj-$(CONFIG_SND_SOC_ES8328_SPI)+= snd-soc-es8328-spi.o +obj-$(CONFIG_SND_SOC_FSL_MQS) += snd-soc-fsl-mqs.o obj-$(CONFIG_SND_SOC_GTM601) += snd-soc-gtm601.o obj-$(CONFIG_SND_SOC_HDAC_HDMI) += snd-soc-hdac-hdmi.o obj-$(CONFIG_SND_SOC_ICS43432) += snd-soc-ics43432.o @@ -391,6 +406,7 @@ obj-$(CONFIG_SND_SOC_WM8350) += snd-soc-wm8350.o obj-$(CONFIG_SND_SOC_WM8400) += snd-soc-wm8400.o obj-$(CONFIG_SND_SOC_WM8510) += snd-soc-wm8510.o obj-$(CONFIG_SND_SOC_WM8523) += snd-soc-wm8523.o +obj-$(CONFIG_SND_SOC_WM8524) += snd-soc-wm8524.o obj-$(CONFIG_SND_SOC_WM8580) += snd-soc-wm8580.o obj-$(CONFIG_SND_SOC_WM8711) += snd-soc-wm8711.o obj-$(CONFIG_SND_SOC_WM8727) += snd-soc-wm8727.o @@ -435,6 +451,8 @@ obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o obj-$(CONFIG_SND_SOC_WM_ADSP) += snd-soc-wm-adsp.o obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o +obj-$(CONFIG_SND_SOC_RPMSG_WM8960) += snd-soc-rpmsg-wm8960.o +obj-$(CONFIG_SND_SOC_RPMSG_CS42XX8) += snd-soc-rpmsg-cs42xx8.o # Amp obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o diff --git a/sound/soc/codecs/ak4458-i2c.c b/sound/soc/codecs/ak4458-i2c.c new file mode 100644 index 000000000000..6ddca79d56d7 --- /dev/null +++ b/sound/soc/codecs/ak4458-i2c.c @@ -0,0 +1,79 @@ +/* + * ak4458-i2c.c -- AK4458 DAC - I2C + * + * Copyright 2017 NXP + * + * Author: Mihai Serban <mihai.serban@nxp.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/pm_runtime.h> + +#include "ak4458.h" + +static int ak4458_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct regmap *regmap; + int ret; + + regmap = devm_regmap_init_i2c(i2c, &ak4458_i2c_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + ret = ak4458_probe(&i2c->dev, regmap); + if (ret) + return ret; + + pm_runtime_enable(&i2c->dev); + + return 0; +} + +static int ak4458_i2c_remove(struct i2c_client *i2c) +{ + ak4458_remove(&i2c->dev); + pm_runtime_disable(&i2c->dev); + + return 0; +} + +static const struct i2c_device_id ak4458_i2c_id[] = { + { "ak4458", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ak4458_i2c_id); + +static const struct of_device_id ak4458_of_match[] = { + { .compatible = "asahi-kasei,ak4458", }, + { }, +}; +MODULE_DEVICE_TABLE(of, ak4458_of_match); + +static struct i2c_driver ak4458_i2c_driver = { + .driver = { + .name = "ak4458", + .pm = &ak4458_pm, + .of_match_table = ak4458_of_match, + }, + .probe = ak4458_i2c_probe, + .remove = ak4458_i2c_remove, + .id_table = ak4458_i2c_id +}; + +module_i2c_driver(ak4458_i2c_driver); + +MODULE_DESCRIPTION("ASoC AK4458 driver - I2C"); +MODULE_AUTHOR("Mihai Serban <mihai.serban@nxp.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ak4458-spi.c b/sound/soc/codecs/ak4458-spi.c new file mode 100644 index 000000000000..fd20e994b291 --- /dev/null +++ b/sound/soc/codecs/ak4458-spi.c @@ -0,0 +1,61 @@ +/* + * ak4458-spi.c -- AK4458 DAC - SPI + * + * Copyright 2017 NXP + * + * Author: Mihai Serban <mihai.serban@nxp.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/spi/spi.h> + +#include "ak4458.h" + +static int ak4458_spi_probe(struct spi_device *spi) +{ + struct regmap *regmap; + + regmap = devm_regmap_init_spi(spi, &ak4458_spi_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return ak4458_probe(&spi->dev, regmap); +} + +static int ak4458_spi_remove(struct spi_device *spi) +{ + ak4458_remove(&spi->dev); + return 0; +} + +static const struct of_device_id ak4458_of_match[] = { + { .compatible = "asahi-kasei,ak4458", }, + { }, +}; +MODULE_DEVICE_TABLE(of, ak4458_of_match); + +static struct spi_driver ak4458_spi_driver = { + .driver = { + .name = "ak4458", + .pm = &ak4458_pm, + .of_match_table = ak4458_of_match, + }, + .probe = ak4458_spi_probe, + .remove = ak4458_spi_remove +}; + +module_spi_driver(ak4458_spi_driver); + +MODULE_DESCRIPTION("ASoC AK4458 driver - SPI"); +MODULE_AUTHOR("Mihai Serban <mihai.serban@nxp.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ak4458.c b/sound/soc/codecs/ak4458.c new file mode 100644 index 000000000000..752fc5706b20 --- /dev/null +++ b/sound/soc/codecs/ak4458.c @@ -0,0 +1,1196 @@ +/* + * ak4458.c -- audio driver for AK4458 DAC + * + * Copyright (C) 2016 Asahi Kasei Microdevices Corporation + * Copyright 2017 NXP + * + * Authors: + * Tsuyoshi Mutsuro + * Junichi Wakasugi <wakasugi.jb@om.asahi-kasei.co.jp> + * Mihai Serban <mihai.serban@nxp.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/gpio/consumer.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <sound/tlv.h> +#include <sound/pcm_params.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> + +#include "ak4458.h" + +#define AK4458_NUM_SUPPLIES 2 +static const char *ak4458_supply_names[AK4458_NUM_SUPPLIES] = { + "DVDD", + "AVDD", +}; + +/* AK4458 Codec Private Data */ +struct ak4458_priv { + struct device *dev; + struct regmap *regmap; + int pdn_gpio; + int mute_gpio; + int sds; /* SDS2-0 bits */ + int digfil; /* SSLOW, SD, SLOW bits */ + int fs; /* sampling rate */ + int lr[4]; /* (MONO, INVL, INVR, SELLR) x4ch */ + int fmt; + int slots; + int slot_width; + struct regulator_bulk_data supplies[AK4458_NUM_SUPPLIES]; +}; + +static const struct reg_default ak4458_reg_defaults[] = { + { 0x00, 0x0C }, /* 0x00 AK4458_00_CONTROL1 */ + { 0x01, 0x22 }, /* 0x01 AK4458_01_CONTROL2 */ + { 0x02, 0x00 }, /* 0x02 AK4458_02_CONTROL3 */ + { 0x03, 0xFF }, /* 0x03 AK4458_03_LCHATT */ + { 0x04, 0xFF }, /* 0x04 AK4458_04_RCHATT */ + { 0x05, 0x00 }, /* 0x05 AK4458_05_CONTROL4 */ + { 0x06, 0x00 }, /* 0x06 AK4458_06_DSD1 */ + { 0x07, 0x03 }, /* 0x07 AK4458_07_CONTROL5 */ + { 0x08, 0x00 }, /* 0x08 AK4458_08_SOUND_CONTROL */ + { 0x09, 0x00 }, /* 0x09 AK4458_09_DSD2 */ + { 0x0A, 0x0D }, /* 0x0A AK4458_0A_CONTROL6 */ + { 0x0B, 0x0C }, /* 0x0B AK4458_0B_CONTROL7 */ + { 0x0C, 0x00 }, /* 0x0C AK4458_0C_CONTROL8 */ + { 0x0D, 0x00 }, /* 0x0D AK4458_0D_CONTROL9 */ + { 0x0E, 0x50 }, /* 0x0E AK4458_0E_CONTROL10 */ + { 0x0F, 0xFF }, /* 0x0F AK4458_0F_L2CHATT */ + { 0x10, 0xFF }, /* 0x10 AK4458_10_R2CHATT */ + { 0x11, 0xFF }, /* 0x11 AK4458_11_L3CHATT */ + { 0x12, 0xFF }, /* 0x12 AK4458_12_R3CHATT */ + { 0x13, 0xFF }, /* 0x13 AK4458_13_L4CHATT */ + { 0x14, 0xFF }, /* 0x14 AK4458_14_R4CHATT */ +}; + +static const struct regmap_range ak4458_spi_non_readable_reg_ranges[] = { + regmap_reg_range(AK4458_00_CONTROL1, AK4458_14_R4CHATT), +}; + +static const struct regmap_access_table ak4458_spi_readable_regs = { + .no_ranges = ak4458_spi_non_readable_reg_ranges, + .n_no_ranges = ARRAY_SIZE(ak4458_spi_non_readable_reg_ranges), +}; + +/* + * Volume control: + * from -127 to 0 dB in 0.5 dB steps (mute instead of -127.5 dB) + */ +static DECLARE_TLV_DB_SCALE(latt_tlv, -12750, 50, 1); +static DECLARE_TLV_DB_SCALE(ratt_tlv, -12750, 50, 1); + +/* + * DEM1 bit DEM0 bit Mode + * 0 0 44.1kHz + * 0 1 OFF (default) + * 1 0 48kHz + * 1 1 32kHz + */ +static const char * const ak4458_dem_select_texts[] = { + "44.1kHz", "OFF", "48kHz", "32kHz" +}; + +/* + * SSLOW, SD, SLOW bits Digital Filter Setting + * 0, 0, 0 : Sharp Roll-Off Filter + * 0, 0, 1 : Slow Roll-Off Filter + * 0, 1, 0 : Short delay Sharp Roll-Off Filter + * 0, 1, 1 : Short delay Slow Roll-Off Filter + * 1, *, * : Super Slow Roll-Off Filter + */ +static const char * const ak4458_digfil_select_texts[] = { + "Sharp Roll-Off Filter", + "Slow Roll-Off Filter", + "Short delay Sharp Roll-Off Filter", + "Short delay Slow Roll-Off Filter", + "Super Slow Roll-Off Filter" +}; + +/* + * DZFB: Inverting Enable of DZF + * 0: DZF goes H at Zero Detection + * 1: DZF goes L at Zero Detection + */ +static const char * const ak4458_dzfb_select_texts[] = {"H", "L"}; + +/* + * SC1-0 bits: Sound Mode Setting + * 0 0 : Sound Mode 0 + * 0 1 : Sound Mode 1 + * 1 0 : Sound Mode 2 + * 1 1 : Reserved + */ +static const char * const ak4458_sc_select_texts[] = { + "Sound Mode 0", "Sound Mode 1", "Sound Mode 2" +}; + +/* + * SDS2-0 bits: Output Data Select + * Refer to Data Sheet + */ +static const char * const ak4458_sds_select_texts[] = { + "Setting 0", "Setting 1", "Setting 2", "Setting 3", + "Setting 4", "Setting 5", "Setting 6", "Setting 7", +}; + +/* + * TDM1-0 bits: TDM Mode Setting + * 0 0 : Normal Mode + * 0 1 : TDM128 Mode + * 1 0 : TDM256 Mode + * 1 1 : TDM512 Mode + */ +static const char * const ak4458_tdm_select_texts[] = { + "Normal Mode", "TDM128 Mode", "TDM256 Mode", "TDM512 Mode" +}; + +/* FIR2-0 bits: FIR Filter Mode Setting */ +static const char * const ak4458_fir_select_texts[] = { + "Mode 0", "Mode 1", "Mode 2", "Mode 3", + "Mode 4", "Mode 5", "Mode 6", "Mode 7", +}; + +/* Mono and SELLR bit Setting (1~4) */ +static const char * const ak4458_dac_LR_select_texts[] = { + "Lch In, Rch In", + "Lch In, Rch In Invert", + "Lch In Invert, Rch In", + "Lch In Invert, Rch In Invert", + "Rch In, Lch In", + "Rch In, Lch In Invert", + "Rch In Invert, Lch In", + "Rch In Invert, Lch In Invert", + "Lch In, Lch In", + "Lch In, Lch In Invert", + "Lch In Invert, Lch In", + "Lch In Invert, Lch In Invert", + "Rch In, Rch In", + "Rch In, Rch In Invert", + "Rch In Invert, Rch In", + "Rch In Invert, Rch In Invert", +}; + +/* ATS1-0 bits Attenuation Speed */ +static const char * const ak4458_ats_select_texts[] = { + "4080/fs", "2040/fs", "510/fs", "255/fs", +}; + +/* DIF2 bit Audio Interface Format Setting(BICK fs) */ +static const char * const ak4458_dif_select_texts[] = {"32fs,48fs", "64fs",}; + +static int get_DAC1_LR(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct ak4458_priv *ak4458 = snd_soc_codec_get_drvdata(codec); + + ucontrol->value.enumerated.item[0] = ak4458->lr[0]; + + return 0; +}; + +static int get_DAC2_LR(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct ak4458_priv *ak4458 = snd_soc_codec_get_drvdata(codec); + + ucontrol->value.enumerated.item[0] = ak4458->lr[1]; + + return 0; +}; + +static int get_DAC3_LR(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct ak4458_priv *ak4458 = snd_soc_codec_get_drvdata(codec); + + ucontrol->value.enumerated.item[0] = ak4458->lr[2]; + + return 0; +}; + +static int get_DAC4_LR(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct ak4458_priv *ak4458 = snd_soc_codec_get_drvdata(codec); + + ucontrol->value.enumerated.item[0] = ak4458->lr[3]; + + return 0; +}; + +static int get_digfil(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct ak4458_priv *ak4458 = snd_soc_codec_get_drvdata(codec); + + ucontrol->value.enumerated.item[0] = ak4458->digfil; + + return 0; +}; + +static int get_sds(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct ak4458_priv *ak4458 = snd_soc_codec_get_drvdata(codec); + + ucontrol->value.enumerated.item[0] = ak4458->sds; + + return 0; +}; + +static int set_digfil(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct ak4458_priv *ak4458 = snd_soc_codec_get_drvdata(codec); + + int reg_01, reg_02, reg_05, num; + + num = ucontrol->value.enumerated.item[0]; + if (num > 4) + return -EINVAL; + + ak4458->digfil = num; + + /* write SD bit */ + reg_01 = snd_soc_read(codec, AK4458_01_CONTROL2); + reg_01 &= ~AK4458_SD_MASK; + + reg_01 |= ((ak4458->digfil & 0x02) << 4); + snd_soc_write(codec, AK4458_01_CONTROL2, reg_01); + + /* write SLOW bit */ + reg_02 = snd_soc_read(codec, AK4458_02_CONTROL3); + reg_02 &= ~AK4458_SLOW_MASK; + + reg_02 |= (ak4458->digfil & 0x01); + snd_soc_write(codec, AK4458_02_CONTROL3, reg_02); + + /* write SSLOW bit */ + reg_05 = snd_soc_read(codec, AK4458_05_CONTROL4); + reg_05 &= ~AK4458_SSLOW_MASK; + + reg_05 |= ((ak4458->digfil & 0x04) >> 2); + snd_soc_write(codec, AK4458_05_CONTROL4, reg_05); + + return 0; +}; + +static int set_sds(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct ak4458_priv *ak4458 = snd_soc_codec_get_drvdata(codec); + + int reg_0b, reg_0a; + + if (ucontrol->value.enumerated.item[0] > 7) + return -EINVAL; + + ak4458->sds = ucontrol->value.enumerated.item[0]; + + /* write SDS0 bit */ + reg_0b = snd_soc_read(codec, AK4458_0B_CONTROL7); + reg_0b &= ~AK4458_SDS0__MASK; + + reg_0b |= ((ak4458->sds & 0x01) << 4); + snd_soc_write(codec, AK4458_0B_CONTROL7, reg_0b); + + /* write SDS1,2 bits */ + reg_0a = snd_soc_read(codec, AK4458_0A_CONTROL6); + reg_0a &= ~AK4458_SDS12_MASK; + + reg_0a |= ((ak4458->sds & 0x02) << 4); + reg_0a |= ((ak4458->sds & 0x04) << 2); + snd_soc_write(codec, AK4458_0A_CONTROL6, reg_0a); + + return 0; + +}; + +static int set_DAC1_LR(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct ak4458_priv *ak4458 = snd_soc_codec_get_drvdata(codec); + + int reg_02, reg_05; + + if (ucontrol->value.enumerated.item[0] > 15) + return -EINVAL; + + ak4458->lr[0] = ucontrol->value.enumerated.item[0]; + + /* write MONO1 and SELLR1 bits */ + reg_02 = snd_soc_read(codec, AK4458_02_CONTROL3); + reg_02 &= ~AK4458_DAC1_LR_MASK; + + + reg_02 |= (ak4458->lr[0] & 0x08) << 0; + reg_02 |= (ak4458->lr[0] & 0x04) >> 1; + snd_soc_write(codec, AK4458_02_CONTROL3, reg_02); + + /* write INVL1 and INVR1 bits */ + reg_05 = snd_soc_read(codec, AK4458_05_CONTROL4); + reg_05 &= ~AK4458_DAC1_INV_MASK; + + reg_05 |= (ak4458->lr[0] & 0x02) << 6; + reg_05 |= (ak4458->lr[0] & 0x01) << 6; + snd_soc_write(codec, AK4458_05_CONTROL4, reg_05); + + return 0; + +}; + +static int set_DAC2_LR(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct ak4458_priv *ak4458 = snd_soc_codec_get_drvdata(codec); + + int reg_0D, reg_05; + + if (ucontrol->value.enumerated.item[0] > 15) + return -EINVAL; + + + ak4458->lr[1] = ucontrol->value.enumerated.item[0]; + + /* write MONO2 bit */ + reg_0D = snd_soc_read(codec, AK4458_0D_CONTROL9); + reg_0D &= ~AK4458_DAC2_MASK1; + + reg_0D |= (ak4458->lr[1] & 0x08) << 2; + snd_soc_write(codec, AK4458_0D_CONTROL9, reg_0D); + + /* write SELLR2 and INVL1 and INVR1 bits */ + reg_05 = snd_soc_read(codec, AK4458_05_CONTROL4); + reg_05 &= ~AK4458_DAC2_MASK2; + + reg_05 |= (ak4458->lr[1] & 0x04) << 1; + reg_05 |= (ak4458->lr[1] & 0x02) << 4; + reg_05 |= (ak4458->lr[1] & 0x01) << 4; + snd_soc_write(codec, AK4458_05_CONTROL4, reg_05); + + return 0; + +}; + +static int set_DAC3_LR(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct ak4458_priv *ak4458 = snd_soc_codec_get_drvdata(codec); + + int reg_0C, reg_0D; + + if (ucontrol->value.enumerated.item[0] > 15) + return -EINVAL; + + + ak4458->lr[2] = ucontrol->value.enumerated.item[0]; + + /* write MONO3 and SELLR3 bits */ + reg_0D = snd_soc_read(codec, AK4458_0D_CONTROL9); + reg_0D &= ~AK4458_DAC3_LR_MASK; + + + reg_0D |= (ak4458->lr[2] & 0x08) << 3; + reg_0D |= (ak4458->lr[2] & 0x04) << 0; + snd_soc_write(codec, AK4458_0D_CONTROL9, reg_0D); + + /* write INVL3 and INVR3 bits */ + reg_0C = snd_soc_read(codec, AK4458_0C_CONTROL8); + reg_0C &= ~AK4458_DAC3_INV_MASK; + + reg_0C |= (ak4458->lr[2] & 0x02) << 3; + reg_0C |= (ak4458->lr[2] & 0x01) << 5; + snd_soc_write(codec, AK4458_0C_CONTROL8, reg_0C); + + return 0; + +}; + +static int set_DAC4_LR(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct ak4458_priv *ak4458 = snd_soc_codec_get_drvdata(codec); + + int reg_0C, reg_0D; + + if (ucontrol->value.enumerated.item[0] > 15) + return -EINVAL; + + + ak4458->lr[3] = ucontrol->value.enumerated.item[0]; + + /* write MONO4 and SELLR4 bits */ + reg_0D = snd_soc_read(codec, AK4458_0D_CONTROL9); + reg_0D &= ~AK4458_DAC4_LR_MASK; + + + reg_0D |= (ak4458->lr[3] & 0x08) << 4; + reg_0D |= (ak4458->lr[3] & 0x04) << 1; + snd_soc_write(codec, AK4458_0D_CONTROL9, reg_0D); + + /* write INVL4 and INVR4 bits */ + reg_0C = snd_soc_read(codec, AK4458_0C_CONTROL8); + reg_0C &= ~AK4458_DAC4_INV_MASK; + + reg_0C |= (ak4458->lr[3] & 0x02) << 5; + reg_0C |= (ak4458->lr[3] & 0x01) << 7; + snd_soc_write(codec, AK4458_0C_CONTROL8, reg_0C); + + return 0; + +}; + +static const struct soc_enum ak4458_dac_enum[] = { +/*0*/ SOC_ENUM_SINGLE(AK4458_01_CONTROL2, 1, + ARRAY_SIZE(ak4458_dem_select_texts), + ak4458_dem_select_texts), +/*1*/ SOC_ENUM_SINGLE(AK4458_0A_CONTROL6, 0, + ARRAY_SIZE(ak4458_dem_select_texts), + ak4458_dem_select_texts), +/*2*/ SOC_ENUM_SINGLE(AK4458_0E_CONTROL10, 4, + ARRAY_SIZE(ak4458_dem_select_texts), + ak4458_dem_select_texts), +/*3*/ SOC_ENUM_SINGLE(AK4458_0E_CONTROL10, 6, + ARRAY_SIZE(ak4458_dem_select_texts), + ak4458_dem_select_texts), +/*4*/ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(ak4458_digfil_select_texts), + ak4458_digfil_select_texts), +/*5*/ SOC_ENUM_SINGLE(AK4458_02_CONTROL3, 2, + ARRAY_SIZE(ak4458_dzfb_select_texts), + ak4458_dzfb_select_texts), +/*6*/ SOC_ENUM_SINGLE(AK4458_08_SOUND_CONTROL, 0, + ARRAY_SIZE(ak4458_sc_select_texts), + ak4458_sc_select_texts), +/*7*/ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(ak4458_sds_select_texts), + ak4458_sds_select_texts), +/*8*/ SOC_ENUM_SINGLE(AK4458_0C_CONTROL8, 0, + ARRAY_SIZE(ak4458_fir_select_texts), + ak4458_fir_select_texts), +/*9*/ SOC_ENUM_SINGLE(AK4458_0A_CONTROL6, 6, + ARRAY_SIZE(ak4458_tdm_select_texts), + ak4458_tdm_select_texts), +/*10*/ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(ak4458_dac_LR_select_texts), + ak4458_dac_LR_select_texts), +/*11*/ SOC_ENUM_SINGLE(AK4458_0B_CONTROL7, 6, + ARRAY_SIZE(ak4458_ats_select_texts), + ak4458_ats_select_texts), +/*12*/ SOC_ENUM_SINGLE(AK4458_00_CONTROL1, 3, + ARRAY_SIZE(ak4458_dif_select_texts), + ak4458_dif_select_texts), +}; + +static const struct snd_kcontrol_new ak4458_snd_controls[] = { + SOC_SINGLE_TLV("AK4458 L1ch Digital Volume", + AK4458_03_LCHATT, 0/*shift*/, 0xFF/*max value*/, + 0/*invert*/, latt_tlv), + SOC_SINGLE_TLV("AK4458 R1ch Digital Volume", + AK4458_04_RCHATT, 0, 0xFF, 0, ratt_tlv), + SOC_SINGLE_TLV("AK4458 L2ch Digital Volume", + AK4458_0F_L2CHATT, 0/*shift*/, 0xFF/*max value*/, + 0/*invert*/, latt_tlv), + SOC_SINGLE_TLV("AK4458 R2ch Digital Volume", + AK4458_10_R2CHATT, 0, 0xFF, 0, ratt_tlv), + SOC_SINGLE_TLV("AK4458 L3ch Digital Volume", + AK4458_11_L3CHATT, 0/*shift*/, 0xFF/*max value*/, + 0/*invert*/, latt_tlv), + SOC_SINGLE_TLV("AK4458 R3ch Digital Volume", + AK4458_12_R3CHATT, 0, 0xFF, 0, ratt_tlv), + SOC_SINGLE_TLV("AK4458 L4ch Digital Volume", + AK4458_13_L4CHATT, 0/*shift*/, 0xFF/*max value*/, + 0/*invert*/, latt_tlv), + SOC_SINGLE_TLV("AK4458 R4ch Digital Volume", + AK4458_14_R4CHATT, 0, 0xFF, 0, ratt_tlv), + + SOC_ENUM("AK4458 De-emphasis Response DAC1", ak4458_dac_enum[0]), + SOC_ENUM("AK4458 De-emphasis Response DAC2", ak4458_dac_enum[1]), + SOC_ENUM("AK4458 De-emphasis Response DAC3", ak4458_dac_enum[2]), + SOC_ENUM("AK4458 De-emphasis Response DAC4", ak4458_dac_enum[3]), + SOC_ENUM_EXT("AK4458 Digital Filter Setting", ak4458_dac_enum[4], + get_digfil, set_digfil), + SOC_ENUM("AK4458 Inverting Enable of DZFB", ak4458_dac_enum[5]), + SOC_ENUM("AK4458 Sound Mode", ak4458_dac_enum[6]), + SOC_ENUM_EXT("AK4458 SDS Setting", ak4458_dac_enum[7], + get_sds, set_sds), + SOC_ENUM("AK4458 FIR Filter Mode Setting", ak4458_dac_enum[8]), + SOC_ENUM("AK4458 TDM Mode Setting", ak4458_dac_enum[9]), + SOC_ENUM_EXT("AK4458 DAC1 LRch Setting", ak4458_dac_enum[10], + get_DAC1_LR, set_DAC1_LR), + SOC_ENUM_EXT("AK4458 DAC2 LRch Setting", ak4458_dac_enum[10], + get_DAC2_LR, set_DAC2_LR), + SOC_ENUM_EXT("AK4458 DAC3 LRch Setting", ak4458_dac_enum[10], + get_DAC3_LR, set_DAC3_LR), + SOC_ENUM_EXT("AK4458 DAC4 LRch Setting", ak4458_dac_enum[10], + get_DAC4_LR, set_DAC4_LR), + SOC_ENUM("AK4458 Attenuation transition Time Setting", + ak4458_dac_enum[11]), + SOC_ENUM("AK4458 BICK fs Setting", ak4458_dac_enum[12]), +}; + +static const char * const ak4458_dac_select_texts[] = { "OFF", "ON" }; + +static const struct soc_enum ak4458_dac_mux_enum = + SOC_ENUM_SINGLE(0, 0, + ARRAY_SIZE(ak4458_dac_select_texts), + ak4458_dac_select_texts); +static const struct snd_kcontrol_new ak4458_dac1_mux_control = + SOC_DAPM_ENUM("DAC1 Switch", ak4458_dac_mux_enum); +static const struct snd_kcontrol_new ak4458_dac2_mux_control = + SOC_DAPM_ENUM("DAC2 Switch", ak4458_dac_mux_enum); +static const struct snd_kcontrol_new ak4458_dac3_mux_control = + SOC_DAPM_ENUM("DAC3 Switch", ak4458_dac_mux_enum); +static const struct snd_kcontrol_new ak4458_dac4_mux_control = + SOC_DAPM_ENUM("DAC4 Switch", ak4458_dac_mux_enum); + +/* ak4458 dapm widgets */ +static const struct snd_soc_dapm_widget ak4458_dapm_widgets[] = { + SND_SOC_DAPM_DAC("AK4458 DAC1", NULL, AK4458_0A_CONTROL6, 2, 0),/*pw*/ + SND_SOC_DAPM_AIF_IN("AK4458 SDTI", "Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_OUTPUT("AK4458 AOUTA"), + + SND_SOC_DAPM_DAC("AK4458 DAC2", NULL, AK4458_0A_CONTROL6, 3, 0),/*pw*/ + SND_SOC_DAPM_OUTPUT("AK4458 AOUTB"), + + SND_SOC_DAPM_DAC("AK4458 DAC3", NULL, AK4458_0B_CONTROL7, 2, 0),/*pw*/ + SND_SOC_DAPM_OUTPUT("AK4458 AOUTC"), + + SND_SOC_DAPM_DAC("AK4458 DAC4", NULL, AK4458_0B_CONTROL7, 3, 0),/*pw*/ + SND_SOC_DAPM_OUTPUT("AK4458 AOUTD"), + + SND_SOC_DAPM_MUX("DAC1 to AOUTA", SND_SOC_NOPM, 0, 0, + &ak4458_dac1_mux_control),/*nopm*/ + SND_SOC_DAPM_MUX("DAC2 to AOUTB", SND_SOC_NOPM, 0, 0, + &ak4458_dac2_mux_control),/*nopm*/ + SND_SOC_DAPM_MUX("DAC3 to AOUTC", SND_SOC_NOPM, 0, 0, + &ak4458_dac3_mux_control),/*nopm*/ + SND_SOC_DAPM_MUX("DAC4 to AOUTD", SND_SOC_NOPM, 0, 0, + &ak4458_dac4_mux_control),/*nopm*/ +}; + +static const struct snd_soc_dapm_route ak4458_intercon[] = { + {"DAC1 to AOUTA", "ON", "AK4458 SDTI"}, + {"AK4458 DAC1", NULL, "DAC1 to AOUTA"}, + {"AK4458 AOUTA", NULL, "AK4458 DAC1"}, + + {"DAC2 to AOUTB", "ON", "AK4458 SDTI"}, + {"AK4458 DAC2", NULL, "DAC2 to AOUTB"}, + {"AK4458 AOUTB", NULL, "AK4458 DAC2"}, + + {"DAC3 to AOUTC", "ON", "AK4458 SDTI"}, + {"AK4458 DAC3", NULL, "DAC3 to AOUTC"}, + {"AK4458 AOUTC", NULL, "AK4458 DAC3"}, + + {"DAC4 to AOUTD", "ON", "AK4458 SDTI"}, + {"AK4458 DAC4", NULL, "DAC4 to AOUTD"}, + {"AK4458 AOUTD", NULL, "AK4458 DAC4"}, + +}; + +static int ak4458_rstn_control(struct snd_soc_codec *codec, int bit) +{ + u8 rstn; + + dev_dbg(codec->dev, "%s(%d)\n", __func__, __LINE__); + + rstn = snd_soc_read(codec, AK4458_00_CONTROL1); + rstn &= ~AK4458_RSTN_MASK; + + if (bit) + rstn |= AK4458_RSTN; + + return snd_soc_write(codec, AK4458_00_CONTROL1, rstn); +} + +static int ak4458_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct ak4458_priv *ak4458 = snd_soc_codec_get_drvdata(codec); + u8 format; + int pcm_width = max(params_physical_width(params), ak4458->slot_width); + int ret; + +#ifdef AK4458_ACKS_USE_MANUAL_MODE + u8 dfs1, dfs2; +#endif + int nfs1; + + dev_dbg(dai->dev, "%s(%d)\n", __func__, __LINE__); + + format = snd_soc_read(codec, AK4458_00_CONTROL1); + format &= ~AK4458_DIF_MASK; + + nfs1 = params_rate(params); + ak4458->fs = nfs1; + +#ifdef AK4458_ACKS_USE_MANUAL_MODE + dfs1 = snd_soc_read(codec, AK4458_01_CONTROL2); + dfs1 &= ~AK4458_DFS01_MASK; + + dfs2 = snd_soc_read(codec, AK4458_05_CONTROL4); + dfs2 &= ~AK4458_DFS2__MASK; + + switch (nfs1) { + case 8000: + case 11025: + case 16000: + case 22050: + case 32000: + case 44100: + case 48000: + dfs1 |= AK4458_DFS01_48KHZ; + dfs2 |= AK4458_DFS2__48KHZ; + break; + case 88200: + case 96000: + dfs1 |= AK4458_DFS01_96KHZ; + dfs2 |= AK4458_DFS2__96KHZ; + break; + case 176400: + case 192000: + dfs1 |= AK4458_DFS01_192KHZ; + dfs2 |= AK4458_DFS2__192KHZ; + break; + case 352800: + case 384000: + dfs1 |= AK4458_DFS01_384KHZ; + dfs2 |= AK4458_DFS2__384KHZ; + break; + case 705600: + case 768000: + dfs1 |= AK4458_DFS01_768KHZ; + dfs2 |= AK4458_DFS2__768KHZ; + break; + default: + return -EINVAL; + } + + snd_soc_write(codec, AK4458_01_CONTROL2, dfs1); + snd_soc_write(codec, AK4458_05_CONTROL4, dfs2); + + ret = ak4458_rstn_control(codec, 0); + if (ret) + return ret; + + ak4458_rstn_control(codec, 1); + if (ret) + return ret; +#else + snd_soc_update_bits(codec, AK4458_00_CONTROL1, 0x80, 0x80); +#endif + + switch (pcm_width) { + case 16: + if (ak4458->fmt == SND_SOC_DAIFMT_I2S) + format |= AK4458_DIF_24BIT_I2S; + else + format |= AK4458_DIF_16BIT_LSB; + break; + case 32: + if (ak4458->fmt == SND_SOC_DAIFMT_I2S) + format |= AK4458_DIF_32BIT_I2S; + else if (ak4458->fmt == SND_SOC_DAIFMT_LEFT_J) + format |= AK4458_DIF_32BIT_MSB; + else if (ak4458->fmt == SND_SOC_DAIFMT_RIGHT_J) + format |= AK4458_DIF_32BIT_LSB; + else if (ak4458->fmt == SND_SOC_DAIFMT_DSP_B) + format |= AK4458_DIF_32BIT_MSB; + else + return -EINVAL; + break; + default: + return -EINVAL; + } + + snd_soc_write(codec, AK4458_00_CONTROL1, format); + + ret = ak4458_rstn_control(codec, 0); + if (ret) + return ret; + + ret = ak4458_rstn_control(codec, 1); + if (ret) + return ret; + + return 0; +} + +static int ak4458_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + dev_dbg(dai->dev, "%s(%d)\n", __func__, __LINE__); + + return 0; +} + +static int ak4458_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = dai->codec; + struct ak4458_priv *ak4458 = snd_soc_codec_get_drvdata(codec); + u8 format; + int ret; + + /* set master/slave audio interface */ + format = snd_soc_read(codec, AK4458_00_CONTROL1); + + dev_dbg(dai->dev, "%s(%d) addr 00H = %02X\n", + __func__, __LINE__, format); + + format &= ~AK4458_DIF_MASK; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: /* Slave Mode */ + break; + case SND_SOC_DAIFMT_CBM_CFM: /* Master Mode is not supported */ + case SND_SOC_DAIFMT_CBS_CFM: + case SND_SOC_DAIFMT_CBM_CFS: + default: + dev_err(codec->dev, "Master mode unsupported\n"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_LEFT_J: + case SND_SOC_DAIFMT_RIGHT_J: + ak4458->fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK; + break; + case SND_SOC_DAIFMT_DSP_B: + ak4458->fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK; + break; + default: + dev_err(codec->dev, "Audio format 0x%02X unsupported\n", + fmt & SND_SOC_DAIFMT_FORMAT_MASK); + return -EINVAL; + } + + /* set format */ + dev_dbg(dai->dev, "%s(%d) addr 00H = %02X\n", + __func__, __LINE__, format); + snd_soc_write(codec, AK4458_00_CONTROL1, format); + + ret = ak4458_rstn_control(codec, 0); + if (ret) + return ret; + + ret = ak4458_rstn_control(codec, 1); + if (ret) + return ret; + + return 0; +} + +static int ak4458_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *codec_dai) +{ + int ret = 0; + + dev_dbg(codec_dai->dev, "%s(%d)\n", __func__, __LINE__); + return ret; +} + + +static int ak4458_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + dev_dbg(codec->dev, "%s(%d)\n", __func__, __LINE__); + + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + case SND_SOC_BIAS_STANDBY: + break; + case SND_SOC_BIAS_OFF: + break; + } + + return 0; +} + +static const int att_speed[] = { 4080, 2040, 510, 255 }; + +static int ak4458_set_dai_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + struct ak4458_priv *ak4458 = snd_soc_codec_get_drvdata(codec); + int nfs, ndt, ret, reg; + int ats; + + nfs = ak4458->fs; + + reg = snd_soc_read(codec, AK4458_0B_CONTROL7); + ats = (reg & 0xC0) >> 6; + + dev_dbg(dai->dev, "%s mute[%s] nfs[%d]\n", __func__, + mute ? "ON":"OFF", nfs); + + ndt = att_speed[ats] / (nfs / 1000); + + if (mute) { /* SMUTE: 1 , MUTE */ + ret = snd_soc_update_bits(codec, AK4458_01_CONTROL2, 0x01, 1); + mdelay(ndt); + dev_dbg(dai->dev, "%s(%d) mdelay(%d ms)\n", + __func__, __LINE__, ndt); + + if (gpio_is_valid(ak4458->mute_gpio)) + gpio_set_value_cansleep(ak4458->mute_gpio, 1); + + dev_dbg(dai->dev, "%s External Mute = ON\n", __func__); + } else { /* SMUTE: 0 ,NORMAL operation */ + if (gpio_is_valid(ak4458->mute_gpio)) + gpio_set_value_cansleep(ak4458->mute_gpio, 0); + + dev_dbg(dai->dev, "%s External Mute = OFF\n", __func__); + ret = snd_soc_update_bits(codec, AK4458_01_CONTROL2, 0x01, 0); + mdelay(ndt); + dev_dbg(dai->dev, "%s(%d) mdelay(%d ms)\n", + __func__, __LINE__, ndt); + } + dev_dbg(dai->dev, "%s(%d) ret[%d]\n", __func__, __LINE__, ret); + + return 0; +} + +static int ak4458_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int slot_width) +{ + + struct snd_soc_codec *codec = dai->codec; + struct ak4458_priv *ak4458 = snd_soc_codec_get_drvdata(codec); + int tdm_mode = 0; + int reg; + + ak4458->slots = slots; + ak4458->slot_width = slot_width; + + switch(slots * slot_width) { + case 128: + tdm_mode = 1; + break; + case 256: + tdm_mode = 2; + break; + case 512: + tdm_mode = 3; + break; + default: + tdm_mode = 0; + break; + } + + reg = snd_soc_read(codec, AK4458_0A_CONTROL6); + reg &= ~(0x3 << 6); + reg |= tdm_mode << 6; + snd_soc_write(codec, AK4458_0A_CONTROL6, reg); + + return 0; +} + + + +#define AK4458_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |\ + SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 |\ + SNDRV_PCM_RATE_192000) + /* | SNDRV_PCM_RATE_384000 | SNDRV_PCM_RATE_768000 */ + +#define AK4458_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) + +static const unsigned int ak4458_rates[] = { + 8000, 11025, 16000, 22050, + 32000, 44100, 48000, 88200, + 96000, 176400, 192000, 352800, + 384000, 705600, 768000, 1411200, + 2822400, +}; + +static const struct snd_pcm_hw_constraint_list ak4458_rate_constraints = { + .count = ARRAY_SIZE(ak4458_rates), + .list = ak4458_rates, +}; + +static int ak4458_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) { + int ret; + + ret = snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &ak4458_rate_constraints); + + return ret; +} + +static struct snd_soc_dai_ops ak4458_dai_ops = { + .startup = ak4458_startup, + .hw_params = ak4458_hw_params, + .set_sysclk = ak4458_set_dai_sysclk, + .set_fmt = ak4458_set_dai_fmt, + .trigger = ak4458_trigger, + .digital_mute = ak4458_set_dai_mute, + .set_tdm_slot = ak4458_set_tdm_slot, +}; + +static struct snd_soc_dai_driver ak4458_dai = { + .name = "ak4458-aif", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = AK4458_FORMATS, + }, + .ops = &ak4458_dai_ops, +}; + +static int ak4458_init_reg(struct snd_soc_codec *codec) +{ + struct ak4458_priv *ak4458 = snd_soc_codec_get_drvdata(codec); + int ret; + + dev_dbg(codec->dev, "%s(%d)\n", __func__, __LINE__); + + /* External Mute ON */ + if (gpio_is_valid(ak4458->mute_gpio)) + gpio_set_value_cansleep(ak4458->mute_gpio, 1); + + if (gpio_is_valid(ak4458->pdn_gpio)) { + gpio_set_value_cansleep(ak4458->pdn_gpio, 0); + usleep_range(1000, 2000); + gpio_set_value_cansleep(ak4458->pdn_gpio, 1); + usleep_range(1000, 2000); + } + + ak4458_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + +#ifndef AK4458_ACKS_USE_MANUAL_MODE + snd_soc_update_bits(codec, AK4458_00_CONTROL1, + 0x80, 0x80); /* ACKS bit = 1; 10000000 */ + dev_dbg(codec->dev, "%s ACKS bit = 1\n", __func__); +#endif + + ret = ak4458_rstn_control(codec, 0); + if (ret) + return ret; + + ret = ak4458_rstn_control(codec, 1); + if (ret) + return ret; + + return 0; +} + +static int ak4458_codec_probe(struct snd_soc_codec *codec) +{ + struct ak4458_priv *ak4458 = snd_soc_codec_get_drvdata(codec); + int ret; + + dev_dbg(codec->dev, "%s(%d)\n", __func__, __LINE__); + + ret = ak4458_init_reg(codec); + + ak4458->fs = 48000; + + return ret; +} + +static int ak4458_codec_remove(struct snd_soc_codec *codec) +{ + dev_dbg(codec->dev, "%s(%d)\n", __func__, __LINE__); + + ak4458_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +#ifdef CONFIG_PM +static int ak4458_runtime_suspend(struct device *dev) +{ + struct ak4458_priv *ak4458 = dev_get_drvdata(dev); + + regcache_cache_only(ak4458->regmap, true); + + if (gpio_is_valid(ak4458->pdn_gpio)) { + gpio_set_value_cansleep(ak4458->pdn_gpio, 0); + usleep_range(1000, 2000); + } + + if (gpio_is_valid(ak4458->mute_gpio)) + gpio_set_value_cansleep(ak4458->mute_gpio, 0); + + return 0; +} + +static int ak4458_runtime_resume(struct device *dev) +{ + struct ak4458_priv *ak4458 = dev_get_drvdata(dev); + + if (gpio_is_valid(ak4458->mute_gpio)) + gpio_set_value_cansleep(ak4458->mute_gpio, 1); + + if (gpio_is_valid(ak4458->pdn_gpio)) { + gpio_set_value_cansleep(ak4458->pdn_gpio, 0); + usleep_range(1000, 2000); + gpio_set_value_cansleep(ak4458->pdn_gpio, 1); + usleep_range(1000, 2000); + } + + regcache_cache_only(ak4458->regmap, false); + regcache_mark_dirty(ak4458->regmap); + + return regcache_sync(ak4458->regmap); +} +#endif /* CONFIG_PM */ + +struct snd_soc_codec_driver soc_codec_dev_ak4458 = { + .probe = ak4458_codec_probe, + .remove = ak4458_codec_remove, + .set_bias_level = ak4458_set_bias_level, + + .component_driver = { + .controls = ak4458_snd_controls, + .num_controls = ARRAY_SIZE(ak4458_snd_controls), + .dapm_widgets = ak4458_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(ak4458_dapm_widgets), + .dapm_routes = ak4458_intercon, + .num_dapm_routes = ARRAY_SIZE(ak4458_intercon), + }, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_ak4458); + +const struct regmap_config ak4458_i2c_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = AK4458_14_R4CHATT, + .reg_defaults = ak4458_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(ak4458_reg_defaults), + .cache_type = REGCACHE_RBTREE, +}; +EXPORT_SYMBOL_GPL(ak4458_i2c_regmap_config); + +const struct regmap_config ak4458_spi_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = AK4458_14_R4CHATT, + .rd_table = &ak4458_spi_readable_regs, + .reg_defaults = ak4458_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(ak4458_reg_defaults), + .cache_type = REGCACHE_RBTREE, +}; +EXPORT_SYMBOL_GPL(ak4458_spi_regmap_config); + +int ak4458_probe(struct device *dev, struct regmap *regmap) +{ + struct ak4458_priv *ak4458; + struct device_node *np = dev->of_node; + int ret; + int i; + + ak4458 = devm_kzalloc(dev, sizeof(*ak4458), GFP_KERNEL); + if (!ak4458) + return -ENOMEM; + + dev_set_drvdata(dev, ak4458); + + ak4458->dev = dev; + ak4458->regmap = regmap; + + ak4458->pdn_gpio = of_get_named_gpio(np, "ak4458,pdn-gpio", 0); + if (gpio_is_valid(ak4458->pdn_gpio)) { + ret = devm_gpio_request_one(dev, ak4458->pdn_gpio, + GPIOF_OUT_INIT_LOW, "ak4458,pdn"); + if (ret) { + dev_err(dev, "unable to get pdn gpio\n"); + return ret; + } + } + + ak4458->mute_gpio = of_get_named_gpio(np, "ak4458,mute_gpio", 0); + if (gpio_is_valid(ak4458->mute_gpio)) { + ret = devm_gpio_request_one(dev, ak4458->mute_gpio, + GPIOF_OUT_INIT_LOW, "ak4458,mute"); + if (ret) { + dev_err(dev, "unable to get mute gpio\n"); + return ret; + } + } + + for (i = 0; i < ARRAY_SIZE(ak4458->supplies); i++) + ak4458->supplies[i].supply = ak4458_supply_names[i]; + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ak4458->supplies), + ak4458->supplies); + if (ret != 0) { + dev_err(dev, "Failed to request supplies: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(ak4458->supplies), + ak4458->supplies); + if (ret != 0) { + dev_err(dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + ret = snd_soc_register_codec(dev, &soc_codec_dev_ak4458, + &ak4458_dai, 1); + if (ret < 0) { + dev_err(dev, "Failed to register CODEC: %d\n", ret); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(ak4458_probe); + +void ak4458_remove(struct device *dev) +{ + snd_soc_unregister_codec(dev); +} +EXPORT_SYMBOL_GPL(ak4458_remove); + +const struct dev_pm_ops ak4458_pm = { + SET_RUNTIME_PM_OPS(ak4458_runtime_suspend, ak4458_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) +}; +EXPORT_SYMBOL_GPL(ak4458_pm); + +MODULE_AUTHOR("Junichi Wakasugi <wakasugi.jb@om.asahi-kasei.co.jp>"); +MODULE_AUTHOR("Mihai Serban <mihai.serban@nxp.com>"); +MODULE_DESCRIPTION("ASoC AK4458 DAC driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ak4458.h b/sound/soc/codecs/ak4458.h new file mode 100644 index 000000000000..04ecdf280d45 --- /dev/null +++ b/sound/soc/codecs/ak4458.h @@ -0,0 +1,130 @@ +/* + * ak4458.h -- audio driver for AK4458 + * + * Copyright (C) 2016 Asahi Kasei Microdevices Corporation + * Author: Tsuyoshi Mutsuro + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _AK4458_H +#define _AK4458_H + +#include <linux/regmap.h> + +/* Settings */ +#define AK4458_ACKS_USE_MANUAL_MODE + +#define AK4458_00_CONTROL1 0x00 +#define AK4458_01_CONTROL2 0x01 +#define AK4458_02_CONTROL3 0x02 +#define AK4458_03_LCHATT 0x03 +#define AK4458_04_RCHATT 0x04 +#define AK4458_05_CONTROL4 0x05 +#define AK4458_06_DSD1 0x06 +#define AK4458_07_CONTROL5 0x07 +#define AK4458_08_SOUND_CONTROL 0x08 +#define AK4458_09_DSD2 0x09 +#define AK4458_0A_CONTROL6 0x0A +#define AK4458_0B_CONTROL7 0x0B +#define AK4458_0C_CONTROL8 0x0C +#define AK4458_0D_CONTROL9 0x0D +#define AK4458_0E_CONTROL10 0x0E +#define AK4458_0F_L2CHATT 0x0F +#define AK4458_10_R2CHATT 0x10 +#define AK4458_11_L3CHATT 0x11 +#define AK4458_12_R3CHATT 0x12 +#define AK4458_13_L4CHATT 0x13 +#define AK4458_14_R4CHATT 0x14 + +/* Bitfield Definitions */ + +/* AK4458_00_CONTROL1 (0x00) Fields */ +//Addr Register Name D7 D6 D5 D4 D3 D2 D1 D0 +//00H Control 1 ACKS 0 0 0 DIF2 DIF1 DIF0 RSTN + +//MONO1 & SELLR1 bits +#define AK4458_DAC1_LR_MASK 0x0A +#define AK4458_DAC1_INV_MASK 0xC0 + +//MONO2 & SELLR2 bits +#define AK4458_DAC2_MASK1 0x20 +#define AK4458_DAC2_MASK2 0x38 + +//MONO3 & SELLR3 bits +#define AK4458_DAC3_LR_MASK 0x44 +#define AK4458_DAC3_INV_MASK 0x30 + +//MONO4 & SELLR4 bits +#define AK4458_DAC4_LR_MASK 0x88 +#define AK4458_DAC4_INV_MASK 0xC0 + + +//SDS2-0 bits +#define AK4458_SDS0__MASK 0x10 +#define AK4458_SDS12_MASK 0x30 + +//Digital Filter (SD, SLOW, SSLOW) +#define AK4458_SD_MASK 0x20 +#define AK4458_SLOW_MASK 0x01 +#define AK4458_SSLOW_MASK 0x01 + +//DIF2 1 0 +// x 1 0 MSB justified Figure 3 (default) +// x 1 1 I2S Compliment Figure 4 +#define AK4458_DIF_MASK 0x0E +#define AK4458_DIF_MSB_LOW_FS_MODE (2 << 1) +#define AK4458_DIF_I2S_LOW_FS_MODE (3 << 1) + +#define AK4458_DIF_16BIT_LSB (0 << 1) +#define AK4458_DIF_20BIT_LSB (1 << 1) +#define AK4458_DIF_24BIT_MSB (2 << 1) +#define AK4458_DIF_24BIT_I2S (3 << 1) +#define AK4458_DIF_24BIT_LSB (4 << 1) +#define AK4458_DIF_32BIT_LSB (5 << 1) +#define AK4458_DIF_32BIT_MSB (6 << 1) +#define AK4458_DIF_32BIT_I2S (7 << 1) + + +// ACKS is Auto mode so disable the Manual feature +//#define AK4458_ACKS_USE_MANUAL_MODE +/* AK4458_00_CONTROL1 (0x00) D0 bit */ +#define AK4458_RSTN_MASK 0x01 +#define AK4458_RSTN (0x1 << 0) + + +#ifdef AK4458_ACKS_USE_MANUAL_MODE +/* AK4458_01_CONTROL2 (0x01) and AK4458_05_CONTROL4 (0x05) Fields */ +#define AK4458_DFS01_MASK 0x18 +#define AK4458_DFS2__MASK 0x02 +#define AK4458_DFS01_48KHZ (0x0 << 3) // 30kHz to 54kHz +#define AK4458_DFS2__48KHZ (0x0 << 1) // 30kHz to 54kHz + +#define AK4458_DFS01_96KHZ (0x1 << 3) // 54kHz to 108kHz +#define AK4458_DFS2__96KHZ (0x0 << 1) // 54kHz to 108kHz + +#define AK4458_DFS01_192KHZ (0x2 << 3) // 120kHz to 216kHz +#define AK4458_DFS2__192KHZ (0x0 << 1) // 120kHz to 216kHz + +#define AK4458_DFS01_384KHZ (0x0 << 3) // 384kHz +#define AK4458_DFS2__384KHZ (0x1 << 1) // 384kHz + +#define AK4458_DFS01_768KHZ (0x1 << 3) // 768kHz +#define AK4458_DFS2__768KHZ (0x1 << 1) // 768kHz +#endif + +extern const struct regmap_config ak4458_i2c_regmap_config; +extern const struct regmap_config ak4458_spi_regmap_config; +extern const struct dev_pm_ops ak4458_pm; + +int ak4458_probe(struct device *dev, struct regmap *regmap); +void ak4458_remove(struct device *dev); + +#endif diff --git a/sound/soc/codecs/ak4497.c b/sound/soc/codecs/ak4497.c new file mode 100644 index 000000000000..eee1afb81b8c --- /dev/null +++ b/sound/soc/codecs/ak4497.c @@ -0,0 +1,1094 @@ +/* + * ak4497.c -- audio driver for AK4497 + * + * Copyright (C) 2016 Asahi Kasei Microdevices Corporation + * Copyright (C) 2017, NXP + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <sound/tlv.h> +#include <sound/pcm_params.h> +#include <linux/of_gpio.h> +#include <linux/regmap.h> +#include <sound/pcm_params.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> + +#include "ak4497.h" + +//#define AK4497_DEBUG //used at debug mode +#define AK4497_NUM_SUPPLIES 2 +static const char *ak4497_supply_names[AK4497_NUM_SUPPLIES] = { + "DVDD", + "AVDD", +}; + +/* AK4497 Codec Private Data */ +struct ak4497_priv { + struct i2c_client *i2c; + struct regmap *regmap; + int fs1; /* Sampling Frequency */ + int nBickFreq; /* 0: 48fs for 24bit, 1: 64fs or more for 32bit */ + int nTdmSds; + int pdn_gpio; + int mute_gpio; + int fmt; + struct regulator_bulk_data supplies[AK4497_NUM_SUPPLIES]; +}; + +/* ak4497 register cache & default register settings */ +static const struct reg_default ak4497_reg[] = { + { AK4497_00_CONTROL1, 0x0C}, + { AK4497_01_CONTROL2, 0x22}, + { AK4497_02_CONTROL3, 0x00}, + { AK4497_03_LCHATT, 0xFF}, + { AK4497_04_RCHATT, 0xFF}, + { AK4497_05_CONTROL4, 0x00}, + { AK4497_06_DSD1, 0x00}, + { AK4497_07_CONTROL5, 0x00}, + { AK4497_08_SOUNDCONTROL, 0x00}, + { AK4497_09_DSD2, 0x00}, + { AK4497_0A_CONTROL7, 0x04}, + { AK4497_0B_CONTROL8, 0x00}, + { AK4497_0C_RESERVED, 0x00}, + { AK4497_0D_RESERVED, 0x00}, + { AK4497_0E_RESERVED, 0x00}, + { AK4497_0F_RESERVED, 0x00}, + { AK4497_10_RESERVED, 0x00}, + { AK4497_11_RESERVED, 0x00}, + { AK4497_12_RESERVED, 0x00}, + { AK4497_13_RESERVED, 0x00}, + { AK4497_14_RESERVED, 0x00}, + { AK4497_15_DFSREAD, 0x00}, +}; + +/* Volume control: + * from -127 to 0 dB in 0.5 dB steps (mute instead of -127.5 dB) + */ +static DECLARE_TLV_DB_SCALE(latt_tlv, -12750, 50, 0); +static DECLARE_TLV_DB_SCALE(ratt_tlv, -12750, 50, 0); + +static const char * const ak4497_ecs_select_texts[] = {"768kHz", "384kHz"}; + +static const char * const ak4497_dem_select_texts[] = { + "44.1kHz", "OFF", "48kHz", "32kHz"}; +static const char * const ak4497_dzfm_select_texts[] = { + "Separated", "ANDed"}; + +static const char * const ak4497_sellr_select_texts[] = { + "Rch", "Lch"}; +static const char * const ak4497_dckb_select_texts[] = { + "Falling", "Rising"}; +static const char * const ak4497_dcks_select_texts[] = { + "512fs", "768fs"}; + +static const char * const ak4497_dsdd_select_texts[] = { + "Normal", "Volume Bypass"}; + +static const char * const ak4497_sc_select_texts[] = { + "Setting 1", "Setting 2", "Setting 3"}; +static const char * const ak4497_dsdf_select_texts[] = { + "50kHz", "150kHz"}; +static const char * const ak4497_dsd_input_path_select[] = { + "16_17_19pin", "3_4_5pin"}; +static const char * const ak4497_ats_select_texts[] = { + "4080/fs", "2040/fs", "510/fs", "255/fs"}; + +static const struct soc_enum ak4497_dac_enum[] = { + SOC_ENUM_SINGLE(AK4497_00_CONTROL1, 5, + ARRAY_SIZE(ak4497_ecs_select_texts), + ak4497_ecs_select_texts), + SOC_ENUM_SINGLE(AK4497_01_CONTROL2, 1, + ARRAY_SIZE(ak4497_dem_select_texts), + ak4497_dem_select_texts), + SOC_ENUM_SINGLE(AK4497_01_CONTROL2, 6, + ARRAY_SIZE(ak4497_dzfm_select_texts), + ak4497_dzfm_select_texts), + SOC_ENUM_SINGLE(AK4497_02_CONTROL3, 1, + ARRAY_SIZE(ak4497_sellr_select_texts), + ak4497_sellr_select_texts), + SOC_ENUM_SINGLE(AK4497_02_CONTROL3, 4, + ARRAY_SIZE(ak4497_dckb_select_texts), + ak4497_dckb_select_texts), + SOC_ENUM_SINGLE(AK4497_02_CONTROL3, 5, + ARRAY_SIZE(ak4497_dcks_select_texts), + ak4497_dcks_select_texts), + SOC_ENUM_SINGLE(AK4497_06_DSD1, 1, + ARRAY_SIZE(ak4497_dsdd_select_texts), + ak4497_dsdd_select_texts), + SOC_ENUM_SINGLE(AK4497_08_SOUNDCONTROL, 0, + ARRAY_SIZE(ak4497_sc_select_texts), + ak4497_sc_select_texts), + SOC_ENUM_SINGLE(AK4497_09_DSD2, 1, + ARRAY_SIZE(ak4497_dsdf_select_texts), + ak4497_dsdf_select_texts), + SOC_ENUM_SINGLE(AK4497_09_DSD2, 2, + ARRAY_SIZE(ak4497_dsd_input_path_select), + ak4497_dsd_input_path_select), + SOC_ENUM_SINGLE(AK4497_0B_CONTROL8, 6, + ARRAY_SIZE(ak4497_ats_select_texts), + ak4497_ats_select_texts), +}; + +static const char * const ak4497_dsdsel_select_texts[] = { + "64fs", "128fs", "256fs", "512fs"}; +static const char * const ak4497_bickfreq_select[] = {"48fs", "64fs"}; + +static const char * const ak4497_tdm_sds_select[] = { + "L1R1", "TDM128_L1R1", "TDM128_L2R2", + "TDM256_L1R1", "TDM256_L2R2", "TDM256_L3R3", "TDM256_L4R4", + "TDM512_L1R1", "TDM512_L2R2", "TDM512_L3R3", "TDM512_L4R4", + "TDM512_L5R5", "TDM512_L6R6", "TDM512_L7R7", "TDM512_L8R8", +}; + +static const char * const ak4497_adfs_select[] = { + "Normal Speed Mode", "Double Speed Mode", "Quad Speed Mode", + "Quad Speed Mode", "Oct Speed Mode", "Hex Speed Mode", "Oct Speed Mode", + "Hex Speed Mode" +}; + +static const struct soc_enum ak4497_dac_enum2[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(ak4497_dsdsel_select_texts), + ak4497_dsdsel_select_texts), + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(ak4497_bickfreq_select), + ak4497_bickfreq_select), + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(ak4497_tdm_sds_select), + ak4497_tdm_sds_select), + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(ak4497_adfs_select), + ak4497_adfs_select) +}; + +int ak4497_read(struct snd_soc_codec *codec, unsigned int reg, + unsigned int *val) +{ + int ret; + + ret = snd_soc_component_read(&codec->component, reg, val); + if (ret < 0) + dev_err(codec->dev, "Register %u read failed, ret=%d.\n", reg, ret); + + return ret; +} + +static int ak4497_get_dsdsel(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + unsigned int dsdsel0, dsdsel1; + + ak4497_read(codec, AK4497_06_DSD1, &dsdsel0); + dsdsel0 &= AK4497_DSDSEL0; + + ak4497_read(codec, AK4497_09_DSD2, &dsdsel1); + dsdsel1 &= AK4497_DSDSEL1; + + ucontrol->value.enumerated.item[0] = ((dsdsel1 << 1) | dsdsel0); + + return 0; +} + +static int ak4497_set_dsdsel(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + unsigned int dsdsel = ucontrol->value.enumerated.item[0]; + + switch (dsdsel) { + case 0: /* 2.8224MHz */ + snd_soc_update_bits(codec, AK4497_06_DSD1, 0x01, 0x00); + snd_soc_update_bits(codec, AK4497_09_DSD2, 0x01, 0x00); + break; + case 1: /* 5.6448MHz */ + snd_soc_update_bits(codec, AK4497_06_DSD1, 0x01, 0x01); + snd_soc_update_bits(codec, AK4497_09_DSD2, 0x01, 0x00); + break; + case 2: /* 11.2896MHz */ + snd_soc_update_bits(codec, AK4497_06_DSD1, 0x01, 0x00); + snd_soc_update_bits(codec, AK4497_09_DSD2, 0x01, 0x01); + break; + case 3: /* 22.5792MHz */ + snd_soc_update_bits(codec, AK4497_06_DSD1, 0x01, 0x01); + snd_soc_update_bits(codec, AK4497_09_DSD2, 0x01, 0x01); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int ak4497_get_bickfs(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct ak4497_priv *ak4497 = snd_soc_codec_get_drvdata(codec); + + ucontrol->value.enumerated.item[0] = ak4497->nBickFreq; + + return 0; +} + +static int ak4497_set_bickfs(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct ak4497_priv *ak4497 = snd_soc_codec_get_drvdata(codec); + + ak4497->nBickFreq = ucontrol->value.enumerated.item[0]; + + return 0; +} + +static int ak4497_get_tdmsds(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct ak4497_priv *ak4497 = snd_soc_codec_get_drvdata(codec); + + ucontrol->value.enumerated.item[0] = ak4497->nTdmSds; + + return 0; +} + +static int ak4497_set_tdmsds(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct ak4497_priv *ak4497 = snd_soc_codec_get_drvdata(codec); + int regA, regB; + + ak4497->nTdmSds = ucontrol->value.enumerated.item[0]; + + if (ak4497->nTdmSds == 0) + regB = 0; /* SDS0 bit = 0 */ + else + regB = (1 & (ak4497->nTdmSds - 1)); /* SDS0 bit = 1 */ + + switch (ak4497->nTdmSds) { + case 0: + regA = 0; /* Normal */ + break; + case 1: + case 2: + regA = 4; /* TDM128 TDM1-0bits = 1 */ + break; + case 3: + case 4: + regA = 8; /* TDM128 TDM1-0bits = 2 */ + break; + case 5: + case 6: + regA = 9; /* TDM128 TDM1-0bits = 2 */ + break; + case 7: + case 8: + regA = 0xC; /* TDM128 TDM1-0bits = 3 */ + break; + case 9: + case 10: + regA = 0xD; /* TDM128 TDM1-0bits = 3 */ + break; + case 11: + case 12: + regA = 0xE; /* TDM128 TDM1-0bits = 3 */ + break; + case 13: + case 14: + regA = 0xF; /* TDM128 TDM1-0bits = 3 */ + break; + default: + regA = 0; + regB = 0; + break; + } + + regA <<= 4; + regB <<= 4; + + snd_soc_update_bits(codec, AK4497_0A_CONTROL7, 0xF0, regA); + snd_soc_update_bits(codec, AK4497_0B_CONTROL8, 0x10, regB); + + return 0; +} + +static int ak4497_get_adfs(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + unsigned int nADFSbit; + + ak4497_read(codec, AK4497_15_DFSREAD, &nADFSbit); + nADFSbit &= 0x7; + + ucontrol->value.enumerated.item[0] = nADFSbit; + + return 0; +} + +static int ak4497_set_adfs(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("AK4497 : ADFS is read only\n"); + + return 0; +} + +static const char * const gain_control_texts[] = { + "2.8_2.8Vpp", "2.8_2.5Vpp", "2.5_2.5Vpp", "3.75_3.75Vpp", "3.75_2.5Vpp" +}; + +static const unsigned int gain_control_values[] = { + 0, 1, 2, 4, 5 +}; + +static const struct soc_enum ak4497_gain_control_enum = + SOC_VALUE_ENUM_SINGLE(AK4497_07_CONTROL5, 1, 7, + ARRAY_SIZE(gain_control_texts), + gain_control_texts, + gain_control_values); + +#ifdef AK4497_DEBUG + +static const char * const test_reg_select[] = { + "read AK4497 Reg 00:0B", + "read AK4497 Reg 15" +}; + +static const struct soc_enum ak4497_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(test_reg_select), test_reg_select), +}; + +static int nTestRegNo; + +static int get_test_reg(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + /* Get the current output routing */ + ucontrol->value.enumerated.item[0] = nTestRegNo; + + return 0; +} + +static int set_test_reg(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + u32 currMode = ucontrol->value.enumerated.item[0]; + int i, regs, rege; + unsigned int value; + + nTestRegNo = currMode; + + if (nTestRegNo == 0) { + regs = 0x00; + rege = 0x0B; + } else { + regs = 0x15; + rege = 0x15; + } + + for (i = regs; i <= rege; i++) { + ak4497_read(codec, i, &value); + pr_debug("***AK4497 Addr,Reg=(%x, %x)\n", i, value); + } + + return 0; +} +#endif + +static const struct snd_kcontrol_new ak4497_snd_controls[] = { + SOC_SINGLE_TLV("AK4497 Lch Digital Volume", + AK4497_03_LCHATT, 0, 0xFF, 0, latt_tlv), + SOC_SINGLE_TLV("AK4497 Rch Digital Volume", + AK4497_04_RCHATT, 0, 0xFF, 0, ratt_tlv), + + SOC_ENUM("AK4497 EX DF I/F clock", ak4497_dac_enum[0]), + SOC_ENUM("AK4497 De-emphasis Response", ak4497_dac_enum[1]), + SOC_ENUM("AK4497 Data Zero Detect Mode", ak4497_dac_enum[2]), + SOC_ENUM("AK4497 Data Selection at Mono Mode", ak4497_dac_enum[3]), + + SOC_ENUM("AK4497 Polarity of DCLK", ak4497_dac_enum[4]), + SOC_ENUM("AK4497 DCKL Frequency", ak4497_dac_enum[5]), + + SOC_ENUM("AK4497 DDSD Play Back Path", ak4497_dac_enum[6]), + SOC_ENUM("AK4497 Sound control", ak4497_dac_enum[7]), + SOC_ENUM("AK4497 Cut Off of DSD Filter", ak4497_dac_enum[8]), + + SOC_ENUM_EXT("AK4497 DSD Data Stream", ak4497_dac_enum2[0], + ak4497_get_dsdsel, ak4497_set_dsdsel), + SOC_ENUM_EXT("AK4497 BICK Frequency Select", ak4497_dac_enum2[1], + ak4497_get_bickfs, ak4497_set_bickfs), + SOC_ENUM_EXT("AK4497 TDM Data Select", ak4497_dac_enum2[2], + ak4497_get_tdmsds, ak4497_set_tdmsds), + + SOC_SINGLE("AK4497 External Digital Filter", AK4497_00_CONTROL1, + 6, 1, 0), + SOC_SINGLE("AK4497 MCLK Frequency Auto Setting", AK4497_00_CONTROL1, + 7, 1, 0), + SOC_SINGLE("AK4497 MCLK FS Auto Detect", AK4497_00_CONTROL1, 4, 1, 0), + + SOC_SINGLE("AK4497 Soft Mute Control", AK4497_01_CONTROL2, 0, 1, 0), + SOC_SINGLE("AK4497 Short delay filter", AK4497_01_CONTROL2, 5, 1, 0), + SOC_SINGLE("AK4497 Data Zero Detect Enable", AK4497_01_CONTROL2, + 7, 1, 0), + SOC_SINGLE("AK4497 Slow Roll-off Filter", AK4497_02_CONTROL3, 0, 1, 0), + SOC_SINGLE("AK4497 Invering Enable of DZF", AK4497_02_CONTROL3, + 4, 1, 0), + SOC_SINGLE("AK4497 Mono Mode", AK4497_02_CONTROL3, 3, 1, 0), + SOC_SINGLE("AK4497 Super Slow Roll-off Filter", AK4497_05_CONTROL4, + 0, 1, 0), + SOC_SINGLE("AK4497 AOUTR Phase Inverting", AK4497_05_CONTROL4, + 6, 1, 0), + SOC_SINGLE("AK4497 AOUTL Phase Inverting", AK4497_05_CONTROL4, + 7, 1, 0), + SOC_SINGLE("AK4497 DSD Mute Release", AK4497_06_DSD1, 3, 1, 0), + SOC_SINGLE("AK4497 DSD Mute Control Hold", AK4497_06_DSD1, 4, 1, 0), + SOC_SINGLE("AK4497 DSDR is detected", AK4497_06_DSD1, 5, 1, 0), + SOC_SINGLE("AK4497 DSDL is detected", AK4497_06_DSD1, 6, 1, 0), + SOC_SINGLE("AK4497 DSD Data Mute", AK4497_06_DSD1, 7, 1, 0), + SOC_SINGLE("AK4497 Synchronization Control", AK4497_07_CONTROL5, + 0, 1, 0), + + SOC_ENUM("AK4497 Output Level", ak4497_gain_control_enum), + SOC_SINGLE("AK4497 High Sonud Quality Mode", AK4497_08_SOUNDCONTROL, + 2, 1, 0), + SOC_SINGLE("AK4497 Heavy Load Mode", AK4497_08_SOUNDCONTROL, 3, 1, 0), + SOC_ENUM("AK4497 DSD Data Input Pin", ak4497_dac_enum[9]), + SOC_SINGLE("AK4497 Daisy Chain", AK4497_0B_CONTROL8, 1, 1, 0), + SOC_ENUM("AK4497 ATT Transit Time", ak4497_dac_enum[10]), + + SOC_ENUM_EXT("AK4497 Read FS Auto Detect Mode", ak4497_dac_enum2[3], + ak4497_get_adfs, ak4497_set_adfs), + +#ifdef AK4497_DEBUG + SOC_ENUM_EXT("Reg Read", ak4497_enum[0], get_test_reg, set_test_reg), +#endif + +}; + +static const char * const ak4497_dac_enable_texts[] = {"Off", "On"}; + +static SOC_ENUM_SINGLE_VIRT_DECL(ak4497_dac_enable_enum, + ak4497_dac_enable_texts); + +static const struct snd_kcontrol_new ak4497_dac_enable_control = + SOC_DAPM_ENUM("DAC Switch", ak4497_dac_enable_enum); + +/* ak4497 dapm widgets */ +static const struct snd_soc_dapm_widget ak4497_dapm_widgets[] = { + SND_SOC_DAPM_AIF_IN("AK4497 SDTI", "Playback", 0, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_DAC("AK4497 DAC", NULL, AK4497_0A_CONTROL7, 2, 0), + + SND_SOC_DAPM_MUX("AK4497 DAC Enable", SND_SOC_NOPM, + 0, 0, &ak4497_dac_enable_control), + + SND_SOC_DAPM_OUTPUT("AK4497 AOUT"), + +}; + +static const struct snd_soc_dapm_route ak4497_intercon[] = { + {"AK4497 DAC", NULL, "AK4497 SDTI"}, + {"AK4497 DAC Enable", "On", "AK4497 DAC"}, + {"AK4497 AOUT", NULL, "AK4497 DAC Enable"}, +}; + +static int ak4497_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct ak4497_priv *ak4497 = snd_soc_codec_get_drvdata(codec); + snd_pcm_format_t pcm_format = params_format(params); + + unsigned int dfs, dfs2, dsdsel0, dsdsel1, format; + int nfs1; + bool is_dsd = false; + int dsd_bclk; + + if (pcm_format == SNDRV_PCM_FORMAT_DSD_U8 || + pcm_format == SNDRV_PCM_FORMAT_DSD_U16_LE || + pcm_format == SNDRV_PCM_FORMAT_DSD_U16_BE || + pcm_format == SNDRV_PCM_FORMAT_DSD_U32_LE || + pcm_format == SNDRV_PCM_FORMAT_DSD_U32_BE) + is_dsd = true; + + nfs1 = params_rate(params); + ak4497->fs1 = nfs1; + + ak4497_read(codec, AK4497_01_CONTROL2, &dfs); + dfs &= ~AK4497_DFS; + + ak4497_read(codec, AK4497_05_CONTROL4, &dfs2); + dfs2 &= ~AK4497_DFS2; + + ak4497_read(codec, AK4497_06_DSD1, &dsdsel0); + dsdsel0 &= ~AK4497_DSDSEL0; + + ak4497_read(codec, AK4497_09_DSD2, &dsdsel1); + dsdsel1 &= ~AK4497_DSDSEL1; + + if (!is_dsd) { + switch (nfs1) { + case 8000: + case 11025: + case 16000: + case 22050: + case 32000: + case 44100: + case 48000: + dfs |= AK4497_DFS_48KHZ; + dfs2 |= AK4497_DFS2_48KHZ; + break; + case 88200: + case 96000: + dfs |= AK4497_DFS_96KHZ; + dfs2 |= AK4497_DFS2_48KHZ; + break; + case 176400: + case 192000: + dfs |= AK4497_DFS_192KHZ; + dfs2 |= AK4497_DFS2_48KHZ; + break; + case 352800: + case 384000: + dfs |= AK4497_DFS_384KHZ; + dfs2 |= AK4497_DFS2_384KHZ; + break; + case 705600: + case 768000: + dfs |= AK4497_DFS_768KHZ; + dfs2 |= AK4497_DFS2_384KHZ; + break; + default: + return -EINVAL; + } + } else { + dsd_bclk = params_rate(params) * + params_physical_width(params); + + switch (dsd_bclk) { + case 2822400: + dsdsel0 |= AK4497_DSDSEL0_2MHZ; + dsdsel1 |= AK4497_DSDSEL1_2MHZ; + break; + case 5644800: + dsdsel0 |= AK4497_DSDSEL0_5MHZ; + dsdsel1 |= AK4497_DSDSEL1_5MHZ; + break; + case 11289600: + dsdsel0 |= AK4497_DSDSEL0_11MHZ; + dsdsel1 |= AK4497_DSDSEL1_11MHZ; + break; + case 22579200: + dsdsel0 |= AK4497_DSDSEL0_22MHZ; + dsdsel1 |= AK4497_DSDSEL1_22MHZ; + break; + default: + return -EINVAL; + } + + snd_soc_write(codec, AK4497_06_DSD1, dsdsel0); + snd_soc_write(codec, AK4497_09_DSD2, dsdsel1); + } + + snd_soc_write(codec, AK4497_01_CONTROL2, dfs); + snd_soc_write(codec, AK4497_05_CONTROL4, dfs2); + + ak4497_read(codec, AK4497_00_CONTROL1, &format); + format &= ~AK4497_DIF; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + if (ak4497->fmt == SND_SOC_DAIFMT_I2S) + format |= AK4497_DIF_24BIT_I2S; + else + format |= AK4497_DIF_16BIT_LSB; + break; + case SNDRV_PCM_FORMAT_S24_LE: + case SNDRV_PCM_FORMAT_S32_LE: + if (ak4497->fmt == SND_SOC_DAIFMT_I2S) + format |= AK4497_DIF_32BIT_I2S; + else if (ak4497->fmt == SND_SOC_DAIFMT_LEFT_J) + format |= AK4497_DIF_32BIT_MSB; + else if (ak4497->fmt == SND_SOC_DAIFMT_RIGHT_J) + format |= AK4497_DIF_32BIT_LSB; + else + return -EINVAL; + break; + case SNDRV_PCM_FORMAT_DSD_U8: + case SNDRV_PCM_FORMAT_DSD_U16_LE: + case SNDRV_PCM_FORMAT_DSD_U32_LE: + break; + default: + return -EINVAL; + } + + snd_soc_write(codec, AK4497_00_CONTROL1, format); + + return 0; +} + +static int ak4497_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ +// struct snd_soc_codec *codec = dai->codec; + + return 0; +} + +static int ak4497_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = dai->codec; + struct ak4497_priv *ak4497 = snd_soc_codec_get_drvdata(codec); + unsigned int format, format2; + + /* set master/slave audio interface */ + ak4497_read(codec, AK4497_00_CONTROL1, &format); + format &= ~AK4497_DIF; + + ak4497_read(codec, AK4497_02_CONTROL3, &format2); + format2 &= ~AK4497_DIF_DSD; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + case SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_CBS_CFM: + case SND_SOC_DAIFMT_CBM_CFS: + default: + dev_err(codec->dev, "Clock mode unsupported"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + ak4497->fmt = SND_SOC_DAIFMT_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + ak4497->fmt = SND_SOC_DAIFMT_LEFT_J; + break; + case SND_SOC_DAIFMT_RIGHT_J: + ak4497->fmt = SND_SOC_DAIFMT_RIGHT_J; + break; + case SND_SOC_DAIFMT_PDM: + format2 |= AK4497_DIF_DSD_MODE; + break; + default: + return -EINVAL; + } + + /* set format */ + snd_soc_write(codec, AK4497_00_CONTROL1, format); + snd_soc_write(codec, AK4497_02_CONTROL3, format2); + + return 0; +} + +static bool ak4497_volatile(struct device *dev, unsigned int reg) +{ + int ret; + +#ifdef AK4497_DEBUG + ret = 1; +#else + switch (reg) { + case AK4497_15_DFSREAD: + ret = 1; + break; + default: + ret = 0; + break; + } +#endif + return ret; +} + +static int ak4497_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + case SND_SOC_BIAS_STANDBY: + /* RSTN bit = 1 */ + snd_soc_update_bits(codec, AK4497_00_CONTROL1, 0x01, 0x01); + break; + case SND_SOC_BIAS_OFF: + /* RSTN bit = 0 */ + snd_soc_update_bits(codec, AK4497_00_CONTROL1, 0x01, 0x00); + break; + } + + return 0; +} + +static int ak4497_set_dai_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + struct ak4497_priv *ak4497 = snd_soc_codec_get_drvdata(codec); + int nfs, ndt; + + nfs = ak4497->fs1; + + if (mute) { /* SMUTE: 1 , MUTE */ + snd_soc_update_bits(codec, AK4497_01_CONTROL2, 0x01, 0x01); + ndt = 7424000 / nfs; + mdelay(ndt); + + /* External Mute ON */ + if (gpio_is_valid(ak4497->mute_gpio)) + gpio_set_value_cansleep(ak4497->mute_gpio, 1); + } else { /* SMUTE: 0, NORMAL operation */ + + /* External Mute OFF */ + if (gpio_is_valid(ak4497->mute_gpio)) + gpio_set_value_cansleep(ak4497->mute_gpio, 0); + snd_soc_update_bits(codec, AK4497_01_CONTROL2, 0x01, 0x00); + } + + return 0; +} + +#define AK4497_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |\ + SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 |\ + SNDRV_PCM_RATE_192000) + +#define AK4497_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FMTBIT_S32_LE |\ + SNDRV_PCM_FMTBIT_DSD_U8 |\ + SNDRV_PCM_FMTBIT_DSD_U16_LE |\ + SNDRV_PCM_FMTBIT_DSD_U32_LE) + +static const unsigned int ak4497_rates[] = { + 8000, 11025, 16000, 22050, + 32000, 44100, 48000, 88200, + 96000, 176400, 192000, 352800, + 384000, 705600, 768000, 1411200, + 2822400, +}; + +static const struct snd_pcm_hw_constraint_list ak4497_rate_constraints = { + .count = ARRAY_SIZE(ak4497_rates), + .list = ak4497_rates, +}; + +static int ak4497_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) { + int ret; + + ret = snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &ak4497_rate_constraints); + + return ret; +} + +static struct snd_soc_dai_ops ak4497_dai_ops = { + .startup = ak4497_startup, + .hw_params = ak4497_hw_params, + .set_sysclk = ak4497_set_dai_sysclk, + .set_fmt = ak4497_set_dai_fmt, + .digital_mute = ak4497_set_dai_mute, +}; + +struct snd_soc_dai_driver ak4497_dai[] = { + { + .name = "ak4497-aif", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = AK4497_FORMATS, + }, + .ops = &ak4497_dai_ops, + }, +}; + +static int ak4497_init_reg(struct snd_soc_codec *codec) +{ + struct ak4497_priv *ak4497 = snd_soc_codec_get_drvdata(codec); + int ret = 0; + + /* External Mute ON */ + if (gpio_is_valid(ak4497->mute_gpio)) + gpio_set_value_cansleep(ak4497->mute_gpio, 1); + + if (gpio_is_valid(ak4497->pdn_gpio)) { + gpio_set_value_cansleep(ak4497->pdn_gpio, 0); + usleep_range(1000, 2000); + gpio_set_value_cansleep(ak4497->pdn_gpio, 1); + usleep_range(1000, 2000); + } + + /* ak4497_set_bias_level(codec, SND_SOC_BIAS_STANDBY); */ + + /* SYNCE bit = 1 */ + ret = snd_soc_update_bits(codec, AK4497_07_CONTROL5, 0x01, 0x01); + if (ret) + return ret; + + /* HLOAD bit = 1, SC2 bit = 1 */ + ret = snd_soc_update_bits(codec, AK4497_08_SOUNDCONTROL, 0x0F, 0x0C); + if (ret) + return ret; + + return ret; +} + +static int ak4497_parse_dt(struct ak4497_priv *ak4497) +{ + struct device *dev; + struct device_node *np; + + dev = &(ak4497->i2c->dev); + np = dev->of_node; + + ak4497->pdn_gpio = -1; + ak4497->mute_gpio = -1; + + if (!np) + return -1; + + ak4497->pdn_gpio = of_get_named_gpio(np, "ak4497,pdn-gpio", 0); + if (ak4497->pdn_gpio < 0) + ak4497->pdn_gpio = -1; + + if (!gpio_is_valid(ak4497->pdn_gpio)) { + dev_err(dev, "ak4497 pdn pin(%u) is invalid\n", + ak4497->pdn_gpio); + ak4497->pdn_gpio = -1; + } + + ak4497->mute_gpio = of_get_named_gpio(np, "ak4497,mute-gpio", 0); + if (ak4497->mute_gpio < 0) + ak4497->mute_gpio = -1; + + if (!gpio_is_valid(ak4497->mute_gpio)) { + dev_err(dev, "ak4497 mute_gpio(%u) is invalid\n", + ak4497->mute_gpio); + ak4497->mute_gpio = -1; + } + + return 0; +} + +static int ak4497_probe(struct snd_soc_codec *codec) +{ + struct ak4497_priv *ak4497 = snd_soc_codec_get_drvdata(codec); + int ret = 0; + + ret = ak4497_parse_dt(ak4497); + if (ret) + return ret; + + if (gpio_is_valid(ak4497->pdn_gpio)) { + ret = gpio_request(ak4497->pdn_gpio, "ak4497 pdn"); + if (ret) + return ret; + gpio_direction_output(ak4497->pdn_gpio, 0); + } + if (gpio_is_valid(ak4497->mute_gpio)) { + ret = gpio_request(ak4497->mute_gpio, "ak4497 mute"); + if (ret) + return ret; + gpio_direction_output(ak4497->mute_gpio, 0); + } + + ret = ak4497_init_reg(codec); + if (ret) + return ret; + + ak4497->fs1 = 48000; + ak4497->nBickFreq = 1; + ak4497->nTdmSds = 0; + + return ret; +} + +static int ak4497_remove(struct snd_soc_codec *codec) +{ + struct ak4497_priv *ak4497 = snd_soc_codec_get_drvdata(codec); + + ak4497_set_bias_level(codec, SND_SOC_BIAS_OFF); + if (gpio_is_valid(ak4497->pdn_gpio)) { + gpio_set_value_cansleep(ak4497->pdn_gpio, 0); + gpio_free(ak4497->pdn_gpio); + } + if (gpio_is_valid(ak4497->mute_gpio)) + gpio_free(ak4497->mute_gpio); + + return 0; +} + +#ifdef CONFIG_PM +static int ak4497_runtime_suspend(struct device *dev) +{ + struct ak4497_priv *ak4497 = dev_get_drvdata(dev); + + regcache_cache_only(ak4497->regmap, true); + + if (gpio_is_valid(ak4497->pdn_gpio)) { + gpio_set_value_cansleep(ak4497->pdn_gpio, 0); + usleep_range(1000, 2000); + } + + if (gpio_is_valid(ak4497->mute_gpio)) + gpio_free(ak4497->mute_gpio); + + return 0; +} + +static int ak4497_runtime_resume(struct device *dev) +{ + struct ak4497_priv *ak4497 = dev_get_drvdata(dev); + + /* External Mute ON */ + if (gpio_is_valid(ak4497->mute_gpio)) + gpio_set_value_cansleep(ak4497->mute_gpio, 1); + + if (gpio_is_valid(ak4497->pdn_gpio)) { + gpio_set_value_cansleep(ak4497->pdn_gpio, 1); + usleep_range(1000, 2000); + } + + regcache_cache_only(ak4497->regmap, false); + regcache_mark_dirty(ak4497->regmap); + + return regcache_sync(ak4497->regmap); +} +#endif /* CONFIG_PM */ + +static const struct dev_pm_ops ak4497_pm = { + SET_RUNTIME_PM_OPS(ak4497_runtime_suspend, ak4497_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) +}; + +struct snd_soc_codec_driver soc_codec_dev_ak4497 = { + .probe = ak4497_probe, + .remove = ak4497_remove, + + .idle_bias_off = true, + .set_bias_level = ak4497_set_bias_level, + + .component_driver = { + .controls = ak4497_snd_controls, + .num_controls = ARRAY_SIZE(ak4497_snd_controls), + .dapm_widgets = ak4497_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(ak4497_dapm_widgets), + .dapm_routes = ak4497_intercon, + .num_dapm_routes = ARRAY_SIZE(ak4497_intercon), + }, +}; + +static const struct regmap_config ak4497_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = AK4497_MAX_REGISTERS, + .volatile_reg = ak4497_volatile, + + .reg_defaults = ak4497_reg, + .num_reg_defaults = ARRAY_SIZE(ak4497_reg), + .cache_type = REGCACHE_RBTREE, +}; + +static int ak4497_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct ak4497_priv *ak4497; + int ret = 0; + int i; + + ak4497 = devm_kzalloc(&i2c->dev, + sizeof(struct ak4497_priv), GFP_KERNEL); + if (ak4497 == NULL) + return -ENOMEM; + + ak4497->regmap = devm_regmap_init_i2c(i2c, &ak4497_regmap); + if (IS_ERR(ak4497->regmap)) + return PTR_ERR(ak4497->regmap); + + i2c_set_clientdata(i2c, ak4497); + ak4497->i2c = i2c; + + for (i = 0; i < ARRAY_SIZE(ak4497->supplies); i++) + ak4497->supplies[i].supply = ak4497_supply_names[i]; + + ret = devm_regulator_bulk_get(&i2c->dev, ARRAY_SIZE(ak4497->supplies), + ak4497->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to request supplies: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(ak4497->supplies), + ak4497->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_ak4497, + &ak4497_dai[0], ARRAY_SIZE(ak4497_dai)); + if (ret < 0) + return ret; + + pm_runtime_enable(&i2c->dev); + + return 0; +} + +static int ak4497_i2c_remove(struct i2c_client *client) +{ + snd_soc_unregister_codec(&client->dev); + pm_runtime_disable(&client->dev); + + return 0; +} + +static const struct of_device_id ak4497_i2c_dt_ids[] = { + { .compatible = "asahi-kasei,ak4497"}, + { } +}; + +static const struct i2c_device_id ak4497_i2c_id[] = { + { "ak4497", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ak4497_i2c_id); + +static struct i2c_driver ak4497_i2c_driver = { + .driver = { + .name = "ak4497", + .of_match_table = of_match_ptr(ak4497_i2c_dt_ids), + .pm = &ak4497_pm, + }, + .probe = ak4497_i2c_probe, + .remove = ak4497_i2c_remove, + .id_table = ak4497_i2c_id, +}; + +module_i2c_driver(ak4497_i2c_driver); + +MODULE_AUTHOR("Junichi Wakasugi <wakasugi.jb@om.asahi-kasei.co.jp>"); +MODULE_AUTHOR("Daniel Baluta <daniel.baluta@nxp.com>"); +MODULE_DESCRIPTION("ASoC ak4497 codec driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ak4497.h b/sound/soc/codecs/ak4497.h new file mode 100644 index 000000000000..3ba6762e5ddc --- /dev/null +++ b/sound/soc/codecs/ak4497.h @@ -0,0 +1,90 @@ +/* + * ak4497.h -- audio driver for ak4497 + * + * Copyright (C) 2016 Asahi Kasei Microdevices Corporation + * Copyright (C) 2017, NXP + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ +#ifndef _AK4497_H +#define _AK4497_H + +#define AK4497_00_CONTROL1 0x00 +#define AK4497_01_CONTROL2 0x01 +#define AK4497_02_CONTROL3 0x02 +#define AK4497_03_LCHATT 0x03 +#define AK4497_04_RCHATT 0x04 +#define AK4497_05_CONTROL4 0x05 +#define AK4497_06_DSD1 0x06 +#define AK4497_07_CONTROL5 0x07 +#define AK4497_08_SOUNDCONTROL 0x08 +#define AK4497_09_DSD2 0x09 +#define AK4497_0A_CONTROL7 0x0A +#define AK4497_0B_CONTROL8 0x0B +#define AK4497_0C_RESERVED 0x0C +#define AK4497_0D_RESERVED 0x0D +#define AK4497_0E_RESERVED 0x0E +#define AK4497_0F_RESERVED 0x0F +#define AK4497_10_RESERVED 0x10 +#define AK4497_11_RESERVED 0x11 +#define AK4497_12_RESERVED 0x12 +#define AK4497_13_RESERVED 0x13 +#define AK4497_14_RESERVED 0x14 +#define AK4497_15_DFSREAD 0x15 + + +#define AK4497_MAX_REGISTERS (AK4497_15_DFSREAD) + +/* Bitfield Definitions */ + +/* AK4497_00_CONTROL1 (0x00) Fields */ +#define AK4497_DIF 0x0E +#define AK4497_DIF_MSB_MODE (2 << 1) +#define AK4497_DIF_I2S_MODE (3 << 1) +#define AK4497_DIF_32BIT_MODE (4 << 1) + +#define AK4497_DIF_16BIT_LSB (0 << 1) +#define AK4497_DIF_20BIT_LSB (1 << 1) +#define AK4497_DIF_24BIT_MSB (2 << 1) +#define AK4497_DIF_24BIT_I2S (3 << 1) +#define AK4497_DIF_24BIT_LSB (4 << 1) +#define AK4497_DIF_32BIT_LSB (5 << 1) +#define AK4497_DIF_32BIT_MSB (6 << 1) +#define AK4497_DIF_32BIT_I2S (7 << 1) + +/* AK4497_02_CONTROL3 (0x02) Fields */ +#define AK4497_DIF_DSD 0x80 +#define AK4497_DIF_DSD_MODE (1 << 7) + + +/* AK4497_01_CONTROL2 (0x01) Fields */ +/* AK4497_05_CONTROL4 (0x05) Fields */ +#define AK4497_DFS 0x18 +#define AK4497_DFS_48KHZ (0x0 << 3) // 30kHz to 54kHz +#define AK4497_DFS_96KHZ (0x1 << 3) // 54kHz to 108kHz +#define AK4497_DFS_192KHZ (0x2 << 3) // 120kHz to 216kHz +#define AK4497_DFS_384KHZ (0x0 << 3) +#define AK4497_DFS_768KHZ (0x1 << 3) + +#define AK4497_DFS2 0x2 +#define AK4497_DFS2_48KHZ (0x0 << 1) // 30kHz to 216kHz +#define AK4497_DFS2_384KHZ (0x1 << 1) // 384kHz, 768kHz to 108kHz + + +#define AK4497_DSDSEL0 0x1 +#define AK4497_DSDSEL0_2MHZ 0x0 +#define AK4497_DSDSEL0_5MHZ 0x1 +#define AK4497_DSDSEL0_11MHZ 0x0 +#define AK4497_DSDSEL0_22MHZ 0x1 + +#define AK4497_DSDSEL1 0x1 +#define AK4497_DSDSEL1_2MHZ 0x0 +#define AK4497_DSDSEL1_5MHZ 0x0 +#define AK4497_DSDSEL1_11MHZ 0x1 +#define AK4497_DSDSEL1_22MHZ 0x1 + +#endif diff --git a/sound/soc/codecs/ak5558.c b/sound/soc/codecs/ak5558.c new file mode 100644 index 000000000000..4218178a6c47 --- /dev/null +++ b/sound/soc/codecs/ak5558.c @@ -0,0 +1,844 @@ +/* + * ak5558.c -- audio driver for AK5558 ADC + * + * Copyright (C) 2015 Asahi Kasei Microdevices Corporation + * Copyright 2017 NXP + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/gpio/consumer.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/tlv.h> +#include <linux/of_gpio.h> +#include <linux/regmap.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> + +#include "ak5558.h" + +#define AK5558_SLAVE_CKS_AUTO + +/* enable debug */ +/* #define AK5558_DEBUG */ + +#define AK5558_NUM_SUPPLIES 2 +static const char *ak5558_supply_names[AK5558_NUM_SUPPLIES] = { + "DVDD", + "AVDD", +}; + +/* AK5558 Codec Private Data */ +struct ak5558_priv { + struct snd_soc_codec codec; + struct regmap *regmap; + struct i2c_client *i2c; + int fs; /* Sampling Frequency */ + int rclk; /* Master Clock */ + int pdn_gpio; /* Power on / Reset GPIO */ + int slots; + int slot_width; + struct regulator_bulk_data supplies[AK5558_NUM_SUPPLIES]; +}; + +/* ak5558 register cache & default register settings */ +static const struct reg_default ak5558_reg[] = { + { 0x0, 0xFF }, /* 0x00 AK5558_00_POWER_MANAGEMENT1 */ + { 0x1, 0x01 }, /* 0x01 AK5558_01_POWER_MANAGEMENT2 */ + { 0x2, 0x01 }, /* 0x02 AK5558_02_CONTROL1 */ + { 0x3, 0x00 }, /* 0x03 AK5558_03_CONTROL2 */ + { 0x4, 0x00 }, /* 0x04 AK5558_04_CONTROL3 */ + { 0x5, 0x00 } /* 0x05 AK5558_05_DSD */ +}; + +static const char * const mono_texts[] = { + "8 Slot", "2 Slot", "4 Slot", "1 Slot", +}; + +static const struct soc_enum ak5558_mono_enum[] = { + SOC_ENUM_SINGLE(AK5558_01_POWER_MANAGEMENT2, 1, + ARRAY_SIZE(mono_texts), mono_texts) +}; + +static const char * const tdm_texts[] = { + "Off", "TDM128", "TDM256", "TDM512", +}; + +static const char * const digfil_texts[] = { + "Sharp Roll-Off", "Show Roll-Off", + "Short Delay Sharp Roll-Off", "Short Delay Show Roll-Off", +}; + +static const struct soc_enum ak5558_adcset_enum[] = { + SOC_ENUM_SINGLE(AK5558_03_CONTROL2, 5, + ARRAY_SIZE(tdm_texts), tdm_texts), + SOC_ENUM_SINGLE(AK5558_04_CONTROL3, 0, + ARRAY_SIZE(digfil_texts), digfil_texts), +}; + +static const char * const dsdon_texts[] = { + "PCM", "DSD", +}; + +static const char * const dsdsel_texts[] = { + "64fs", "128fs", "256fs" +}; + +static const char * const dckb_texts[] = { + "Falling", "Rising", +}; + +static const char * const dcks_texts[] = { + "512fs", "768fs", +}; + +static const struct soc_enum ak5558_dsdset_enum[] = { + SOC_ENUM_SINGLE(AK5558_04_CONTROL3, 7, + ARRAY_SIZE(dsdon_texts), dsdon_texts), + SOC_ENUM_SINGLE(AK5558_05_DSD, 0, + ARRAY_SIZE(dsdsel_texts), dsdsel_texts), + SOC_ENUM_SINGLE(AK5558_05_DSD, 2, ARRAY_SIZE(dckb_texts), dckb_texts), + SOC_ENUM_SINGLE(AK5558_05_DSD, 5, ARRAY_SIZE(dcks_texts), dcks_texts), +}; + +#ifdef AK5558_DEBUG +static const char * const test_reg_select[] = { + "read AK5558 Reg 00:05", +}; + +static const struct soc_enum ak5558_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(test_reg_select), test_reg_select), +}; + +static int nTestRegNo; + +static int get_test_reg(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = nTestRegNo; + + return 0; +} + +static int set_test_reg(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + u32 currMode = ucontrol->value.enumerated.item[0]; + int i, value; + int regs, rege; + + nTestRegNo = currMode; + + regs = 0x00; + rege = 0x05; + + for (i = regs; i <= rege; i++) { + value = snd_soc_read(codec, i); + pr_info("***AK5558 Addr,Reg=(%x, %x)\n", i, value); + } + + return 0; +} + +#endif + +static const struct snd_kcontrol_new ak5558_snd_controls[] = { + SOC_ENUM("AK5558 Monaural Mode", ak5558_mono_enum[0]), + SOC_ENUM("AK5558 TDM mode", ak5558_adcset_enum[0]), + SOC_ENUM("AK5558 Digital Filter", ak5558_adcset_enum[1]), + + SOC_ENUM("AK5558 DSD Mode", ak5558_dsdset_enum[0]), + SOC_ENUM("AK5558 Frequency of DCLK", ak5558_dsdset_enum[1]), + SOC_ENUM("AK5558 Polarity of DCLK", ak5558_dsdset_enum[2]), + SOC_ENUM("AK5558 Master Clock Frequency at DSD Mode", + ak5558_dsdset_enum[3]), + + SOC_SINGLE("AK5558 DSD Phase Modulation", AK5558_05_DSD, 3, 1, 0), + +#ifdef AK5558_DEBUG + SOC_ENUM_EXT("AK5558 Reg Read", ak5558_enum[0], + get_test_reg, set_test_reg), +#endif + +}; + +static const char * const ak5558_channel_select_texts[] = {"Off", "On"}; + +static SOC_ENUM_SINGLE_VIRT_DECL(ak5558_channel1_mux_enum, + ak5558_channel_select_texts); + +static const struct snd_kcontrol_new ak5558_channel1_mux_control = + SOC_DAPM_ENUM("Ch1 Switch", ak5558_channel1_mux_enum); + +static SOC_ENUM_SINGLE_VIRT_DECL(ak5558_channel2_mux_enum, + ak5558_channel_select_texts); + +static const struct snd_kcontrol_new ak5558_channel2_mux_control = + SOC_DAPM_ENUM("Ch2 Switch", ak5558_channel2_mux_enum); + +static SOC_ENUM_SINGLE_VIRT_DECL(ak5558_channel3_mux_enum, + ak5558_channel_select_texts); + +static const struct snd_kcontrol_new ak5558_channel3_mux_control = + SOC_DAPM_ENUM("Ch3 Switch", ak5558_channel3_mux_enum); + +static SOC_ENUM_SINGLE_VIRT_DECL(ak5558_channel4_mux_enum, + ak5558_channel_select_texts); + +static const struct snd_kcontrol_new ak5558_channel4_mux_control = + SOC_DAPM_ENUM("Ch4 Switch", ak5558_channel4_mux_enum); + +static SOC_ENUM_SINGLE_VIRT_DECL(ak5558_channel5_mux_enum, + ak5558_channel_select_texts); + +static const struct snd_kcontrol_new ak5558_channel5_mux_control = + SOC_DAPM_ENUM("Ch5 Switch", ak5558_channel5_mux_enum); + +static SOC_ENUM_SINGLE_VIRT_DECL(ak5558_channel6_mux_enum, + ak5558_channel_select_texts); + +static const struct snd_kcontrol_new ak5558_channel6_mux_control = + SOC_DAPM_ENUM("Ch6 Switch", ak5558_channel6_mux_enum); + +static SOC_ENUM_SINGLE_VIRT_DECL(ak5558_channel7_mux_enum, + ak5558_channel_select_texts); + +static const struct snd_kcontrol_new ak5558_channel7_mux_control = + SOC_DAPM_ENUM("Ch7 Switch", ak5558_channel7_mux_enum); + +static SOC_ENUM_SINGLE_VIRT_DECL(ak5558_channel8_mux_enum, + ak5558_channel_select_texts); + +static const struct snd_kcontrol_new ak5558_channel8_mux_control = + SOC_DAPM_ENUM("Ch8 Switch", ak5558_channel8_mux_enum); + +static const struct snd_soc_dapm_widget ak5558_dapm_widgets[] = { + + /* Analog Input */ + SND_SOC_DAPM_INPUT("AIN1"), + SND_SOC_DAPM_INPUT("AIN2"), + SND_SOC_DAPM_INPUT("AIN3"), + SND_SOC_DAPM_INPUT("AIN4"), + SND_SOC_DAPM_INPUT("AIN5"), + SND_SOC_DAPM_INPUT("AIN6"), + SND_SOC_DAPM_INPUT("AIN7"), + SND_SOC_DAPM_INPUT("AIN8"), + + SND_SOC_DAPM_MUX("AK5558 Ch1 Enable", SND_SOC_NOPM, 0, 0, + &ak5558_channel1_mux_control), + SND_SOC_DAPM_MUX("AK5558 Ch2 Enable", SND_SOC_NOPM, 0, 0, + &ak5558_channel2_mux_control), + SND_SOC_DAPM_MUX("AK5558 Ch3 Enable", SND_SOC_NOPM, 0, 0, + &ak5558_channel3_mux_control), + SND_SOC_DAPM_MUX("AK5558 Ch4 Enable", SND_SOC_NOPM, 0, 0, + &ak5558_channel4_mux_control), + SND_SOC_DAPM_MUX("AK5558 Ch5 Enable", SND_SOC_NOPM, 0, 0, + &ak5558_channel5_mux_control), + SND_SOC_DAPM_MUX("AK5558 Ch6 Enable", SND_SOC_NOPM, 0, 0, + &ak5558_channel6_mux_control), + SND_SOC_DAPM_MUX("AK5558 Ch7 Enable", SND_SOC_NOPM, 0, 0, + &ak5558_channel7_mux_control), + SND_SOC_DAPM_MUX("AK5558 Ch8 Enable", SND_SOC_NOPM, 0, 0, + &ak5558_channel8_mux_control), + + SND_SOC_DAPM_ADC("ADC Ch1", NULL, AK5558_00_POWER_MANAGEMENT1, 0, 0), + SND_SOC_DAPM_ADC("ADC Ch2", NULL, AK5558_00_POWER_MANAGEMENT1, 1, 0), + SND_SOC_DAPM_ADC("ADC Ch3", NULL, AK5558_00_POWER_MANAGEMENT1, 2, 0), + SND_SOC_DAPM_ADC("ADC Ch4", NULL, AK5558_00_POWER_MANAGEMENT1, 3, 0), + SND_SOC_DAPM_ADC("ADC Ch5", NULL, AK5558_00_POWER_MANAGEMENT1, 4, 0), + SND_SOC_DAPM_ADC("ADC Ch6", NULL, AK5558_00_POWER_MANAGEMENT1, 5, 0), + SND_SOC_DAPM_ADC("ADC Ch7", NULL, AK5558_00_POWER_MANAGEMENT1, 6, 0), + SND_SOC_DAPM_ADC("ADC Ch8", NULL, AK5558_00_POWER_MANAGEMENT1, 7, 0), + + SND_SOC_DAPM_AIF_OUT("SDTO", "Capture", 0, SND_SOC_NOPM, 0, 0), + +}; + +static const struct snd_soc_dapm_route ak5558_intercon[] = { + + {"AK5558 Ch1 Enable", "On", "AIN1"}, + {"ADC Ch1", NULL, "AK5558 Ch1 Enable"}, + {"SDTO", NULL, "ADC Ch1"}, + + {"AK5558 Ch2 Enable", "On", "AIN2"}, + {"ADC Ch2", NULL, "AK5558 Ch2 Enable"}, + {"SDTO", NULL, "ADC Ch2"}, + + {"AK5558 Ch3 Enable", "On", "AIN3"}, + {"ADC Ch3", NULL, "AK5558 Ch3 Enable"}, + {"SDTO", NULL, "ADC Ch3"}, + + {"AK5558 Ch4 Enable", "On", "AIN4"}, + {"ADC Ch4", NULL, "AK5558 Ch4 Enable"}, + {"SDTO", NULL, "ADC Ch4"}, + + {"AK5558 Ch5 Enable", "On", "AIN5"}, + {"ADC Ch5", NULL, "AK5558 Ch5 Enable"}, + {"SDTO", NULL, "ADC Ch5"}, + + {"AK5558 Ch6 Enable", "On", "AIN6"}, + {"ADC Ch6", NULL, "AK5558 Ch6 Enable"}, + {"SDTO", NULL, "ADC Ch6"}, + + {"AK5558 Ch7 Enable", "On", "AIN7"}, + {"ADC Ch7", NULL, "AK5558 Ch7 Enable"}, + {"SDTO", NULL, "ADC Ch7"}, + + {"AK5558 Ch8 Enable", "On", "AIN8"}, + {"ADC Ch8", NULL, "AK5558 Ch8 Enable"}, + {"SDTO", NULL, "ADC Ch8"}, + +}; + +static int ak5558_set_mcki(struct snd_soc_codec *codec, int fs, int rclk) +{ + u8 mode; +#ifndef AK5558_SLAVE_CKS_AUTO + int mcki_rate; +#endif + + dev_dbg(codec->dev, "%s fs=%d rclk=%d\n", __func__, fs, rclk); + + mode = snd_soc_read(codec, AK5558_02_CONTROL1); + mode &= ~AK5558_CKS; + +#ifdef AK5558_SLAVE_CKS_AUTO + mode |= AK5558_CKS_AUTO; +#else + if (fs != 0 && rclk != 0) { + if (rclk % fs) + return -EINVAL; + mcki_rate = rclk / fs; + + if (fs > 400000) { + switch (mcki_rate) { + case 32: + mode |= AK5558_CKS_32FS_768KHZ; + break; + case 48: + mode |= AK5558_CKS_48FS_768KHZ; + break; + case 64: + mode |= AK5558_CKS_64FS_768KHZ; + break; + default: + return -EINVAL; + } + } else if (fs > 200000) { + switch (mcki_rate) { + case 64: + mode |= AK5558_CKS_64FS_384KHZ; + break; + case 96: + mode |= AK5558_CKS_96FS_384KHZ; + break; + default: + return -EINVAL; + } + } else if (fs > 108000) { + switch (mcki_rate) { + case 128: + mode |= AK5558_CKS_128FS_192KHZ; + break; + case 192: + mode |= AK5558_CKS_192FS_192KHZ; + break; + default: + return -EINVAL; + } + } else if (fs > 54000) { + switch (mcki_rate) { + case 256: + mode |= AK5558_CKS_256FS_96KHZ; + break; + case 384: + mode |= AK5558_CKS_384FS_96KHZ; + break; + default: + return -EINVAL; + } + } else { + switch (mcki_rate) { + case 256: + mode |= AK5558_CKS_256FS_48KHZ; + break; + case 384: + mode |= AK5558_CKS_384FS_48KHZ; + break; + case 512: + mode |= AK5558_CKS_512FS_48KHZ; + break; + case 768: + mode |= AK5558_CKS_768FS_48KHZ; + break; + case 1024: + if (fs > 32000) + return -EINVAL; + mode |= AK5558_CKS_1024FS_16KHZ; + break; + default: + return -EINVAL; + } + } + } +#endif + + snd_soc_update_bits(codec, AK5558_02_CONTROL1, AK5558_CKS, mode); + + return 0; +} + +static int ak5558_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct ak5558_priv *ak5558 = snd_soc_codec_get_drvdata(codec); + u8 bits; + int pcm_width = max(params_physical_width(params), ak5558->slot_width); + + dev_dbg(dai->dev, "%s(%d)\n", __func__, __LINE__); + + /* set master/slave audio interface */ + bits = snd_soc_read(codec, AK5558_02_CONTROL1); + bits &= ~AK5558_BITS; + + switch (pcm_width) { + case 16: + bits |= AK5558_DIF_24BIT_MODE; + break; + case 32: + bits |= AK5558_DIF_32BIT_MODE; + break; + default: + return -EINVAL; + } + + ak5558->fs = params_rate(params); + snd_soc_update_bits(codec, AK5558_02_CONTROL1, AK5558_BITS, bits); + + ak5558_set_mcki(codec, ak5558->fs, ak5558->rclk); + + return 0; +} + +static int ak5558_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = dai->codec; + struct ak5558_priv *ak5558 = snd_soc_codec_get_drvdata(codec); + + dev_dbg(dai->dev, "%s(%d)\n", __func__, __LINE__); + + ak5558->rclk = freq; + ak5558_set_mcki(codec, ak5558->fs, ak5558->rclk); + + return 0; +} + +static int ak5558_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + + struct snd_soc_codec *codec = dai->codec; + u8 format; + + dev_dbg(dai->dev, "%s(%d)\n", __func__, __LINE__); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + case SND_SOC_DAIFMT_CBM_CFM: +#ifdef AK5558_SLAVE_CKS_AUTO + break; +#else + return -EINVAL; +#endif + case SND_SOC_DAIFMT_CBS_CFM: + case SND_SOC_DAIFMT_CBM_CFS: + default: + dev_err(codec->dev, "Clock mode unsupported"); + return -EINVAL; + } + + /* set master/slave audio interface */ + format = snd_soc_read(codec, AK5558_02_CONTROL1); + format &= ~AK5558_DIF; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + format |= AK5558_DIF_I2S_MODE; + break; + case SND_SOC_DAIFMT_LEFT_J: + format |= AK5558_DIF_MSB_MODE; + break; + case SND_SOC_DAIFMT_DSP_B: + format |= AK5558_DIF_MSB_MODE; + break; + default: + return -EINVAL; + } + + snd_soc_update_bits(codec, + AK5558_02_CONTROL1, AK5558_DIF, format); + + return 0; +} + +static int ak5558_set_dai_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + struct ak5558_priv *ak5558 = snd_soc_codec_get_drvdata(codec); + int ndt; + + if (mute) { + ndt = 0; + if (ak5558->fs != 0) + ndt = 583000 / ak5558->fs; + if (ndt < 5) + ndt = 5; + msleep(ndt); + } + + return 0; +} + +static int ak5558_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + + dev_dbg(codec->dev, "%s bias level=%d\n", __func__, (int)level); + + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + case SND_SOC_BIAS_STANDBY: + break; + case SND_SOC_BIAS_OFF: + snd_soc_write(codec, AK5558_00_POWER_MANAGEMENT1, 0x00); + break; + } + return 0; +} + +static int ak5558_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, + int slot_width) +{ + struct snd_soc_codec *codec = dai->codec; + struct ak5558_priv *ak5558 = snd_soc_codec_get_drvdata(codec); + int tdm_mode = 0; + int reg; + + ak5558->slots = slots; + ak5558->slot_width = slot_width; + + switch (slots * slot_width) { + case 128: + tdm_mode = 1; + break; + case 256: + tdm_mode = 2; + break; + case 512: + tdm_mode = 3; + break; + default: + tdm_mode = 0; + break; + } + + reg = snd_soc_read(codec, AK5558_03_CONTROL2); + reg &= ~(0x3 << 5); + reg |= tdm_mode << 5; + snd_soc_write(codec, AK5558_03_CONTROL2, reg); + + return 0; +} + + + +#define AK5558_RATES SNDRV_PCM_RATE_8000_192000 + +#define AK5558_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) + +static const unsigned int ak5558_rates[] = { + 8000, 11025, 16000, 22050, + 32000, 44100, 48000, 88200, + 96000, 176400, 192000, 352800, + 384000, 705600, 768000, 1411200, + 2822400, +}; + +static const struct snd_pcm_hw_constraint_list ak5558_rate_constraints = { + .count = ARRAY_SIZE(ak5558_rates), + .list = ak5558_rates, +}; + +static int ak5558_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) { + int ret; + + ret = snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &ak5558_rate_constraints); + + return ret; +} + +static struct snd_soc_dai_ops ak5558_dai_ops = { + .startup = ak5558_startup, + .hw_params = ak5558_hw_params, + .set_sysclk = ak5558_set_dai_sysclk, + .set_fmt = ak5558_set_dai_fmt, + .digital_mute = ak5558_set_dai_mute, + .set_tdm_slot = ak5558_set_tdm_slot, +}; + +static struct snd_soc_dai_driver ak5558_dai = { + .name = "ak5558-aif", + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = AK5558_FORMATS, + }, + .ops = &ak5558_dai_ops, +}; + +static int ak5558_init_reg(struct snd_soc_codec *codec) +{ + int ret; + struct ak5558_priv *ak5558 = snd_soc_codec_get_drvdata(codec); + + dev_dbg(codec->dev, "%s(%d)\n", __func__, __LINE__); + + usleep_range(10000, 11000); + if (gpio_is_valid(ak5558->pdn_gpio)) { + gpio_set_value_cansleep(ak5558->pdn_gpio, 0); + usleep_range(1000, 2000); + gpio_set_value_cansleep(ak5558->pdn_gpio, 1); + usleep_range(1000, 2000); + } + + ret = snd_soc_write(codec, AK5558_00_POWER_MANAGEMENT1, 0x0); + if (ret < 0) + return ret; + + ret = snd_soc_update_bits(codec, AK5558_02_CONTROL1, AK5558_CKS, + AK5558_CKS_AUTO); + if (ret < 0) + return ret; + + return 0; +} + +static int ak5558_probe(struct snd_soc_codec *codec) +{ + struct ak5558_priv *ak5558 = snd_soc_codec_get_drvdata(codec); + int ret = 0; + + dev_dbg(codec->dev, "%s(%d)\n", __func__, __LINE__); + + ret = ak5558_init_reg(codec); + + ak5558->fs = 48000; + ak5558->rclk = 0; + + return ret; +} + +static int ak5558_remove(struct snd_soc_codec *codec) +{ + struct ak5558_priv *ak5558 = snd_soc_codec_get_drvdata(codec); + + ak5558_set_bias_level(codec, SND_SOC_BIAS_OFF); + + if (gpio_is_valid(ak5558->pdn_gpio)) { + gpio_set_value_cansleep(ak5558->pdn_gpio, 0); + usleep_range(1000, 2000); + } + + return 0; +} + +#ifdef CONFIG_PM +static int ak5558_runtime_suspend(struct device *dev) +{ + struct ak5558_priv *ak5558 = dev_get_drvdata(dev); + + regcache_cache_only(ak5558->regmap, true); + + if (gpio_is_valid(ak5558->pdn_gpio)) { + gpio_set_value_cansleep(ak5558->pdn_gpio, 0); + usleep_range(1000, 2000); + } + + return 0; +} + +static int ak5558_runtime_resume(struct device *dev) +{ + struct ak5558_priv *ak5558 = dev_get_drvdata(dev); + + if (gpio_is_valid(ak5558->pdn_gpio)) { + gpio_set_value_cansleep(ak5558->pdn_gpio, 0); + usleep_range(1000, 2000); + gpio_set_value_cansleep(ak5558->pdn_gpio, 1); + usleep_range(1000, 2000); + + } + + regcache_cache_only(ak5558->regmap, false); + regcache_mark_dirty(ak5558->regmap); + + return regcache_sync(ak5558->regmap); +} +#endif + +const struct dev_pm_ops ak5558_pm = { + SET_RUNTIME_PM_OPS(ak5558_runtime_suspend, ak5558_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) +}; + +struct snd_soc_codec_driver soc_codec_dev_ak5558 = { + .probe = ak5558_probe, + .remove = ak5558_remove, + .idle_bias_off = true, + .set_bias_level = ak5558_set_bias_level, + + .component_driver = { + .controls = ak5558_snd_controls, + .num_controls = ARRAY_SIZE(ak5558_snd_controls), + .dapm_widgets = ak5558_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(ak5558_dapm_widgets), + .dapm_routes = ak5558_intercon, + .num_dapm_routes = ARRAY_SIZE(ak5558_intercon), + }, +}; + +static const struct regmap_config ak5558_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = AK5558_05_DSD, + .reg_defaults = ak5558_reg, + .num_reg_defaults = ARRAY_SIZE(ak5558_reg), + .cache_type = REGCACHE_RBTREE, +}; + +static int ak5558_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct device_node *np = i2c->dev.of_node; + struct ak5558_priv *ak5558; + int ret = 0; + int i; + + dev_dbg(&i2c->dev, "%s(%d)\n", __func__, __LINE__); + + ak5558 = devm_kzalloc(&i2c->dev, sizeof(struct ak5558_priv), + GFP_KERNEL); + if (ak5558 == NULL) + return -ENOMEM; + + ak5558->regmap = devm_regmap_init_i2c(i2c, &ak5558_regmap); + if (IS_ERR(ak5558->regmap)) + return PTR_ERR(ak5558->regmap); + + i2c_set_clientdata(i2c, ak5558); + ak5558->i2c = i2c; + + ak5558->pdn_gpio = of_get_named_gpio(np, "ak5558,pdn-gpio", 0); + if (gpio_is_valid(ak5558->pdn_gpio)) { + ret = devm_gpio_request_one(&i2c->dev, ak5558->pdn_gpio, + GPIOF_OUT_INIT_LOW, "ak5558,pdn"); + if (ret) { + dev_err(&i2c->dev, "unable to get pdn gpio\n"); + return ret; + } + } + + for (i = 0; i < ARRAY_SIZE(ak5558->supplies); i++) + ak5558->supplies[i].supply = ak5558_supply_names[i]; + + ret = devm_regulator_bulk_get(&i2c->dev, ARRAY_SIZE(ak5558->supplies), + ak5558->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to request supplies: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(ak5558->supplies), + ak5558->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_ak5558, + &ak5558_dai, 1); + if (ret) + return ret; + + pm_runtime_enable(&i2c->dev); + + return 0; +} + +static int ak5558_i2c_remove(struct i2c_client *client) +{ + snd_soc_unregister_codec(&client->dev); + pm_runtime_disable(&client->dev); + + + return 0; +} + +static const struct of_device_id ak5558_i2c_dt_ids[] = { + { .compatible = "asahi-kasei,ak5558"}, + { } +}; + +static const struct i2c_device_id ak5558_i2c_id[] = { + { "ak5558", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ak5558_i2c_id); + +static struct i2c_driver ak5558_i2c_driver = { + .driver = { + .name = "ak5558", + .of_match_table = of_match_ptr(ak5558_i2c_dt_ids), + .pm = &ak5558_pm, + }, + .probe = ak5558_i2c_probe, + .remove = ak5558_i2c_remove, + .id_table = ak5558_i2c_id, +}; + +module_i2c_driver(ak5558_i2c_driver); + +MODULE_AUTHOR("Junichi Wakasugi <wakasugi.jb@om.asahi-kasei.co.jp>"); +MODULE_AUTHOR("Mihai Serban <mihai.serban@nxp.com>"); +MODULE_DESCRIPTION("ASoC AK5558 ADC driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ak5558.h b/sound/soc/codecs/ak5558.h new file mode 100644 index 000000000000..3cc030fd089d --- /dev/null +++ b/sound/soc/codecs/ak5558.h @@ -0,0 +1,55 @@ +/* + * ak5558.h -- audio driver for AK5558 + * + * Copyright (C) 2016 Asahi Kasei Microdevices Corporation + * Copyright 2017 NXP + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _AK5558_H +#define _AK5558_H + +#define AK5558_00_POWER_MANAGEMENT1 0x00 +#define AK5558_01_POWER_MANAGEMENT2 0x01 +#define AK5558_02_CONTROL1 0x02 +#define AK5558_03_CONTROL2 0x03 +#define AK5558_04_CONTROL3 0x04 +#define AK5558_05_DSD 0x05 + +/* Bitfield Definitions */ + +/* AK5558_02_CONTROL1 (0x03) Fields */ +#define AK5558_DIF 0x02 +#define AK5558_DIF_MSB_MODE (0 << 1) +#define AK5558_DIF_I2S_MODE (1 << 1) + +#define AK5558_BITS 0x04 +#define AK5558_DIF_24BIT_MODE (0 << 2) +#define AK5558_DIF_32BIT_MODE (1 << 2) + +#define AK5558_CKS 0x78 +#define AK5558_CKS_128FS_192KHZ (0 << 3) +#define AK5558_CKS_192FS_192KHZ (1 << 3) +#define AK5558_CKS_256FS_48KHZ (2 << 3) +#define AK5558_CKS_256FS_96KHZ (3 << 3) +#define AK5558_CKS_384FS_96KHZ (4 << 3) +#define AK5558_CKS_384FS_48KHZ (5 << 3) +#define AK5558_CKS_512FS_48KHZ (6 << 3) +#define AK5558_CKS_768FS_48KHZ (7 << 3) +#define AK5558_CKS_64FS_384KHZ (8 << 3) +#define AK5558_CKS_32FS_768KHZ (9 << 3) +#define AK5558_CKS_96FS_384KHZ (10 << 3) +#define AK5558_CKS_48FS_768KHZ (11 << 3) +#define AK5558_CKS_64FS_768KHZ (12 << 3) +#define AK5558_CKS_1024FS_16KHZ (13 << 3) +#define AK5558_CKS_AUTO (15 << 3) + +#endif diff --git a/sound/soc/codecs/cs42xx8.c b/sound/soc/codecs/cs42xx8.c index 462341fef5a9..7cbbe64e3509 100644 --- a/sound/soc/codecs/cs42xx8.c +++ b/sound/soc/codecs/cs42xx8.c @@ -1,7 +1,7 @@ /* * Cirrus Logic CS42448/CS42888 Audio CODEC Digital Audio Interface (DAI) driver * - * Copyright (C) 2014 Freescale Semiconductor, Inc. + * Copyright (C) 2014-2016 Freescale Semiconductor, Inc. * * Author: Nicolin Chen <Guangyu.Chen@freescale.com> * @@ -14,6 +14,7 @@ #include <linux/delay.h> #include <linux/module.h> #include <linux/of_device.h> +#include <linux/of_gpio.h> #include <linux/pm_runtime.h> #include <linux/regulator/consumer.h> #include <sound/pcm_params.h> @@ -32,8 +33,7 @@ static const char *const cs42xx8_supply_names[CS42XX8_NUM_SUPPLIES] = { #define CS42XX8_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S20_3LE | \ - SNDRV_PCM_FMTBIT_S24_LE | \ - SNDRV_PCM_FMTBIT_S32_LE) + SNDRV_PCM_FMTBIT_S24_LE) /* codec private data */ struct cs42xx8_priv { @@ -45,6 +45,8 @@ struct cs42xx8_priv { bool slave_mode; unsigned long sysclk; u32 tx_channels; + int rate[2]; + int reset_gpio; }; /* -127.5dB to 0dB with step of 0.5dB */ @@ -128,7 +130,6 @@ static const struct snd_soc_dapm_widget cs42xx8_dapm_widgets[] = { SND_SOC_DAPM_INPUT("AIN2L"), SND_SOC_DAPM_INPUT("AIN2R"), - SND_SOC_DAPM_SUPPLY("PWR", CS42XX8_PWRCTL, 0, 1, NULL, 0), }; static const struct snd_soc_dapm_widget cs42xx8_adc3_dapm_widgets[] = { @@ -142,53 +143,43 @@ static const struct snd_soc_dapm_route cs42xx8_dapm_routes[] = { /* Playback */ { "AOUT1L", NULL, "DAC1" }, { "AOUT1R", NULL, "DAC1" }, - { "DAC1", NULL, "PWR" }, { "AOUT2L", NULL, "DAC2" }, { "AOUT2R", NULL, "DAC2" }, - { "DAC2", NULL, "PWR" }, { "AOUT3L", NULL, "DAC3" }, { "AOUT3R", NULL, "DAC3" }, - { "DAC3", NULL, "PWR" }, { "AOUT4L", NULL, "DAC4" }, { "AOUT4R", NULL, "DAC4" }, - { "DAC4", NULL, "PWR" }, /* Capture */ { "ADC1", NULL, "AIN1L" }, { "ADC1", NULL, "AIN1R" }, - { "ADC1", NULL, "PWR" }, { "ADC2", NULL, "AIN2L" }, { "ADC2", NULL, "AIN2R" }, - { "ADC2", NULL, "PWR" }, }; static const struct snd_soc_dapm_route cs42xx8_adc3_dapm_routes[] = { /* Capture */ { "ADC3", NULL, "AIN3L" }, { "ADC3", NULL, "AIN3R" }, - { "ADC3", NULL, "PWR" }, }; struct cs42xx8_ratios { - unsigned int ratio; - unsigned char speed; - unsigned char mclk; + unsigned int mfreq; + unsigned int min_mclk; + unsigned int max_mclk; + unsigned int ratio[3]; }; static const struct cs42xx8_ratios cs42xx8_ratios[] = { - { 64, CS42XX8_FM_QUAD, CS42XX8_FUNCMOD_MFREQ_256(4) }, - { 96, CS42XX8_FM_QUAD, CS42XX8_FUNCMOD_MFREQ_384(4) }, - { 128, CS42XX8_FM_QUAD, CS42XX8_FUNCMOD_MFREQ_512(4) }, - { 192, CS42XX8_FM_QUAD, CS42XX8_FUNCMOD_MFREQ_768(4) }, - { 256, CS42XX8_FM_SINGLE, CS42XX8_FUNCMOD_MFREQ_256(1) }, - { 384, CS42XX8_FM_SINGLE, CS42XX8_FUNCMOD_MFREQ_384(1) }, - { 512, CS42XX8_FM_SINGLE, CS42XX8_FUNCMOD_MFREQ_512(1) }, - { 768, CS42XX8_FM_SINGLE, CS42XX8_FUNCMOD_MFREQ_768(1) }, - { 1024, CS42XX8_FM_SINGLE, CS42XX8_FUNCMOD_MFREQ_1024(1) } + { 0, 1029000, 12800000, {256, 128, 64} }, + { 2, 1536000, 19200000, {384, 192, 96} }, + { 4, 2048000, 25600000, {512, 256, 128} }, + { 6, 3072000, 38400000, {768, 384, 192} }, + { 8, 4096000, 51200000, {1024, 512, 256} }, }; static int cs42xx8_set_dai_sysclk(struct snd_soc_dai *codec_dai, @@ -255,15 +246,70 @@ static int cs42xx8_hw_params(struct snd_pcm_substream *substream, struct snd_soc_codec *codec = dai->codec; struct cs42xx8_priv *cs42xx8 = snd_soc_codec_get_drvdata(codec); bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; - u32 ratio = cs42xx8->sysclk / params_rate(params); + u32 rate = params_rate(params); + u32 ratio_tx, ratio_rx; + u32 rate_tx, rate_rx; + u32 fm_tx, fm_rx; u32 i, fm, val, mask; if (tx) cs42xx8->tx_channels = params_channels(params); - for (i = 0; i < ARRAY_SIZE(cs42xx8_ratios); i++) { - if (cs42xx8_ratios[i].ratio == ratio) - break; + rate_tx = tx ? rate : cs42xx8->rate[0]; + rate_rx = tx ? cs42xx8->rate[1] : rate; + + ratio_tx = rate_tx > 0 ? cs42xx8->sysclk / rate_tx : 0; + ratio_rx = rate_rx > 0 ? cs42xx8->sysclk / rate_rx : 0; + + if (cs42xx8->slave_mode) { + fm_rx = CS42XX8_FM_AUTO; + fm_tx = CS42XX8_FM_AUTO; + } else { + if (rate_tx < 50000) + fm_tx = CS42XX8_FM_SINGLE; + else if (rate_tx > 50000 && rate_tx < 100000) + fm_tx = CS42XX8_FM_DOUBLE; + else if (rate_tx > 100000 && rate_tx < 200000) + fm_tx = CS42XX8_FM_QUAD; + else { + dev_err(codec->dev, "unsupported sample rate or rate combine\n"); + return -EINVAL; + } + + if (rate_rx < 50000) + fm_rx = CS42XX8_FM_SINGLE; + else if (rate_rx > 50000 && rate_rx < 100000) + fm_rx = CS42XX8_FM_DOUBLE; + else if (rate_rx > 100000 && rate_rx < 200000) + fm_rx = CS42XX8_FM_QUAD; + else { + dev_err(codec->dev, "unsupported sample rate or rate combine\n"); + return -EINVAL; + } + } + + fm = tx ? fm_tx : fm_rx; + + if (fm == CS42XX8_FM_AUTO) { + for (i = 0; i < ARRAY_SIZE(cs42xx8_ratios); i++) { + if ((ratio_tx > 0 ? (cs42xx8_ratios[i].ratio[0] == ratio_tx || + cs42xx8_ratios[i].ratio[1] == ratio_tx || + cs42xx8_ratios[i].ratio[2] == ratio_tx) : true) && + (ratio_rx > 0 ? (cs42xx8_ratios[i].ratio[0] == ratio_rx || + cs42xx8_ratios[i].ratio[1] == ratio_rx || + cs42xx8_ratios[i].ratio[2] == ratio_rx) : true) && + cs42xx8->sysclk >= cs42xx8_ratios[i].min_mclk && + cs42xx8->sysclk <= cs42xx8_ratios[i].max_mclk) + break; + } + } else { + for (i = 0; i < ARRAY_SIZE(cs42xx8_ratios); i++) { + if ((ratio_tx > 0 ? (cs42xx8_ratios[i].ratio[fm_tx] == ratio_tx) : true) && + (ratio_rx > 0 ? (cs42xx8_ratios[i].ratio[fm_rx] == ratio_rx) : true) && + cs42xx8->sysclk >= cs42xx8_ratios[i].min_mclk && + cs42xx8->sysclk <= cs42xx8_ratios[i].max_mclk) + break; + } } if (i == ARRAY_SIZE(cs42xx8_ratios)) { @@ -271,10 +317,10 @@ static int cs42xx8_hw_params(struct snd_pcm_substream *substream, return -EINVAL; } - mask = CS42XX8_FUNCMOD_MFREQ_MASK; - val = cs42xx8_ratios[i].mclk; + cs42xx8->rate[substream->stream] = rate; - fm = cs42xx8->slave_mode ? CS42XX8_FM_AUTO : cs42xx8_ratios[i].speed; + mask = CS42XX8_FUNCMOD_MFREQ_MASK; + val = cs42xx8_ratios[i].mfreq; regmap_update_bits(cs42xx8->regmap, CS42XX8_FUNCMOD, CS42XX8_FUNCMOD_xC_FM_MASK(tx) | mask, @@ -283,6 +329,22 @@ static int cs42xx8_hw_params(struct snd_pcm_substream *substream, return 0; } +static int cs42xx8_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->codec; + struct cs42xx8_priv *cs42xx8 = snd_soc_codec_get_drvdata(codec); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + + cs42xx8->rate[substream->stream] = 0; + + regmap_update_bits(cs42xx8->regmap, CS42XX8_FUNCMOD, + CS42XX8_FUNCMOD_xC_FM_MASK(tx), + CS42XX8_FUNCMOD_xC_FM(tx, CS42XX8_FM_AUTO)); + return 0; +} + static int cs42xx8_digital_mute(struct snd_soc_dai *dai, int mute) { struct snd_soc_codec *codec = dai->codec; @@ -300,6 +362,7 @@ static const struct snd_soc_dai_ops cs42xx8_dai_ops = { .set_fmt = cs42xx8_set_dai_fmt, .set_sysclk = cs42xx8_set_dai_sysclk, .hw_params = cs42xx8_hw_params, + .hw_free = cs42xx8_hw_free, .digital_mute = cs42xx8_digital_mute, }; @@ -321,7 +384,6 @@ static struct snd_soc_dai_driver cs42xx8_dai = { }; static const struct reg_default cs42xx8_reg[] = { - { 0x01, 0x01 }, /* Chip I.D. and Revision Register */ { 0x02, 0x00 }, /* Power Control */ { 0x03, 0xF0 }, /* Functional Mode */ { 0x04, 0x46 }, /* Interface Formats */ @@ -403,7 +465,8 @@ static int cs42xx8_codec_probe(struct snd_soc_codec *codec) /* Mute all DAC channels */ regmap_write(cs42xx8->regmap, CS42XX8_DACMUTE, CS42XX8_DACMUTE_ALL); - + regmap_update_bits(cs42xx8->regmap, CS42XX8_PWRCTL, + CS42XX8_PWRCTL_PDN_MASK, 0); return 0; } @@ -443,7 +506,8 @@ EXPORT_SYMBOL_GPL(cs42xx8_of_match); int cs42xx8_probe(struct device *dev, struct regmap *regmap) { - const struct of_device_id *of_id; + const struct of_device_id *of_id = of_match_device(cs42xx8_of_match, dev); + struct device_node *np = dev->of_node; struct cs42xx8_priv *cs42xx8; int ret, val, i; @@ -469,6 +533,17 @@ int cs42xx8_probe(struct device *dev, struct regmap *regmap) return -EINVAL; } + cs42xx8->reset_gpio = of_get_named_gpio(np, "reset-gpio", 0); + if (gpio_is_valid(cs42xx8->reset_gpio)) { + ret = devm_gpio_request_one(dev, cs42xx8->reset_gpio, + GPIOF_OUT_INIT_LOW, "cs42xx8 reset"); + if (ret) { + dev_err(dev, "unable to get reset gpio\n"); + return ret; + } + gpio_set_value_cansleep(cs42xx8->reset_gpio, 1); + } + cs42xx8->clk = devm_clk_get(dev, "mclk"); if (IS_ERR(cs42xx8->clk)) { dev_err(dev, "failed to get the clock: %ld\n", @@ -558,6 +633,11 @@ static int cs42xx8_runtime_resume(struct device *dev) return ret; } + if (gpio_is_valid(cs42xx8->reset_gpio)) { + gpio_set_value_cansleep(cs42xx8->reset_gpio, 0); + gpio_set_value_cansleep(cs42xx8->reset_gpio, 1); + } + ret = regulator_bulk_enable(ARRAY_SIZE(cs42xx8->supplies), cs42xx8->supplies); if (ret) { @@ -565,9 +645,14 @@ static int cs42xx8_runtime_resume(struct device *dev) goto err_clk; } + regmap_update_bits(cs42xx8->regmap, CS42XX8_PWRCTL, + CS42XX8_PWRCTL_PDN_MASK, 1); /* Make sure hardware reset done */ msleep(5); + regmap_update_bits(cs42xx8->regmap, CS42XX8_PWRCTL, + CS42XX8_PWRCTL_PDN_MASK, 0); + regcache_cache_only(cs42xx8->regmap, false); regcache_mark_dirty(cs42xx8->regmap); @@ -604,6 +689,7 @@ static int cs42xx8_runtime_suspend(struct device *dev) #endif const struct dev_pm_ops cs42xx8_pm = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) SET_RUNTIME_PM_OPS(cs42xx8_runtime_suspend, cs42xx8_runtime_resume, NULL) }; EXPORT_SYMBOL_GPL(cs42xx8_pm); diff --git a/sound/soc/codecs/fsl_mqs.c b/sound/soc/codecs/fsl_mqs.c new file mode 100644 index 000000000000..d2890d6f9185 --- /dev/null +++ b/sound/soc/codecs/fsl_mqs.c @@ -0,0 +1,359 @@ +/* + * ALSA SoC IMX MQS driver + * + * Copyright (C) 2014-2015 Freescale Semiconductor, Inc. + * + * 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/clk.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/mfd/syscon.h> +#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h> +#include <linux/pm_runtime.h> +#include <linux/of.h> +#include <linux/pm.h> +#include <linux/slab.h> +#include <sound/soc.h> +#include <sound/pcm.h> +#include <sound/initval.h> + + +#define REG_MQS_CTRL 0x00 + +#define MQS_EN_MASK (0x1 << 28) +#define MQS_EN_SHIFT (28) +#define MQS_SW_RST_MASK (0x1 << 24) +#define MQS_SW_RST_SHIFT (24) +#define MQS_OVERSAMPLE_MASK (0x1 << 20) +#define MQS_OVERSAMPLE_SHIFT (20) +#define MQS_CLK_DIV_MASK (0xFF << 0) +#define MQS_CLK_DIV_SHIFT (0) + + +/* codec private data */ +struct fsl_mqs { + struct platform_device *pdev; + struct regmap *gpr; + unsigned int reg_iomuxc_gpr2; + + struct regmap *regmap; + unsigned int reg_mqs_ctrl; + + struct clk *mclk; + struct clk *ipg; + + unsigned long mclk_rate; + + int sysclk_rate; + int bclk; + int lrclk; + bool use_gpr; + char name[32]; +}; + +#define FSL_MQS_RATES SNDRV_PCM_RATE_8000_192000 +#define FSL_MQS_FORMATS SNDRV_PCM_FMTBIT_S16_LE + +static int fsl_mqs_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct fsl_mqs *mqs_priv = snd_soc_codec_get_drvdata(codec); + int div, res; + + mqs_priv->mclk_rate = clk_get_rate(mqs_priv->mclk); + + mqs_priv->bclk = snd_soc_params_to_bclk(params); + mqs_priv->lrclk = params_rate(params); + + /* + * mclk_rate / (oversample(32,64) * FS * 2 * divider ) = repeat_rate; + * if repeat_rate is 8, mqs can achieve better quality. + * oversample rate is fix to 32 currently. + */ + div = mqs_priv->mclk_rate / (32 * 2 * mqs_priv->lrclk * 8); + res = mqs_priv->mclk_rate % (32 * 2 * mqs_priv->lrclk * 8); + + if (res == 0 && div > 0 && div <= 256) { + if (mqs_priv->use_gpr) { + regmap_update_bits(mqs_priv->gpr, IOMUXC_GPR2, + IMX6SX_GPR2_MQS_CLK_DIV_MASK, + (div-1) << IMX6SX_GPR2_MQS_CLK_DIV_SHIFT); + regmap_update_bits(mqs_priv->gpr, IOMUXC_GPR2, + IMX6SX_GPR2_MQS_OVERSAMPLE_MASK, + 0 << IMX6SX_GPR2_MQS_OVERSAMPLE_SHIFT); + } else { + regmap_update_bits(mqs_priv->regmap, REG_MQS_CTRL, + MQS_CLK_DIV_MASK, + (div-1) << MQS_CLK_DIV_SHIFT); + regmap_update_bits(mqs_priv->regmap, REG_MQS_CTRL, + MQS_OVERSAMPLE_MASK, + 0 << MQS_OVERSAMPLE_SHIFT); + } + } else + dev_err(&mqs_priv->pdev->dev, "can't get proper divider\n"); + + return 0; +} + +static int fsl_mqs_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_LEFT_J: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + return 0; +} + +static int fsl_mqs_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = dai->codec; + struct fsl_mqs *mqs_priv = snd_soc_codec_get_drvdata(codec); + + mqs_priv->sysclk_rate = freq; + + return 0; +} + +static int fsl_mqs_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct fsl_mqs *mqs_priv = snd_soc_codec_get_drvdata(codec); + + if (mqs_priv->use_gpr) + regmap_update_bits(mqs_priv->gpr, IOMUXC_GPR2, IMX6SX_GPR2_MQS_EN_MASK, + 1 << IMX6SX_GPR2_MQS_EN_SHIFT); + else + regmap_update_bits(mqs_priv->regmap, REG_MQS_CTRL, + MQS_EN_MASK, + 1 << MQS_EN_SHIFT); + return 0; +} + +static void fsl_mqs_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct fsl_mqs *mqs_priv = snd_soc_codec_get_drvdata(codec); + + if (mqs_priv->use_gpr) + regmap_update_bits(mqs_priv->gpr, IOMUXC_GPR2, + IMX6SX_GPR2_MQS_EN_MASK, 0); + else + regmap_update_bits(mqs_priv->regmap, REG_MQS_CTRL, + MQS_EN_MASK, 0); +} + + +static struct snd_soc_codec_driver soc_codec_fsl_mqs = { + .idle_bias_off = true, +}; + +static const struct snd_soc_dai_ops fsl_mqs_dai_ops = { + .startup = fsl_mqs_startup, + .shutdown = fsl_mqs_shutdown, + .hw_params = fsl_mqs_hw_params, + .set_fmt = fsl_mqs_set_dai_fmt, + .set_sysclk = fsl_mqs_set_dai_sysclk, +}; + +static struct snd_soc_dai_driver fsl_mqs_dai = { + .name = "fsl-mqs-dai", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = FSL_MQS_RATES, + .formats = FSL_MQS_FORMATS, + }, + .ops = &fsl_mqs_dai_ops, +}; + +static const struct regmap_config fsl_mqs_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = REG_MQS_CTRL, + .cache_type = REGCACHE_NONE, +}; + +static int fsl_mqs_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *gpr_np = 0; + struct fsl_mqs *mqs_priv; + struct resource *res; + void __iomem *regs; + int ret = 0; + + mqs_priv = devm_kzalloc(&pdev->dev, sizeof(*mqs_priv), GFP_KERNEL); + if (!mqs_priv) + return -ENOMEM; + + mqs_priv->pdev = pdev; + strncpy(mqs_priv->name, np->name, sizeof(mqs_priv->name) - 1); + + if (of_device_is_compatible(np, "fsl,imx8qm-mqs")) + mqs_priv->use_gpr = false; + else + mqs_priv->use_gpr = true; + + if (mqs_priv->use_gpr) { + gpr_np = of_parse_phandle(np, "gpr", 0); + if (IS_ERR(gpr_np)) { + dev_err(&pdev->dev, "failed to get gpr node by phandle\n"); + ret = PTR_ERR(gpr_np); + goto out; + } + + mqs_priv->gpr = syscon_node_to_regmap(gpr_np); + if (IS_ERR(mqs_priv->gpr)) { + dev_err(&pdev->dev, "failed to get gpr regmap\n"); + ret = PTR_ERR(mqs_priv->gpr); + goto out; + } + } else { + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + mqs_priv->regmap = devm_regmap_init_mmio_clk(&pdev->dev, + "core", regs, &fsl_mqs_regmap_config); + if (IS_ERR(mqs_priv->regmap)) { + dev_err(&pdev->dev, "failed to init regmap: %ld\n", + PTR_ERR(mqs_priv->regmap)); + return PTR_ERR(mqs_priv->regmap); + } + + mqs_priv->ipg = devm_clk_get(&pdev->dev, "core"); + if (IS_ERR(mqs_priv->ipg)) { + dev_err(&pdev->dev, "failed to get the clock: %ld\n", + PTR_ERR(mqs_priv->ipg)); + goto out; + } + } + + mqs_priv->mclk = devm_clk_get(&pdev->dev, "mclk"); + if (IS_ERR(mqs_priv->mclk)) { + dev_err(&pdev->dev, "failed to get the clock: %ld\n", + PTR_ERR(mqs_priv->mclk)); + goto out; + } + + dev_set_drvdata(&pdev->dev, mqs_priv); + pm_runtime_enable(&pdev->dev); + + return snd_soc_register_codec(&pdev->dev, &soc_codec_fsl_mqs, + &fsl_mqs_dai, 1); + +out: + if (!IS_ERR(gpr_np)) + of_node_put(gpr_np); + + return ret; +} + +static int fsl_mqs_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + pm_runtime_disable(&pdev->dev); + return 0; +} + +#ifdef CONFIG_PM +static int fsl_mqs_runtime_resume(struct device *dev) +{ + struct fsl_mqs *mqs_priv = dev_get_drvdata(dev); + + if (mqs_priv->ipg) + clk_prepare_enable(mqs_priv->ipg); + + if (mqs_priv->mclk) + clk_prepare_enable(mqs_priv->mclk); + + if (mqs_priv->use_gpr) + regmap_write(mqs_priv->gpr, IOMUXC_GPR2, + mqs_priv->reg_iomuxc_gpr2); + else + regmap_write(mqs_priv->regmap, REG_MQS_CTRL, + mqs_priv->reg_mqs_ctrl); + return 0; +} + +static int fsl_mqs_runtime_suspend(struct device *dev) +{ + struct fsl_mqs *mqs_priv = dev_get_drvdata(dev); + + if (mqs_priv->use_gpr) + regmap_read(mqs_priv->gpr, IOMUXC_GPR2, + &mqs_priv->reg_iomuxc_gpr2); + else + regmap_read(mqs_priv->regmap, REG_MQS_CTRL, + &mqs_priv->reg_mqs_ctrl); + + if (mqs_priv->mclk) + clk_disable_unprepare(mqs_priv->mclk); + + if (mqs_priv->ipg) + clk_disable_unprepare(mqs_priv->ipg); + + return 0; +} +#endif + +static const struct dev_pm_ops fsl_mqs_pm_ops = { + SET_RUNTIME_PM_OPS(fsl_mqs_runtime_suspend, + fsl_mqs_runtime_resume, + NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) +}; + +static const struct of_device_id fsl_mqs_dt_ids[] = { + { .compatible = "fsl,imx8qm-mqs", }, + { .compatible = "fsl,imx6sx-mqs", }, + {} +}; +MODULE_DEVICE_TABLE(of, fsl_mqs_dt_ids); + + +static struct platform_driver fsl_mqs_driver = { + .probe = fsl_mqs_probe, + .remove = fsl_mqs_remove, + .driver = { + .name = "fsl-mqs", + .of_match_table = fsl_mqs_dt_ids, + .pm = &fsl_mqs_pm_ops, + }, +}; + +module_platform_driver(fsl_mqs_driver); + +MODULE_AUTHOR("shengjiu wang <shengjiu.wang@freescale.com>"); +MODULE_DESCRIPTION("MQS dummy codec driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform: fsl-mqs"); diff --git a/sound/soc/codecs/hdmi-codec.c b/sound/soc/codecs/hdmi-codec.c index cba5b5a29da0..ca86eefdb9eb 100644 --- a/sound/soc/codecs/hdmi-codec.c +++ b/sound/soc/codecs/hdmi-codec.c @@ -45,10 +45,12 @@ struct hdmi_codec_priv { static const struct snd_soc_dapm_widget hdmi_widgets[] = { SND_SOC_DAPM_OUTPUT("TX"), + SND_SOC_DAPM_OUTPUT("RX"), }; static const struct snd_soc_dapm_route hdmi_routes[] = { { "TX", NULL, "Playback" }, + { "Capture", NULL, "RX", }, }; enum { @@ -112,6 +114,7 @@ static int hdmi_codec_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; int ret = 0; dev_dbg(dai->dev, "%s()\n", __func__); @@ -130,7 +133,7 @@ static int hdmi_codec_startup(struct snd_pcm_substream *substream, } } - if (hcp->hcd.ops->get_eld) { + if (tx && hcp->hcd.ops->get_eld) { ret = hcp->hcd.ops->get_eld(dai->dev->parent, hcp->hcd.data, hcp->eld, sizeof(hcp->eld)); @@ -342,6 +345,14 @@ static struct snd_soc_dai_driver hdmi_i2s_dai = { .formats = I2S_FORMATS, .sig_bits = 24, }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 8, + .rates = HDMI_RATES, + .formats = I2S_FORMATS, + .sig_bits = 24, + }, .ops = &hdmi_dai_ops, }; @@ -354,6 +365,13 @@ static const struct snd_soc_dai_driver hdmi_spdif_dai = { .rates = HDMI_RATES, .formats = SPDIF_FORMATS, }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = HDMI_RATES, + .formats = SPDIF_FORMATS, + }, .ops = &hdmi_dai_ops, }; diff --git a/sound/soc/codecs/rpmsg_cs42xx8.c b/sound/soc/codecs/rpmsg_cs42xx8.c new file mode 100644 index 000000000000..dfc520b925d5 --- /dev/null +++ b/sound/soc/codecs/rpmsg_cs42xx8.c @@ -0,0 +1,745 @@ +/* + * Cirrus Logic CS42448/CS42888 Audio CODEC Digital Audio Interface (DAI) driver + * + * Copyright (C) 2014-2016 Freescale Semiconductor, Inc. + * + * Author: Nicolin Chen <Guangyu.Chen@freescale.com> + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/tlv.h> + +#include "rpmsg_cs42xx8.h" +#include "../fsl/fsl_rpmsg_i2s.h" + +#define CS42XX8_NUM_SUPPLIES 4 +static const char *const cs42xx8_supply_names[CS42XX8_NUM_SUPPLIES] = { + "VA", + "VD", + "VLS", + "VLC", +}; + +#define CS42XX8_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +/* codec private data */ +struct rpmsg_cs42xx8_priv { + struct regulator_bulk_data supplies[CS42XX8_NUM_SUPPLIES]; + struct cs42xx8_driver_data *drvdata; + struct regmap *regmap; + struct clk *clk; + + bool slave_mode; + unsigned long sysclk; + u32 tx_channels; + int rate[2]; + int reset_gpio; + int audioindex; + struct fsl_rpmsg_i2s *rpmsg_i2s; +}; + +/* -127.5dB to 0dB with step of 0.5dB */ +static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1); +/* -64dB to 24dB with step of 0.5dB */ +static const DECLARE_TLV_DB_SCALE(adc_tlv, -6400, 50, 0); + +static const char *const cs42xx8_adc_single[] = { "Differential", "Single-Ended" }; +static const char *const cs42xx8_szc[] = { "Immediate Change", "Zero Cross", + "Soft Ramp", "Soft Ramp on Zero Cross" }; + +static const struct soc_enum adc1_single_enum = + SOC_ENUM_SINGLE(CS42XX8_ADCCTL, 4, 2, cs42xx8_adc_single); +static const struct soc_enum adc2_single_enum = + SOC_ENUM_SINGLE(CS42XX8_ADCCTL, 3, 2, cs42xx8_adc_single); +static const struct soc_enum adc3_single_enum = + SOC_ENUM_SINGLE(CS42XX8_ADCCTL, 2, 2, cs42xx8_adc_single); +static const struct soc_enum dac_szc_enum = + SOC_ENUM_SINGLE(CS42XX8_TXCTL, 5, 4, cs42xx8_szc); +static const struct soc_enum adc_szc_enum = + SOC_ENUM_SINGLE(CS42XX8_TXCTL, 0, 4, cs42xx8_szc); + +static const struct snd_kcontrol_new cs42xx8_snd_controls[] = { + SOC_DOUBLE_R_TLV("DAC1 Playback Volume", CS42XX8_VOLAOUT1, + CS42XX8_VOLAOUT2, 0, 0xff, 1, dac_tlv), + SOC_DOUBLE_R_TLV("DAC2 Playback Volume", CS42XX8_VOLAOUT3, + CS42XX8_VOLAOUT4, 0, 0xff, 1, dac_tlv), + SOC_DOUBLE_R_TLV("DAC3 Playback Volume", CS42XX8_VOLAOUT5, + CS42XX8_VOLAOUT6, 0, 0xff, 1, dac_tlv), + SOC_DOUBLE_R_TLV("DAC4 Playback Volume", CS42XX8_VOLAOUT7, + CS42XX8_VOLAOUT8, 0, 0xff, 1, dac_tlv), + SOC_DOUBLE_R_S_TLV("ADC1 Capture Volume", CS42XX8_VOLAIN1, + CS42XX8_VOLAIN2, 0, -0x80, 0x30, 7, 0, adc_tlv), + SOC_DOUBLE_R_S_TLV("ADC2 Capture Volume", CS42XX8_VOLAIN3, + CS42XX8_VOLAIN4, 0, -0x80, 0x30, 7, 0, adc_tlv), + SOC_DOUBLE("DAC1 Invert Switch", CS42XX8_DACINV, 0, 1, 1, 0), + SOC_DOUBLE("DAC2 Invert Switch", CS42XX8_DACINV, 2, 3, 1, 0), + SOC_DOUBLE("DAC3 Invert Switch", CS42XX8_DACINV, 4, 5, 1, 0), + SOC_DOUBLE("DAC4 Invert Switch", CS42XX8_DACINV, 6, 7, 1, 0), + SOC_DOUBLE("ADC1 Invert Switch", CS42XX8_ADCINV, 0, 1, 1, 0), + SOC_DOUBLE("ADC2 Invert Switch", CS42XX8_ADCINV, 2, 3, 1, 0), + SOC_SINGLE("ADC High-Pass Filter Switch", CS42XX8_ADCCTL, 7, 1, 1), + SOC_SINGLE("DAC De-emphasis Switch", CS42XX8_ADCCTL, 5, 1, 0), + SOC_ENUM("ADC1 Single Ended Mode Switch", adc1_single_enum), + SOC_ENUM("ADC2 Single Ended Mode Switch", adc2_single_enum), + SOC_SINGLE("DAC Single Volume Control Switch", CS42XX8_TXCTL, 7, 1, 0), + SOC_ENUM("DAC Soft Ramp & Zero Cross Control Switch", dac_szc_enum), + SOC_SINGLE("DAC Auto Mute Switch", CS42XX8_TXCTL, 4, 1, 0), + SOC_SINGLE("Mute ADC Serial Port Switch", CS42XX8_TXCTL, 3, 1, 0), + SOC_SINGLE("ADC Single Volume Control Switch", CS42XX8_TXCTL, 2, 1, 0), + SOC_ENUM("ADC Soft Ramp & Zero Cross Control Switch", adc_szc_enum), +}; + +static const struct snd_kcontrol_new cs42xx8_adc3_snd_controls[] = { + SOC_DOUBLE_R_S_TLV("ADC3 Capture Volume", CS42XX8_VOLAIN5, + CS42XX8_VOLAIN6, 0, -0x80, 0x30, 7, 0, adc_tlv), + SOC_DOUBLE("ADC3 Invert Switch", CS42XX8_ADCINV, 4, 5, 1, 0), + SOC_ENUM("ADC3 Single Ended Mode Switch", adc3_single_enum), +}; + +static const struct snd_soc_dapm_widget cs42xx8_dapm_widgets[] = { + SND_SOC_DAPM_DAC("DAC1", "Playback", CS42XX8_PWRCTL, 1, 1), + SND_SOC_DAPM_DAC("DAC2", "Playback", CS42XX8_PWRCTL, 2, 1), + SND_SOC_DAPM_DAC("DAC3", "Playback", CS42XX8_PWRCTL, 3, 1), + SND_SOC_DAPM_DAC("DAC4", "Playback", CS42XX8_PWRCTL, 4, 1), + + SND_SOC_DAPM_OUTPUT("AOUT1L"), + SND_SOC_DAPM_OUTPUT("AOUT1R"), + SND_SOC_DAPM_OUTPUT("AOUT2L"), + SND_SOC_DAPM_OUTPUT("AOUT2R"), + SND_SOC_DAPM_OUTPUT("AOUT3L"), + SND_SOC_DAPM_OUTPUT("AOUT3R"), + SND_SOC_DAPM_OUTPUT("AOUT4L"), + SND_SOC_DAPM_OUTPUT("AOUT4R"), + + SND_SOC_DAPM_ADC("ADC1", "Capture", CS42XX8_PWRCTL, 5, 1), + SND_SOC_DAPM_ADC("ADC2", "Capture", CS42XX8_PWRCTL, 6, 1), + + SND_SOC_DAPM_INPUT("AIN1L"), + SND_SOC_DAPM_INPUT("AIN1R"), + SND_SOC_DAPM_INPUT("AIN2L"), + SND_SOC_DAPM_INPUT("AIN2R"), + +}; + +static const struct snd_soc_dapm_widget cs42xx8_adc3_dapm_widgets[] = { + SND_SOC_DAPM_ADC("ADC3", "Capture", CS42XX8_PWRCTL, 7, 1), + + SND_SOC_DAPM_INPUT("AIN3L"), + SND_SOC_DAPM_INPUT("AIN3R"), +}; + +static const struct snd_soc_dapm_route cs42xx8_dapm_routes[] = { + /* Playback */ + { "AOUT1L", NULL, "DAC1" }, + { "AOUT1R", NULL, "DAC1" }, + + { "AOUT2L", NULL, "DAC2" }, + { "AOUT2R", NULL, "DAC2" }, + + { "AOUT3L", NULL, "DAC3" }, + { "AOUT3R", NULL, "DAC3" }, + + { "AOUT4L", NULL, "DAC4" }, + { "AOUT4R", NULL, "DAC4" }, + + /* Capture */ + { "ADC1", NULL, "AIN1L" }, + { "ADC1", NULL, "AIN1R" }, + + { "ADC2", NULL, "AIN2L" }, + { "ADC2", NULL, "AIN2R" }, +}; + +static const struct snd_soc_dapm_route cs42xx8_adc3_dapm_routes[] = { + /* Capture */ + { "ADC3", NULL, "AIN3L" }, + { "ADC3", NULL, "AIN3R" }, +}; + +struct cs42xx8_ratios { + unsigned int mfreq; + unsigned int min_mclk; + unsigned int max_mclk; + unsigned int ratio[3]; +}; + +static const struct cs42xx8_ratios cs42xx8_ratios[] = { + { 0, 1029000, 12800000, {256, 128, 64} }, + { 2, 1536000, 19200000, {384, 192, 96} }, + { 4, 2048000, 25600000, {512, 256, 128} }, + { 6, 3072000, 38400000, {768, 384, 192} }, + { 8, 4096000, 51200000, {1024, 512, 256} }, +}; + +static int cs42xx8_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct rpmsg_cs42xx8_priv *cs42xx8 = snd_soc_codec_get_drvdata(codec); + + cs42xx8->sysclk = freq; + + return 0; +} + +static int cs42xx8_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int format) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct rpmsg_cs42xx8_priv *cs42xx8 = snd_soc_codec_get_drvdata(codec); + u32 val; + + /* Set DAI format */ + switch (format & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_LEFT_J: + val = CS42XX8_INTF_DAC_DIF_LEFTJ | CS42XX8_INTF_ADC_DIF_LEFTJ; + break; + case SND_SOC_DAIFMT_I2S: + val = CS42XX8_INTF_DAC_DIF_I2S | CS42XX8_INTF_ADC_DIF_I2S; + break; + case SND_SOC_DAIFMT_RIGHT_J: + val = CS42XX8_INTF_DAC_DIF_RIGHTJ | CS42XX8_INTF_ADC_DIF_RIGHTJ; + break; + case SND_SOC_DAIFMT_DSP_A: + val = CS42XX8_INTF_DAC_DIF_TDM | CS42XX8_INTF_ADC_DIF_TDM; + break; + default: + dev_err(codec->dev, "unsupported dai format\n"); + return -EINVAL; + } + + regmap_update_bits(cs42xx8->regmap, CS42XX8_INTF, + CS42XX8_INTF_DAC_DIF_MASK | + CS42XX8_INTF_ADC_DIF_MASK, val); + + /* Set master/slave audio interface */ + switch (format & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + cs42xx8->slave_mode = true; + break; + case SND_SOC_DAIFMT_CBM_CFM: + cs42xx8->slave_mode = false; + break; + default: + dev_err(codec->dev, "unsupported master/slave mode\n"); + return -EINVAL; + } + + return 0; +} + +static int cs42xx8_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct rpmsg_cs42xx8_priv *cs42xx8 = snd_soc_codec_get_drvdata(codec); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + u32 rate = params_rate(params); + u32 ratio_tx, ratio_rx; + u32 rate_tx, rate_rx; + u32 fm_tx, fm_rx; + u32 i, fm, val, mask; + + if (tx) + cs42xx8->tx_channels = params_channels(params); + + rate_tx = tx ? rate : cs42xx8->rate[0]; + rate_rx = tx ? cs42xx8->rate[1] : rate; + + ratio_tx = rate_tx > 0 ? cs42xx8->sysclk / rate_tx : 0; + ratio_rx = rate_rx > 0 ? cs42xx8->sysclk / rate_rx : 0; + + if (cs42xx8->slave_mode) { + fm_rx = CS42XX8_FM_AUTO; + fm_tx = CS42XX8_FM_AUTO; + } else { + if (rate_tx < 50000) + fm_tx = CS42XX8_FM_SINGLE; + else if (rate_tx > 50000 && rate_tx < 100000) + fm_tx = CS42XX8_FM_DOUBLE; + else if (rate_tx > 100000 && rate_tx < 200000) + fm_tx = CS42XX8_FM_QUAD; + else { + dev_err(codec->dev, "unsupported sample rate or rate combine\n"); + return -EINVAL; + } + + if (rate_rx < 50000) + fm_rx = CS42XX8_FM_SINGLE; + else if (rate_rx > 50000 && rate_rx < 100000) + fm_rx = CS42XX8_FM_DOUBLE; + else if (rate_rx > 100000 && rate_rx < 200000) + fm_rx = CS42XX8_FM_QUAD; + else { + dev_err(codec->dev, "unsupported sample rate or rate combine\n"); + return -EINVAL; + } + } + + fm = tx ? fm_tx : fm_rx; + + if (fm == CS42XX8_FM_AUTO) { + for (i = 0; i < ARRAY_SIZE(cs42xx8_ratios); i++) { + if ((ratio_tx > 0 ? (cs42xx8_ratios[i].ratio[0] == ratio_tx || + cs42xx8_ratios[i].ratio[1] == ratio_tx || + cs42xx8_ratios[i].ratio[2] == ratio_tx) : true) && + (ratio_rx > 0 ? (cs42xx8_ratios[i].ratio[0] == ratio_rx || + cs42xx8_ratios[i].ratio[1] == ratio_rx || + cs42xx8_ratios[i].ratio[2] == ratio_rx) : true) && + cs42xx8->sysclk >= cs42xx8_ratios[i].min_mclk && + cs42xx8->sysclk <= cs42xx8_ratios[i].max_mclk) + break; + } + } else { + for (i = 0; i < ARRAY_SIZE(cs42xx8_ratios); i++) { + if ((ratio_tx > 0 ? (cs42xx8_ratios[i].ratio[fm_tx] == ratio_tx) : true) && + (ratio_rx > 0 ? (cs42xx8_ratios[i].ratio[fm_rx] == ratio_rx) : true) && + cs42xx8->sysclk >= cs42xx8_ratios[i].min_mclk && + cs42xx8->sysclk <= cs42xx8_ratios[i].max_mclk) + break; + } + } + + if (i == ARRAY_SIZE(cs42xx8_ratios)) { + dev_err(codec->dev, "unsupported sysclk ratio\n"); + return -EINVAL; + } + + cs42xx8->rate[substream->stream] = rate; + + mask = CS42XX8_FUNCMOD_MFREQ_MASK; + val = cs42xx8_ratios[i].mfreq; + + regmap_update_bits(cs42xx8->regmap, CS42XX8_FUNCMOD, + CS42XX8_FUNCMOD_xC_FM_MASK(tx) | mask, + CS42XX8_FUNCMOD_xC_FM(tx, fm) | val); + + return 0; +} + +static int cs42xx8_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->codec; + struct rpmsg_cs42xx8_priv *cs42xx8 = snd_soc_codec_get_drvdata(codec); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + + cs42xx8->rate[substream->stream] = 0; + + regmap_update_bits(cs42xx8->regmap, CS42XX8_FUNCMOD, + CS42XX8_FUNCMOD_xC_FM_MASK(tx), + CS42XX8_FUNCMOD_xC_FM(tx, CS42XX8_FM_AUTO)); + return 0; +} + +static int cs42xx8_digital_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + struct rpmsg_cs42xx8_priv *cs42xx8 = snd_soc_codec_get_drvdata(codec); + u8 dac_unmute = cs42xx8->tx_channels ? + ~((0x1 << cs42xx8->tx_channels) - 1) : 0; + + regmap_write(cs42xx8->regmap, CS42XX8_DACMUTE, + mute ? CS42XX8_DACMUTE_ALL : dac_unmute); + + return 0; +} + +static const struct snd_soc_dai_ops cs42xx8_dai_ops = { + .set_fmt = cs42xx8_set_dai_fmt, + .set_sysclk = cs42xx8_set_dai_sysclk, + .hw_params = cs42xx8_hw_params, + .hw_free = cs42xx8_hw_free, + .digital_mute = cs42xx8_digital_mute, +}; + +static struct snd_soc_dai_driver cs42xx8_dai = { + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = CS42XX8_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = CS42XX8_FORMATS, + }, + .ops = &cs42xx8_dai_ops, +}; + +static const struct reg_default cs42xx8_reg[] = { + { 0x02, 0x00 }, /* Power Control */ + { 0x03, 0xF0 }, /* Functional Mode */ + { 0x04, 0x46 }, /* Interface Formats */ + { 0x05, 0x00 }, /* ADC Control & DAC De-Emphasis */ + { 0x06, 0x10 }, /* Transition Control */ + { 0x07, 0x00 }, /* DAC Channel Mute */ + { 0x08, 0x00 }, /* Volume Control AOUT1 */ + { 0x09, 0x00 }, /* Volume Control AOUT2 */ + { 0x0a, 0x00 }, /* Volume Control AOUT3 */ + { 0x0b, 0x00 }, /* Volume Control AOUT4 */ + { 0x0c, 0x00 }, /* Volume Control AOUT5 */ + { 0x0d, 0x00 }, /* Volume Control AOUT6 */ + { 0x0e, 0x00 }, /* Volume Control AOUT7 */ + { 0x0f, 0x00 }, /* Volume Control AOUT8 */ + { 0x10, 0x00 }, /* DAC Channel Invert */ + { 0x11, 0x00 }, /* Volume Control AIN1 */ + { 0x12, 0x00 }, /* Volume Control AIN2 */ + { 0x13, 0x00 }, /* Volume Control AIN3 */ + { 0x14, 0x00 }, /* Volume Control AIN4 */ + { 0x15, 0x00 }, /* Volume Control AIN5 */ + { 0x16, 0x00 }, /* Volume Control AIN6 */ + { 0x17, 0x00 }, /* ADC Channel Invert */ + { 0x18, 0x00 }, /* Status Control */ + { 0x1a, 0x00 }, /* Status Mask */ + { 0x1b, 0x00 }, /* MUTEC Pin Control */ +}; + +static bool cs42xx8_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS42XX8_STATUS: + return true; + default: + return false; + } +} + +static bool cs42xx8_writeable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS42XX8_CHIPID: + case CS42XX8_STATUS: + return false; + default: + return true; + } +} + +static int rpmsg_cs42xx8_read(void *context, unsigned int reg, unsigned int *val) +{ + struct rpmsg_cs42xx8_priv *cs42xx8 = context; + struct fsl_rpmsg_i2s *rpmsg_i2s = cs42xx8->rpmsg_i2s; + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + struct i2s_rpmsg_s *rpmsg = &i2s_info->rpmsg[GET_CODEC_VALUE].send_msg; + int err, reg_val; + + mutex_lock(&i2s_info->i2c_lock); + rpmsg->param.audioindex = cs42xx8->audioindex; + rpmsg->param.buffer_addr = reg; + rpmsg->header.cmd = GET_CODEC_VALUE; + err = i2s_info->send_message(&i2s_info->rpmsg[GET_CODEC_VALUE], i2s_info); + reg_val = i2s_info->rpmsg[GET_CODEC_VALUE].recv_msg.param.reg_data; + mutex_unlock(&i2s_info->i2c_lock); + if (err) + return -EIO; + + *val = reg_val; + return 0; +} + +static int rpmsg_cs42xx8_write(void *context, unsigned int reg, unsigned int val) +{ + struct rpmsg_cs42xx8_priv *cs42xx8 = context; + struct fsl_rpmsg_i2s *rpmsg_i2s = cs42xx8->rpmsg_i2s; + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + struct i2s_rpmsg_s *rpmsg = &i2s_info->rpmsg[SET_CODEC_VALUE].send_msg; + int err; + + mutex_lock(&i2s_info->i2c_lock); + rpmsg->param.audioindex = cs42xx8->audioindex; + rpmsg->param.buffer_addr = reg; + rpmsg->param.buffer_size = val; + rpmsg->header.cmd = SET_CODEC_VALUE; + err = i2s_info->send_message(&i2s_info->rpmsg[SET_CODEC_VALUE], i2s_info); + mutex_unlock(&i2s_info->i2c_lock); + if (err) + return -EIO; + + return 0; +} + +static struct regmap_config rpmsg_cs42xx8_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = CS42XX8_LASTREG, + .reg_defaults = cs42xx8_reg, + .num_reg_defaults = ARRAY_SIZE(cs42xx8_reg), + .volatile_reg = cs42xx8_volatile_register, + .writeable_reg = cs42xx8_writeable_register, + .cache_type = REGCACHE_RBTREE, + + .reg_read = rpmsg_cs42xx8_read, + .reg_write = rpmsg_cs42xx8_write, +}; + +static int cs42xx8_codec_probe(struct snd_soc_codec *codec) +{ + struct rpmsg_cs42xx8_priv *cs42xx8 = snd_soc_codec_get_drvdata(codec); + struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec); + + switch (cs42xx8->drvdata->num_adcs) { + case 3: + snd_soc_add_codec_controls(codec, cs42xx8_adc3_snd_controls, + ARRAY_SIZE(cs42xx8_adc3_snd_controls)); + snd_soc_dapm_new_controls(dapm, cs42xx8_adc3_dapm_widgets, + ARRAY_SIZE(cs42xx8_adc3_dapm_widgets)); + snd_soc_dapm_add_routes(dapm, cs42xx8_adc3_dapm_routes, + ARRAY_SIZE(cs42xx8_adc3_dapm_routes)); + break; + default: + break; + } + + /* Mute all DAC channels */ + regmap_write(cs42xx8->regmap, CS42XX8_DACMUTE, CS42XX8_DACMUTE_ALL); + regmap_update_bits(cs42xx8->regmap, CS42XX8_PWRCTL, + CS42XX8_PWRCTL_PDN_MASK, 0); + return 0; +} + +static const struct snd_soc_codec_driver cs42xx8_driver = { + .probe = cs42xx8_codec_probe, + .idle_bias_off = true, + + .component_driver = { + .controls = cs42xx8_snd_controls, + .num_controls = ARRAY_SIZE(cs42xx8_snd_controls), + .dapm_widgets = cs42xx8_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cs42xx8_dapm_widgets), + .dapm_routes = cs42xx8_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(cs42xx8_dapm_routes), + }, +}; + +static int rpmsg_cs42xx8_codec_probe(struct platform_device *pdev) +{ + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(pdev->dev.parent); + struct fsl_rpmsg_codec *pdata = pdev->dev.platform_data; + struct rpmsg_cs42xx8_priv *cs42xx8; + struct device *dev = &pdev->dev; + int ret, val, i; + + cs42xx8 = devm_kzalloc(&pdev->dev, sizeof(*cs42xx8), GFP_KERNEL); + if (cs42xx8 == NULL) + return -ENOMEM; + + cs42xx8->regmap = devm_regmap_init(&pdev->dev, NULL, + cs42xx8, &rpmsg_cs42xx8_regmap_config); + if (IS_ERR(cs42xx8->regmap)) + return PTR_ERR(cs42xx8->regmap); + + dev_set_drvdata(&pdev->dev, cs42xx8); + + cs42xx8->drvdata = devm_kzalloc(&pdev->dev, + sizeof(struct cs42xx8_driver_data), GFP_KERNEL); + if (!cs42xx8->drvdata) + return -ENOMEM; + + cs42xx8->rpmsg_i2s = rpmsg_i2s; + + memcpy(cs42xx8->drvdata->name, pdata->name, 32); + cs42xx8->drvdata->num_adcs = pdata->num_adcs; + cs42xx8->audioindex = pdata->audioindex; + cs42xx8->reset_gpio = of_get_named_gpio(pdev->dev.parent->of_node, + "reset-gpio", 0); + if (gpio_is_valid(cs42xx8->reset_gpio)) { + ret = devm_gpio_request_one(dev, cs42xx8->reset_gpio, + GPIOF_OUT_INIT_LOW, "cs42xx8 reset"); + if (ret) { + dev_err(dev, "unable to get reset gpio\n"); + return ret; + } + gpio_set_value_cansleep(cs42xx8->reset_gpio, 1); + } + + cs42xx8->clk = devm_clk_get(pdev->dev.parent, "mclk"); + if (IS_ERR(cs42xx8->clk)) { + dev_err(dev, "failed to get the clock: %ld\n", + PTR_ERR(cs42xx8->clk)); + return -EINVAL; + } + + cs42xx8->sysclk = clk_get_rate(cs42xx8->clk); + + for (i = 0; i < ARRAY_SIZE(cs42xx8->supplies); i++) + cs42xx8->supplies[i].supply = cs42xx8_supply_names[i]; + + ret = devm_regulator_bulk_get(pdev->dev.parent, + ARRAY_SIZE(cs42xx8->supplies), cs42xx8->supplies); + if (ret) { + dev_err(dev, "failed to request supplies: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(cs42xx8->supplies), + cs42xx8->supplies); + if (ret) { + dev_err(dev, "failed to enable supplies: %d\n", ret); + return ret; + } + + /* Make sure hardware reset done */ + usleep_range(5000, 10000); + + /* + * We haven't marked the chip revision as volatile due to + * sharing a register with the right input volume; explicitly + * bypass the cache to read it. + */ + regcache_cache_bypass(cs42xx8->regmap, true); + + /* Validate the chip ID */ + ret = regmap_read(cs42xx8->regmap, CS42XX8_CHIPID, &val); + if (ret < 0) { + dev_err(dev, "failed to get device ID, ret = %d", ret); + goto err_enable; + } + + /* The top four bits of the chip ID should be 0000 */ + if (((val & CS42XX8_CHIPID_CHIP_ID_MASK) >> 4) != 0x00) { + dev_err(dev, "unmatched chip ID: %d\n", + (val & CS42XX8_CHIPID_CHIP_ID_MASK) >> 4); + ret = -EINVAL; + goto err_enable; + } + + dev_info(dev, "found device, revision %X\n", + val & CS42XX8_CHIPID_REV_ID_MASK); + + regcache_cache_bypass(cs42xx8->regmap, false); + + cs42xx8_dai.name = cs42xx8->drvdata->name; + + /* Each adc supports stereo input */ + cs42xx8_dai.capture.channels_max = cs42xx8->drvdata->num_adcs * 2; + + pm_runtime_enable(dev); + pm_request_idle(dev); + + ret = snd_soc_register_codec(dev, &cs42xx8_driver, &cs42xx8_dai, 1); + if (ret) { + dev_err(dev, "failed to register codec:%d\n", ret); + goto err_enable; + } + + regcache_cache_only(cs42xx8->regmap, true); + +err_enable: + regulator_bulk_disable(ARRAY_SIZE(cs42xx8->supplies), + cs42xx8->supplies); + + return ret; +} + +#ifdef CONFIG_PM +static int cs42xx8_runtime_resume(struct device *dev) +{ + struct rpmsg_cs42xx8_priv *cs42xx8 = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(cs42xx8->clk); + if (ret) { + dev_err(dev, "failed to enable mclk: %d\n", ret); + return ret; + } + + if (gpio_is_valid(cs42xx8->reset_gpio)) { + gpio_set_value_cansleep(cs42xx8->reset_gpio, 0); + gpio_set_value_cansleep(cs42xx8->reset_gpio, 1); + } + + ret = regulator_bulk_enable(ARRAY_SIZE(cs42xx8->supplies), + cs42xx8->supplies); + if (ret) { + dev_err(dev, "failed to enable supplies: %d\n", ret); + goto err_clk; + } + + regmap_update_bits(cs42xx8->regmap, CS42XX8_PWRCTL, + CS42XX8_PWRCTL_PDN_MASK, 1); + /* Make sure hardware reset done */ + usleep_range(5000, 10000); + + regmap_update_bits(cs42xx8->regmap, CS42XX8_PWRCTL, + CS42XX8_PWRCTL_PDN_MASK, 0); + + regcache_cache_only(cs42xx8->regmap, false); + + ret = regcache_sync(cs42xx8->regmap); + if (ret) { + dev_err(dev, "failed to sync regmap: %d\n", ret); + goto err_bulk; + } + + return 0; + +err_bulk: + regulator_bulk_disable(ARRAY_SIZE(cs42xx8->supplies), + cs42xx8->supplies); +err_clk: + clk_disable_unprepare(cs42xx8->clk); + + return ret; +} + +static int cs42xx8_runtime_suspend(struct device *dev) +{ + struct rpmsg_cs42xx8_priv *cs42xx8 = dev_get_drvdata(dev); + + regcache_cache_only(cs42xx8->regmap, true); + + regulator_bulk_disable(ARRAY_SIZE(cs42xx8->supplies), + cs42xx8->supplies); + + clk_disable_unprepare(cs42xx8->clk); + + return 0; +} +#endif + +const struct dev_pm_ops rpmsg_cs42xx8_pm = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(cs42xx8_runtime_suspend, cs42xx8_runtime_resume, NULL) +}; + +static int rpmsg_cs42xx8_codec_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + return 0; +} + +static struct platform_driver rpmsg_cs42xx8_codec_driver = { + .driver = { + .name = RPMSG_CODEC_DRV_NAME_CS42888, + .pm = &rpmsg_cs42xx8_pm, + }, + .probe = rpmsg_cs42xx8_codec_probe, + .remove = rpmsg_cs42xx8_codec_remove, +}; + +module_platform_driver(rpmsg_cs42xx8_codec_driver); + +MODULE_DESCRIPTION("Cirrus Logic CS42448/CS42888 ALSA SoC Codec Driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/rpmsg_cs42xx8.h b/sound/soc/codecs/rpmsg_cs42xx8.h new file mode 100644 index 000000000000..682295272f49 --- /dev/null +++ b/sound/soc/codecs/rpmsg_cs42xx8.h @@ -0,0 +1,232 @@ +/* + * cs42xx8.h - Cirrus Logic CS42448/CS42888 Audio CODEC driver header file + * + * Copyright (C) 2014 Freescale Semiconductor, Inc. + * + * Author: Nicolin Chen <Guangyu.Chen@freescale.com> + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#ifndef _RPMSG_CS42XX8_H +#define _RPMSG_CS42XX8_H + +struct cs42xx8_driver_data { + char name[32]; + int num_adcs; +}; + +/* CS42888 register map */ +#define CS42XX8_CHIPID 0x01 /* Chip ID */ +#define CS42XX8_PWRCTL 0x02 /* Power Control */ +#define CS42XX8_FUNCMOD 0x03 /* Functional Mode */ +#define CS42XX8_INTF 0x04 /* Interface Formats */ +#define CS42XX8_ADCCTL 0x05 /* ADC Control */ +#define CS42XX8_TXCTL 0x06 /* Transition Control */ +#define CS42XX8_DACMUTE 0x07 /* DAC Mute Control */ +#define CS42XX8_VOLAOUT1 0x08 /* Volume Control AOUT1 */ +#define CS42XX8_VOLAOUT2 0x09 /* Volume Control AOUT2 */ +#define CS42XX8_VOLAOUT3 0x0A /* Volume Control AOUT3 */ +#define CS42XX8_VOLAOUT4 0x0B /* Volume Control AOUT4 */ +#define CS42XX8_VOLAOUT5 0x0C /* Volume Control AOUT5 */ +#define CS42XX8_VOLAOUT6 0x0D /* Volume Control AOUT6 */ +#define CS42XX8_VOLAOUT7 0x0E /* Volume Control AOUT7 */ +#define CS42XX8_VOLAOUT8 0x0F /* Volume Control AOUT8 */ +#define CS42XX8_DACINV 0x10 /* DAC Channel Invert */ +#define CS42XX8_VOLAIN1 0x11 /* Volume Control AIN1 */ +#define CS42XX8_VOLAIN2 0x12 /* Volume Control AIN2 */ +#define CS42XX8_VOLAIN3 0x13 /* Volume Control AIN3 */ +#define CS42XX8_VOLAIN4 0x14 /* Volume Control AIN4 */ +#define CS42XX8_VOLAIN5 0x15 /* Volume Control AIN5 */ +#define CS42XX8_VOLAIN6 0x16 /* Volume Control AIN6 */ +#define CS42XX8_ADCINV 0x17 /* ADC Channel Invert */ +#define CS42XX8_STATUSCTL 0x18 /* Status Control */ +#define CS42XX8_STATUS 0x19 /* Status */ +#define CS42XX8_STATUSM 0x1A /* Status Mask */ +#define CS42XX8_MUTEC 0x1B /* MUTEC Pin Control */ + +#define CS42XX8_FIRSTREG CS42XX8_CHIPID +#define CS42XX8_LASTREG CS42XX8_MUTEC +#define CS42XX8_NUMREGS (CS42XX8_LASTREG - CS42XX8_FIRSTREG + 1) +#define CS42XX8_I2C_INCR 0x80 + +/* Chip I.D. and Revision Register (Address 01h) */ +#define CS42XX8_CHIPID_CHIP_ID_MASK 0xF0 +#define CS42XX8_CHIPID_REV_ID_MASK 0x0F + +/* Power Control (Address 02h) */ +#define CS42XX8_PWRCTL_PDN_ADC3_SHIFT 7 +#define CS42XX8_PWRCTL_PDN_ADC3_MASK (1 << CS42XX8_PWRCTL_PDN_ADC3_SHIFT) +#define CS42XX8_PWRCTL_PDN_ADC3 (1 << CS42XX8_PWRCTL_PDN_ADC3_SHIFT) +#define CS42XX8_PWRCTL_PDN_ADC2_SHIFT 6 +#define CS42XX8_PWRCTL_PDN_ADC2_MASK (1 << CS42XX8_PWRCTL_PDN_ADC2_SHIFT) +#define CS42XX8_PWRCTL_PDN_ADC2 (1 << CS42XX8_PWRCTL_PDN_ADC2_SHIFT) +#define CS42XX8_PWRCTL_PDN_ADC1_SHIFT 5 +#define CS42XX8_PWRCTL_PDN_ADC1_MASK (1 << CS42XX8_PWRCTL_PDN_ADC1_SHIFT) +#define CS42XX8_PWRCTL_PDN_ADC1 (1 << CS42XX8_PWRCTL_PDN_ADC1_SHIFT) +#define CS42XX8_PWRCTL_PDN_DAC4_SHIFT 4 +#define CS42XX8_PWRCTL_PDN_DAC4_MASK (1 << CS42XX8_PWRCTL_PDN_DAC4_SHIFT) +#define CS42XX8_PWRCTL_PDN_DAC4 (1 << CS42XX8_PWRCTL_PDN_DAC4_SHIFT) +#define CS42XX8_PWRCTL_PDN_DAC3_SHIFT 3 +#define CS42XX8_PWRCTL_PDN_DAC3_MASK (1 << CS42XX8_PWRCTL_PDN_DAC3_SHIFT) +#define CS42XX8_PWRCTL_PDN_DAC3 (1 << CS42XX8_PWRCTL_PDN_DAC3_SHIFT) +#define CS42XX8_PWRCTL_PDN_DAC2_SHIFT 2 +#define CS42XX8_PWRCTL_PDN_DAC2_MASK (1 << CS42XX8_PWRCTL_PDN_DAC2_SHIFT) +#define CS42XX8_PWRCTL_PDN_DAC2 (1 << CS42XX8_PWRCTL_PDN_DAC2_SHIFT) +#define CS42XX8_PWRCTL_PDN_DAC1_SHIFT 1 +#define CS42XX8_PWRCTL_PDN_DAC1_MASK (1 << CS42XX8_PWRCTL_PDN_DAC1_SHIFT) +#define CS42XX8_PWRCTL_PDN_DAC1 (1 << CS42XX8_PWRCTL_PDN_DAC1_SHIFT) +#define CS42XX8_PWRCTL_PDN_SHIFT 0 +#define CS42XX8_PWRCTL_PDN_MASK (1 << CS42XX8_PWRCTL_PDN_SHIFT) +#define CS42XX8_PWRCTL_PDN (1 << CS42XX8_PWRCTL_PDN_SHIFT) + +/* Functional Mode (Address 03h) */ +#define CS42XX8_FUNCMOD_DAC_FM_SHIFT 6 +#define CS42XX8_FUNCMOD_DAC_FM_WIDTH 2 +#define CS42XX8_FUNCMOD_DAC_FM_MASK (((1 << CS42XX8_FUNCMOD_DAC_FM_WIDTH) - 1) << CS42XX8_FUNCMOD_DAC_FM_SHIFT) +#define CS42XX8_FUNCMOD_DAC_FM(v) ((v) << CS42XX8_FUNCMOD_DAC_FM_SHIFT) +#define CS42XX8_FUNCMOD_ADC_FM_SHIFT 4 +#define CS42XX8_FUNCMOD_ADC_FM_WIDTH 2 +#define CS42XX8_FUNCMOD_ADC_FM_MASK (((1 << CS42XX8_FUNCMOD_ADC_FM_WIDTH) - 1) << CS42XX8_FUNCMOD_ADC_FM_SHIFT) +#define CS42XX8_FUNCMOD_ADC_FM(v) ((v) << CS42XX8_FUNCMOD_ADC_FM_SHIFT) +#define CS42XX8_FUNCMOD_xC_FM_MASK(x) ((x) ? CS42XX8_FUNCMOD_DAC_FM_MASK : CS42XX8_FUNCMOD_ADC_FM_MASK) +#define CS42XX8_FUNCMOD_xC_FM(x, v) ((x) ? CS42XX8_FUNCMOD_DAC_FM(v) : CS42XX8_FUNCMOD_ADC_FM(v)) +#define CS42XX8_FUNCMOD_MFREQ_SHIFT 1 +#define CS42XX8_FUNCMOD_MFREQ_WIDTH 3 +#define CS42XX8_FUNCMOD_MFREQ_MASK (((1 << CS42XX8_FUNCMOD_MFREQ_WIDTH) - 1) << CS42XX8_FUNCMOD_MFREQ_SHIFT) +#define CS42XX8_FUNCMOD_MFREQ_256(s) ((0 << CS42XX8_FUNCMOD_MFREQ_SHIFT) >> (s >> 1)) +#define CS42XX8_FUNCMOD_MFREQ_384(s) ((1 << CS42XX8_FUNCMOD_MFREQ_SHIFT) >> (s >> 1)) +#define CS42XX8_FUNCMOD_MFREQ_512(s) ((2 << CS42XX8_FUNCMOD_MFREQ_SHIFT) >> (s >> 1)) +#define CS42XX8_FUNCMOD_MFREQ_768(s) ((3 << CS42XX8_FUNCMOD_MFREQ_SHIFT) >> (s >> 1)) +#define CS42XX8_FUNCMOD_MFREQ_1024(s) ((4 << CS42XX8_FUNCMOD_MFREQ_SHIFT) >> (s >> 1)) + +#define CS42XX8_FM_SINGLE 0 +#define CS42XX8_FM_DOUBLE 1 +#define CS42XX8_FM_QUAD 2 +#define CS42XX8_FM_AUTO 3 + +/* Interface Formats (Address 04h) */ +#define CS42XX8_INTF_FREEZE_SHIFT 7 +#define CS42XX8_INTF_FREEZE_MASK (1 << CS42XX8_INTF_FREEZE_SHIFT) +#define CS42XX8_INTF_FREEZE (1 << CS42XX8_INTF_FREEZE_SHIFT) +#define CS42XX8_INTF_AUX_DIF_SHIFT 6 +#define CS42XX8_INTF_AUX_DIF_MASK (1 << CS42XX8_INTF_AUX_DIF_SHIFT) +#define CS42XX8_INTF_AUX_DIF (1 << CS42XX8_INTF_AUX_DIF_SHIFT) +#define CS42XX8_INTF_DAC_DIF_SHIFT 3 +#define CS42XX8_INTF_DAC_DIF_WIDTH 3 +#define CS42XX8_INTF_DAC_DIF_MASK (((1 << CS42XX8_INTF_DAC_DIF_WIDTH) - 1) << CS42XX8_INTF_DAC_DIF_SHIFT) +#define CS42XX8_INTF_DAC_DIF_LEFTJ (0 << CS42XX8_INTF_DAC_DIF_SHIFT) +#define CS42XX8_INTF_DAC_DIF_I2S (1 << CS42XX8_INTF_DAC_DIF_SHIFT) +#define CS42XX8_INTF_DAC_DIF_RIGHTJ (2 << CS42XX8_INTF_DAC_DIF_SHIFT) +#define CS42XX8_INTF_DAC_DIF_RIGHTJ_16 (3 << CS42XX8_INTF_DAC_DIF_SHIFT) +#define CS42XX8_INTF_DAC_DIF_ONELINE_20 (4 << CS42XX8_INTF_DAC_DIF_SHIFT) +#define CS42XX8_INTF_DAC_DIF_ONELINE_24 (5 << CS42XX8_INTF_DAC_DIF_SHIFT) +#define CS42XX8_INTF_DAC_DIF_TDM (6 << CS42XX8_INTF_DAC_DIF_SHIFT) +#define CS42XX8_INTF_ADC_DIF_SHIFT 0 +#define CS42XX8_INTF_ADC_DIF_WIDTH 3 +#define CS42XX8_INTF_ADC_DIF_MASK (((1 << CS42XX8_INTF_ADC_DIF_WIDTH) - 1) << CS42XX8_INTF_ADC_DIF_SHIFT) +#define CS42XX8_INTF_ADC_DIF_LEFTJ (0 << CS42XX8_INTF_ADC_DIF_SHIFT) +#define CS42XX8_INTF_ADC_DIF_I2S (1 << CS42XX8_INTF_ADC_DIF_SHIFT) +#define CS42XX8_INTF_ADC_DIF_RIGHTJ (2 << CS42XX8_INTF_ADC_DIF_SHIFT) +#define CS42XX8_INTF_ADC_DIF_RIGHTJ_16 (3 << CS42XX8_INTF_ADC_DIF_SHIFT) +#define CS42XX8_INTF_ADC_DIF_ONELINE_20 (4 << CS42XX8_INTF_ADC_DIF_SHIFT) +#define CS42XX8_INTF_ADC_DIF_ONELINE_24 (5 << CS42XX8_INTF_ADC_DIF_SHIFT) +#define CS42XX8_INTF_ADC_DIF_TDM (6 << CS42XX8_INTF_ADC_DIF_SHIFT) + +/* ADC Control & DAC De-Emphasis (Address 05h) */ +#define CS42XX8_ADCCTL_ADC_HPF_FREEZE_SHIFT 7 +#define CS42XX8_ADCCTL_ADC_HPF_FREEZE_MASK (1 << CS42XX8_ADCCTL_ADC_HPF_FREEZE_SHIFT) +#define CS42XX8_ADCCTL_ADC_HPF_FREEZE (1 << CS42XX8_ADCCTL_ADC_HPF_FREEZE_SHIFT) +#define CS42XX8_ADCCTL_DAC_DEM_SHIFT 5 +#define CS42XX8_ADCCTL_DAC_DEM_MASK (1 << CS42XX8_ADCCTL_DAC_DEM_SHIFT) +#define CS42XX8_ADCCTL_DAC_DEM (1 << CS42XX8_ADCCTL_DAC_DEM_SHIFT) +#define CS42XX8_ADCCTL_ADC1_SINGLE_SHIFT 4 +#define CS42XX8_ADCCTL_ADC1_SINGLE_MASK (1 << CS42XX8_ADCCTL_ADC1_SINGLE_SHIFT) +#define CS42XX8_ADCCTL_ADC1_SINGLE (1 << CS42XX8_ADCCTL_ADC1_SINGLE_SHIFT) +#define CS42XX8_ADCCTL_ADC2_SINGLE_SHIFT 3 +#define CS42XX8_ADCCTL_ADC2_SINGLE_MASK (1 << CS42XX8_ADCCTL_ADC2_SINGLE_SHIFT) +#define CS42XX8_ADCCTL_ADC2_SINGLE (1 << CS42XX8_ADCCTL_ADC2_SINGLE_SHIFT) +#define CS42XX8_ADCCTL_ADC3_SINGLE_SHIFT 2 +#define CS42XX8_ADCCTL_ADC3_SINGLE_MASK (1 << CS42XX8_ADCCTL_ADC3_SINGLE_SHIFT) +#define CS42XX8_ADCCTL_ADC3_SINGLE (1 << CS42XX8_ADCCTL_ADC3_SINGLE_SHIFT) +#define CS42XX8_ADCCTL_AIN5_MUX_SHIFT 1 +#define CS42XX8_ADCCTL_AIN5_MUX_MASK (1 << CS42XX8_ADCCTL_AIN5_MUX_SHIFT) +#define CS42XX8_ADCCTL_AIN5_MUX (1 << CS42XX8_ADCCTL_AIN5_MUX_SHIFT) +#define CS42XX8_ADCCTL_AIN6_MUX_SHIFT 0 +#define CS42XX8_ADCCTL_AIN6_MUX_MASK (1 << CS42XX8_ADCCTL_AIN6_MUX_SHIFT) +#define CS42XX8_ADCCTL_AIN6_MUX (1 << CS42XX8_ADCCTL_AIN6_MUX_SHIFT) + +/* Transition Control (Address 06h) */ +#define CS42XX8_TXCTL_DAC_SNGVOL_SHIFT 7 +#define CS42XX8_TXCTL_DAC_SNGVOL_MASK (1 << CS42XX8_TXCTL_DAC_SNGVOL_SHIFT) +#define CS42XX8_TXCTL_DAC_SNGVOL (1 << CS42XX8_TXCTL_DAC_SNGVOL_SHIFT) +#define CS42XX8_TXCTL_DAC_SZC_SHIFT 5 +#define CS42XX8_TXCTL_DAC_SZC_WIDTH 2 +#define CS42XX8_TXCTL_DAC_SZC_MASK (((1 << CS42XX8_TXCTL_DAC_SZC_WIDTH) - 1) << CS42XX8_TXCTL_DAC_SZC_SHIFT) +#define CS42XX8_TXCTL_DAC_SZC_IC (0 << CS42XX8_TXCTL_DAC_SZC_SHIFT) +#define CS42XX8_TXCTL_DAC_SZC_ZC (1 << CS42XX8_TXCTL_DAC_SZC_SHIFT) +#define CS42XX8_TXCTL_DAC_SZC_SR (2 << CS42XX8_TXCTL_DAC_SZC_SHIFT) +#define CS42XX8_TXCTL_DAC_SZC_SRZC (3 << CS42XX8_TXCTL_DAC_SZC_SHIFT) +#define CS42XX8_TXCTL_AMUTE_SHIFT 4 +#define CS42XX8_TXCTL_AMUTE_MASK (1 << CS42XX8_TXCTL_AMUTE_SHIFT) +#define CS42XX8_TXCTL_AMUTE (1 << CS42XX8_TXCTL_AMUTE_SHIFT) +#define CS42XX8_TXCTL_MUTE_ADC_SP_SHIFT 3 +#define CS42XX8_TXCTL_MUTE_ADC_SP_MASK (1 << CS42XX8_TXCTL_MUTE_ADC_SP_SHIFT) +#define CS42XX8_TXCTL_MUTE_ADC_SP (1 << CS42XX8_TXCTL_MUTE_ADC_SP_SHIFT) +#define CS42XX8_TXCTL_ADC_SNGVOL_SHIFT 2 +#define CS42XX8_TXCTL_ADC_SNGVOL_MASK (1 << CS42XX8_TXCTL_ADC_SNGVOL_SHIFT) +#define CS42XX8_TXCTL_ADC_SNGVOL (1 << CS42XX8_TXCTL_ADC_SNGVOL_SHIFT) +#define CS42XX8_TXCTL_ADC_SZC_SHIFT 0 +#define CS42XX8_TXCTL_ADC_SZC_MASK (((1 << CS42XX8_TXCTL_ADC_SZC_WIDTH) - 1) << CS42XX8_TXCTL_ADC_SZC_SHIFT) +#define CS42XX8_TXCTL_ADC_SZC_IC (0 << CS42XX8_TXCTL_ADC_SZC_SHIFT) +#define CS42XX8_TXCTL_ADC_SZC_ZC (1 << CS42XX8_TXCTL_ADC_SZC_SHIFT) +#define CS42XX8_TXCTL_ADC_SZC_SR (2 << CS42XX8_TXCTL_ADC_SZC_SHIFT) +#define CS42XX8_TXCTL_ADC_SZC_SRZC (3 << CS42XX8_TXCTL_ADC_SZC_SHIFT) + +/* DAC Channel Mute (Address 07h) */ +#define CS42XX8_DACMUTE_AOUT(n) (0x1 << n) +#define CS42XX8_DACMUTE_ALL 0xff + +/* Status Control (Address 18h)*/ +#define CS42XX8_STATUSCTL_INI_SHIFT 2 +#define CS42XX8_STATUSCTL_INI_WIDTH 2 +#define CS42XX8_STATUSCTL_INI_MASK (((1 << CS42XX8_STATUSCTL_INI_WIDTH) - 1) << CS42XX8_STATUSCTL_INI_SHIFT) +#define CS42XX8_STATUSCTL_INT_ACTIVE_HIGH (0 << CS42XX8_STATUSCTL_INI_SHIFT) +#define CS42XX8_STATUSCTL_INT_ACTIVE_LOW (1 << CS42XX8_STATUSCTL_INI_SHIFT) +#define CS42XX8_STATUSCTL_INT_OPEN_DRAIN (2 << CS42XX8_STATUSCTL_INI_SHIFT) + +/* Status (Address 19h)*/ +#define CS42XX8_STATUS_DAC_CLK_ERR_SHIFT 4 +#define CS42XX8_STATUS_DAC_CLK_ERR_MASK (1 << CS42XX8_STATUS_DAC_CLK_ERR_SHIFT) +#define CS42XX8_STATUS_ADC_CLK_ERR_SHIFT 3 +#define CS42XX8_STATUS_ADC_CLK_ERR_MASK (1 << CS42XX8_STATUS_ADC_CLK_ERR_SHIFT) +#define CS42XX8_STATUS_ADC3_OVFL_SHIFT 2 +#define CS42XX8_STATUS_ADC3_OVFL_MASK (1 << CS42XX8_STATUS_ADC3_OVFL_SHIFT) +#define CS42XX8_STATUS_ADC2_OVFL_SHIFT 1 +#define CS42XX8_STATUS_ADC2_OVFL_MASK (1 << CS42XX8_STATUS_ADC2_OVFL_SHIFT) +#define CS42XX8_STATUS_ADC1_OVFL_SHIFT 0 +#define CS42XX8_STATUS_ADC1_OVFL_MASK (1 << CS42XX8_STATUS_ADC1_OVFL_SHIFT) + +/* Status Mask (Address 1Ah) */ +#define CS42XX8_STATUS_DAC_CLK_ERR_M_SHIFT 4 +#define CS42XX8_STATUS_DAC_CLK_ERR_M_MASK (1 << CS42XX8_STATUS_DAC_CLK_ERR_M_SHIFT) +#define CS42XX8_STATUS_ADC_CLK_ERR_M_SHIFT 3 +#define CS42XX8_STATUS_ADC_CLK_ERR_M_MASK (1 << CS42XX8_STATUS_ADC_CLK_ERR_M_SHIFT) +#define CS42XX8_STATUS_ADC3_OVFL_M_SHIFT 2 +#define CS42XX8_STATUS_ADC3_OVFL_M_MASK (1 << CS42XX8_STATUS_ADC3_OVFL_M_SHIFT) +#define CS42XX8_STATUS_ADC2_OVFL_M_SHIFT 1 +#define CS42XX8_STATUS_ADC2_OVFL_M_MASK (1 << CS42XX8_STATUS_ADC2_OVFL_M_SHIFT) +#define CS42XX8_STATUS_ADC1_OVFL_M_SHIFT 0 +#define CS42XX8_STATUS_ADC1_OVFL_M_MASK (1 << CS42XX8_STATUS_ADC1_OVFL_M_SHIFT) + +/* MUTEC Pin Control (Address 1Bh) */ +#define CS42XX8_MUTEC_MCPOLARITY_SHIFT 1 +#define CS42XX8_MUTEC_MCPOLARITY_MASK (1 << CS42XX8_MUTEC_MCPOLARITY_SHIFT) +#define CS42XX8_MUTEC_MCPOLARITY_ACTIVE_LOW (0 << CS42XX8_MUTEC_MCPOLARITY_SHIFT) +#define CS42XX8_MUTEC_MCPOLARITY_ACTIVE_HIGH (1 << CS42XX8_MUTEC_MCPOLARITY_SHIFT) +#define CS42XX8_MUTEC_MUTEC_ACTIVE_SHIFT 0 +#define CS42XX8_MUTEC_MUTEC_ACTIVE_MASK (1 << CS42XX8_MUTEC_MUTEC_ACTIVE_SHIFT) +#define CS42XX8_MUTEC_MUTEC_ACTIVE (1 << CS42XX8_MUTEC_MUTEC_ACTIVE_SHIFT) +#endif /* _CS42XX8_H */ diff --git a/sound/soc/codecs/rpmsg_wm8960.c b/sound/soc/codecs/rpmsg_wm8960.c new file mode 100644 index 000000000000..644a1eea13ab --- /dev/null +++ b/sound/soc/codecs/rpmsg_wm8960.c @@ -0,0 +1,1542 @@ +/* + * Copyright 2018 NXP + * + * Copyright 2007-11 Wolfson Microelectronics, plc + * + * Author: Liam Girdwood + * + * 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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/pm_runtime.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/tlv.h> +#include <sound/wm8960.h> +#include "../fsl/fsl_rpmsg_i2s.h" +#include "wm8960.h" + +/* R25 - Power 1 */ +#define WM8960_VMID_MASK 0x180 +#define WM8960_VREF 0x40 + +/* R26 - Power 2 */ +#define WM8960_PWR2_LOUT1 0x40 +#define WM8960_PWR2_ROUT1 0x20 +#define WM8960_PWR2_OUT3 0x02 + +/* R28 - Anti-pop 1 */ +#define WM8960_POBCTRL 0x80 +#define WM8960_BUFDCOPEN 0x10 +#define WM8960_BUFIOEN 0x08 +#define WM8960_SOFT_ST 0x04 +#define WM8960_HPSTBY 0x01 + +/* R29 - Anti-pop 2 */ +#define WM8960_DISOP 0x40 +#define WM8960_DRES_MASK 0x30 + +static bool is_pll_freq_available(unsigned int source, unsigned int target); +static int wm8960_set_pll(struct snd_soc_codec *codec, + unsigned int freq_in, unsigned int freq_out); +/* + * wm8960 register cache + * We can't read the WM8960 register space when we are + * using 2 wire for device control, so we cache them instead. + */ +static const struct reg_default wm8960_reg_defaults[] = { + { 0x0, 0x00a7 }, + { 0x1, 0x00a7 }, + { 0x2, 0x0000 }, + { 0x3, 0x0000 }, + { 0x4, 0x0000 }, + { 0x5, 0x0008 }, + { 0x6, 0x0000 }, + { 0x7, 0x000a }, + { 0x8, 0x01c0 }, + { 0x9, 0x0000 }, + { 0xa, 0x00ff }, + { 0xb, 0x00ff }, + + { 0x10, 0x0000 }, + { 0x11, 0x007b }, + { 0x12, 0x0100 }, + { 0x13, 0x0032 }, + { 0x14, 0x0000 }, + { 0x15, 0x00c3 }, + { 0x16, 0x00c3 }, + { 0x17, 0x01c0 }, + { 0x18, 0x0000 }, + { 0x19, 0x0000 }, + { 0x1a, 0x0000 }, + { 0x1b, 0x0000 }, + { 0x1c, 0x0000 }, + { 0x1d, 0x0000 }, + + { 0x20, 0x0100 }, + { 0x21, 0x0100 }, + { 0x22, 0x0050 }, + + { 0x25, 0x0050 }, + { 0x26, 0x0000 }, + { 0x27, 0x0000 }, + { 0x28, 0x0000 }, + { 0x29, 0x0000 }, + { 0x2a, 0x0040 }, + { 0x2b, 0x0000 }, + { 0x2c, 0x0000 }, + { 0x2d, 0x0050 }, + { 0x2e, 0x0050 }, + { 0x2f, 0x0000 }, + { 0x30, 0x0002 }, + { 0x31, 0x0037 }, + + { 0x33, 0x0080 }, + { 0x34, 0x0008 }, + { 0x35, 0x0031 }, + { 0x36, 0x0026 }, + { 0x37, 0x00e9 }, +}; + +struct rpmsg_wm8960_priv { + struct clk *mclk; + struct regmap *regmap; + int (*set_bias_level)(struct snd_soc_codec *, + enum snd_soc_bias_level level); + struct snd_soc_dapm_widget *lout1; + struct snd_soc_dapm_widget *rout1; + struct snd_soc_dapm_widget *out3; + bool deemph; + int lrclk; + int bclk; + int sysclk; + int clk_id; + int freq_in; + bool is_stream_in_use[2]; + struct wm8960_data pdata; + struct fsl_rpmsg_i2s *rpmsg_i2s; + int audioindex; +}; + +static bool wm8960_volatile(struct device *dev, unsigned int reg) +{ + struct rpmsg_wm8960_priv *wm8960 = dev_get_drvdata(dev); + + if (!wm8960->mclk) + return true; + + switch (reg) { + case WM8960_RESET: + return true; + default: + return false; + } +} + +#define wm8960_reset(c) regmap_write(c, WM8960_RESET, 0) + +/* enumerated controls */ +static const char * const wm8960_polarity[] = {"No Inversion", "Left Inverted", + "Right Inverted", "Stereo Inversion"}; +static const char * const wm8960_3d_upper_cutoff[] = {"High", "Low"}; +static const char * const wm8960_3d_lower_cutoff[] = {"Low", "High"}; +static const char * const wm8960_alcfunc[] = {"Off", "Right", "Left", "Stereo"}; +static const char * const wm8960_alcmode[] = {"ALC", "Limiter"}; +static const char * const wm8960_adc_data_output_sel[] = { + "Left Data = Left ADC; Right Data = Right ADC", + "Left Data = Left ADC; Right Data = Left ADC", + "Left Data = Right ADC; Right Data = Right ADC", + "Left Data = Right ADC; Right Data = Left ADC", +}; +static const char * const wm8960_dmonomix[] = {"Stereo", "Mono"}; + +static const struct soc_enum wm8960_enum[] = { + SOC_ENUM_SINGLE(WM8960_DACCTL1, 5, 4, wm8960_polarity), + SOC_ENUM_SINGLE(WM8960_DACCTL2, 5, 4, wm8960_polarity), + SOC_ENUM_SINGLE(WM8960_3D, 6, 2, wm8960_3d_upper_cutoff), + SOC_ENUM_SINGLE(WM8960_3D, 5, 2, wm8960_3d_lower_cutoff), + SOC_ENUM_SINGLE(WM8960_ALC1, 7, 4, wm8960_alcfunc), + SOC_ENUM_SINGLE(WM8960_ALC3, 8, 2, wm8960_alcmode), + SOC_ENUM_SINGLE(WM8960_ADDCTL1, 2, 4, wm8960_adc_data_output_sel), + SOC_ENUM_SINGLE(WM8960_ADDCTL1, 4, 2, wm8960_dmonomix), +}; + +static const int deemph_settings[] = { 0, 32000, 44100, 48000 }; + +static int wm8960_set_deemph(struct snd_soc_codec *codec) +{ + struct rpmsg_wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + int val, i, best; + + /* If we're using deemphasis select the nearest available sample + * rate. + */ + if (wm8960->deemph) { + best = 1; + for (i = 2; i < ARRAY_SIZE(deemph_settings); i++) { + if (abs(deemph_settings[i] - wm8960->lrclk) < + abs(deemph_settings[best] - wm8960->lrclk)) + best = i; + } + + val = best << 1; + } else { + val = 0; + } + + dev_dbg(codec->dev, "Set deemphasis %d\n", val); + + return snd_soc_update_bits(codec, WM8960_DACCTL1, + 0x6, val); +} + +static int wm8960_get_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct rpmsg_wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + + ucontrol->value.integer.value[0] = wm8960->deemph; + return 0; +} + +static int wm8960_put_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct rpmsg_wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + unsigned int deemph = ucontrol->value.integer.value[0]; + + if (deemph > 1) + return -EINVAL; + + wm8960->deemph = deemph; + + return wm8960_set_deemph(codec); +} + +static const DECLARE_TLV_DB_SCALE(adc_tlv, -9750, 50, 1); +static const DECLARE_TLV_DB_SCALE(inpga_tlv, -1725, 75, 0); +static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1); +static const DECLARE_TLV_DB_SCALE(bypass_tlv, -2100, 300, 0); +static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1); +static const DECLARE_TLV_DB_SCALE(lineinboost_tlv, -1500, 300, 1); +static const SNDRV_CTL_TLVD_DECLARE_DB_RANGE(micboost_tlv, + 0, 1, TLV_DB_SCALE_ITEM(0, 1300, 0), + 2, 3, TLV_DB_SCALE_ITEM(2000, 900, 0), +); + +static const struct snd_kcontrol_new wm8960_snd_controls[] = { +SOC_DOUBLE_R_TLV("Capture Volume", WM8960_LINVOL, WM8960_RINVOL, + 0, 63, 0, inpga_tlv), +SOC_DOUBLE_R("Capture Volume ZC Switch", WM8960_LINVOL, WM8960_RINVOL, + 6, 1, 0), +SOC_DOUBLE_R("Capture Switch", WM8960_LINVOL, WM8960_RINVOL, + 7, 1, 1), + +SOC_SINGLE_TLV("Left Input Boost Mixer LINPUT3 Volume", + WM8960_INBMIX1, 4, 7, 0, lineinboost_tlv), +SOC_SINGLE_TLV("Left Input Boost Mixer LINPUT2 Volume", + WM8960_INBMIX1, 1, 7, 0, lineinboost_tlv), +SOC_SINGLE_TLV("Right Input Boost Mixer RINPUT3 Volume", + WM8960_INBMIX2, 4, 7, 0, lineinboost_tlv), +SOC_SINGLE_TLV("Right Input Boost Mixer RINPUT2 Volume", + WM8960_INBMIX2, 1, 7, 0, lineinboost_tlv), +SOC_SINGLE_TLV("Right Input Boost Mixer RINPUT1 Volume", + WM8960_RINPATH, 4, 3, 0, micboost_tlv), +SOC_SINGLE_TLV("Left Input Boost Mixer LINPUT1 Volume", + WM8960_LINPATH, 4, 3, 0, micboost_tlv), + +SOC_DOUBLE_R_TLV("Playback Volume", WM8960_LDAC, WM8960_RDAC, + 0, 255, 0, dac_tlv), + +SOC_DOUBLE_R_TLV("Headphone Playback Volume", WM8960_LOUT1, WM8960_ROUT1, + 0, 127, 0, out_tlv), +SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8960_LOUT1, WM8960_ROUT1, + 7, 1, 0), + +SOC_DOUBLE_R_TLV("Speaker Playback Volume", WM8960_LOUT2, WM8960_ROUT2, + 0, 127, 0, out_tlv), +SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8960_LOUT2, WM8960_ROUT2, + 7, 1, 0), +SOC_SINGLE("Speaker DC Volume", WM8960_CLASSD3, 3, 5, 0), +SOC_SINGLE("Speaker AC Volume", WM8960_CLASSD3, 0, 5, 0), + +SOC_SINGLE("PCM Playback -6dB Switch", WM8960_DACCTL1, 7, 1, 0), +SOC_ENUM("ADC Polarity", wm8960_enum[0]), +SOC_SINGLE("ADC High Pass Filter Switch", WM8960_DACCTL1, 0, 1, 0), + +SOC_ENUM("DAC Polarity", wm8960_enum[1]), +SOC_SINGLE_BOOL_EXT("DAC Deemphasis Switch", 0, + wm8960_get_deemph, wm8960_put_deemph), + +SOC_ENUM("3D Filter Upper Cut-Off", wm8960_enum[2]), +SOC_ENUM("3D Filter Lower Cut-Off", wm8960_enum[3]), +SOC_SINGLE("3D Volume", WM8960_3D, 1, 15, 0), +SOC_SINGLE("3D Switch", WM8960_3D, 0, 1, 0), + +SOC_ENUM("ALC Function", wm8960_enum[4]), +SOC_SINGLE("ALC Max Gain", WM8960_ALC1, 4, 7, 0), +SOC_SINGLE("ALC Target", WM8960_ALC1, 0, 15, 1), +SOC_SINGLE("ALC Min Gain", WM8960_ALC2, 4, 7, 0), +SOC_SINGLE("ALC Hold Time", WM8960_ALC2, 0, 15, 0), +SOC_ENUM("ALC Mode", wm8960_enum[5]), +SOC_SINGLE("ALC Decay", WM8960_ALC3, 4, 15, 0), +SOC_SINGLE("ALC Attack", WM8960_ALC3, 0, 15, 0), + +SOC_SINGLE("Noise Gate Threshold", WM8960_NOISEG, 3, 31, 0), +SOC_SINGLE("Noise Gate Switch", WM8960_NOISEG, 0, 1, 0), + +SOC_DOUBLE_R_TLV("ADC PCM Capture Volume", WM8960_LADC, WM8960_RADC, + 0, 255, 0, adc_tlv), + +SOC_SINGLE_TLV("Left Output Mixer Boost Bypass Volume", + WM8960_BYPASS1, 4, 7, 1, bypass_tlv), +SOC_SINGLE_TLV("Left Output Mixer LINPUT3 Volume", + WM8960_LOUTMIX, 4, 7, 1, bypass_tlv), +SOC_SINGLE_TLV("Right Output Mixer Boost Bypass Volume", + WM8960_BYPASS2, 4, 7, 1, bypass_tlv), +SOC_SINGLE_TLV("Right Output Mixer RINPUT3 Volume", + WM8960_ROUTMIX, 4, 7, 1, bypass_tlv), + +SOC_ENUM("ADC Data Output Select", wm8960_enum[6]), +SOC_ENUM("DAC Mono Mix", wm8960_enum[7]), +}; + +static const struct snd_kcontrol_new wm8960_lin_boost[] = { +SOC_DAPM_SINGLE("LINPUT2 Switch", WM8960_LINPATH, 6, 1, 0), +SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LINPATH, 7, 1, 0), +SOC_DAPM_SINGLE("LINPUT1 Switch", WM8960_LINPATH, 8, 1, 0), +}; + +static const struct snd_kcontrol_new wm8960_lin[] = { +SOC_DAPM_SINGLE("Boost Switch", WM8960_LINPATH, 3, 1, 0), +}; + +static const struct snd_kcontrol_new wm8960_rin_boost[] = { +SOC_DAPM_SINGLE("RINPUT2 Switch", WM8960_RINPATH, 6, 1, 0), +SOC_DAPM_SINGLE("RINPUT3 Switch", WM8960_RINPATH, 7, 1, 0), +SOC_DAPM_SINGLE("RINPUT1 Switch", WM8960_RINPATH, 8, 1, 0), +}; + +static const struct snd_kcontrol_new wm8960_rin[] = { +SOC_DAPM_SINGLE("Boost Switch", WM8960_RINPATH, 3, 1, 0), +}; + +static const struct snd_kcontrol_new wm8960_loutput_mixer[] = { +SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_LOUTMIX, 8, 1, 0), +SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LOUTMIX, 7, 1, 0), +SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS1, 7, 1, 0), +}; + +static const struct snd_kcontrol_new wm8960_routput_mixer[] = { +SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_ROUTMIX, 8, 1, 0), +SOC_DAPM_SINGLE("RINPUT3 Switch", WM8960_ROUTMIX, 7, 1, 0), +SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS2, 7, 1, 0), +}; + +static const struct snd_kcontrol_new wm8960_mono_out[] = { +SOC_DAPM_SINGLE("Left Switch", WM8960_MONOMIX1, 7, 1, 0), +SOC_DAPM_SINGLE("Right Switch", WM8960_MONOMIX2, 7, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8960_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("LINPUT1"), +SND_SOC_DAPM_INPUT("RINPUT1"), +SND_SOC_DAPM_INPUT("LINPUT2"), +SND_SOC_DAPM_INPUT("RINPUT2"), +SND_SOC_DAPM_INPUT("LINPUT3"), +SND_SOC_DAPM_INPUT("RINPUT3"), + +SND_SOC_DAPM_SUPPLY("MICB", WM8960_POWER1, 1, 0, NULL, 0), + +SND_SOC_DAPM_MIXER("Left Boost Mixer", WM8960_POWER1, 5, 0, + wm8960_lin_boost, ARRAY_SIZE(wm8960_lin_boost)), +SND_SOC_DAPM_MIXER("Right Boost Mixer", WM8960_POWER1, 4, 0, + wm8960_rin_boost, ARRAY_SIZE(wm8960_rin_boost)), + +SND_SOC_DAPM_MIXER("Left Input Mixer", WM8960_POWER3, 5, 0, + wm8960_lin, ARRAY_SIZE(wm8960_lin)), +SND_SOC_DAPM_MIXER("Right Input Mixer", WM8960_POWER3, 4, 0, + wm8960_rin, ARRAY_SIZE(wm8960_rin)), + +SND_SOC_DAPM_ADC("Left ADC", "Capture", WM8960_POWER1, 3, 0), +SND_SOC_DAPM_ADC("Right ADC", "Capture", WM8960_POWER1, 2, 0), + +SND_SOC_DAPM_DAC("Left DAC", "Playback", WM8960_POWER2, 8, 0), +SND_SOC_DAPM_DAC("Right DAC", "Playback", WM8960_POWER2, 7, 0), + +SND_SOC_DAPM_MIXER("Left Output Mixer", WM8960_POWER3, 3, 0, + &wm8960_loutput_mixer[0], + ARRAY_SIZE(wm8960_loutput_mixer)), +SND_SOC_DAPM_MIXER("Right Output Mixer", WM8960_POWER3, 2, 0, + &wm8960_routput_mixer[0], + ARRAY_SIZE(wm8960_routput_mixer)), + +SND_SOC_DAPM_PGA("LOUT1 PGA", WM8960_POWER2, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA("ROUT1 PGA", WM8960_POWER2, 5, 0, NULL, 0), + +SND_SOC_DAPM_PGA("Left Speaker PGA", WM8960_POWER2, 4, 0, NULL, 0), +SND_SOC_DAPM_PGA("Right Speaker PGA", WM8960_POWER2, 3, 0, NULL, 0), + +SND_SOC_DAPM_PGA("Right Speaker Output", WM8960_CLASSD1, 7, 0, NULL, 0), +SND_SOC_DAPM_PGA("Left Speaker Output", WM8960_CLASSD1, 6, 0, NULL, 0), + +SND_SOC_DAPM_OUTPUT("SPK_LP"), +SND_SOC_DAPM_OUTPUT("SPK_LN"), +SND_SOC_DAPM_OUTPUT("HP_L"), +SND_SOC_DAPM_OUTPUT("HP_R"), +SND_SOC_DAPM_OUTPUT("SPK_RP"), +SND_SOC_DAPM_OUTPUT("SPK_RN"), +SND_SOC_DAPM_OUTPUT("OUT3"), +}; + +static const struct snd_soc_dapm_widget wm8960_dapm_widgets_out3[] = { +SND_SOC_DAPM_MIXER("Mono Output Mixer", WM8960_POWER2, 1, 0, + &wm8960_mono_out[0], + ARRAY_SIZE(wm8960_mono_out)), +}; + +/* Represent OUT3 as a PGA so that it gets turned on with LOUT1/ROUT1 */ +static const struct snd_soc_dapm_widget wm8960_dapm_widgets_capless[] = { +SND_SOC_DAPM_PGA("OUT3 VMID", WM8960_POWER2, 1, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_route audio_paths[] = { + { "Left Boost Mixer", "LINPUT1 Switch", "LINPUT1" }, + { "Left Boost Mixer", "LINPUT2 Switch", "LINPUT2" }, + { "Left Boost Mixer", "LINPUT3 Switch", "LINPUT3" }, + + { "Left Input Mixer", "Boost Switch", "Left Boost Mixer" }, + { "Left Input Mixer", "Boost Switch", "LINPUT1" }, /* Really Boost Switch */ + { "Left Input Mixer", NULL, "LINPUT2" }, + { "Left Input Mixer", NULL, "LINPUT3" }, + + { "Right Boost Mixer", "RINPUT1 Switch", "RINPUT1" }, + { "Right Boost Mixer", "RINPUT2 Switch", "RINPUT2" }, + { "Right Boost Mixer", "RINPUT3 Switch", "RINPUT3" }, + + { "Right Input Mixer", "Boost Switch", "Right Boost Mixer" }, + { "Right Input Mixer", "Boost Switch", "RINPUT1" }, /* Really Boost Switch */ + { "Right Input Mixer", NULL, "RINPUT2" }, + { "Right Input Mixer", NULL, "RINPUT3" }, + + { "Left ADC", NULL, "Left Input Mixer" }, + { "Right ADC", NULL, "Right Input Mixer" }, + + { "Left Output Mixer", "LINPUT3 Switch", "LINPUT3" }, + { "Left Output Mixer", "Boost Bypass Switch", "Left Boost Mixer" }, + { "Left Output Mixer", "PCM Playback Switch", "Left DAC" }, + + { "Right Output Mixer", "RINPUT3 Switch", "RINPUT3" }, + { "Right Output Mixer", "Boost Bypass Switch", "Right Boost Mixer" }, + { "Right Output Mixer", "PCM Playback Switch", "Right DAC" }, + + { "LOUT1 PGA", NULL, "Left Output Mixer" }, + { "ROUT1 PGA", NULL, "Right Output Mixer" }, + + { "HP_L", NULL, "LOUT1 PGA" }, + { "HP_R", NULL, "ROUT1 PGA" }, + + { "Left Speaker PGA", NULL, "Left Output Mixer" }, + { "Right Speaker PGA", NULL, "Right Output Mixer" }, + + { "Left Speaker Output", NULL, "Left Speaker PGA" }, + { "Right Speaker Output", NULL, "Right Speaker PGA" }, + + { "SPK_LN", NULL, "Left Speaker Output" }, + { "SPK_LP", NULL, "Left Speaker Output" }, + { "SPK_RN", NULL, "Right Speaker Output" }, + { "SPK_RP", NULL, "Right Speaker Output" }, +}; + +static const struct snd_soc_dapm_route audio_paths_out3[] = { + { "Mono Output Mixer", "Left Switch", "Left Output Mixer" }, + { "Mono Output Mixer", "Right Switch", "Right Output Mixer" }, + + { "OUT3", NULL, "Mono Output Mixer", } +}; + +static const struct snd_soc_dapm_route audio_paths_capless[] = { + { "HP_L", NULL, "OUT3 VMID" }, + { "HP_R", NULL, "OUT3 VMID" }, + + { "OUT3 VMID", NULL, "Left Output Mixer" }, + { "OUT3 VMID", NULL, "Right Output Mixer" }, +}; + +static int wm8960_add_widgets(struct snd_soc_codec *codec) +{ + struct rpmsg_wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + struct wm8960_data *pdata = &wm8960->pdata; + struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec); + struct snd_soc_dapm_widget *w; + + snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets, + ARRAY_SIZE(wm8960_dapm_widgets)); + + snd_soc_dapm_add_routes(dapm, audio_paths, ARRAY_SIZE(audio_paths)); + + /* In capless mode OUT3 is used to provide VMID for the + * headphone outputs, otherwise it is used as a mono mixer. + */ + if (pdata && pdata->capless) { + snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets_capless, + ARRAY_SIZE(wm8960_dapm_widgets_capless)); + + snd_soc_dapm_add_routes(dapm, audio_paths_capless, + ARRAY_SIZE(audio_paths_capless)); + } else { + snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets_out3, + ARRAY_SIZE(wm8960_dapm_widgets_out3)); + + snd_soc_dapm_add_routes(dapm, audio_paths_out3, + ARRAY_SIZE(audio_paths_out3)); + } + + /* We need to power up the headphone output stage out of + * sequence for capless mode. To save scanning the widget + * list each time to find the desired power state do so now + * and save the result. + */ + list_for_each_entry(w, &codec->component.card->widgets, list) { + if (w->dapm != dapm) + continue; + if (strcmp(w->name, "LOUT1 PGA") == 0) + wm8960->lout1 = w; + if (strcmp(w->name, "ROUT1 PGA") == 0) + wm8960->rout1 = w; + if (strcmp(w->name, "OUT3 VMID") == 0) + wm8960->out3 = w; + } + + return 0; +} + +static int wm8960_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface = 0; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface |= 0x0040; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= 0x0013; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x0090; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0010; + break; + default: + return -EINVAL; + } + + /* set iface */ + snd_soc_write(codec, WM8960_IFACE1, iface); + return 0; +} + +static struct { + int rate; + unsigned int val; +} alc_rates[] = { + { 48000, 0 }, + { 44100, 0 }, + { 32000, 1 }, + { 22050, 2 }, + { 24000, 2 }, + { 16000, 3 }, + { 11025, 4 }, + { 12000, 4 }, + { 8000, 5 }, +}; + +/* -1 for reserved value */ +static const int sysclk_divs[] = { 1, -1, 2, -1 }; + +/* Multiply 256 for internal 256 div */ +static const int dac_divs[] = { 256, 384, 512, 768, 1024, 1408, 1536 }; + +/* Multiply 10 to eliminate decimials */ +static const int bclk_divs[] = { + 10, 15, 20, 30, 40, 55, 60, 80, 110, + 120, 160, 220, 240, 320, 320, 320 +}; + +/** + * wm8960_configure_sysclk - checks if there is a sysclk frequency available + * The sysclk must be chosen such that: + * - sysclk = MCLK / sysclk_divs + * - lrclk = sysclk / dac_divs + * - 10 * bclk = sysclk / bclk_divs + * + * @wm8960_priv: wm8960 codec private data + * @mclk: MCLK used to derive sysclk + * @sysclk_idx: sysclk_divs index for found sysclk + * @dac_idx: dac_divs index for found lrclk + * @bclk_idx: bclk_divs index for found bclk + * + * Returns: + * -1, in case no sysclk frequency available found + * >=0, in case we could derive bclk and lrclk from sysclk using + * (@sysclk_idx, @dac_idx, @bclk_idx) dividers + */ +static +int wm8960_configure_sysclk(struct rpmsg_wm8960_priv *wm8960, int mclk, + int *sysclk_idx, int *dac_idx, int *bclk_idx) +{ + int sysclk, bclk, lrclk; + int i, j, k; + int diff; + + /* marker for no match */ + *bclk_idx = -1; + + bclk = wm8960->bclk; + lrclk = wm8960->lrclk; + + /* check if the sysclk frequency is available. */ + for (i = 0; i < ARRAY_SIZE(sysclk_divs); ++i) { + if (sysclk_divs[i] == -1) + continue; + sysclk = mclk / sysclk_divs[i]; + for (j = 0; j < ARRAY_SIZE(dac_divs); ++j) { + if (sysclk != dac_divs[j] * lrclk) + continue; + for (k = 0; k < ARRAY_SIZE(bclk_divs); ++k) { + diff = sysclk - bclk * bclk_divs[k] / 10; + if (diff == 0) { + *sysclk_idx = i; + *dac_idx = j; + *bclk_idx = k; + break; + } + } + if (k != ARRAY_SIZE(bclk_divs)) + break; + } + if (j != ARRAY_SIZE(dac_divs)) + break; + } + return *bclk_idx; +} + +/** + * wm8960_configure_pll - checks if there is a PLL out frequency available + * The PLL out frequency must be chosen such that: + * - sysclk = lrclk * dac_divs + * - freq_out = sysclk * sysclk_divs + * - 10 * sysclk = bclk * bclk_divs + * + * @codec: codec structure + * @freq_in: input frequency used to derive freq out via PLL + * @sysclk_idx: sysclk_divs index for found sysclk + * @dac_idx: dac_divs index for found lrclk + * @bclk_idx: bclk_divs index for found bclk + * + * Returns: + * -1, in case no PLL frequency out available was found + * >=0, in case we could derive bclk, lrclk, sysclk from PLL out using + * (@sysclk_idx, @dac_idx, @bclk_idx) dividers + */ +static +int wm8960_configure_pll(struct snd_soc_codec *codec, int freq_in, + int *sysclk_idx, int *dac_idx, int *bclk_idx) +{ + struct rpmsg_wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + int sysclk, bclk, lrclk, freq_out; + int diff, best_freq_out = 0; + int i, j, k; + + bclk = wm8960->bclk; + lrclk = wm8960->lrclk; + + *bclk_idx = *dac_idx = *sysclk_idx = -1; + + for (i = 0; i < ARRAY_SIZE(sysclk_divs); ++i) { + if (sysclk_divs[i] == -1) + continue; + for (j = 0; j < ARRAY_SIZE(dac_divs); ++j) { + sysclk = lrclk * dac_divs[j]; + freq_out = sysclk * sysclk_divs[i]; + + for (k = 0; k < ARRAY_SIZE(bclk_divs); ++k) { + if (!is_pll_freq_available(freq_in, freq_out)) + continue; + + diff = sysclk - bclk * bclk_divs[k] / 10; + if (diff == 0) { + *sysclk_idx = i; + *dac_idx = j; + *bclk_idx = k; + best_freq_out = freq_out; + break; + } + } + if (k != ARRAY_SIZE(bclk_divs)) + break; + } + if (j != ARRAY_SIZE(dac_divs)) + break; + } + + if (*bclk_idx != -1) + wm8960_set_pll(codec, freq_in, best_freq_out); + + return *bclk_idx; +} +static int wm8960_configure_clocking(struct snd_soc_codec *codec) +{ + struct rpmsg_wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + int freq_out, freq_in; + u16 iface1 = snd_soc_read(codec, WM8960_IFACE1); + int i, j, k; + int ret; + + if (!(iface1 & (1<<6))) { + dev_dbg(codec->dev, + "Codec is slave mode, no need to configure clock\n"); + return 0; + } + + if (wm8960->clk_id != WM8960_SYSCLK_MCLK && !wm8960->freq_in) { + dev_err(codec->dev, "No MCLK configured\n"); + return -EINVAL; + } + + freq_in = wm8960->freq_in; + /* + * If it's sysclk auto mode, check if the MCLK can provide sysclk or + * not. If MCLK can provide sysclk, using MCLK to provide sysclk + * directly. Otherwise, auto select a available pll out frequency + * and set PLL. + */ + if (wm8960->clk_id == WM8960_SYSCLK_AUTO) { + /* disable the PLL and using MCLK to provide sysclk */ + wm8960_set_pll(codec, 0, 0); + freq_out = freq_in; + } else if (wm8960->sysclk) { + freq_out = wm8960->sysclk; + } else { + dev_err(codec->dev, "No SYSCLK configured\n"); + return -EINVAL; + } + + if (wm8960->clk_id != WM8960_SYSCLK_PLL) { + ret = wm8960_configure_sysclk(wm8960, freq_out, &i, &j, &k); + if (ret >= 0) { + goto configure_clock; + } else if (wm8960->clk_id != WM8960_SYSCLK_AUTO) { + dev_err(codec->dev, "failed to configure clock\n"); + return -EINVAL; + } + } + + ret = wm8960_configure_pll(codec, freq_in, &i, &j, &k); + if (ret < 0) { + dev_err(codec->dev, "failed to configure clock via PLL\n"); + return -EINVAL; + } + +configure_clock: + /* configure sysclk clock */ + snd_soc_update_bits(codec, WM8960_CLOCK1, 3 << 1, i << 1); + + /* configure frame clock */ + snd_soc_update_bits(codec, WM8960_CLOCK1, 0x7 << 3, j << 3); + snd_soc_update_bits(codec, WM8960_CLOCK1, 0x7 << 6, j << 6); + + /* configure bit clock */ + snd_soc_update_bits(codec, WM8960_CLOCK2, 0xf, k); + + return 0; +} + +static int wm8960_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct rpmsg_wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + u16 iface = snd_soc_read(codec, WM8960_IFACE1) & 0xfff3; + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + int i; + + wm8960->bclk = snd_soc_params_to_bclk(params); + if (params_channels(params) == 1) + wm8960->bclk *= 2; + + /* bit size */ + switch (params_width(params)) { + case 16: + break; + case 20: + iface |= 0x0004; + break; + case 24: + iface |= 0x0008; + break; + case 32: + /* right justify mode does not support 32 word length */ + if ((iface & 0x3) != 0) { + iface |= 0x000c; + break; + } + default: + dev_err(codec->dev, "unsupported width %d\n", + params_width(params)); + return -EINVAL; + } + + wm8960->lrclk = params_rate(params); + /* Update filters for the new rate */ + if (tx) { + wm8960_set_deemph(codec); + } else { + for (i = 0; i < ARRAY_SIZE(alc_rates); i++) + if (alc_rates[i].rate == params_rate(params)) + snd_soc_update_bits(codec, + WM8960_ADDCTL3, 0x7, + alc_rates[i].val); + } + + /* set iface */ + snd_soc_write(codec, WM8960_IFACE1, iface); + + wm8960->is_stream_in_use[tx] = true; + + if (!wm8960->is_stream_in_use[!tx]) + return wm8960_configure_clocking(codec); + + return 0; +} + +static int wm8960_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct rpmsg_wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + + wm8960->is_stream_in_use[tx] = false; + + return 0; +} + +static int wm8960_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + + if (mute) + snd_soc_update_bits(codec, WM8960_DACCTL1, 0x8, 0x8); + else + snd_soc_update_bits(codec, WM8960_DACCTL1, 0x8, 0); + return 0; +} + +static int wm8960_set_bias_level_out3(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct rpmsg_wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + u16 pm2 = snd_soc_read(codec, WM8960_POWER2); + int ret; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + switch (snd_soc_codec_get_bias_level(codec)) { + case SND_SOC_BIAS_STANDBY: + if (!IS_ERR(wm8960->mclk)) { + ret = clk_prepare_enable(wm8960->mclk); + if (ret) { + dev_err(codec->dev, + "Failed to enable MCLK: %d\n", + ret); + return ret; + } + } + + ret = wm8960_configure_clocking(codec); + if (ret) + return ret; + + /* Set VMID to 2x50k */ + snd_soc_update_bits(codec, WM8960_POWER1, 0x180, 0x80); + break; + + case SND_SOC_BIAS_ON: + /* + * If it's sysclk auto mode, and the pll is enabled, + * disable the pll + */ + if (wm8960->clk_id == WM8960_SYSCLK_AUTO && (pm2 & 0x1)) + wm8960_set_pll(codec, 0, 0); + + if (!IS_ERR(wm8960->mclk)) + clk_disable_unprepare(wm8960->mclk); + break; + + default: + break; + } + + break; + + case SND_SOC_BIAS_STANDBY: + if (snd_soc_codec_get_bias_level(codec) == SND_SOC_BIAS_OFF) { + regcache_sync(wm8960->regmap); + + /* Enable anti-pop features */ + snd_soc_write(codec, WM8960_APOP1, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN | WM8960_BUFIOEN); + + /* Enable & ramp VMID at 2x50k */ + snd_soc_update_bits(codec, WM8960_POWER1, 0x80, 0x80); + msleep(100); + + /* Enable VREF */ + snd_soc_update_bits(codec, WM8960_POWER1, WM8960_VREF, + WM8960_VREF); + + /* Disable anti-pop features */ + snd_soc_write(codec, WM8960_APOP1, WM8960_BUFIOEN); + } + + /* Set VMID to 2x250k */ + snd_soc_update_bits(codec, WM8960_POWER1, 0x180, 0x100); + break; + + case SND_SOC_BIAS_OFF: + /* Enable anti-pop features */ + snd_soc_write(codec, WM8960_APOP1, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN | WM8960_BUFIOEN); + + /* Disable VMID and VREF, let them discharge */ + snd_soc_write(codec, WM8960_POWER1, 0); + msleep(600); + break; + } + + return 0; +} + +static int wm8960_set_bias_level_capless(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct rpmsg_wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + u16 pm2 = snd_soc_read(codec, WM8960_POWER2); + int reg, ret; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + switch (snd_soc_codec_get_bias_level(codec)) { + case SND_SOC_BIAS_STANDBY: + /* Enable anti pop mode */ + snd_soc_update_bits(codec, WM8960_APOP1, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN); + + /* Enable LOUT1, ROUT1 and OUT3 if they're enabled */ + reg = 0; + if (wm8960->lout1 && wm8960->lout1->power) + reg |= WM8960_PWR2_LOUT1; + if (wm8960->rout1 && wm8960->rout1->power) + reg |= WM8960_PWR2_ROUT1; + if (wm8960->out3 && wm8960->out3->power) + reg |= WM8960_PWR2_OUT3; + snd_soc_update_bits(codec, WM8960_POWER2, + WM8960_PWR2_LOUT1 | + WM8960_PWR2_ROUT1 | + WM8960_PWR2_OUT3, reg); + + /* Enable VMID at 2*50k */ + snd_soc_update_bits(codec, WM8960_POWER1, + WM8960_VMID_MASK, 0x80); + + /* Ramp */ + msleep(100); + + /* Enable VREF */ + snd_soc_update_bits(codec, WM8960_POWER1, + WM8960_VREF, WM8960_VREF); + + msleep(100); + + if (!IS_ERR(wm8960->mclk)) { + ret = clk_prepare_enable(wm8960->mclk); + if (ret) { + dev_err(codec->dev, + "Failed to enable MCLK: %d\n", + ret); + return ret; + } + } + + ret = wm8960_configure_clocking(codec); + if (ret) + return ret; + + break; + + case SND_SOC_BIAS_ON: + /* + * If it's sysclk auto mode, and the pll is enabled, + * disable the pll + */ + if (wm8960->clk_id == WM8960_SYSCLK_AUTO && (pm2 & 0x1)) + wm8960_set_pll(codec, 0, 0); + + if (!IS_ERR(wm8960->mclk)) + clk_disable_unprepare(wm8960->mclk); + + /* Enable anti-pop mode */ + snd_soc_update_bits(codec, WM8960_APOP1, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN); + + /* Disable VMID and VREF */ + snd_soc_update_bits(codec, WM8960_POWER1, + WM8960_VREF | WM8960_VMID_MASK, 0); + break; + + case SND_SOC_BIAS_OFF: + regcache_sync(wm8960->regmap); + break; + default: + break; + } + break; + + case SND_SOC_BIAS_STANDBY: + switch (snd_soc_codec_get_bias_level(codec)) { + case SND_SOC_BIAS_PREPARE: + /* Disable HP discharge */ + snd_soc_update_bits(codec, WM8960_APOP2, + WM8960_DISOP | WM8960_DRES_MASK, + 0); + + /* Disable anti-pop features */ + snd_soc_update_bits(codec, WM8960_APOP1, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN); + break; + + default: + break; + } + break; + + case SND_SOC_BIAS_OFF: + break; + } + + return 0; +} + +/* PLL divisors */ +struct _pll_div { + u32 pre_div:1; + u32 n:4; + u32 k:24; +}; + +static bool is_pll_freq_available(unsigned int source, unsigned int target) +{ + unsigned int Ndiv; + + if (source == 0 || target == 0) + return false; + + /* Scale up target to PLL operating frequency */ + target *= 4; + Ndiv = target / source; + + if ((Ndiv < 6) || (Ndiv > 12)) + return false; + + return true; +} + +/* The size in bits of the pll divide multiplied by 10 + * to allow rounding later + */ +#define FIXED_PLL_SIZE ((1 << 24) * 10) + +static int pll_factors(unsigned int source, unsigned int target, + struct _pll_div *pll_div) +{ + unsigned long long Kpart; + unsigned int K, Ndiv, Nmod; + + pr_debug("WM8960 PLL: setting %dHz->%dHz\n", source, target); + + /* Scale up target to PLL operating frequency */ + target *= 4; + + Ndiv = target / source; + if (Ndiv < 6) { + source >>= 1; + pll_div->pre_div = 1; + Ndiv = target / source; + } else + pll_div->pre_div = 0; + + if ((Ndiv < 6) || (Ndiv > 12)) { + pr_err("WM8960 PLL: Unsupported N=%d\n", Ndiv); + return -EINVAL; + } + + pll_div->n = Ndiv; + Nmod = target % source; + Kpart = FIXED_PLL_SIZE * (long long)Nmod; + + do_div(Kpart, source); + + K = Kpart & 0xFFFFFFFF; + + /* Check if we need to round */ + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + K /= 10; + + pll_div->k = K; + + pr_debug("WM8960 PLL: N=%x K=%x pre_div=%d\n", + pll_div->n, pll_div->k, pll_div->pre_div); + + return 0; +} + +static int wm8960_set_pll(struct snd_soc_codec *codec, + unsigned int freq_in, unsigned int freq_out) +{ + u16 reg; + static struct _pll_div pll_div; + int ret; + + if (freq_in && freq_out) { + ret = pll_factors(freq_in, freq_out, &pll_div); + if (ret != 0) + return ret; + } + + /* Disable the PLL: even if we are changing the frequency the + * PLL needs to be disabled while we do so. + */ + snd_soc_update_bits(codec, WM8960_CLOCK1, 0x1, 0); + snd_soc_update_bits(codec, WM8960_POWER2, 0x1, 0); + + if (!freq_in || !freq_out) + return 0; + + reg = snd_soc_read(codec, WM8960_PLL1) & ~0x3f; + reg |= pll_div.pre_div << 4; + reg |= pll_div.n; + + if (pll_div.k) { + reg |= 0x20; + + snd_soc_write(codec, WM8960_PLL2, (pll_div.k >> 16) & 0xff); + snd_soc_write(codec, WM8960_PLL3, (pll_div.k >> 8) & 0xff); + snd_soc_write(codec, WM8960_PLL4, pll_div.k & 0xff); + } + snd_soc_write(codec, WM8960_PLL1, reg); + + /* Turn it on */ + snd_soc_update_bits(codec, WM8960_POWER2, 0x1, 0x1); + msleep(250); + snd_soc_update_bits(codec, WM8960_CLOCK1, 0x1, 0x1); + + return 0; +} + +static int wm8960_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + int source, unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct rpmsg_wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + + wm8960->freq_in = freq_in; + + if (pll_id == WM8960_SYSCLK_AUTO) + return 0; + + if (is_pll_freq_available(freq_in, freq_out)) + return -EINVAL; + + return wm8960_set_pll(codec, freq_in, freq_out); +} + +static int wm8960_set_dai_clkdiv(struct snd_soc_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + + switch (div_id) { + case WM8960_SYSCLKDIV: + reg = snd_soc_read(codec, WM8960_CLOCK1) & 0x1f9; + snd_soc_write(codec, WM8960_CLOCK1, reg | div); + break; + case WM8960_DACDIV: + reg = snd_soc_read(codec, WM8960_CLOCK1) & 0x1c7; + snd_soc_write(codec, WM8960_CLOCK1, reg | div); + break; + case WM8960_OPCLKDIV: + reg = snd_soc_read(codec, WM8960_PLL1) & 0x03f; + snd_soc_write(codec, WM8960_PLL1, reg | div); + break; + case WM8960_DCLKDIV: + reg = snd_soc_read(codec, WM8960_CLOCK2) & 0x03f; + snd_soc_write(codec, WM8960_CLOCK2, reg | div); + break; + case WM8960_TOCLKSEL: + reg = snd_soc_read(codec, WM8960_ADDCTL1) & 0x1fd; + snd_soc_write(codec, WM8960_ADDCTL1, reg | div); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int wm8960_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct rpmsg_wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + + return wm8960->set_bias_level(codec, level); +} + +static int wm8960_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = dai->codec; + struct rpmsg_wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + + switch (clk_id) { + case WM8960_SYSCLK_MCLK: + snd_soc_update_bits(codec, WM8960_CLOCK1, + 0x1, WM8960_SYSCLK_MCLK); + break; + case WM8960_SYSCLK_PLL: + snd_soc_update_bits(codec, WM8960_CLOCK1, + 0x1, WM8960_SYSCLK_PLL); + break; + case WM8960_SYSCLK_AUTO: + break; + default: + return -EINVAL; + } + + wm8960->sysclk = freq; + wm8960->clk_id = clk_id; + + return 0; +} + +#define RPMSG_RATES SNDRV_PCM_RATE_8000_48000 + +#define RPMSG_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops rpmsg_wm8960_dai_ops = { + .hw_params = wm8960_hw_params, + .hw_free = wm8960_hw_free, + .digital_mute = wm8960_mute, + .set_fmt = wm8960_set_dai_fmt, + .set_clkdiv = wm8960_set_dai_clkdiv, + .set_pll = wm8960_set_dai_pll, + .set_sysclk = wm8960_set_dai_sysclk, +}; + +static struct snd_soc_dai_driver rpmsg_wm8960_codec_dai = { + .name = "rpmsg-wm8960-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RPMSG_RATES, + .formats = RPMSG_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RPMSG_RATES, + .formats = RPMSG_FORMATS, + }, + .ops = &rpmsg_wm8960_dai_ops, + .symmetric_rates = 1, +}; + +static int rpmsg_wm8960_probe(struct snd_soc_codec *codec) +{ + struct rpmsg_wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + struct wm8960_data *pdata = &wm8960->pdata; + + if (pdata->capless) + wm8960->set_bias_level = wm8960_set_bias_level_capless; + else + wm8960->set_bias_level = wm8960_set_bias_level_out3; + + snd_soc_add_codec_controls(codec, wm8960_snd_controls, + ARRAY_SIZE(wm8960_snd_controls)); + wm8960_add_widgets(codec); + + return 0; +} + +static int rpmsg_wm8960_read(void *context, unsigned int reg, unsigned int *val) +{ + struct rpmsg_wm8960_priv *wm8960 = context; + struct fsl_rpmsg_i2s *rpmsg_i2s = wm8960->rpmsg_i2s; + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + struct i2s_rpmsg_s *rpmsg = &i2s_info->rpmsg[GET_CODEC_VALUE].send_msg; + int err, reg_val; + + mutex_lock(&i2s_info->i2c_lock); + rpmsg->param.audioindex = wm8960->audioindex; + rpmsg->param.buffer_addr = reg; + rpmsg->header.cmd = GET_CODEC_VALUE; + err = i2s_info->send_message(&i2s_info->rpmsg[GET_CODEC_VALUE], i2s_info); + reg_val = i2s_info->rpmsg[GET_CODEC_VALUE].recv_msg.param.reg_data; + mutex_unlock(&i2s_info->i2c_lock); + if (err) + return -EIO; + + *val = reg_val; + return 0; +} + +static int rpmsg_wm8960_write(void *context, unsigned int reg, unsigned int val) +{ + struct rpmsg_wm8960_priv *wm8960 = context; + struct fsl_rpmsg_i2s *rpmsg_i2s = wm8960->rpmsg_i2s; + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + struct i2s_rpmsg_s *rpmsg = &i2s_info->rpmsg[SET_CODEC_VALUE].send_msg; + int err; + + mutex_lock(&i2s_info->i2c_lock); + rpmsg->param.audioindex = wm8960->audioindex; + rpmsg->param.buffer_addr = reg; + rpmsg->param.buffer_size = val; + rpmsg->header.cmd = SET_CODEC_VALUE; + err = i2s_info->send_message(&i2s_info->rpmsg[SET_CODEC_VALUE], i2s_info); + mutex_unlock(&i2s_info->i2c_lock); + if (err) + return -EIO; + + return 0; +} + +static struct snd_soc_codec_driver rpmsg_wm8960_codec = { + .probe = rpmsg_wm8960_probe, + .set_bias_level = wm8960_set_bias_level, + .suspend_bias_off = true, +}; + +static const struct regmap_config rpmsg_wm8960_regmap = { + .reg_bits = 7, + .val_bits = 9, + .max_register = WM8960_PLL4, + + .reg_defaults = wm8960_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(wm8960_reg_defaults), + .cache_type = REGCACHE_RBTREE, + + .volatile_reg = wm8960_volatile, + .reg_read = rpmsg_wm8960_read, + .reg_write = rpmsg_wm8960_write, +}; + +#ifdef CONFIG_PM +static int wm8960_runtime_resume(struct device *dev) +{ + struct rpmsg_wm8960_priv *wm8960 = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(wm8960->mclk); + if (ret) { + dev_err(dev, "Failed to enable MCLK: %d\n", ret); + return ret; + } + return 0; +} + +static int wm8960_runtime_suspend(struct device *dev) +{ + struct rpmsg_wm8960_priv *wm8960 = dev_get_drvdata(dev); + + clk_disable_unprepare(wm8960->mclk); + + return 0; +} +#endif + +static const struct dev_pm_ops wm8960_pm = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(wm8960_runtime_suspend, wm8960_runtime_resume, NULL) +}; + +static int rpmsg_wm8960_codec_probe(struct platform_device *pdev) +{ + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(pdev->dev.parent); + struct fsl_rpmsg_codec *pdata = pdev->dev.platform_data; + struct rpmsg_wm8960_priv *wm8960; + int ret; + int repeat_reset = 10; + + wm8960 = devm_kzalloc(&pdev->dev, sizeof(struct rpmsg_wm8960_priv), + GFP_KERNEL); + if (wm8960 == NULL) + return -ENOMEM; + + wm8960->rpmsg_i2s = rpmsg_i2s; + + wm8960->mclk = devm_clk_get(pdev->dev.parent, "mclk"); + if (IS_ERR(wm8960->mclk)) { + if (PTR_ERR(wm8960->mclk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + wm8960->mclk = NULL; + } + + dev_set_drvdata(&pdev->dev, wm8960); + + wm8960->regmap = devm_regmap_init(&pdev->dev, NULL, wm8960, &rpmsg_wm8960_regmap); + if (IS_ERR(wm8960->regmap)) + return PTR_ERR(wm8960->regmap); + + if (pdata) { + wm8960->pdata.shared_lrclk = pdata->shared_lrclk; + wm8960->pdata.capless = pdata->capless; + wm8960->audioindex = pdata->audioindex; + } + + if (wm8960->mclk) { + do { + ret = wm8960_reset(wm8960->regmap); + repeat_reset--; + } while (repeat_reset > 0 && ret != 0); + + if (ret != 0) { + dev_err(&pdev->dev, "Failed to issue reset\n"); + return ret; + } + + if (wm8960->pdata.shared_lrclk) { + ret = regmap_update_bits(wm8960->regmap, WM8960_ADDCTL2, + 0x4, 0x4); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to enable LRCM: %d\n", + ret); + return ret; + } + } + } + + /* Latch the update bits */ + regmap_update_bits(wm8960->regmap, WM8960_LINVOL, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_RINVOL, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_LADC, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_RADC, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_LDAC, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_RDAC, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_LOUT1, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_ROUT1, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_LOUT2, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_ROUT2, 0x100, 0x100); + + pm_runtime_enable(&pdev->dev); + + if (!wm8960->mclk) + rpmsg_wm8960_codec_dai.ops = NULL; + + ret = snd_soc_register_codec(&pdev->dev, + &rpmsg_wm8960_codec, &rpmsg_wm8960_codec_dai, 1); + + return ret; +} + +static int rpmsg_wm8960_codec_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + return 0; +} + +static struct platform_driver rpmsg_wm8960_codec_driver = { + .driver = { + .name = RPMSG_CODEC_DRV_NAME_WM8960, + .pm = &wm8960_pm, + }, + .probe = rpmsg_wm8960_codec_probe, + .remove = rpmsg_wm8960_codec_remove, +}; + +module_platform_driver(rpmsg_wm8960_codec_driver); + +MODULE_DESCRIPTION("rpmsg wm8960 Codec Driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/si476x.c b/sound/soc/codecs/si476x.c index 5344f4aa8fde..db5676adc385 100644 --- a/sound/soc/codecs/si476x.c +++ b/sound/soc/codecs/si476x.c @@ -208,9 +208,28 @@ out: return err; } +static int si476x_codec_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) { + struct si476x_core *core = i2c_mfd_cell_to_core(dai->dev); + + if (!si476x_core_is_powered_up(core)) + si476x_core_set_power_state(core, SI476X_POWER_UP_FULL); + return 0; +} + +static void si476x_codec_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) { + struct si476x_core *core = i2c_mfd_cell_to_core(dai->dev); + + if (si476x_core_is_powered_up(core)) + si476x_core_set_power_state(core, SI476X_POWER_DOWN); +} + static const struct snd_soc_dai_ops si476x_dai_ops = { .hw_params = si476x_codec_hw_params, .set_fmt = si476x_codec_set_dai_fmt, + .startup = si476x_codec_startup, + .shutdown = si476x_codec_shutdown, }; static struct snd_soc_dai_driver si476x_dai = { diff --git a/sound/soc/codecs/wm8524.c b/sound/soc/codecs/wm8524.c new file mode 100644 index 000000000000..9c9d245bddad --- /dev/null +++ b/sound/soc/codecs/wm8524.c @@ -0,0 +1,255 @@ +/* + * wm8524.c -- WM8524 ALSA SoC Audio driver + * + * Copyright 2009 Wolfson Microelectronics plc + * Copyright 2017 NXP + * + * Based on WM8523 ALSA SoC Audio driver written by Mark Brown + * + * 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/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/gpio/consumer.h> +#include <linux/of_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/initval.h> + +#define WM8524_NUM_RATES 7 + +/* codec private data */ +struct wm8524_priv { + struct gpio_desc *mute; + unsigned int sysclk; + unsigned int rate_constraint_list[WM8524_NUM_RATES]; + struct snd_pcm_hw_constraint_list rate_constraint; +}; + + +static const struct snd_soc_dapm_widget wm8524_dapm_widgets[] = { +SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_OUTPUT("LINEVOUTL"), +SND_SOC_DAPM_OUTPUT("LINEVOUTR"), +}; + +static const struct snd_soc_dapm_route wm8524_dapm_routes[] = { + { "LINEVOUTL", NULL, "DAC" }, + { "LINEVOUTR", NULL, "DAC" }, +}; + +static struct { + int value; + int ratio; +} lrclk_ratios[WM8524_NUM_RATES] = { + { 1, 128 }, + { 2, 192 }, + { 3, 256 }, + { 4, 384 }, + { 5, 512 }, + { 6, 768 }, + { 7, 1152 }, +}; + +static int wm8524_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct wm8524_priv *wm8524 = snd_soc_codec_get_drvdata(codec); + + /* The set of sample rates that can be supported depends on the + * MCLK supplied to the CODEC - enforce this. + */ + if (!wm8524->sysclk) { + dev_err(codec->dev, + "No MCLK configured, call set_sysclk() on init\n"); + return -EINVAL; + } + + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &wm8524->rate_constraint); + + gpiod_set_value_cansleep(wm8524->mute, 1); + return 0; +} + +static void wm8524_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct wm8524_priv *wm8524 = snd_soc_codec_get_drvdata(codec); + + gpiod_set_value_cansleep(wm8524->mute, 0); +} + +static int wm8524_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + return 0; +} + +static int wm8524_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct wm8524_priv *wm8524 = snd_soc_codec_get_drvdata(codec); + unsigned int val; + int i, j = 0; + + wm8524->sysclk = freq; + + wm8524->rate_constraint.count = 0; + for (i = 0; i < ARRAY_SIZE(lrclk_ratios); i++) { + val = freq / lrclk_ratios[i].ratio; + /* Check that it's a standard rate since core can't + * cope with others and having the odd rates confuses + * constraint matching. + */ + switch (val) { + case 8000: + case 32000: + case 44100: + case 48000: + case 88200: + case 96000: + case 176400: + case 192000: + dev_err(codec->dev, "Supported sample rate: %dHz\n", + val); + wm8524->rate_constraint_list[j++] = val; + wm8524->rate_constraint.count++; + break; + default: + dev_dbg(codec->dev, "Skipping sample rate: %dHz\n", + val); + } + } + + /* Need at least one supported rate... */ + if (wm8524->rate_constraint.count == 0) + return -EINVAL; + + return 0; +} + +static int wm8524_mute_stream(struct snd_soc_dai *dai, int mute, int stream) +{ + struct wm8524_priv *wm8524 = snd_soc_codec_get_drvdata(dai->codec); + + if (wm8524->mute) + gpiod_set_value_cansleep(wm8524->mute, mute); + + return 0; +} + +#define WM8524_RATES SNDRV_PCM_RATE_8000_192000 + +#define WM8524_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops wm8524_dai_ops = { + .startup = wm8524_startup, + .shutdown = wm8524_shutdown, + .hw_params = wm8524_hw_params, + .set_sysclk = wm8524_set_dai_sysclk, + .mute_stream = wm8524_mute_stream, +}; + +static struct snd_soc_dai_driver wm8524_dai = { + .name = "wm8524-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = WM8524_RATES, + .formats = WM8524_FORMATS, + }, + .ops = &wm8524_dai_ops, +}; + +static int wm8524_probe(struct snd_soc_codec *codec) +{ + struct wm8524_priv *wm8524 = snd_soc_codec_get_drvdata(codec); + + wm8524->rate_constraint.list = &wm8524->rate_constraint_list[0]; + wm8524->rate_constraint.count = + ARRAY_SIZE(wm8524->rate_constraint_list); + + return 0; +} + +static const struct snd_soc_codec_driver soc_codec_dev_wm8524 = { + .probe = wm8524_probe, + + .component_driver = { + .dapm_widgets = wm8524_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8524_dapm_widgets), + .dapm_routes = wm8524_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(wm8524_dapm_routes), + }, +}; + +static const struct of_device_id wm8524_of_match[] = { + { .compatible = "wlf,wm8524" }, + { /* sentinel*/ } +}; +MODULE_DEVICE_TABLE(of, wm8524_of_match); + +static int wm8524_codec_probe(struct platform_device *pdev) +{ + int ret; + struct wm8524_priv *wm8524 = devm_kzalloc(&pdev->dev, + sizeof(struct wm8524_priv), + GFP_KERNEL); + if (wm8524 == NULL) + return -ENOMEM; + platform_set_drvdata(pdev, wm8524); + + wm8524->mute = devm_gpiod_get(&pdev->dev, "wlf,mute", GPIOD_OUT_LOW); + if (IS_ERR(wm8524->mute)) { + ret = PTR_ERR(wm8524->mute); + dev_err(&pdev->dev, "Failed to get mute line: %d\n", ret); + return ret; + } + + ret = snd_soc_register_codec(&pdev->dev, + &soc_codec_dev_wm8524, &wm8524_dai, 1); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to register codec: %d\n", ret); + snd_soc_unregister_platform(&pdev->dev); + } + + return ret; +} + +static int wm8524_codec_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + return 0; +} + +static struct platform_driver wm8524_codec_driver = { + .probe = wm8524_codec_probe, + .remove = wm8524_codec_remove, + .driver = { + .name = "wm8524-codec", + // TODO .pm = XXX; + .of_match_table = wm8524_of_match, + }, +}; +module_platform_driver(wm8524_codec_driver); + +MODULE_DESCRIPTION("ASoC WM8524 driver"); +MODULE_AUTHOR("Mihai Serban <mihai.serban@nxp.com>"); +MODULE_ALIAS("platform:wm8524-codec"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c index 3bf081a7e450..540665bd26a7 100644 --- a/sound/soc/codecs/wm8960.c +++ b/sound/soc/codecs/wm8960.c @@ -18,6 +18,7 @@ #include <linux/clk.h> #include <linux/i2c.h> #include <linux/slab.h> +#include <linux/pm_runtime.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/pcm_params.h> @@ -604,12 +605,135 @@ static const int bclk_divs[] = { 120, 160, 220, 240, 320, 320, 320 }; +/** + * wm8960_configure_sysclk - checks if there is a sysclk frequency available + * The sysclk must be chosen such that: + * - sysclk = MCLK / sysclk_divs + * - lrclk = sysclk / dac_divs + * - 10 * bclk = sysclk / bclk_divs + * + * @wm8960_priv: wm8960 codec private data + * @mclk: MCLK used to derive sysclk + * @sysclk_idx: sysclk_divs index for found sysclk + * @dac_idx: dac_divs index for found lrclk + * @bclk_idx: bclk_divs index for found bclk + * + * Returns: + * -1, in case no sysclk frequency available found + * >=0, in case we could derive bclk and lrclk from sysclk using + * (@sysclk_idx, @dac_idx, @bclk_idx) dividers + */ +static +int wm8960_configure_sysclk(struct wm8960_priv *wm8960, int mclk, + int *sysclk_idx, int *dac_idx, int *bclk_idx) +{ + int sysclk, bclk, lrclk; + int i, j, k; + int diff; + + /* marker for no match */ + *bclk_idx = -1; + + bclk = wm8960->bclk; + lrclk = wm8960->lrclk; + + /* check if the sysclk frequency is available. */ + for (i = 0; i < ARRAY_SIZE(sysclk_divs); ++i) { + if (sysclk_divs[i] == -1) + continue; + sysclk = mclk / sysclk_divs[i]; + for (j = 0; j < ARRAY_SIZE(dac_divs); ++j) { + if (sysclk != dac_divs[j] * lrclk) + continue; + for (k = 0; k < ARRAY_SIZE(bclk_divs); ++k) { + diff = sysclk - bclk * bclk_divs[k] / 10; + if (diff == 0) { + *sysclk_idx = i; + *dac_idx = j; + *bclk_idx = k; + break; + } + } + if (k != ARRAY_SIZE(bclk_divs)) + break; + } + if (j != ARRAY_SIZE(dac_divs)) + break; + } + return *bclk_idx; +} + +/** + * wm8960_configure_pll - checks if there is a PLL out frequency available + * The PLL out frequency must be chosen such that: + * - sysclk = lrclk * dac_divs + * - freq_out = sysclk * sysclk_divs + * - 10 * sysclk = bclk * bclk_divs + * + * @codec: codec structure + * @freq_in: input frequency used to derive freq out via PLL + * @sysclk_idx: sysclk_divs index for found sysclk + * @dac_idx: dac_divs index for found lrclk + * @bclk_idx: bclk_divs index for found bclk + * + * Returns: + * -1, in case no PLL frequency out available was found + * >=0, in case we could derive bclk, lrclk, sysclk from PLL out using + * (@sysclk_idx, @dac_idx, @bclk_idx) dividers + */ +static +int wm8960_configure_pll(struct snd_soc_codec *codec, int freq_in, + int *sysclk_idx, int *dac_idx, int *bclk_idx) +{ + struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + int sysclk, bclk, lrclk, freq_out; + int diff, best_freq_out = 0; + int i, j, k; + + bclk = wm8960->bclk; + lrclk = wm8960->lrclk; + + *bclk_idx = *dac_idx = *sysclk_idx = -1; + + for (i = 0; i < ARRAY_SIZE(sysclk_divs); ++i) { + if (sysclk_divs[i] == -1) + continue; + for (j = 0; j < ARRAY_SIZE(dac_divs); ++j) { + sysclk = lrclk * dac_divs[j]; + freq_out = sysclk * sysclk_divs[i]; + + for (k = 0; k < ARRAY_SIZE(bclk_divs); ++k) { + if (!is_pll_freq_available(freq_in, freq_out)) + continue; + + diff = sysclk - bclk * bclk_divs[k] / 10; + if (diff == 0) { + *sysclk_idx = i; + *dac_idx = j; + *bclk_idx = k; + best_freq_out = freq_out; + break; + } + } + if (k != ARRAY_SIZE(bclk_divs)) + break; + } + if (j != ARRAY_SIZE(dac_divs)) + break; + } + + if (*bclk_idx != -1) + wm8960_set_pll(codec, freq_in, best_freq_out); + + return *bclk_idx; +} static int wm8960_configure_clocking(struct snd_soc_codec *codec) { struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); - int sysclk, bclk, lrclk, freq_out, freq_in; + int freq_out, freq_in; u16 iface1 = snd_soc_read(codec, WM8960_IFACE1); int i, j, k; + int ret; if (!(iface1 & (1<<6))) { dev_dbg(codec->dev, @@ -623,8 +747,6 @@ static int wm8960_configure_clocking(struct snd_soc_codec *codec) } freq_in = wm8960->freq_in; - bclk = wm8960->bclk; - lrclk = wm8960->lrclk; /* * If it's sysclk auto mode, check if the MCLK can provide sysclk or * not. If MCLK can provide sysclk, using MCLK to provide sysclk @@ -643,58 +765,18 @@ static int wm8960_configure_clocking(struct snd_soc_codec *codec) } if (wm8960->clk_id != WM8960_SYSCLK_PLL) { - /* check if the sysclk frequency is available. */ - for (i = 0; i < ARRAY_SIZE(sysclk_divs); ++i) { - if (sysclk_divs[i] == -1) - continue; - sysclk = freq_out / sysclk_divs[i]; - for (j = 0; j < ARRAY_SIZE(dac_divs); ++j) { - if (sysclk != dac_divs[j] * lrclk) - continue; - for (k = 0; k < ARRAY_SIZE(bclk_divs); ++k) - if (sysclk == bclk * bclk_divs[k] / 10) - break; - if (k != ARRAY_SIZE(bclk_divs)) - break; - } - if (j != ARRAY_SIZE(dac_divs)) - break; - } - - if (i != ARRAY_SIZE(sysclk_divs)) { + ret = wm8960_configure_sysclk(wm8960, freq_out, &i, &j, &k); + if (ret >= 0) { goto configure_clock; } else if (wm8960->clk_id != WM8960_SYSCLK_AUTO) { dev_err(codec->dev, "failed to configure clock\n"); return -EINVAL; } } - /* get a available pll out frequency and set pll */ - for (i = 0; i < ARRAY_SIZE(sysclk_divs); ++i) { - if (sysclk_divs[i] == -1) - continue; - for (j = 0; j < ARRAY_SIZE(dac_divs); ++j) { - sysclk = lrclk * dac_divs[j]; - freq_out = sysclk * sysclk_divs[i]; - - for (k = 0; k < ARRAY_SIZE(bclk_divs); ++k) { - if (sysclk == bclk * bclk_divs[k] / 10 && - is_pll_freq_available(freq_in, freq_out)) { - wm8960_set_pll(codec, - freq_in, freq_out); - break; - } else { - continue; - } - } - if (k != ARRAY_SIZE(bclk_divs)) - break; - } - if (j != ARRAY_SIZE(dac_divs)) - break; - } - if (i == ARRAY_SIZE(sysclk_divs)) { - dev_err(codec->dev, "failed to configure clock\n"); + ret = wm8960_configure_pll(codec, freq_in, &i, &j, &k); + if (ret < 0) { + dev_err(codec->dev, "failed to configure clock via PLL\n"); return -EINVAL; } @@ -765,8 +847,7 @@ static int wm8960_hw_params(struct snd_pcm_substream *substream, wm8960->is_stream_in_use[tx] = true; - if (snd_soc_codec_get_bias_level(codec) == SND_SOC_BIAS_ON && - !wm8960->is_stream_in_use[!tx]) + if (!wm8960->is_stream_in_use[!tx]) return wm8960_configure_clocking(codec); return 0; @@ -1025,11 +1106,6 @@ static bool is_pll_freq_available(unsigned int source, unsigned int target) target *= 4; Ndiv = target / source; - if (Ndiv < 6) { - source >>= 1; - Ndiv = target / source; - } - if ((Ndiv < 6) || (Ndiv > 12)) return false; @@ -1140,6 +1216,9 @@ static int wm8960_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, if (pll_id == WM8960_SYSCLK_AUTO) return 0; + if (is_pll_freq_available(freq_in, freq_out)) + return -EINVAL; + return wm8960_set_pll(codec, freq_in, freq_out); } @@ -1299,6 +1378,7 @@ static int wm8960_i2c_probe(struct i2c_client *i2c, struct wm8960_data *pdata = dev_get_platdata(&i2c->dev); struct wm8960_priv *wm8960; int ret; + int repeat_reset = 10; wm8960 = devm_kzalloc(&i2c->dev, sizeof(struct wm8960_priv), GFP_KERNEL); @@ -1320,7 +1400,11 @@ static int wm8960_i2c_probe(struct i2c_client *i2c, else if (i2c->dev.of_node) wm8960_set_pdata_from_of(i2c, &wm8960->pdata); - ret = wm8960_reset(wm8960->regmap); + do { + ret = wm8960_reset(wm8960->regmap); + repeat_reset--; + } while (repeat_reset > 0 && ret != 0); + if (ret != 0) { dev_err(&i2c->dev, "Failed to issue reset\n"); return ret; @@ -1350,6 +1434,8 @@ static int wm8960_i2c_probe(struct i2c_client *i2c, i2c_set_clientdata(i2c, wm8960); + pm_runtime_enable(&i2c->dev); + ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_wm8960, &wm8960_dai, 1); @@ -1362,6 +1448,35 @@ static int wm8960_i2c_remove(struct i2c_client *client) return 0; } +#ifdef CONFIG_PM +static int wm8960_runtime_resume(struct device *dev) +{ + struct wm8960_priv *wm8960 = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(wm8960->mclk); + if (ret) { + dev_err(dev, "Failed to enable MCLK: %d\n", ret); + return ret; + } + return 0; +} + +static int wm8960_runtime_suspend(struct device *dev) +{ + struct wm8960_priv *wm8960 = dev_get_drvdata(dev); + + clk_disable_unprepare(wm8960->mclk); + + return 0; +} +#endif + +static const struct dev_pm_ops wm8960_pm = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(wm8960_runtime_suspend, wm8960_runtime_resume, NULL) +}; + static const struct i2c_device_id wm8960_i2c_id[] = { { "wm8960", 0 }, { } @@ -1378,6 +1493,7 @@ static struct i2c_driver wm8960_i2c_driver = { .driver = { .name = "wm8960", .of_match_table = wm8960_of_match, + .pm = &wm8960_pm, }, .probe = wm8960_i2c_probe, .remove = wm8960_i2c_remove, diff --git a/sound/soc/codecs/wm8962.c b/sound/soc/codecs/wm8962.c index 0e8008d38161..f25c6ba0e251 100644 --- a/sound/soc/codecs/wm8962.c +++ b/sound/soc/codecs/wm8962.c @@ -2,6 +2,7 @@ * wm8962.c -- WM8962 ALSA SoC Audio driver * * Copyright 2010-2 Wolfson Microelectronics plc + * Copyright 2017 NXP * * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> * @@ -86,6 +87,7 @@ struct wm8962_priv { #endif int irq; + u32 cache_clocking2_reg; }; /* We can't use the same notifier block for more than one supply and @@ -1781,8 +1783,11 @@ SND_SOC_BYTES("HD Bass Coefficients", WM8962_HDBASS_AI_1, 30), SOC_DOUBLE("ALC Switch", WM8962_ALC1, WM8962_ALCL_ENA_SHIFT, WM8962_ALCR_ENA_SHIFT, 1, 0), -SND_SOC_BYTES_MASK("ALC Coefficients", WM8962_ALC1, 4, +SND_SOC_BYTES_MASK("ALC1", WM8962_ALC1, 1, WM8962_ALCL_ENA_MASK | WM8962_ALCR_ENA_MASK), +SND_SOC_BYTES("ALC2", WM8962_ALC2, 1), +SND_SOC_BYTES("ALC3", WM8962_ALC3, 1), +SND_SOC_BYTES("Noise Gate", WM8962_NOISE_GATE, 1), }; static const struct snd_kcontrol_new wm8962_spk_mono_controls[] = { @@ -2558,11 +2563,17 @@ static int wm8962_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_codec *codec = dai->codec; struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec); + snd_pcm_format_t sample_format = params_format(params); int i; int aif0 = 0; int adctl3 = 0; - wm8962->bclk = snd_soc_params_to_bclk(params); + if (sample_format == SNDRV_PCM_FORMAT_S20_3LE) + wm8962->bclk = params_rate(params) * + params_channels(params) * + params_physical_width(params); + else + wm8962->bclk = snd_soc_params_to_bclk(params); if (params_channels(params) == 1) wm8962->bclk *= 2; @@ -3820,6 +3831,10 @@ static int wm8962_runtime_resume(struct device *dev) regcache_sync(wm8962->regmap); + regmap_update_bits(wm8962->regmap, WM8962_CLOCKING2, + WM8962_SYSCLK_SRC_MASK, + wm8962->cache_clocking2_reg); + regmap_update_bits(wm8962->regmap, WM8962_ANTI_POP, WM8962_STARTUP_BIAS_ENA | WM8962_VMID_BUF_ENA, WM8962_STARTUP_BIAS_ENA | WM8962_VMID_BUF_ENA); @@ -3849,6 +3864,9 @@ static int wm8962_runtime_suspend(struct device *dev) WM8962_STARTUP_BIAS_ENA | WM8962_VMID_BUF_ENA, 0); + regmap_read(wm8962->regmap, WM8962_CLOCKING2, + &wm8962->cache_clocking2_reg); + regcache_cache_only(wm8962->regmap, true); regulator_bulk_disable(ARRAY_SIZE(wm8962->supplies), @@ -3861,6 +3879,7 @@ static int wm8962_runtime_suspend(struct device *dev) #endif static const struct dev_pm_ops wm8962_pm = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) SET_RUNTIME_PM_OPS(wm8962_runtime_suspend, wm8962_runtime_resume, NULL) }; diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c index f289762cd676..57cf28120759 100644 --- a/sound/soc/codecs/wm8994.c +++ b/sound/soc/codecs/wm8994.c @@ -3151,9 +3151,67 @@ static struct snd_soc_dai_driver wm8994_dai[] = { }; #ifdef CONFIG_PM +static void wm8994_store_context(struct wm8994 *wm8994) +{ + struct device *dev = wm8994->dev; + int ret; + + /* Disable LDO pulldowns while the device is suspended if we + * don't know that something will be driving them. */ + if (!wm8994->ldo_ena_always_driven) + wm8994_set_bits(wm8994, WM8994_PULL_CONTROL_2, + WM8994_LDO1ENA_PD | WM8994_LDO2ENA_PD, + WM8994_LDO1ENA_PD | WM8994_LDO2ENA_PD); + + /* Explicitly put the device into reset in case regulators + * don't get disabled in order to ensure consistent restart. + */ + wm8994_reg_write(wm8994, WM8994_SOFTWARE_RESET, + wm8994_reg_read(wm8994, WM8994_SOFTWARE_RESET)); + + regcache_mark_dirty(wm8994->regmap); + + /* Restore GPIO registers to prevent problems with mismatched + * pin configurations. + */ + ret = regcache_sync_region(wm8994->regmap, WM8994_GPIO_1, + WM8994_GPIO_11); + if (ret != 0) + dev_err(dev, "Failed to restore GPIO registers: %d\n", ret); + + /* In case one of the GPIOs is used as a wake input. */ + ret = regcache_sync_region(wm8994->regmap, + WM8994_INTERRUPT_STATUS_1_MASK, + WM8994_INTERRUPT_STATUS_1_MASK); + if (ret != 0) + dev_err(dev, "Failed to restore interrupt mask: %d\n", ret); + + regcache_cache_only(wm8994->regmap, true); +} + +static int wm8994_load_context(struct wm8994 *wm8994) +{ + struct device *dev = wm8994->dev; + int ret; + + regcache_cache_only(wm8994->regmap, false); + ret = regcache_sync(wm8994->regmap); + if (ret != 0) { + dev_err(dev, "Failed to restore register map: %d\n", ret); + return ret; + } + + /* Disable LDO pulldowns while the device is active */ + wm8994_set_bits(wm8994, WM8994_PULL_CONTROL_2, + WM8994_LDO1ENA_PD | WM8994_LDO2ENA_PD, 0); + + return 0; +} + static int wm8994_codec_suspend(struct snd_soc_codec *codec) { struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + struct wm8994 *control = wm8994->wm8994; int i, ret; for (i = 0; i < ARRAY_SIZE(wm8994->fll); i++) { @@ -3165,6 +3223,8 @@ static int wm8994_codec_suspend(struct snd_soc_codec *codec) i + 1, ret); } + wm8994_store_context(control); + snd_soc_codec_force_bias_level(codec, SND_SOC_BIAS_OFF); return 0; @@ -3173,8 +3233,15 @@ static int wm8994_codec_suspend(struct snd_soc_codec *codec) static int wm8994_codec_resume(struct snd_soc_codec *codec) { struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + struct wm8994 *control = wm8994->wm8994; int i, ret; + ret = wm8994_load_context(control); + if (ret != 0) { + dev_err(codec->dev, "Failed to load context: %d\n", ret); + return ret; + } + for (i = 0; i < ARRAY_SIZE(wm8994->fll); i++) { if (!wm8994->fll_suspend[i].out) continue; diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 8a2873a7899a..0fcf4065d0e4 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -24,6 +24,23 @@ config SND_SOC_FSL_SAI This option is only useful for out-of-tree drivers since in-tree drivers select it automatically. +config SND_SOC_FSL_ACM + tristate "Audio Clock Multiplexer (ACM) module support" + help + Say Y if you want to add Audio Clock Multiplexer (ACM) + support for the Freescale CPUs. + This option is only useful for out-of-tree drivers since + in-tree drivers select it automatically. + +config SND_SOC_FSL_AMIX + tristate "Audio Mixer (AMIX) module support" + select REGMAP_MMIO + help + Say Y if you want to add Audio Mixer (AMIX) + support for the Freescale CPUs. + This option is only useful for out-of-tree drivers since + in-tree drivers select it automatically. + config SND_SOC_FSL_SSI tristate "Synchronous Serial Interface module (SSI) support" select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n @@ -56,13 +73,47 @@ config SND_SOC_FSL_ESAI This option is only useful for out-of-tree drivers since in-tree drivers select it automatically. +config SND_SOC_FSL_MICFIL + tristate "Pulse Density Modulation Microphone Interface (MICFIL) module support" + select REGMAP_MMIO + select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y if you want to add Pulse Density Modulation microphone + interface (MICFIL) support for NXP. + +config SND_SOC_FSL_RPMSG_I2S + tristate "I2S base on the RPMSG support" + depends on RPMSG + help + Say Y if you want to add rpmsg i2s support for the Freescale CPUs. + which is depends on the rpmsg. + This option is only useful for out-of-tree drivers since + in-tree drivers select it automatically. + +config SND_SOC_FSL_DSP + tristate "dsp module support" + help + Say Y if you want to add hifi 4 support for the Freescale CPUs. + which is a DSP core for audio processing. + This option is only useful for out-of-tree drivers since + in-tree drivers select it automatically. + config SND_SOC_FSL_UTILS tristate +config SND_SOC_FSL_HDMI + tristate + config SND_SOC_IMX_PCM_DMA tristate select SND_SOC_GENERIC_DMAENGINE_PCM +config SND_SOC_IMX_PCM_RPMSG + tristate + depends on RPMSG + select SND_SOC_GENERIC_DMAENGINE_PCM + config SND_SOC_IMX_AUDMUX tristate "Digital Audio Mux module support" help @@ -80,7 +131,7 @@ config SND_POWERPC_SOC config SND_IMX_SOC tristate "SoC Audio for Freescale i.MX CPUs" - depends on ARCH_MXC || COMPILE_TEST + depends on ARCH_MXC || ARCH_MXC_ARM64 || COMPILE_TEST help Say Y or M if you want to add support for codecs attached to the i.MX CPUs. @@ -183,6 +234,11 @@ config SND_SOC_IMX_SSI tristate select SND_SOC_FSL_UTILS +config SND_SOC_IMX_HDMI_DMA + bool + select SND_SOC_GENERIC_DMAENGINE_PCM + select SND_SOC_IMX_PCM_DMA + comment "SoC Audio support for Freescale i.MX boards:" config SND_MXC_SOC_WM1133_EV1 @@ -231,6 +287,106 @@ config SND_SOC_EUKREA_TLV320 Enable I2S based access to the TLV320AIC23B codec attached to the SSI interface +config SND_SOC_IMX_AK4458 + tristate "SoC Audio support for i.MX boards with AK4458" + depends on OF && I2C + select SND_SOC_AK4458_I2C + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_SAI + select SND_SOC_FSL_UTILS + help + SoC Audio support for i.MX boards with AK4458 + Say Y if you want to add support for SoC audio on an i.MX board with + an AK4458 DAC. + +config SND_SOC_IMX_AK5558 + tristate "SoC Audio support for i.MX boards with AK5558" + depends on OF && I2C + select SND_SOC_AK5558 + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_SAI + select SND_SOC_FSL_UTILS + help + SoC Audio support for i.MX boards with AK5558 + Say Y if you want to add support for SoC audio on an i.MX board with + an AK5558 ADC. + +config SND_SOC_IMX_AK4497 + tristate "SoC Audio support for i.MX boards with AK4497" + depends on OF && I2C + select SND_SOC_AK4497 + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_SAI + select SND_SOC_FSL_UTILS + help + SoC Audio support for i.MX boards with AK4497 + Say Y if you want to add support for SoC audio on an i.MX board with + an AK4497 DAC. + +config SND_SOC_IMX_WM8960 + tristate "SoC Audio support for i.MX boards with wm8960" + depends on OF && I2C + select SND_SOC_WM8960 + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_SAI + select SND_SOC_FSL_UTILS + select SND_KCTL_JACK + help + SoC Audio support for i.MX boards with WM8960 + Say Y if you want to add support for SoC audio on an i.MX board with + a wm8960 codec. + +config SND_SOC_IMX_WM8524 + tristate "SoC Audio support for i.MX boards with wm8524" + depends on OF && I2C + select SND_SOC_WM8524 + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_SAI + select SND_SOC_FSL_UTILS + select SND_KCTL_JACK + help + SoC Audio support for i.MX boards with WM8524 + Say Y if you want to add support for SoC audio on an i.MX board with + a wm8524 codec. + +config SND_SOC_IMX_SII902X + tristate "SoC Audio support for i.MX boards with sii902x" + depends on OF && I2C + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_SAI + select SND_SOC_FSL_UTILS + help + SoC Audio support for i.MX boards with SII902X + Say Y if you want to add support for SoC audio on an i.MX board with + a sii902x. + +config SND_SOC_IMX_WM8958 + tristate "SoC Audio support for i.MX boards with wm8958" + depends on OF && I2C + select MFD_WM8994 + select SND_SOC_WM8994 + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_SAI + select SND_SOC_FSL_UTILS + select SND_KCTL_JACK + help + SoC Audio support for i.MX boards with WM8958 + Say Y if you want to add support for SoC audio on an i.MX board with + a wm8958 codec. + +config SND_SOC_IMX_CS42888 + tristate "SoC Audio support for i.MX boards with cs42888" + depends on OF && I2C + select SND_SOC_CS42XX8_I2C + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_ESAI + select SND_SOC_FSL_ASRC + select SND_SOC_FSL_UTILS + help + SoC Audio support for i.MX boards with cs42888 + Say Y if you want to add support for SoC audio on an i.MX board with + a cs42888 codec. + config SND_SOC_IMX_WM8962 tristate "SoC Audio support for i.MX boards with wm8962" depends on OF && I2C && INPUT @@ -238,10 +394,41 @@ config SND_SOC_IMX_WM8962 select SND_SOC_IMX_PCM_DMA select SND_SOC_IMX_AUDMUX select SND_SOC_FSL_SSI + select SND_KCTL_JACK help Say Y if you want to add support for SoC audio on an i.MX board with a wm8962 codec. +config SND_SOC_IMX_WM8962_ANDROID + tristate "SoC Audio support for i.MX boards with wm8962 in android" + depends on SND_SOC_IMX_WM8962=y + help + Say Y if you want to add support for SoC audio on an i.MX board with + a wm8962 codec in android. + +config SND_SOC_IMX_MICFIL + tristate "SoC Audio support for i.MX boards with micfil" + depends on OF && I2C + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_MICFIL + help + Soc Audio support for i.MX boards with micfil + Say Y if you want to add support for SoC audio on + an i.MX board with micfil. + +config SND_SOC_IMX_RPMSG + tristate "SoC Audio support for i.MX boards with rpmsg" + depends on OF && I2C && INPUT + select SND_SOC_IMX_PCM_RPMSG + select SND_SOC_FSL_RPMSG_I2S + select SND_SOC_RPMSG_WM8960 + select SND_SOC_RPMSG_CS42XX8 + help + SoC Audio support for i.MX boards with rpmsg. + There should be rpmsg devices defined in other core + Say Y if you want to add support for SoC audio on an i.MX board with + a rpmsg devices. + config SND_SOC_IMX_ES8328 tristate "SoC Audio support for i.MX boards with the ES8328 codec" depends on OF && (I2C || SPI) @@ -254,6 +441,17 @@ config SND_SOC_IMX_ES8328 Say Y if you want to add support for the ES8328 audio codec connected via SSI/I2S over either SPI or I2C. +config SND_SOC_IMX_XTOR + tristate "SoC Audio support for i.MX boards with xtor codec" + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_ESAI + select SND_SOC_FSL_SAI + select SND_SOC_FSL_UTILS + help + SoC Audio support for i.MX boards with xtor codec + Say Y if you want to add support for SoC audio on + an i.MX board with a xtor codec. + config SND_SOC_IMX_SGTL5000 tristate "SoC Audio support for i.MX boards with sgtl5000" depends on OF && I2C @@ -265,6 +463,14 @@ config SND_SOC_IMX_SGTL5000 Say Y if you want to add support for SoC audio on an i.MX board with a sgtl5000 codec. +config SND_SOC_IMX_MQS + tristate "SoC Audio support for i.MX boards with MQS" + depends on OF + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_SAI + select SND_SOC_FSL_MQS + select SND_SOC_FSL_UTILS + config SND_SOC_IMX_SPDIF tristate "SoC Audio support for i.MX boards with S/PDIF" select SND_SOC_IMX_PCM_DMA @@ -295,9 +501,61 @@ config SND_SOC_FSL_ASOC_CARD help ALSA SoC Audio support with ASRC feature for Freescale SoCs that have ESAI/SAI/SSI and connect with external CODECs such as WM8962, CS42888, - CS4271, CS4272 and SGTL5000. + CS4271, CS4272, and SGTL5000. Say Y if you want to add support for Freescale Generic ASoC Sound Card. +config SND_SOC_IMX_SI476X + tristate "SoC Audio support for i.MX boards with si476x" + select SND_SOC_IMX_PCM_DMA + select SND_SOC_IMX_AUDMUX + select SND_SOC_FSL_SSI + select SND_SOC_FSL_UTILS + select SND_SOC_SI476X + help + SoC Audio support for i.MX boards with SI476x + Say Y if you want to add support for Soc audio for the AMFM Tuner chip + SI476x module. + +config SND_SOC_IMX_HDMI + tristate "SoC Audio support for i.MX boards with HDMI port" + depends on MFD_MXC_HDMI + select SND_SOC_IMX_HDMI_DMA + select SND_SOC_FSL_HDMI + select SND_SOC_HDMI_CODEC + help + SoC Audio support for i.MX boards with HDMI audio + Say Y if you want to add support for SoC audio on an i.MX board with + IMX HDMI. + +config SND_SOC_IMX_AMIX + tristate "SoC Audio support for i.MX boards with AMIX" + select SND_SOC_FSL_AMIX + help + SoC Audio support for i.MX boards with AMIX + Say Y if you want to add support for SoC audio on an i.MX board with + an AMIX. + +config SND_SOC_IMX_CDNHDMI + tristate "SoC Audio support for i.MX boards with CDN HDMI port" + depends on DRM_IMX_HDP + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_SAI + select SND_SOC_HDMI_CODEC + help + SoC Audio support for i.MX boards with CDN HDMI audio + Say Y if you want to add support for SoC audio on an i.MX board with + IMX CDN HDMI. + +config SND_SOC_IMX_PDM_MIC + tristate "SoC Audio support for i.MX boards with PDM mic on SAI" + depends on OF + select SND_SOC_IMX_PDM_DMA + select SND_SOC_FSL_SAI + help + SoC Audio support for i.MX boards with PDM microphones on SAI + Say Y if you want to add support for SoC Audio support for i.MX boards + with PDM microphones on SAI. + endif # SND_IMX_SOC endmenu diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile index d28dc25c9375..3e41fd9abfbd 100644 --- a/sound/soc/fsl/Makefile +++ b/sound/soc/fsl/Makefile @@ -11,23 +11,37 @@ snd-soc-p1022-rdk-objs := p1022_rdk.o obj-$(CONFIG_SND_SOC_P1022_RDK) += snd-soc-p1022-rdk.o # Freescale SSI/DMA/SAI/SPDIF Support -snd-soc-fsl-asoc-card-objs := fsl-asoc-card.o +snd-soc-fsl-acm-objs := fsl_acm.o +snd-soc-fsl-amix-objs := fsl_amix.o snd-soc-fsl-asrc-objs := fsl_asrc.o fsl_asrc_dma.o +snd-soc-fsl-dma-workaround-objs := fsl_dma_workaround.o +snd-soc-fsl-dsp-objs := fsl_dsp.o fsl_dsp_proxy.o snd-soc-fsl-sai-objs := fsl_sai.o snd-soc-fsl-ssi-y := fsl_ssi.o snd-soc-fsl-ssi-$(CONFIG_DEBUG_FS) += fsl_ssi_dbg.o snd-soc-fsl-spdif-objs := fsl_spdif.o -snd-soc-fsl-esai-objs := fsl_esai.o +snd-soc-fsl-esai-objs := fsl_esai.o fsl_dma_workaround.o snd-soc-fsl-utils-objs := fsl_utils.o snd-soc-fsl-dma-objs := fsl_dma.o -obj-$(CONFIG_SND_SOC_FSL_ASOC_CARD) += snd-soc-fsl-asoc-card.o +snd-soc-fsl-rpmsg-i2s-objs := fsl_rpmsg_i2s.o +snd-soc-fsl-hdmi-objs := fsl_hdmi.o +snd-soc-fsl-asoc-card-objs := fsl-asoc-card.o +snd-soc-fsl-micfil-objs := fsl_micfil.o + +obj-$(CONFIG_SND_SOC_FSL_ACM) += snd-soc-fsl-acm.o +obj-$(CONFIG_SND_SOC_FSL_AMIX) += snd-soc-fsl-amix.o obj-$(CONFIG_SND_SOC_FSL_ASRC) += snd-soc-fsl-asrc.o +obj-$(CONFIG_SND_SOC_FSL_DSP) += snd-soc-fsl-dsp.o obj-$(CONFIG_SND_SOC_FSL_SAI) += snd-soc-fsl-sai.o obj-$(CONFIG_SND_SOC_FSL_SSI) += snd-soc-fsl-ssi.o obj-$(CONFIG_SND_SOC_FSL_SPDIF) += snd-soc-fsl-spdif.o obj-$(CONFIG_SND_SOC_FSL_ESAI) += snd-soc-fsl-esai.o obj-$(CONFIG_SND_SOC_FSL_UTILS) += snd-soc-fsl-utils.o +obj-$(CONFIG_SND_SOC_FSL_HDMI) += snd-soc-fsl-hdmi.o obj-$(CONFIG_SND_SOC_POWERPC_DMA) += snd-soc-fsl-dma.o +obj-$(CONFIG_SND_SOC_FSL_RPMSG_I2S) += snd-soc-fsl-rpmsg-i2s.o +obj-$(CONFIG_SND_SOC_FSL_ASOC_CARD) += snd-soc-fsl-asoc-card.o +obj-$(CONFIG_SND_SOC_FSL_MICFIL) += snd-soc-fsl-micfil.o # MPC5200 Platform Support obj-$(CONFIG_SND_MPC52xx_DMA) += mpc5200_dma.o @@ -45,7 +59,9 @@ obj-$(CONFIG_SND_SOC_IMX_SSI) += snd-soc-imx-ssi.o obj-$(CONFIG_SND_SOC_IMX_AUDMUX) += snd-soc-imx-audmux.o obj-$(CONFIG_SND_SOC_IMX_PCM_FIQ) += imx-pcm-fiq.o -obj-$(CONFIG_SND_SOC_IMX_PCM_DMA) += imx-pcm-dma.o +obj-$(CONFIG_SND_SOC_IMX_PCM_DMA) += imx-pcm-dma.o imx-pcm-dma-v2.o +obj-$(CONFIG_SND_SOC_IMX_PCM_RPMSG) += imx-pcm-rpmsg.o +obj-$(CONFIG_SND_SOC_IMX_HDMI_DMA) += imx-hdmi-dma.o hdmi_pcm.o # i.MX Machine Support snd-soc-eukrea-tlv320-objs := eukrea-tlv320.o @@ -53,17 +69,53 @@ snd-soc-phycore-ac97-objs := phycore-ac97.o snd-soc-mx27vis-aic32x4-objs := mx27vis-aic32x4.o snd-soc-wm1133-ev1-objs := wm1133-ev1.o snd-soc-imx-es8328-objs := imx-es8328.o +snd-soc-imx-cs42888-objs := imx-cs42888.o snd-soc-imx-sgtl5000-objs := imx-sgtl5000.o +snd-soc-imx-wm8958-objs := imx-wm8958.o +snd-soc-imx-wm8960-objs := imx-wm8960.o +snd-soc-imx-wm8524-objs := imx-wm8524.o snd-soc-imx-wm8962-objs := imx-wm8962.o +snd-soc-imx-xtor-objs := imx-xtor.o +snd-soc-imx-sii902x-objs := imx-sii902x.o snd-soc-imx-spdif-objs := imx-spdif.o snd-soc-imx-mc13783-objs := imx-mc13783.o +snd-soc-imx-mqs-objs := imx-mqs.o +snd-soc-imx-si476x-objs := imx-si476x.o +snd-soc-imx-hdmi-objs := imx-hdmi.o +snd-soc-imx-cdnhdmi-objs := imx-cdnhdmi.o +snd-soc-imx-rpmsg-objs := imx-rpmsg.o +snd-soc-imx-amix-objs := imx-amix.o +snd-soc-imx-pdm-objs := imx-pdm.o +snd-soc-imx-ak4458-objs := imx-ak4458.o +snd-soc-imx-ak5558-objs := imx-ak5558.o +snd-soc-imx-ak4497-objs := imx-ak4497.o +snd-soc-imx-micfil-objs := imx-micfil.o obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o obj-$(CONFIG_SND_SOC_MX27VIS_AIC32X4) += snd-soc-mx27vis-aic32x4.o obj-$(CONFIG_SND_MXC_SOC_WM1133_EV1) += snd-soc-wm1133-ev1.o obj-$(CONFIG_SND_SOC_IMX_ES8328) += snd-soc-imx-es8328.o +obj-$(CONFIG_SND_SOC_IMX_CS42888) += snd-soc-imx-cs42888.o obj-$(CONFIG_SND_SOC_IMX_SGTL5000) += snd-soc-imx-sgtl5000.o +obj-${CONFIG_SND_SOC_IMX_WM8958} += snd-soc-imx-wm8958.o +obj-$(CONFIG_SND_SOC_IMX_WM8960) += snd-soc-imx-wm8960.o +obj-$(CONFIG_SND_SOC_IMX_WM8524) += snd-soc-imx-wm8524.o obj-$(CONFIG_SND_SOC_IMX_WM8962) += snd-soc-imx-wm8962.o +obj-$(CONFIG_SND_SOC_IMX_XTOR) += snd-soc-imx-xtor.o +obj-$(CONFIG_SND_SOC_IMX_RPMSG) += snd-soc-imx-rpmsg.o +obj-$(CONFIG_SND_SOC_IMX_SII902X) += snd-soc-imx-sii902x.o obj-$(CONFIG_SND_SOC_IMX_SPDIF) += snd-soc-imx-spdif.o +obj-$(CONFIG_SND_SOC_IMX_MICFIL) += snd-soc-imx-micfil.o obj-$(CONFIG_SND_SOC_IMX_MC13783) += snd-soc-imx-mc13783.o +obj-$(CONFIG_SND_SOC_IMX_MQS) += snd-soc-imx-mqs.o +obj-$(CONFIG_SND_SOC_IMX_SI476X) += snd-soc-imx-si476x.o +obj-$(CONFIG_SND_SOC_IMX_AMIX) += snd-soc-imx-amix.o +obj-$(CONFIG_SND_SOC_IMX_PDM_MIC) += snd-soc-imx-pdm.o +obj-$(CONFIG_SND_SOC_IMX_AK4458) += snd-soc-imx-ak4458.o +obj-$(CONFIG_SND_SOC_IMX_AK4497) += snd-soc-imx-ak4497.o +obj-$(CONFIG_SND_SOC_IMX_AK5558) += snd-soc-imx-ak5558.o +obj-$(CONFIG_SND_SOC_IMX_CDNHDMI) += snd-soc-imx-cdnhdmi.o +obj-$(CONFIG_SND_SOC_IMX_HDMI) += snd-soc-imx-hdmi.o + +AFLAGS_hdmi_pcm.o := -march=armv7-a -mtune=cortex-a9 -mfpu=neon -mfloat-abi=softfp diff --git a/sound/soc/fsl/fsl_acm.c b/sound/soc/fsl/fsl_acm.c new file mode 100644 index 000000000000..d923e92149e9 --- /dev/null +++ b/sound/soc/fsl/fsl_acm.c @@ -0,0 +1,55 @@ +/* + * Freescale ALSA SoC Digital Audio Interface (ACM) driver. + * + * Copyright 2016 Freescale Semiconductor, Inc. + * + * This program is free software, you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 2 of the License, or(at your + * option) any later version. + * + */ +#include <linux/clk.h> +#include <linux/clkdev.h> +#include <linux/clk-provider.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/types.h> +#include <linux/module.h> +#include <linux/of_platform.h> + +static int fsl_acm_probe(struct platform_device *pdev) +{ + struct resource *res; + void __iomem *base; + + pr_info("***** imx8qm_acm_init *****\n"); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + return 0; +} + +static const struct of_device_id fsl_acm_ids[] = { + { .compatible = "nxp,imx8qm-acm", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, fsl_acm_ids); + +static struct platform_driver fsl_acm_driver = { + .probe = fsl_acm_probe, + .driver = { + .name = "fsl-acm", + .of_match_table = fsl_acm_ids, + }, +}; +module_platform_driver(fsl_acm_driver); + +MODULE_DESCRIPTION("Freescale Soc ACM Interface"); +MODULE_ALIAS("platform:fsl-acm"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/fsl/fsl_acm.h b/sound/soc/fsl/fsl_acm.h new file mode 100644 index 000000000000..fb05a7c95d5a --- /dev/null +++ b/sound/soc/fsl/fsl_acm.h @@ -0,0 +1,93 @@ +/* + * fsl_acm.h - ALSA ACM interface for the Freescale i.MX SoC + * + * Copyright 2017 NXP + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#ifndef _FSL_ACM_H +#define _FSL_ACM_H + +/* The offset of ACM control registers */ +#define AUD_CLK0_SEL_OFF 0x00000 +#define AUD_CLK1_SEL_OFF 0x10000 +#define MCLKOUT0_SEL_OFF 0x20000 +#define MCLKOUT1_SEL_OFF 0x30000 +#define ASRC0_CLK_SEL_OFF 0x40000 + +#define ESAI0_CLK_SEL_OFF 0x60000 +#define ESAI1_CLK_SEL_OFF 0x70000 +#define GPT0_CLK_SEL_OFF 0x80000 +#define GPT0_CAPIN1_SEL_OFF 0x80004 +#define GPT0_CAPIN2_SEL_OFF 0x80008 +#define GPT1_CLK_SEL_OFF 0x90000 +#define GPT1_CAPIN1_SEL_OFF 0x90004 +#define GPT1_CAPIN2_SEL_OFF 0x90008 +#define GPT2_CLK_SEL_OFF 0xA0000 +#define GPT2_CAPIN1_SEL_OFF 0xA0004 +#define GPT2_CAPIN2_SEL_OFF 0xA0008 +#define GPT3_CLK_SEL_OFF 0xB0000 +#define GPT3_CAPIN1_SEL_OFF 0xB0004 +#define GPT3_CAPIN2_SEL_OFF 0xB0008 +#define GPT4_CLK_SEL_OFF 0xC0000 +#define GPT4_CAPIN1_SEL_OFF 0xC0004 +#define GPT4_CAPIN2_SEL_OFF 0xC0008 +#define GPT5_CLK_SEL_OFF 0xD0000 +#define GPT5_CAPIN1_SEL_OFF 0xD0004 +#define GPT5_CAPIN2_SEL_OFF 0xD0008 +#define SAI0_MCLK_SEL_OFF 0xE0000 +#define SAI1_MCLK_SEL_OFF 0xF0000 +#define SAI2_MCLK_SEL_OFF 0x100000 +#define SAI3_MCLK_SEL_OFF 0x110000 +#define SAI_HDMIRX0_MCLK_SEL_OFF 0x120000 +#define SAI_HDMITX0_MCLK_SEL_OFF 0x130000 +#define SAI6_MCLK_SEL_OFF 0x140000 +#define SAI7_MCLK_SEL_OFF 0x150000 + +/* in imx8qxp SAI6=>SAI4, SAI7=>SAI5 */ +#define SAI4_MCLK_SEL_OFF 0x140000 +#define SAI5_MCLK_SEL_OFF 0x150000 + +#define SPDIF0_TX_CLK_SEL_OFF 0x1A0000 +#define SPDIF1_TX_CLK_SEL_OFF 0x1B0000 +#define MQS_HMCLK_SEL_OFF 0x1C0000 + +/* GPT CAPTURE Event definition*/ +#define IPI_USB0_SOF 0 +#define IPI_USB1_SOF 1 +#define IPI_USB30_ITP 2 +#define IPI_ETHERNET0_EVENT 3 +#define IPI_ETHERNET1_EVENT 4 +#define IPI_MPEG0_EVENT 5 +#define IPI_MPEG1_EVENT 6 +#define ASRC0_DMA1_REQ 7 +#define ASRC0_DMA2_REQ 8 +#define ASRC0_DMA3_REQ 9 +#define ASRC0_DMA4_REQ 10 +#define ASRC0_DMA5_REQ 11 +#define ASRC0_DMA6_REQ 12 +#define ESAI0_IPD_ESAI_RX_B 13 +#define ESAI0_IPD_ESAI_TX_B 14 +#define SPDIF0_DRQ0_SPDIF_B 15 +#define SPDIF0_DRQ1_SPDIF_B 16 +#define SPDIF1_DRQ0_SPDIF_B 17 +#define SPDIF1_DRQ1_SPDIF_B 18 +#define SAI_HDMIRX0_IPD_REQ_SAI_RX 19 +#define SAI_HDMITX0_IPD_REQ_SAI_TX 20 +#define ASRC1_DMA1_REQ 21 +#define ASRC1_DMA2_REQ 22 +#define ASRC1_DMA3_REQ 23 +#define ASRC1_DMA4_REQ 24 +#define ASRC1_DMA5_REQ 25 +#define ASRC1_DMA6_REQ 26 +#define ESAI1_IPD_ESAI_RX_B 27 +#define ESAI1_IPD_ESAI_TX_B 28 +#define SAI6_IPD_REQ_SAI_RX 29 +#define SAI6_IPD_REQ_SAI_TX 30 +#define SAI7_IPD_REQ_SAI_TX 31 + + +#endif /* _FSL_ACM_H */ diff --git a/sound/soc/fsl/fsl_amix.c b/sound/soc/fsl/fsl_amix.c new file mode 100644 index 000000000000..934bc55f8b63 --- /dev/null +++ b/sound/soc/fsl/fsl_amix.c @@ -0,0 +1,684 @@ +/* + * NXP AMIX ALSA SoC Digital Audio Interface (DAI) driver + * + * Copyright 2017 NXP + * + * Author: Viorel Suman <viorel.suman@nxp.com> + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/pm_runtime.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> + +#include "fsl_amix.h" + +#define SOC_ENUM_SINGLE_S(xreg, xshift, xtexts) \ + SOC_ENUM_SINGLE(xreg, xshift, ARRAY_SIZE(xtexts), xtexts) + +typedef int (*fsl_amix_state_handler)(struct snd_soc_component *comp, + unsigned int *mask, unsigned int *ctr); + +static const char + *tdm_sel[] = { "TDM1", "TDM2", }, + *mode_sel[] = { "Disabled", "TDM1", "TDM2", "Mixed", }, + *width_sel[] = { "16b", "18b", "20b", "24b", "32b", }, + *pol_sel[] = { "Positive edge", "Negative edge", }, + *endis_sel[] = { "Disabled", "Enabled", }, + *updn_sel[] = { "Downward", "Upward", }, + *mask_sel[] = { "Unmask", "Mask", }; + +static const struct soc_enum fsl_amix_enum[] = { +/* FSL_AMIX_CTR enums */ +SOC_ENUM_SINGLE_S(FSL_AMIX_CTR, FSL_AMIX_CTR_MIXCLK_SHIFT, tdm_sel), +SOC_ENUM_SINGLE_S(FSL_AMIX_CTR, FSL_AMIX_CTR_OUTSRC_SHIFT, mode_sel), +SOC_ENUM_SINGLE_S(FSL_AMIX_CTR, FSL_AMIX_CTR_OUTWIDTH_SHIFT, width_sel), +SOC_ENUM_SINGLE_S(FSL_AMIX_CTR, FSL_AMIX_CTR_OUTCKPOL_SHIFT, pol_sel), +SOC_ENUM_SINGLE_S(FSL_AMIX_CTR, FSL_AMIX_CTR_MASKRTDF_SHIFT, mask_sel), +SOC_ENUM_SINGLE_S(FSL_AMIX_CTR, FSL_AMIX_CTR_MASKCKDF_SHIFT, mask_sel), +SOC_ENUM_SINGLE_S(FSL_AMIX_CTR, FSL_AMIX_CTR_SYNCMODE_SHIFT, endis_sel), +SOC_ENUM_SINGLE_S(FSL_AMIX_CTR, FSL_AMIX_CTR_SYNCSRC_SHIFT, tdm_sel), +/* FSL_AMIX_ATCR0 enums */ +SOC_ENUM_SINGLE_S(FSL_AMIX_ATCR0, 0, endis_sel), +SOC_ENUM_SINGLE_S(FSL_AMIX_ATCR0, 1, updn_sel), +/* FSL_AMIX_ATCR1 enums */ +SOC_ENUM_SINGLE_S(FSL_AMIX_ATCR1, 0, endis_sel), +SOC_ENUM_SINGLE_S(FSL_AMIX_ATCR1, 1, updn_sel), +}; + +static int fsl_amix_state_dis_tdm1(struct snd_soc_component *comp, + unsigned int *mask, unsigned int *ctr) +{ + struct fsl_amix *priv = snd_soc_component_get_drvdata(comp); + /* Enforce the proper TDM is started */ + if (!(priv->tdms & BIT(0))) { + dev_err(comp->dev, "DIS->TDM1: TDM1 is not started!\n"); + return -EINVAL; + } + /* Set mix clock */ + (*mask) |= FSL_AMIX_CTR_MIXCLK_MASK; + (*ctr) |= FSL_AMIX_CTR_MIXCLK(0); + return 0; +} + +static int fsl_amix_state_dis_tdm2(struct snd_soc_component *comp, + unsigned int *mask, unsigned int *ctr) +{ + struct fsl_amix *priv = snd_soc_component_get_drvdata(comp); + /* Enforce the proper TDM is started */ + if (!(priv->tdms & BIT(1))) { + dev_err(comp->dev, "DIS->TDM2: TDM2 is not started!\n"); + return -EINVAL; + } + /* Set mix clock */ + (*mask) |= FSL_AMIX_CTR_MIXCLK_MASK; + (*ctr) |= FSL_AMIX_CTR_MIXCLK(1); + return 0; +} + +static int fsl_amix_state_tdm1_dis(struct snd_soc_component *comp, + unsigned int *mask, unsigned int *ctr) +{ + struct fsl_amix *priv = snd_soc_component_get_drvdata(comp); + /* Enforce the proper TDM is started */ + if (!(priv->tdms & BIT(0))) { + dev_err(comp->dev, "TDM1->DIS: TDM1 is not started!\n"); + return -EINVAL; + } + /* Keep mix clock unchanged */ + return 0; +} + +static int fsl_amix_state_tdm2_dis(struct snd_soc_component *comp, + unsigned int *mask, unsigned int *ctr) +{ + struct fsl_amix *priv = snd_soc_component_get_drvdata(comp); + /* Enforce the proper TDM is started */ + if (!(priv->tdms & BIT(1))) { + dev_err(comp->dev, "TDM2->DIS: TDM2 is not started!\n"); + return -EINVAL; + } + /* Keep mix clock unchanged */ + return 0; +} + +static int fsl_amix_state_dis_mix(struct snd_soc_component *comp, + unsigned int *mask, unsigned int *ctr) +{ + struct fsl_amix *priv = snd_soc_component_get_drvdata(comp); + /* Enforce all TDMs are started */ + if (priv->tdms != 3) { + dev_err(comp->dev, "DIS->MIX: Please start both TDMs!\n"); + return -EINVAL; + } + /* Keep mix clock unchanged */ + return 0; +} + +static int fsl_amix_state_tdm1_tdm2(struct snd_soc_component *comp, + unsigned int *mask, unsigned int *ctr) +{ + struct fsl_amix *priv = snd_soc_component_get_drvdata(comp); + /* Enforce all TDMs are started */ + if (priv->tdms != 3) { + dev_err(comp->dev, "TDM1->TDM2: Please start both TDMs!\n"); + return -EINVAL; + } + /* Set mix clock */ + (*mask) |= FSL_AMIX_CTR_MIXCLK_MASK; + (*ctr) |= FSL_AMIX_CTR_MIXCLK(1); + return 0; +} + +static int fsl_amix_state_tdm2_tdm1(struct snd_soc_component *comp, + unsigned int *mask, unsigned int *ctr) +{ + struct fsl_amix *priv = snd_soc_component_get_drvdata(comp); + /* Enforce all TDMs are started */ + if (priv->tdms != 3) { + dev_err(comp->dev, "TDM2->TDM1: Please start both TDMs!\n"); + return -EINVAL; + } + /* Set mix clock */ + (*mask) |= FSL_AMIX_CTR_MIXCLK_MASK; + (*ctr) |= FSL_AMIX_CTR_MIXCLK(0); + return 0; +} + +static int fsl_amix_state_tdm1_mix(struct snd_soc_component *comp, + unsigned int *mask, unsigned int *ctr) +{ + struct fsl_amix *priv = snd_soc_component_get_drvdata(comp); + /* Enforce all TDMs are started */ + if (priv->tdms != 3) { + dev_err(comp->dev, "TDM1->MIX: Please start both TDMs!\n"); + return -EINVAL; + } + /* Keep mix clock unchanged */ + return 0; +} + +static int fsl_amix_state_tdm2_mix(struct snd_soc_component *comp, + unsigned int *mask, unsigned int *ctr) +{ + struct fsl_amix *priv = snd_soc_component_get_drvdata(comp); + /* Enforce all TDMs are started */ + if (priv->tdms != 3) { + dev_err(comp->dev, "TDM2->MIX: Please start both TDMs!\n"); + return -EINVAL; + } + /* Keep mix clock unchanged */ + return 0; +} + +static int fsl_amix_state_mix_tdm1(struct snd_soc_component *comp, + unsigned int *mask, unsigned int *ctr) +{ + struct fsl_amix *priv = snd_soc_component_get_drvdata(comp); + /* Enforce all TDMs are started */ + if (priv->tdms != 3) { + dev_err(comp->dev, "MIX->TDM1: Please start both TDMs!\n"); + return -EINVAL; + } + /* Set mix clock */ + (*mask) |= FSL_AMIX_CTR_MIXCLK_MASK; + (*ctr) |= FSL_AMIX_CTR_MIXCLK(0); + return 0; +} + +static int fsl_amix_state_mix_tdm2(struct snd_soc_component *comp, + unsigned int *mask, unsigned int *ctr) +{ + struct fsl_amix *priv = snd_soc_component_get_drvdata(comp); + /* Enforce all TDMs are started */ + if (priv->tdms != 3) { + dev_err(comp->dev, "MIX->TDM2: Please start both TDMs!\n"); + return -EINVAL; + } + /* Set mix clock */ + (*mask) |= FSL_AMIX_CTR_MIXCLK_MASK; + (*ctr) |= FSL_AMIX_CTR_MIXCLK(1); + return 0; +} + +static int fsl_amix_state_mix_dis(struct snd_soc_component *comp, + unsigned int *mask, unsigned int *ctr) +{ + struct fsl_amix *priv = snd_soc_component_get_drvdata(comp); + /* Enforce all TDMs are started */ + if (priv->tdms != 3) { + dev_err(comp->dev, "MIX->DIS: Please start both TDMs!\n"); + return -EINVAL; + } + /* Keep mix clock unchanged */ + return 0; +} + +static const fsl_amix_state_handler state_machine[4][4] = { + /* From Disabled */ + { + 0, /* To Disabled, do nothing */ + fsl_amix_state_dis_tdm1, /* To TDM1*/ + fsl_amix_state_dis_tdm2, /* To TDM2 */ + fsl_amix_state_dis_mix /* To Mixed */ + }, + /* From TDM1 */ + { + fsl_amix_state_tdm1_dis, /* To Disabled */ + 0, /* To TDM1, do nothing */ + fsl_amix_state_tdm1_tdm2, /* To TDM2 */ + fsl_amix_state_tdm1_mix /* To Mixed */ + }, + /* From TDM2 */ + { + fsl_amix_state_tdm2_dis, /* To Disabled */ + fsl_amix_state_tdm2_tdm1, /* To TDM1 */ + 0, /* To TDM2, do nothing */ + fsl_amix_state_tdm2_mix /* To Mixed */ + }, + /* From Mixed */ + { + fsl_amix_state_mix_dis, /* To Disabled */ + fsl_amix_state_mix_tdm1, /* To TDM1 */ + fsl_amix_state_mix_tdm2, /* To TDM2 */ + 0 /* To Mixed, do nothing */ + } +}; + +static int fsl_amix_put_mix_clk_src(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_amix *priv = snd_soc_component_get_drvdata(comp); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + unsigned int reg_val, val, mix_clk; + int ret = 0; + + /* Get current state */ + ret = snd_soc_component_read(comp, FSL_AMIX_CTR, ®_val); + if (ret) + return ret; + + mix_clk = reg_val & 1; + val = snd_soc_enum_item_to_val(e, item[0]); + + dev_dbg(comp->dev, "[%s]: TDMs=x%08x, val=x%08x\n", __func__, priv->tdms, val); + + /** + * Ensure the current selected mixer clock is available + * for configuration propagation + */ + if (!(priv->tdms & BIT(mix_clk))) { + dev_err(comp->dev, "MIXCLK: A started TDM%d is required " + "for configuration propagation!\n", mix_clk + 1); + return -EINVAL; + } + + if (!(priv->tdms & BIT(val))) { + dev_err(comp->dev, "The selected clock source has " + "no TDM%d enabled!\n", val + 1); + return -EINVAL; + } + + return snd_soc_put_enum_double(kcontrol, ucontrol); +} + +static int fsl_amix_put_out_src(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_amix *priv = snd_soc_component_get_drvdata(comp); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + u32 out_src, mix_clk; + unsigned int reg_val, val, mask = 0, ctr = 0; + int ret = 0; + + /* Get current state */ + ret = snd_soc_component_read(comp, FSL_AMIX_CTR, ®_val); + if (ret) + return ret; + + /* "From" state */ + out_src = ((reg_val & FSL_AMIX_CTR_OUTSRC_MASK) >> FSL_AMIX_CTR_OUTSRC_SHIFT); + mix_clk = reg_val & 1; + + /* "To" state */ + val = snd_soc_enum_item_to_val(e, item[0]); + + dev_dbg(comp->dev, "[%s]: TDMs=x%08x, val=x%08x\n", __func__, priv->tdms, val); + + /* Check if state is changing ... */ + if (!state_machine[out_src][val]) + return 0; + /** + * Ensure the current selected mixer clock is available + * for configuration propagation + */ + if (!(priv->tdms & BIT(mix_clk))) { + dev_err(comp->dev, "MIXCLK: A started TDM%d is required " + "for configuration propagation!\n", mix_clk + 1); + return -EINVAL; + } + /* Check state transition constraints */ + ret = state_machine[out_src][val](comp, &mask, &ctr); + if (ret) + return ret; + + /* Complete transition to new state */ + mask |= FSL_AMIX_CTR_OUTSRC_MASK; + ctr |= FSL_AMIX_CTR_OUTSRC(val); + + return snd_soc_component_update_bits(comp, FSL_AMIX_CTR, mask, ctr); +} + +static const struct snd_kcontrol_new fsl_amix_snd_controls[] = { + /* FSL_AMIX_CTR controls */ + SOC_ENUM_EXT("Mixing Clock Source", fsl_amix_enum[0], + snd_soc_get_enum_double, fsl_amix_put_mix_clk_src), + SOC_ENUM_EXT("Output Source", fsl_amix_enum[1], + snd_soc_get_enum_double, fsl_amix_put_out_src), + SOC_ENUM("Output Width", fsl_amix_enum[2]), + SOC_ENUM("Output Clock Polarity", fsl_amix_enum[3]), + SOC_ENUM("Frame Rate Diff Error", fsl_amix_enum[4]), + SOC_ENUM("Clock Freq Diff Error", fsl_amix_enum[5]), + SOC_ENUM("Sync Mode Config", fsl_amix_enum[6]), + SOC_ENUM("Sync Mode Clk Source", fsl_amix_enum[7]), + /* TDM1 Attenuation controls */ + SOC_ENUM("TDM1 Attenuation", fsl_amix_enum[8]), + SOC_ENUM("TDM1 Attenuation Direction", fsl_amix_enum[9]), + SOC_SINGLE("TDM1 Attenuation Step Divider", FSL_AMIX_ATCR0, + 2, 0x00fff, 0), + SOC_SINGLE("TDM1 Attenuation Initial Value", FSL_AMIX_ATIVAL0, + 0, 0x3ffff, 0), + SOC_SINGLE("TDM1 Attenuation Step Up Factor", FSL_AMIX_ATSTPUP0, + 0, 0x3ffff, 0), + SOC_SINGLE("TDM1 Attenuation Step Down Factor", FSL_AMIX_ATSTPDN0, + 0, 0x3ffff, 0), + SOC_SINGLE("TDM1 Attenuation Step Target", FSL_AMIX_ATSTPTGT0, + 0, 0x3ffff, 0), + /* TDM2 Attenuation controls */ + SOC_ENUM("TDM2 Attenuation", fsl_amix_enum[10]), + SOC_ENUM("TDM2 Attenuation Direction", fsl_amix_enum[11]), + SOC_SINGLE("TDM2 Attenuation Step Divider", FSL_AMIX_ATCR1, + 2, 0x00fff, 0), + SOC_SINGLE("TDM2 Attenuation Initial Value", FSL_AMIX_ATIVAL1, + 0, 0x3ffff, 0), + SOC_SINGLE("TDM2 Attenuation Step Up Factor", FSL_AMIX_ATSTPUP1, + 0, 0x3ffff, 0), + SOC_SINGLE("TDM2 Attenuation Step Down Factor", FSL_AMIX_ATSTPDN1, + 0, 0x3ffff, 0), + SOC_SINGLE("TDM2 Attenuation Step Target", FSL_AMIX_ATSTPTGT1, + 0, 0x3ffff, 0), +}; + +static int fsl_amix_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct fsl_amix *priv = snd_soc_dai_get_drvdata(dai); + u32 mask = 0, ctr = 0; + + /* AMIX is working in DSP_A format only */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + break; + default: + return -EINVAL; + } + + /* For playback the AMIX is slave, and for record is master */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_NF: + /* Output data will be written on positive edge of the clock */ + ctr |= FSL_AMIX_CTR_OUTCKPOL(0); + break; + case SND_SOC_DAIFMT_NB_NF: + /* Output data will be written on negative edge of the clock */ + ctr |= FSL_AMIX_CTR_OUTCKPOL(1); + break; + default: + return -EINVAL; + } + + mask |= FSL_AMIX_CTR_OUTCKPOL_MASK; + + return regmap_update_bits(priv->regmap, FSL_AMIX_CTR, mask, ctr); +} + +static int fsl_amix_dai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct fsl_amix *priv = snd_soc_dai_get_drvdata(dai); + + /* Capture stream shall not be handled */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + return 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + priv->tdms |= BIT(dai->driver->id); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + priv->tdms &= ~BIT(dai->driver->id); + break; + default: + return -EINVAL; + } + + return 0; +} + +static struct snd_soc_dai_ops fsl_amix_dai_ops = { + .set_fmt = fsl_amix_dai_set_fmt, + .trigger = fsl_amix_dai_trigger, +}; + +static struct snd_soc_dai_driver fsl_amix_dai[] = { + { + .id = 0, + .name = "amix-0", + .playback = { + .stream_name = "AMIX-Playback-0", + .channels_min = 8, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 96000, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = FSL_AMIX_FORMATS, + }, + .capture = { + .stream_name = "AMIX-Capture-0", + .channels_min = 8, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 96000, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = FSL_AMIX_FORMATS, + }, + .ops = &fsl_amix_dai_ops, + }, + { + .id = 1, + .name = "amix-1", + .playback = { + .stream_name = "AMIX-Playback-1", + .channels_min = 8, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 96000, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = FSL_AMIX_FORMATS, + }, + .capture = { + .stream_name = "AMIX-Capture-1", + .channels_min = 8, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 96000, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = FSL_AMIX_FORMATS, + }, + .ops = &fsl_amix_dai_ops, + }, +}; + +static const struct snd_soc_component_driver fsl_amix_component = { + .name = "fsl-amix-dai", + .controls = fsl_amix_snd_controls, + .num_controls = ARRAY_SIZE(fsl_amix_snd_controls), +}; + +static bool fsl_amix_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case FSL_AMIX_CTR: + case FSL_AMIX_STR: + case FSL_AMIX_ATCR0: + case FSL_AMIX_ATIVAL0: + case FSL_AMIX_ATSTPUP0: + case FSL_AMIX_ATSTPDN0: + case FSL_AMIX_ATSTPTGT0: + case FSL_AMIX_ATTNVAL0: + case FSL_AMIX_ATSTP0: + case FSL_AMIX_ATCR1: + case FSL_AMIX_ATIVAL1: + case FSL_AMIX_ATSTPUP1: + case FSL_AMIX_ATSTPDN1: + case FSL_AMIX_ATSTPTGT1: + case FSL_AMIX_ATTNVAL1: + case FSL_AMIX_ATSTP1: + return true; + default: + return false; + } +} + +static bool fsl_amix_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case FSL_AMIX_CTR: + case FSL_AMIX_ATCR0: + case FSL_AMIX_ATIVAL0: + case FSL_AMIX_ATSTPUP0: + case FSL_AMIX_ATSTPDN0: + case FSL_AMIX_ATSTPTGT0: + case FSL_AMIX_ATCR1: + case FSL_AMIX_ATIVAL1: + case FSL_AMIX_ATSTPUP1: + case FSL_AMIX_ATSTPDN1: + case FSL_AMIX_ATSTPTGT1: + return true; + default: + return false; + } +} + +static struct reg_default fsl_amix_reg[] = { + { FSL_AMIX_CTR, 0x00060 }, + { FSL_AMIX_STR, 0x00003 }, + { FSL_AMIX_ATCR0, 0x00000 }, + { FSL_AMIX_ATIVAL0, 0x3FFFF }, + { FSL_AMIX_ATSTPUP0, 0x2AAAA }, + { FSL_AMIX_ATSTPDN0, 0x30000 }, + { FSL_AMIX_ATSTPTGT0, 0x00010 }, + { FSL_AMIX_ATTNVAL0, 0x00000 }, + { FSL_AMIX_ATSTP0, 0x00000 }, + { FSL_AMIX_ATCR1, 0x00000 }, + { FSL_AMIX_ATIVAL1, 0x3FFFF }, + { FSL_AMIX_ATSTPUP1, 0x2AAAA }, + { FSL_AMIX_ATSTPDN1, 0x30000 }, + { FSL_AMIX_ATSTPTGT1, 0x00010 }, + { FSL_AMIX_ATTNVAL1, 0x00000 }, + { FSL_AMIX_ATSTP1, 0x00000 }, +}; + +static const struct regmap_config fsl_amix_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = FSL_AMIX_ATSTP1, + .reg_defaults = fsl_amix_reg, + .num_reg_defaults = ARRAY_SIZE(fsl_amix_reg), + .readable_reg = fsl_amix_readable_reg, + .writeable_reg = fsl_amix_writeable_reg, + .cache_type = REGCACHE_FLAT, +}; + +static int fsl_amix_probe(struct platform_device *pdev) +{ + struct fsl_amix *priv; + struct resource *res; + void __iomem *regs; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->pdev = pdev; + + /* Get the addresses */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + priv->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "ipg", regs, + &fsl_amix_regmap_config); + if (IS_ERR(priv->regmap)) { + dev_err(&pdev->dev, "failed to init regmap\n"); + return PTR_ERR(priv->regmap); + } + + priv->ipg_clk = devm_clk_get(&pdev->dev, "ipg"); + if (IS_ERR(priv->ipg_clk)) { + dev_err(&pdev->dev, "failed to get ipg clock\n"); + return PTR_ERR(priv->ipg_clk); + } + + platform_set_drvdata(pdev, priv); + pm_runtime_enable(&pdev->dev); + + ret = devm_snd_soc_register_component(&pdev->dev, &fsl_amix_component, + fsl_amix_dai, ARRAY_SIZE(fsl_amix_dai)); + if (ret) { + dev_err(&pdev->dev, "failed to register ASoC DAI\n"); + return ret; + } + + return 0; +} + +#ifdef CONFIG_PM +static int fsl_amix_runtime_resume(struct device *dev) +{ + struct fsl_amix *priv = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(priv->ipg_clk); + if (ret) { + dev_err(dev, "Failed to enable IPG clock: %d\n", ret); + return ret; + } + + regcache_cache_only(priv->regmap, false); + regcache_mark_dirty(priv->regmap); + + return regcache_sync(priv->regmap); +} + +static int fsl_amix_runtime_suspend(struct device *dev) +{ + struct fsl_amix *priv = dev_get_drvdata(dev); + + regcache_cache_only(priv->regmap, true); + + clk_disable_unprepare(priv->ipg_clk); + + return 0; +} +#endif /* CONFIG_PM */ + +static const struct dev_pm_ops fsl_amix_pm = { + SET_RUNTIME_PM_OPS(fsl_amix_runtime_suspend, fsl_amix_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) +}; + +static const struct of_device_id fsl_amix_ids[] = { + { .compatible = "fsl,imx8qm-amix", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, fsl_amix_ids); + +static struct platform_driver fsl_amix_driver = { + .probe = fsl_amix_probe, + .driver = { + .name = "fsl-amix", + .of_match_table = fsl_amix_ids, + .pm = &fsl_amix_pm, + }, +}; +module_platform_driver(fsl_amix_driver); + +MODULE_DESCRIPTION("NXP AMIX ASoC DAI driver"); +MODULE_AUTHOR("Viorel Suman <viorel.suman@nxp.com>"); +MODULE_ALIAS("platform:fsl-amix"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/fsl_amix.h b/sound/soc/fsl/fsl_amix.h new file mode 100644 index 000000000000..582fb981849d --- /dev/null +++ b/sound/soc/fsl/fsl_amix.h @@ -0,0 +1,102 @@ +/* + * Copyright 2017 NXP Corp. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __FSL_AMIX_H +#define __FSL_AMIX_H + +#define FSL_AMIX_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) +/* AMIX Registers */ +#define FSL_AMIX_CTR 0x200 /* Control */ +#define FSL_AMIX_STR 0x204 /* Status */ + +#define FSL_AMIX_ATCR0 0x208 /* Attenuation Control */ +#define FSL_AMIX_ATIVAL0 0x20c /* Attenuation Initial Value */ +#define FSL_AMIX_ATSTPUP0 0x210 /* Attenuation step up factor */ +#define FSL_AMIX_ATSTPDN0 0x214 /* Attenuation step down factor */ +#define FSL_AMIX_ATSTPTGT0 0x218 /* Attenuation step target */ +#define FSL_AMIX_ATTNVAL0 0x21c /* Attenuation Value */ +#define FSL_AMIX_ATSTP0 0x220 /* Attenuation step number */ + +#define FSL_AMIX_ATCR1 0x228 /* Attenuation Control */ +#define FSL_AMIX_ATIVAL1 0x22c /* Attenuation Initial Value */ +#define FSL_AMIX_ATSTPUP1 0x230 /* Attenuation step up factor */ +#define FSL_AMIX_ATSTPDN1 0x234 /* Attenuation step down factor */ +#define FSL_AMIX_ATSTPTGT1 0x238 /* Attenuation step target */ +#define FSL_AMIX_ATTNVAL1 0x23c /* Attenuation Value */ +#define FSL_AMIX_ATSTP1 0x240 /* Attenuation step number */ + +/* AMIX Control Register */ +#define FSL_AMIX_CTR_MIXCLK_SHIFT 0 +#define FSL_AMIX_CTR_MIXCLK_MASK (1 << FSL_AMIX_CTR_MIXCLK_SHIFT) +#define FSL_AMIX_CTR_MIXCLK(i) ((i) << FSL_AMIX_CTR_MIXCLK_SHIFT) +#define FSL_AMIX_CTR_OUTSRC_SHIFT 1 +#define FSL_AMIX_CTR_OUTSRC_MASK (0x3 << FSL_AMIX_CTR_OUTSRC_SHIFT) +#define FSL_AMIX_CTR_OUTSRC(i) (((i) << FSL_AMIX_CTR_OUTSRC_SHIFT) \ + & FSL_AMIX_CTR_OUTSRC_MASK) +#define FSL_AMIX_CTR_OUTWIDTH_SHIFT 3 +#define FSL_AMIX_CTR_OUTWIDTH_MASK (0x7 << FSL_AMIX_CTR_OUTWIDTH_SHIFT) +#define FSL_AMIX_CTR_OUTWIDTH(i) (((i) << FSL_AMIX_CTR_OUTWIDTH_SHIFT) \ + & FSL_AMIX_CTR_OUTWIDTH_MASK) +#define FSL_AMIX_CTR_OUTCKPOL_SHIFT 6 +#define FSL_AMIX_CTR_OUTCKPOL_MASK (1 << FSL_AMIX_CTR_OUTCKPOL_SHIFT) +#define FSL_AMIX_CTR_OUTCKPOL(i) ((i) << FSL_AMIX_CTR_OUTCKPOL_SHIFT) +#define FSL_AMIX_CTR_MASKRTDF_SHIFT 7 +#define FSL_AMIX_CTR_MASKRTDF_MASK (1 << FSL_AMIX_CTR_MASKRTDF_SHIFT) +#define FSL_AMIX_CTR_MASKRTDF(i) ((i) << FSL_AMIX_CTR_MASKRTDF_SHIFT) +#define FSL_AMIX_CTR_MASKCKDF_SHIFT 8 +#define FSL_AMIX_CTR_MASKCKDF_MASK (1 << FSL_AMIX_CTR_MASKCKDF_SHIFT) +#define FSL_AMIX_CTR_MASKCKDF(i) ((i) << FSL_AMIX_CTR_MASKCKDF_SHIFT) +#define FSL_AMIX_CTR_SYNCMODE_SHIFT 9 +#define FSL_AMIX_CTR_SYNCMODE_MASK (1 << FSL_AMIX_CTR_SYNCMODE_SHIFT) +#define FSL_AMIX_CTR_SYNCMODE(i) ((i) << FSL_AMIX_CTR_SYNCMODE_SHIFT) +#define FSL_AMIX_CTR_SYNCSRC_SHIFT 10 +#define FSL_AMIX_CTR_SYNCSRC_MASK (1 << FSL_AMIX_CTR_SYNCSRC_SHIFT) +#define FSL_AMIX_CTR_SYNCSRC(i) ((i) << FSL_AMIX_CTR_SYNCSRC_SHIFT) + +/* AMIX Status Register */ +#define FSL_AMIX_STR_RATEDIFF BIT(0) +#define FSL_AMIX_STR_CLKDIFF BIT(1) +#define FSL_AMIX_STR_MIXSTAT_SHIFT 2 +#define FSL_AMIX_STR_MIXSTAT_MASK (0x3 << FSL_AMIX_STR_MIXSTAT_SHIFT) +#define FSL_AMIX_STR_MIXSTAT(i) ((i & FSL_AMIX_STR_MIXSTAT_MASK) \ + >> FSL_AMIX_STR_MIXSTAT_SHIFT) +/* AMIX Attenuation Control Register */ +#define FSL_AMIX_ATCR_AT_EN BIT(0) +#define FSL_AMIX_ATCR_AT_UPDN BIT(1) +#define FSL_AMIX_ATCR_ATSTPDIF_SHIFT 2 +#define FSL_AMIX_ATCR_ATSTPDFI_MASK (0xfff << FSL_AMIX_ATCR_ATSTPDIF_SHIFT) + +/* AMIX Attenuation Initial Value Register */ +#define FSL_AMIX_ATIVAL_ATINVAL_MASK 0x3FFFF + +/* AMIX Attenuation Step Up Factor Register */ +#define FSL_AMIX_ATSTPUP_ATSTEPUP_MASK 0x3FFFF + +/* AMIX Attenuation Step Down Factor Register */ +#define FSL_AMIX_ATSTPDN_ATSTEPDN_MASK 0x3FFFF + +/* AMIX Attenuation Step Target Register */ +#define FSL_AMIX_ATSTPTGT_ATSTPTG_MASK 0x3FFFF + +/* AMIX Attenuation Value Register */ +#define FSL_AMIX_ATTNVAL_ATCURVAL_MASK 0x3FFFF + +/* AMIX Attenuation Step Number Register */ +#define FSL_AMIX_ATSTP_STPCTR_MASK 0x3FFFF + +#define FSL_AMIX_MAX_DAIS 2 +struct fsl_amix { + struct platform_device *pdev; + struct regmap *regmap; + struct clk *ipg_clk; + u8 tdms; +}; + +#endif /* __FSL_AMIX_H */ diff --git a/sound/soc/fsl/fsl_asrc.c b/sound/soc/fsl/fsl_asrc.c index 88a438f6c2de..bc3fe4a3fc84 100644 --- a/sound/soc/fsl/fsl_asrc.c +++ b/sound/soc/fsl/fsl_asrc.c @@ -1,7 +1,8 @@ /* * Freescale ASRC ALSA SoC Digital Audio Interface (DAI) driver * - * Copyright (C) 2014 Freescale Semiconductor, Inc. + * Copyright (C) 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2017 NXP * * Author: Nicolin Chen <nicoleotsuka@gmail.com> * @@ -21,67 +22,86 @@ #include <sound/pcm_params.h> #include "fsl_asrc.h" +#include "imx-pcm.h" #define IDEAL_RATIO_DECIMAL_DEPTH 26 #define pair_err(fmt, ...) \ dev_err(&asrc_priv->pdev->dev, "Pair %c: " fmt, 'A' + index, ##__VA_ARGS__) +#define pair_warn(fmt, ...) \ + dev_warn(&asrc_priv->pdev->dev, "Pair %c: " fmt, 'A' + index, ##__VA_ARGS__) + #define pair_dbg(fmt, ...) \ dev_dbg(&asrc_priv->pdev->dev, "Pair %c: " fmt, 'A' + index, ##__VA_ARGS__) -/* Sample rates are aligned with that defined in pcm.h file */ -static const u8 process_option[][12][2] = { - /* 8kHz 11.025kHz 16kHz 22.05kHz 32kHz 44.1kHz 48kHz 64kHz 88.2kHz 96kHz 176kHz 192kHz */ - {{0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, /* 5512Hz */ - {{0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, /* 8kHz */ - {{0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, /* 11025Hz */ - {{1, 2}, {0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, /* 16kHz */ - {{1, 2}, {1, 2}, {0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, /* 22050Hz */ - {{1, 2}, {2, 1}, {2, 1}, {0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0},}, /* 32kHz */ - {{2, 2}, {2, 2}, {2, 1}, {2, 1}, {0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0},}, /* 44.1kHz */ - {{2, 2}, {2, 2}, {2, 1}, {2, 1}, {0, 2}, {0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0},}, /* 48kHz */ - {{2, 2}, {2, 2}, {2, 2}, {2, 1}, {1, 2}, {0, 2}, {0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0},}, /* 64kHz */ - {{2, 2}, {2, 2}, {2, 2}, {2, 2}, {1, 2}, {1, 2}, {1, 2}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1},}, /* 88.2kHz */ - {{2, 2}, {2, 2}, {2, 2}, {2, 2}, {1, 2}, {1, 2}, {1, 2}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1},}, /* 96kHz */ - {{2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 1}, {2, 1}, {2, 1}, {2, 1}, {2, 1},}, /* 176kHz */ - {{2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 1}, {2, 1}, {2, 1}, {2, 1}, {2, 1},}, /* 192kHz */ -}; - /* Corresponding to process_option */ -static int supported_input_rate[] = { - 5512, 8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000, 88200, - 96000, 176400, 192000, +static unsigned int supported_asrc_rate[] = { + 5512, 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, + 64000, 88200, 96000, 128000, 176400, 192000, }; -static int supported_asrc_rate[] = { - 8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000, 88200, 96000, 176400, 192000, +static struct snd_pcm_hw_constraint_list fsl_asrc_rate_constraints = { + .count = ARRAY_SIZE(supported_asrc_rate), + .list = supported_asrc_rate, }; /** * The following tables map the relationship between asrc_inclk/asrc_outclk in * fsl_asrc.h and the registers of ASRCSR */ +#define CLK_MAP_NUM 48 static unsigned char input_clk_map_imx35[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, }; static unsigned char output_clk_map_imx35[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, }; /* i.MX53 uses the same map for input and output */ static unsigned char input_clk_map_imx53[] = { /* 0x0 0x1 0x2 0x3 0x4 0x5 0x6 0x7 0x8 0x9 0xa 0xb 0xc 0xd 0xe 0xf */ 0x0, 0x1, 0x2, 0x7, 0x4, 0x5, 0x6, 0x3, 0x8, 0x9, 0xa, 0xb, 0xc, 0xf, 0xe, 0xd, + 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, + 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, }; static unsigned char output_clk_map_imx53[] = { /* 0x0 0x1 0x2 0x3 0x4 0x5 0x6 0x7 0x8 0x9 0xa 0xb 0xc 0xd 0xe 0xf */ 0x8, 0x9, 0xa, 0x7, 0xc, 0x5, 0x6, 0xb, 0x0, 0x1, 0x2, 0x3, 0x4, 0xf, 0xe, 0xd, + 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, + 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, +}; + +/* i.MX8 uses the same map for input and output */ +static unsigned char input_clk_map_imx8_0[] = { + 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0x0, + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, + 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, }; -static unsigned char *clk_map[2]; +static unsigned char output_clk_map_imx8_0[] = { + 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0x0, + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, + 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, +}; + +static unsigned char input_clk_map_imx8_1[] = { + 0xf, 0xf, 0xf, 0xf, 0xf, 0x7, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0x0, + 0x0, 0x1, 0x2, 0x3, 0xb, 0xc, 0xf, 0xf, 0xd, 0xe, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, + 0x4, 0x5, 0x6, 0xf, 0x8, 0x9, 0xa, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, +}; + +static unsigned char output_clk_map_imx8_1[] = { + 0xf, 0xf, 0xf, 0xf, 0xf, 0x7, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0x0, + 0x0, 0x1, 0x2, 0x3, 0xb, 0xc, 0xf, 0xf, 0xd, 0xe, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, + 0x4, 0x5, 0x6, 0xf, 0x8, 0x9, 0xa, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, +}; /** * Request ASRC pair @@ -90,7 +110,7 @@ static unsigned char *clk_map[2]; * within range [ANCA, ANCA+ANCB-1], depends on the channels of pair A * while pair A and pair C are comparatively independent. */ -static int fsl_asrc_request_pair(int channels, struct fsl_asrc_pair *pair) +int fsl_asrc_request_pair(int channels, struct fsl_asrc_pair *pair) { enum asrc_pair_index index = ASRC_INVALID_PAIR; struct fsl_asrc *asrc_priv = pair->asrc_priv; @@ -113,7 +133,8 @@ static int fsl_asrc_request_pair(int channels, struct fsl_asrc_pair *pair) if (index == ASRC_INVALID_PAIR) { dev_err(dev, "all pairs are busy now\n"); ret = -EBUSY; - } else if (asrc_priv->channel_avail < channels) { + } else if (asrc_priv->channel_avail < channels || + (asrc_priv->channel_bits < 4 && channels % 2 != 0)) { dev_err(dev, "can't afford required channels: %d\n", channels); ret = -EINVAL; } else { @@ -128,12 +149,53 @@ static int fsl_asrc_request_pair(int channels, struct fsl_asrc_pair *pair) return ret; } +static int proc_autosel(int Fsin, int Fsout, int *pre_proc, int *post_proc) +{ + bool det_out_op2_cond; + bool det_out_op0_cond; + det_out_op2_cond = (((Fsin * 15 > Fsout * 16) & (Fsout < 56000)) | + ((Fsin > 56000) & (Fsout < 56000))); + det_out_op0_cond = (Fsin * 23 < Fsout * 8); + + /* + * Not supported case: Tsout>16.125*Tsin, and Tsout>8.125*Tsin. + */ + if (Fsin * 8 > 129 * Fsout) + *pre_proc = 5; + else if (Fsin * 8 > 65 * Fsout) + *pre_proc = 4; + else if (Fsin * 8 > 33 * Fsout) + *pre_proc = 2; + else if (Fsin * 8 > 15 * Fsout) { + if (Fsin > 152000) + *pre_proc = 2; + else + *pre_proc = 1; + } else if (Fsin < 76000) + *pre_proc = 0; + else if (Fsin > 152000) + *pre_proc = 2; + else + *pre_proc = 1; + + if (det_out_op2_cond) + *post_proc = 2; + else if (det_out_op0_cond) + *post_proc = 0; + else + *post_proc = 1; + + if (*pre_proc == 4 || *pre_proc == 5) + return -EINVAL; + return 0; +} + /** * Release ASRC pair * * It clears the resource from asrc_priv and releases the occupied channels. */ -static void fsl_asrc_release_pair(struct fsl_asrc_pair *pair) +void fsl_asrc_release_pair(struct fsl_asrc_pair *pair) { struct fsl_asrc *asrc_priv = pair->asrc_priv; enum asrc_pair_index index = pair->index; @@ -235,7 +297,7 @@ static int fsl_asrc_set_ideal_ratio(struct fsl_asrc_pair *pair, * of struct asrc_config which includes in/output sample rate, width, channel * and clock settings. */ -static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair) +static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair, bool p2p_in, bool p2p_out) { struct asrc_config *config = pair->config; struct fsl_asrc *asrc_priv = pair->asrc_priv; @@ -243,8 +305,10 @@ static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair) u32 inrate, outrate, indiv, outdiv; u32 clk_index[2], div[2]; int in, out, channels; + int pre_proc, post_proc; struct clk *clk; bool ideal; + int ret; if (!config) { pair_err("invalid pair config\n"); @@ -268,11 +332,11 @@ static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair) ideal = config->inclk == INCLK_NONE; /* Validate input and output sample rates */ - for (in = 0; in < ARRAY_SIZE(supported_input_rate); in++) - if (inrate == supported_input_rate[in]) + for (in = 0; in < ARRAY_SIZE(supported_asrc_rate); in++) + if (inrate == supported_asrc_rate[in]) break; - if (in == ARRAY_SIZE(supported_input_rate)) { + if (in == ARRAY_SIZE(supported_asrc_rate)) { pair_err("unsupported input sample rate: %dHz\n", inrate); return -EINVAL; } @@ -294,8 +358,8 @@ static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair) } /* Validate input and output clock sources */ - clk_index[IN] = clk_map[IN][config->inclk]; - clk_index[OUT] = clk_map[OUT][config->outclk]; + clk_index[IN] = asrc_priv->clk_map[IN][config->inclk]; + clk_index[OUT] = asrc_priv->clk_map[OUT][config->outclk]; /* We only have output clock for ideal ratio mode */ clk = asrc_priv->asrck_clk[clk_index[ideal ? OUT : IN]]; @@ -309,11 +373,17 @@ static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair) clk = asrc_priv->asrck_clk[clk_index[OUT]]; - /* Use fixed output rate for Ideal Ratio mode (INCLK_NONE) */ - if (ideal) - div[OUT] = clk_get_rate(clk) / IDEAL_RATIO_RATE; - else + /* + * When P2P mode, output rate should align with the out samplerate. + * if set too high output rate, there will be lots of Overload. + * When M2M mode, output rate should also need to align with the out + * samplerate, but M2M must use less time to achieve good performance. + */ + if (p2p_out || p2p_in) div[OUT] = clk_get_rate(clk) / outrate; + else + div[OUT] = clk_get_rate(clk) / IDEAL_RATIO_RATE; + if (div[OUT] == 0) { pair_err("failed to support output sample rate %dHz by asrck_%x\n", @@ -321,6 +391,17 @@ static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair) return -EINVAL; } + if (div[IN] > 1024 && div[OUT] > 1024) { + pair_warn("both divider (%d, %d) are larger than threshold\n", + div[IN], div[OUT]); + } + + if (div[IN] > 1024) + div[IN] = 1024; + + if (div[OUT] > 1024) + div[OUT] = 1024; + /* Set the channel number */ channels = config->channel_num; @@ -335,8 +416,11 @@ static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair) /* Default setting: Automatic selection for processing mode */ regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, ASRCTR_ATSi_MASK(index), ASRCTR_ATS(index)); + + /* Default setting: use internal measured ratio */ regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, - ASRCTR_USRi_MASK(index), 0); + ASRCTR_USRi_MASK(index) | ASRCTR_IDRi_MASK(index), + ASRCTR_USR(index)); /* Set the input and output clock sources */ regmap_update_bits(asrc_priv->regmap, REG_ASRCSR, @@ -381,11 +465,17 @@ static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair) ASRCTR_IDRi_MASK(index) | ASRCTR_USRi_MASK(index), ASRCTR_IDR(index) | ASRCTR_USR(index)); + ret = proc_autosel(inrate, outrate, &pre_proc, &post_proc); + if (ret) { + pair_err("No supported pre-processing options\n"); + return ret; + } + /* Apply configurations for pre- and post-processing */ regmap_update_bits(asrc_priv->regmap, REG_ASRCFG, ASRCFG_PREMODi_MASK(index) | ASRCFG_POSTMODi_MASK(index), - ASRCFG_PREMOD(index, process_option[in][out][0]) | - ASRCFG_POSTMOD(index, process_option[in][out][1])); + ASRCFG_PREMOD(index, pre_proc) | + ASRCFG_POSTMOD(index, post_proc)); return fsl_asrc_set_ideal_ratio(pair, inrate, outrate); } @@ -449,6 +539,61 @@ struct dma_chan *fsl_asrc_get_dma_channel(struct fsl_asrc_pair *pair, bool dir) } EXPORT_SYMBOL_GPL(fsl_asrc_get_dma_channel); +static int fsl_asrc_select_clk(struct fsl_asrc *asrc_priv, + struct fsl_asrc_pair *pair, + int in_rate, + int out_rate) +{ + struct asrc_config *config = pair->config; + int clk_rate; + int clk_index; + int i = 0, j = 0; + int rate[2]; + int select_clk[2]; + bool clk_sel[2]; + + rate[0] = in_rate; + rate[1] = out_rate; + + /*select proper clock for asrc p2p mode*/ + for (j = 0; j < 2; j++) { + for (i = 0; i < CLK_MAP_NUM; i++) { + clk_index = asrc_priv->clk_map[j][i]; + clk_rate = clk_get_rate(asrc_priv->asrck_clk[clk_index]); + if (clk_rate != 0 && (clk_rate / rate[j]) <= 1024 && + (clk_rate % rate[j]) == 0) + break; + } + + if (i == CLK_MAP_NUM) { + select_clk[j] = OUTCLK_ASRCK1_CLK; + clk_sel[j] = false; + } else { + select_clk[j] = i; + clk_sel[j] = true; + } + } + + if (clk_sel[0] != true || clk_sel[1] != true) + select_clk[IN] = INCLK_NONE; + + config->inclk = select_clk[IN]; + config->outclk = select_clk[OUT]; + + /* + * FIXME: workaroud for 176400/192000 with 8 channel input case + * the output sample rate is 48kHz. + * with ideal ratio mode, the asrc seems has performance issue + * that the output sound is not correct. so switch to non-ideal + * ratio mode + */ + if (config->channel_num >= 8 && config->input_sample_rate >= 176400 + && config->inclk == INCLK_NONE) + config->inclk = INCLK_ASRCK1_CLK; + + return 0; +} + static int fsl_asrc_dai_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) @@ -468,6 +613,7 @@ static int fsl_asrc_dai_hw_params(struct snd_pcm_substream *substream, return ret; } + pair->pair_streams |= BIT(substream->stream); pair->config = &config; if (width == 16) @@ -482,25 +628,46 @@ static int fsl_asrc_dai_hw_params(struct snd_pcm_substream *substream, config.pair = pair->index; config.channel_num = channels; - config.inclk = INCLK_NONE; - config.outclk = OUTCLK_ASRCK1_CLK; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { config.input_word_width = width; config.output_word_width = word_width; config.input_sample_rate = rate; config.output_sample_rate = asrc_priv->asrc_rate; + + ret = fsl_asrc_select_clk(asrc_priv, pair, + config.input_sample_rate, + config.output_sample_rate); + if (ret) { + dev_err(dai->dev, "fail to select clock\n"); + return ret; + } + + ret = fsl_asrc_config_pair(pair, false, true); + if (ret) { + dev_err(dai->dev, "fail to config asrc pair\n"); + return ret; + } + } else { config.input_word_width = word_width; config.output_word_width = width; config.input_sample_rate = asrc_priv->asrc_rate; config.output_sample_rate = rate; - } - ret = fsl_asrc_config_pair(pair); - if (ret) { - dev_err(dai->dev, "fail to config asrc pair\n"); - return ret; + ret = fsl_asrc_select_clk(asrc_priv, pair, + config.input_sample_rate, + config.output_sample_rate); + if (ret) { + dev_err(dai->dev, "fail to select clock\n"); + return ret; + } + + ret = fsl_asrc_config_pair(pair, true, false); + if (ret) { + dev_err(dai->dev, "fail to config asrc pair\n"); + return ret; + } } return 0; @@ -512,8 +679,10 @@ static int fsl_asrc_dai_hw_free(struct snd_pcm_substream *substream, struct snd_pcm_runtime *runtime = substream->runtime; struct fsl_asrc_pair *pair = runtime->private_data; - if (pair) + if (pair && (pair->pair_streams & BIT(substream->stream))) { fsl_asrc_release_pair(pair); + pair->pair_streams &= ~BIT(substream->stream); + } return 0; } @@ -529,6 +698,8 @@ static int fsl_asrc_dai_trigger(struct snd_pcm_substream *substream, int cmd, case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: fsl_asrc_start_pair(pair); + /* Output enough data to content the DMA burstsize of BE */ + mdelay(1); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: @@ -542,7 +713,28 @@ static int fsl_asrc_dai_trigger(struct snd_pcm_substream *substream, int cmd, return 0; } +static int fsl_asrc_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct fsl_asrc *asrc_priv = snd_soc_dai_get_drvdata(cpu_dai); + + asrc_priv->substream[substream->stream] = substream; + + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &fsl_asrc_rate_constraints); +} + +static void fsl_asrc_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct fsl_asrc *asrc_priv = snd_soc_dai_get_drvdata(cpu_dai); + + asrc_priv->substream[substream->stream] = NULL; +} + static struct snd_soc_dai_ops fsl_asrc_dai_ops = { + .startup = fsl_asrc_dai_startup, + .shutdown = fsl_asrc_dai_shutdown, .hw_params = fsl_asrc_dai_hw_params, .hw_free = fsl_asrc_dai_hw_free, .trigger = fsl_asrc_dai_trigger, @@ -561,7 +753,7 @@ static int fsl_asrc_dai_probe(struct snd_soc_dai *dai) #define FSL_ASRC_RATES SNDRV_PCM_RATE_8000_192000 #define FSL_ASRC_FORMATS (SNDRV_PCM_FMTBIT_S24_LE | \ SNDRV_PCM_FMTBIT_S16_LE | \ - SNDRV_PCM_FMTBIT_S20_3LE) + SNDRV_PCM_FMTBIT_S24_3LE) static struct snd_soc_dai_driver fsl_asrc_dai = { .probe = fsl_asrc_dai_probe, @@ -569,14 +761,18 @@ static struct snd_soc_dai_driver fsl_asrc_dai = { .stream_name = "ASRC-Playback", .channels_min = 1, .channels_max = 10, - .rates = FSL_ASRC_RATES, + .rate_min = 5512, + .rate_max = 192000, + .rates = SNDRV_PCM_RATE_KNOT, .formats = FSL_ASRC_FORMATS, }, .capture = { .stream_name = "ASRC-Capture", .channels_min = 1, .channels_max = 10, - .rates = FSL_ASRC_RATES, + .rate_min = 5512, + .rate_max = 192000, + .rates = SNDRV_PCM_RATE_KNOT, .formats = FSL_ASRC_FORMATS, }, .ops = &fsl_asrc_dai_ops, @@ -729,11 +925,71 @@ static const struct regmap_config fsl_asrc_regmap_config = { .cache_type = REGCACHE_FLAT, }; +#include "fsl_asrc_m2m.c" + +static bool fsl_asrc_check_xrun(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_dmaengine_dai_dma_data *dma_params_be = NULL; + struct snd_pcm_substream *be_substream; + struct snd_soc_dpcm *dpcm; + int ret = 0; + + /* find the be for this fe stream */ + list_for_each_entry(dpcm, &rtd->dpcm[substream->stream].be_clients, list_be) { + struct snd_soc_pcm_runtime *be = dpcm->be; + struct snd_soc_dai *dai = be->cpu_dai; + + if (dpcm->fe != rtd) + continue; + + be_substream = snd_soc_dpcm_get_substream(be, substream->stream); + dma_params_be = snd_soc_dai_get_dma_data(dai, be_substream); + if (dma_params_be->check_xrun && dma_params_be->check_xrun(be_substream)) + ret = 1; + } + + return ret; +} + +static void fsl_asrc_reset(struct snd_pcm_substream *substream, bool stop) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_asrc *asrc_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct snd_dmaengine_dai_dma_data *dma_params_be = NULL; + struct snd_soc_dpcm *dpcm; + struct snd_pcm_substream *be_substream; + unsigned long flags = 0; + + if (stop) + imx_stop_lock_pcm_streams(asrc_priv->substream, 2, &flags); + + /* find the be for this fe stream */ + list_for_each_entry(dpcm, &rtd->dpcm[substream->stream].be_clients, list_be) { + struct snd_soc_pcm_runtime *be = dpcm->be; + struct snd_soc_dai *dai = be->cpu_dai; + + if (dpcm->fe != rtd) + continue; + + be_substream = snd_soc_dpcm_get_substream(be, substream->stream); + dma_params_be = snd_soc_dai_get_dma_data(dai, be_substream); + dma_params_be->device_reset(be_substream, 0); + break; + } + + if (stop) + imx_start_unlock_pcm_streams(asrc_priv->substream, 2, &flags); +} + /** * Initialize ASRC registers with a default configurations */ static int fsl_asrc_init(struct fsl_asrc *asrc_priv) { + unsigned long ipg_rate; + /* Halt ASRC internal FP when input FIFO needs data for pair A, B, C */ regmap_write(asrc_priv->regmap, REG_ASRCTR, ASRCTR_ASRCEN); @@ -751,11 +1007,12 @@ static int fsl_asrc_init(struct fsl_asrc *asrc_priv) regmap_update_bits(asrc_priv->regmap, REG_ASRTFR1, ASRTFR1_TF_BASE_MASK, ASRTFR1_TF_BASE(0xfc)); - /* Set the processing clock for 76KHz to 133M */ - regmap_write(asrc_priv->regmap, REG_ASR76K, 0x06D6); - - /* Set the processing clock for 56KHz to 133M */ - return regmap_write(asrc_priv->regmap, REG_ASR56K, 0x0947); + ipg_rate = clk_get_rate(asrc_priv->ipg_clk); + /* Set the period of the 76KHz and 56KHz sampling clocks based on + * the ASRC processing clock. + */ + regmap_write(asrc_priv->regmap, REG_ASR76K, ipg_rate / 76000); + return regmap_write(asrc_priv->regmap, REG_ASR56K, ipg_rate / 56000); } /** @@ -881,12 +1138,32 @@ static int fsl_asrc_probe(struct platform_device *pdev) if (of_device_is_compatible(np, "fsl,imx35-asrc")) { asrc_priv->channel_bits = 3; - clk_map[IN] = input_clk_map_imx35; - clk_map[OUT] = output_clk_map_imx35; - } else { + strncpy(asrc_priv->name, "mxc_asrc", + sizeof(asrc_priv->name) - 1); + asrc_priv->clk_map[IN] = input_clk_map_imx35; + asrc_priv->clk_map[OUT] = output_clk_map_imx35; + asrc_priv->dma_type = DMA_SDMA; + } else if (of_device_is_compatible(np, "fsl,imx53-asrc")) { + asrc_priv->channel_bits = 4; + strncpy(asrc_priv->name, "mxc_asrc", + sizeof(asrc_priv->name) - 1); + asrc_priv->clk_map[IN] = input_clk_map_imx53; + asrc_priv->clk_map[OUT] = output_clk_map_imx53; + asrc_priv->dma_type = DMA_SDMA; + } else if (of_device_is_compatible(np, "fsl,imx8qm-asrc0")) { + asrc_priv->channel_bits = 4; + strncpy(asrc_priv->name, "mxc_asrc", + sizeof(asrc_priv->name) - 1); + asrc_priv->clk_map[IN] = input_clk_map_imx8_0; + asrc_priv->clk_map[OUT] = output_clk_map_imx8_0; + asrc_priv->dma_type = DMA_EDMA; + } else if (of_device_is_compatible(np, "fsl,imx8qm-asrc1")) { asrc_priv->channel_bits = 4; - clk_map[IN] = input_clk_map_imx53; - clk_map[OUT] = output_clk_map_imx53; + strncpy(asrc_priv->name, "mxc_asrc1", + sizeof(asrc_priv->name) - 1); + asrc_priv->clk_map[IN] = input_clk_map_imx8_1; + asrc_priv->clk_map[OUT] = output_clk_map_imx8_1; + asrc_priv->dma_type = DMA_EDMA; } ret = fsl_asrc_init(asrc_priv); @@ -911,6 +1188,11 @@ static int fsl_asrc_probe(struct platform_device *pdev) return ret; } + asrc_priv->dma_params_tx.check_xrun = fsl_asrc_check_xrun; + asrc_priv->dma_params_rx.check_xrun = fsl_asrc_check_xrun; + asrc_priv->dma_params_tx.device_reset = fsl_asrc_reset; + asrc_priv->dma_params_rx.device_reset = fsl_asrc_reset; + if (asrc_priv->asrc_width != 16 && asrc_priv->asrc_width != 24) { dev_warn(&pdev->dev, "unsupported width, switching to 24bit\n"); asrc_priv->asrc_width = 24; @@ -920,6 +1202,8 @@ static int fsl_asrc_probe(struct platform_device *pdev) pm_runtime_enable(&pdev->dev); spin_lock_init(&asrc_priv->lock); + regcache_cache_only(asrc_priv->regmap, true); + ret = devm_snd_soc_register_component(&pdev->dev, &fsl_asrc_component, &fsl_asrc_dai, 1); if (ret) { @@ -933,6 +1217,12 @@ static int fsl_asrc_probe(struct platform_device *pdev) return ret; } + ret = fsl_asrc_m2m_init(asrc_priv); + if (ret) { + dev_err(&pdev->dev, "failed to init m2m device %d\n", ret); + return ret; + } + return 0; } @@ -941,6 +1231,7 @@ static int fsl_asrc_runtime_resume(struct device *dev) { struct fsl_asrc *asrc_priv = dev_get_drvdata(dev); int i, ret; + u32 asrctr; ret = clk_prepare_enable(asrc_priv->mem_clk); if (ret) @@ -959,6 +1250,24 @@ static int fsl_asrc_runtime_resume(struct device *dev) goto disable_asrck_clk; } + /* Stop all pairs provisionally */ + regmap_read(asrc_priv->regmap, REG_ASRCTR, &asrctr); + regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, + ASRCTR_ASRCEi_ALL_MASK, 0); + + /* Restore all registers */ + regcache_cache_only(asrc_priv->regmap, false); + regcache_mark_dirty(asrc_priv->regmap); + regcache_sync(asrc_priv->regmap); + + regmap_update_bits(asrc_priv->regmap, REG_ASRCFG, + ASRCFG_NDPRi_ALL_MASK | ASRCFG_POSTMODi_ALL_MASK | + ASRCFG_PREMODi_ALL_MASK, asrc_priv->regcache_cfg); + + /* Restart enabled pairs */ + regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, + ASRCTR_ASRCEi_ALL_MASK, asrctr); + return 0; disable_asrck_clk: @@ -978,6 +1287,11 @@ static int fsl_asrc_runtime_suspend(struct device *dev) struct fsl_asrc *asrc_priv = dev_get_drvdata(dev); int i; + regmap_read(asrc_priv->regmap, REG_ASRCFG, + &asrc_priv->regcache_cfg); + + regcache_cache_only(asrc_priv->regmap, true); + for (i = 0; i < ASRC_CLK_MAX_NUM; i++) clk_disable_unprepare(asrc_priv->asrck_clk[i]); if (!IS_ERR(asrc_priv->spba_clk)) @@ -993,39 +1307,22 @@ static int fsl_asrc_runtime_suspend(struct device *dev) static int fsl_asrc_suspend(struct device *dev) { struct fsl_asrc *asrc_priv = dev_get_drvdata(dev); + int ret; - regmap_read(asrc_priv->regmap, REG_ASRCFG, - &asrc_priv->regcache_cfg); + fsl_asrc_m2m_suspend(asrc_priv); - regcache_cache_only(asrc_priv->regmap, true); - regcache_mark_dirty(asrc_priv->regmap); + ret = pm_runtime_force_suspend(dev); - return 0; + return ret; } static int fsl_asrc_resume(struct device *dev) { - struct fsl_asrc *asrc_priv = dev_get_drvdata(dev); - u32 asrctr; - - /* Stop all pairs provisionally */ - regmap_read(asrc_priv->regmap, REG_ASRCTR, &asrctr); - regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, - ASRCTR_ASRCEi_ALL_MASK, 0); + int ret; - /* Restore all registers */ - regcache_cache_only(asrc_priv->regmap, false); - regcache_sync(asrc_priv->regmap); + ret = pm_runtime_force_resume(dev); - regmap_update_bits(asrc_priv->regmap, REG_ASRCFG, - ASRCFG_NDPRi_ALL_MASK | ASRCFG_POSTMODi_ALL_MASK | - ASRCFG_PREMODi_ALL_MASK, asrc_priv->regcache_cfg); - - /* Restart enabled pairs */ - regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, - ASRCTR_ASRCEi_ALL_MASK, asrctr); - - return 0; + return ret; } #endif /* CONFIG_PM_SLEEP */ @@ -1037,12 +1334,15 @@ static const struct dev_pm_ops fsl_asrc_pm = { static const struct of_device_id fsl_asrc_ids[] = { { .compatible = "fsl,imx35-asrc", }, { .compatible = "fsl,imx53-asrc", }, + { .compatible = "fsl,imx8qm-asrc0", }, + { .compatible = "fsl,imx8qm-asrc1", }, {} }; MODULE_DEVICE_TABLE(of, fsl_asrc_ids); static struct platform_driver fsl_asrc_driver = { .probe = fsl_asrc_probe, + .remove = fsl_asrc_m2m_remove, .driver = { .name = "fsl-asrc", .of_match_table = fsl_asrc_ids, diff --git a/sound/soc/fsl/fsl_asrc.h b/sound/soc/fsl/fsl_asrc.h index 0f163abe4ba3..1d592f7b73d2 100644 --- a/sound/soc/fsl/fsl_asrc.h +++ b/sound/soc/fsl/fsl_asrc.h @@ -1,7 +1,7 @@ /* * fsl_asrc.h - Freescale ASRC ALSA SoC header file * - * Copyright (C) 2014 Freescale Semiconductor, Inc. + * Copyright (C) 2014-2016 Freescale Semiconductor, Inc. * * Author: Nicolin Chen <nicoleotsuka@gmail.com> * @@ -13,6 +13,8 @@ #ifndef _FSL_ASRC_H #define _FSL_ASRC_H +#include <uapi/linux/mxc_asrc.h> + #define IN 0 #define OUT 1 @@ -23,7 +25,8 @@ #define ASRC_FIFO_THRESHOLD_MAX 63 #define ASRC_DMA_BUFFER_SIZE (1024 * 48 * 4) #define ASRC_MAX_BUFFER_SIZE (1024 * 48) -#define ASRC_OUTPUT_LAST_SAMPLE 8 +#define ASRC_OUTPUT_LAST_SAMPLE_MAX 32 +#define ASRC_OUTPUT_LAST_SAMPLE 4 #define IDEAL_RATIO_RATE 1000000 @@ -286,106 +289,10 @@ #define ASRMCR1i_OW16_MASK (1 << ASRMCR1i_OW16_SHIFT) #define ASRMCR1i_OW16(v) ((v) << ASRMCR1i_OW16_SHIFT) - -enum asrc_pair_index { - ASRC_INVALID_PAIR = -1, - ASRC_PAIR_A = 0, - ASRC_PAIR_B = 1, - ASRC_PAIR_C = 2, -}; - -#define ASRC_PAIR_MAX_NUM (ASRC_PAIR_C + 1) - -enum asrc_inclk { - INCLK_NONE = 0x03, - INCLK_ESAI_RX = 0x00, - INCLK_SSI1_RX = 0x01, - INCLK_SSI2_RX = 0x02, - INCLK_SSI3_RX = 0x07, - INCLK_SPDIF_RX = 0x04, - INCLK_MLB_CLK = 0x05, - INCLK_PAD = 0x06, - INCLK_ESAI_TX = 0x08, - INCLK_SSI1_TX = 0x09, - INCLK_SSI2_TX = 0x0a, - INCLK_SSI3_TX = 0x0b, - INCLK_SPDIF_TX = 0x0c, - INCLK_ASRCK1_CLK = 0x0f, -}; - -enum asrc_outclk { - OUTCLK_NONE = 0x03, - OUTCLK_ESAI_TX = 0x00, - OUTCLK_SSI1_TX = 0x01, - OUTCLK_SSI2_TX = 0x02, - OUTCLK_SSI3_TX = 0x07, - OUTCLK_SPDIF_TX = 0x04, - OUTCLK_MLB_CLK = 0x05, - OUTCLK_PAD = 0x06, - OUTCLK_ESAI_RX = 0x08, - OUTCLK_SSI1_RX = 0x09, - OUTCLK_SSI2_RX = 0x0a, - OUTCLK_SSI3_RX = 0x0b, - OUTCLK_SPDIF_RX = 0x0c, - OUTCLK_ASRCK1_CLK = 0x0f, -}; - #define ASRC_CLK_MAX_NUM 16 -enum asrc_word_width { - ASRC_WIDTH_24_BIT = 0, - ASRC_WIDTH_16_BIT = 1, - ASRC_WIDTH_8_BIT = 2, -}; - -struct asrc_config { - enum asrc_pair_index pair; - unsigned int channel_num; - unsigned int buffer_num; - unsigned int dma_buffer_size; - unsigned int input_sample_rate; - unsigned int output_sample_rate; - enum asrc_word_width input_word_width; - enum asrc_word_width output_word_width; - enum asrc_inclk inclk; - enum asrc_outclk outclk; -}; - -struct asrc_req { - unsigned int chn_num; - enum asrc_pair_index index; -}; - -struct asrc_querybuf { - unsigned int buffer_index; - unsigned int input_length; - unsigned int output_length; - unsigned long input_offset; - unsigned long output_offset; -}; - -struct asrc_convert_buffer { - void *input_buffer_vaddr; - void *output_buffer_vaddr; - unsigned int input_buffer_length; - unsigned int output_buffer_length; -}; - -struct asrc_status_flags { - enum asrc_pair_index index; - unsigned int overload_error; -}; - -enum asrc_error_status { - ASRC_TASK_Q_OVERLOAD = 0x01, - ASRC_OUTPUT_TASK_OVERLOAD = 0x02, - ASRC_INPUT_TASK_OVERLOAD = 0x04, - ASRC_OUTPUT_BUFFER_OVERFLOW = 0x08, - ASRC_INPUT_BUFFER_UNDERRUN = 0x10, -}; struct dma_block { - dma_addr_t dma_paddr; void *dma_vaddr; unsigned int length; }; @@ -416,6 +323,7 @@ struct fsl_asrc_pair { struct dma_chan *dma_chan[2]; struct imx_dma_data dma_data; unsigned int pos; + unsigned int pair_streams; void *private; }; @@ -436,6 +344,7 @@ struct fsl_asrc_pair { * @pair: pair pointers * @channel_bits: width of ASRCNCR register for each pair * @channel_avail: non-occupied channel numbers + * @pair_streams:indicat which substream is running * @asrc_rate: default sample rate for ASoC Back-Ends * @asrc_width: default sample width for ASoC Back-Ends * @regcache_cfg: store register value of REG_ASRCFG @@ -450,18 +359,29 @@ struct fsl_asrc { struct clk *ipg_clk; struct clk *spba_clk; struct clk *asrck_clk[ASRC_CLK_MAX_NUM]; + unsigned char *clk_map[2]; spinlock_t lock; + struct snd_pcm_substream *substream[2]; struct fsl_asrc_pair *pair[ASRC_PAIR_MAX_NUM]; + struct miscdevice asrc_miscdev; unsigned int channel_bits; unsigned int channel_avail; int asrc_rate; int asrc_width; + int dma_type; /* 0 is sdma, 1 is edma */ u32 regcache_cfg; + char name[20]; }; +#define DMA_SDMA 0 +#define DMA_EDMA 1 + extern struct snd_soc_platform_driver fsl_asrc_platform; struct dma_chan *fsl_asrc_get_dma_channel(struct fsl_asrc_pair *pair, bool dir); +int fsl_asrc_request_pair(int channels, struct fsl_asrc_pair *pair); +void fsl_asrc_release_pair(struct fsl_asrc_pair *pair); + #endif /* _FSL_ASRC_H */ diff --git a/sound/soc/fsl/fsl_asrc_dma.c b/sound/soc/fsl/fsl_asrc_dma.c index dc30d780f874..3c2f2b0de47a 100644 --- a/sound/soc/fsl/fsl_asrc_dma.c +++ b/sound/soc/fsl/fsl_asrc_dma.c @@ -1,7 +1,7 @@ /* * Freescale ASRC ALSA SoC Platform (DMA) driver * - * Copyright (C) 2014 Freescale Semiconductor, Inc. + * Copyright (C) 2014-2015 Freescale Semiconductor, Inc. * * Author: Nicolin Chen <nicoleotsuka@gmail.com> * @@ -24,12 +24,10 @@ static struct snd_pcm_hardware snd_imx_hardware = { .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP | - SNDRV_PCM_INFO_MMAP_VALID | - SNDRV_PCM_INFO_PAUSE | - SNDRV_PCM_INFO_RESUME, + SNDRV_PCM_INFO_MMAP_VALID, .buffer_bytes_max = FSL_ASRC_DMABUF_SIZE, .period_bytes_min = 128, - .period_bytes_max = 65535, /* Limited by SDMA engine */ + .period_bytes_max = 65532, /* Limited by SDMA engine */ .periods_min = 2, .periods_max = 255, .fifo_size = 0, @@ -50,12 +48,19 @@ static void fsl_asrc_dma_complete(void *arg) struct snd_pcm_substream *substream = arg; struct snd_pcm_runtime *runtime = substream->runtime; struct fsl_asrc_pair *pair = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_dmaengine_dai_dma_data *dma_data; pair->pos += snd_pcm_lib_period_bytes(substream); if (pair->pos >= snd_pcm_lib_buffer_bytes(substream)) pair->pos = 0; snd_pcm_period_elapsed(substream); + + dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + if (dma_data->check_xrun && dma_data->check_xrun(substream)) + dma_data->device_reset(substream, 1); + } static int fsl_asrc_dma_prepare_and_submit(struct snd_pcm_substream *substream) @@ -150,6 +155,7 @@ static int fsl_asrc_dma_hw_params(struct snd_pcm_substream *substream, struct device *dev_be; u8 dir = tx ? OUT : IN; dma_cap_mask_t mask; + enum sdma_peripheral_type be_peripheral_type; int ret; /* Fetch the Back-End dma_data from DPCM */ @@ -203,19 +209,43 @@ static int fsl_asrc_dma_hw_params(struct snd_pcm_substream *substream, /* Get DMA request of Back-End */ tmp_chan = dma_request_slave_channel(dev_be, tx ? "tx" : "rx"); - tmp_data = tmp_chan->private; - pair->dma_data.dma_request = tmp_data->dma_request; - dma_release_channel(tmp_chan); + if (tmp_chan) { + tmp_data = tmp_chan->private; + if (tmp_data) { + pair->dma_data.dma_request = tmp_data->dma_request; + be_peripheral_type = tmp_data->peripheral_type; + if (tx && be_peripheral_type == IMX_DMATYPE_SSI_DUAL) + pair->dma_data.dst_dualfifo = true; + if (!tx && be_peripheral_type == IMX_DMATYPE_SSI_DUAL) + pair->dma_data.src_dualfifo = true; + } + dma_release_channel(tmp_chan); + } /* Get DMA request of Front-End */ tmp_chan = fsl_asrc_get_dma_channel(pair, dir); - tmp_data = tmp_chan->private; - pair->dma_data.dma_request2 = tmp_data->dma_request; - pair->dma_data.peripheral_type = tmp_data->peripheral_type; - pair->dma_data.priority = tmp_data->priority; - dma_release_channel(tmp_chan); + if (tmp_chan) { + tmp_data = tmp_chan->private; + if (tmp_data) { + pair->dma_data.dma_request2 = tmp_data->dma_request; + pair->dma_data.peripheral_type = + tmp_data->peripheral_type; + pair->dma_data.priority = tmp_data->priority; + } + dma_release_channel(tmp_chan); + } + + /* For sdma DEV_TO_DEV, there is two dma request + * But for emda DEV_TO_DEV, there is only one dma request, which is + * from the BE. + */ + if (pair->dma_data.dma_request2 != pair->dma_data.dma_request) + pair->dma_chan[dir] = + dma_request_channel(mask, filter, &pair->dma_data); + else + pair->dma_chan[dir] = + dma_request_slave_channel(dev_be, tx ? "tx" : "rx"); - pair->dma_chan[dir] = dma_request_channel(mask, filter, &pair->dma_data); if (!pair->dma_chan[dir]) { dev_err(dev, "failed to request DMA channel for Back-End\n"); return -EINVAL; @@ -226,6 +256,8 @@ static int fsl_asrc_dma_hw_params(struct snd_pcm_substream *substream, else buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES; + memset(&config_be, 0, sizeof(config_be)); + config_be.direction = DMA_DEV_TO_DEV; config_be.src_addr_width = buswidth; config_be.src_maxburst = dma_params_be->maxburst; @@ -277,6 +309,16 @@ static int fsl_asrc_dma_startup(struct snd_pcm_substream *substream) struct device *dev = rtd->platform->dev; struct fsl_asrc *asrc_priv = dev_get_drvdata(dev); struct fsl_asrc_pair *pair; + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + u8 dir = tx ? OUT : IN; + struct dma_slave_caps dma_caps; + struct dma_chan *tmp_chan; + struct snd_dmaengine_dai_dma_data *dma_data; + u32 addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) | + BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | + BIT(DMA_SLAVE_BUSWIDTH_4_BYTES); + int ret; + int i; pair = kzalloc(sizeof(struct fsl_asrc_pair), GFP_KERNEL); if (!pair) { @@ -288,8 +330,78 @@ static int fsl_asrc_dma_startup(struct snd_pcm_substream *substream) runtime->private_data = pair; - snd_pcm_hw_constraint_integer(substream->runtime, - SNDRV_PCM_HW_PARAM_PERIODS); + ret = snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + dev_err(dev, "failed to set pcm hw params periods\n"); + return ret; + } + + dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + + fsl_asrc_request_pair(1, pair); + + tmp_chan = fsl_asrc_get_dma_channel(pair, dir); + if (!tmp_chan) { + dev_err(dev, "can't get dma channel\n"); + return -EINVAL; + } + + ret = dma_get_slave_caps(tmp_chan, &dma_caps); + if (ret == 0) { + if (dma_caps.cmd_pause) + snd_imx_hardware.info |= SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME; + if (dma_caps.residue_granularity <= DMA_RESIDUE_GRANULARITY_SEGMENT) + snd_imx_hardware.info |= SNDRV_PCM_INFO_BATCH; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + addr_widths = dma_caps.dst_addr_widths; + else + addr_widths = dma_caps.src_addr_widths; + } + + /* + * If SND_DMAENGINE_PCM_DAI_FLAG_PACK is set keep + * hw.formats set to 0, meaning no restrictions are in place. + * In this case it's the responsibility of the DAI driver to + * provide the supported format information. + */ + if (!(dma_data->flags & SND_DMAENGINE_PCM_DAI_FLAG_PACK)) + /* + * Prepare formats mask for valid/allowed sample types. If the + * dma does not have support for the given physical word size, + * it needs to be masked out so user space can not use the + * format which produces corrupted audio. + * In case the dma driver does not implement the slave_caps the + * default assumption is that it supports 1, 2 and 4 bytes + * widths. + */ + for (i = 0; i <= SNDRV_PCM_FORMAT_LAST; i++) { + int bits = snd_pcm_format_physical_width(i); + + /* + * Enable only samples with DMA supported physical + * widths + */ + switch (bits) { + case 8: + case 16: + case 24: + case 32: + case 64: + if (addr_widths & (1 << (bits / 8))) + snd_imx_hardware.formats |= (1LL << i); + break; + default: + /* Unsupported types */ + break; + } + } + + if (tmp_chan) + dma_release_channel(tmp_chan); + fsl_asrc_release_pair(pair); + snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware); return 0; diff --git a/sound/soc/fsl/fsl_asrc_m2m.c b/sound/soc/fsl/fsl_asrc_m2m.c new file mode 100644 index 000000000000..2684518d26ee --- /dev/null +++ b/sound/soc/fsl/fsl_asrc_m2m.c @@ -0,0 +1,1023 @@ +/* + * Freescale ASRC Memory to Memory (M2M) driver + * + * Copyright (C) 2014-2016 Freescale Semiconductor, Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#define FSL_ASRC_INPUTFIFO_WML 0x4 +#define FSL_ASRC_OUTPUTFIFO_WML 0x2 + +#define DIR_STR(dir) dir == IN ? "in" : "out" + +struct fsl_asrc_m2m { + struct fsl_asrc_pair *pair; + struct completion complete[2]; + struct dma_block dma_block[2]; + unsigned int pair_hold; + unsigned int asrc_active; + unsigned int sg_nodes[2]; + struct scatterlist sg[2][4]; + + enum asrc_word_width word_width[2]; + unsigned int rate[2]; + unsigned int last_period_size; + u32 watermark[2]; + spinlock_t lock; +}; + +static void fsl_asrc_get_status(struct fsl_asrc_pair *pair, + struct asrc_status_flags *flags) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + unsigned long lock_flags; + + spin_lock_irqsave(&asrc_priv->lock, lock_flags); + + flags->overload_error = pair->error; + + spin_unlock_irqrestore(&asrc_priv->lock, lock_flags); +} + +#define ASRC_xPUT_DMA_CALLBACK(dir) \ + ((dir == IN) ? fsl_asrc_input_dma_callback : fsl_asrc_output_dma_callback) + +static void fsl_asrc_input_dma_callback(void *data) +{ + struct fsl_asrc_pair *pair = (struct fsl_asrc_pair *)data; + struct fsl_asrc_m2m *m2m = pair->private; + + complete(&m2m->complete[IN]); +} + +static void fsl_asrc_output_dma_callback(void *data) +{ + struct fsl_asrc_pair *pair = (struct fsl_asrc_pair *)data; + struct fsl_asrc_m2m *m2m = pair->private; + + complete(&m2m->complete[OUT]); +} + +static unsigned int fsl_asrc_get_output_FIFO_size(struct fsl_asrc_pair *pair) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + u32 val; + + regmap_read(asrc_priv->regmap, REG_ASRFST(index), &val); + + val &= ASRFSTi_OUTPUT_FIFO_MASK; + + return val >> ASRFSTi_OUTPUT_FIFO_SHIFT; +} + +static void fsl_asrc_read_last_FIFO(struct fsl_asrc_pair *pair) +{ + struct fsl_asrc_m2m *m2m = pair->private; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + struct dma_block *output = &m2m->dma_block[OUT]; + u32 i, reg, size, t_size = 0; + u32 *reg24 = NULL; + u16 *reg16 = NULL; + + if (m2m->word_width[OUT] == ASRC_WIDTH_24_BIT) + reg24 = output->dma_vaddr + output->length; + else + reg16 = output->dma_vaddr + output->length; + +retry: + size = fsl_asrc_get_output_FIFO_size(pair); + + for (i = 0; i < size * pair->channels; i++) { + regmap_read(asrc_priv->regmap, REG_ASRDO(index), ®); + if (reg24) { + *(reg24) = reg; + reg24++; + } else { + *(reg16) = (u16)reg; + reg16++; + } + } + t_size += size; + + if (size) + goto retry; + + if (t_size > m2m->last_period_size) + t_size = m2m->last_period_size; + + if (reg24) + output->length += t_size * pair->channels * 4; + else + output->length += t_size * pair->channels * 2; +} + +static int fsl_allocate_dma_buf(struct fsl_asrc_pair *pair) +{ + struct fsl_asrc_m2m *m2m = pair->private; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct dma_block *input = &m2m->dma_block[IN]; + struct dma_block *output = &m2m->dma_block[OUT]; + enum asrc_pair_index index = pair->index; + + input->dma_vaddr = kzalloc(input->length, GFP_KERNEL); + if (!input->dma_vaddr) { + pair_err("failed to allocate input DMA buffer\n"); + return -ENOMEM; + } + + output->dma_vaddr = kzalloc(output->length, GFP_KERNEL); + if (!output->dma_vaddr) { + pair_err("failed to allocate output DMA buffer\n"); + goto exit; + } + + return 0; + +exit: + kfree(input->dma_vaddr); + + return -ENOMEM; +} + +static int fsl_asrc_dmaconfig(struct fsl_asrc_pair *pair, struct dma_chan *chan, + u32 dma_addr, void *buf_addr, u32 buf_len, + bool dir, enum asrc_word_width word_width) +{ + struct dma_async_tx_descriptor *desc = pair->desc[dir]; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct fsl_asrc_m2m *m2m = pair->private; + unsigned int sg_nent = m2m->sg_nodes[dir]; + enum asrc_pair_index index = pair->index; + struct scatterlist *sg = m2m->sg[dir]; + struct dma_slave_config slave_config; + enum dma_slave_buswidth buswidth; + int ret, i; + + switch (word_width) { + case ASRC_WIDTH_16_BIT: + buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES; + break; + case ASRC_WIDTH_24_BIT: + buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES; + break; + default: + pair_err("invalid word width\n"); + return -EINVAL; + } + + if (dir == IN) { + slave_config.direction = DMA_MEM_TO_DEV; + slave_config.dst_addr = dma_addr; + slave_config.dst_addr_width = buswidth; + if (asrc_priv->dma_type == DMA_SDMA) + slave_config.dst_maxburst = + m2m->watermark[IN] * pair->channels; + else + slave_config.dst_maxburst = 1; + } else { + slave_config.direction = DMA_DEV_TO_MEM; + slave_config.src_addr = dma_addr; + slave_config.src_addr_width = buswidth; + slave_config.src_maxburst = + m2m->watermark[OUT] * pair->channels; + } + + ret = dmaengine_slave_config(chan, &slave_config); + if (ret) { + pair_err("failed to config dmaengine for %sput task: %d\n", + DIR_STR(dir), ret); + return -EINVAL; + } + + sg_init_table(sg, sg_nent); + switch (sg_nent) { + case 1: + sg_init_one(sg, buf_addr, buf_len); + break; + case 2: + case 3: + case 4: + for (i = 0; i < (sg_nent - 1); i++) + sg_set_buf(&sg[i], buf_addr + i * ASRC_MAX_BUFFER_SIZE, + ASRC_MAX_BUFFER_SIZE); + + sg_set_buf(&sg[i], buf_addr + i * ASRC_MAX_BUFFER_SIZE, + buf_len - ASRC_MAX_BUFFER_SIZE * i); + break; + default: + pair_err("invalid input DMA nodes number: %d\n", sg_nent); + return -EINVAL; + } + + ret = dma_map_sg(&asrc_priv->pdev->dev, sg, sg_nent, slave_config.direction); + if (ret != sg_nent) { + pair_err("failed to map DMA sg for %sput task\n", DIR_STR(dir)); + return -EINVAL; + } + + desc = dmaengine_prep_slave_sg(chan, sg, sg_nent, + slave_config.direction, DMA_PREP_INTERRUPT); + if (!desc) { + pair_err("failed to prepare dmaengine for %sput task\n", + DIR_STR(dir)); + return -EINVAL; + } + + pair->desc[dir] = desc; + pair->desc[dir]->callback = ASRC_xPUT_DMA_CALLBACK(dir); + + desc->callback = ASRC_xPUT_DMA_CALLBACK(dir); + desc->callback_param = pair; + + return 0; +} + +static int fsl_asrc_prepare_io_buffer(struct fsl_asrc_pair *pair, + struct asrc_convert_buffer *pbuf, bool dir) +{ + struct fsl_asrc_m2m *m2m = pair->private; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + unsigned int *dma_len = &m2m->dma_block[dir].length; + enum asrc_word_width width = m2m->word_width[dir]; + void *dma_vaddr = m2m->dma_block[dir].dma_vaddr; + struct dma_chan *dma_chan = pair->dma_chan[dir]; + unsigned int buf_len, wm = m2m->watermark[dir]; + unsigned int *sg_nodes = &m2m->sg_nodes[dir]; + unsigned int last_period_size = m2m->last_period_size; + enum asrc_pair_index index = pair->index; + u32 word_size, fifo_addr; + void __user *buf_vaddr; + + /* Clean the DMA buffer */ + memset(dma_vaddr, 0, ASRC_DMA_BUFFER_SIZE); + + if (dir == IN) { + buf_vaddr = (void __user *)pbuf->input_buffer_vaddr; + buf_len = pbuf->input_buffer_length; + } else { + buf_vaddr = (void __user *)pbuf->output_buffer_vaddr; + buf_len = pbuf->output_buffer_length; + } + + if (width == ASRC_WIDTH_24_BIT) + word_size = 4; + else + word_size = 2; + + if (buf_len < word_size * pair->channels * wm || + buf_len > ASRC_DMA_BUFFER_SIZE || + (dir == OUT && buf_len < word_size * pair->channels * last_period_size)) { + pair_err("%sput buffer size is error: [%d]\n", + DIR_STR(dir), buf_len); + return -EINVAL; + } + + /* Copy origin data into input buffer */ + if (dir == IN && copy_from_user(dma_vaddr, buf_vaddr, buf_len)) + return -EFAULT; + + *dma_len = buf_len; + if (dir == OUT) { + *dma_len -= last_period_size * word_size * pair->channels; + *dma_len = *dma_len / (word_size * pair->channels) * + (word_size * pair->channels); + if (asrc_priv->dma_type == DMA_EDMA) + *dma_len = *dma_len / (word_size * pair->channels * m2m->watermark[OUT]) + * (word_size * pair->channels * m2m->watermark[OUT]); + } + + *sg_nodes = *dma_len / ASRC_MAX_BUFFER_SIZE + 1; + + fifo_addr = asrc_priv->paddr + REG_ASRDx(dir, index); + + return fsl_asrc_dmaconfig(pair, dma_chan, fifo_addr, dma_vaddr, + *dma_len, dir, width); +} + +static int fsl_asrc_prepare_buffer(struct fsl_asrc_pair *pair, + struct asrc_convert_buffer *pbuf) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + int ret; + + ret = fsl_asrc_prepare_io_buffer(pair, pbuf, IN); + if (ret) { + pair_err("failed to prepare input buffer: %d\n", ret); + return ret; + } + + ret = fsl_asrc_prepare_io_buffer(pair, pbuf, OUT); + if (ret) { + pair_err("failed to prepare output buffer: %d\n", ret); + return ret; + } + + return 0; +} + +int fsl_asrc_process_buffer_pre(struct completion *complete, + enum asrc_pair_index index, bool dir) +{ + if (!wait_for_completion_interruptible_timeout(complete, 10 * HZ)) { + pr_err("%sput DMA task timeout\n", DIR_STR(dir)); + return -ETIME; + } else if (signal_pending(current)) { + pr_err("%sput task forcibly aborted\n", DIR_STR(dir)); + return -EBUSY; + } + + return 0; +} + +#define mxc_asrc_dma_umap(dev, m2m) \ + do { \ + dma_unmap_sg(dev, m2m->sg[IN], m2m->sg_nodes[IN], \ + DMA_MEM_TO_DEV); \ + dma_unmap_sg(dev, m2m->sg[OUT], m2m->sg_nodes[OUT], \ + DMA_DEV_TO_MEM); \ + } while (0) + +int fsl_asrc_process_buffer(struct fsl_asrc_pair *pair, + struct asrc_convert_buffer *pbuf) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct fsl_asrc_m2m *m2m = pair->private; + enum asrc_pair_index index = pair->index; + unsigned long lock_flags; + int ret; + + /* Check input task first */ + ret = fsl_asrc_process_buffer_pre(&m2m->complete[IN], index, IN); + if (ret) { + mxc_asrc_dma_umap(&asrc_priv->pdev->dev, m2m); + return ret; + } + + /* ...then output task*/ + ret = fsl_asrc_process_buffer_pre(&m2m->complete[OUT], index, OUT); + if (ret) { + mxc_asrc_dma_umap(&asrc_priv->pdev->dev, m2m); + return ret; + } + + mxc_asrc_dma_umap(&asrc_priv->pdev->dev, m2m); + + /* Fetch the remaining data */ + spin_lock_irqsave(&m2m->lock, lock_flags); + if (!m2m->pair_hold) { + spin_unlock_irqrestore(&m2m->lock, lock_flags); + return -EFAULT; + } + spin_unlock_irqrestore(&m2m->lock, lock_flags); + + fsl_asrc_read_last_FIFO(pair); + + /* Update final lengths after getting last FIFO */ + pbuf->input_buffer_length = m2m->dma_block[IN].length; + pbuf->output_buffer_length = m2m->dma_block[OUT].length; + + if (copy_to_user((void __user *)pbuf->output_buffer_vaddr, + m2m->dma_block[OUT].dma_vaddr, + m2m->dma_block[OUT].length)) + return -EFAULT; + + return 0; +} + +#ifdef ASRC_POLLING_WITHOUT_DMA +/* THIS FUNCTION ONLY EXISTS FOR DEBUGGING AND ONLY SUPPORTS TWO CHANNELS */ +static void fsl_asrc_polling_debug(struct fsl_asrc_pair *pair) +{ + struct fsl_asrc_m2m *m2m = pair->private; + enum asrc_pair_index index = pair->index; + u32 *in24 = m2m->dma_block[IN].dma_vaddr; + u32 dma_len = m2m->dma_block[IN].length / (pair->channels * 4); + u32 *reg24 = m2m->dma_block[OUT].dma_vaddr; + u32 size, i, j, t_size, reg; + + t_size = 0; + + for (i = 0; i < dma_len; ) { + for (j = 0; j < 2; j++) { + regmap_write(asrc_priv->regmap, REG_ASRDx(index), *in24); + in24++; + regmap_write(asrc_priv->regmap, REG_ASRDx(index), *in24); + in24++; + i++; + } + udelay(50); + udelay(50 * m2m->rate[OUT] / m2m->rate[IN]); + + size = fsl_asrc_get_output_FIFO_size(index); + for (j = 0; j < size; j++) { + regmap_read(asrc_priv->regmap, REG_ASRDO(index), ®); + *(reg24) = reg; + reg24++; + regmap_read(asrc_priv->regmap, REG_ASRDO(index), ®); + *(reg24) = reg; + reg24++; + } + t_size += size; + } + + mdelay(1); + size = fsl_asrc_get_output_FIFO_size(index); + for (j = 0; j < size; j++) { + regmap_read(asrc_priv->regmap, REG_ASRDO(index), ®); + *(reg24) = reg; + reg24++; + regmap_read(asrc_priv->regmap, REG_ASRDO(index), ®); + *(reg24) = reg; + reg24++; + } + t_size += size; + + m2m->dma_block[OUT].length = t_size * pair->channels * 4; + + complete(&m2m->complete[OUT]); + complete(&m2m->complete[IN]); +} +#else +static void fsl_asrc_submit_dma(struct fsl_asrc_pair *pair) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct fsl_asrc_m2m *m2m = pair->private; + enum asrc_pair_index index = pair->index; + u32 size = fsl_asrc_get_output_FIFO_size(pair); + int i; + + /* Read all data in OUTPUT FIFO */ + while (size) { + u32 val; + for (i = 0; i < size * pair->channels; i++) + regmap_read(asrc_priv->regmap, REG_ASRDO(index), &val); + /* Fetch the data every 100us */ + udelay(100); + + size = fsl_asrc_get_output_FIFO_size(pair); + } + + /* Submit DMA request */ + dmaengine_submit(pair->desc[IN]); + dma_async_issue_pending(pair->desc[IN]->chan); + + dmaengine_submit(pair->desc[OUT]); + dma_async_issue_pending(pair->desc[OUT]->chan); + + /* + * Clear DMA request during the stall state of ASRC: + * During STALL state, the remaining in input fifo would never be + * smaller than the input threshold while the output fifo would not + * be bigger than output one. Thus the DMA request would be cleared. + */ + fsl_asrc_set_watermarks(pair, ASRC_FIFO_THRESHOLD_MIN, + ASRC_FIFO_THRESHOLD_MAX); + + /* Update the real input threshold to raise DMA request */ + fsl_asrc_set_watermarks(pair, m2m->watermark[IN], m2m->watermark[OUT]); +} +#endif /* ASRC_POLLING_WITHOUT_DMA */ + +static long fsl_asrc_ioctl_req_pair(struct fsl_asrc_pair *pair, + void __user *user) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct fsl_asrc_m2m *m2m = pair->private; + struct device *dev = &asrc_priv->pdev->dev; + struct asrc_req req; + unsigned long lock_flags; + long ret; + + ret = copy_from_user(&req, user, sizeof(req)); + if (ret) { + dev_err(dev, "failed to get req from user space: %ld\n", ret); + return ret; + } + + ret = fsl_asrc_request_pair(req.chn_num, pair); + if (ret) { + dev_err(dev, "failed to request pair: %ld\n", ret); + return ret; + } + + spin_lock_irqsave(&m2m->lock, lock_flags); + m2m->pair_hold = 1; + spin_unlock_irqrestore(&m2m->lock, lock_flags); + pair->channels = req.chn_num; + + req.index = pair->index; + + ret = copy_to_user(user, &req, sizeof(req)); + if (ret) { + dev_err(dev, "failed to send req to user space: %ld\n", ret); + return ret; + } + + return 0; +} + +static long fsl_asrc_ioctl_config_pair(struct fsl_asrc_pair *pair, + void __user *user) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct fsl_asrc_m2m *m2m = pair->private; + struct device *dev = &asrc_priv->pdev->dev; + struct asrc_config config; + enum asrc_pair_index index; + long ret; + + ret = copy_from_user(&config, user, sizeof(config)); + if (ret) { + dev_err(dev, "failed to get config from user space: %ld\n", ret); + return ret; + } + + index = config.pair; + + pair->config = &config; + ret = fsl_asrc_config_pair(pair, false, false); + if (ret) { + pair_err("failed to config pair: %ld\n", ret); + return ret; + } + + m2m->watermark[IN] = FSL_ASRC_INPUTFIFO_WML; + m2m->watermark[OUT] = FSL_ASRC_OUTPUTFIFO_WML; + + fsl_asrc_set_watermarks(pair, m2m->watermark[IN], m2m->watermark[OUT]); + + m2m->dma_block[IN].length = ASRC_DMA_BUFFER_SIZE; + m2m->dma_block[OUT].length = ASRC_DMA_BUFFER_SIZE; + + m2m->word_width[IN] = config.input_word_width; + m2m->word_width[OUT] = config.output_word_width; + + m2m->rate[IN] = config.input_sample_rate; + m2m->rate[OUT] = config.output_sample_rate; + + m2m->last_period_size = ASRC_OUTPUT_LAST_SAMPLE; + + ret = fsl_allocate_dma_buf(pair); + if (ret) { + pair_err("failed to allocate DMA buffer: %ld\n", ret); + return ret; + } + + /* Request DMA channel for both input and output */ + pair->dma_chan[IN] = fsl_asrc_get_dma_channel(pair, IN); + if (pair->dma_chan[IN] == NULL) { + pair_err("failed to request input task DMA channel\n"); + return -EBUSY; + } + + pair->dma_chan[OUT] = fsl_asrc_get_dma_channel(pair, OUT); + if (pair->dma_chan[OUT] == NULL) { + pair_err("failed to request output task DMA channel\n"); + return -EBUSY; + } + + ret = copy_to_user(user, &config, sizeof(config)); + if (ret) { + pair_err("failed to send config to user space: %ld\n", ret); + return ret; + } + + return 0; +} + +static long fsl_asrc_ioctl_release_pair(struct fsl_asrc_pair *pair, + void __user *user) +{ + struct fsl_asrc_m2m *m2m = pair->private; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index; + unsigned long lock_flags; + long ret; + + ret = copy_from_user(&index, user, sizeof(index)); + if (ret) { + pair_err("failed to get index from user space: %ld\n", ret); + return ret; + } + + /* index might be not valid due to some application failure. */ + if (index < 0) + return -EINVAL; + + m2m->asrc_active = 0; + + spin_lock_irqsave(&m2m->lock, lock_flags); + m2m->pair_hold = 0; + spin_unlock_irqrestore(&m2m->lock, lock_flags); + + if (pair->dma_chan[IN]) + dma_release_channel(pair->dma_chan[IN]); + if (pair->dma_chan[OUT]) + dma_release_channel(pair->dma_chan[OUT]); + kfree(m2m->dma_block[IN].dma_vaddr); + kfree(m2m->dma_block[OUT].dma_vaddr); + fsl_asrc_release_pair(pair); + + return 0; +} + +static long fsl_asrc_calc_last_period_size(struct fsl_asrc_pair *pair, + struct asrc_convert_buffer *pbuf) +{ + struct fsl_asrc_m2m *m2m = pair->private; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + unsigned int out_length; + unsigned int in_width, out_width; + unsigned int channels = pair->channels; + unsigned int in_samples, out_samples; + unsigned int last_period_size; + unsigned int remain; + + switch (m2m->word_width[IN]) { + case ASRC_WIDTH_24_BIT: + in_width = 4; + break; + case ASRC_WIDTH_16_BIT: + in_width = 2; + break; + case ASRC_WIDTH_8_BIT: + in_width = 1; + break; + default: + in_width = 2; + break; + } + + switch (m2m->word_width[OUT]) { + case ASRC_WIDTH_24_BIT: + out_width = 4; + break; + case ASRC_WIDTH_16_BIT: + out_width = 2; + break; + case ASRC_WIDTH_8_BIT: + out_width = 1; + break; + default: + out_width = 2; + break; + } + + in_samples = pbuf->input_buffer_length / (in_width * channels); + + out_samples = (m2m->rate[OUT] * in_samples / m2m->rate[IN]); + + out_length = out_samples * out_width * channels; + + last_period_size = pbuf->output_buffer_length / (out_width * channels) + - out_samples; + + m2m->last_period_size = last_period_size + 1 + ASRC_OUTPUT_LAST_SAMPLE; + + if (asrc_priv->dma_type == DMA_EDMA) { + remain = pbuf->output_buffer_length % (out_width * channels * m2m->watermark[OUT]); + if (remain) + m2m->last_period_size += remain / (out_width * channels); + } + + return 0; +} + +static long fsl_asrc_ioctl_convert(struct fsl_asrc_pair *pair, + void __user *user) +{ + struct fsl_asrc_m2m *m2m = pair->private; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + struct asrc_convert_buffer buf; + long ret; + + ret = copy_from_user(&buf, user, sizeof(buf)); + if (ret) { + pair_err("failed to get buf from user space: %ld\n", ret); + return ret; + } + + fsl_asrc_calc_last_period_size(pair, &buf); + + ret = fsl_asrc_prepare_buffer(pair, &buf); + if (ret) { + pair_err("failed to prepare buffer: %ld\n", ret); + return ret; + } + + init_completion(&m2m->complete[IN]); + init_completion(&m2m->complete[OUT]); + +#ifdef ASRC_POLLING_WITHOUT_DMA + fsl_asrc_polling_debug(pair); +#else + fsl_asrc_submit_dma(pair); +#endif + + ret = fsl_asrc_process_buffer(pair, &buf); + if (ret) { + pair_err("failed to process buffer: %ld\n", ret); + return ret; + } + + ret = copy_to_user(user, &buf, sizeof(buf)); + if (ret) { + pair_err("failed to send buf to user space: %ld\n", ret); + return ret; + } + + return 0; +} + +static long fsl_asrc_ioctl_start_conv(struct fsl_asrc_pair *pair, + void __user *user) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct fsl_asrc_m2m *m2m = pair->private; + enum asrc_pair_index index; + long ret; + + ret = copy_from_user(&index, user, sizeof(index)); + if (ret) { + pair_err("failed to get index from user space: %ld\n", ret); + return ret; + } + + m2m->asrc_active = 1; + fsl_asrc_start_pair(pair); + + return 0; +} + +static long fsl_asrc_ioctl_stop_conv(struct fsl_asrc_pair *pair, + void __user *user) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct fsl_asrc_m2m *m2m = pair->private; + enum asrc_pair_index index; + long ret; + + ret = copy_from_user(&index, user, sizeof(index)); + if (ret) { + pair_err("failed to get index from user space: %ld\n", ret); + return ret; + } + + dmaengine_terminate_all(pair->dma_chan[IN]); + dmaengine_terminate_all(pair->dma_chan[OUT]); + + fsl_asrc_stop_pair(pair); + m2m->asrc_active = 0; + + return 0; +} + +static long fsl_asrc_ioctl_status(struct fsl_asrc_pair *pair, void __user *user) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + struct asrc_status_flags flags; + long ret; + + ret = copy_from_user(&flags, user, sizeof(flags)); + if (ret) { + pair_err("failed to get flags from user space: %ld\n", ret); + return ret; + } + + fsl_asrc_get_status(pair, &flags); + + ret = copy_to_user(user, &flags, sizeof(flags)); + if (ret) { + pair_err("failed to send flags to user space: %ld\n", ret); + return ret; + } + + return 0; +} + +static long fsl_asrc_ioctl_flush(struct fsl_asrc_pair *pair, void __user *user) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + + /* Release DMA and request again */ + dma_release_channel(pair->dma_chan[IN]); + dma_release_channel(pair->dma_chan[OUT]); + + pair->dma_chan[IN] = fsl_asrc_get_dma_channel(pair, IN); + if (pair->dma_chan[IN] == NULL) { + pair_err("failed to request input task DMA channel\n"); + return -EBUSY; + } + + pair->dma_chan[OUT] = fsl_asrc_get_dma_channel(pair, OUT); + if (pair->dma_chan[OUT] == NULL) { + pair_err("failed to request output task DMA channel\n"); + return -EBUSY; + } + + return 0; +} + +static long fsl_asrc_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct fsl_asrc_pair *pair = file->private_data; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + void __user *user = (void __user *)arg; + long ret = 0; + + switch (cmd) { + case ASRC_REQ_PAIR: + ret = fsl_asrc_ioctl_req_pair(pair, user); + break; + case ASRC_CONFIG_PAIR: + ret = fsl_asrc_ioctl_config_pair(pair, user); + break; + case ASRC_RELEASE_PAIR: + ret = fsl_asrc_ioctl_release_pair(pair, user); + break; + case ASRC_CONVERT: + ret = fsl_asrc_ioctl_convert(pair, user); + break; + case ASRC_START_CONV: + ret = fsl_asrc_ioctl_start_conv(pair, user); + break; + case ASRC_STOP_CONV: + ret = fsl_asrc_ioctl_stop_conv(pair, user); + break; + case ASRC_STATUS: + ret = fsl_asrc_ioctl_status(pair, user); + break; + case ASRC_FLUSH: + ret = fsl_asrc_ioctl_flush(pair, user); + break; + default: + dev_err(&asrc_priv->pdev->dev, "invalid ioctl cmd!\n"); + break; + } + + return ret; +} + +static int fsl_asrc_open(struct inode *inode, struct file *file) +{ + struct miscdevice *asrc_miscdev = file->private_data; + struct fsl_asrc *asrc_priv = dev_get_drvdata(asrc_miscdev->parent); + struct device *dev = &asrc_priv->pdev->dev; + struct fsl_asrc_pair *pair; + struct fsl_asrc_m2m *m2m; + int ret; + + ret = signal_pending(current); + if (ret) { + dev_err(dev, "current process has a signal pending\n"); + return ret; + } + + pair = kzalloc(sizeof(struct fsl_asrc_pair), GFP_KERNEL); + if (!pair) { + dev_err(dev, "failed to allocate pair\n"); + return -ENOMEM; + } + + m2m = kzalloc(sizeof(struct fsl_asrc_m2m), GFP_KERNEL); + if (!m2m) { + dev_err(dev, "failed to allocate m2m resource\n"); + ret = -ENOMEM; + goto out; + } + + pair->private = m2m; + pair->asrc_priv = asrc_priv; + + spin_lock_init(&m2m->lock); + + file->private_data = pair; + + pm_runtime_get_sync(dev); + + return 0; +out: + kfree(pair); + + return ret; +} + +static int fsl_asrc_close(struct inode *inode, struct file *file) +{ + struct fsl_asrc_pair *pair = file->private_data; + struct fsl_asrc_m2m *m2m = pair->private; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct device *dev = &asrc_priv->pdev->dev; + unsigned long lock_flags; + + if (m2m->asrc_active) { + m2m->asrc_active = 0; + + dmaengine_terminate_all(pair->dma_chan[IN]); + dmaengine_terminate_all(pair->dma_chan[OUT]); + + fsl_asrc_stop_pair(pair); + fsl_asrc_input_dma_callback((void *)pair); + fsl_asrc_output_dma_callback((void *)pair); + } + + spin_lock_irqsave(&m2m->lock, lock_flags); + if (m2m->pair_hold) { + m2m->pair_hold = 0; + spin_unlock_irqrestore(&m2m->lock, lock_flags); + + if (pair->dma_chan[IN]) + dma_release_channel(pair->dma_chan[IN]); + if (pair->dma_chan[OUT]) + dma_release_channel(pair->dma_chan[OUT]); + + kfree(m2m->dma_block[IN].dma_vaddr); + kfree(m2m->dma_block[OUT].dma_vaddr); + + fsl_asrc_release_pair(pair); + } else + spin_unlock_irqrestore(&m2m->lock, lock_flags); + + spin_lock_irqsave(&asrc_priv->lock, lock_flags); + kfree(m2m); + kfree(pair); + spin_unlock_irqrestore(&asrc_priv->lock, lock_flags); + file->private_data = NULL; + + pm_runtime_put_sync(dev); + + return 0; +} + +static const struct file_operations asrc_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = fsl_asrc_ioctl, + .open = fsl_asrc_open, + .release = fsl_asrc_close, +}; + +static int fsl_asrc_m2m_init(struct fsl_asrc *asrc_priv) +{ + struct device *dev = &asrc_priv->pdev->dev; + int ret; + + asrc_priv->asrc_miscdev.fops = &asrc_fops; + asrc_priv->asrc_miscdev.parent = dev; + asrc_priv->asrc_miscdev.name = asrc_priv->name; + asrc_priv->asrc_miscdev.minor = MISC_DYNAMIC_MINOR; + ret = misc_register(&asrc_priv->asrc_miscdev); + if (ret) { + dev_err(dev, "failed to register char device %d\n", ret); + return ret; + } + + return 0; +} + +static int fsl_asrc_m2m_remove(struct platform_device *pdev) +{ + struct fsl_asrc *asrc_priv = dev_get_drvdata(&pdev->dev); + + misc_deregister(&asrc_priv->asrc_miscdev); + return 0; +} + +static void fsl_asrc_m2m_suspend(struct fsl_asrc *asrc_priv) +{ + struct fsl_asrc_pair *pair; + struct fsl_asrc_m2m *m2m; + unsigned long lock_flags; + int i; + + for (i = 0; i < ASRC_PAIR_MAX_NUM; i++) { + spin_lock_irqsave(&asrc_priv->lock, lock_flags); + pair = asrc_priv->pair[i]; + if (!pair || !pair->private) { + spin_unlock_irqrestore(&asrc_priv->lock, lock_flags); + continue; + } + m2m = pair->private; + + if (!completion_done(&m2m->complete[IN])) { + if (pair->dma_chan[IN]) + dmaengine_terminate_all(pair->dma_chan[IN]); + fsl_asrc_input_dma_callback((void *)pair); + } + if (!completion_done(&m2m->complete[OUT])) { + if (pair->dma_chan[OUT]) + dmaengine_terminate_all(pair->dma_chan[OUT]); + fsl_asrc_output_dma_callback((void *)pair); + } + + spin_unlock_irqrestore(&asrc_priv->lock, lock_flags); + } +} diff --git a/sound/soc/fsl/fsl_dma_workaround.c b/sound/soc/fsl/fsl_dma_workaround.c new file mode 100644 index 000000000000..22ccef7fc08c --- /dev/null +++ b/sound/soc/fsl/fsl_dma_workaround.c @@ -0,0 +1,254 @@ +/* + * fsl_dma_workaround.c - DMA workaround bits + * + * Copyright (C) 2017 NXP + * + * Author: Daniel Baluta <daniel.baluta@nxp.com> + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ +#include <linux/slab.h> +#include "fsl_acm.h" +#include "fsl_dma_workaround.h" + +int gpt_events[2][2] = { + {ESAI0_IPD_ESAI_TX_B, ESAI0_IPD_ESAI_RX_B}, + {SPDIF0_DRQ1_SPDIF_B, SPDIF0_DRQ0_SPDIF_B}, +}; + +/* + * configure_gpt_dma - configures GPT DMA for a given audio interface + * @substream: PCM substream + * @info: DMA workaround specific info + * + */ +int configure_gpt_dma(struct snd_pcm_substream *substream, + struct fsl_dma_workaround_info *info) +{ + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + + if (tx) { + writel_relaxed(gpt_events[info->iface][0], + info->base_acm + GPT0_CAPIN1_SEL_OFF); + writel_relaxed(gpt_events[info->iface][0], + info->base_acm + GPT1_CAPIN1_SEL_OFF); + + writel(le32_to_cpu(info->tcd_sw[0].vtcd->saddr), + info->base_edma_gpt1 + EDMA_TCD_SADDR); + writel(le32_to_cpu(info->tcd_sw[0].vtcd->daddr), + info->base_edma_gpt1 + EDMA_TCD_DADDR); + writew(le16_to_cpu(info->tcd_sw[0].vtcd->attr), + info->base_edma_gpt1 + EDMA_TCD_ATTR); + writew(le16_to_cpu(info->tcd_sw[0].vtcd->soff), + info->base_edma_gpt1 + EDMA_TCD_SOFF); + writel(le32_to_cpu(info->tcd_sw[0].vtcd->nbytes), + info->base_edma_gpt1 + EDMA_TCD_NBYTES); + writel(le32_to_cpu(info->tcd_sw[0].vtcd->slast), + info->base_edma_gpt1 + EDMA_TCD_SLAST); + writew(le16_to_cpu(info->tcd_sw[0].vtcd->citer), + info->base_edma_gpt1 + EDMA_TCD_CITER); + writew(le16_to_cpu(info->tcd_sw[0].vtcd->biter), + info->base_edma_gpt1 + EDMA_TCD_BITER); + writew(le16_to_cpu(info->tcd_sw[0].vtcd->doff), + info->base_edma_gpt1 + EDMA_TCD_DOFF); + writel(le32_to_cpu(info->tcd_sw[0].vtcd->dlast_sga), + info->base_edma_gpt1 + EDMA_TCD_DLAST_SGA); + writew(le16_to_cpu(info->tcd_sw[0].vtcd->csr), + info->base_edma_gpt1 + EDMA_TCD_CSR); + + writel(0x0, info->base_edma_gpt1 + EDMA_CH_SBR); + writel(0x1, info->base_edma_gpt1 + EDMA_CH_CSR); + + /* configure this gpt for dma tx */ + writel_relaxed(0x8, info->base_gpt0 + GPT_IR); + writel_relaxed(0x7<<12, info->base_gpt0 + GPT_PR); + writel_relaxed(0x20441, info->base_gpt0 + GPT_CR); + + /* configure this gpt for dma tx request clear */ + writel_relaxed(0x8, info->base_gpt1 + GPT_IR); + writel_relaxed(0x7<<12, info->base_gpt1 + GPT_PR); + writel_relaxed(0x10441, info->base_gpt1 + GPT_CR); + + } else { + writel_relaxed(gpt_events[info->iface][1], + info->base_acm + GPT2_CAPIN1_SEL_OFF); + writel_relaxed(gpt_events[info->iface][1], + info->base_acm + GPT3_CAPIN1_SEL_OFF); + + writel(le32_to_cpu(info->tcd_sw[2].vtcd->saddr), + info->base_edma_gpt3 + EDMA_TCD_SADDR); + writel(le32_to_cpu(info->tcd_sw[2].vtcd->daddr), + info->base_edma_gpt3 + EDMA_TCD_DADDR); + writew(le16_to_cpu(info->tcd_sw[2].vtcd->attr), + info->base_edma_gpt3 + EDMA_TCD_ATTR); + writew(le16_to_cpu(info->tcd_sw[2].vtcd->soff), + info->base_edma_gpt3 + EDMA_TCD_SOFF); + writel(le32_to_cpu(info->tcd_sw[2].vtcd->nbytes), + info->base_edma_gpt3 + EDMA_TCD_NBYTES); + writel(le32_to_cpu(info->tcd_sw[2].vtcd->slast), + info->base_edma_gpt3 + EDMA_TCD_SLAST); + writew(le16_to_cpu(info->tcd_sw[2].vtcd->citer), + info->base_edma_gpt3 + EDMA_TCD_CITER); + writew(le16_to_cpu(info->tcd_sw[2].vtcd->biter), + info->base_edma_gpt3 + EDMA_TCD_BITER); + writew(le16_to_cpu(info->tcd_sw[2].vtcd->doff), + info->base_edma_gpt3 + EDMA_TCD_DOFF); + writel(le32_to_cpu(info->tcd_sw[2].vtcd->dlast_sga), + info->base_edma_gpt3 + EDMA_TCD_DLAST_SGA); + writew(le16_to_cpu(info->tcd_sw[2].vtcd->csr), + info->base_edma_gpt3 + EDMA_TCD_CSR); + + writel(0x0, info->base_edma_gpt3 + EDMA_CH_SBR); + writel(0x1, info->base_edma_gpt3 + EDMA_CH_CSR); + + /* configure this gpt for dma rx */ + writel_relaxed(0x8, info->base_gpt2 + GPT_IR); + writel_relaxed(0x7<<12, info->base_gpt2 + GPT_PR); + writel_relaxed(0x20441, info->base_gpt2 + GPT_CR); + + /* configure this gpt for dma rx request clear*/ + writel_relaxed(0x8, info->base_gpt3 + GPT_IR); + writel_relaxed(0x7<<12, info->base_gpt3 + GPT_PR); + writel_relaxed(0x10441, info->base_gpt3 + GPT_CR); + + } + + return 0; +} + + +int clear_gpt_dma(struct snd_pcm_substream *substream, + struct fsl_dma_workaround_info *info) +{ + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + u32 val; + + if (tx) { + val = readl(info->base_edma_gpt1 + EDMA_CH_CSR); + val &= ~0x1; + writel(val, info->base_edma_gpt1 + EDMA_CH_CSR); + + /* disable gpt */ + writel_relaxed(0, info->base_gpt0 + GPT_IR); + writel_relaxed(0, info->base_gpt0 + GPT_PR); + writel_relaxed(0, info->base_gpt0 + GPT_CR); + + writel_relaxed(0, info->base_gpt1 + GPT_IR); + writel_relaxed(0, info->base_gpt1 + GPT_PR); + writel_relaxed(0, info->base_gpt1 + GPT_CR); + + } else { + val = readl(info->base_edma_gpt3 + EDMA_CH_CSR); + val &= ~0x1; + writel(val, info->base_edma_gpt3 + EDMA_CH_CSR); + + /* disable gpt */ + writel_relaxed(0, info->base_gpt2 + GPT_IR); + writel_relaxed(0, info->base_gpt2 + GPT_PR); + writel_relaxed(0, info->base_gpt2 + GPT_CR); + + writel_relaxed(0, info->base_gpt3 + GPT_IR); + writel_relaxed(0, info->base_gpt3 + GPT_PR); + writel_relaxed(0, info->base_gpt3 + GPT_CR); + } + + return 0; +} + +struct fsl_dma_workaround_info * +fsl_dma_workaround_alloc_info(const char *pool_name, struct device *dma_dev, + const char *base_acm_compat, int iface) +{ + struct fsl_dma_workaround_info *info; + int *buffer; + int i; + + info = devm_kzalloc(dma_dev, sizeof(*info), GFP_KERNEL); + if (!info) + return ERR_PTR(-ENOMEM); + + info->tcd_pool = dma_pool_create(pool_name, dma_dev, + sizeof(struct fsl_edma3_hw_tcd), + 32, 0); + + info->buf.area = dma_alloc_writecombine(dma_dev, 0x1000, + &info->buf.addr, GFP_KERNEL); + + buffer = (int *)info->buf.area; + buffer[0] = 0x8; + + info->tcd_sw[0].vtcd = dma_pool_alloc(info->tcd_pool, + GFP_ATOMIC, &info->tcd_sw[0].ptcd); + info->tcd_sw[1].vtcd = dma_pool_alloc(info->tcd_pool, + GFP_ATOMIC, &info->tcd_sw[1].ptcd); + info->tcd_sw[2].vtcd = dma_pool_alloc(info->tcd_pool, + GFP_ATOMIC, &info->tcd_sw[2].ptcd); + info->tcd_sw[3].vtcd = dma_pool_alloc(info->tcd_pool, + GFP_ATOMIC, &info->tcd_sw[3].ptcd); + + for (i = 0; i < 4; i++) { + info->tcd_sw[i].vtcd->saddr = info->buf.addr; + info->tcd_sw[i].vtcd->attr = 0x0202; + info->tcd_sw[i].vtcd->soff = 0x0; + info->tcd_sw[i].vtcd->nbytes = 0x4; + info->tcd_sw[i].vtcd->slast = 0x0; + info->tcd_sw[i].vtcd->citer = 0x1; + info->tcd_sw[i].vtcd->biter = 0x1; + info->tcd_sw[i].vtcd->doff = 0x0; + info->tcd_sw[i].vtcd->csr = 0x10; + } + + info->tcd_sw[0].vtcd->daddr = GPT5_ADDR + GPT_SR; + info->tcd_sw[1].vtcd->daddr = GPT6_ADDR + GPT_SR; + info->tcd_sw[2].vtcd->daddr = GPT7_ADDR + GPT_SR; + info->tcd_sw[3].vtcd->daddr = GPT8_ADDR + GPT_SR; + + info->tcd_sw[0].vtcd->dlast_sga = + info->tcd_sw[1].ptcd; + info->tcd_sw[1].vtcd->dlast_sga = + info->tcd_sw[0].ptcd; + info->tcd_sw[2].vtcd->dlast_sga = + info->tcd_sw[3].ptcd; + info->tcd_sw[3].vtcd->dlast_sga = + info->tcd_sw[2].ptcd; + + info->base_gpt0 = ioremap(GPT5_ADDR, SZ_64K); + info->base_gpt1 = ioremap(GPT6_ADDR, SZ_64K); + info->base_gpt2 = ioremap(GPT7_ADDR, SZ_64K); + info->base_gpt3 = ioremap(GPT8_ADDR, SZ_64K); + + info->base_edma_gpt1 = ioremap(EDMA_GPT6_ADDR, SZ_64K); + info->base_edma_gpt3 = ioremap(EDMA_GPT8_ADDR, SZ_64K); + + info->base_acm = of_iomap(of_find_compatible_node( + NULL, NULL, base_acm_compat), 0); + + info->iface = iface; + return info; +} + +void fsl_dma_workaround_free_info(struct fsl_dma_workaround_info *info, + struct device *dma_dev) +{ + dma_free_writecombine(dma_dev, + 0x1000, + info->buf.area, + info->buf.addr); + + dma_pool_free(info->tcd_pool, + info->tcd_sw[0].vtcd, + info->tcd_sw[0].ptcd); + dma_pool_free(info->tcd_pool, + info->tcd_sw[1].vtcd, + info->tcd_sw[1].ptcd); + dma_pool_free(info->tcd_pool, + info->tcd_sw[2].vtcd, + info->tcd_sw[2].ptcd); + dma_pool_free(info->tcd_pool, + info->tcd_sw[3].vtcd, + info->tcd_sw[3].ptcd); + + dma_pool_destroy(info->tcd_pool); +} diff --git a/sound/soc/fsl/fsl_dma_workaround.h b/sound/soc/fsl/fsl_dma_workaround.h new file mode 100644 index 000000000000..0602817ebd7f --- /dev/null +++ b/sound/soc/fsl/fsl_dma_workaround.h @@ -0,0 +1,102 @@ +/* + * fsl_dma_workaround.h - EDMA bits useful for DMA workaround + * + * Copyright (C) 2017 NXP + * + * Author: Daniel Baluta <daniel.baluta@nxp.com> + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#ifndef _FSL_DMA_WORKAROUND_H +#define _FSL_DMA_WORKAROUND_H + +#include <sound/pcm.h> +#include <linux/dmapool.h> +#include <linux/dma-mapping.h> +#include <linux/of.h> +#include <linux/of_address.h> + +#define EDMA_CH_CSR 0x00 +#define EDMA_CH_ES 0x04 +#define EDMA_CH_INT 0x08 +#define EDMA_CH_SBR 0x0C +#define EDMA_CH_PRI 0x10 +#define EDMA_TCD_SADDR 0x20 +#define EDMA_TCD_SOFF 0x24 +#define EDMA_TCD_ATTR 0x26 +#define EDMA_TCD_NBYTES 0x28 +#define EDMA_TCD_SLAST 0x2C +#define EDMA_TCD_DADDR 0x30 +#define EDMA_TCD_DOFF 0x34 +#define EDMA_TCD_CITER_ELINK 0x36 +#define EDMA_TCD_CITER 0x36 +#define EDMA_TCD_DLAST_SGA 0x38 +#define EDMA_TCD_CSR 0x3C +#define EDMA_TCD_BITER_ELINK 0x3E +#define EDMA_TCD_BITER 0x3E + +#define GPT_CR 0x00 +#define GPT_PR 0x04 +#define GPT_SR 0x08 +#define GPT_IR 0x0C + +#define GPT5_ADDR 0x590b0000 +#define GPT6_ADDR 0x590c0000 +#define GPT7_ADDR 0x590d0000 +#define GPT8_ADDR 0x590e0000 + +#define EDMA_GPT6_ADDR 0x59360000 +#define EDMA_GPT8_ADDR 0x59380000 + +#define FSL_DMA_WORKAROUND_ESAI 0 +#define FSL_DMA_WORKAROUND_SPDIF 1 + +struct fsl_edma3_hw_tcd { + __le32 saddr; + __le16 soff; + __le16 attr; + __le32 nbytes; + __le32 slast; + __le32 daddr; + __le16 doff; + __le16 citer; + __le32 dlast_sga; + __le16 csr; + __le16 biter; +}; + +struct fsl_edma3_sw_tcd { + dma_addr_t ptcd; + struct fsl_edma3_hw_tcd *vtcd; +}; + +struct fsl_dma_workaround_info { + struct fsl_edma3_sw_tcd tcd_sw[4]; + struct dma_pool *tcd_pool; + struct snd_dma_buffer buf; + void __iomem *base_gpt0; + void __iomem *base_gpt1; + void __iomem *base_gpt2; + void __iomem *base_gpt3; + void __iomem *base_edma_gpt1; + void __iomem *base_edma_gpt3; + void __iomem *base_acm; + int iface; +}; + +int configure_gpt_dma(struct snd_pcm_substream *substream, + struct fsl_dma_workaround_info *info); + +int clear_gpt_dma(struct snd_pcm_substream *substream, + struct fsl_dma_workaround_info *info); + +struct fsl_dma_workaround_info * +fsl_dma_workaround_alloc_info(const char *pool_name, struct device *dma_dev, + const char *base_acm_compat, int iface); + +void fsl_dma_workaround_free_info(struct fsl_dma_workaround_info *info, + struct device *dma_dev); +#endif /* _FSL_EDMA_H */ diff --git a/sound/soc/fsl/fsl_dsd.h b/sound/soc/fsl/fsl_dsd.h new file mode 100644 index 000000000000..bc149631a2af --- /dev/null +++ b/sound/soc/fsl/fsl_dsd.h @@ -0,0 +1,58 @@ +/* + * Copyright 2018 NXP + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __FSL_DSD_H +#define __FSL_DSD_H + +#include <linux/pinctrl/consumer.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +static bool fsl_is_dsd(struct snd_pcm_hw_params *params) +{ + snd_pcm_format_t format = params_format(params); + + switch (format) { + case SNDRV_PCM_FORMAT_DSD_U8: + case SNDRV_PCM_FORMAT_DSD_U16_LE: + case SNDRV_PCM_FORMAT_DSD_U16_BE: + case SNDRV_PCM_FORMAT_DSD_U32_LE: + case SNDRV_PCM_FORMAT_DSD_U32_BE: + return true; + default: + return false; + } +} + +static struct pinctrl_state *fsl_get_pins_state(struct pinctrl *pinctrl, + struct snd_pcm_hw_params *params, u32 bclk) +{ + struct pinctrl_state *state = 0; + + if (fsl_is_dsd(params)) { + /* DSD512@44.1kHz, DSD512@48kHz */ + if (bclk >= 22579200) + state = pinctrl_lookup_state(pinctrl, "dsd512"); + + /* Get default DSD state */ + if (IS_ERR_OR_NULL(state)) + state = pinctrl_lookup_state(pinctrl, "dsd"); + } else { + /* 706k32b2c, 768k32b2c, etc */ + if (bclk >= 45158400) + state = pinctrl_lookup_state(pinctrl, "pcm_b2m"); + } + + /* Get default state */ + if (IS_ERR_OR_NULL(state)) + state = pinctrl_lookup_state(pinctrl, "default"); + + return state; +} + +#endif /* __FSL_DSD_H */ diff --git a/sound/soc/fsl/fsl_dsp.c b/sound/soc/fsl/fsl_dsp.c new file mode 100644 index 000000000000..e8a77eb1660c --- /dev/null +++ b/sound/soc/fsl/fsl_dsp.c @@ -0,0 +1,1050 @@ +/* + * Freescale DSP driver + * + * Copyright (c) 2012-2013 by Tensilica Inc. ALL RIGHTS RESERVED. + * Copyright 2018 NXP + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * Copyright (c) 2001 William L. Pitts + * All rights reserved. + * + * Redistribution and use in source and binary forms are freely + * permitted provided that the above copyright notice and this + * paragraph and the following disclaimer are duplicated in all + * such forms. + * + * This software is provided "AS IS" and without any express or + * implied warranties, including, without limitation, the implied + * warranties of merchantability and fitness for a particular + * purpose. + * + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/module.h> +#include <linux/firmware.h> +#include <linux/interrupt.h> +#include <linux/file.h> +#include <linux/of_platform.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/slab.h> +#include <linux/platform_data/dma-imx.h> +#include <linux/miscdevice.h> +#include <linux/fs.h> +#include <linux/pm_runtime.h> +#include <linux/mx8_mu.h> +#include <linux/uaccess.h> +#include <linux/poll.h> +#ifdef CONFIG_COMPAT +#include <linux/compat.h> +#endif +#include <uapi/linux/mxc_dsp.h> +#include <soc/imx8/sc/svc/irq/api.h> +#include <soc/imx8/sc/ipc.h> +#include <soc/imx8/sc/sci.h> +#include "fsl_dsp.h" + + +/* ...allocate new client */ +static inline struct xf_client *xf_client_alloc(struct fsl_dsp *dsp_priv) +{ + struct xf_client *client; + u32 id; + + id = dsp_priv->xf_client_map[0].next; + + /* ...try to allocate a client handle */ + if (id != 0) { + /* ...allocate client memory */ + client = kmalloc(sizeof(*client), GFP_KERNEL); + if (!client) + return ERR_PTR(-ENOMEM); + + /* ...advance the head of free clients */ + dsp_priv->xf_client_map[0].next = + dsp_priv->xf_client_map[id].next; + + /* ...put associate client id with given object */ + dsp_priv->xf_client_map[id].client = client; + + /* ...mark client is not yet bound to proxy */ + client->proxy = NULL; + + /* ...save global proxy client identifier */ + client->id = id; + + return client; + } + + /* ...number of clients exceeded */ + return ERR_PTR(-EBUSY); +} + +/* ...recycle client object */ +static inline void xf_client_free(struct xf_client *client) +{ + int id = client->id; + struct fsl_dsp *dsp_priv = (struct fsl_dsp *)client->global; + + /* ...put proxy client id into free clients list */ + dsp_priv->xf_client_map[id].next = dsp_priv->xf_client_map[0].next; + dsp_priv->xf_client_map[0].next = id; + + /* ...destroy client data */ + kfree(client); +} + +/* ...lookup client basing on id */ +struct xf_client *xf_client_lookup(struct fsl_dsp *dsp_priv, u32 id) +{ + if ((id >= XF_CFG_MAX_IPC_CLIENTS) || + (dsp_priv->xf_client_map[id].next < XF_CFG_MAX_IPC_CLIENTS) + ) + return NULL; + else + return dsp_priv->xf_client_map[id].client; +} + +/* ...helper function for retrieving the client handle */ +static inline struct xf_client *xf_get_client(struct file *file) +{ + struct xf_client *client; + u32 id; + + client = (struct xf_client *)file->private_data; + if (!client) + return ERR_PTR(-EINVAL); + + id = client->id; + if (id >= XF_CFG_MAX_IPC_CLIENTS) + return ERR_PTR(-EINVAL); + + return client; +} + +static int fsl_dsp_client_register(struct xf_client *client) +{ + struct fsl_dsp *dsp_priv; + struct device *dev; + + dsp_priv = (struct fsl_dsp *)client->global; + dev = dsp_priv->dev; + + /* ...make sure client is not registered yet */ + if (client->proxy != NULL) { + pr_err("client-%x already registered", client->id); + return -EBUSY; + } + + /* ...complete association (no communication with remote proxy here) */ + client->proxy = &dsp_priv->proxy; + + pr_debug("client-%x registered within proxy", client->id); + + return 0; +} + +/* ...unregister client from shared memory interface */ +static int fsl_dsp_client_unregister(struct xf_client *client) +{ + struct xf_proxy *proxy = client->proxy; + + /* ...make sure client is registered */ + if (proxy == NULL) { + pr_err("client-%x is not registered", client->id); + return -EBUSY; + } + + /* ...just clean proxy reference */ + client->proxy = NULL; + + pr_debug("client-%x registered within proxy", client->id); + + return 0; +} + +static int fsl_dsp_ipc_msg_to_dsp(struct xf_client *client, + void __user *user) +{ + struct fsl_dsp *dsp_priv = (struct fsl_dsp *)client->global; + struct device *dev = dsp_priv->dev; + struct xf_proxy_message msg; + void *buffer; + unsigned long ret = 0; + + ret = copy_from_user(&msg, user, sizeof(struct xf_proxy_message)); + if (ret) { + dev_err(dev, "failed to get message from user space\n"); + return -EFAULT; + } + + /* ...make sure message pointer is sane */ + buffer = xf_proxy_a2b(&dsp_priv->proxy, msg.address); + if (buffer == (void *)-1) + return -EFAULT; + + /* ...put current proxy client into message session id */ + msg.session_id = XF_MSG_AP_FROM_USER(msg.session_id, client->id); + + xf_cmd_send(&dsp_priv->proxy, + msg.session_id, + msg.opcode, + buffer, + msg.length); + + return 0; +} + +static int fsl_dsp_ipc_msg_from_dsp(struct xf_client *client, + void __user *user) +{ + struct fsl_dsp *dsp_priv = (struct fsl_dsp *)client->global; + struct device *dev = dsp_priv->dev; + struct xf_message *m; + struct xf_proxy_message msg; + unsigned long ret = 0; + + m = xf_cmd_recv(&dsp_priv->proxy, &client->wait, &client->queue, 0); + if (IS_ERR(m)) { + dev_err(dev, "receiving failed: %d", (int)PTR_ERR(m)); + return PTR_ERR(m); + } + + /* ...check if there is a response available */ + if (m == NULL) + return -EAGAIN; + + /* ...prepare message parameters (lock is taken) */ + msg.session_id = XF_MSG_AP_TO_USER(m->id); + msg.opcode = m->opcode; + msg.length = m->length; + msg.address = xf_proxy_b2a(&dsp_priv->proxy, m->buffer); + msg.ret = m->ret; + + /* ...return the message back to a pool and release lock */ + xf_msg_free(&dsp_priv->proxy, m); + xf_unlock(&dsp_priv->proxy.lock); + + ret = copy_to_user(user, &msg, sizeof(struct xf_proxy_message)); + if (ret) { + dev_err(dev, "failed to response message to user space\n"); + return -EFAULT; + } + + return 0; +} + +static int fsl_dsp_get_shmem_info(struct xf_client *client, + void __user *user) +{ + struct fsl_dsp *dsp_priv = (struct fsl_dsp *)client->global; + struct device *dev = dsp_priv->dev; + struct shmem_info mem_info; + unsigned long ret = 0; + + mem_info.phys_addr = dsp_priv->scratch_buf_phys; + mem_info.size = dsp_priv->scratch_buf_size; + + ret = copy_to_user(user, &mem_info, sizeof(struct shmem_info)); + if (ret) { + dev_err(dev, "failed to response message to user space\n"); + return -EFAULT; + } + + return ret; +} + +static struct miscdevice dsp_miscdev = { + .name = "mxc_hifi4", + .minor = MISC_DYNAMIC_MINOR, +}; + +static long fsl_dsp_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct xf_client *client; + struct fsl_dsp *dsp_priv; + struct xf_proxy *proxy; + struct device *dev; + void __user *user; + long ret = 0; + + /* ...basic sanity checks */ + client = xf_get_client(file); + if (IS_ERR(client)) + return PTR_ERR(client); + + dsp_priv = (struct fsl_dsp *)client->global; + proxy = &dsp_priv->proxy; + dev = dsp_priv->dev; + user = (void __user *)arg; + + mutex_lock(&dsp_priv->dsp_mutex); + + if (!proxy->is_ready) { + mutex_unlock(&dsp_priv->dsp_mutex); + dev_err(dev, "dsp firmware is not ready\n"); + return -EFAULT; + } + + switch (cmd) { + case DSP_CLIENT_REGISTER: + ret = fsl_dsp_client_register(client); + break; + case DSP_CLIENT_UNREGISTER: + ret = fsl_dsp_client_unregister(client); + break; + case DSP_IPC_MSG_SEND: + ret = fsl_dsp_ipc_msg_to_dsp(client, user); + break; + case DSP_IPC_MSG_RECV: + ret = fsl_dsp_ipc_msg_from_dsp(client, user); + break; + case DSP_GET_SHMEM_INFO: + ret = fsl_dsp_get_shmem_info(client, user); + break; + default: + break; + } + + mutex_unlock(&dsp_priv->dsp_mutex); + + return ret; +} + +void resource_release(struct fsl_dsp *dsp_priv) +{ + int i; + + /* ...initialize client association map */ + for (i = 0; i < XF_CFG_MAX_IPC_CLIENTS - 1; i++) + dsp_priv->xf_client_map[i].next = i + 1; + /* ...set list terminator */ + dsp_priv->xf_client_map[i].next = 0; + + /* ...set pointer to shared memory */ + xf_proxy_init(&dsp_priv->proxy); +} + +static int fsl_dsp_open(struct inode *inode, struct file *file) +{ + struct fsl_dsp *dsp_priv = dev_get_drvdata(dsp_miscdev.parent); + struct device *dev = dsp_priv->dev; + struct xf_client *client; + int ret = 0; + + /* ...basic sanity checks */ + if (!inode || !file) + return -EINVAL; + + /* ...allocate new proxy client object */ + client = xf_client_alloc(dsp_priv); + if (IS_ERR(client)) + return PTR_ERR(client); + + /* ...initialize waiting queue */ + init_waitqueue_head(&client->wait); + + /* ...initialize client pending message queue */ + xf_msg_queue_init(&client->queue); + + /* ...mark user data is not mapped */ + client->vm_start = 0; + + /* ...reset mappings counter */ + atomic_set(&client->vm_use, 0); + + client->global = (void *)dsp_priv; + + file->private_data = (void *)client; + + pm_runtime_get_sync(dev); + + mutex_lock(&dsp_priv->dsp_mutex); + /* increase reference counter when opening device */ + atomic_long_inc(&dsp_priv->refcnt); + mutex_unlock(&dsp_priv->dsp_mutex); + + return ret; +} + +static int fsl_dsp_close(struct inode *inode, struct file *file) +{ + struct fsl_dsp *dsp_priv; + struct device *dev; + struct xf_proxy *proxy; + struct xf_client *client; + + /* ...basic sanity checks */ + client = xf_get_client(file); + if (IS_ERR(client)) + return PTR_ERR(client); + + proxy = client->proxy; + + /* release all pending messages */ + if (proxy) + xf_msg_free_all(proxy, &client->queue); + + dsp_priv = (struct fsl_dsp *)client->global; + dev = dsp_priv->dev; + pm_runtime_put_sync(dev); + + /* ...recycle client id and release memory */ + xf_client_free(client); + + mutex_lock(&dsp_priv->dsp_mutex); + /* decrease reference counter when closing device */ + atomic_long_dec(&dsp_priv->refcnt); + /* If device is free, reinitialize the resource of + * dsp driver and framework + */ + if (atomic_long_read(&dsp_priv->refcnt) <= 0) + resource_release(dsp_priv); + + mutex_unlock(&dsp_priv->dsp_mutex); + + return 0; +} + +/* ...wait until data is available in the response queue */ +static unsigned int fsl_dsp_poll(struct file *file, poll_table *wait) +{ + struct xf_proxy *proxy; + struct xf_client *client; + int mask; + + /* ...basic sanity checks */ + client = xf_get_client(file); + if (IS_ERR(client)) + return PTR_ERR(client); + + /* ...get proxy interface */ + proxy = client->proxy; + if (!proxy) + return -EPERM; + + /* ...register client waiting queue */ + poll_wait(file, &client->wait, wait); + + /* ...return current queue state */ + mask = (xf_msg_queue_head(&client->queue) ? POLLIN | POLLRDNORM : 0); + + return mask; +} + +/******************************************************************************* + * Low-level mmap interface + ******************************************************************************/ + +/* ...add reference to shared buffer */ +static void dsp_mmap_open(struct vm_area_struct *vma) +{ + struct xf_client *client = vma->vm_private_data; + + /* ...probably just increase counter of open references? - tbd */ + atomic_inc(&client->vm_use); + + pr_debug("xf_mmap_open: vma = %p, client = %p", vma, client); +} + +/* ...close reference to shared buffer */ +static void dsp_mmap_close(struct vm_area_struct *vma) +{ + struct xf_client *client = vma->vm_private_data; + + pr_debug("xf_mmap_close: vma = %p, b = %p", vma, client); + + /* ...decrement number of mapping */ + atomic_dec_return(&client->vm_use); +} + +/* ...memory map operations */ +static const struct vm_operations_struct dsp_mmap_ops = { + .open = dsp_mmap_open, + .close = dsp_mmap_close, +}; + +/* ...shared memory mapping */ +static int fsl_dsp_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct xf_proxy *proxy; + struct xf_client *client; + unsigned long size; + unsigned long pfn; + int r; + struct fsl_dsp *dsp_priv; + + /* ...basic sanity checks */ + client = xf_get_client(file); + if (IS_ERR(client)) + return PTR_ERR(client); + + /* ...get proxy interface */ + proxy = client->proxy; + if (!proxy) + return -EPERM; + + /* ...check it was not mapped already */ + if (client->vm_start != 0) + return -EBUSY; + + /* ...check mapping flags (tbd) */ + if ((vma->vm_flags & (VM_READ | VM_WRITE | VM_SHARED)) + != (VM_READ | VM_WRITE | VM_SHARED)) + return -EPERM; + + /* ...set memory map operations */ + vma->vm_ops = &dsp_mmap_ops; + + /* ...assign private data */ + client->vm_start = vma->vm_start; + + /* ...set private memory data */ + vma->vm_private_data = client; + + /* ...set page number of shared memory */ + dsp_priv = (struct fsl_dsp *)client->global; + pfn = dsp_priv->scratch_buf_phys >> PAGE_SHIFT; + size = dsp_priv->scratch_buf_size; + + /* ...remap shared memory to user-space */ + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + r = remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot); + if (r != 0) { + pr_err("mapping failed: %d", r); + return r; + } + + /* ...system-specific hook for registering shared memory mapping */ + return 0; +} + +void *memset_dsp(void *dest, int c, size_t count) +{ + uint *dl = (uint *)dest; + void *dl_1, *dl_2; + size_t align = 4; + size_t n, n1, n2; + + /* while all data is aligned (common case), copy a word at a time */ + if ((((ulong)dest) & (sizeof(*dl) - 1)) != 0) { + dl = (unsigned int *)(((ulong)dest + align - 1) & + (~(align - 1))); + dl_1 = dest; + dl_2 = (void *)(((ulong)dest + count) & (~(align - 1))); + n1 = (ulong)dl - (ulong)dl_1; + n2 = (ulong)dest + count - (ulong)dl_2; + n = (count - n1 - n2) / align; + + while (n--) { + writel_relaxed(0, dl); + dl++; + } + while (n1--) { + writeb_relaxed(0, dl_1); + dl_1++; + } + while (n2--) { + writeb_relaxed(0, dl_2); + dl_2++; + } + } else { + n = count / align; + n1 = count - n * align; + dl_1 = dest + n * align; + while (n--) { + writel_relaxed(0, dl); + dl++; + } + while (n1--) { + writeb_relaxed(0, dl_1); + dl_1++; + } + } + + return dest; +} + +void *memcpy_dsp(void *dest, const void *src, size_t count) +{ + unsigned int *dl = (unsigned int *)dest, *sl = (unsigned int *)src; + size_t n = round_up(count, 4) / 4; + + if (src == dest) + return dest; + + /* while all data is aligned (common case), copy a word at a time */ + if ((((ulong)dest | (ulong)src) & (sizeof(*dl) - 1)) != 0) + pr_info("dest %p src %p not 4 bytes aligned\n", dest, src); + + while (n--) { + writel_relaxed(*sl, dl); + dl++; + sl++; + } + + return dest; +} + +static void dsp_load_firmware(const struct firmware *fw, void *context) +{ + struct fsl_dsp *dsp_priv = context; + struct device *dev = dsp_priv->dev; + Elf32_Ehdr *ehdr; /* Elf header structure pointer */ + Elf32_Shdr *shdr; /* Section header structure pointer */ + Elf32_Addr sh_addr; + unsigned char *strtab = 0; /* String table pointer */ + unsigned char *image; /* Binary image pointer */ + int i; /* Loop counter */ + unsigned long addr; + + if (!fw) { + dev_info(dev, "external firmware not found\n"); + return; + } + + addr = (unsigned long)fw->data; + ehdr = (Elf32_Ehdr *)addr; + + /* Find the section header string table for output info */ + shdr = (Elf32_Shdr *)(addr + ehdr->e_shoff + + (ehdr->e_shstrndx * sizeof(Elf32_Shdr))); + + if (shdr->sh_type == SHT_STRTAB) + strtab = (unsigned char *)(addr + shdr->sh_offset); + + /* Load each appropriate section */ + for (i = 0; i < ehdr->e_shnum; ++i) { + shdr = (Elf32_Shdr *)(addr + ehdr->e_shoff + + (i * sizeof(Elf32_Shdr))); + + if (!(shdr->sh_flags & SHF_ALLOC) || + shdr->sh_addr == 0 || shdr->sh_size == 0) + continue; + + if (strtab) { + dev_dbg(dev, "%sing %s @ 0x%08lx (%ld bytes)\n", + (shdr->sh_type == SHT_NOBITS) ? "Clear" : "Load", + &strtab[shdr->sh_name], + (unsigned long)shdr->sh_addr, + (long)shdr->sh_size); + } + + sh_addr = shdr->sh_addr; + + if (shdr->sh_type == SHT_NOBITS) { + memset_dsp((void *)(dsp_priv->sdram_vir_addr + + (sh_addr - dsp_priv->sdram_phys_addr)), + 0, + shdr->sh_size); + } else { + image = (unsigned char *)addr + shdr->sh_offset; + if ((!strcmp(&strtab[shdr->sh_name], ".rodata")) || + (!strcmp(&strtab[shdr->sh_name], ".text")) || + (!strcmp(&strtab[shdr->sh_name], ".data")) || + (!strcmp(&strtab[shdr->sh_name], ".bss")) + ) { + memcpy_dsp((void *)(dsp_priv->sdram_vir_addr + + (sh_addr - dsp_priv->sdram_phys_addr)), + (const void *)image, + shdr->sh_size); + } else { + /* sh_addr is from DSP view, we need to + * fixup addr because we load the firmware from + * the ARM core side + */ + sh_addr -= dsp_priv->fixup_offset; + + memcpy_dsp((void *)(dsp_priv->regs + + (sh_addr - dsp_priv->paddr)), + (const void *)image, + shdr->sh_size); + } + } + } + + /* start the core */ + sc_pm_cpu_start(dsp_priv->dsp_ipcHandle, + SC_R_DSP, true, dsp_priv->iram); +} + +/* Initialization of the MU code. */ +int dsp_mu_init(struct fsl_dsp *dsp_priv) +{ + struct device *dev = dsp_priv->dev; + struct device_node *np; + unsigned int dsp_mu_id; + u32 irq; + int ret = 0; + + /* + * Get the address of MU to be used for communication with the dsp + */ + np = of_find_compatible_node(NULL, NULL, "fsl,imx8-mu-dsp"); + if (!np) { + dev_err(dev, "Cannot find MU entry in device tree\n"); + return -EINVAL; + } + dsp_priv->mu_base_virtaddr = of_iomap(np, 0); + WARN_ON(!dsp_priv->mu_base_virtaddr); + + ret = of_property_read_u32_index(np, + "fsl,dsp_ap_mu_id", 0, &dsp_mu_id); + if (ret) { + dev_err(dev, "Cannot get mu_id %d\n", ret); + return -EINVAL; + } + + dsp_priv->dsp_mu_id = dsp_mu_id; + + irq = of_irq_get(np, 0); + + ret = devm_request_irq(dsp_priv->dev, irq, fsl_dsp_mu_isr, + IRQF_EARLY_RESUME, "dsp_mu_isr", &dsp_priv->proxy); + if (ret) { + dev_err(dev, "request_irq failed %d, err = %d\n", irq, ret); + return -EINVAL; + } + + return ret; +} + +static const struct file_operations dsp_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = fsl_dsp_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = fsl_dsp_ioctl, +#endif + .open = fsl_dsp_open, + .poll = fsl_dsp_poll, + .mmap = fsl_dsp_mmap, + .release = fsl_dsp_close, +}; + +static int fsl_dsp_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *reserved_node; + struct resource reserved_res; + struct fsl_dsp *dsp_priv; + const char *fw_name; + struct resource *res; + void __iomem *regs; + uint32_t mu_id; + sc_err_t sciErr; + void *buf_virt; + dma_addr_t buf_phys; + int size, offset, i; + int ret; + + dsp_priv = devm_kzalloc(&pdev->dev, sizeof(*dsp_priv), GFP_KERNEL); + if (!dsp_priv) + return -ENOMEM; + + dsp_priv->dev = &pdev->dev; + + /* Get the addresses and IRQ */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + dsp_priv->paddr = res->start; + dsp_priv->regs = regs; + + dsp_priv->dram0 = dsp_priv->paddr + DRAM0_OFFSET; + dsp_priv->dram1 = dsp_priv->paddr + DRAM1_OFFSET; + dsp_priv->iram = dsp_priv->paddr + IRAM_OFFSET; + dsp_priv->sram = dsp_priv->paddr + SYSRAM_OFFSET; + + sciErr = sc_ipc_getMuID(&mu_id); + if (sciErr != SC_ERR_NONE) { + dev_err(&pdev->dev, "Cannot obtain MU ID\n"); + return sciErr; + } + + sciErr = sc_ipc_open(&dsp_priv->dsp_ipcHandle, mu_id); + if (sciErr != SC_ERR_NONE) { + dev_err(&pdev->dev, "Cannot open MU channel to SCU %d, %d\n", + mu_id, sciErr); + return sciErr; + }; + + sciErr = sc_misc_set_control(dsp_priv->dsp_ipcHandle, SC_R_DSP, + SC_C_OFS_SEL, 1); + if (sciErr != SC_ERR_NONE) { + dev_err(&pdev->dev, "Error system address offset source select\n"); + return -EIO; + } + + sciErr = sc_misc_set_control(dsp_priv->dsp_ipcHandle, SC_R_DSP, + SC_C_OFS_AUDIO, 0x80); + if (sciErr != SC_ERR_NONE) { + dev_err(&pdev->dev, "Error system address offset of AUDIO\n"); + return -EIO; + } + + sciErr = sc_misc_set_control(dsp_priv->dsp_ipcHandle, SC_R_DSP, + SC_C_OFS_PERIPH, 0x5A); + if (sciErr != SC_ERR_NONE) { + dev_err(&pdev->dev, "Error system address offset of PERIPH %d\n", + sciErr); + } + + sciErr = sc_misc_set_control(dsp_priv->dsp_ipcHandle, SC_R_DSP, + SC_C_OFS_IRQ, 0x51); + if (sciErr != SC_ERR_NONE) { + dev_err(&pdev->dev, "Error system address offset of IRQ\n"); + return -EIO; + } + + ret = dsp_mu_init(dsp_priv); + if (ret) + return ret; + + ret = of_property_read_string(np, "fsl,dsp-firmware", &fw_name); + dsp_priv->fw_name = fw_name; + + ret = of_property_read_u32(np, "fixup-offset", &dsp_priv->fixup_offset); + + platform_set_drvdata(pdev, dsp_priv); + pm_runtime_enable(&pdev->dev); + + dsp_miscdev.fops = &dsp_fops, + dsp_miscdev.parent = &pdev->dev, + ret = misc_register(&dsp_miscdev); + if (ret) { + dev_err(&pdev->dev, "failed to register misc device %d\n", ret); + return ret; + } + + reserved_node = of_parse_phandle(np, "reserved-region", 0); + if (!reserved_node) { + dev_err(&pdev->dev, "failed to get reserved region node\n"); + return -ENODEV; + } + + if (of_address_to_resource(reserved_node, 0, &reserved_res)) { + dev_err(&pdev->dev, "failed to get reserved region address\n"); + return -EINVAL; + } + + dsp_priv->sdram_phys_addr = reserved_res.start; + dsp_priv->sdram_reserved_size = (reserved_res.end - reserved_res.start) + + 1; + if (dsp_priv->sdram_reserved_size <= 0) { + dev_err(&pdev->dev, "invalid value of reserved region size\n"); + return -EINVAL; + } + + dsp_priv->sdram_vir_addr = ioremap_wc(dsp_priv->sdram_phys_addr, + dsp_priv->sdram_reserved_size); + if (!dsp_priv->sdram_vir_addr) { + dev_err(&pdev->dev, "failed to remap sdram space for dsp firmware\n"); + return -ENXIO; + } + memset_io(dsp_priv->sdram_vir_addr, 0, dsp_priv->sdram_reserved_size); + + size = MSG_BUF_SIZE + DSP_CONFIG_SIZE; + + buf_virt = dma_alloc_coherent(&pdev->dev, size, &buf_phys, GFP_KERNEL); + if (!buf_virt) { + dev_err(&pdev->dev, "failed alloc memory.\n"); + return -ENOMEM; + } + + /* msg ring buffer memory */ + dsp_priv->msg_buf_virt = buf_virt; + dsp_priv->msg_buf_phys = buf_phys; + dsp_priv->msg_buf_size = MSG_BUF_SIZE; + offset = MSG_BUF_SIZE; + + /* keep dsp framework's global data when suspend/resume */ + dsp_priv->dsp_config_virt = buf_virt + offset; + dsp_priv->dsp_config_phys = buf_phys + offset; + dsp_priv->dsp_config_size = DSP_CONFIG_SIZE; + + /* scratch memory for dsp framework. The sdram reserved memory + * is split into two equal parts currently. The front part is + * used to keep the dsp firmware, the other part is considered + * as scratch memory for dsp framework. + */ + dsp_priv->scratch_buf_virt = dsp_priv->sdram_vir_addr + + dsp_priv->sdram_reserved_size / 2; + dsp_priv->scratch_buf_phys = dsp_priv->sdram_phys_addr + + dsp_priv->sdram_reserved_size / 2; + dsp_priv->scratch_buf_size = dsp_priv->sdram_reserved_size / 2; + + /* initialize the reference counter for dsp_priv + * structure + */ + atomic_long_set(&dsp_priv->refcnt, 0); + + /* ...initialize client association map */ + for (i = 0; i < XF_CFG_MAX_IPC_CLIENTS - 1; i++) + dsp_priv->xf_client_map[i].next = i + 1; + /* ...set list terminator */ + dsp_priv->xf_client_map[i].next = 0; + + /* ...set pointer to shared memory */ + xf_proxy_init(&dsp_priv->proxy); + + /* ...initialize mutex */ + mutex_init(&dsp_priv->dsp_mutex); + + return 0; +} + +static int fsl_dsp_remove(struct platform_device *pdev) +{ + struct fsl_dsp *dsp_priv = platform_get_drvdata(pdev); + int size; + + misc_deregister(&dsp_miscdev); + + size = MSG_BUF_SIZE + DSP_CONFIG_SIZE; + dma_free_coherent(&pdev->dev, size, dsp_priv->msg_buf_virt, + dsp_priv->msg_buf_phys); + if (dsp_priv->sdram_vir_addr) + iounmap(dsp_priv->sdram_vir_addr); + + return 0; +} + +#ifdef CONFIG_PM +static int fsl_dsp_runtime_resume(struct device *dev) +{ + struct fsl_dsp *dsp_priv = dev_get_drvdata(dev); + struct xf_proxy *proxy = &dsp_priv->proxy; + int ret; + + if (!dsp_priv->dsp_mu_init) { + MU_Init(dsp_priv->mu_base_virtaddr); + MU_EnableRxFullInt(dsp_priv->mu_base_virtaddr, 0); + dsp_priv->dsp_mu_init = 1; + } + + if (!proxy->is_ready) { + init_completion(&proxy->cmd_complete); + + ret = request_firmware_nowait(THIS_MODULE, + FW_ACTION_HOTPLUG, dsp_priv->fw_name, + dev, + GFP_KERNEL, dsp_priv, dsp_load_firmware); + + if (ret) { + dev_err(dev, "failed to load firmware\n"); + return ret; + } + + ret = icm_ack_wait(proxy, 0); + if (ret) + return ret; + + dev_info(dev, "dsp driver registered\n"); + } + + return 0; +} + +static int fsl_dsp_runtime_suspend(struct device *dev) +{ + struct fsl_dsp *dsp_priv = dev_get_drvdata(dev); + struct xf_proxy *proxy = &dsp_priv->proxy; + + dsp_priv->dsp_mu_init = 0; + proxy->is_ready = 0; + return 0; +} +#endif /* CONFIG_PM */ + + +#ifdef CONFIG_PM_SLEEP +static int fsl_dsp_suspend(struct device *dev) +{ + struct fsl_dsp *dsp_priv = dev_get_drvdata(dev); + struct xf_proxy *proxy = &dsp_priv->proxy; + int ret = 0; + + if (proxy->is_ready) { + ret = xf_cmd_send_suspend(proxy); + if (ret) { + dev_err(dev, "dsp suspend fail\n"); + return ret; + } + } + + ret = pm_runtime_force_suspend(dev); + + return ret; +} + +static int fsl_dsp_resume(struct device *dev) +{ + struct fsl_dsp *dsp_priv = dev_get_drvdata(dev); + struct xf_proxy *proxy = &dsp_priv->proxy; + int ret = 0; + + ret = pm_runtime_force_resume(dev); + if (ret) + return ret; + + if (proxy->is_ready) { + ret = xf_cmd_send_resume(proxy); + if (ret) { + dev_err(dev, "dsp resume fail\n"); + return ret; + } + } + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops fsl_dsp_pm = { + SET_RUNTIME_PM_OPS(fsl_dsp_runtime_suspend, + fsl_dsp_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(fsl_dsp_suspend, fsl_dsp_resume) +}; + +static const struct of_device_id fsl_dsp_ids[] = { + { .compatible = "fsl,imx8qxp-dsp", }, + {} +}; +MODULE_DEVICE_TABLE(of, fsl_dsp_ids); + +static struct platform_driver fsl_dsp_driver = { + .probe = fsl_dsp_probe, + .remove = fsl_dsp_remove, + .driver = { + .name = "fsl-dsp", + .of_match_table = fsl_dsp_ids, + .pm = &fsl_dsp_pm, + }, +}; +module_platform_driver(fsl_dsp_driver); + +MODULE_DESCRIPTION("Freescale DSP driver"); +MODULE_ALIAS("platform:fsl-dsp"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/fsl/fsl_dsp.h b/sound/soc/fsl/fsl_dsp.h new file mode 100644 index 000000000000..9288ac1cdf33 --- /dev/null +++ b/sound/soc/fsl/fsl_dsp.h @@ -0,0 +1,118 @@ +/* SPDX-License-Identifier: (GPL-2.0+ OR MIT)*/ +/* + * Copyright (C) 2017 Cadence Design Systems, Inc. + * Copyright 2018 NXP + * + */ + +#include <uapi/linux/mxc_dsp.h> +#include "fsl_dsp_proxy.h" + + +typedef void (*memcpy_func) (void *dest, const void *src, size_t n); +typedef void (*memset_func) (void *s, int c, size_t n); + +/* ...maximal number of IPC clients per proxy */ +#define XF_CFG_MAX_IPC_CLIENTS (1 << 4) + + +/* ...proxy client data */ +struct xf_client { + /* ...pointer to proxy interface */ + struct xf_proxy *proxy; + + /* ...allocated proxy client id */ + u32 id; + + /* ...pending response queue */ + struct xf_msg_queue queue; + + /* ...response waiting queue */ + wait_queue_head_t wait; + + /* ...virtual memory mapping */ + unsigned long vm_start; + + /* ...counter of memory mappings (no real use of it yet - tbd) */ + atomic_t vm_use; + + /* ...global structure pointer */ + void *global; +}; + +union xf_client_link { + /* ...index of next client in free list */ + u32 next; + + /* ...reference to proxy data for allocated client */ + struct xf_client *client; +}; + +struct fsl_dsp { + struct device *dev; + const char *fw_name; + void __iomem *regs; + void __iomem *mu_base_virtaddr; + sc_ipc_t dsp_ipcHandle; + sc_ipc_t mu_ipcHandle; + unsigned int dsp_mu_id; + int dsp_mu_init; + atomic_long_t refcnt; + unsigned long paddr; + unsigned long dram0; + unsigned long dram1; + unsigned long iram; + unsigned long sram; + void *sdram_vir_addr; + unsigned long sdram_phys_addr; + int sdram_reserved_size; + void *msg_buf_virt; + dma_addr_t msg_buf_phys; + int msg_buf_size; + void *scratch_buf_virt; + dma_addr_t scratch_buf_phys; + int scratch_buf_size; + void *dsp_config_virt; + dma_addr_t dsp_config_phys; + int dsp_config_size; + + unsigned int fixup_offset; + + /* ...proxy data structures */ + struct xf_proxy proxy; + + /* ...mutex lock */ + struct mutex dsp_mutex; + + /* ...global clients pool (item[0] serves as list terminator) */ + union xf_client_link xf_client_map[XF_CFG_MAX_IPC_CLIENTS]; +}; + +#define IRAM_OFFSET 0x10000 +#define IRAM_SIZE 2048 + +#define DRAM0_OFFSET 0x0 +#define DRAM0_SIZE 0x8000 + +#define DRAM1_OFFSET 0x8000 +#define DRAM1_SIZE 0x8000 + +#define SYSRAM_OFFSET 0x18000 +#define SYSRAM_SIZE 0x40000 + +#define SYSROM_OFFSET 0x58000 +#define SYSROM_SIZE 0x30000 + +#define MSG_BUF_SIZE 8192 +#define INPUT_BUF_SIZE 4096 +#define OUTPUT_BUF_SIZE 16384 +#define DSP_CONFIG_SIZE 4096 + +#define SC_C_OFS_SEL 39 +#define SC_C_OFS_AUDIO 40 +#define SC_C_OFS_PERIPH 41 +#define SC_C_OFS_IRQ 42 + +void *memcpy_dsp(void *dest, const void *src, size_t count); +void *memset_dsp(void *dest, int c, size_t count); +struct xf_client *xf_client_lookup(struct fsl_dsp *dsp_priv, u32 id); diff --git a/sound/soc/fsl/fsl_dsp_proxy.c b/sound/soc/fsl/fsl_dsp_proxy.c new file mode 100644 index 000000000000..5bce99547207 --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_proxy.c @@ -0,0 +1,699 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/******************************************************************************* + * + * Copyright (C) 2017 Cadence Design Systems, Inc. + * Copyright 2018 NXP + * + ******************************************************************************/ +/******************************************************************************* + * fsl_dsp_proxy.c + * + * DSP proxy driver + * + * DSP proxy driver is used to transfer messages between dsp driver + * and dsp framework + ******************************************************************************/ + +#include <soc/imx8/sc/ipc.h> +#include "fsl_dsp_proxy.h" +#include "fsl_dsp.h" + + +/* ...initialize message queue */ +void xf_msg_queue_init(struct xf_msg_queue *queue) +{ + queue->head = queue->tail = NULL; +} + +/* ...get message queue head */ +struct xf_message *xf_msg_queue_head(struct xf_msg_queue *queue) +{ + return queue->head; +} + +/* ...allocate new message from the pool */ +struct xf_message *xf_msg_alloc(struct xf_proxy *proxy) +{ + struct xf_message *m = proxy->free; + + /* ...make sure we have a free message item */ + if (m != NULL) { + /* ...get message from the pool */ + proxy->free = m->next, m->next = NULL; + } + + return m; +} + +/* ...return message to the pool of free items */ +void xf_msg_free(struct xf_proxy *proxy, struct xf_message *m) +{ + /* ...put message into the head of free items list */ + m->next = proxy->free, proxy->free = m; + + /* ...notify potential client waiting for message */ + wake_up(&proxy->busy); +} + +/* ...return all messages from the queue to the pool of free items */ +void xf_msg_free_all(struct xf_proxy *proxy, struct xf_msg_queue *queue) +{ + struct xf_message *m = queue->head; + + /* ...check if there is anything in the queue */ + if (m != NULL) { + queue->tail->next = proxy->free; + proxy->free = queue->head; + queue->head = queue->tail = NULL; + + /* ...notify potential client waiting for message */ + wake_up(&proxy->busy); + } +} + +/* ...submit message to a queue */ +int xf_msg_enqueue(struct xf_msg_queue *queue, struct xf_message *m) +{ + int first = (queue->head == NULL); + + /* ...set pointer to next item */ + m->next = NULL; + + /* ...advance head/tail pointer as required */ + if (first) + queue->head = m; + else + queue->tail->next = m; + + /* ...new tail points to this message */ + queue->tail = m; + + return first; +} + +/* ...retrieve next message from the per-task queue */ +struct xf_message *xf_msg_dequeue(struct xf_msg_queue *queue) +{ + struct xf_message *m = queue->head; + + /* ...check if there is anything in the queue */ + if (m != NULL) { + /* ...pop message from the head of the list */ + queue->head = m->next; + if (queue->head == NULL) + queue->tail = NULL; + } + + return m; +} + +/* ...helper function for requesting execution message from a pool */ +struct xf_message *xf_msg_available(struct xf_proxy *proxy) +{ + struct xf_message *m; + + /* ...acquire global lock */ + xf_lock(&proxy->lock); + + /* ...try to allocate the message */ + m = xf_msg_alloc(proxy); + if (m == NULL) { + /* ...failed to allocate message; release lock */ + xf_unlock(&proxy->lock); + } + + /* ...if successfully allocated */ + return m; +} + +/* ...helper function for receiving a message from per-client queue */ +struct xf_message *xf_msg_received(struct xf_proxy *proxy, + struct xf_msg_queue *queue) +{ + struct xf_message *m; + + /* ...acquire global lock */ + xf_lock(&proxy->lock); + + /* ...try to peek message from the queue */ + m = xf_msg_dequeue(queue); + if (m == NULL) { + /* ...queue is empty; release lock */ + xf_unlock(&proxy->lock); + } + + /* ...if message is non-null, lock is held */ + return m; +} + +/* + * MU related functions + */ +u32 icm_intr_send(struct xf_proxy *proxy, u32 msg) +{ + struct fsl_dsp *dsp_priv = container_of(proxy, + struct fsl_dsp, proxy); + + MU_SendMessage(dsp_priv->mu_base_virtaddr, 0, msg); + return 0; +} + +int icm_intr_extended_send(struct xf_proxy *proxy, + u32 msg, + struct dsp_ext_msg *ext_msg) +{ + struct fsl_dsp *dsp_priv = container_of(proxy, + struct fsl_dsp, proxy); + struct device *dev = dsp_priv->dev; + union icm_header_t msghdr; + + msghdr.allbits = msg; + if (msghdr.size != 8) + dev_err(dev, "too much ext msg\n"); + + MU_SendMessage(dsp_priv->mu_base_virtaddr, 1, ext_msg->phys); + MU_SendMessage(dsp_priv->mu_base_virtaddr, 2, ext_msg->size); + MU_SendMessage(dsp_priv->mu_base_virtaddr, 0, msg); + + return 0; +} + +int send_dpu_ext_msg_addr(struct xf_proxy *proxy) +{ + struct fsl_dsp *dsp_priv = container_of(proxy, + struct fsl_dsp, proxy); + union icm_header_t msghdr; + struct dsp_ext_msg ext_msg; + struct dsp_mem_msg *dpu_ext_msg = + (struct dsp_mem_msg *)((unsigned char *)dsp_priv->msg_buf_virt + + (MSG_BUF_SIZE / 2)); + int ret_val = 0; + + msghdr.allbits = 0; /* clear all bits; */ + msghdr.ack = 0; + msghdr.intr = 1; + msghdr.msg = ICM_CORE_INIT; + msghdr.size = 8; + ext_msg.phys = dsp_priv->msg_buf_phys + (MSG_BUF_SIZE / 2); + ext_msg.size = sizeof(struct dsp_mem_msg); + + dpu_ext_msg->ext_msg_phys = dsp_priv->msg_buf_phys; + dpu_ext_msg->ext_msg_size = MSG_BUF_SIZE; + dpu_ext_msg->scratch_phys = dsp_priv->scratch_buf_phys; + dpu_ext_msg->scratch_size = dsp_priv->scratch_buf_size; + dpu_ext_msg->dsp_config_phys = dsp_priv->dsp_config_phys; + dpu_ext_msg->dsp_config_size = dsp_priv->dsp_config_size; + + icm_intr_extended_send(proxy, msghdr.allbits, &ext_msg); + + return ret_val; +} + +long icm_ack_wait(struct xf_proxy *proxy, u32 msg) +{ + struct fsl_dsp *dsp_priv = container_of(proxy, + struct fsl_dsp, proxy); + struct device *dev = dsp_priv->dev; + union icm_header_t msghdr; + int err; + + msghdr.allbits = msg; + /* wait response from mu */ + err = wait_for_completion_timeout(&proxy->cmd_complete, + msecs_to_jiffies(1000)); + if (!err) { + dev_err(dev, "icm ack timeout! %x\n", msg); + return -ETIMEDOUT; + } + + dev_dbg(dev, "Ack recd for message 0x%08x\n", msghdr.allbits); + + return 0; +} + +irqreturn_t fsl_dsp_mu_isr(int irq, void *dev_id) +{ + struct xf_proxy *proxy = dev_id; + struct fsl_dsp *dsp_priv = container_of(proxy, + struct fsl_dsp, proxy); + struct device *dev = dsp_priv->dev; + union icm_header_t msghdr; + u32 reg; + + MU_ReceiveMsg(dsp_priv->mu_base_virtaddr, 0, ®); + msghdr = (union icm_header_t)reg; + + if (msghdr.intr == 1) { + dev_dbg(dev, "INTR: Received ICM intr, msg 0x%08x\n", + msghdr.allbits); + switch (msghdr.msg) { + case ICM_CORE_EXIT: + break; + case ICM_CORE_READY: + send_dpu_ext_msg_addr(proxy); + proxy->is_ready = 1; + complete(&proxy->cmd_complete); + break; + case XF_SUSPEND: + case XF_RESUME: + complete(&proxy->cmd_complete); + break; + default: + schedule_work(&proxy->work); + break; + } + } else if (msghdr.ack == 1) { + dev_dbg(dev, "INTR: Received ICM ack 0x%08x\n", msghdr.size); + msghdr.ack = 0; + } else { + dev_dbg(dev, "Received false ICM intr 0x%08x\n", + msghdr.allbits); + } + + return IRQ_HANDLED; +} + +/* + * Proxy related functions + */ +/* ...NULL-address specification */ +#define XF_PROXY_NULL (~0U) + +#define XF_PROXY_BADADDR (dsp_priv->scratch_buf_size) + +/* ...shared memory translation - kernel virtual address to shared address */ +u32 xf_proxy_b2a(struct xf_proxy *proxy, void *b) +{ + struct fsl_dsp *dsp_priv = container_of(proxy, + struct fsl_dsp, proxy); + + if (b == NULL) + return XF_PROXY_NULL; + else if ((u32)(b - dsp_priv->scratch_buf_virt) < + dsp_priv->scratch_buf_size) + return (u32)(b - dsp_priv->scratch_buf_virt); + else + return XF_PROXY_BADADDR; +} + +/* ...shared memory translation - shared address to kernel virtual address */ +void *xf_proxy_a2b(struct xf_proxy *proxy, u32 address) +{ + struct fsl_dsp *dsp_priv = container_of(proxy, + struct fsl_dsp, proxy); + + if (address < dsp_priv->scratch_buf_size) + return dsp_priv->scratch_buf_virt + address; + else if (address == XF_PROXY_NULL) + return NULL; + else + return (void *) -1; +} + +/* ...process association between response received and intended client */ +static void xf_cmap(struct xf_proxy *proxy, struct xf_message *m) +{ + struct fsl_dsp *dsp_priv = container_of(proxy, + struct fsl_dsp, proxy); + u32 id = XF_AP_IPC_CLIENT(m->id); + struct xf_client *client; + + /* ...process messages addressed to proxy itself */ + if (id == 0) { + /* ...place message into local response queue */ + xf_msg_enqueue(&proxy->response, m); + wake_up(&proxy->wait); + return; + } + + /* ...make sure the client ID is sane */ + client = xf_client_lookup(dsp_priv, id); + if (!client) { + pr_err("rsp[id:%08x]: client lookup failed", m->id); + xf_msg_free(proxy, m); + return; + } + + /* ...make sure client is bound to this proxy interface */ + if (client->proxy != proxy) { + pr_err("rsp[id:%08x]: wrong proxy interface", m->id); + xf_msg_free(proxy, m); + return; + } + + /* ...place message into local response queue */ + if (xf_msg_enqueue(&client->queue, m)) + wake_up(&client->wait); +} + +/* ...retrieve pending responses from shared memory ring-buffer */ +static u32 xf_shmem_process_responses(struct xf_proxy *proxy) +{ + struct xf_message *m; + u32 read_idx, write_idx; + int status; + + status = 0; + + /* ...get current values of read/write pointers in response queue */ + read_idx = XF_PROXY_READ(proxy, rsp_read_idx); + write_idx = XF_PROXY_READ(proxy, rsp_write_idx); + + /* ...process all committed responses */ + while (!XF_QUEUE_EMPTY(read_idx, write_idx)) { + struct xf_proxy_message *response; + + /* ...allocate execution message */ + m = xf_msg_alloc(proxy); + if (m == NULL) + break; + + /* ...mark the interface status has changed */ + status |= (XF_QUEUE_FULL(read_idx, write_idx) ? 0x3 : 0x1); + + /* ...get oldest not yet processed response */ + response = XF_PROXY_RESPONSE(proxy, XF_QUEUE_IDX(read_idx)); + + /* ...fill message parameters */ + m->id = response->session_id; + m->opcode = response->opcode; + m->length = response->length; + m->buffer = xf_proxy_a2b(proxy, response->address); + m->ret = response->ret; + + /* ...advance local reading index copy */ + read_idx = XF_QUEUE_ADVANCE_IDX(read_idx); + + /* ...update shadow copy of reading index */ + XF_PROXY_WRITE(proxy, rsp_read_idx, read_idx); + + /* ...submit message to proper client */ + xf_cmap(proxy, m); + } + + return status; +} + +/* ...put pending commands into shared memory ring-buffer */ +static u32 xf_shmem_process_commands(struct xf_proxy *proxy) +{ + struct xf_message *m; + u32 read_idx, write_idx; + int status = 0; + + /* ...get current value of peer read pointer */ + write_idx = XF_PROXY_READ(proxy, cmd_write_idx); + read_idx = XF_PROXY_READ(proxy, cmd_read_idx); + + /* ...submit any pending commands */ + while (!XF_QUEUE_FULL(read_idx, write_idx)) { + struct xf_proxy_message *command; + + /* ...check if we have a pending command */ + m = xf_msg_dequeue(&proxy->command); + if (m == NULL) + break; + + /* ...always mark the interface status has changed */ + status |= 0x3; + + /* ...select the place for the command */ + command = XF_PROXY_COMMAND(proxy, XF_QUEUE_IDX(write_idx)); + + /* ...put the response message fields */ + command->session_id = m->id; + command->opcode = m->opcode; + command->length = m->length; + command->address = xf_proxy_b2a(proxy, m->buffer); + command->ret = m->ret; + + /* ...return message back to the pool */ + xf_msg_free(proxy, m); + + /* ...advance local writing index copy */ + write_idx = XF_QUEUE_ADVANCE_IDX(write_idx); + + /* ...update shared copy of queue write pointer */ + XF_PROXY_WRITE(proxy, cmd_write_idx, write_idx); + } + + if (status) + icm_intr_send(proxy, 0); + + return status; +} + +/* ...shared memory interface maintenance routine */ +void xf_proxy_process(struct work_struct *w) +{ + struct xf_proxy *proxy = container_of(w, struct xf_proxy, work); + int status = 0; + + /* ...get exclusive access to internal data */ + xf_lock(&proxy->lock); + + do { + /* ...process outgoing commands first */ + status = xf_shmem_process_commands(proxy); + + /* ...process all pending responses */ + status |= xf_shmem_process_responses(proxy); + + } while (status); + + /* ...unlock internal proxy data */ + xf_unlock(&proxy->lock); +} + +/* ...initialize shared memory interface */ +int xf_proxy_init(struct xf_proxy *proxy) +{ + struct fsl_dsp *dsp_priv = container_of(proxy, + struct fsl_dsp, proxy); + struct xf_message *m; + int i; + + /* ...create a list of all messages in a pool; set head pointer */ + proxy->free = &proxy->pool[0]; + + /* ...put all messages into a single-linked list */ + for (i = 0, m = proxy->free; i < XF_CFG_MESSAGE_POOL_SIZE - 1; i++, m++) + m->next = m + 1; + + /* ...set list tail pointer */ + m->next = NULL; + + /* ...initialize proxy lock */ + xf_lock_init(&proxy->lock); + + /* ...initialize proxy thread message queues */ + xf_msg_queue_init(&proxy->command); + xf_msg_queue_init(&proxy->response); + + /* ...initialize global busy queue */ + init_waitqueue_head(&proxy->busy); + init_waitqueue_head(&proxy->wait); + + /* ...create work structure */ + INIT_WORK(&proxy->work, xf_proxy_process); + + /* ...set pointer to shared memory */ + proxy->ipc.shmem = (struct xf_shmem_data *)dsp_priv->msg_buf_virt; + + /* ...initialize shared memory interface */ + XF_PROXY_WRITE(proxy, cmd_read_idx, 0); + XF_PROXY_WRITE(proxy, cmd_write_idx, 0); + XF_PROXY_WRITE(proxy, cmd_invalid, 0); + XF_PROXY_WRITE(proxy, rsp_read_idx, 0); + XF_PROXY_WRITE(proxy, rsp_write_idx, 0); + XF_PROXY_WRITE(proxy, rsp_invalid, 0); + + return 0; +} + +/* ...trigger shared memory interface processing */ +void xf_proxy_notify(struct xf_proxy *proxy) +{ + schedule_work(&proxy->work); +} + +/* ...submit a command to proxy pending queue (lock released upon return) */ +void xf_proxy_command(struct xf_proxy *proxy, struct xf_message *m) +{ + int first; + + /* ...submit message to proxy thread */ + first = xf_msg_enqueue(&proxy->command, m); + + /* ...release the lock */ + xf_unlock(&proxy->lock); + + /* ...notify thread about command reception */ + (first ? xf_proxy_notify(proxy), 1 : 0); +} + +/* + * Proxy cmd send and receive functions + */ +int xf_cmd_send(struct xf_proxy *proxy, + u32 id, + u32 opcode, + void *buffer, + u32 length) +{ + struct xf_message *m; + int ret; + + /* ...retrieve message handle (take the lock on success) */ + ret = wait_event_interruptible(proxy->busy, + (m = xf_msg_available(proxy)) != NULL); + if (ret) + return -EINTR; + + /* ...fill-in message parameters (lock is taken) */ + m->id = id; + m->opcode = opcode; + m->length = length; + m->buffer = buffer; + m->ret = 0; + + /* ...submit command to the proxy */ + xf_proxy_command(proxy, m); + + return 0; +} + +struct xf_message *xf_cmd_recv(struct xf_proxy *proxy, + wait_queue_head_t *wq, + struct xf_msg_queue *queue, + int wait) +{ + struct xf_message *m; + int ret; + + /* ...wait for message reception (take lock on success) */ + ret = wait_event_interruptible(*wq, + (m = xf_msg_received(proxy, queue)) != NULL || !wait); + if (ret) + return ERR_PTR(-EINTR); + + /* ...return message with a lock taken */ + return m; +} + +/* ...helper function for synchronous command execution */ +struct xf_message *xf_cmd_send_recv(struct xf_proxy *proxy, + u32 id, u32 opcode, + void *buffer, + u32 length) +{ + int ret; + + /* ...send command to remote proxy */ + ret = xf_cmd_send(proxy, id, opcode, buffer, length); + if (ret) + return ERR_PTR(ret); + + /* ...wait for message delivery */ + return xf_cmd_recv(proxy, &proxy->wait, &proxy->response, 1); +} + +/* + * Proxy allocate and free memory functions + */ +/* ...allocate memory buffer for kernel use */ +int xf_cmd_alloc(struct xf_proxy *proxy, void **buffer, u32 length) +{ + struct xf_message *m; + u32 id = 0; + int ret; + + /* ...send command to remote proxy */ + m = xf_cmd_send_recv(proxy, id, XF_ALLOC, NULL, length); + if (IS_ERR(m)) { + ret = PTR_ERR(m); + return ret; + } + + /* ...check if response is expected */ + if (m->opcode == XF_ALLOC && m->buffer != NULL) { + *buffer = m->buffer; + ret = 0; + } else { + ret = -ENOMEM; + } + + /* ...free message and release proxy lock */ + xf_msg_free(proxy, m); + + return ret; +} + +/* ...free memory buffer */ +int xf_cmd_free(struct xf_proxy *proxy, void *buffer, u32 length) +{ + struct xf_message *m; + u32 id = 0; + int ret; + + /* ...synchronously execute freeing command */ + m = xf_cmd_send_recv(proxy, id, XF_FREE, buffer, length); + if (IS_ERR(m)) { + ret = PTR_ERR(m); + return ret; + } + + /* ...check if response is expected */ + if (m->opcode == XF_FREE) + ret = 0; + else + ret = -EINVAL; + + /* ...free message and release proxy lock */ + xf_msg_free(proxy, m); + + return ret; +} + +/* + * suspend & resume functions + */ +int xf_cmd_send_suspend(struct xf_proxy *proxy) +{ + union icm_header_t msghdr; + int ret = 0; + + init_completion(&proxy->cmd_complete); + + msghdr.allbits = 0; /* clear all bits; */ + msghdr.ack = 0; + msghdr.intr = 1; + msghdr.msg = XF_SUSPEND; + msghdr.size = 0; + icm_intr_send(proxy, msghdr.allbits); + + /* wait for response here */ + ret = icm_ack_wait(proxy, msghdr.allbits); + + return ret; +} + +int xf_cmd_send_resume(struct xf_proxy *proxy) +{ + union icm_header_t msghdr; + int ret = 0; + + init_completion(&proxy->cmd_complete); + + msghdr.allbits = 0; /* clear all bits; */ + msghdr.ack = 0; + msghdr.intr = 1; + msghdr.msg = XF_RESUME; + msghdr.size = 0; + icm_intr_send(proxy, msghdr.allbits); + + /* wait for response here */ + ret = icm_ack_wait(proxy, msghdr.allbits); + + return ret; +} diff --git a/sound/soc/fsl/fsl_dsp_proxy.h b/sound/soc/fsl/fsl_dsp_proxy.h new file mode 100644 index 000000000000..0942b8d85a6e --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_proxy.h @@ -0,0 +1,401 @@ +/* SPDX-License-Identifier: (GPL-2.0+ OR MIT)*/ +/******************************************************************************* + * + * Copyright (c) 2017 Cadence Design Systems, Inc. + * Copyright 2018 NXP + * + ************************************************************/ +/************************************************************ + * fsl_dsp_proxy.h + * + * Proxy commmand/response messages + ************************************************************/ + +#ifndef __FSL_DSP_PROXY_H +#define __FSL_DSP_PROXY_H + +#include <linux/wait.h> +#include <linux/device.h> +#include <linux/workqueue.h> +#include <linux/spinlock.h> +#include <linux/compiler.h> +#include <linux/dma-mapping.h> +#include <linux/platform_data/dma-imx.h> +#include <linux/mx8_mu.h> +#include <linux/interrupt.h> + +#define XF_CFG_MESSAGE_POOL_SIZE 256 + +/******************************************************************************* + * Local proxy data + ******************************************************************************/ + +/* ...execution message */ +struct xf_message { + /* ...pointer to next message in a list */ + struct xf_message *next; + + /* ...session-id */ + u32 id; + + /* ...operation code */ + u32 opcode; + + /* ...length of data buffer */ + u32 length; + + /* ...translated data pointer */ + void *buffer; + + /* ...return message status */ + u32 ret; +}; + +/* ...message queue */ +struct xf_msg_queue { + /* ...pointer to list head */ + struct xf_message *head; + + /* ...pointer to list tail */ + struct xf_message *tail; +}; + +struct xf_proxy_message { + /* ...session ID */ + u32 session_id; + + /* ...proxy API command/response code */ + u32 opcode; + + /* ...length of attached buffer */ + u32 length; + + /* ...physical address of message buffer */ + u32 address; + + /* ...return message status */ + u32 ret; +}; +/**********************************************************************/ + +enum icm_action_t { + ICM_CORE_READY = 1, + ICM_CORE_INIT, + ICM_CORE_EXIT, +}; + +/* ...adjust IPC client of message going from user-space */ +#define XF_MSG_AP_FROM_USER(id, client) (((id) & ~(0xF << 2)) | (client << 2)) + +/* ...message id contains source and destination ports specification */ +#define __XF_MSG_ID(src, dst) (((src) & 0xFFFF) | (((dst) & 0xFFFF) << 16)) + +/* ...wipe out IPC client from message going to user-space */ +#define XF_MSG_AP_TO_USER(id) ((id) & ~(0xF << 18)) +#define __XF_AP_PROXY(core) ((core) | 0x8000) +#define __XF_DSP_PROXY(core) ((core) | 0x8000) + +/* ...message id contains source and destination ports specification */ +#define __XF_MSG_ID(src, dst) (((src) & 0xFFFF) | (((dst) & 0xFFFF) << 16)) +#define XF_MSG_SRC_CLIENT(id) (((id) >> 2) & 0x3F) +#define XF_MSG_DST_CLIENT(id) (((id) >> 18) & 0x3F) + +/* ...special treatment of AP-proxy destination field */ +#define XF_AP_IPC_CLIENT(id) (((id) >> 18) & 0xF) +#define __XF_AP_PROXY(core) ((core) | 0x8000) +#define __XF_DSP_PROXY(core) ((core) | 0x8000) + +/* ...opcode composition with command/response data tags */ +#define __XF_OPCODE(c, r, op) (((c) << 31) | ((r) << 30) | ((op) & 0x3F)) + +/* ...shared buffer allocation */ +#define XF_ALLOC __XF_OPCODE(0, 0, 4) + +/* ...shared buffer freeing */ +#define XF_FREE __XF_OPCODE(0, 0, 5) + +/* ...resume component operation */ +#define XF_RESUME __XF_OPCODE(0, 0, 14) + +/* ...resume component operation */ +#define XF_SUSPEND __XF_OPCODE(0, 0, 15) + + +/******************************************************************************* + * Ring buffer support + ******************************************************************************/ +/* ...cache-line size on DSP */ +#define XF_PROXY_ALIGNMENT 64 + +/* ...total length of shared memory queue (for commands and responses) */ +#define XF_PROXY_MESSAGE_QUEUE_LENGTH (1 << 6) + +/* ...index mask */ +#define XF_PROXY_MESSAGE_QUEUE_MASK 0x3F + +/* ...ring-buffer index */ +#define __XF_QUEUE_IDX(idx, counter) \ + (((idx) & XF_PROXY_MESSAGE_QUEUE_MASK) | ((counter) << 16)) + +/* ...retrieve ring-buffer index */ +#define XF_QUEUE_IDX(idx) \ + ((idx) & XF_PROXY_MESSAGE_QUEUE_MASK) + +/* ...increment ring-buffer index */ +#define XF_QUEUE_ADVANCE_IDX(idx) \ + (((idx) + 0x10001) & (0xFFFF0000 | XF_PROXY_MESSAGE_QUEUE_MASK)) + +/* ...test if ring buffer is empty */ +#define XF_QUEUE_EMPTY(read, write) \ + ((read) == (write)) + +/* ...test if ring buffer is full */ +#define XF_QUEUE_FULL(read, write) \ + ((write) == (read) + (XF_PROXY_MESSAGE_QUEUE_LENGTH << 16)) + +/* ...basic cache operations */ +#define XF_PROXY_INVALIDATE(addr, len) { } + +#define XF_PROXY_FLUSH(addr, len) { } + +/* ...data managed by host CPU (remote) - in case of shunt it is a IPC layer */ +struct xf_proxy_host_data { + /* ...command queue */ + struct xf_proxy_message command[XF_PROXY_MESSAGE_QUEUE_LENGTH]; + + /* ...writing index into command queue */ + u32 cmd_write_idx; + + /* ...reading index for response queue */ + u32 rsp_read_idx; + + /* ...indicate command queue is valid or not */ + u32 cmd_invalid; +}; + +/* ...data managed by DSP (local) */ +struct xf_proxy_dsp_data { + /* ...response queue */ + struct xf_proxy_message response[XF_PROXY_MESSAGE_QUEUE_LENGTH]; + + /* ...writing index into response queue */ + u32 rsp_write_idx; + + /* ...reading index for command queue */ + u32 cmd_read_idx; + + /* ...indicate response queue is valid or not */ + u32 rsp_invalid; +}; + +/* ...shared memory data */ +struct xf_shmem_data { + /* ...ingoing data (maintained by DSP (local side)) */ + struct xf_proxy_host_data local; + + /* ...outgoing data (maintained by host CPU (remote side)) */ + struct xf_proxy_dsp_data remote; + +}; + +/* ...shared memory data accessor */ +#define XF_SHMEM_DATA(proxy) \ + ((proxy)->ipc.shmem) + +/* ...atomic reading */ +#define __XF_PROXY_READ_ATOMIC(var) \ + ({ XF_PROXY_INVALIDATE(&(var), sizeof(var)); \ + *(u32 *)&(var); }) + +/* ...atomic writing */ +#define __XF_PROXY_WRITE_ATOMIC(var, value) \ + ({*(u32 *)&(var) = (value); \ + XF_PROXY_FLUSH(&(var), sizeof(var)); \ + (value); }) + +/* ...accessors */ +#define XF_PROXY_READ(proxy, field) \ + __XF_PROXY_READ_##field(XF_SHMEM_DATA(proxy)) + +#define XF_PROXY_WRITE(proxy, field, v) \ + __XF_PROXY_WRITE_##field(XF_SHMEM_DATA(proxy), (v)) + +/* ...individual fields reading */ +#define __XF_PROXY_READ_cmd_write_idx(shmem) \ + __XF_PROXY_READ_ATOMIC(shmem->local.cmd_write_idx) + +#define __XF_PROXY_READ_cmd_read_idx(shmem) \ + shmem->remote.cmd_read_idx + +#define __XF_PROXY_READ_cmd_invalid(shmem) \ + __XF_PROXY_READ_ATOMIC(shmem->local.cmd_invalid) + +#define __XF_PROXY_READ_rsp_write_idx(shmem) \ + __XF_PROXY_READ_ATOMIC(shmem->remote.rsp_write_idx) + +#define __XF_PROXY_READ_rsp_read_idx(shmem) \ + shmem->local.rsp_read_idx + +#define __XF_PROXY_READ_rsp_invalid(shmem) \ + __XF_PROXY_READ_ATOMIC(shmem->remote.rsp_invalid) + +/* ...individual fields writings */ +#define __XF_PROXY_WRITE_cmd_write_idx(shmem, v) \ + __XF_PROXY_WRITE_ATOMIC(shmem->local.cmd_write_idx, v) + +#define __XF_PROXY_WRITE_cmd_read_idx(shmem, v) \ + __XF_PROXY_WRITE_ATOMIC(shmem->remote.cmd_read_idx, v) + +#define __XF_PROXY_WRITE_cmd_invalid(shmem, v) \ + __XF_PROXY_WRITE_ATOMIC(shmem->local.cmd_invalid, v) + +#define __XF_PROXY_WRITE_rsp_read_idx(shmem, v) \ + __XF_PROXY_WRITE_ATOMIC(shmem->local.rsp_read_idx, v) + +#define __XF_PROXY_WRITE_rsp_write_idx(shmem, v) \ + __XF_PROXY_WRITE_ATOMIC(shmem->remote.rsp_write_idx, v) + +#define __XF_PROXY_WRITE_rsp_invalid(shmem, v) \ + __XF_PROXY_WRITE_ATOMIC(shmem->remote.rsp_invalid, v) + +/* ...command buffer accessor */ +#define XF_PROXY_COMMAND(proxy, idx) \ + (&XF_SHMEM_DATA(proxy)->local.command[(idx)]) + +/* ...response buffer accessor */ +#define XF_PROXY_RESPONSE(proxy, idx) \ + (&XF_SHMEM_DATA(proxy)->remote.response[(idx)]) + +/******************************************************************************* + * Local proxy data + ******************************************************************************/ + +struct xf_proxy_ipc_data { + /* ...shared memory data pointer */ + struct xf_shmem_data __iomem *shmem; + + /* ...core identifier */ + u32 core; + + /* ...IPC registers memory */ + void __iomem *regs; +}; + +/* ...proxy data */ +struct xf_proxy { + /* ...IPC layer data */ + struct xf_proxy_ipc_data ipc; + + /* ...shared memory status change processing item */ + struct work_struct work; + + struct completion cmd_complete; + int is_ready; + + /* ...internal lock */ + spinlock_t lock; + + /* ...busy queue (for clients waiting ON NOTIFIcation) */ + wait_queue_head_t busy; + + /* ...waiting queue for synchronous proxy operations */ + wait_queue_head_t wait; + + /* ...submitted commands queue */ + struct xf_msg_queue command; + + /* ...pending responses queue */ + struct xf_msg_queue response; + + /* ...global message pool */ + struct xf_message pool[XF_CFG_MESSAGE_POOL_SIZE]; + + /* ...pointer to first free message in the pool */ + struct xf_message *free; +}; + +union icm_header_t { + struct { + u32 msg:6; + u32 sub_msg:6; // sub_msg will have ICM_MSG + u32 rsvd:3; /* reserved */ + u32 intr:1; /* intr = 1 when sending msg. */ + u32 size:15; /* =size in bytes (excluding header) */ + u32 ack:1; /* response message when ack=1 */ + }; + u32 allbits; +}; + +struct dsp_ext_msg { + u32 phys; + u32 size; +}; + +struct dsp_mem_msg { + u32 ext_msg_phys; + u32 ext_msg_size; + u32 scratch_phys; + u32 scratch_size; + u32 dsp_config_phys; + u32 dsp_config_size; +}; + +static inline void xf_lock_init(spinlock_t *lock) +{ + spin_lock_init(lock); +} + +static inline void xf_lock(spinlock_t *lock) +{ + spin_lock(lock); +} + +static inline void xf_unlock(spinlock_t *lock) +{ + spin_unlock(lock); +} + +/* ...init proxy */ +int xf_proxy_init(struct xf_proxy *proxy); + +/* ...send message to proxy */ +int xf_cmd_send(struct xf_proxy *proxy, + u32 id, + u32 opcode, + void *buffer, + u32 length); + +/* ...get message from proxy */ +struct xf_message *xf_cmd_recv(struct xf_proxy *proxy, + wait_queue_head_t *wq, + struct xf_msg_queue *queue, + int wait); + +/* ...mu interrupt handle */ +irqreturn_t fsl_dsp_mu_isr(int irq, void *dev_id); + +/* ...initialize client pending message queue */ +void xf_msg_queue_init(struct xf_msg_queue *queue); + +/* ...return current queue state */ +struct xf_message *xf_msg_queue_head(struct xf_msg_queue *queue); + +/* ...return the message back to a pool */ +void xf_msg_free(struct xf_proxy *proxy, struct xf_message *m); + +/* ...release all pending messages */ +void xf_msg_free_all(struct xf_proxy *proxy, struct xf_msg_queue *queue); + +/* ...wait mu interrupt */ +long icm_ack_wait(struct xf_proxy *proxy, u32 msg); + +/* ...shared memory translation - kernel virtual address to shared address */ +u32 xf_proxy_b2a(struct xf_proxy *proxy, void *b); + +/* ...shared memory translation - shared address to kernel virtual address */ +void *xf_proxy_a2b(struct xf_proxy *proxy, u32 address); + +int xf_cmd_send_suspend(struct xf_proxy *proxy); +int xf_cmd_send_resume(struct xf_proxy *proxy); + +#endif diff --git a/sound/soc/fsl/fsl_esai.c b/sound/soc/fsl/fsl_esai.c index fa64cc2b1729..efe4904bb6f2 100644 --- a/sound/soc/fsl/fsl_esai.c +++ b/sound/soc/fsl/fsl_esai.c @@ -1,7 +1,8 @@ /* * Freescale ESAI ALSA SoC Digital Audio Interface (DAI) driver * - * Copyright (C) 2014 Freescale Semiconductor, Inc. + * Copyright (C) 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2017 NXP * * This file is licensed under the terms of the GNU General Public License * version 2. This program is licensed "as is" without any warranty of any @@ -11,13 +12,20 @@ #include <linux/clk.h> #include <linux/dmaengine.h> #include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> #include <linux/of_irq.h> #include <linux/of_platform.h> +#include <linux/dma-mapping.h> +#include <linux/dmapool.h> +#include <linux/pm_runtime.h> #include <sound/dmaengine_pcm.h> #include <sound/pcm_params.h> #include "fsl_esai.h" +#include "fsl_acm.h" #include "imx-pcm.h" +#include "fsl_dma_workaround.h" #define FSL_ESAI_RATES SNDRV_PCM_RATE_8000_192000 #define FSL_ESAI_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ @@ -25,6 +33,13 @@ SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE) +struct fsl_esai_soc_data { + bool imx; + bool dma_workaround; + bool channel_swap_workaround; + bool constrain_period_size; +}; + /** * fsl_esai: ESAI private data * @@ -50,12 +65,15 @@ struct fsl_esai { struct snd_dmaengine_dai_dma_data dma_params_rx; struct snd_dmaengine_dai_dma_data dma_params_tx; + struct snd_pcm_substream *substream[2]; struct platform_device *pdev; struct regmap *regmap; struct clk *coreclk; struct clk *extalclk; struct clk *fsysclk; struct clk *spbaclk; + const struct fsl_esai_soc_data *soc; + struct fsl_dma_workaround_info *dma_info; u32 fifo_depth; u32 slot_width; u32 slots; @@ -70,6 +88,45 @@ struct fsl_esai { char name[32]; }; +static struct fsl_esai_soc_data fsl_esai_vf610 = { + .imx = false, + .dma_workaround = false, + .channel_swap_workaround = true, + .constrain_period_size = false, +}; + +static struct fsl_esai_soc_data fsl_esai_imx35 = { + .imx = true, + .dma_workaround = false, + .channel_swap_workaround = true, + .constrain_period_size = false, +}; + +static struct fsl_esai_soc_data fsl_esai_imx6ull = { + .imx = true, + .dma_workaround = false, + .channel_swap_workaround = false, + .constrain_period_size = false, +}; + +/* In imx8qxp rev1, the dma request signal is not revert. For esai + * dma request is low valid, but edma assert it as high level valid. + * so we need to use GPT to transfer the dma request signal. + */ +static struct fsl_esai_soc_data fsl_esai_imx8qxp_v1 = { + .imx = true, + .dma_workaround = true, + .channel_swap_workaround = false, + .constrain_period_size = true, +}; + +static struct fsl_esai_soc_data fsl_esai_imx8qm = { + .imx = true, + .dma_workaround = false, + .channel_swap_workaround = false, + .constrain_period_size = true, +}; + static irqreturn_t esai_isr(int irq, void *devid) { struct fsl_esai *esai_priv = (struct fsl_esai *)devid; @@ -174,7 +231,7 @@ static int fsl_esai_divisor_cal(struct snd_soc_dai *dai, bool tx, u32 ratio, /* Calculate the fraction */ sub = sub * 1000 / ratio; - if (sub < savesub) { + if (sub <= savesub) { savesub = sub; pm = i; fp = j; @@ -229,6 +286,21 @@ static int fsl_esai_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned long clk_rate; int ret; + if (esai_priv->synchronous && !tx) { + switch (clk_id) { + case ESAI_HCKR_FSYS: + fsl_esai_set_dai_sysclk(dai, ESAI_HCKT_FSYS, + freq, dir); + break; + case ESAI_HCKR_EXTAL: + fsl_esai_set_dai_sysclk(dai, ESAI_HCKT_EXTAL, + freq, dir); + break; + default: + return -EINVAL; + } + } + /* Bypass divider settings if the requirement doesn't change */ if (freq == esai_priv->hck_rate[tx] && dir == esai_priv->hck_dir[tx]) return 0; @@ -250,6 +322,7 @@ static int fsl_esai_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, break; case ESAI_HCKT_EXTAL: ecr |= ESAI_ECR_ETI; + break; case ESAI_HCKR_EXTAL: ecr |= ESAI_ECR_ERI; break; @@ -464,30 +537,8 @@ static int fsl_esai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); - int ret; - - /* - * Some platforms might use the same bit to gate all three or two of - * clocks, so keep all clocks open/close at the same time for safety - */ - ret = clk_prepare_enable(esai_priv->coreclk); - if (ret) - return ret; - if (!IS_ERR(esai_priv->spbaclk)) { - ret = clk_prepare_enable(esai_priv->spbaclk); - if (ret) - goto err_spbaclk; - } - if (!IS_ERR(esai_priv->extalclk)) { - ret = clk_prepare_enable(esai_priv->extalclk); - if (ret) - goto err_extalck; - } - if (!IS_ERR(esai_priv->fsysclk)) { - ret = clk_prepare_enable(esai_priv->fsysclk); - if (ret) - goto err_fsysclk; - } + struct snd_soc_pcm_runtime *rtd = substream->private_data; + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; if (!dai->active) { /* Set synchronous mode */ @@ -502,18 +553,30 @@ static int fsl_esai_startup(struct snd_pcm_substream *substream, ESAI_xCCR_xDC_MASK, ESAI_xCCR_xDC(2)); } - return 0; + esai_priv->substream[substream->stream] = substream; + + if (esai_priv->soc->constrain_period_size) { + if (tx) + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + esai_priv->dma_params_tx.maxburst); + else + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + esai_priv->dma_params_rx.maxburst); + } -err_fsysclk: - if (!IS_ERR(esai_priv->extalclk)) - clk_disable_unprepare(esai_priv->extalclk); -err_extalck: - if (!IS_ERR(esai_priv->spbaclk)) - clk_disable_unprepare(esai_priv->spbaclk); -err_spbaclk: - clk_disable_unprepare(esai_priv->coreclk); + if (esai_priv->soc->dma_workaround) { + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, 1, 2); + + if (!rtd->dai_link->be_hw_params_fixup) + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, 48000, 48000); + } + + return 0; - return ret; } static int fsl_esai_hw_params(struct snd_pcm_substream *substream, @@ -529,16 +592,30 @@ static int fsl_esai_hw_params(struct snd_pcm_substream *substream, u32 bclk, mask, val; int ret; + if (esai_priv->soc->dma_workaround) + configure_gpt_dma(substream, esai_priv->dma_info); + /* Override slot_width if being specifically set */ if (esai_priv->slot_width) slot_width = esai_priv->slot_width; bclk = params_rate(params) * slot_width * esai_priv->slots; - ret = fsl_esai_set_bclk(dai, tx, bclk); + ret = fsl_esai_set_bclk(dai, esai_priv->synchronous ? true : tx, bclk); if (ret) return ret; + if (esai_priv->synchronous && !tx) { + /* Use Normal mode to support monaural audio */ + regmap_update_bits(esai_priv->regmap, REG_ESAI_TCR, + ESAI_xCR_xMOD_MASK, params_channels(params) > 1 ? + ESAI_xCR_xMOD_NETWORK : 0); + + mask = ESAI_xCR_xSWS_MASK | ESAI_xCR_PADC; + val = ESAI_xCR_xSWS(slot_width, width) | ESAI_xCR_PADC; + regmap_update_bits(esai_priv->regmap, REG_ESAI_TCR, mask, val); + } + /* Use Normal mode to support monaural audio */ regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx), ESAI_xCR_xMOD_MASK, params_channels(params) > 1 ? @@ -572,13 +649,8 @@ static void fsl_esai_shutdown(struct snd_pcm_substream *substream, { struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); - if (!IS_ERR(esai_priv->fsysclk)) - clk_disable_unprepare(esai_priv->fsysclk); - if (!IS_ERR(esai_priv->extalclk)) - clk_disable_unprepare(esai_priv->extalclk); - if (!IS_ERR(esai_priv->spbaclk)) - clk_disable_unprepare(esai_priv->spbaclk); - clk_disable_unprepare(esai_priv->coreclk); + esai_priv->substream[substream->stream] = NULL; + } static int fsl_esai_trigger(struct snd_pcm_substream *substream, int cmd, @@ -647,11 +719,23 @@ static int fsl_esai_trigger(struct snd_pcm_substream *substream, int cmd, return 0; } +static int fsl_esai_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(cpu_dai); + + if (esai_priv->soc->dma_workaround) + clear_gpt_dma(substream, esai_priv->dma_info); + + return 0; +} + static struct snd_soc_dai_ops fsl_esai_dai_ops = { .startup = fsl_esai_startup, .shutdown = fsl_esai_shutdown, .trigger = fsl_esai_trigger, .hw_params = fsl_esai_hw_params, + .hw_free = fsl_esai_hw_free, .set_sysclk = fsl_esai_set_dai_sysclk, .set_fmt = fsl_esai_set_dai_fmt, .set_tdm_slot = fsl_esai_set_dai_tdm_slot, @@ -810,14 +894,104 @@ static const struct regmap_config fsl_esai_regmap_config = { .cache_type = REGCACHE_FLAT, }; +static bool fsl_esai_check_xrun(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(cpu_dai); + u32 saisr; + + regmap_read(esai_priv->regmap, REG_ESAI_SAISR, &saisr); + + return saisr & (ESAI_SAISR_TUE | ESAI_SAISR_ROE) ; +} + +/* + *Here is ESAI underrun reset step: + *1. Read "TUE" and got TUE=1 + *2. stop DMA. + *3. stop ESAI TX section. + *4. Set the transmitter section individual reset "TPR=1" + *5. Reset the ESAI Transmit FIFO (set ESAI_TFCR[1]=1). + *6. Config the control registers ESAI_TCCR and ESAI_TCR.config the Transmit FIFO register. + *7. clear "TPR" + *8. read "TUE" + *9. Prefill ESAI TX FIFO. + *10.Start DMA. + *11 Enable the ESAI + */ +static void fsl_esai_reset(struct snd_pcm_substream *substream, bool stop) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(cpu_dai); + unsigned long flags = 0; + u32 saisr; + + if (stop) + imx_stop_lock_pcm_streams(esai_priv->substream, 2, &flags); + + regmap_update_bits(esai_priv->regmap, REG_ESAI_ECR, + ESAI_ECR_ESAIEN_MASK | ESAI_ECR_ERST_MASK, + ESAI_ECR_ESAIEN | ESAI_ECR_ERST); + regmap_update_bits(esai_priv->regmap, REG_ESAI_ECR, + ESAI_ECR_ESAIEN_MASK | ESAI_ECR_ERST_MASK, + ESAI_ECR_ESAIEN); + + regmap_update_bits(esai_priv->regmap, REG_ESAI_TCR, ESAI_xCR_xPR_MASK, ESAI_xCR_xPR); + regmap_update_bits(esai_priv->regmap, REG_ESAI_RCR, ESAI_xCR_xPR_MASK, ESAI_xCR_xPR); + + regmap_update_bits(esai_priv->regmap, REG_ESAI_PRRC, ESAI_PRRC_PDC_MASK, 0); + regmap_update_bits(esai_priv->regmap, REG_ESAI_PCRC, ESAI_PCRC_PC_MASK, 0); + + /* + * Add fifo reset here, because the regcache_sync will write one more data to ETDR. + * Which will cause channel shift. + */ + regmap_update_bits(esai_priv->regmap, REG_ESAI_TFCR, ESAI_xFCR_xFR_MASK, ESAI_xFCR_xFR); + regmap_update_bits(esai_priv->regmap, REG_ESAI_RFCR, ESAI_xFCR_xFR_MASK, ESAI_xFCR_xFR); + + regcache_mark_dirty(esai_priv->regmap); + regcache_sync(esai_priv->regmap); + + regmap_update_bits(esai_priv->regmap, REG_ESAI_TFCR, ESAI_xFCR_xFR_MASK, 0); + regmap_update_bits(esai_priv->regmap, REG_ESAI_RFCR, ESAI_xFCR_xFR_MASK, 0); + + regmap_update_bits(esai_priv->regmap, REG_ESAI_TCR, ESAI_xCR_xPR_MASK, 0); + regmap_update_bits(esai_priv->regmap, REG_ESAI_RCR, ESAI_xCR_xPR_MASK, 0); + + regmap_update_bits(esai_priv->regmap, REG_ESAI_PRRC, + ESAI_PRRC_PDC_MASK, ESAI_PRRC_PDC(ESAI_GPIO)); + regmap_update_bits(esai_priv->regmap, REG_ESAI_PCRC, + ESAI_PCRC_PC_MASK, ESAI_PCRC_PC(ESAI_GPIO)); + + regmap_read(esai_priv->regmap, REG_ESAI_SAISR, &saisr); + + if (stop) + imx_start_unlock_pcm_streams(esai_priv->substream, 2, &flags); +} + +static const struct of_device_id fsl_esai_dt_ids[] = { + { .compatible = "fsl,imx8qxp-v1-esai", .data = &fsl_esai_imx8qxp_v1 }, + { .compatible = "fsl,imx8qm-esai", .data = &fsl_esai_imx8qm }, + { .compatible = "fsl,imx6ull-esai", .data = &fsl_esai_imx6ull }, + { .compatible = "fsl,imx35-esai", .data = &fsl_esai_imx35 }, + { .compatible = "fsl,vf610-esai", .data = &fsl_esai_vf610 }, + {} +}; +MODULE_DEVICE_TABLE(of, fsl_esai_dt_ids); + static int fsl_esai_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; + const struct of_device_id *of_id; struct fsl_esai *esai_priv; struct resource *res; const uint32_t *iprop; void __iomem *regs; int irq, ret; + u32 buffer_size; + unsigned long irqflag = 0; esai_priv = devm_kzalloc(&pdev->dev, sizeof(*esai_priv), GFP_KERNEL); if (!esai_priv) @@ -826,6 +1000,12 @@ static int fsl_esai_probe(struct platform_device *pdev) esai_priv->pdev = pdev; strncpy(esai_priv->name, np->name, sizeof(esai_priv->name) - 1); + of_id = of_match_device(fsl_esai_dt_ids, &pdev->dev); + if (!of_id || !of_id->data) + return -EINVAL; + + esai_priv->soc = of_id->data; + /* Get the addresses and IRQ */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); regs = devm_ioremap_resource(&pdev->dev, res); @@ -868,7 +1048,11 @@ static int fsl_esai_probe(struct platform_device *pdev) return irq; } - ret = devm_request_irq(&pdev->dev, irq, esai_isr, 0, + /* ESAI shared interrupt */ + if (of_property_read_bool(np, "shared-interrupt")) + irqflag = IRQF_SHARED; + + ret = devm_request_irq(&pdev->dev, irq, esai_isr, irqflag, esai_priv->name, esai_priv); if (ret) { dev_err(&pdev->dev, "failed to claim irq %u\n", irq); @@ -888,11 +1072,23 @@ static int fsl_esai_probe(struct platform_device *pdev) else esai_priv->fifo_depth = 64; + esai_priv->dma_params_rx.chan_name = "rx"; + esai_priv->dma_params_tx.chan_name = "tx"; esai_priv->dma_params_tx.maxburst = 16; esai_priv->dma_params_rx.maxburst = 16; esai_priv->dma_params_tx.addr = res->start + REG_ESAI_ETDR; esai_priv->dma_params_rx.addr = res->start + REG_ESAI_ERDR; + /* From imx6ull, the channel swap issue in underrun/overrun is + * fixed in hardware. So remove the workaround. + */ + if (esai_priv->soc->channel_swap_workaround) { + esai_priv->dma_params_tx.check_xrun = fsl_esai_check_xrun; + esai_priv->dma_params_rx.check_xrun = fsl_esai_check_xrun; + esai_priv->dma_params_tx.device_reset = fsl_esai_reset; + esai_priv->dma_params_rx.device_reset = fsl_esai_reset; + } + esai_priv->synchronous = of_property_read_bool(np, "fsl,esai-synchronous"); @@ -938,37 +1134,70 @@ static int fsl_esai_probe(struct platform_device *pdev) return ret; } - ret = imx_pcm_dma_init(pdev, IMX_ESAI_DMABUF_SIZE); + if (of_property_read_u32(np, "fsl,dma-buffer-size", &buffer_size)) + buffer_size = IMX_ESAI_DMABUF_SIZE; + + /* workaround for esai issue in imx8qxp */ + if (esai_priv->soc->dma_workaround) + esai_priv->dma_info = + fsl_dma_workaround_alloc_info("tcd_pool_esai", + &pdev->dev, + "nxp,imx8qm-acm", + FSL_DMA_WORKAROUND_ESAI); + + pm_runtime_enable(&pdev->dev); + regcache_cache_only(esai_priv->regmap, true); + + ret = imx_pcm_platform_register(&pdev->dev); if (ret) dev_err(&pdev->dev, "failed to init imx pcm dma: %d\n", ret); return ret; } -static const struct of_device_id fsl_esai_dt_ids[] = { - { .compatible = "fsl,imx35-esai", }, - { .compatible = "fsl,vf610-esai", }, - {} -}; -MODULE_DEVICE_TABLE(of, fsl_esai_dt_ids); - -#ifdef CONFIG_PM_SLEEP -static int fsl_esai_suspend(struct device *dev) +static int fsl_esai_remove(struct platform_device *pdev) { - struct fsl_esai *esai = dev_get_drvdata(dev); + struct fsl_esai *esai_priv = dev_get_drvdata(&pdev->dev); - regcache_cache_only(esai->regmap, true); - regcache_mark_dirty(esai->regmap); + if (esai_priv->soc->dma_workaround) + fsl_dma_workaround_free_info(esai_priv->dma_info, &pdev->dev); + + pm_runtime_disable(&pdev->dev); return 0; } -static int fsl_esai_resume(struct device *dev) +#ifdef CONFIG_PM +static int fsl_esai_runtime_resume(struct device *dev) { struct fsl_esai *esai = dev_get_drvdata(dev); int ret; + /* + * Some platforms might use the same bit to gate all three or two of + * clocks, so keep all clocks open/close at the same time for safety + */ + ret = clk_prepare_enable(esai->coreclk); + if (ret) + return ret; + if (!IS_ERR(esai->spbaclk)) { + ret = clk_prepare_enable(esai->spbaclk); + if (ret) + goto disable_core_clk; + } + if (!IS_ERR(esai->extalclk)) { + ret = clk_prepare_enable(esai->extalclk); + if (ret) + goto disable_spba_clk; + } + if (!IS_ERR(esai->fsysclk)) { + ret = clk_prepare_enable(esai->fsysclk); + if (ret) + goto disable_extal_clk; + } + regcache_cache_only(esai->regmap, false); + regcache_mark_dirty(esai->regmap); /* FIFO reset for safety */ regmap_update_bits(esai->regmap, REG_ESAI_TFCR, @@ -978,22 +1207,57 @@ static int fsl_esai_resume(struct device *dev) ret = regcache_sync(esai->regmap); if (ret) - return ret; + goto disable_fsys_clk; /* FIFO reset done */ regmap_update_bits(esai->regmap, REG_ESAI_TFCR, ESAI_xFCR_xFR, 0); regmap_update_bits(esai->regmap, REG_ESAI_RFCR, ESAI_xFCR_xFR, 0); return 0; + +disable_fsys_clk: + if (!IS_ERR(esai->fsysclk)) + clk_disable_unprepare(esai->fsysclk); +disable_extal_clk: + if (!IS_ERR(esai->extalclk)) + clk_disable_unprepare(esai->extalclk); +disable_spba_clk: + if (!IS_ERR(esai->spbaclk)) + clk_disable_unprepare(esai->spbaclk); +disable_core_clk: + clk_disable_unprepare(esai->coreclk); + + return ret; +} + +static int fsl_esai_runtime_suspend(struct device *dev) +{ + struct fsl_esai *esai = dev_get_drvdata(dev); + + regcache_cache_only(esai->regmap, true); + + if (!IS_ERR(esai->fsysclk)) + clk_disable_unprepare(esai->fsysclk); + if (!IS_ERR(esai->extalclk)) + clk_disable_unprepare(esai->extalclk); + if (!IS_ERR(esai->spbaclk)) + clk_disable_unprepare(esai->spbaclk); + clk_disable_unprepare(esai->coreclk); + + return 0; } -#endif /* CONFIG_PM_SLEEP */ +#endif static const struct dev_pm_ops fsl_esai_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(fsl_esai_suspend, fsl_esai_resume) + SET_RUNTIME_PM_OPS(fsl_esai_runtime_suspend, + fsl_esai_runtime_resume, + NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) }; static struct platform_driver fsl_esai_driver = { .probe = fsl_esai_probe, + .remove = fsl_esai_remove, .driver = { .name = "fsl-esai-dai", .pm = &fsl_esai_pm_ops, diff --git a/sound/soc/fsl/fsl_hdmi.c b/sound/soc/fsl/fsl_hdmi.c new file mode 100644 index 000000000000..05e9a7c1c1d9 --- /dev/null +++ b/sound/soc/fsl/fsl_hdmi.c @@ -0,0 +1,750 @@ +/* + * ALSA SoC HDMI Audio Layer for Freescale i.MX + * + * Copyright (C) 2011-2014 Freescale Semiconductor, Inc. + * + * Some code from patch_hdmi.c + * Copyright (c) 2008-2010 Intel Corporation. All rights reserved. + * Copyright (c) 2006 ATI Technologies Inc. + * Copyright (c) 2008 NVIDIA Corp. All rights reserved. + * Copyright (c) 2008 Wei Ni <wni@nvidia.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/dma-mapping.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/mfd/mxc-hdmi-core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/asoundef.h> +#include <sound/hdmi-codec.h> + +#include <video/mxc_hdmi.h> + +#include "imx-hdmi.h" + + +static struct mxc_edid_cfg edid_cfg; + +static u32 playback_rates[HDMI_MAX_RATES]; +static u32 playback_sample_size[HDMI_MAX_SAMPLE_SIZE]; +static u32 playback_channels[HDMI_MAX_CHANNEL_CONSTRAINTS]; + +static struct snd_pcm_hw_constraint_list playback_constraint_rates; +static struct snd_pcm_hw_constraint_list playback_constraint_bits; +static struct snd_pcm_hw_constraint_list playback_constraint_channels; + +#ifdef DEBUG +static void dumpregs(struct snd_soc_dai *dai) +{ + u32 n, cts; + + cts = (hdmi_readb(HDMI_AUD_CTS3) << 16) | + (hdmi_readb(HDMI_AUD_CTS2) << 8) | + hdmi_readb(HDMI_AUD_CTS1); + + n = (hdmi_readb(HDMI_AUD_N3) << 16) | + (hdmi_readb(HDMI_AUD_N2) << 8) | + hdmi_readb(HDMI_AUD_N1); + + dev_dbg(dai->dev, "HDMI_PHY_CONF0 0x%02x\n", + hdmi_readb(HDMI_PHY_CONF0)); + dev_dbg(dai->dev, "HDMI_MC_CLKDIS 0x%02x\n", + hdmi_readb(HDMI_MC_CLKDIS)); + dev_dbg(dai->dev, "HDMI_AUD_N[1-3] 0x%06x (%d)\n", + n, n); + dev_dbg(dai->dev, "HDMI_AUD_CTS[1-3] 0x%06x (%d)\n", + cts, cts); + dev_dbg(dai->dev, "HDMI_FC_AUDSCONF 0x%02x\n", + hdmi_readb(HDMI_FC_AUDSCONF)); +} +#else +static void dumpregs(struct snd_soc_dai *dai) {} +#endif + +enum cea_speaker_placement { + FL = (1 << 0), /* Front Left */ + FC = (1 << 1), /* Front Center */ + FR = (1 << 2), /* Front Right */ + FLC = (1 << 3), /* Front Left Center */ + FRC = (1 << 4), /* Front Right Center */ + RL = (1 << 5), /* Rear Left */ + RC = (1 << 6), /* Rear Center */ + RR = (1 << 7), /* Rear Right */ + RLC = (1 << 8), /* Rear Left Center */ + RRC = (1 << 9), /* Rear Right Center */ + LFE = (1 << 10), /* Low Frequency Effect */ + FLW = (1 << 11), /* Front Left Wide */ + FRW = (1 << 12), /* Front Right Wide */ + FLH = (1 << 13), /* Front Left High */ + FCH = (1 << 14), /* Front Center High */ + FRH = (1 << 15), /* Front Right High */ + TC = (1 << 16), /* Top Center */ +}; + +/* + * EDID SA bits in the CEA Speaker Allocation data block + */ +static int edid_speaker_allocation_bits[] = { + [0] = FL | FR, + [1] = LFE, + [2] = FC, + [3] = RL | RR, + [4] = RC, + [5] = FLC | FRC, + [6] = RLC | RRC, + [7] = FLW | FRW, + [8] = FLH | FRH, + [9] = TC, + [10] = FCH, +}; + +struct cea_channel_speaker_allocation { + int ca_index; + int speakers[8]; + + /* Derived values, just for convenience */ + int channels; + int spk_mask; +}; + +/* + * This is an ordered list! + * + * The preceding ones have better chances to be selected by + * hdmi_channel_allocation(). + */ +static struct cea_channel_speaker_allocation channel_allocations[] = { + /* channel: 7 6 5 4 3 2 1 0 */ + { .ca_index = 0x00, .speakers = { 0, 0, 0, 0, 0, 0, FR, FL },}, + /* 2.1 */ + { .ca_index = 0x01, .speakers = { 0, 0, 0, 0, 0, LFE, FR, FL },}, + /* Dolby Surround */ + { .ca_index = 0x02, .speakers = { 0, 0, 0, 0, FC, 0, FR, FL },}, + { .ca_index = 0x03, .speakers = { 0, 0, 0, 0, FC, LFE, FR, FL },}, + { .ca_index = 0x04, .speakers = { 0, 0, 0, RC, 0, 0, FR, FL },}, + { .ca_index = 0x05, .speakers = { 0, 0, 0, RC, 0, LFE, FR, FL },}, + { .ca_index = 0x06, .speakers = { 0, 0, 0, RC, FC, 0, FR, FL },}, + { .ca_index = 0x07, .speakers = { 0, 0, 0, RC, FC, LFE, FR, FL },}, + { .ca_index = 0x08, .speakers = { 0, 0, RR, RL, 0, 0, FR, FL },}, + { .ca_index = 0x09, .speakers = { 0, 0, RR, RL, 0, LFE, FR, FL },}, + { .ca_index = 0x0a, .speakers = { 0, 0, RR, RL, FC, 0, FR, FL },}, + /* surround51 */ + { .ca_index = 0x0b, .speakers = { 0, 0, RR, RL, FC, LFE, FR, FL },}, + { .ca_index = 0x0c, .speakers = { 0, RC, RR, RL, 0, 0, FR, FL },}, + { .ca_index = 0x0d, .speakers = { 0, RC, RR, RL, 0, LFE, FR, FL },}, + { .ca_index = 0x0e, .speakers = { 0, RC, RR, RL, FC, 0, FR, FL },}, + /* 6.1 */ + { .ca_index = 0x0f, .speakers = { 0, RC, RR, RL, FC, LFE, FR, FL },}, + { .ca_index = 0x10, .speakers = { RRC, RLC, RR, RL, 0, 0, FR, FL },}, + { .ca_index = 0x11, .speakers = { RRC, RLC, RR, RL, 0, LFE, FR, FL },}, + { .ca_index = 0x12, .speakers = { RRC, RLC, RR, RL, FC, 0, FR, FL },}, + /* surround71 */ + { .ca_index = 0x13, .speakers = { RRC, RLC, RR, RL, FC, LFE, FR, FL },}, + { .ca_index = 0x14, .speakers = { FRC, FLC, 0, 0, 0, 0, FR, FL },}, + { .ca_index = 0x15, .speakers = { FRC, FLC, 0, 0, 0, LFE, FR, FL },}, + { .ca_index = 0x16, .speakers = { FRC, FLC, 0, 0, FC, 0, FR, FL },}, + { .ca_index = 0x17, .speakers = { FRC, FLC, 0, 0, FC, LFE, FR, FL },}, + { .ca_index = 0x18, .speakers = { FRC, FLC, 0, RC, 0, 0, FR, FL },}, + { .ca_index = 0x19, .speakers = { FRC, FLC, 0, RC, 0, LFE, FR, FL },}, + { .ca_index = 0x1a, .speakers = { FRC, FLC, 0, RC, FC, 0, FR, FL },}, + { .ca_index = 0x1b, .speakers = { FRC, FLC, 0, RC, FC, LFE, FR, FL },}, + { .ca_index = 0x1c, .speakers = { FRC, FLC, RR, RL, 0, 0, FR, FL },}, + { .ca_index = 0x1d, .speakers = { FRC, FLC, RR, RL, 0, LFE, FR, FL },}, + { .ca_index = 0x1e, .speakers = { FRC, FLC, RR, RL, FC, 0, FR, FL },}, + { .ca_index = 0x1f, .speakers = { FRC, FLC, RR, RL, FC, LFE, FR, FL },}, + { .ca_index = 0x20, .speakers = { 0, FCH, RR, RL, FC, 0, FR, FL },}, + { .ca_index = 0x21, .speakers = { 0, FCH, RR, RL, FC, LFE, FR, FL },}, + { .ca_index = 0x22, .speakers = { TC, 0, RR, RL, FC, 0, FR, FL },}, + { .ca_index = 0x23, .speakers = { TC, 0, RR, RL, FC, LFE, FR, FL },}, + { .ca_index = 0x24, .speakers = { FRH, FLH, RR, RL, 0, 0, FR, FL },}, + { .ca_index = 0x25, .speakers = { FRH, FLH, RR, RL, 0, LFE, FR, FL },}, + { .ca_index = 0x26, .speakers = { FRW, FLW, RR, RL, 0, 0, FR, FL },}, + { .ca_index = 0x27, .speakers = { FRW, FLW, RR, RL, 0, LFE, FR, FL },}, + { .ca_index = 0x28, .speakers = { TC, RC, RR, RL, FC, 0, FR, FL },}, + { .ca_index = 0x29, .speakers = { TC, RC, RR, RL, FC, LFE, FR, FL },}, + { .ca_index = 0x2a, .speakers = { FCH, RC, RR, RL, FC, 0, FR, FL },}, + { .ca_index = 0x2b, .speakers = { FCH, RC, RR, RL, FC, LFE, FR, FL },}, + { .ca_index = 0x2c, .speakers = { TC, FCH, RR, RL, FC, 0, FR, FL },}, + { .ca_index = 0x2d, .speakers = { TC, FCH, RR, RL, FC, LFE, FR, FL },}, + { .ca_index = 0x2e, .speakers = { FRH, FLH, RR, RL, FC, 0, FR, FL },}, + { .ca_index = 0x2f, .speakers = { FRH, FLH, RR, RL, FC, LFE, FR, FL },}, + { .ca_index = 0x30, .speakers = { FRW, FLW, RR, RL, FC, 0, FR, FL },}, + { .ca_index = 0x31, .speakers = { FRW, FLW, RR, RL, FC, LFE, FR, FL },}, +}; + +/* Compute derived values in channel_allocations[] */ +static void init_channel_allocations(void) +{ + struct cea_channel_speaker_allocation *p; + int i, j; + + for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) { + p = channel_allocations + i; + p->channels = 0; + p->spk_mask = 0; + for (j = 0; j < ARRAY_SIZE(p->speakers); j++) + if (p->speakers[j]) { + p->channels++; + p->spk_mask |= p->speakers[j]; + } + } +} + +/* + * The transformation takes two steps: + * + * speaker_alloc => (edid_speaker_allocation_bits[]) => spk_mask + * spk_mask => (channel_allocations[]) => CA + * + * TODO: it could select the wrong CA from multiple candidates. +*/ +static int hdmi_channel_allocation(int channels) +{ + int spk_mask = 0, ca = 0, i, tmpchn, tmpspk; + + /* CA defaults to 0 for basic stereo audio */ + if (channels <= 2) + return 0; + + /* + * Expand EDID's speaker allocation mask + * + * EDID tells the speaker mask in a compact(paired) form, + * expand EDID's notions to match the ones used by Audio InfoFrame. + */ + for (i = 0; i < ARRAY_SIZE(edid_speaker_allocation_bits); i++) { + if (edid_cfg.speaker_alloc & (1 << i)) + spk_mask |= edid_speaker_allocation_bits[i]; + } + + /* Search for the first working match in the CA table */ + for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) { + tmpchn = channel_allocations[i].channels; + tmpspk = channel_allocations[i].spk_mask; + + if (channels == tmpchn && (spk_mask & tmpspk) == tmpspk) { + ca = channel_allocations[i].ca_index; + break; + } + } + + return ca; +} + +static void hdmi_set_audio_infoframe(unsigned int channels) +{ + u8 audiconf0, audiconf2; + + /* + * From CEA-861-D spec: + * HDMI requires the CT, SS and SF fields to be set to 0 ("Refer + * to Stream Header") as these items are carried in the audio stream. + * + * So we only set the CC and CA fields. + */ + audiconf0 = ((channels - 1) << HDMI_FC_AUDICONF0_CC_OFFSET) & + HDMI_FC_AUDICONF0_CC_MASK; + + audiconf2 = hdmi_channel_allocation(channels); + + hdmi_writeb(audiconf0, HDMI_FC_AUDICONF0); + hdmi_writeb(0, HDMI_FC_AUDICONF1); + hdmi_writeb(audiconf2, HDMI_FC_AUDICONF2); + hdmi_writeb(0, HDMI_FC_AUDICONF3); +} + +static int cea_audio_rates[HDMI_MAX_RATES] = { + 32000, 44100, 48000, 88200, 96000, 176400, 192000, +}; + +static void fsl_hdmi_get_playback_rates(void) +{ + int i, count = 0; + u8 rates; + + /* Always assume basic audio support */ + rates = edid_cfg.sample_rates | 0x7; + + for (i = 0 ; i < HDMI_MAX_RATES ; i++) + if ((rates & (1 << i)) != 0) + playback_rates[count++] = cea_audio_rates[i]; + + playback_constraint_rates.list = playback_rates; + playback_constraint_rates.count = count; + + for (i = 0 ; i < playback_constraint_rates.count ; i++) + pr_debug("%s: constraint = %d Hz\n", __func__, playback_rates[i]); +} + +static void fsl_hdmi_get_playback_sample_size(void) +{ + int i = 0; + + /* Always assume basic audio support */ + playback_sample_size[i++] = 16; + + if (edid_cfg.sample_sizes & 0x4) + playback_sample_size[i++] = 24; + + playback_constraint_bits.list = playback_sample_size; + playback_constraint_bits.count = i; + + for (i = 0 ; i < playback_constraint_bits.count ; i++) + pr_debug("%s: constraint = %d bits\n", __func__, playback_sample_size[i]); +} + +static void fsl_hdmi_get_playback_channels(void) +{ + int channels = 2, i = 0; + + /* Always assume basic audio support */ + playback_channels[i++] = channels; + channels += 2; + + while ((i < HDMI_MAX_CHANNEL_CONSTRAINTS) && + (channels <= edid_cfg.max_channels)) { + playback_channels[i++] = channels; + channels += 2; + } + + playback_constraint_channels.list = playback_channels; + playback_constraint_channels.count = i; + + for (i = 0 ; i < playback_constraint_channels.count ; i++) + pr_debug("%s: constraint = %d channels\n", __func__, playback_channels[i]); +} + +static int fsl_hdmi_update_constraints(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int ret; + + hdmi_get_edid_cfg(&edid_cfg); + + fsl_hdmi_get_playback_rates(); + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &playback_constraint_rates); + if (ret) + return ret; + + fsl_hdmi_get_playback_sample_size(); + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, + &playback_constraint_bits); + if (ret) + return ret; + + fsl_hdmi_get_playback_channels(); + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &playback_constraint_channels); + if (ret) + return ret; + + ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (ret) + return ret; + + return 0; +} + +static int fsl_hdmi_soc_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct imx_hdmi *hdmi_data = snd_soc_dai_get_drvdata(dai); + int ret; + + clk_prepare_enable(hdmi_data->mipi_core_clk); + clk_prepare_enable(hdmi_data->isfr_clk); + clk_prepare_enable(hdmi_data->iahb_clk); + + dev_dbg(dai->dev, "%s hdmi clks: mipi_core: %d isfr:%d iahb:%d\n", __func__, + (int)clk_get_rate(hdmi_data->mipi_core_clk), + (int)clk_get_rate(hdmi_data->isfr_clk), + (int)clk_get_rate(hdmi_data->iahb_clk)); + + ret = fsl_hdmi_update_constraints(substream); + if (ret < 0) + return ret; + + /* Indicates the subpacket represents a flatline sample */ + hdmi_audio_writeb(FC_AUDSCONF, AUD_PACKET_SAMPFIT, 0x0); + + return 0; +} + +static void fsl_hdmi_soc_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct imx_hdmi *hdmi_data = snd_soc_dai_get_drvdata(dai); + + clk_disable_unprepare(hdmi_data->iahb_clk); + clk_disable_unprepare(hdmi_data->isfr_clk); + clk_disable_unprepare(hdmi_data->mipi_core_clk); +} + +static int fsl_hdmi_soc_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + hdmi_set_audio_infoframe(runtime->channels); + hdmi_audio_writeb(FC_AUDSCONF, AUD_PACKET_LAYOUT, + (runtime->channels > 2) ? 0x1 : 0x0); + hdmi_set_sample_rate(runtime->rate); + dumpregs(dai); + + return 0; +} + +static struct snd_soc_dai_ops fsl_hdmi_soc_dai_ops = { + .startup = fsl_hdmi_soc_startup, + .shutdown = fsl_hdmi_soc_shutdown, + .prepare = fsl_hdmi_soc_prepare, +}; + +/* IEC60958 status functions */ +static int fsl_hdmi_iec_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + + return 0; +} + + +static int fsl_hdmi_iec_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + int i; + + for (i = 0 ; i < 4 ; i++) + uvalue->value.iec958.status[i] = iec_header.status[i]; + + return 0; +} + +static int fsl_hdmi_iec_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + int i; + + /* Do not allow professional mode */ + if (uvalue->value.iec958.status[0] & IEC958_AES0_PROFESSIONAL) + return -EPERM; + + for (i = 0 ; i < 4 ; i++) { + iec_header.status[i] = uvalue->value.iec958.status[i]; + pr_debug("%s status[%d]=0x%02x\n", __func__, i, iec_header.status[i]); + } + + return 0; +} + +static int fsl_hdmi_channels_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + hdmi_get_edid_cfg(&edid_cfg); + fsl_hdmi_get_playback_channels(); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = playback_constraint_channels.count; + + return 0; +} + + +static int fsl_hdmi_channels_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + int i; + hdmi_get_edid_cfg(&edid_cfg); + fsl_hdmi_get_playback_channels(); + + for (i = 0 ; i < playback_constraint_channels.count ; i++) + uvalue->value.integer.value[i] = playback_channels[i]; + + return 0; +} + +static int fsl_hdmi_rates_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + hdmi_get_edid_cfg(&edid_cfg); + fsl_hdmi_get_playback_rates(); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = playback_constraint_rates.count; + + return 0; +} + +static int fsl_hdmi_rates_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + int i; + hdmi_get_edid_cfg(&edid_cfg); + fsl_hdmi_get_playback_rates(); + + for (i = 0 ; i < playback_constraint_rates.count ; i++) + uvalue->value.integer.value[i] = playback_rates[i]; + + return 0; +} + +static int fsl_hdmi_formats_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + hdmi_get_edid_cfg(&edid_cfg); + fsl_hdmi_get_playback_sample_size(); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = playback_constraint_bits.count; + + return 0; +} + +static int fsl_hdmi_formats_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + int i; + hdmi_get_edid_cfg(&edid_cfg); + fsl_hdmi_get_playback_sample_size(); + + for (i = 0 ; i < playback_constraint_bits.count ; i++) + uvalue->value.integer.value[i] = playback_sample_size[i]; + + return 0; +} + +static struct snd_kcontrol_new fsl_hdmi_ctrls[] = { + /* Status cchanel controller */ + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = fsl_hdmi_iec_info, + .get = fsl_hdmi_iec_get, + .put = fsl_hdmi_iec_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HDMI Support Channels", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = fsl_hdmi_channels_info, + .get = fsl_hdmi_channels_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HDMI Support Rates", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = fsl_hdmi_rates_info, + .get = fsl_hdmi_rates_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HDMI Support Formats", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = fsl_hdmi_formats_info, + .get = fsl_hdmi_formats_get, + }, +}; + +static int fsl_hdmi_soc_dai_probe(struct snd_soc_dai *dai) +{ + int ret; + + init_channel_allocations(); + + ret = snd_soc_add_dai_controls(dai, fsl_hdmi_ctrls, + ARRAY_SIZE(fsl_hdmi_ctrls)); + if (ret) + dev_warn(dai->dev, "failed to add dai controls\n"); + + return 0; +} + +static struct snd_soc_dai_driver fsl_hdmi_dai = { + .probe = &fsl_hdmi_soc_dai_probe, + .playback = { + .channels_min = 2, + .channels_max = 8, + .rates = MXC_HDMI_RATES_PLAYBACK, + .formats = MXC_HDMI_FORMATS_PLAYBACK, + }, + .ops = &fsl_hdmi_soc_dai_ops, +}; + +static const struct snd_soc_component_driver fsl_hdmi_component = { + .name = "fsl-hdmi", +}; + +/* HDMI audio codec callbacks */ +static int fsl_hdmi_audio_hw_params(struct device *dev, void *data, + struct hdmi_codec_daifmt *fmt, + struct hdmi_codec_params *hparms) +{ + dev_dbg(dev, "[%s]: %u Hz, %d bit, %d channels\n", __func__, + hparms->sample_rate, hparms->sample_width, hparms->cea.channels); + + return 0; +} + +static void fsl_hdmi_audio_shutdown(struct device *dev, void *data) +{ + dev_dbg(dev, "[%s]\n", __func__); +} + +static const struct hdmi_codec_ops fsl_hdmi_audio_codec_ops = { + .hw_params = fsl_hdmi_audio_hw_params, + .audio_shutdown = fsl_hdmi_audio_shutdown, +}; + +static struct hdmi_codec_pdata codec_data = { + .ops = &fsl_hdmi_audio_codec_ops, + .i2s = 1, + .max_i2s_channels = 8, +}; + +static int fsl_hdmi_dai_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct imx_hdmi *hdmi_data; + int ret = 0; + + if (!np) + return -ENODEV; + + if (!hdmi_get_registered()) { + dev_err(&pdev->dev, "failed to probe. Load HDMI-video first.\n"); + return -ENOMEM; + } + + hdmi_data = devm_kzalloc(&pdev->dev, sizeof(*hdmi_data), GFP_KERNEL); + if (!hdmi_data) { + dev_err(&pdev->dev, "failed to alloc hdmi_data\n"); + return -ENOMEM; + } + + hdmi_data->pdev = pdev; + + memcpy(&hdmi_data->cpu_dai_drv, &fsl_hdmi_dai, sizeof(fsl_hdmi_dai)); + hdmi_data->cpu_dai_drv.name = np->name; + + hdmi_data->mipi_core_clk = devm_clk_get(&pdev->dev, "mipi_core"); + if (IS_ERR(hdmi_data->mipi_core_clk)) { + ret = PTR_ERR(hdmi_data->mipi_core_clk); + dev_err(&pdev->dev, "failed to get mipi core clk: %d\n", ret); + return -EINVAL; + } + + hdmi_data->isfr_clk = devm_clk_get(&pdev->dev, "hdmi_isfr"); + if (IS_ERR(hdmi_data->isfr_clk)) { + ret = PTR_ERR(hdmi_data->isfr_clk); + dev_err(&pdev->dev, "failed to get HDMI isfr clk: %d\n", ret); + return -EINVAL; + } + + hdmi_data->iahb_clk = devm_clk_get(&pdev->dev, "hdmi_iahb"); + if (IS_ERR(hdmi_data->iahb_clk)) { + ret = PTR_ERR(hdmi_data->iahb_clk); + dev_err(&pdev->dev, "failed to get HDMI ahb clk: %d\n", ret); + return -EINVAL; + } + + dev_set_drvdata(&pdev->dev, hdmi_data); + ret = snd_soc_register_component(&pdev->dev, &fsl_hdmi_component, + &hdmi_data->cpu_dai_drv, 1); + if (ret) { + dev_err(&pdev->dev, "register DAI failed\n"); + return ret; + } + + hdmi_data->codec_dev = platform_device_register_data(&pdev->dev, + HDMI_CODEC_DRV_NAME, PLATFORM_DEVID_NONE, + &codec_data, sizeof(codec_data)); + if (IS_ERR(hdmi_data->codec_dev)) { + dev_err(&pdev->dev, "failed to register HDMI audio codec\n"); + ret = PTR_ERR(hdmi_data->codec_dev); + goto fail; + } + + hdmi_data->dma_dev = platform_device_alloc("imx-hdmi-audio", -1); + if (!hdmi_data->dma_dev) { + ret = -ENOMEM; + goto fail_dma; + } + + platform_set_drvdata(hdmi_data->dma_dev, hdmi_data); + + ret = platform_device_add(hdmi_data->dma_dev); + if (ret) { + platform_device_put(hdmi_data->dma_dev); + goto fail_dma; + } + + return 0; + +fail_dma: + platform_device_unregister(hdmi_data->codec_dev); +fail: + snd_soc_unregister_component(&pdev->dev); + + return ret; +} + +static int fsl_hdmi_dai_remove(struct platform_device *pdev) +{ + struct imx_hdmi *hdmi_data = platform_get_drvdata(pdev); + + platform_device_unregister(hdmi_data->dma_dev); + platform_device_unregister(hdmi_data->codec_dev); + snd_soc_unregister_component(&pdev->dev); + + return 0; +} + +static const struct of_device_id fsl_hdmi_dai_dt_ids[] = { + { .compatible = "fsl,imx6dl-hdmi-audio", }, + { .compatible = "fsl,imx6q-hdmi-audio", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, fsl_hdmi_dai_dt_ids); + +static struct platform_driver fsl_hdmi_driver = { + .probe = fsl_hdmi_dai_probe, + .remove = fsl_hdmi_dai_remove, + .driver = { + .name = "fsl-hdmi-dai", + .owner = THIS_MODULE, + .of_match_table = fsl_hdmi_dai_dt_ids, + }, +}; +module_platform_driver(fsl_hdmi_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("IMX HDMI TX DAI"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:fsl-hdmi-dai"); diff --git a/sound/soc/fsl/fsl_micfil.c b/sound/soc/fsl/fsl_micfil.c new file mode 100644 index 000000000000..e25513fc522b --- /dev/null +++ b/sound/soc/fsl/fsl_micfil.c @@ -0,0 +1,2110 @@ +/* + * Copyright 2018 NXP + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/atomic.h> +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/kobject.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/sysfs.h> +#include <linux/types.h> +#include <sound/dmaengine_pcm.h> +#include <sound/pcm.h> +#include <sound/soc.h> + +#include "fsl_micfil.h" +#include "imx-pcm.h" + +#define FSL_MICFIL_RATES SNDRV_PCM_RATE_8000_48000 +#define FSL_MICFIL_FORMATS (SNDRV_PCM_FMTBIT_S16_LE) + +struct fsl_micfil { + struct platform_device *pdev; + struct regmap *regmap; + const struct fsl_micfil_soc_data *soc; + struct clk *mclk; + struct snd_dmaengine_dai_dma_data dma_params_rx; + struct kobject *hwvad_kobject; + unsigned int channels; + unsigned int dataline; + char name[32]; + unsigned int mclk_streams; + int quality; /*QUALITY 2-0 bits */ + bool slave_mode; + int vad_sound_gain; + int vad_noise_gain; + int vad_input_gain; + int vad_frame_time; + int vad_init_time; + int vad_init_mode; + int vad_nfil_adjust; + int vad_hpf; + int vad_zcd_th; + int vad_zcd_auto; + int vad_zcd_en; + int vad_zcd_adj; + atomic_t state; + atomic_t voice_detected; + atomic_t init_hwvad_done; +}; + +struct fsl_micfil_soc_data { + unsigned int fifos; + unsigned int fifo_depth; + unsigned int dataline; + bool imx; +}; + +static char *envp[] = { + "EVENT=PDM_VOICE_DETECT", + NULL, + }; + +static struct fsl_micfil_soc_data fsl_micfil_imx8mm = { + .imx = true, + .fifos = 8, + .fifo_depth = 8, + .dataline = 0xf, +}; + +static const struct of_device_id fsl_micfil_dt_ids[] = { + { .compatible = "fsl,imx8mm-micfil", .data = &fsl_micfil_imx8mm }, + {} +}; +MODULE_DEVICE_TABLE(of, fsl_micfil_dt_ids); + +/* Table 5. Quality Modes + * Medium 0 0 0 + * High 0 0 1 + * Very Low 2 1 0 0 + * Very Low 1 1 0 1 + * Very Low 0 1 1 0 + * Low 1 1 1 + */ +static const char * const micfil_quality_select_texts[] = { + "Medium", "High", + "VLow2", "VLow1", + "VLow0", "Low", +}; + +static const char * const micfil_hwvad_init_mode[] = { + "Envelope mode", "Energy mode", +}; + +static const char * const micfil_hwvad_hpf_texts[] = { + "Filter bypass", + "Cut-off @1750Hz", + "Cut-off @215Hz", + "Cut-off @102Hz", +}; + +static const char * const micfil_hwvad_zcd_enable[] = { + "OFF", "ON", +}; + +static const char * const micfil_hwvad_zcdauto_enable[] = { + "OFF", "ON", +}; + + +static const struct soc_enum fsl_micfil_enum[] = { + SOC_ENUM_SINGLE(REG_MICFIL_CTRL2, + MICFIL_CTRL2_QSEL_SHIFT, + ARRAY_SIZE(micfil_quality_select_texts), + micfil_quality_select_texts), + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(micfil_hwvad_init_mode), + micfil_hwvad_init_mode), + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(micfil_hwvad_hpf_texts), + micfil_hwvad_hpf_texts), + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(micfil_hwvad_zcd_enable), + micfil_hwvad_zcd_enable), + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(micfil_hwvad_zcdauto_enable), + micfil_hwvad_zcd_enable), +}; + +static int set_quality(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + int val = snd_soc_enum_item_to_val(e, item[0]); + int ret; + + switch (val) { + case 0: + case 1: + micfil->quality = val; + break; + case 2: + case 3: + case 4: + case 5: + micfil->quality = val + 2; + break; + default: + dev_err(comp->dev, "Undefined value %d\n", val); + return -EINVAL; + } + + ret = snd_soc_component_update_bits(comp, + REG_MICFIL_CTRL2, + MICFIL_CTRL2_QSEL_MASK, + micfil->quality << MICFIL_CTRL2_QSEL_SHIFT); + if (ret) + return ret; + + return 0; +} + +static int hwvad_put_init_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + int val = snd_soc_enum_item_to_val(e, item[0]); + + /* 0 - Envelope-based Mode + * 1 - Energy-based Mode + */ + micfil->vad_init_mode = val; + return 0; +} + +static int hwvad_get_init_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_init_mode; + + return 0; +} + +static int hwvad_put_hpf(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + int val = snd_soc_enum_item_to_val(e, item[0]); + + /* 00 - HPF Bypass + * 01 - Cut-off frequency 1750Hz + * 10 - Cut-off frequency 215Hz + * 11 - Cut-off frequency 102Hz + */ + micfil->vad_hpf = val; + + return 0; +} + +static int hwvad_get_hpf(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_hpf; + + return 0; +} + +static int hwvad_put_zcd_en(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + int val = snd_soc_enum_item_to_val(e, item[0]); + + micfil->vad_zcd_en = val; + + return 0; +} + +static int hwvad_get_zcd_en(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_zcd_en; + + return 0; +} + +static int hwvad_put_zcd_auto(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + int val = snd_soc_enum_item_to_val(e, item[0]); + + micfil->vad_zcd_auto = val; + + return 0; +} + +static int hwvad_get_zcd_auto(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_zcd_auto; + + return 0; +} + +static int hwvad_gain_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 0xf; + + return 0; +} + +static int hwvad_put_input_gain(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + micfil->vad_input_gain = ucontrol->value.integer.value[0]; + + return 0; +} + +static int hwvad_get_input_gain(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_input_gain; + + return 0; +} + +static int hwvad_put_sound_gain(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + micfil->vad_sound_gain = ucontrol->value.integer.value[0]; + + return 0; +} + +static int hwvad_get_sound_gain(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_sound_gain; + + return 0; +} + +static int hwvad_put_noise_gain(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + micfil->vad_noise_gain = ucontrol->value.integer.value[0]; + + return 0; +} + +static int hwvad_get_noise_gain(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_noise_gain; + + return 0; +} + +static int hwvad_framet_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 1; + uinfo->value.integer.max = 64; + + return 0; +} + +static int hwvad_put_frame_time(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + micfil->vad_frame_time = ucontrol->value.integer.value[0]; + + return 0; +} + +static int hwvad_get_frame_time(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_frame_time; + + return 0; +} + +static int hwvad_initt_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 1; + uinfo->value.integer.max = 32; + + return 0; +} + +static int hwvad_put_init_time(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + micfil->vad_init_time = ucontrol->value.integer.value[0]; + + return 0; +} + +static int hwvad_get_init_time(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_init_time; + + return 0; +} + +static int hwvad_nfiladj_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 1; + uinfo->value.integer.max = 32; + + return 0; +} + +static int hwvad_put_nfil_adjust(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + micfil->vad_nfil_adjust = ucontrol->value.integer.value[0]; + + return 0; +} + +static int hwvad_get_nfil_adjust(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_nfil_adjust; + + return 0; +} + +static int hwvad_zcdth_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 1; + uinfo->value.integer.max = 1024; + + return 0; +} + +static int hwvad_put_zcd_th(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + micfil->vad_zcd_th = ucontrol->value.integer.value[0]; + + return 0; +} + +static int hwvad_get_zcd_th(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_zcd_th; + + return 0; +} + +static int hwvad_zcdadj_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 1; + uinfo->value.integer.max = 16; + + return 0; +} + +static int hwvad_put_zcd_adj(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + micfil->vad_zcd_adj = ucontrol->value.integer.value[0]; + + return 0; +} + +static int hwvad_get_zcd_adj(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_zcd_adj; + + return 0; +} + +static const struct snd_kcontrol_new fsl_micfil_snd_controls[] = { + SOC_SINGLE_RANGE("CH1 Gain", REG_MICFIL_OUT_CTRL, + MICFIL_OUTGAIN_CHX_SHIFT(0), 0x0, 0xF, 0), + SOC_SINGLE_RANGE("CH2 Gain", REG_MICFIL_OUT_CTRL, + MICFIL_OUTGAIN_CHX_SHIFT(1), 0x0, 0xF, 0), + SOC_SINGLE_RANGE("CH3 Gain", REG_MICFIL_OUT_CTRL, + MICFIL_OUTGAIN_CHX_SHIFT(2), 0x0, 0xF, 0), + SOC_SINGLE_RANGE("CH4 Gain", REG_MICFIL_OUT_CTRL, + MICFIL_OUTGAIN_CHX_SHIFT(3), 0x0, 0xF, 0), + SOC_SINGLE_RANGE("CH5 Gain", REG_MICFIL_OUT_CTRL, + MICFIL_OUTGAIN_CHX_SHIFT(4), 0x0, 0xF, 0), + SOC_SINGLE_RANGE("CH6 Gain", REG_MICFIL_OUT_CTRL, + MICFIL_OUTGAIN_CHX_SHIFT(5), 0x0, 0xF, 0), + SOC_SINGLE_RANGE("CH7 Gain", REG_MICFIL_OUT_CTRL, + MICFIL_OUTGAIN_CHX_SHIFT(6), 0x0, 0xF, 0), + SOC_SINGLE_RANGE("CH8 Gain", REG_MICFIL_OUT_CTRL, + MICFIL_OUTGAIN_CHX_SHIFT(7), 0x0, 0xF, 0), + SOC_ENUM_EXT("MICFIL Quality Select", fsl_micfil_enum[0], + snd_soc_get_enum_double, set_quality), + SOC_ENUM_EXT("HWVAD Initialization Mode", fsl_micfil_enum[1], + hwvad_get_init_mode, hwvad_put_init_mode), + SOC_ENUM_EXT("HWVAD High-Pass Filter", fsl_micfil_enum[2], + hwvad_get_hpf, hwvad_put_hpf), + SOC_ENUM_EXT("HWVAD Zero-Crossing Detector Enable", fsl_micfil_enum[3], + hwvad_get_zcd_en, hwvad_put_zcd_en), + SOC_ENUM_EXT("HWVAD Zero-Crossing Detector Auto Threshold", fsl_micfil_enum[4], + hwvad_get_zcd_auto, hwvad_put_zcd_auto), + + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HWVAD Input Gain", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE, + .info = hwvad_gain_info, + .get = hwvad_get_input_gain, + .put = hwvad_put_input_gain, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HWVAD Sound Gain", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE, + .info = hwvad_gain_info, + .get = hwvad_get_sound_gain, + .put = hwvad_put_sound_gain, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HWVAD Noise Gain", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE, + .info = hwvad_gain_info, + .get = hwvad_get_noise_gain, + .put = hwvad_put_noise_gain, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HWVAD Detector Frame Time", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE, + .info = hwvad_framet_info, + .get = hwvad_get_frame_time, + .put = hwvad_put_frame_time, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HWVAD Detector Initialization Time", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE, + .info = hwvad_initt_info, + .get = hwvad_get_init_time, + .put = hwvad_put_init_time, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HWVAD Noise Filter Adjustment", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE, + .info = hwvad_nfiladj_info, + .get = hwvad_get_nfil_adjust, + .put = hwvad_put_nfil_adjust, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HWVAD Zero-Crossing Detector Threshold", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE, + .info = hwvad_zcdth_info, + .get = hwvad_get_zcd_th, + .put = hwvad_put_zcd_th, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HWVAD Zero-Crossing Detector Adjustment", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE, + .info = hwvad_zcdadj_info, + .get = hwvad_get_zcd_adj, + .put = hwvad_put_zcd_adj, + }, + +}; + +static int disable_hwvad(struct device *dev); + +static inline unsigned int get_pdm_clk(struct fsl_micfil *micfil, + unsigned int rate); + +static inline unsigned int get_clk_div(struct fsl_micfil *micfil, + unsigned int rate) +{ + u32 ctrl2_reg; + unsigned long mclk_rate; + unsigned int clk_div; + unsigned int osr; + + regmap_read(micfil->regmap, REG_MICFIL_CTRL2, &ctrl2_reg); + osr = 16 - ((ctrl2_reg & MICFIL_CTRL2_CICOSR_MASK) + >> MICFIL_CTRL2_CICOSR_SHIFT); + + mclk_rate = clk_get_rate(micfil->mclk); + + clk_div = mclk_rate / (get_pdm_clk(micfil, rate) * 2); + + return clk_div; +} + +static inline unsigned int get_pdm_clk(struct fsl_micfil *micfil, + unsigned int rate) +{ + u32 ctrl2_reg; + unsigned int qsel, osr; + unsigned int bclk; + + regmap_read(micfil->regmap, REG_MICFIL_CTRL2, &ctrl2_reg); + osr = 16 - ((ctrl2_reg & MICFIL_CTRL2_CICOSR_MASK) + >> MICFIL_CTRL2_CICOSR_SHIFT); + + regmap_read(micfil->regmap, REG_MICFIL_CTRL2, &ctrl2_reg); + qsel = ((ctrl2_reg & MICFIL_CTRL2_QSEL_MASK) + >> MICFIL_CTRL2_QSEL_SHIFT); + + switch (qsel) { + case MICFIL_HIGH_QUALITY: + bclk = rate * 8 * osr; + break; + case MICFIL_MEDIUM_QUALITY: + case MICFIL_VLOW0_QUALITY: + bclk = rate * 4 * osr; + break; + case MICFIL_LOW_QUALITY: + case MICFIL_VLOW1_QUALITY: + bclk = rate * 2 * osr; + break; + case MICFIL_VLOW2_QUALITY: + bclk = rate * osr; + break; + default: + bclk = 0; + break; + } + + return bclk; +} + +/* Check if BSY_FIL flag in STAT register is set. + * Read this flag for max 10 times, sleep 100ms + * after each read and return error if it's not + * cleared after 10 retries. + */ +static int fsl_micfil_bsy(struct device *dev) +{ + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int i; + int ret; + u32 stat; + + for (i = 0; i < MICFIL_MAX_RETRY; i++) { + ret = regmap_read(micfil->regmap, REG_MICFIL_STAT, &stat); + if (ret) { + dev_err(dev, "failed to read register %d\n", + REG_MICFIL_STAT); + return ret; + } + + if (stat & MICFIL_STAT_BSY_FIL_MASK) + usleep_range(MICFIL_SLEEP_MIN, MICFIL_SLEEP_MAX); + else + return 0; + } + + return -EINVAL; +} + +/* The SRES is a self-negated bit which provides the CPU with the + * capability to initialize the PDM Interface module through the + * slave-bus interface. This bit always reads as zero, and this + * bit is only effective when MDIS is cleared + */ +static int fsl_micfil_reset(struct device *dev) +{ + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret; + + ret = regmap_update_bits(micfil->regmap, + REG_MICFIL_CTRL1, + MICFIL_CTRL1_MDIS_MASK, + 0); + if (ret) { + dev_err(dev, "failed to clear MDIS bit %d\n", ret); + return ret; + } + + ret = regmap_update_bits(micfil->regmap, + REG_MICFIL_CTRL1, + MICFIL_CTRL1_SRES_MASK, + MICFIL_CTRL1_SRES); + if (ret) { + dev_err(dev, "failed to reset MICFIL: %d\n", ret); + return ret; + } + + return 0; +} + +/* enable/disable hwvad interrupts */ +static int configure_hwvad_interrupts(struct device *dev, + int enable) +{ + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret; + u32 vadie_reg = enable ? MICFIL_VAD0_CTRL1_IE : 0; + u32 vaderie_reg = enable ? MICFIL_VAD0_CTRL1_ERIE : 0; + + /* Voice Activity Detector Interruption Enable */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_IE_MASK, + vadie_reg); + if (ret) { + dev_err(dev, + "Failed to set/clear VADIE in CTRL1_VAD0 [%d]\n", + ret); + return ret; + } + + /* Voice Activity Detector Error Interruption Enable */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_ERIE_MASK, + vaderie_reg); + if (ret) { + dev_err(dev, + "Failed to set/clear VADERIE in CTRL1_VAD0 [%d]\n", + ret); + return ret; + } + + return 0; +} + +static int init_hwvad_internal_filters(struct device *dev) +{ + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret; + + /* Voice Activity Detector Internal Filters Initialization*/ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_ST10_MASK, + MICFIL_VAD0_CTRL1_ST10); + if (ret) { + dev_err(dev, + "Failed to set VADST10 in CTRL1_VAD0 [%d]\n", + ret); + return ret; + } + + /* sleep for 100ms - it should be enough for bit to stay + * pulsed for more than 2 cycles + */ + usleep_range(MICFIL_SLEEP_MIN, MICFIL_SLEEP_MAX); + + /* Voice Activity Detector Enabled */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_ST10_MASK, + 0); + if (ret) { + dev_err(dev, + "Failed to clear VADST10 in CTRL1_VAD0 [%d]\n", + ret); + return ret; + } + return 0; +} + +/* Zero-Crossing Detector Initialization + * Optionally a Zero-Crossing Detection block (ZCD) could + * be enabled to avoid low energy voiced speech be missed, + * improving the voice detection performance. + * See Section 8.4.3 + */ +static int __maybe_unused init_zcd(struct device *dev) +{ + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret; + + /* exit if zcd is not enabled from userspace */ + if (!micfil->vad_zcd_en) { + dev_info(dev, "Zero Crossing Detector is not enabled.\n"); + return 0; + } + + if (micfil->vad_zcd_auto) { + /* Zero-Crossing Detector Adjustment */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_ZCD, + MICFIL_VAD0_ZCD_ZCDADJ_MASK, + micfil->vad_zcd_adj); + if (ret) { + dev_err(dev, + "Failed to set ZCDADJ in ZCD_VAD0 [%d]\n", + ret); + return ret; + } + } + + /* Zero-Crossing Detector Threshold */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_ZCD, + MICFIL_VAD0_ZCD_ZCDTH_MASK, + MICFIL_VAD0_ZCD_ZCDTH(micfil->vad_zcd_th)); + if (ret) { + dev_err(dev, "Failed to set ZCDTH in ZCD_VAD0 [%d]\n", ret); + return ret; + } + + /* Zero-Crossing Detector AND Behavior */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_ZCD, + MICFIL_VAD0_ZCD_ZCDAND_MASK, + MICFIL_HWVAD_ZCDAND); + if (ret) { + dev_err(dev, "Failed to set ZCDAND in ZCD_VAD0 [%d]\n", ret); + return ret; + } + + /* Zero-Crossing Detector Automatic Threshold */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_ZCD, + MICFIL_VAD0_ZCD_ZCDAUT_MASK, + micfil->vad_zcd_auto); + if (ret) { + dev_err(dev, + "Failed to set/clear ZCDAUT in ZCD_VAD0 [%d]\n", + ret); + return ret; + } + + /* Zero-Crossing Detector Enable */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_ZCD, + MICFIL_VAD0_ZCD_ZCDEN_MASK, + MICFIL_VAD0_ZCD_ZCDEN); + if (ret) { + dev_err(dev, "Failed to set ZCDEN in ZCD_VAD0 [%d]\n", ret); + return ret; + } + + return 0; +} + +/* Configuration done only in energy-based initialization mode */ +static int init_hwvad_energy_mode(struct device *dev) +{ + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret, i; + u32 stat; + u32 flag; + + dev_info(dev, "Energy-based mode initialization\n"); + + /* Voice Activity Detector Reset */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_RST_SHIFT, + MICFIL_VAD0_CTRL1_RST); + if (ret) { + dev_err(dev, "Failed to set VADRST in CTRL1_VAD0 [%d]\n", ret); + return ret; + } + + /* Voice Activity Detector Enabled */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_EN_MASK, + MICFIL_VAD0_CTRL1_EN); + if (ret) { + dev_err(dev, "Failed to set VADEN in CTRL1_VAD0 [%d]\n", ret); + return ret; + } + + /* it would be a good idea to wait some time before VADEN + * is set + */ + usleep_range(5 * MICFIL_SLEEP_MIN, 5 * MICFIL_SLEEP_MAX); + + /* Enable Interrupts */ + ret = configure_hwvad_interrupts(dev, 1); + + /* Initialize Zero Crossing Detector */ + ret = init_zcd(dev); + if (ret) + return ret; + + /* Enable MICFIL module */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL1, + MICFIL_CTRL1_PDMIEN_MASK, + MICFIL_CTRL1_PDMIEN); + if (ret) { + dev_err(dev, "failed to enable the module\n"); + return ret; + } + + /* Wait for INITF to be asserted */ + for (i = 0; i < MICFIL_MAX_RETRY; i++) { + ret = regmap_read(micfil->regmap, REG_MICFIL_VAD0_STAT, &stat); + if (ret) { + dev_err(dev, "failed to read register %d\n", + REG_MICFIL_VAD0_STAT); + return ret; + } + + flag = (stat & MICFIL_VAD0_STAT_INITF_MASK); + if (flag == 0) + break; + + usleep_range(MICFIL_SLEEP_MIN, MICFIL_SLEEP_MAX); + } + + if (i == MICFIL_MAX_RETRY) { + dev_err(dev, "initf not asserted. Failed to init hwvad\n"); + return -EBUSY; + } + + /* Initialize Internal Filters */ + ret = init_hwvad_internal_filters(dev); + if (ret) + return ret; + + return ret; +} + +/* Configuration done only in envelope-based initialization mode */ +static int init_hwvad_envelope_mode(struct device *dev) +{ + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret, i; + u32 stat; + u32 flag; + + dev_info(dev, "Envelope-based mode initialization\n"); + + /* Frame energy disable */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL2, + MICFIL_VAD0_CTRL2_FRENDIS_MASK, + MICFIL_VAD0_CTRL2_FRENDIS); + if (ret) { + dev_err(dev, "Failed to set FRENDIS in CTRL2_VAD0 [%d]\n", ret); + return ret; + } + + /* Enable pre-filter Noise & Signal */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL2, + MICFIL_VAD0_CTRL2_PREFEN_MASK, + MICFIL_VAD0_CTRL2_PREFEN); + if (ret) { + dev_err(dev, "Failed to set PREFEN in CTRL2_VAD0 [%d]\n", ret); + return ret; + } + + /* Enable Signal Filter */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_SCONFIG, + MICFIL_VAD0_SCONFIG_SFILEN_MASK, + MICFIL_VAD0_SCONFIG_SFILEN); + if (ret) { + dev_err(dev, + "Failed to set SFILEN in SCONFIG_VAD0 [%d]\n", + ret); + return ret; + } + + /* Signal Maximum Enable */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_SCONFIG, + MICFIL_VAD0_SCONFIG_SMAXEN_MASK, + MICFIL_VAD0_SCONFIG_SMAXEN); + if (ret) { + dev_err(dev, + "Failed to set SMAXEN in SCONFIG_VAD0 [%d]\n", + ret); + return ret; + } + + /* Allways enable noise filter, not based on voice activity + * information + */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_NCONFIG, + MICFIL_VAD0_NCONFIG_NFILAUT_MASK, + 0); + if (ret) { + dev_err(dev, + "Failed to set NFILAUT in NCONFIG_VAD0 [%d]\n", + ret); + return ret; + } + + /* Noise Minimum Enable */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_NCONFIG, + MICFIL_VAD0_NCONFIG_NMINEN_MASK, + MICFIL_VAD0_NCONFIG_NMINEN); + if (ret) { + dev_err(dev, + "Failed to set NMINEN in NCONFIG_VAD0 [%d]\n", + ret); + return ret; + } + + /* Noise Decimation Enable */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_NCONFIG, + MICFIL_VAD0_NCONFIG_NDECEN_MASK, + MICFIL_VAD0_NCONFIG_NDECEN); + if (ret) { + dev_err(dev, + "Failed to set NDECEN in NCONFIG_VAD0 [%d]\n", + ret); + return ret; + } + + /* Voice Activity Detector Reset */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_RST_SHIFT, + MICFIL_VAD0_CTRL1_RST); + if (ret) { + dev_err(dev, "Failed to set VADRST in CTRL1_VAD0 [%d]\n", ret); + return ret; + } + + /* Initialize Zero Crossing Detector */ + ret = init_zcd(dev); + if (ret) + return ret; + + /* Voice Activity Detector Enabled */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_EN_MASK, + MICFIL_VAD0_CTRL1_EN); + if (ret) { + dev_err(dev, "Failed to set VADEN in CTRL1_VAD0 [%d]\n", ret); + return ret; + } + + /* Enable MICFIL module */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL1, + MICFIL_CTRL1_PDMIEN_MASK, + MICFIL_CTRL1_PDMIEN); + if (ret) { + dev_err(dev, "failed to enable the module\n"); + return ret; + } + + /* it would be a good idea to wait some time before VADEN + * is set + */ + usleep_range(3 * MICFIL_SLEEP_MIN, 3 * MICFIL_SLEEP_MAX); + + /* Wait for INITF to be asserted */ + for (i = 0; i < MICFIL_MAX_RETRY; i++) { + ret = regmap_read(micfil->regmap, REG_MICFIL_VAD0_STAT, &stat); + if (ret) { + dev_err(dev, "failed to read register %d\n", + REG_MICFIL_VAD0_STAT); + return ret; + } + + flag = (stat & MICFIL_VAD0_STAT_INITF_MASK); + if (flag == 0) + break; + + usleep_range(MICFIL_SLEEP_MIN, MICFIL_SLEEP_MAX); + } + + if (i == MICFIL_MAX_RETRY) { + dev_err(dev, "initf not asserted. Failed to init hwvad\n"); + return -EBUSY; + } + + /* Initialize Internal Filters */ + ret = init_hwvad_internal_filters(dev); + if (ret) + return ret; + + /* Enable interrupts */ + ret = configure_hwvad_interrupts(dev, 1); + if (ret) + return ret; + + return ret; +} + +/* Hardware Voice Active Detection: The HWVAD takes data from the input + * of a selected PDM microphone to detect if there is any + * voice activity. When a voice activity is detected, an interrupt could + * be delivered to the system. Initialization in section 8.4: + * Can work in two modes: + * -> Eneveope-based mode (section 8.4.1) + * -> Energy-based mode (section 8.4.2) + * + * It is important to remark that the HWVAD detector could be enabled + * or reset only when the MICFIL isn't running i.e. when the BSY_FIL + * bit in STAT register is cleared + */ +static int __maybe_unused init_hwvad(struct device *dev) +{ + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret; + + ret = fsl_micfil_bsy(dev); + if (ret) { + dev_err(dev, + "Hardware Voice Active Detection initialization fail. BSY_FIL flag is set [%d]\n", + ret); + return ret; + } + + /* configure CIC OSR in VADCICOSR */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_CICOSR_MASK, + MICFIL_CTRL2_OSR_DEFAULT); + if (ret) { + dev_err(dev, "Failed to set CICOSR in CTRL1_VAD0i [%d]\n", ret); + return ret; + } + + /* configure source channel in VADCHSEL */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_CHSEL_MASK, + MICFIL_VAD0_CTRL1_CHSEL(micfil->channels)); + if (ret) { + dev_err(dev, "Failed to set CHSEL in CTRL1_VAD0 [%d]\n", ret); + return ret; + } + + /* configure detector frame time VADFRAMET */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL2, + MICFIL_VAD0_CTRL2_FRAMET_MASK, + MICFIL_VAD0_CTRL2_FRAMET(micfil->vad_frame_time)); + if (ret) { + dev_err(dev, "Failed to set FRAMET in CTRL2_VAD0 [%d]\n", ret); + return ret; + } + + /* configure initialization time in VADINITT */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_INITT_MASK, + MICFIL_VAD0_CTRL1_INITT(micfil->vad_init_time)); + if (ret) { + dev_err(dev, "Failed to set INITT in CTRL1_VAD0 [%d]\n", ret); + return ret; + } + + /* configure input gain in VADINPGAIN */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL2, + MICFIL_VAD0_CTRL2_INPGAIN_MASK, + MICFIL_VAD0_CTRL2_INPGAIN(micfil->vad_input_gain)); + if (ret) { + dev_err(dev, "Failed to set INPGAIN in CTRL2_VAD0 [%d]\n", ret); + return ret; + } + + /* configure sound gain in SGAIN */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_SCONFIG, + MICFIL_VAD0_SCONFIG_SGAIN_MASK, + MICFIL_VAD0_SCONFIG_SGAIN(micfil->vad_sound_gain)); + if (ret) { + dev_err(dev, "Failed to set SGAIN in SCONFIG_VAD0 [%d]\n", ret); + return ret; + } + + /* configure noise gain in NGAIN */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_NCONFIG, + MICFIL_VAD0_NCONFIG_NGAIN_MASK, + MICFIL_VAD0_NCONFIG_NGAIN(micfil->vad_noise_gain)); + if (ret) { + dev_err(dev, "Failed to set NGAIN in NCONFIG_VAD0 [%d]\n", ret); + return ret; + } + + /* configure or clear the VADNFILADJ based on mode */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_NCONFIG, + MICFIL_VAD0_NCONFIG_NFILADJ_MASK, + MICFIL_VAD0_NCONFIG_NFILADJ(micfil->vad_nfil_adjust)); + if (ret) { + dev_err(dev, + "Failed to set VADNFILADJ in NCONFIG_VAD0 [%d]\n", + ret); + return ret; + } + + /* enable the high-pass filter in VADHPF */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL2, + MICFIL_VAD0_CTRL2_HPF_MASK, + MICFIL_VAD0_CTRL2_HPF(micfil->vad_hpf)); + if (ret) { + dev_err(dev, "Failed to set HPF in CTRL2_VAD0 [%d]\n", ret); + return ret; + } + + /* envelope-based specific initialization */ + if (micfil->vad_init_mode == MICFIL_HWVAD_ENVELOPE_MODE) { + ret = init_hwvad_envelope_mode(dev); + if (ret) + return ret; + } else { + ret = init_hwvad_energy_mode(dev); + if (ret) + return ret; + } + + return 0; +} + +static int fsl_micfil_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct fsl_micfil *micfil = snd_soc_dai_get_drvdata(dai); + struct device *dev = &micfil->pdev->dev; + int state; + + state = atomic_read(&micfil->state); + if (state != RECORDING_OFF_HWVAD_OFF) { + dev_err(dev, "Cannot record while recording or hwvad is on\n"); + return -EPERM; + } + + if (!micfil) { + dev_err(dev, "micfil dai priv_data not set\n"); + return -EINVAL; + } + + return 0; +} + +static int fsl_micfil_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct fsl_micfil *micfil = snd_soc_dai_get_drvdata(dai); + struct device *dev = &micfil->pdev->dev; + int ret; + int old_state; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + /* mark as stream open. Not allowed: + * - two paralel recordings + * - hwvad enabled while recording + */ + old_state = atomic_cmpxchg(&micfil->state, + RECORDING_OFF_HWVAD_OFF, + RECORDING_ON_HWVAD_OFF); + if (old_state != RECORDING_OFF_HWVAD_OFF) { + dev_err(dev, "Another record or hwvad is on\n"); + return -EBUSY; + } + + ret = fsl_micfil_reset(dev); + if (ret) { + dev_err(dev, "failed to soft reset\n"); + return ret; + } + + /* DMA Interrupt Selection - DISEL bits + * 00 - DMA and IRQ disabled + * 01 - DMA req enabled + * 10 - IRQ enabled + * 11 - reserved + */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL1, + MICFIL_CTRL1_DISEL_MASK, + (1 << MICFIL_CTRL1_DISEL_SHIFT)); + if (ret) { + dev_err(dev, "failed to update DISEL bits\n"); + return ret; + } + + /* Enable the module */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL1, + MICFIL_CTRL1_PDMIEN_MASK, + MICFIL_CTRL1_PDMIEN); + if (ret) { + dev_err(dev, "failed to enable the module\n"); + return ret; + } + + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + /* Disable the module */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL1, + MICFIL_CTRL1_PDMIEN_MASK, + 0); + if (ret) { + dev_err(dev, "failed to enable the module\n"); + return ret; + } + + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL1, + MICFIL_CTRL1_DISEL_MASK, + (0 << MICFIL_CTRL1_DISEL_SHIFT)); + if (ret) { + dev_err(dev, "failed to update DISEL bits\n"); + return ret; + } + + /* clear the stream open flag */ + atomic_set(&micfil->state, RECORDING_OFF_HWVAD_OFF); + break; + default: + return -EINVAL; + } + return 0; +} + +static int fsl_set_clock_params(struct device *dev, unsigned int rate) +{ + struct fsl_micfil *micfil = dev_get_drvdata(dev); + unsigned int clk_div; + int ret; + + if (fsl_micfil_bsy(dev)) + return -EBUSY; + + /* set CICOSR */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL2, + MICFIL_CTRL2_CICOSR_MASK, + MICFIL_CTRL2_OSR_DEFAULT); + if (ret) { + dev_err(dev, "failed to set CICOSR in reg 0x%X\n", + REG_MICFIL_CTRL2); + return ret; + } + + /* set CLK_DIV */ + clk_div = get_clk_div(micfil, rate); + + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL2, + MICFIL_CTRL2_CLKDIV_MASK, clk_div); + if (ret) { + dev_err(dev, "failed to set CLKDIV in reg 0x%X\n", + REG_MICFIL_CTRL2); + return ret; + } + + ret = clk_prepare_enable(micfil->mclk); + if (ret) + return ret; + + return 0; +} + +static int fsl_micfil_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct fsl_micfil *micfil = snd_soc_dai_get_drvdata(dai); + unsigned int channels = params_channels(params); + unsigned int rate = params_rate(params); + struct device *dev = &micfil->pdev->dev; + int ret; + + /* 1. Disable the module */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL1, + MICFIL_CTRL1_PDMIEN_MASK, 0); + if (ret) { + dev_err(dev, "failed to disable the module\n"); + return ret; + } + + /* enable channels */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL1, + 0xFF, ((1 << channels) - 1)); + if (ret) { + dev_err(dev, "failed to enable channels %d, reg 0x%X\n", ret, + REG_MICFIL_CTRL1); + return ret; + } + + ret = fsl_set_clock_params(dev, rate); + if (ret) + return ret; + + micfil->dma_params_rx.fifo_num = channels; + micfil->dma_params_rx.maxburst = channels * MICFIL_DMA_MAXBURST_RX; + micfil->channels = channels; + + return 0; +} + +static int fsl_micfil_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct fsl_micfil *micfil = snd_soc_dai_get_drvdata(dai); + + if (!micfil->slave_mode && + micfil->mclk_streams & BIT(substream->stream)) { + clk_disable_unprepare(micfil->mclk); + micfil->mclk_streams &= ~BIT(substream->stream); + } + + return 0; +} + +static int fsl_micfil_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct fsl_micfil *micfil = snd_soc_dai_get_drvdata(dai); + struct device *dev = &micfil->pdev->dev; + + int ret; + + if (!freq) + return 0; + + ret = clk_set_rate(micfil->mclk, freq); + if (ret < 0) + dev_err(dev, "failed to set mclk[%lu] to rate %u\n", + clk_get_rate(micfil->mclk), freq); + + return ret; +} + +static int fsl_micfil_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct fsl_micfil *micfil = snd_soc_dai_get_drvdata(dai); + + /* DAI MODE */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + default: + return -EINVAL; + } + + /* DAI CLK INVERSION */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + default: + return -EINVAL; + } + + micfil->slave_mode = false; + + return 0; +} + +static struct snd_soc_dai_ops fsl_micfil_dai_ops = { + .startup = fsl_micfil_startup, + .trigger = fsl_micfil_trigger, + .hw_params = fsl_micfil_hw_params, + .hw_free = fsl_micfil_hw_free, + .set_sysclk = fsl_micfil_set_dai_sysclk, + .set_fmt = fsl_micfil_set_dai_fmt, +}; + +static int fsl_micfil_dai_probe(struct snd_soc_dai *cpu_dai) +{ + struct fsl_micfil *micfil = dev_get_drvdata(cpu_dai->dev); + struct device *dev = cpu_dai->dev; + unsigned int val; + int ret; + + /* set qsel to medium */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL2, + MICFIL_CTRL2_QSEL_MASK, MICFIL_MEDIUM_QUALITY); + if (ret) { + dev_err(dev, "failed to set quality mode bits, reg 0x%X\n", + REG_MICFIL_CTRL2); + return ret; + } + + regmap_write(micfil->regmap, REG_MICFIL_OUT_CTRL, 0x77777777); + + snd_soc_dai_init_dma_data(cpu_dai, NULL, + &micfil->dma_params_rx); + + /* FIFO Watermark Control - FIFOWMK*/ + val = MICFIL_FIFO_CTRL_FIFOWMK(micfil->soc->fifo_depth) - 1; + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_FIFO_CTRL, + MICFIL_FIFO_CTRL_FIFOWMK_MASK, + val); + if (ret) { + dev_err(dev, "failed to set FIFOWMK\n"); + return ret; + } + + snd_soc_dai_set_drvdata(cpu_dai, micfil); + + return 0; +} + +static struct snd_soc_dai_driver fsl_micfil_dai = { + .probe = fsl_micfil_dai_probe, + .capture = { + .stream_name = "CPU-Capture", + .channels_min = 1, + .channels_max = 8, + .rates = FSL_MICFIL_RATES, + .formats = FSL_MICFIL_FORMATS, + }, + .ops = &fsl_micfil_dai_ops, +}; + +static const struct snd_soc_component_driver fsl_micfil_component = { + .name = "fsl-micfil-dai", + .controls = fsl_micfil_snd_controls, + .num_controls = ARRAY_SIZE(fsl_micfil_snd_controls), + +}; + +/* REGMAP */ +static const struct reg_default fsl_micfil_reg_defaults[] = { + {REG_MICFIL_CTRL1, 0x00000000}, + {REG_MICFIL_CTRL2, 0x00000000}, + {REG_MICFIL_STAT, 0x00000000}, + {REG_MICFIL_FIFO_CTRL, 0x00000007}, + {REG_MICFIL_FIFO_STAT, 0x00000000}, + {REG_MICFIL_DATACH0, 0x00000000}, + {REG_MICFIL_DATACH1, 0x00000000}, + {REG_MICFIL_DATACH2, 0x00000000}, + {REG_MICFIL_DATACH3, 0x00000000}, + {REG_MICFIL_DATACH4, 0x00000000}, + {REG_MICFIL_DATACH5, 0x00000000}, + {REG_MICFIL_DATACH6, 0x00000000}, + {REG_MICFIL_DATACH7, 0x00000000}, + {REG_MICFIL_DC_CTRL, 0x00000000}, + {REG_MICFIL_OUT_CTRL, 0x00000000}, + {REG_MICFIL_OUT_STAT, 0x00000000}, + {REG_MICFIL_VAD0_CTRL1, 0x00000000}, + {REG_MICFIL_VAD0_CTRL2, 0x000A0000}, + {REG_MICFIL_VAD0_STAT, 0x00000000}, + {REG_MICFIL_VAD0_SCONFIG, 0x00000000}, + {REG_MICFIL_VAD0_NCONFIG, 0x80000000}, + {REG_MICFIL_VAD0_NDATA, 0x00000000}, + {REG_MICFIL_VAD0_ZCD, 0x00000004}, +}; + +static bool fsl_micfil_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case REG_MICFIL_CTRL1: + case REG_MICFIL_CTRL2: + case REG_MICFIL_STAT: + case REG_MICFIL_FIFO_CTRL: + case REG_MICFIL_FIFO_STAT: + case REG_MICFIL_DATACH0: + case REG_MICFIL_DATACH1: + case REG_MICFIL_DATACH2: + case REG_MICFIL_DATACH3: + case REG_MICFIL_DATACH4: + case REG_MICFIL_DATACH5: + case REG_MICFIL_DATACH6: + case REG_MICFIL_DATACH7: + case REG_MICFIL_DC_CTRL: + case REG_MICFIL_OUT_CTRL: + case REG_MICFIL_OUT_STAT: + case REG_MICFIL_VAD0_CTRL1: + case REG_MICFIL_VAD0_CTRL2: + case REG_MICFIL_VAD0_STAT: + case REG_MICFIL_VAD0_SCONFIG: + case REG_MICFIL_VAD0_NCONFIG: + case REG_MICFIL_VAD0_NDATA: + case REG_MICFIL_VAD0_ZCD: + return true; + default: + return false; + } +} + +static bool fsl_micfil_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case REG_MICFIL_CTRL1: + case REG_MICFIL_CTRL2: + case REG_MICFIL_STAT: /* Write 1 to Clear */ + case REG_MICFIL_FIFO_CTRL: + case REG_MICFIL_FIFO_STAT: /* Write 1 to Clear */ + case REG_MICFIL_DC_CTRL: + case REG_MICFIL_OUT_CTRL: + case REG_MICFIL_OUT_STAT: /* Write 1 to Clear */ + case REG_MICFIL_VAD0_CTRL1: + case REG_MICFIL_VAD0_CTRL2: + case REG_MICFIL_VAD0_STAT: /* Write 1 to Clear */ + case REG_MICFIL_VAD0_SCONFIG: + case REG_MICFIL_VAD0_NCONFIG: + case REG_MICFIL_VAD0_ZCD: + return true; + default: + return false; + } +} + +static bool fsl_micfil_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case REG_MICFIL_CTRL1: + case REG_MICFIL_CTRL2: + case REG_MICFIL_STAT: + case REG_MICFIL_DATACH0: + case REG_MICFIL_DATACH1: + case REG_MICFIL_DATACH2: + case REG_MICFIL_DATACH3: + case REG_MICFIL_DATACH4: + case REG_MICFIL_DATACH5: + case REG_MICFIL_DATACH6: + case REG_MICFIL_DATACH7: + case REG_MICFIL_VAD0_CTRL1: + case REG_MICFIL_VAD0_CTRL2: + case REG_MICFIL_VAD0_STAT: + case REG_MICFIL_VAD0_SCONFIG: + case REG_MICFIL_VAD0_NCONFIG: + case REG_MICFIL_VAD0_NDATA: + case REG_MICFIL_VAD0_ZCD: + return true; + default: + return false; + } +} + +static const struct regmap_config fsl_micfil_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + + .max_register = REG_MICFIL_VAD0_ZCD, + .reg_defaults = fsl_micfil_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(fsl_micfil_reg_defaults), + .readable_reg = fsl_micfil_readable_reg, + .volatile_reg = fsl_micfil_volatile_reg, + .writeable_reg = fsl_micfil_writeable_reg, + .cache_type = REGCACHE_RBTREE, +}; + +/* END OF REGMAP */ + +static void voice_detected_irq(struct fsl_micfil *micfil) +{ + struct device *dev = &micfil->pdev->dev; + int ret; + + /* disable hwvad */ + ret = disable_hwvad(dev); + if (ret) + dev_err(dev, "Failed to disable HWVAD module\n"); + + /* disable hwvad interrupts */ + ret = configure_hwvad_interrupts(dev, 0); + if (ret) + dev_err(dev, "Failed to disable interrupts\n"); + + /* notify userspace that voice was detected */ + kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp); +} + +static irqreturn_t hwvad_isr(int irq, void *devid) +{ + struct fsl_micfil *micfil = (struct fsl_micfil *)devid; + struct device *dev = &micfil->pdev->dev; + u32 vad0_reg; + int old_flag; + + regmap_read(micfil->regmap, REG_MICFIL_VAD0_STAT, &vad0_reg); + + old_flag = atomic_cmpxchg(&micfil->voice_detected, 0, 1); + if ((vad0_reg & MICFIL_VAD0_STAT_IF_MASK) && !old_flag) { + dev_info(dev, "Detected voice\n"); + + /* Write 1 to clear */ + regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_STAT, + MICFIL_VAD0_STAT_IF_MASK, + MICFIL_VAD0_STAT_IF); + + voice_detected_irq(micfil); + } + + return IRQ_HANDLED; +} + +static irqreturn_t micfil_isr(int irq, void *devid) +{ + struct fsl_micfil *micfil = (struct fsl_micfil *)devid; + struct platform_device *pdev = micfil->pdev; + irqreturn_t ret = IRQ_HANDLED; + u32 stat_reg; + u32 ctrl1_reg; + u32 vad0_reg; + bool dma_enabled; + int i; + + regmap_read(micfil->regmap, REG_MICFIL_STAT, &stat_reg); + regmap_read(micfil->regmap, REG_MICFIL_CTRL1, &ctrl1_reg); + regmap_read(micfil->regmap, REG_MICFIL_VAD0_STAT, &vad0_reg); + dma_enabled = MICFIL_DMA_ENABLED(ctrl1_reg); + + if (vad0_reg & MICFIL_VAD0_STAT_IF_MASK) + ret = IRQ_WAKE_THREAD; + + if (vad0_reg & MICFIL_VAD0_STAT_EF_MASK) + dev_dbg(&pdev->dev, "isr: voice activity event detected\n"); + + if (vad0_reg & MICFIL_VAD0_STAT_INSATF_MASK) + dev_dbg(&pdev->dev, "isr: voice activity input overflow/underflow detected\n"); + + if (vad0_reg & MICFIL_VAD0_STAT_INITF_MASK) + dev_dbg(&pdev->dev, "isr: voice activity dectector is initializing\n"); + + if (stat_reg & MICFIL_STAT_BSY_FIL_MASK) + dev_dbg(&pdev->dev, "isr: Decimation Filter is running\n"); + + if (stat_reg & MICFIL_STAT_FIR_RDY_MASK) + dev_dbg(&pdev->dev, "isr: FIR Filter Data ready\n"); + + if (stat_reg & MICFIL_STAT_LOWFREQF_MASK) { + dev_dbg(&pdev->dev, "isr: ipg_clk_app is too low\n"); + regmap_update_bits(micfil->regmap, REG_MICFIL_STAT, + MICFIL_STAT_LOWFREQF_MASK, 1); + } + + /* Channel 0-7 Output Data Flags */ + for (i = 0; i < MICFIL_OUTPUT_CHANNELS; i++) { + if (stat_reg & MICFIL_STAT_CHXF_MASK(i)) + dev_dbg(&pdev->dev, + "isr: Data available in Data Channel %d\n", i); + /* if DMA is not enabled, field must be written with 1 + * to clear + */ + if (!dma_enabled) + regmap_update_bits(micfil->regmap, + REG_MICFIL_STAT, + MICFIL_STAT_CHXF_MASK(i), + 1); + } + + return ret; +} + +static int fsl_set_clock_params(struct device *, unsigned int); + +static int enable_hwvad(struct device *dev) +{ + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret; + int old_state; + + /* go further with enablement only if both recording and + * hwvad are not on + */ + old_state = atomic_cmpxchg(&micfil->state, + RECORDING_OFF_HWVAD_OFF, + RECORDING_OFF_HWVAD_ON); + if (old_state != RECORDING_OFF_HWVAD_OFF) { + dev_err(dev, "Another record or hwvad is on\n"); + return -EBUSY; + } + + /* This is required because if an arecord was done, + * suspend function will mark regmap as cache only + * and reads/writes in volatile regs will fail + */ + regcache_mark_dirty(micfil->regmap); + regcache_sync(micfil->regmap); + regcache_cache_only(micfil->regmap, false); + + /* clear voice detected flag */ + atomic_set(&micfil->voice_detected, 0); + + ret = fsl_set_clock_params(dev, MICFIL_DEFAULT_RATE); + if (ret) + goto enable_err; + + ret = fsl_micfil_reset(dev); + if (ret) + return ret; + + /* Initialize Hardware Voice Activity */ + ret = init_hwvad(dev); + if (ret) + goto enable_err; + + return 0; + +enable_err: + atomic_cmpxchg(&micfil->state, RECORDING_OFF_HWVAD_ON, RECORDING_OFF_HWVAD_OFF); + return ret; +} + +static int disable_hwvad(struct device *dev) +{ + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret = 0; + int old_state; + + old_state = atomic_read(&micfil->state); + + if (old_state == RECORDING_OFF_HWVAD_ON) { + /* Disable MICFIL module */ + ret |= regmap_update_bits(micfil->regmap, + REG_MICFIL_CTRL1, + MICFIL_CTRL1_PDMIEN_MASK, + 0); + + /* Voice Activity Detector Reset */ + ret |= regmap_update_bits(micfil->regmap, + REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_RST_SHIFT, + MICFIL_VAD0_CTRL1_RST); + + /* Disable HWVAD */ + ret |= regmap_update_bits(micfil->regmap, + REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_EN_MASK, + 0); + + /* Disable Signal Filter */ + ret |= regmap_update_bits(micfil->regmap, + REG_MICFIL_VAD0_SCONFIG, + MICFIL_VAD0_SCONFIG_SFILEN_MASK, + 0); + + /* Signal Maximum Enable */ + ret |= regmap_update_bits(micfil->regmap, + REG_MICFIL_VAD0_SCONFIG, + MICFIL_VAD0_SCONFIG_SMAXEN_MASK, + 0); + + /* Enable pre-filter Noise & Signal */ + ret |= regmap_update_bits(micfil->regmap, + REG_MICFIL_VAD0_CTRL2, + MICFIL_VAD0_CTRL2_PREFEN_MASK, + 0); + + /* Noise Decimation Enable */ + ret |= regmap_update_bits(micfil->regmap, + REG_MICFIL_VAD0_NCONFIG, + MICFIL_VAD0_NCONFIG_NDECEN_MASK, + 0); + + /* disable the clock */ + clk_disable_unprepare(micfil->mclk); + + atomic_set(&micfil->state, RECORDING_OFF_HWVAD_OFF); + } else { + ret = -EPERM; + dev_err(dev, "HWVAD is not enabled %d\n", ret); + } + + return ret; +} + +static ssize_t micfil_hwvad_handler(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, + size_t count) +{ + struct kobject *nand_kobj = kobj->parent; + struct device *dev = container_of(nand_kobj, struct device, kobj); + struct fsl_micfil *micfil = dev_get_drvdata(dev); + unsigned long enabled_channels; + int ret; + + ret = kstrtoul(buf, 16, &enabled_channels); + if (ret < 0) + return -EINVAL; + + if (enabled_channels > 0 && enabled_channels <= 8) { + dev_info(dev, + "enabling hwvad with %lu channels at rate %d\n", + enabled_channels, MICFIL_DEFAULT_RATE); + micfil->channels = enabled_channels; + ret = enable_hwvad(dev); + } else if (!enabled_channels) { + dev_info(dev, "disabling hwvad\n"); + micfil->channels = 0; + ret = disable_hwvad(dev); + } else { + dev_err(dev, "Unsupported number of channels. Try 0,1..8\n"); + ret = -EINVAL; + } + if (ret) + return ret; + + return count; +} + +static struct kobj_attribute hwvad_enable_attribute = __ATTR(enable, 0660, NULL, micfil_hwvad_handler); + +static int fsl_micfil_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + const struct of_device_id *of_id; + struct fsl_micfil *micfil; + struct resource *res; + void __iomem *regs; + int irq, ret; + unsigned long irqflag = 0; + + micfil = devm_kzalloc(&pdev->dev, sizeof(*micfil), GFP_KERNEL); + if (!micfil) + return -ENOMEM; + + micfil->pdev = pdev; + strncpy(micfil->name, np->name, sizeof(micfil->name) - 1); + + of_id = of_match_device(fsl_micfil_dt_ids, &pdev->dev); + if (!of_id || !of_id->data) + return -EINVAL; + + micfil->soc = of_id->data; + + /* ipg_clk is used to control the registers + * ipg_clk_app is used to operate the filter + */ + micfil->mclk = devm_clk_get(&pdev->dev, "ipg_clk_app"); + if (IS_ERR(micfil->mclk)) { + dev_err(&pdev->dev, "failed to get core clock: %ld\n", + PTR_ERR(micfil->mclk)); + return PTR_ERR(micfil->mclk); + } + + /* init regmap */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + micfil->regmap = devm_regmap_init_mmio_clk(&pdev->dev, + "ipg_clk", + regs, + &fsl_micfil_regmap_config); + if (IS_ERR(micfil->regmap)) { + dev_err(&pdev->dev, "failed to init MICFIL regmap: %ld\n", + PTR_ERR(micfil->regmap)); + return PTR_ERR(micfil->regmap); + } + + /* dataline mask for RX */ + ret = of_property_read_u32_index(np, + "fsl,dataline", + 0, + &micfil->dataline); + if (ret) + micfil->dataline = 1; + + if (micfil->dataline & (~micfil->soc->dataline)) { + dev_err(&pdev->dev, "dataline setting error, Mask is 0x%X\n", + micfil->soc->dataline); + return -EINVAL; + } + + /* get IRQs */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "no irq for node %s\n", pdev->name); + return irq; + } + + if (of_property_read_bool(np, "fsl,shared-interrupt")) + irqflag = IRQF_SHARED; + + ret = devm_request_threaded_irq(&pdev->dev, irq, micfil_isr, + hwvad_isr, irqflag, + micfil->name, micfil); + if (ret) { + dev_err(&pdev->dev, "failed to claim irq %u\n", irq); + return ret; + } + + micfil->slave_mode = false; + + micfil->dma_params_rx.chan_name = "rx"; + micfil->dma_params_rx.addr = res->start + REG_MICFIL_DATACH0; + micfil->dma_params_rx.maxburst = MICFIL_DMA_MAXBURST_RX; + + platform_set_drvdata(pdev, micfil); + + pm_runtime_enable(&pdev->dev); + + ret = devm_snd_soc_register_component(&pdev->dev, &fsl_micfil_component, + &fsl_micfil_dai, 1); + if (ret) { + dev_err(&pdev->dev, "failed to register component %s\n", + fsl_micfil_component.name); + return ret; + } + + if (micfil->soc->imx) + ret = imx_pcm_platform_register(&pdev->dev); + else + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + + if (ret) { + dev_err(&pdev->dev, "failed to pcm register\n"); + return ret; + } + + /* create sysfs entry used to enable hwvad from userspace */ + micfil->hwvad_kobject = kobject_create_and_add("hwvad", + &pdev->dev.kobj); + if (!micfil->hwvad_kobject) + return -ENOMEM; + + ret = sysfs_create_file(micfil->hwvad_kobject, + &hwvad_enable_attribute.attr); + if (ret) { + dev_err(&pdev->dev, "failed to create file for hwvad_enable\n"); + kobject_put(micfil->hwvad_kobject); + return -ENOMEM; + } + + return 0; +} + +static int fsl_micfil_remove(struct platform_device *pdev) +{ + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int __maybe_unused fsl_micfil_runtime_suspend(struct device *dev) +{ + struct fsl_micfil *micfil = dev_get_drvdata(dev); + + regcache_cache_only(micfil->regmap, true); + + if (micfil->mclk_streams & BIT(SNDRV_PCM_STREAM_CAPTURE)) + clk_disable_unprepare(micfil->mclk); + + return 0; +} + +static int __maybe_unused fsl_micfil_runtime_resume(struct device *dev) +{ + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret; + + /* enable mclk */ + if (micfil->mclk_streams & BIT(SNDRV_PCM_STREAM_CAPTURE)) { + ret = clk_prepare_enable(micfil->mclk); + if (ret < 0) { + dev_err(dev, "failed to enable mclk"); + return ret; + } + } + + regcache_cache_only(micfil->regmap, false); + regcache_mark_dirty(micfil->regmap); + regcache_sync(micfil->regmap); + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops fsl_micfil_pm_ops = { + SET_RUNTIME_PM_OPS(fsl_micfil_runtime_suspend, + fsl_micfil_runtime_resume, + NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) +}; + +static struct platform_driver fsl_micfil_driver = { + .probe = fsl_micfil_probe, + .remove = fsl_micfil_remove, + .driver = { + .name = "fsl-micfil-dai", + .pm = &fsl_micfil_pm_ops, + .of_match_table = fsl_micfil_dt_ids, + }, +}; +module_platform_driver(fsl_micfil_driver); + +MODULE_AUTHOR("Cosmin-Gabriel Samoila <cosmin.samoila@nxp.com>"); +MODULE_DESCRIPTION("NXP PDM Microphone Interface (MICFIL) driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/fsl_micfil.h b/sound/soc/fsl/fsl_micfil.h new file mode 100644 index 000000000000..50a06b5f40d5 --- /dev/null +++ b/sound/soc/fsl/fsl_micfil.h @@ -0,0 +1,304 @@ +/* + * fsl_micfil.h - PDM Microphone Interface for the NXP i.MX SoC + * + * Copyright (C) 2018 NXP + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#ifndef _FSL_MICFIL_H +#define _FSL_MICFIL_H + +/* MICFIL Register Map */ +#define REG_MICFIL_CTRL1 0x00 +#define REG_MICFIL_CTRL2 0x04 +#define REG_MICFIL_STAT 0x08 +#define REG_MICFIL_FIFO_CTRL 0x10 +#define REG_MICFIL_FIFO_STAT 0x14 +#define REG_MICFIL_DATACH0 0x24 +#define REG_MICFIL_DATACH1 0x28 +#define REG_MICFIL_DATACH2 0x2C +#define REG_MICFIL_DATACH3 0x30 +#define REG_MICFIL_DATACH4 0x34 +#define REG_MICFIL_DATACH5 0x38 +#define REG_MICFIL_DATACH6 0x3C +#define REG_MICFIL_DATACH7 0x40 +#define REG_MICFIL_DC_CTRL 0x64 +#define REG_MICFIL_OUT_CTRL 0x74 +#define REG_MICFIL_OUT_STAT 0x7C +#define REG_MICFIL_VAD0_CTRL1 0x90 +#define REG_MICFIL_VAD0_CTRL2 0x94 +#define REG_MICFIL_VAD0_STAT 0x98 +#define REG_MICFIL_VAD0_SCONFIG 0x9C +#define REG_MICFIL_VAD0_NCONFIG 0xA0 +#define REG_MICFIL_VAD0_NDATA 0xA4 +#define REG_MICFIL_VAD0_ZCD 0xA8 + +/* MICFIL Control Register 1 -- REG_MICFILL_CTRL1 0x00 */ +#define MICFIL_CTRL1_MDIS_SHIFT 31 +#define MICFIL_CTRL1_MDIS_MASK BIT(MICFIL_CTRL1_MDIS_SHIFT) +#define MICFIL_CTRL1_MDIS BIT(MICFIL_CTRL1_MDIS_SHIFT) +#define MICFIL_CTRL1_DOZEN_SHIFT 30 +#define MICFIL_CTRL1_DOZEN_MASK BIT(MICFIL_CTRL1_DOZEN_SHIFT) +#define MICFIL_CTRL1_DOZEN BIT(MICFIL_CTRL1_DOZEN_SHIFT) +#define MICFIL_CTRL1_PDMIEN_SHIFT 29 +#define MICFIL_CTRL1_PDMIEN_MASK BIT(MICFIL_CTRL1_PDMIEN_SHIFT) +#define MICFIL_CTRL1_PDMIEN BIT(MICFIL_CTRL1_PDMIEN_SHIFT) +#define MICFIL_CTRL1_DBG_SHIFT 28 +#define MICFIL_CTRL1_DBG_MASK BIT(MICFIL_CTRL1_DBG_SHIFT) +#define MICFIL_CTRL1_DBG BIT(MICFIL_CTRL1_DBG_SHIFT) +#define MICFIL_CTRL1_SRES_SHIFT 27 +#define MICFIL_CTRL1_SRES_MASK BIT(MICFIL_CTRL1_SRES_SHIFT) +#define MICFIL_CTRL1_SRES BIT(MICFIL_CTRL1_SRES_SHIFT) +#define MICFIL_CTRL1_DBGE_SHIFT 26 +#define MICFIL_CTRL1_DBGE_MASK BIT(MICFIL_CTRL1_DBGE_SHIFT) +#define MICFIL_CTRL1_DBGE BIT(MICFIL_CTRL1_DBGE_SHIFT) +#define MICFIL_CTRL1_DISEL_SHIFT 24 +#define MICFIL_CTRL1_DISEL_WIDTH 2 +#define MICFIL_CTRL1_DISEL_MASK ((BIT(MICFIL_CTRL1_DISEL_WIDTH) - 1) \ + << MICFIL_CTRL1_DISEL_SHIFT) +#define MICFIL_CTRL1_DISEL(v) (((v) << MICFIL_CTRL1_DISEL_SHIFT) \ + & MICFIL_CTRL1_DISEL_MASK) +#define MICFIL_CTRL1_ERREN_SHIFT 23 +#define MICFIL_CTRL1_ERREN_MASK BIT(MICFIL_CTRL1_ERREN_SHIFT) +#define MICFIL_CTRL1_ERREN BIT(MICFIL_CTRL1_ERREN_SHIFT) +#define MICFIL_CTRL1_CHEN_SHIFT 0 +#define MICFIL_CTRL1_CHEN_WIDTH 8 +#define MICFIL_CTRL1_CHEN_MASK(x) (BIT(x) << MICFIL_CTRL1_CHEN_SHIFT) +#define MICFIL_CTRL1_CHEN(x) (MICFIL_CTRL1_CHEN_MASK(x)) + +/* MICFIL Control Register 2 -- REG_MICFILL_CTRL2 0x04 */ +#define MICFIL_CTRL2_QSEL_SHIFT 25 +#define MICFIL_CTRL2_QSEL_WIDTH 3 +#define MICFIL_CTRL2_QSEL_MASK ((BIT(MICFIL_CTRL2_QSEL_WIDTH) - 1) \ + << MICFIL_CTRL2_QSEL_SHIFT) +#define MICFIL_HIGH_QUALITY BIT(MICFIL_CTRL2_QSEL_SHIFT) +#define MICFIL_MEDIUM_QUALITY (0 << MICFIL_CTRL2_QSEL_SHIFT) +#define MICFIL_LOW_QUALITY (7 << MICFIL_CTRL2_QSEL_SHIFT) +#define MICFIL_VLOW0_QUALITY (6 << MICFIL_CTRL2_QSEL_SHIFT) +#define MICFIL_VLOW1_QUALITY (5 << MICFIL_CTRL2_QSEL_SHIFT) +#define MICFIL_VLOW2_QUALITY (4 << MICFIL_CTRL2_QSEL_SHIFT) + +#define MICFIL_CTRL2_CICOSR_SHIFT 16 +#define MICFIL_CTRL2_CICOSR_WIDTH 4 +#define MICFIL_CTRL2_CICOSR_MASK ((BIT(MICFIL_CTRL2_CICOSR_WIDTH) - 1) \ + << MICFIL_CTRL2_CICOSR_SHIFT) +#define MICFIL_CTRL2_CICOSR(v) (((v) << MICFIL_CTRL2_CICOSR_SHIFT) \ + & MICFIL_CTRL2_CICOSR_MASK) +#define MICFIL_CTRL2_CLKDIV_SHIFT 0 +#define MICFIL_CTRL2_CLKDIV_WIDTH 8 +#define MICFIL_CTRL2_CLKDIV_MASK ((BIT(MICFIL_CTRL2_CLKDIV_WIDTH) - 1) \ + << MICFIL_CTRL2_CLKDIV_SHIFT) +#define MICFIL_CTRL2_CLKDIV(v) (((v) << MICFIL_CTRL2_CLKDIV_SHIFT) \ + & MICFIL_CTRL2_CLKDIV_MASK) + +/* MICFIL Status Register -- REG_MICFIL_STAT 0x08 */ +#define MICFIL_STAT_BSY_FIL_SHIFT 31 +#define MICFIL_STAT_BSY_FIL_MASK BIT(MICFIL_STAT_BSY_FIL_SHIFT) +#define MICFIL_STAT_BSY_FIL BIT(MICFIL_STAT_BSY_FIL_SHIFT) +#define MICFIL_STAT_FIR_RDY_SHIFT 30 +#define MICFIL_STAT_FIR_RDY_MASK BIT(MICFIL_STAT_FIR_RDY_SHIFT) +#define MICFIL_STAT_FIR_RDY BIT(MICFIL_STAT_FIR_RDY_SHIFT) +#define MICFIL_STAT_LOWFREQF_SHIFT 29 +#define MICFIL_STAT_LOWFREQF_MASK BIT(MICFIL_STAT_LOWFREQF_SHIFT) +#define MICFIL_STAT_LOWFREQF BIT(MICFIL_STAT_LOWFREQF_SHIFT) +#define MICFIL_STAT_CHXF_SHIFT(v) (v) +#define MICFIL_STAT_CHXF_MASK(v) BIT(MICFIL_STAT_CHXF_SHIFT(v)) +#define MICFIL_STAT_CHXF(v) BIT(MICFIL_STAT_CHXF_SHIFT(v)) + +/* MICFIL FIFO Control Register -- REG_MICFIL_FIFO_CTRL 0x10 */ +#define MICFIL_FIFO_CTRL_FIFOWMK_SHIFT 0 +#define MICFIL_FIFO_CTRL_FIFOWMK_WIDTH 3 +#define MICFIL_FIFO_CTRL_FIFOWMK_MASK ((BIT(MICFIL_FIFO_CTRL_FIFOWMK_WIDTH) - 1) \ + << MICFIL_FIFO_CTRL_FIFOWMK_SHIFT) +#define MICFIL_FIFO_CTRL_FIFOWMK(v) (((v) << MICFIL_FIFO_CTRL_FIFOWMK_SHIFT) \ + & MICFIL_FIFO_CTRL_FIFOWMK_MASK) + +/* MICFIL HWVAD0 Control 1 Register -- REG_MICFIL_VAD0_CTRL1*/ +#define MICFIL_VAD0_CTRL1_CHSEL_SHIFT 24 +#define MICFIL_VAD0_CTRL1_CHSEL_WIDTH 3 +#define MICFIL_VAD0_CTRL1_CHSEL_MASK ((BIT(MICFIL_VAD0_CTRL1_CHSEL_WIDTH) - 1) \ + << MICFIL_VAD0_CTRL1_CHSEL_SHIFT) +#define MICFIL_VAD0_CTRL1_CHSEL(v) (((v) << MICFIL_VAD0_CTRL1_CHSEL_SHIFT) \ + & MICFIL_VAD0_CTRL1_CHSEL_MASK) +#define MICFIL_VAD0_CTRL1_CICOSR_SHIFT 16 +#define MICFIL_VAD0_CTRL1_CICOSR_WIDTH 4 +#define MICFIL_VAD0_CTRL1_CICOSR_MASK ((BIT(MICFIL_VAD0_CTRL1_CICOSR_WIDTH) - 1) \ + << MICFIL_VAD0_CTRL1_CICOSR_SHIFT) +#define MICFIL_VAD0_CTRL1_CICOSR(v) (((v) << MICFIL_VAD0_CTRL1_CICOSR_SHIFT) \ + & MICFIL_VAD0_CTRL1_CICOSR_MASK) +#define MICFIL_VAD0_CTRL1_INITT_SHIFT 8 +#define MICFIL_VAD0_CTRL1_INITT_WIDTH 5 +#define MICFIL_VAD0_CTRL1_INITT_MASK ((BIT(MICFIL_VAD0_CTRL1_INITT_WIDTH) - 1) \ + << MICFIL_VAD0_CTRL1_INITT_SHIFT) +#define MICFIL_VAD0_CTRL1_INITT(v) (((v) << MICFIL_VAD0_CTRL1_INITT_SHIFT) \ + & MICFIL_VAD0_CTRL1_INITT_MASK) +#define MICFIL_VAD0_CTRL1_ST10_SHIFT 4 +#define MICFIL_VAD0_CTRL1_ST10_MASK BIT(MICFIL_VAD0_CTRL1_ST10_SHIFT) +#define MICFIL_VAD0_CTRL1_ST10 BIT(MICFIL_VAD0_CTRL1_ST10_SHIFT) +#define MICFIL_VAD0_CTRL1_ERIE_SHIFT 3 +#define MICFIL_VAD0_CTRL1_ERIE_MASK BIT(MICFIL_VAD0_CTRL1_ERIE_SHIFT) +#define MICFIL_VAD0_CTRL1_ERIE BIT(MICFIL_VAD0_CTRL1_ERIE_SHIFT) +#define MICFIL_VAD0_CTRL1_IE_SHIFT 2 +#define MICFIL_VAD0_CTRL1_IE_MASK BIT(MICFIL_VAD0_CTRL1_IE_SHIFT) +#define MICFIL_VAD0_CTRL1_IE BIT(MICFIL_VAD0_CTRL1_IE_SHIFT) +#define MICFIL_VAD0_CTRL1_RST_SHIFT 1 +#define MICFIL_VAD0_CTRL1_RST BIT(MICFIL_VAD0_CTRL1_RST_SHIFT) +#define MICFIL_VAD0_CTRL1_EN_SHIFT 0 +#define MICFIL_VAD0_CTRL1_EN_MASK BIT(MICFIL_VAD0_CTRL1_EN_SHIFT) +#define MICFIL_VAD0_CTRL1_EN BIT(MICFIL_VAD0_CTRL1_EN_SHIFT) + +/* MICFIL HWVAD0 Control 2 Register -- REG_MICFIL_VAD0_CTRL2*/ +#define MICFIL_VAD0_CTRL2_FRENDIS_SHIFT 31 +#define MICFIL_VAD0_CTRL2_FRENDIS_MASK BIT(MICFIL_VAD0_CTRL2_FRENDIS_SHIFT) +#define MICFIL_VAD0_CTRL2_FRENDIS BIT(MICFIL_VAD0_CTRL2_FRENDIS_SHIFT) +#define MICFIL_VAD0_CTRL2_PREFEN_SHIFT 30 +#define MICFIL_VAD0_CTRL2_PREFEN_MASK BIT(MICFIL_VAD0_CTRL2_PREFEN_SHIFT) +#define MICFIL_VAD0_CTRL2_PREFEN BIT(MICFIL_VAD0_CTRL2_PREFEN_SHIFT) +#define MICFIL_VAD0_CTRL2_FOUTDIS_SHIFT 28 +#define MICFIL_VAD0_CTRL2_FOUTDIS_MASK BIT(MICFIL_VAD0_CTRL2_FOUTDIS_SHIFT) +#define MICFIL_VAD0_CTRL2_FOUTDIS BIT(MICFIL_VAD0_CTRL2_FOUTDIS_SHIFT) +#define MICFIL_VAD0_CTRL2_FRAMET_SHIFT 16 +#define MICFIL_VAD0_CTRL2_FRAMET_WIDTH 6 +#define MICFIL_VAD0_CTRL2_FRAMET_MASK ((BIT(MICFIL_VAD0_CTRL2_FRAMET_WIDTH) - 1) \ + << MICFIL_VAD0_CTRL2_FRAMET_SHIFT) +#define MICFIL_VAD0_CTRL2_FRAMET(v) (((v) << MICFIL_VAD0_CTRL2_FRAMET_SHIFT) \ + & MICFIL_VAD0_CTRL2_FRAMET_MASK) +#define MICFIL_VAD0_CTRL2_INPGAIN_SHIFT 8 +#define MICFIL_VAD0_CTRL2_INPGAIN_WIDTH 4 +#define MICFIL_VAD0_CTRL2_INPGAIN_MASK ((BIT(MICFIL_VAD0_CTRL2_INPGAIN_WIDTH) - 1) \ + << MICFIL_VAD0_CTRL2_INPGAIN_SHIFT) +#define MICFIL_VAD0_CTRL2_INPGAIN(v) (((v) << MICFIL_VAD0_CTRL2_INPGAIN_SHIFT) \ + & MICFIL_VAD0_CTRL2_INPGAIN_MASK) +#define MICFIL_VAD0_CTRL2_HPF_SHIFT 0 +#define MICFIL_VAD0_CTRL2_HPF_WIDTH 2 +#define MICFIL_VAD0_CTRL2_HPF_MASK ((BIT(MICFIL_VAD0_CTRL2_HPF_WIDTH) - 1) \ + << MICFIL_VAD0_CTRL2_HPF_SHIFT) +#define MICFIL_VAD0_CTRL2_HPF(v) (((v) << MICFIL_VAD0_CTRL2_HPF_SHIFT) \ + & MICFIL_VAD0_CTRL2_HPF_MASK) + +/* MICFIL HWVAD0 Signal CONFIG Register -- REG_MICFIL_VAD0_SCONFIG */ +#define MICFIL_VAD0_SCONFIG_SFILEN_SHIFT 31 +#define MICFIL_VAD0_SCONFIG_SFILEN_MASK BIT(MICFIL_VAD0_SCONFIG_SFILEN_SHIFT) +#define MICFIL_VAD0_SCONFIG_SFILEN BIT(MICFIL_VAD0_SCONFIG_SFILEN_SHIFT) +#define MICFIL_VAD0_SCONFIG_SMAXEN_SHIFT 30 +#define MICFIL_VAD0_SCONFIG_SMAXEN_MASK BIT(MICFIL_VAD0_SCONFIG_SMAXEN_SHIFT) +#define MICFIL_VAD0_SCONFIG_SMAXEN BIT(MICFIL_VAD0_SCONFIG_SMAXEN_SHIFT) +#define MICFIL_VAD0_SCONFIG_SGAIN_SHIFT 0 +#define MICFIL_VAD0_SCONFIG_SGAIN_WIDTH 4 +#define MICFIL_VAD0_SCONFIG_SGAIN_MASK ((BIT(MICFIL_VAD0_SCONFIG_SGAIN_WIDTH) - 1) \ + << MICFIL_VAD0_SCONFIG_SGAIN_SHIFT) +#define MICFIL_VAD0_SCONFIG_SGAIN(v) (((v) << MICFIL_VAD0_SCONFIG_SGAIN_SHIFT) \ + & MICFIL_VAD0_SCONFIG_SGAIN_MASK) + +/* MICFIL HWVAD0 Noise CONFIG Register -- REG_MICFIL_VAD0_NCONFIG */ +#define MICFIL_VAD0_NCONFIG_NFILAUT_SHIFT 31 +#define MICFIL_VAD0_NCONFIG_NFILAUT_MASK BIT(MICFIL_VAD0_NCONFIG_NFILAUT_SHIFT) +#define MICFIL_VAD0_NCONFIG_NFILAUT BIT(MICFIL_VAD0_NCONFIG_NFILAUT_SHIFT) +#define MICFIL_VAD0_NCONFIG_NMINEN_SHIFT 30 +#define MICFIL_VAD0_NCONFIG_NMINEN_MASK BIT(MICFIL_VAD0_NCONFIG_NMINEN_SHIFT) +#define MICFIL_VAD0_NCONFIG_NMINEN BIT(MICFIL_VAD0_NCONFIG_NMINEN_SHIFT) +#define MICFIL_VAD0_NCONFIG_NDECEN_SHIFT 29 +#define MICFIL_VAD0_NCONFIG_NDECEN_MASK BIT(MICFIL_VAD0_NCONFIG_NDECEN_SHIFT) +#define MICFIL_VAD0_NCONFIG_NDECEN BIT(MICFIL_VAD0_NCONFIG_NDECEN_SHIFT) +#define MICFIL_VAD0_NCONFIG_NOREN_SHIFT 28 +#define MICFIL_VAD0_NCONFIG_NOREN BIT(MICFIL_VAD0_NCONFIG_NOREN_SHIFT) +#define MICFIL_VAD0_NCONFIG_NFILADJ_SHIFT 8 +#define MICFIL_VAD0_NCONFIG_NFILADJ_WIDTH 5 +#define MICFIL_VAD0_NCONFIG_NFILADJ_MASK ((BIT(MICFIL_VAD0_NCONFIG_NFILADJ_WIDTH) - 1) \ + << MICFIL_VAD0_NCONFIG_NFILADJ_SHIFT) +#define MICFIL_VAD0_NCONFIG_NFILADJ(v) (((v) << MICFIL_VAD0_NCONFIG_NFILADJ_SHIFT) \ + & MICFIL_VAD0_NCONFIG_NFILADJ_MASK) +#define MICFIL_VAD0_NCONFIG_NGAIN_SHIFT 0 +#define MICFIL_VAD0_NCONFIG_NGAIN_WIDTH 4 +#define MICFIL_VAD0_NCONFIG_NGAIN_MASK ((BIT(MICFIL_VAD0_NCONFIG_NGAIN_WIDTH) - 1) \ + << MICFIL_VAD0_NCONFIG_NGAIN_SHIFT) +#define MICFIL_VAD0_NCONFIG_NGAIN(v) (((v) << MICFIL_VAD0_NCONFIG_NGAIN_SHIFT) \ + & MICFIL_VAD0_NCONFIG_NGAIN_MASK) + +/* MICFIL HWVAD0 Zero-Crossing Detector - REG_MICFIL_VAD0_ZCD */ +#define MICFIL_VAD0_ZCD_ZCDTH_SHIFT 16 +#define MICFIL_VAD0_ZCD_ZCDTH_WIDTH 10 +#define MICFIL_VAD0_ZCD_ZCDTH_MASK ((BIT(MICFIL_VAD0_ZCD_ZCDTH_WIDTH) - 1) \ + << MICFIL_VAD0_ZCD_ZCDTH_SHIFT) +#define MICFIL_VAD0_ZCD_ZCDTH(v) (((v) << MICFIL_VAD0_ZCD_ZCDTH_SHIFT)\ + & MICFIL_VAD0_ZCD_ZCDTH_MASK) +#define MICFIL_VAD0_ZCD_ZCDADJ_SHIFT 8 +#define MICFIL_VAD0_ZCD_ZCDADJ_WIDTH 4 +#define MICFIL_VAD0_ZCD_ZCDADJ_MASK ((BIT(MICFIL_VAD0_ZCD_ZCDADJ_WIDTH) - 1)\ + << MICFIL_VAD0_ZCD_ZCDADJ_SHIFT) +#define MICFIL_VAD0_ZCD_ZCDADJ(v) (((v) << MICFIL_VAD0_ZCD_ZCDADJ_SHIFT)\ + & MICFIL_VAD0_ZCD_ZCDADJ_MASK) +#define MICFIL_VAD0_ZCD_ZCDAND_SHIFT 4 +#define MICFIL_VAD0_ZCD_ZCDAND_MASK BIT(MICFIL_VAD0_ZCD_ZCDAND_SHIFT) +#define MICFIL_VAD0_ZCD_ZCDAND BIT(MICFIL_VAD0_ZCD_ZCDAND_SHIFT) +#define MICFIL_VAD0_ZCD_ZCDAUT_SHIFT 2 +#define MICFIL_VAD0_ZCD_ZCDAUT_MASK BIT(MICFIL_VAD0_ZCD_ZCDAUT_SHIFT) +#define MICFIL_VAD0_ZCD_ZCDAUT BIT(MICFIL_VAD0_ZCD_ZCDAUT_SHIFT) +#define MICFIL_VAD0_ZCD_ZCDEN_SHIFT 0 +#define MICFIL_VAD0_ZCD_ZCDEN_MASK BIT(MICFIL_VAD0_ZCD_ZCDEN_SHIFT) +#define MICFIL_VAD0_ZCD_ZCDEN BIT(MICFIL_VAD0_ZCD_ZCDEN_SHIFT) + +/* MICFIL HWVAD0 Status Register - REG_MICFIL_VAD0_STAT */ +#define MICFIL_VAD0_STAT_INITF_SHIFT 31 +#define MICFIL_VAD0_STAT_INITF_MASK BIT(MICFIL_VAD0_STAT_INITF_SHIFT) +#define MICFIL_VAD0_STAT_INITF BIT(MICFIL_VAD0_STAT_INITF_SHIFT) +#define MICFIL_VAD0_STAT_INSATF_SHIFT 16 +#define MICFIL_VAD0_STAT_INSATF_MASK BIT(MICFIL_VAD0_STAT_INSATF_SHIFT) +#define MICFIL_VAD0_STAT_INSATF BIT(MICFIL_VAD0_STAT_INSATF_SHIFT) +#define MICFIL_VAD0_STAT_EF_SHIFT 15 +#define MICFIL_VAD0_STAT_EF_MASK BIT(MICFIL_VAD0_STAT_EF_SHIFT) +#define MICFIL_VAD0_STAT_EF BIT(MICFIL_VAD0_STAT_EF_SHIFT) +#define MICFIL_VAD0_STAT_IF_SHIFT 0 +#define MICFIL_VAD0_STAT_IF_MASK BIT(MICFIL_VAD0_STAT_IF_SHIFT) +#define MICFIL_VAD0_STAT_IF BIT(MICFIL_VAD0_STAT_IF_SHIFT) + +/* HWVAD Constants */ +#define MICFIL_HWVAD_ENVELOPE_MODE 0 +#define MICFIL_HWVAD_ENERGY_MODE 1 +#define MICFIL_HWVAD_INIT_FRAMES 10 +#define MICFIL_HWVAD_INPGAIN 0 +#define MICFIL_HWVAD_SGAIN 6 +#define MICFIL_HWVAD_NGAIN 3 +#define MICFIL_HWVAD_NFILADJ 0 +#define MICFIL_HWVAD_ZCDADJ (1 << (MICFIL_VAD0_ZCD_ZCDADJ_WIDTH - 2)) +#define MICFIL_HWVAD_ZCDTH 10 /* initial threshold value */ +#define MICFIL_HWVAD_ZCDOR 0 +#define MICFIL_HWVAD_ZCDAND 1 +#define MICFIL_HWVAD_ZCD_MANUAL 0 +#define MICFIL_HWVAD_ZCD_AUTO 1 +#define MICFIL_HWVAD_HPF_BYPASS 0 +#define MICFIL_HWVAD_HPF_1750HZ 1 +#define MICFIL_HWVAD_HPF_215HZ 2 +#define MICFIL_HWVAD_HPF_102HZ 3 +#define MICFIL_HWVAD_FRAMET_DEFAULT 10 + +/* MICFIL Output Control Register */ +#define MICFIL_OUTGAIN_CHX_SHIFT(v) (4 * (v)) + +/* Constants */ +#define MICFIL_DMA_IRQ_DISABLED(v) ((v) & MICFIL_CTRL1_DISEL_MASK) +#define MICFIL_DMA_ENABLED(v) ((0x1 << MICFIL_CTRL1_DISEL_SHIFT) \ + == ((v) & MICFIL_CTRL1_DISEL_MASK)) +#define MICFIL_IRQ_ENABLED(v) ((0x2 << MICFIL_CTRL1_DISEL_SHIFT) \ + == ((v) & MICFIL_CTRL1_DISEL_MASK)) +#define MICFIL_OUTPUT_CHANNELS 8 + +#define FIFO_PTRWID 3 +#define FIFO_LEN BIT(FIFO_PTRWID) + +#define MICFIL_MAX_RETRY 25 +#define MICFIL_SLEEP_MIN 90000 /* in us */ +#define MICFIL_SLEEP_MAX 100000 /* in us */ +#define MICFIL_DMA_MAXBURST_RX 6 +#define MICFIL_CTRL2_OSR_DEFAULT (0 << MICFIL_CTRL2_CICOSR_SHIFT) +#define MICFIL_DEFAULT_RATE 48000 + +/* States of micfil */ +#define RECORDING_OFF_HWVAD_OFF 0 +#define RECORDING_OFF_HWVAD_ON 1 +#define RECORDING_ON_HWVAD_OFF 2 + +#endif /* _FSL_MICFIL_H */ diff --git a/sound/soc/fsl/fsl_rpmsg_i2s.c b/sound/soc/fsl/fsl_rpmsg_i2s.c new file mode 100644 index 000000000000..b7ace88f2d93 --- /dev/null +++ b/sound/soc/fsl/fsl_rpmsg_i2s.c @@ -0,0 +1,320 @@ +/* + * Freescale ALSA SoC rpmsg i2s driver. + * + * Copyright 2017 NXP + * + * This program is free software, you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 2 of the License, or(at your + * option) any later version. + * + */ +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/dmaengine.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of_address.h> +#include <linux/pm_runtime.h> +#include <linux/rpmsg.h> +#include <linux/slab.h> + +#include <sound/core.h> +#include <sound/dmaengine_pcm.h> +#include <sound/pcm_params.h> + +#include "fsl_rpmsg_i2s.h" +#include "imx-pcm.h" + +#define FSL_RPMSG_I2S_RATES (SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|\ + SNDRV_PCM_RATE_48000) +#define FSL_RPMSG_I2S_FORMATS SNDRV_PCM_FMTBIT_S16_LE + +static int i2s_send_message(struct i2s_rpmsg *msg, + struct i2s_info *info) +{ + int err = 0; + + mutex_lock(&info->tx_lock); + if (!info->rpdev) { + dev_err(info->dev, "rpmsg channel not ready, m4 image ready?\n"); + mutex_unlock(&info->tx_lock); + return -EINVAL; + } + + dev_dbg(&info->rpdev->dev, "send cmd %d\n", msg->send_msg.header.cmd); + + if (!(msg->send_msg.header.type == I2S_TYPE_C)) + reinit_completion(&info->cmd_complete); + + err = rpmsg_send(info->rpdev->ept, (void *)&msg->send_msg, + sizeof(struct i2s_rpmsg_s)); + if (err) { + dev_err(&info->rpdev->dev, "rpmsg_send failed: %d\n", err); + mutex_unlock(&info->tx_lock); + return err; + } + + if (!(msg->send_msg.header.type == I2S_TYPE_C)) { + /* wait response from rpmsg */ + err = wait_for_completion_timeout(&info->cmd_complete, + msecs_to_jiffies(RPMSG_TIMEOUT)); + if (!err) { + dev_err(&info->rpdev->dev, + "rpmsg_send cmd %d timeout!\n", + msg->send_msg.header.cmd); + mutex_unlock(&info->tx_lock); + return -ETIMEDOUT; + } + memcpy(&msg->recv_msg, &info->recv_msg, + sizeof(struct i2s_rpmsg_r)); + memcpy(&info->rpmsg[msg->recv_msg.header.cmd].recv_msg, + &msg->recv_msg, sizeof(struct i2s_rpmsg_r)); + } + + dev_dbg(&info->rpdev->dev, "cmd:%d, resp %d\n", + msg->send_msg.header.cmd, + info->recv_msg.param.resp); + mutex_unlock(&info->tx_lock); + + return 0; +} + +static struct snd_soc_dai_driver fsl_rpmsg_i2s_dai = { + .playback = { + .stream_name = "CPU-Playback", + .channels_min = 2, + .channels_max = 2, + .rates = FSL_RPMSG_I2S_RATES, + .formats = FSL_RPMSG_I2S_FORMATS, + }, + .capture = { + .stream_name = "CPU-Capture", + .channels_min = 2, + .channels_max = 2, + .rates = FSL_RPMSG_I2S_RATES, + .formats = FSL_RPMSG_I2S_FORMATS, + }, + .symmetric_rates = 1, + .symmetric_channels = 1, + .symmetric_samplebits = 1, +}; + +static const struct snd_soc_component_driver fsl_component = { + .name = "fsl-rpmsg-i2s", +}; + +static const struct of_device_id fsl_rpmsg_i2s_ids[] = { + { .compatible = "fsl,imx7ulp-rpmsg-i2s"}, + { .compatible = "fsl,imx8mq-rpmsg-i2s"}, + { .compatible = "fsl,imx8qxp-rpmsg-i2s"}, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, fsl_rpmsg_i2s_ids); + +static void rpmsg_i2s_work(struct work_struct *work) +{ + struct work_of_rpmsg *work_of_rpmsg; + struct i2s_info *i2s_info; + + work_of_rpmsg = container_of(work, struct work_of_rpmsg, work); + i2s_info = work_of_rpmsg->i2s_info; + + i2s_send_message(&work_of_rpmsg->msg, i2s_info); +} + +static int fsl_rpmsg_i2s_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct fsl_rpmsg_i2s *rpmsg_i2s; + struct i2s_info *i2s_info; + int audioindex = 0; + int ret; + int i; + + rpmsg_i2s = devm_kzalloc(&pdev->dev, sizeof(struct fsl_rpmsg_i2s), + GFP_KERNEL); + if (!rpmsg_i2s) + return -ENOMEM; + + rpmsg_i2s->pdev = pdev; + i2s_info = &rpmsg_i2s->i2s_info; + + ret = of_property_read_u32(np, "fsl,audioindex", &audioindex); + if (ret) + audioindex = 0; + + /* Setup work queue */ + i2s_info->rpmsg_wq = create_singlethread_workqueue("rpmsg_i2s"); + if (i2s_info->rpmsg_wq == NULL) { + dev_err(&pdev->dev, "workqueue create failed\n"); + return -ENOMEM; + } + + i2s_info->send_message = i2s_send_message; + + for (i = 0; i < WORK_MAX_NUM; i++) { + INIT_WORK(&i2s_info->work_list[i].work, rpmsg_i2s_work); + i2s_info->work_list[i].i2s_info = i2s_info; + } + + for (i = 0; i < I2S_CMD_MAX_NUM ; i++) { + i2s_info->rpmsg[i].send_msg.header.cate = IMX_RPMSG_AUDIO; + i2s_info->rpmsg[i].send_msg.header.major = IMX_RMPSG_MAJOR; + i2s_info->rpmsg[i].send_msg.header.minor = IMX_RMPSG_MINOR; + i2s_info->rpmsg[i].send_msg.header.type = I2S_TYPE_A; + i2s_info->rpmsg[i].send_msg.param.audioindex = audioindex; + } + + mutex_init(&i2s_info->tx_lock); + mutex_init(&i2s_info->i2c_lock); + + if (of_device_is_compatible(pdev->dev.of_node, + "fsl,imx7ulp-rpmsg-i2s")) { + rpmsg_i2s->codec_wm8960 = 1; + rpmsg_i2s->version = 1; + rpmsg_i2s->rates = SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_48000; + rpmsg_i2s->formats = SNDRV_PCM_FMTBIT_S16_LE; + fsl_rpmsg_i2s_dai.playback.rates = rpmsg_i2s->rates; + fsl_rpmsg_i2s_dai.playback.formats = rpmsg_i2s->formats; + fsl_rpmsg_i2s_dai.capture.rates = rpmsg_i2s->rates; + fsl_rpmsg_i2s_dai.capture.formats = rpmsg_i2s->formats; + } + + if (of_device_is_compatible(pdev->dev.of_node, + "fsl,imx8qxp-rpmsg-i2s")) { + rpmsg_i2s->codec_wm8960 = 1 + (1 << 16); + rpmsg_i2s->version = 1; + rpmsg_i2s->codec_cs42888 = 1 + (2 << 16); + } + + if (of_device_is_compatible(pdev->dev.of_node, + "fsl,imx8mq-rpmsg-i2s")) { + rpmsg_i2s->codec_wm8960 = 0; + rpmsg_i2s->version = 2; + rpmsg_i2s->rates = SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000; + rpmsg_i2s->formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE; + fsl_rpmsg_i2s_dai.playback.rates = rpmsg_i2s->rates; + fsl_rpmsg_i2s_dai.playback.formats = rpmsg_i2s->formats; + fsl_rpmsg_i2s_dai.capture.rates = rpmsg_i2s->rates; + fsl_rpmsg_i2s_dai.capture.formats = rpmsg_i2s->formats; + } + + if (of_property_read_bool(pdev->dev.of_node, "fsl,enable-lpa")) + rpmsg_i2s->enable_lpa = 1; + + if (of_property_read_u32(np, "fsl,dma-buffer-size", + &i2s_info->prealloc_buffer_size)) + i2s_info->prealloc_buffer_size = IMX_DEFAULT_DMABUF_SIZE; + + platform_set_drvdata(pdev, rpmsg_i2s); + pm_runtime_enable(&pdev->dev); + + ret = devm_snd_soc_register_component(&pdev->dev, &fsl_component, + &fsl_rpmsg_i2s_dai, 1); + if (ret) + return ret; + + return imx_rpmsg_platform_register(&pdev->dev); +} + +static int fsl_rpmsg_i2s_remove(struct platform_device *pdev) +{ + struct fsl_rpmsg_i2s *rpmsg_i2s = platform_get_drvdata(pdev); + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + + if (i2s_info->rpmsg_wq) + destroy_workqueue(i2s_info->rpmsg_wq); + + return 0; +} + +#ifdef CONFIG_PM +static int fsl_rpmsg_i2s_runtime_resume(struct device *dev) +{ + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(dev); + + pm_qos_add_request(&rpmsg_i2s->pm_qos_req, PM_QOS_CPU_DMA_LATENCY, 0); + return 0; +} + +static int fsl_rpmsg_i2s_runtime_suspend(struct device *dev) +{ + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(dev); + + pm_qos_remove_request(&rpmsg_i2s->pm_qos_req); + return 0; +} +#endif + +#ifdef CONFIG_PM_SLEEP +static int fsl_rpmsg_i2s_suspend(struct device *dev) +{ + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(dev); + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + struct i2s_rpmsg *rpmsg_tx; + struct i2s_rpmsg *rpmsg_rx; + + flush_workqueue(i2s_info->rpmsg_wq); + rpmsg_tx = &i2s_info->rpmsg[I2S_TX_SUSPEND]; + rpmsg_rx = &i2s_info->rpmsg[I2S_RX_SUSPEND]; + + rpmsg_tx->send_msg.header.cmd = I2S_TX_SUSPEND; + i2s_send_message(rpmsg_tx, i2s_info); + + rpmsg_rx->send_msg.header.cmd = I2S_RX_SUSPEND; + i2s_send_message(rpmsg_rx, i2s_info); + + return 0; +} + +static int fsl_rpmsg_i2s_resume(struct device *dev) +{ + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(dev); + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + struct i2s_rpmsg *rpmsg_tx; + struct i2s_rpmsg *rpmsg_rx; + + rpmsg_tx = &i2s_info->rpmsg[I2S_TX_RESUME]; + rpmsg_rx = &i2s_info->rpmsg[I2S_RX_RESUME]; + + rpmsg_tx->send_msg.header.cmd = I2S_TX_RESUME; + i2s_send_message(rpmsg_tx, i2s_info); + + rpmsg_rx->send_msg.header.cmd = I2S_RX_RESUME; + i2s_send_message(rpmsg_rx, i2s_info); + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops fsl_rpmsg_i2s_pm_ops = { + SET_RUNTIME_PM_OPS(fsl_rpmsg_i2s_runtime_suspend, + fsl_rpmsg_i2s_runtime_resume, + NULL) + SET_SYSTEM_SLEEP_PM_OPS(fsl_rpmsg_i2s_suspend, fsl_rpmsg_i2s_resume) +}; + +static struct platform_driver fsl_rpmsg_i2s_driver = { + .probe = fsl_rpmsg_i2s_probe, + .remove = fsl_rpmsg_i2s_remove, + .driver = { + .name = "fsl-rpmsg-i2s", + .pm = &fsl_rpmsg_i2s_pm_ops, + .of_match_table = fsl_rpmsg_i2s_ids, + }, +}; + +module_platform_driver(fsl_rpmsg_i2s_driver); + +MODULE_DESCRIPTION("Freescale Soc rpmsg_i2s Interface"); +MODULE_AUTHOR("Shengjiu Wang <shengjiu.wang@freescale.com>"); +MODULE_ALIAS("platform:fsl-rpmsg_i2s"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/fsl/fsl_rpmsg_i2s.h b/sound/soc/fsl/fsl_rpmsg_i2s.h new file mode 100644 index 000000000000..e52c861c4d43 --- /dev/null +++ b/sound/soc/fsl/fsl_rpmsg_i2s.h @@ -0,0 +1,438 @@ +/* + * Copyright 2017 NXP + * + * 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. + * + ****************************************************************************** + * communication stack of audio with rpmsg + ****************************************************************************** + * Packet structure: + * A SRTM message consists of a 10 bytes header followed by 0~N bytes of data + * + * +---------------+-------------------------------+ + * | | Content | + * +---------------+-------------------------------+ + * | Byte Offset | 7 6 5 4 3 2 1 0 | + * +---------------+---+---+---+---+---+---+---+---+ + * | 0 | Category | + * +---------------+---+---+---+---+---+---+---+---+ + * | 1 ~ 2 | Version | + * +---------------+---+---+---+---+---+---+---+---+ + * | 3 | Type | + * +---------------+---+---+---+---+---+---+---+---+ + * | 4 | Command | + * +---------------+---+---+---+---+---+---+---+---+ + * | 5 | Reserved0 | + * +---------------+---+---+---+---+---+---+---+---+ + * | 6 | Reserved1 | + * +---------------+---+---+---+---+---+---+---+---+ + * | 7 | Reserved2 | + * +---------------+---+---+---+---+---+---+---+---+ + * | 8 | Reserved3 | + * +---------------+---+---+---+---+---+---+---+---+ + * | 9 | Reserved4 | + * +---------------+---+---+---+---+---+---+---+---+ + * | 10 | DATA 0 | + * +---------------+---+---+---+---+---+---+---+---+ + * : : : : : : : : : : : : : + * +---------------+---+---+---+---+---+---+---+---+ + * | N + 10 - 1 | DATA N-1 | + * +---------------+---+---+---+---+---+---+---+---+ + * + * +----------+------------+------------------------------------------------+ + * | Field | Byte | | + * +----------+------------+------------------------------------------------+ + * | Category | 0 | The destination category. | + * +----------+------------+------------------------------------------------+ + * | Version | 1 ~ 2 | The category version of the sender of the | + * | | | packet. | + * | | | The first byte represent the major version of | + * | | | the packet.The second byte represent the minor | + * | | | version of the packet. | + * +----------+------------+------------------------------------------------+ + * | Type | 3 | The message type of current message packet. | + * +----------+------------+------------------------------------------------+ + * | Command | 4 | The command byte sent to remote processor/SoC. | + * +----------+------------+------------------------------------------------+ + * | Reserved | 5 ~ 9 | Reserved field for future extension. | + * +----------+------------+------------------------------------------------+ + * | Data | N | The data payload of the message packet. | + * +----------+------------+------------------------------------------------+ + * + * Audio control: + * SRTM Audio Control Category Request Command Table: + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | Category | Version | Type | Command | Data | Function | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x00 | Data[0]: Audio Device Index | Open an Audio TX Instance. | + * | | | | | Data[1]: format | | + * | | | | | Data[2]: channels | | + * | | | | | Data[3-6]: samplerate | | + * | | | | | Data[7-10]: buffer_addr | | + * | | | | | Data[11-14]: buffer_size | | + * | | | | | Data[15-18]: period_size | | + * | | | | | Data[19-22]: buffer_tail | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x01 | Data[0]: Audio Device Index | Start an Audio TX Instance. | + * | | | | | Same as above command | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x02 | Data[0]: Audio Device Index | Pause an Audio TX Instance. | + * | | | | | Same as above command | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x03 | Data[0]: Audio Device Index | Resume an Audio TX Instance. | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x04 | Data[0]: Audio Device Index | Terminate an Audio TX Instance. | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x05 | Data[0]: Audio Device Index | Close an Audio TX Instance. | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x06 | Data[0]: Audio Device Index | Set Parameters for an Audio TX Instance. | + * | | | | | Data[1]: format | | + * | | | | | Data[2]: channels | | + * | | | | | Data[3-6]: samplerate | | + * | | | | | Data[7-22]: reserved | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x07 | Data[0]: Audio Device Index | Set Audio TX Buffer. | + * | | | | | Data[1-6]: reserved | | + * | | | | | Data[7-10]: buffer_addr | | + * | | | | | Data[11-14]: buffer_size | | + * | | | | | Data[15-18]: period_size | | + * | | | | | Data[19-22]: buffer_tail | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x08 | Data[0]: Audio Device Index | Suspend an Audio TX Instance. | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x09 | Data[0]: Audio Device Index | Resume an Audio TX Instance. | + * | | | | | Data[1]: format | | + * | | | | | Data[2]: channels | | + * | | | | | Data[3-6]: samplerate | | + * | | | | | Data[7-10]: buffer_addr | | + * | | | | | Data[11-14]: buffer_size | | + * | | | | | Data[15-18]: period_size | | + * | | | | | Data[19-22]: buffer_tail | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x0A | Data[0]: Audio Device Index | Open an Audio RX Instance. | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x0B | Data[0]: Audio Device Index | Start an Audio RX Instance. | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x0C | Data[0]: Audio Device Index | Pause an Audio RX Instance. | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x0D | Data[0]: Audio Device Index | Resume an Audio RX Instance. | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x0E | Data[0]: Audio Device Index | Terminate an Audio RX Instance. | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x0F | Data[0]: Audio Device Index | Close an Audio RX Instance. | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x10 | Data[0]: Audio Device Index | Set Parameters for an Audio RX Instance. | + * | | | | | Data[1]: format | | + * | | | | | Data[2]: channels | | + * | | | | | Data[3-6]: samplerate | | + * | | | | | Data[7-22]: reserved | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x11 | Data[0]: Audio Device Index | Set Audio RX Buffer. | + * | | | | | Data[1-6]: reserved | | + * | | | | | Data[7-10]: buffer_addr | | + * | | | | | Data[11-14]: buffer_size | | + * | | | | | Data[15-18]: period_size | | + * | | | | | Data[19-22]: buffer_tail | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x12 | Data[0]: Audio Device Index | Suspend an Audio RX Instance. | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x13 | Data[0]: Audio Device Index | Resume an Audio RX Instance. | + * | | | | | Data[1]: format | | + * | | | | | Data[2]: channels | | + * | | | | | Data[3-6]: samplerate | | + * | | | | | Data[7-10]: buffer_addr | | + * | | | | | Data[11-14]: buffer_size | | + * | | | | | Data[15-18]: period_size | | + * | | | | | Data[19-22]: buffer_tail | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x14 | Data[0]: Audio Device Index | Set register value to codec. | + * | | | | | Data[1-6]: reserved | | + * | | | | | Data[7-10]: register | | + * | | | | | Data[11-14]: value | | + * | | | | | Data[15-22]: reserved | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x15 | Data[0]: Audio Device Index | Get register value from codec. | + * | | | | | Data[1-6]: reserved | | + * | | | | | Data[7-10]: register | | + * | | | | | Data[11-22]: reserved | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * Note 1: See <List of Sample Format> for available value of + * Sample Format; + * Note 2: See <List of Audio Channels> for available value of Channels; + * Note 3: Sample Rate of Set Parameters for an Audio TX Instance + * Command and Set Parameters for an Audio RX Instance Command is + * in little-endian format. + * + * SRTM Audio Control Category Response Command Table: + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | Category | Version | Type | Command | Data | Function | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x00 | Data[0]: Audio Device Index | Reply for Open an Audio TX Instance. | + * | | | | | Data[1]: Return code | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x01 | Data[0]: Audio Device Index | Reply for Start an Audio TX Instance. | + * | | | | | Data[1]: Return code | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x02 | Data[0]: Audio Device Index | Reply for Pause an Audio TX Instance. | + * | | | | | Data[1]: Return code | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x03 | Data[0]: Audio Device Index | Reply for Resume an Audio TX Instance. | + * | | | | | Data[1]: Return code | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x04 | Data[0]: Audio Device Index | Reply for Terminate an Audio TX Instance. | + * | | | | | Data[1]: Return code | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x05 | Data[0]: Audio Device Index | Reply for Close an Audio TX Instance. | + * | | | | | Data[1]: Return code | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x06 | Data[0]: Audio Device Index | Reply for Set Parameters for an Audio | + * | | | | | Data[1]: Return code | TX Instance. | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x07 | Data[0]: Audio Device Index | Reply for Set Audio TX Buffer. | + * | | | | | Data[1]: Return code | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x08 | Data[0]: Audio Device Index | Reply for Suspend an Audio TX Instance. | + * | | | | | Data[1]: Return code | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x09 | Data[0]: Audio Device Index | Reply for Resume an Audio TX Instance. | + * | | | | | Data[1]: Return code | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x0A | Data[0]: Audio Device Index | Reply for Open an Audio RX Instance. | + * | | | | | Data[1]: Return code | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x0B | Data[0]: Audio Device Index | Reply for Start an Audio RX Instance. | + * | | | | | Data[1]: Return code | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x0C | Data[0]: Audio Device Index | Reply for Pause an Audio RX Instance. | + * | | | | | Data[1]: Return code | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x0D | Data[0]: Audio Device Index | Reply for Resume an Audio RX Instance. | + * | | | | | Data[1]: Return code | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x0E | Data[0]: Audio Device Index | Reply for Terminate an Audio RX Instance. | + * | | | | | Data[1]: Return code | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x0F | Data[0]: Audio Device Index | Reply for Close an Audio RX Instance. | + * | | | | | Data[1]: Return code | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x10 | Data[0]: Audio Device Index | Reply for Set Parameters for an Audio | + * | | | | | Data[1]: Return code | RX Instance. | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x11 | Data[0]: Audio Device Index | Reply for Set Audio RX Buffer. | + * | | | | | Data[1]: Return code | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x12 | Data[0]: Audio Device Index | Reply for Supend an Audio RX Instance. | + * | | | | | Data[1]: Return code | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x13 | Data[0]: Audio Device Index | Reply for Resume an Audio RX Instance. | + * | | | | | Data[1]: Return code | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x14 | Data[0]: Audio Device Index | Reply for Set codec register value. | + * | | | | | Data[1]: Return code | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x15 | Data[0]: Audio Device Index | Reply for Get codec register value. | + * | | | | | Data[1]: Return code | | + * | | | | | Data[2-6]: reserved | | + * | | | | | Data[7-10]: register | | + * | | | | | Data[11-14]: value | | + * | | | | | Data[15-22]: reserved | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * + * SRTM Audio Control Category Notification Command Table: + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | Category | Version | Type | Command | Data | Function | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x02 | 0x00 | Data[0]: Audio Device Index | Notify one TX period is finished. | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x02 | 0x01 | Data[0]: Audio Device Index | Notify one RX period is finished. | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * + * List of Sample Format: + * +--------------------+----------------------------------------------+ + * | Sample Format | Description | + * +--------------------+----------------------------------------------+ + * | 0x0 | S16_LE | + * +--------------------+----------------------------------------------+ + * | 0x1 | S24_LE | + * +--------------------+----------------------------------------------+ + * + * List of Audio Channels + * +--------------------+----------------------------------------------+ + * | Audio Channel | Description | + * +--------------------+----------------------------------------------+ + * | 0x0 | Left Channel | + * +--------------------+----------------------------------------------+ + * | 0x1 | Right Channel | + * +--------------------+----------------------------------------------+ + * | 0x2 | Left & Right Channel | + * +--------------------+----------------------------------------------+ + * + */ + +#ifndef __FSL_RPMSG_I2S_H +#define __FSL_RPMSG_I2S_H + +#include <linux/pm_qos.h> +#include <linux/imx_rpmsg.h> +#include <linux/interrupt.h> +#include <sound/dmaengine_pcm.h> + +#define RPMSG_TIMEOUT 1000 + +#define I2S_TX_OPEN 0x0 +#define I2S_TX_START 0x1 +#define I2S_TX_PAUSE 0x2 +#define I2S_TX_RESTART 0x3 +#define I2S_TX_TERMINATE 0x4 +#define I2S_TX_CLOSE 0x5 +#define I2S_TX_HW_PARAM 0x6 +#define I2S_TX_BUFFER 0x7 +#define I2S_TX_SUSPEND 0x8 +#define I2S_TX_RESUME 0x9 + +#define I2S_RX_OPEN 0xA +#define I2S_RX_START 0xB +#define I2S_RX_PAUSE 0xC +#define I2S_RX_RESTART 0xD +#define I2S_RX_TERMINATE 0xE +#define I2S_RX_CLOSE 0xF +#define I2S_RX_HW_PARAM 0x10 +#define I2S_RX_BUFFER 0x11 +#define I2S_RX_SUSPEND 0x12 +#define I2S_RX_RESUME 0x13 +#define SET_CODEC_VALUE 0x14 +#define GET_CODEC_VALUE 0x15 +#define I2S_TX_POINTER 0x16 +#define I2S_RX_POINTER 0x17 + +#define I2S_TYPE_A_NUM 0x18 + +#define WORK_MAX_NUM 0x18 + +#define I2S_TX_PERIOD_DONE 0x0 +#define I2S_RX_PERIOD_DONE 0x1 + +#define I2S_TYPE_C_NUM 0x2 + +#define I2S_CMD_MAX_NUM (I2S_TYPE_A_NUM + I2S_TYPE_C_NUM) + +#define I2S_TYPE_A 0x0 +#define I2S_TYPE_B 0x1 +#define I2S_TYPE_C 0x2 + +#define I2S_RESP_NONE 0x0 +#define I2S_RESP_NOT_ALLOWED 0x1 +#define I2S_RESP_SUCCESS 0x2 +#define I2S_RESP_FAILED 0x3 + +#define RPMSG_S16_LE 0x0 +#define RPMSG_S24_LE 0x1 +#define RPMSG_S32_LE 0x2 + +#define RPMSG_CH_LEFT 0x0 +#define RPMSG_CH_RIGHT 0x1 +#define RPMSG_CH_STEREO 0x2 + +struct i2s_param_s { + unsigned char audioindex; + unsigned char format; + unsigned char channels; + unsigned int rate; + unsigned int buffer_addr; /* Register for SET_CODEC_VALUE*/ + unsigned int buffer_size; /* register value for SET_CODEC_VALUE*/ + unsigned int period_size; + unsigned int buffer_tail; +} __packed; + +struct i2s_param_r { + unsigned char audioindex; + unsigned char resp; + unsigned char reserved1[1]; + unsigned int buffer_offset; /* the consumed offset of buffer*/ + unsigned int reg_addr; + unsigned int reg_data; + unsigned char reserved2[4]; + unsigned int buffer_tail; +} __packed; + +/* struct of send message */ +struct i2s_rpmsg_s { + struct imx_rpmsg_head header; + struct i2s_param_s param; +}; + +/* struct of received message */ +struct i2s_rpmsg_r { + struct imx_rpmsg_head header; + struct i2s_param_r param; +}; + +struct i2s_rpmsg { + struct i2s_rpmsg_s send_msg; + struct i2s_rpmsg_r recv_msg; +}; + +struct work_of_rpmsg { + struct i2s_info *i2s_info; + /* sent msg for each work */ + struct i2s_rpmsg msg; + struct work_struct work; +}; + +typedef void (*dma_callback)(void *arg); +struct i2s_info { + struct rpmsg_device *rpdev; + struct device *dev; + struct completion cmd_complete; + + /* received msg (global) */ + struct i2s_rpmsg_r recv_msg; + + struct i2s_rpmsg rpmsg[I2S_CMD_MAX_NUM]; + + struct workqueue_struct *rpmsg_wq; + struct work_of_rpmsg work_list[WORK_MAX_NUM]; + int work_index; + int num_period[2]; + void *callback_param[2]; + int (*send_message)(struct i2s_rpmsg *msg, struct i2s_info *info); + dma_callback callback[2]; + spinlock_t lock[2]; + struct mutex tx_lock; + struct mutex i2c_lock; + struct timer_list stream_timer[2]; + int prealloc_buffer_size; +}; + +struct fsl_rpmsg_i2s { + struct platform_device *pdev; + struct i2s_info i2s_info; + struct pm_qos_request pm_qos_req; + int codec_wm8960; + int codec_cs42888; + int force_lpa; + int version; + int rates; + u64 formats; + int enable_lpa; +}; + +#define RPMSG_CODEC_DRV_NAME_WM8960 "rpmsg-audio-codec-wm8960" +#define RPMSG_CODEC_DRV_NAME_CS42888 "rpmsg-audio-codec-cs42888" + +struct fsl_rpmsg_codec { + int audioindex; + + /*property for wm8960*/ + bool capless; + bool shared_lrclk; + + /*property for cs42xx8*/ + + char name[32]; + int num_adcs; +}; + +#endif /* __FSL_RPMSG_I2S_H */ diff --git a/sound/soc/fsl/fsl_sai.c b/sound/soc/fsl/fsl_sai.c index cb43f57f978b..8f45305a1e9a 100644 --- a/sound/soc/fsl/fsl_sai.c +++ b/sound/soc/fsl/fsl_sai.c @@ -1,7 +1,7 @@ /* * Freescale ALSA SoC Digital Audio Interface (SAI) driver. * - * Copyright 2012-2015 Freescale Semiconductor, Inc. + * Copyright 2012-2016 Freescale Semiconductor, Inc. * * This program is free software, you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -10,30 +10,99 @@ * */ +#include <linux/busfreq-imx.h> #include <linux/clk.h> +#include <linux/clk-provider.h> #include <linux/delay.h> #include <linux/dmaengine.h> #include <linux/module.h> +#include <linux/of_device.h> #include <linux/of_address.h> +#include <linux/pm_runtime.h> #include <linux/regmap.h> #include <linux/slab.h> #include <linux/time.h> +#include <linux/pm_qos.h> #include <sound/core.h> #include <sound/dmaengine_pcm.h> #include <sound/pcm_params.h> #include <linux/mfd/syscon.h> #include <linux/mfd/syscon/imx6q-iomuxc-gpr.h> +#include "fsl_dsd.h" #include "fsl_sai.h" #include "imx-pcm.h" #define FSL_SAI_FLAGS (FSL_SAI_CSR_SEIE |\ FSL_SAI_CSR_FEIE) +#define FSL_SAI_VERID_0301 0x0301 + +static struct fsl_sai_soc_data fsl_sai_vf610 = { + .imx = false, + /*dataline is mask, not index*/ + .dataline = 0x1, + .fifos = 1, + .fifo_depth = 32, + .flags = 0, + .constrain_period_size = false, +}; + +static struct fsl_sai_soc_data fsl_sai_imx6sx = { + .imx = true, + .dataline = 0x1, + .fifos = 1, + .fifo_depth = 32, + .flags = 0, + .reg_offset = 0, + .constrain_period_size = false, +}; + +static struct fsl_sai_soc_data fsl_sai_imx6ul = { + .imx = true, + .dataline = 0x1, + .fifos = 1, + .fifo_depth = 32, + .flags = 0, + .reg_offset = 0, + .constrain_period_size = false, +}; + +static struct fsl_sai_soc_data fsl_sai_imx7ulp = { + .imx = true, + .dataline = 0x3, + .fifos = 2, + .fifo_depth = 16, + .flags = SAI_FLAG_PMQOS, + .reg_offset = 8, + .constrain_period_size = false, +}; + +static struct fsl_sai_soc_data fsl_sai_imx8mq = { + .imx = true, + .dataline = 0xff, + .fifos = 8, + .fifo_depth = 128, + .flags = 0, + .reg_offset = 8, + .constrain_period_size = false, +}; + +static struct fsl_sai_soc_data fsl_sai_imx8qm = { + .imx = true, + .dataline = 0xf, + .fifos = 1, + .fifo_depth = 64, + .flags = 0, + .reg_offset = 0, + .constrain_period_size = true, +}; + static const unsigned int fsl_sai_rates[] = { 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 64000, - 88200, 96000, 176400, 192000 + 88200, 96000, 176400, 192000, 352800, + 384000, 705600, 768000, 1411200, 2822400, }; static const struct snd_pcm_hw_constraint_list fsl_sai_rate_constraints = { @@ -44,6 +113,7 @@ static const struct snd_pcm_hw_constraint_list fsl_sai_rate_constraints = { static irqreturn_t fsl_sai_isr(int irq, void *devid) { struct fsl_sai *sai = (struct fsl_sai *)devid; + unsigned char offset = sai->soc->reg_offset; struct device *dev = &sai->pdev->dev; u32 flags, xcsr, mask; bool irq_none = true; @@ -56,7 +126,7 @@ static irqreturn_t fsl_sai_isr(int irq, void *devid) mask = (FSL_SAI_FLAGS >> FSL_SAI_CSR_xIE_SHIFT) << FSL_SAI_CSR_xF_SHIFT; /* Tx IRQ */ - regmap_read(sai->regmap, FSL_SAI_TCSR, &xcsr); + regmap_read(sai->regmap, FSL_SAI_TCSR(offset), &xcsr); flags = xcsr & mask; if (flags) @@ -68,10 +138,10 @@ static irqreturn_t fsl_sai_isr(int irq, void *devid) dev_dbg(dev, "isr: Start of Tx word detected\n"); if (flags & FSL_SAI_CSR_SEF) - dev_warn(dev, "isr: Tx Frame sync error detected\n"); + dev_dbg(dev, "isr: Tx Frame sync error detected\n"); if (flags & FSL_SAI_CSR_FEF) { - dev_warn(dev, "isr: Transmit underrun detected\n"); + dev_dbg(dev, "isr: Transmit underrun detected\n"); /* FIFO reset for safety */ xcsr |= FSL_SAI_CSR_FR; } @@ -86,11 +156,11 @@ static irqreturn_t fsl_sai_isr(int irq, void *devid) xcsr &= ~FSL_SAI_CSR_xF_MASK; if (flags) - regmap_write(sai->regmap, FSL_SAI_TCSR, flags | xcsr); + regmap_write(sai->regmap, FSL_SAI_TCSR(offset), flags | xcsr); irq_rx: /* Rx IRQ */ - regmap_read(sai->regmap, FSL_SAI_RCSR, &xcsr); + regmap_read(sai->regmap, FSL_SAI_RCSR(offset), &xcsr); flags = xcsr & mask; if (flags) @@ -102,10 +172,10 @@ irq_rx: dev_dbg(dev, "isr: Start of Rx word detected\n"); if (flags & FSL_SAI_CSR_SEF) - dev_warn(dev, "isr: Rx Frame sync error detected\n"); + dev_dbg(dev, "isr: Rx Frame sync error detected\n"); if (flags & FSL_SAI_CSR_FEF) { - dev_warn(dev, "isr: Receive overflow detected\n"); + dev_dbg(dev, "isr: Receive overflow detected\n"); /* FIFO reset for safety */ xcsr |= FSL_SAI_CSR_FR; } @@ -120,7 +190,7 @@ irq_rx: xcsr &= ~FSL_SAI_CSR_xF_MASK; if (flags) - regmap_write(sai->regmap, FSL_SAI_RCSR, flags | xcsr); + regmap_write(sai->regmap, FSL_SAI_RCSR(offset), flags | xcsr); out: if (irq_none) @@ -144,6 +214,7 @@ static int fsl_sai_set_dai_sysclk_tr(struct snd_soc_dai *cpu_dai, int clk_id, unsigned int freq, int fsl_dir) { struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); + unsigned char offset = sai->soc->reg_offset; bool tx = fsl_dir == FSL_FMT_TRANSMITTER; u32 val_cr2 = 0; @@ -164,20 +235,89 @@ static int fsl_sai_set_dai_sysclk_tr(struct snd_soc_dai *cpu_dai, return -EINVAL; } - regmap_update_bits(sai->regmap, FSL_SAI_xCR2(tx), + regmap_update_bits(sai->regmap, FSL_SAI_xCR2(tx, offset), FSL_SAI_CR2_MSEL_MASK, val_cr2); return 0; } +static int fsl_sai_set_mclk_rate(struct snd_soc_dai *dai, int clk_id, + unsigned int freq) +{ + struct fsl_sai *sai = snd_soc_dai_get_drvdata(dai); + struct clk *p = sai->mclk_clk[clk_id], *pll = 0, *npll = 0; + u64 ratio = freq; + int ret; + + while (p && sai->pll8k_clk && sai->pll11k_clk) { + struct clk *pp = clk_get_parent(p); + + if (clk_is_match(pp, sai->pll8k_clk) || + clk_is_match(pp, sai->pll11k_clk)) { + pll = pp; + break; + } + p = pp; + } + + if (pll) { + npll = (do_div(ratio, 8000) ? sai->pll11k_clk : sai->pll8k_clk); + if (!clk_is_match(pll, npll)) { + if (sai->mclk_streams == 0) { + ret = clk_set_parent(p, npll); + if (ret < 0) + dev_warn(dai->dev, + "failed to set parent %s: %d\n", + __clk_get_name(npll), ret); + } else { + dev_err(dai->dev, + "PLL %s is in use by a running stream.\n", + __clk_get_name(pll)); + return -EINVAL; + } + } + } + + ret = clk_set_rate(sai->mclk_clk[clk_id], freq); + if (ret < 0) + dev_err(dai->dev, "failed to set clock rate (%u): %d\n", + freq, ret); + return ret; +} + +static int fsl_sai_set_dai_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) +{ + struct fsl_sai *sai = snd_soc_dai_get_drvdata(dai); + + sai->bitclk_ratio = ratio; + return 0; +} + static int fsl_sai_set_dai_sysclk(struct snd_soc_dai *cpu_dai, int clk_id, unsigned int freq, int dir) { + struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); int ret; if (dir == SND_SOC_CLOCK_IN) return 0; + if (freq > 0) { + if (clk_id < 0 || clk_id >= FSL_SAI_MCLK_MAX) { + dev_err(cpu_dai->dev, "Unknown clock id: %d\n", clk_id); + return -EINVAL; + } + + if (IS_ERR_OR_NULL(sai->mclk_clk[clk_id])) { + dev_err(cpu_dai->dev, "Unassigned clock: %d\n", clk_id); + return -EINVAL; + } + + ret = fsl_sai_set_mclk_rate(cpu_dai, clk_id, freq); + if (ret < 0) + return ret; + } + ret = fsl_sai_set_dai_sysclk_tr(cpu_dai, clk_id, freq, FSL_FMT_TRANSMITTER); if (ret) { @@ -197,12 +337,14 @@ static int fsl_sai_set_dai_fmt_tr(struct snd_soc_dai *cpu_dai, unsigned int fmt, int fsl_dir) { struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); + unsigned char offset = sai->soc->reg_offset; bool tx = fsl_dir == FSL_FMT_TRANSMITTER; u32 val_cr2 = 0, val_cr4 = 0; if (!sai->is_lsb_first) val_cr4 |= FSL_SAI_CR4_MF; + sai->is_dsp_mode = false; /* DAI mode */ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: @@ -241,6 +383,11 @@ static int fsl_sai_set_dai_fmt_tr(struct snd_soc_dai *cpu_dai, val_cr2 |= FSL_SAI_CR2_BCP; sai->is_dsp_mode = true; break; + case SND_SOC_DAIFMT_PDM: + val_cr2 |= FSL_SAI_CR2_BCP; + val_cr4 &= ~FSL_SAI_CR4_MF; + sai->is_dsp_mode = true; + break; case SND_SOC_DAIFMT_RIGHT_J: /* To be done */ default: @@ -269,31 +416,33 @@ static int fsl_sai_set_dai_fmt_tr(struct snd_soc_dai *cpu_dai, return -EINVAL; } + sai->slave_mode[tx] = false; + /* DAI clock master masks */ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { case SND_SOC_DAIFMT_CBS_CFS: val_cr2 |= FSL_SAI_CR2_BCD_MSTR; val_cr4 |= FSL_SAI_CR4_FSD_MSTR; - sai->is_slave_mode = false; + sai->slave_mode[tx] = false; break; case SND_SOC_DAIFMT_CBM_CFM: - sai->is_slave_mode = true; + sai->slave_mode[tx] = true; break; case SND_SOC_DAIFMT_CBS_CFM: val_cr2 |= FSL_SAI_CR2_BCD_MSTR; - sai->is_slave_mode = false; + sai->slave_mode[tx] = false; break; case SND_SOC_DAIFMT_CBM_CFS: val_cr4 |= FSL_SAI_CR4_FSD_MSTR; - sai->is_slave_mode = true; + sai->slave_mode[tx] = true; break; default: return -EINVAL; } - regmap_update_bits(sai->regmap, FSL_SAI_xCR2(tx), + regmap_update_bits(sai->regmap, FSL_SAI_xCR2(tx, offset), FSL_SAI_CR2_BCP | FSL_SAI_CR2_BCD_MSTR, val_cr2); - regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx), + regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx, offset), FSL_SAI_CR4_MF | FSL_SAI_CR4_FSE | FSL_SAI_CR4_FSP | FSL_SAI_CR4_FSD_MSTR, val_cr4); @@ -302,14 +451,23 @@ static int fsl_sai_set_dai_fmt_tr(struct snd_soc_dai *cpu_dai, static int fsl_sai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) { + struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); int ret; + if (sai->masterflag[FSL_FMT_TRANSMITTER]) + fmt = (fmt & (~SND_SOC_DAIFMT_MASTER_MASK)) | + sai->masterflag[FSL_FMT_TRANSMITTER]; + ret = fsl_sai_set_dai_fmt_tr(cpu_dai, fmt, FSL_FMT_TRANSMITTER); if (ret) { dev_err(cpu_dai->dev, "Cannot set tx format: %d\n", ret); return ret; } + if (sai->masterflag[FSL_FMT_RECEIVER]) + fmt = (fmt & (~SND_SOC_DAIFMT_MASTER_MASK)) | + sai->masterflag[FSL_FMT_RECEIVER]; + ret = fsl_sai_set_dai_fmt_tr(cpu_dai, fmt, FSL_FMT_RECEIVER); if (ret) dev_err(cpu_dai->dev, "Cannot set rx format: %d\n", ret); @@ -317,18 +475,64 @@ static int fsl_sai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) return ret; } +static int fsl_sai_check_ver(struct snd_soc_dai *cpu_dai) +{ + struct fsl_sai *sai = dev_get_drvdata(cpu_dai->dev); + unsigned char offset = sai->soc->reg_offset; + unsigned int val; + + if (FSL_SAI_TCSR(offset) == FSL_SAI_VERID) + return 0; + + if (sai->verid.loaded) + return 0; + + sai->verid.loaded = true; + regmap_read(sai->regmap, FSL_SAI_VERID, &val); + dev_dbg(cpu_dai->dev, "VERID: 0x%016X\n", val); + + sai->verid.id = (val & FSL_SAI_VER_ID_MASK) >> FSL_SAI_VER_ID_SHIFT; + sai->verid.extfifo_en = (val & FSL_SAI_VER_EFIFO_EN); + sai->verid.timestamp_en = (val & FSL_SAI_VER_TSTMP_EN); + + regmap_read(sai->regmap, FSL_SAI_PARAM, &val); + dev_dbg(cpu_dai->dev, "PARAM: 0x%016X\n", val); + + /* max slots per frame, power of 2 */ + sai->param.spf = 1 << + ((val & FSL_SAI_PAR_SPF_MASK) >> FSL_SAI_PAR_SPF_SHIFT); + + /* words per fifo, power of 2 */ + sai->param.wpf = 1 << + ((val & FSL_SAI_PAR_WPF_MASK) >> FSL_SAI_PAR_WPF_SHIFT); + + /* number of datalines implemented */ + sai->param.dln = val & FSL_SAI_PAR_DLN_MASK; + + dev_dbg(cpu_dai->dev, + "Version: 0x%08X, SPF: %u, WPF: %u, DLN: %u\n", + sai->verid.id, sai->param.spf, sai->param.wpf, sai->param.dln + ); + + return 0; +} + static int fsl_sai_set_bclk(struct snd_soc_dai *dai, bool tx, u32 freq) { struct fsl_sai *sai = snd_soc_dai_get_drvdata(dai); + unsigned char offset = sai->soc->reg_offset; unsigned long clk_rate; - u32 savediv = 0, ratio, savesub = freq; + unsigned int reg = 0; + u32 ratio, savesub = freq, saveratio = 0, savediv = 0; u32 id; int ret = 0; /* Don't apply to slave mode */ - if (sai->is_slave_mode) + if (sai->slave_mode[tx]) return 0; + fsl_sai_check_ver(dai); + for (id = 0; id < FSL_SAI_MCLK_MAX; id++) { clk_rate = clk_get_rate(sai->mclk_clk[id]); if (!clk_rate) @@ -349,22 +553,21 @@ static int fsl_sai_set_bclk(struct snd_soc_dai *dai, bool tx, u32 freq) "ratio %d for freq %dHz based on clock %ldHz\n", ratio, freq, clk_rate); - if (ratio % 2 == 0 && ratio >= 2 && ratio <= 512) - ratio /= 2; - else - continue; + if ((ratio % 2 == 0 && ratio >= 2 && ratio <= 512) || + (ratio == 1 && sai->verid.id >= FSL_SAI_VERID_0301)) { - if (ret < savesub) { - savediv = ratio; - sai->mclk_id[tx] = id; - savesub = ret; - } + if (ret < savesub) { + saveratio = ratio; + sai->mclk_id[tx] = id; + savesub = ret; + } - if (ret == 0) - break; + if (ret == 0) + break; + } } - if (savediv == 0) { + if (saveratio == 0) { dev_err(dai->dev, "failed to derive required %cx rate: %d\n", tx ? 'T' : 'R', freq); return -EINVAL; @@ -380,24 +583,32 @@ static int fsl_sai_set_bclk(struct snd_soc_dai *dai, bool tx, u32 freq) * 4) For Tx and Rx are both Synchronous with another SAI, we just * ignore it. */ - if ((sai->synchronous[TX] && !sai->synchronous[RX]) || - (!tx && !sai->synchronous[RX])) { - regmap_update_bits(sai->regmap, FSL_SAI_RCR2, - FSL_SAI_CR2_MSEL_MASK, - FSL_SAI_CR2_MSEL(sai->mclk_id[tx])); - regmap_update_bits(sai->regmap, FSL_SAI_RCR2, - FSL_SAI_CR2_DIV_MASK, savediv - 1); - } else if ((sai->synchronous[RX] && !sai->synchronous[TX]) || - (tx && !sai->synchronous[TX])) { - regmap_update_bits(sai->regmap, FSL_SAI_TCR2, - FSL_SAI_CR2_MSEL_MASK, - FSL_SAI_CR2_MSEL(sai->mclk_id[tx])); - regmap_update_bits(sai->regmap, FSL_SAI_TCR2, - FSL_SAI_CR2_DIV_MASK, savediv - 1); - } - - dev_dbg(dai->dev, "best fit: clock id=%d, div=%d, deviation =%d\n", - sai->mclk_id[tx], savediv, savesub); + if ((!tx || sai->synchronous[TX]) && !sai->synchronous[RX]) + reg = FSL_SAI_RCR2(offset); + else if ((tx || sai->synchronous[RX]) && !sai->synchronous[TX]) + reg = FSL_SAI_TCR2(offset); + + if (reg) { + regmap_update_bits(sai->regmap, reg, FSL_SAI_CR2_MSEL_MASK, + FSL_SAI_CR2_MSEL(sai->mclk_id[tx])); + + savediv = (saveratio == 1 ? 0 : (saveratio >> 1) - 1); + regmap_update_bits(sai->regmap, reg, FSL_SAI_CR2_DIV_MASK, savediv); + + if (sai->verid.id >= FSL_SAI_VERID_0301) { + regmap_update_bits(sai->regmap, reg, FSL_SAI_CR2_BYP, + (saveratio == 1 ? FSL_SAI_CR2_BYP : 0)); + } + } + + if (sai->verid.id >= FSL_SAI_VERID_0301) { + /* SAI is in master mode at this point, so enable MCLK */ + regmap_update_bits(sai->regmap, FSL_SAI_MCTL, + FSL_SAI_MCTL_MCLK_EN, FSL_SAI_MCTL_MCLK_EN); + } + + dev_dbg(dai->dev, "best fit: clock id=%d, ratio=%d, deviation=%d\n", + sai->mclk_id[tx], saveratio, savesub); return 0; } @@ -407,23 +618,47 @@ static int fsl_sai_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai) { struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); + unsigned char offset = sai->soc->reg_offset; bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; unsigned int channels = params_channels(params); u32 word_width = params_width(params); + u32 rate = params_rate(params); u32 val_cr4 = 0, val_cr5 = 0; u32 slots = (channels == 1) ? 2 : channels; u32 slot_width = word_width; + u32 pins, bclk; int ret; + int i; + int trce_mask = 0; if (sai->slots) slots = sai->slots; + pins = DIV_ROUND_UP(channels, slots); + sai->is_dsd = fsl_is_dsd(params); + if (sai->is_dsd) + pins = channels; + if (sai->slot_width) slot_width = sai->slot_width; - if (!sai->is_slave_mode) { - ret = fsl_sai_set_bclk(cpu_dai, tx, - slots * slot_width * params_rate(params)); + bclk = rate*(sai->bitclk_ratio ? sai->bitclk_ratio : slots * slot_width); + + if (!IS_ERR_OR_NULL(sai->pinctrl)) { + sai->pins_state = fsl_get_pins_state(sai->pinctrl, params, bclk); + + if (!IS_ERR_OR_NULL(sai->pins_state)) { + ret = pinctrl_select_state(sai->pinctrl, sai->pins_state); + if (ret) { + dev_err(cpu_dai->dev, + "failed to set proper pins state: %d\n", ret); + return ret; + } + } + } + + if (!sai->slave_mode[tx]) { + ret = fsl_sai_set_bclk(cpu_dai, tx, bclk); if (ret) return ret; @@ -443,13 +678,18 @@ static int fsl_sai_hw_params(struct snd_pcm_substream *substream, val_cr5 |= FSL_SAI_CR5_WNW(slot_width); val_cr5 |= FSL_SAI_CR5_W0W(slot_width); - if (sai->is_lsb_first) + if (sai->is_lsb_first || sai->is_dsd) val_cr5 |= FSL_SAI_CR5_FBT(0); else val_cr5 |= FSL_SAI_CR5_FBT(word_width - 1); val_cr4 |= FSL_SAI_CR4_FRSZ(slots); + /* Output Mode - data pins transmit 0 when slots are masked + * or channels are disabled + */ + val_cr4 |= FSL_SAI_CR4_CHMOD; + /* * For SAI master mode, when Tx(Rx) sync with Rx(Tx) clock, Rx(Tx) will * generate bclk and frame clock for Tx(Rx), we should set RCR4(TCR4), @@ -457,36 +697,102 @@ static int fsl_sai_hw_params(struct snd_pcm_substream *substream, * error. */ - if (!sai->is_slave_mode) { + if (!sai->slave_mode[tx]) { if (!sai->synchronous[TX] && sai->synchronous[RX] && !tx) { - regmap_update_bits(sai->regmap, FSL_SAI_TCR4, - FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK, + regmap_update_bits(sai->regmap, FSL_SAI_TCR4(offset), + FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK | + FSL_SAI_CR4_CHMOD_MASK, val_cr4); - regmap_update_bits(sai->regmap, FSL_SAI_TCR5, + regmap_update_bits(sai->regmap, FSL_SAI_TCR5(offset), FSL_SAI_CR5_WNW_MASK | FSL_SAI_CR5_W0W_MASK | FSL_SAI_CR5_FBT_MASK, val_cr5); - regmap_write(sai->regmap, FSL_SAI_TMR, - ~0UL - ((1 << channels) - 1)); } else if (!sai->synchronous[RX] && sai->synchronous[TX] && tx) { - regmap_update_bits(sai->regmap, FSL_SAI_RCR4, - FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK, + regmap_update_bits(sai->regmap, FSL_SAI_RCR4(offset), + FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK | + FSL_SAI_CR4_CHMOD_MASK, val_cr4); - regmap_update_bits(sai->regmap, FSL_SAI_RCR5, + regmap_update_bits(sai->regmap, FSL_SAI_RCR5(offset), FSL_SAI_CR5_WNW_MASK | FSL_SAI_CR5_W0W_MASK | FSL_SAI_CR5_FBT_MASK, val_cr5); - regmap_write(sai->regmap, FSL_SAI_RMR, - ~0UL - ((1 << channels) - 1)); } } - regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx), - FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK, + if (sai->soc->dataline != 0x1) { + + if (sai->dataline[tx] <= 1 || sai->is_multi_lane) + regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx, offset), + FSL_SAI_CR4_FCOMB_MASK, 0); + else + regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx, offset), + FSL_SAI_CR4_FCOMB_MASK, FSL_SAI_CR4_FCOMB_SOFT); + + if (sai->is_multi_lane) { + if (tx) { + sai->dma_params_tx.maxburst = + FSL_SAI_MAXBURST_TX * pins; + if (sai->is_dsd) + sai->dma_params_tx.fifo_num = pins + + (sai->dataline_off_dsd[tx] << 8); + else + sai->dma_params_tx.fifo_num = pins + + (sai->dataline_off[tx] << 8); + } else { + sai->dma_params_rx.maxburst = + FSL_SAI_MAXBURST_RX * pins; + if (sai->is_dsd) + sai->dma_params_rx.fifo_num = pins + + (sai->dataline_off_dsd[tx] << 8); + else + sai->dma_params_rx.fifo_num = pins + + (sai->dataline_off[tx] << 8); + } + } + + snd_soc_dai_init_dma_data(cpu_dai, &sai->dma_params_tx, + &sai->dma_params_rx); + } + + if (sai->is_dsd) { + if (__sw_hweight8(sai->dataline_dsd[tx] & 0xFF) < pins) { + dev_err(cpu_dai->dev, "channel not supported\n"); + return -EINVAL; + } + /*find a proper tcre setting*/ + for (i = 0; i < 8; i++) { + trce_mask = (1 << (i + 1)) - 1; + if (__sw_hweight8(sai->dataline_dsd[tx] & trce_mask) == pins) + break; + } + + regmap_update_bits(sai->regmap, FSL_SAI_xCR3(tx, offset), + FSL_SAI_CR3_TRCE_MASK, + FSL_SAI_CR3_TRCE((sai->dataline_dsd[tx] & trce_mask))); + } else { + if (__sw_hweight8(sai->dataline[tx] & 0xFF) < pins) { + dev_err(cpu_dai->dev, "channel not supported\n"); + return -EINVAL; + } + /*find a proper tcre setting*/ + for (i = 0; i < 8; i++) { + trce_mask = (1 << (i + 1)) - 1; + if (__sw_hweight8(sai->dataline[tx] & trce_mask) == pins) + break; + } + + regmap_update_bits(sai->regmap, FSL_SAI_xCR3(tx, offset), + FSL_SAI_CR3_TRCE_MASK, + FSL_SAI_CR3_TRCE((sai->dataline[tx] & trce_mask))); + } + + regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx, offset), + FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK | + FSL_SAI_CR4_CHMOD_MASK, val_cr4); - regmap_update_bits(sai->regmap, FSL_SAI_xCR5(tx), + regmap_update_bits(sai->regmap, FSL_SAI_xCR5(tx, offset), FSL_SAI_CR5_WNW_MASK | FSL_SAI_CR5_W0W_MASK | FSL_SAI_CR5_FBT_MASK, val_cr5); - regmap_write(sai->regmap, FSL_SAI_xMR(tx), ~0UL - ((1 << channels) - 1)); - + regmap_write(sai->regmap, FSL_SAI_xMR(tx), + ~0UL - ((1 << min(channels, slots)) - 1)); return 0; } @@ -494,9 +800,13 @@ static int fsl_sai_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai) { struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); + unsigned char offset = sai->soc->reg_offset; bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; - if (!sai->is_slave_mode && + regmap_update_bits(sai->regmap, FSL_SAI_xCR3(tx, offset), + FSL_SAI_CR3_TRCE_MASK, 0); + + if (!sai->slave_mode[tx] && sai->mclk_streams & BIT(substream->stream)) { clk_disable_unprepare(sai->mclk_clk[sai->mclk_id[tx]]); sai->mclk_streams &= ~BIT(substream->stream); @@ -510,17 +820,29 @@ static int fsl_sai_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *cpu_dai) { struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); + unsigned char offset = sai->soc->reg_offset; bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + u8 channels = substream->runtime->channels; + u32 slots = (channels == 1) ? 2 : channels; u32 xcsr, count = 100; + u32 pins; + int i = 0, j = 0, k = 0; + if (sai->slots) + slots = sai->slots; + + pins = DIV_ROUND_UP(channels, slots); + + if (sai->is_dsd) + pins = channels; /* * Asynchronous mode: Clear SYNC for both Tx and Rx. * Rx sync with Tx clocks: Clear SYNC for Tx, set it for Rx. * Tx sync with Rx clocks: Clear SYNC for Rx, set it for Tx. */ - regmap_update_bits(sai->regmap, FSL_SAI_TCR2, FSL_SAI_CR2_SYNC, + regmap_update_bits(sai->regmap, FSL_SAI_TCR2(offset), FSL_SAI_CR2_SYNC, sai->synchronous[TX] ? FSL_SAI_CR2_SYNC : 0); - regmap_update_bits(sai->regmap, FSL_SAI_RCR2, FSL_SAI_CR2_SYNC, + regmap_update_bits(sai->regmap, FSL_SAI_RCR2(offset), FSL_SAI_CR2_SYNC, sai->synchronous[RX] ? FSL_SAI_CR2_SYNC : 0); /* @@ -531,43 +853,63 @@ static int fsl_sai_trigger(struct snd_pcm_substream *substream, int cmd, case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx), + + while (tx && i < channels) { + if ((sai->is_dsd ? sai->dataline_dsd[tx] : sai->dataline[tx]) & (1 << j)) { + regmap_write(sai->regmap, FSL_SAI_TDR0 + j * 0x4, 0x0); + i++; + k++; + } + j++; + + if (k%pins == 0) + j = 0; + } + + regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, offset), FSL_SAI_CSR_FRDE, FSL_SAI_CSR_FRDE); - regmap_update_bits(sai->regmap, FSL_SAI_RCSR, + regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, offset), FSL_SAI_CSR_TERE, FSL_SAI_CSR_TERE); - regmap_update_bits(sai->regmap, FSL_SAI_TCSR, + regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, offset), + FSL_SAI_CSR_SE, FSL_SAI_CSR_SE); + if (!sai->synchronous[TX] && sai->synchronous[RX] && !tx) { + regmap_update_bits(sai->regmap, FSL_SAI_xCSR((!tx), offset), + FSL_SAI_CSR_TERE, FSL_SAI_CSR_TERE); + } else if (!sai->synchronous[RX] && sai->synchronous[TX] && tx) { + regmap_update_bits(sai->regmap, FSL_SAI_xCSR((!tx), offset), FSL_SAI_CSR_TERE, FSL_SAI_CSR_TERE); + } - regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx), + regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, offset), FSL_SAI_CSR_xIE_MASK, FSL_SAI_FLAGS); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx), + regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, offset), FSL_SAI_CSR_FRDE, 0); - regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx), + regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, offset), FSL_SAI_CSR_xIE_MASK, 0); /* Check if the opposite FRDE is also disabled */ - regmap_read(sai->regmap, FSL_SAI_xCSR(!tx), &xcsr); + regmap_read(sai->regmap, FSL_SAI_xCSR(!tx, offset), &xcsr); if (!(xcsr & FSL_SAI_CSR_FRDE)) { /* Disable both directions and reset their FIFOs */ - regmap_update_bits(sai->regmap, FSL_SAI_TCSR, + regmap_update_bits(sai->regmap, FSL_SAI_TCSR(offset), FSL_SAI_CSR_TERE, 0); - regmap_update_bits(sai->regmap, FSL_SAI_RCSR, + regmap_update_bits(sai->regmap, FSL_SAI_RCSR(offset), FSL_SAI_CSR_TERE, 0); /* TERE will remain set till the end of current frame */ do { udelay(10); - regmap_read(sai->regmap, FSL_SAI_xCSR(tx), &xcsr); + regmap_read(sai->regmap, FSL_SAI_xCSR(tx, offset), &xcsr); } while (--count && xcsr & FSL_SAI_CSR_TERE); - regmap_update_bits(sai->regmap, FSL_SAI_TCSR, + regmap_update_bits(sai->regmap, FSL_SAI_TCSR(offset), FSL_SAI_CSR_FR, FSL_SAI_CSR_FR); - regmap_update_bits(sai->regmap, FSL_SAI_RCSR, + regmap_update_bits(sai->regmap, FSL_SAI_RCSR(offset), FSL_SAI_CSR_FR, FSL_SAI_CSR_FR); /* @@ -577,15 +919,15 @@ static int fsl_sai_trigger(struct snd_pcm_substream *substream, int cmd, * This is a hardware bug, and will be fix in the * next sai version. */ - if (!sai->is_slave_mode) { + if (!sai->slave_mode[tx]) { /* Software Reset for both Tx and Rx */ regmap_write(sai->regmap, - FSL_SAI_TCSR, FSL_SAI_CSR_SR); + FSL_SAI_TCSR(offset), FSL_SAI_CSR_SR); regmap_write(sai->regmap, - FSL_SAI_RCSR, FSL_SAI_CSR_SR); + FSL_SAI_RCSR(offset), FSL_SAI_CSR_SR); /* Clear SR bit to finish the reset */ - regmap_write(sai->regmap, FSL_SAI_TCSR, 0); - regmap_write(sai->regmap, FSL_SAI_RCSR, 0); + regmap_write(sai->regmap, FSL_SAI_TCSR(offset), 0); + regmap_write(sai->regmap, FSL_SAI_RCSR(offset), 0); } } break; @@ -601,17 +943,19 @@ static int fsl_sai_startup(struct snd_pcm_substream *substream, { struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; - struct device *dev = &sai->pdev->dev; int ret; - ret = clk_prepare_enable(sai->bus_clk); - if (ret) { - dev_err(dev, "failed to enable bus clock: %d\n", ret); - return ret; - } + if (sai->is_stream_opened[tx]) + return -EBUSY; + else + sai->is_stream_opened[tx] = true; - regmap_update_bits(sai->regmap, FSL_SAI_xCR3(tx), FSL_SAI_CR3_TRCE, - FSL_SAI_CR3_TRCE); + /* EDMA engine needs periods of size multiple of tx/rx maxburst */ + if (sai->soc->constrain_period_size) + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + tx ? sai->dma_params_tx.maxburst : + sai->dma_params_rx.maxburst); ret = snd_pcm_hw_constraint_list(substream->runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &fsl_sai_rate_constraints); @@ -625,12 +969,12 @@ static void fsl_sai_shutdown(struct snd_pcm_substream *substream, struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; - regmap_update_bits(sai->regmap, FSL_SAI_xCR3(tx), FSL_SAI_CR3_TRCE, 0); - - clk_disable_unprepare(sai->bus_clk); + if (sai->is_stream_opened[tx]) + sai->is_stream_opened[tx] = false; } static const struct snd_soc_dai_ops fsl_sai_pcm_dai_ops = { + .set_bclk_ratio = fsl_sai_set_dai_bclk_ratio, .set_sysclk = fsl_sai_set_dai_sysclk, .set_fmt = fsl_sai_set_dai_fmt, .set_tdm_slot = fsl_sai_set_dai_tdm_slot, @@ -644,18 +988,21 @@ static const struct snd_soc_dai_ops fsl_sai_pcm_dai_ops = { static int fsl_sai_dai_probe(struct snd_soc_dai *cpu_dai) { struct fsl_sai *sai = dev_get_drvdata(cpu_dai->dev); + unsigned char offset = sai->soc->reg_offset; /* Software Reset for both Tx and Rx */ - regmap_write(sai->regmap, FSL_SAI_TCSR, FSL_SAI_CSR_SR); - regmap_write(sai->regmap, FSL_SAI_RCSR, FSL_SAI_CSR_SR); + regmap_write(sai->regmap, FSL_SAI_TCSR(offset), FSL_SAI_CSR_SR); + regmap_write(sai->regmap, FSL_SAI_RCSR(offset), FSL_SAI_CSR_SR); /* Clear SR bit to finish the reset */ - regmap_write(sai->regmap, FSL_SAI_TCSR, 0); - regmap_write(sai->regmap, FSL_SAI_RCSR, 0); + regmap_write(sai->regmap, FSL_SAI_TCSR(offset), 0); + regmap_write(sai->regmap, FSL_SAI_RCSR(offset), 0); - regmap_update_bits(sai->regmap, FSL_SAI_TCR1, FSL_SAI_CR1_RFW_MASK, - FSL_SAI_MAXBURST_TX * 2); - regmap_update_bits(sai->regmap, FSL_SAI_RCR1, FSL_SAI_CR1_RFW_MASK, - FSL_SAI_MAXBURST_RX - 1); + regmap_update_bits(sai->regmap, FSL_SAI_TCR1(offset), + sai->soc->fifo_depth - 1, + sai->soc->fifo_depth - FSL_SAI_MAXBURST_TX); + regmap_update_bits(sai->regmap, FSL_SAI_RCR1(offset), + sai->soc->fifo_depth - 1, + FSL_SAI_MAXBURST_RX - 1); snd_soc_dai_init_dma_data(cpu_dai, &sai->dma_params_tx, &sai->dma_params_rx); @@ -665,26 +1012,44 @@ static int fsl_sai_dai_probe(struct snd_soc_dai *cpu_dai) return 0; } +static int fsl_sai_dai_resume(struct snd_soc_dai *cpu_dai) +{ + struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); + int ret; + + if (!IS_ERR_OR_NULL(sai->pinctrl) && !IS_ERR_OR_NULL(sai->pins_state)) { + ret = pinctrl_select_state(sai->pinctrl, sai->pins_state); + if (ret) { + dev_err(cpu_dai->dev, + "failed to set proper pins state: %d\n", ret); + return ret; + } + } + + return 0; +} + static struct snd_soc_dai_driver fsl_sai_dai = { .probe = fsl_sai_dai_probe, .playback = { .stream_name = "CPU-Playback", .channels_min = 1, - .channels_max = 2, + .channels_max = 32, .rate_min = 8000, - .rate_max = 192000, + .rate_max = 2822400, .rates = SNDRV_PCM_RATE_KNOT, .formats = FSL_SAI_FORMATS, }, .capture = { .stream_name = "CPU-Capture", .channels_min = 1, - .channels_max = 2, + .channels_max = 32, .rate_min = 8000, - .rate_max = 192000, + .rate_max = 2822400, .rates = SNDRV_PCM_RATE_KNOT, .formats = FSL_SAI_FORMATS, }, + .resume = fsl_sai_dai_resume, .ops = &fsl_sai_pcm_dai_ops, }; @@ -692,42 +1057,90 @@ static const struct snd_soc_component_driver fsl_component = { .name = "fsl-sai", }; -static struct reg_default fsl_sai_reg_defaults[] = { - {FSL_SAI_TCR1, 0}, - {FSL_SAI_TCR2, 0}, - {FSL_SAI_TCR3, 0}, - {FSL_SAI_TCR4, 0}, - {FSL_SAI_TCR5, 0}, - {FSL_SAI_TDR, 0}, +static struct reg_default fsl_sai_v2_reg_defaults[] = { + {FSL_SAI_TCR1(0), 0}, + {FSL_SAI_TCR2(0), 0}, + {FSL_SAI_TCR3(0), 0}, + {FSL_SAI_TCR4(0), 0}, + {FSL_SAI_TCR5(0), 0}, + {FSL_SAI_TDR0, 0}, + {FSL_SAI_TDR1, 0}, + {FSL_SAI_TMR, 0}, + {FSL_SAI_RCR1(0), 0}, + {FSL_SAI_RCR2(0), 0}, + {FSL_SAI_RCR3(0), 0}, + {FSL_SAI_RCR4(0), 0}, + {FSL_SAI_RCR5(0), 0}, + {FSL_SAI_RMR, 0}, +}; + +static struct reg_default fsl_sai_v3_reg_defaults[] = { + {FSL_SAI_TCR1(8), 0}, + {FSL_SAI_TCR2(8), 0}, + {FSL_SAI_TCR3(8), 0}, + {FSL_SAI_TCR4(8), 0}, + {FSL_SAI_TCR5(8), 0}, + {FSL_SAI_TDR0, 0}, + {FSL_SAI_TDR1, 0}, + {FSL_SAI_TDR2, 0}, + {FSL_SAI_TDR3, 0}, + {FSL_SAI_TDR4, 0}, + {FSL_SAI_TDR5, 0}, + {FSL_SAI_TDR6, 0}, + {FSL_SAI_TDR7, 0}, {FSL_SAI_TMR, 0}, - {FSL_SAI_RCR1, 0}, - {FSL_SAI_RCR2, 0}, - {FSL_SAI_RCR3, 0}, - {FSL_SAI_RCR4, 0}, - {FSL_SAI_RCR5, 0}, + {FSL_SAI_RCR1(8), 0}, + {FSL_SAI_RCR2(8), 0}, + {FSL_SAI_RCR3(8), 0}, + {FSL_SAI_RCR4(8), 0}, + {FSL_SAI_RCR5(8), 0}, {FSL_SAI_RMR, 0}, + {FSL_SAI_MCTL, 0}, + {FSL_SAI_MDIV, 0}, }; static bool fsl_sai_readable_reg(struct device *dev, unsigned int reg) { + struct fsl_sai *sai = dev_get_drvdata(dev); + unsigned char offset = sai->soc->reg_offset; + + if (reg >= FSL_SAI_TCSR(offset) && reg <= FSL_SAI_TCR5(offset)) + return true; + + if (reg >= FSL_SAI_RCSR(offset) && reg <= FSL_SAI_RCR5(offset)) + return true; + switch (reg) { - case FSL_SAI_TCSR: - case FSL_SAI_TCR1: - case FSL_SAI_TCR2: - case FSL_SAI_TCR3: - case FSL_SAI_TCR4: - case FSL_SAI_TCR5: - case FSL_SAI_TFR: + case FSL_SAI_TFR0: + case FSL_SAI_TFR1: + case FSL_SAI_TFR2: + case FSL_SAI_TFR3: + case FSL_SAI_TFR4: + case FSL_SAI_TFR5: + case FSL_SAI_TFR6: + case FSL_SAI_TFR7: case FSL_SAI_TMR: - case FSL_SAI_RCSR: - case FSL_SAI_RCR1: - case FSL_SAI_RCR2: - case FSL_SAI_RCR3: - case FSL_SAI_RCR4: - case FSL_SAI_RCR5: - case FSL_SAI_RDR: - case FSL_SAI_RFR: + case FSL_SAI_RDR0: + case FSL_SAI_RDR1: + case FSL_SAI_RDR2: + case FSL_SAI_RDR3: + case FSL_SAI_RDR4: + case FSL_SAI_RDR5: + case FSL_SAI_RDR6: + case FSL_SAI_RDR7: + case FSL_SAI_RFR0: + case FSL_SAI_RFR1: + case FSL_SAI_RFR2: + case FSL_SAI_RFR3: + case FSL_SAI_RFR4: + case FSL_SAI_RFR5: + case FSL_SAI_RFR6: + case FSL_SAI_RFR7: case FSL_SAI_RMR: + case FSL_SAI_MCTL: + case FSL_SAI_MDIV: + case FSL_SAI_VERID: + case FSL_SAI_PARAM: return true; default: return false; @@ -736,12 +1149,41 @@ static bool fsl_sai_readable_reg(struct device *dev, unsigned int reg) static bool fsl_sai_volatile_reg(struct device *dev, unsigned int reg) { + struct fsl_sai *sai = dev_get_drvdata(dev); + unsigned char offset = sai->soc->reg_offset; + + if (reg == FSL_SAI_TCSR(offset) || reg == FSL_SAI_RCSR(offset)) + return true; + + if (sai->soc->reg_offset == 8 && (reg == FSL_SAI_VERID || + reg == FSL_SAI_PARAM)) + return true; + switch (reg) { - case FSL_SAI_TCSR: - case FSL_SAI_RCSR: - case FSL_SAI_TFR: - case FSL_SAI_RFR: - case FSL_SAI_RDR: + case FSL_SAI_TFR0: + case FSL_SAI_TFR1: + case FSL_SAI_TFR2: + case FSL_SAI_TFR3: + case FSL_SAI_TFR4: + case FSL_SAI_TFR5: + case FSL_SAI_TFR6: + case FSL_SAI_TFR7: + case FSL_SAI_RFR0: + case FSL_SAI_RFR1: + case FSL_SAI_RFR2: + case FSL_SAI_RFR3: + case FSL_SAI_RFR4: + case FSL_SAI_RFR5: + case FSL_SAI_RFR6: + case FSL_SAI_RFR7: + case FSL_SAI_RDR0: + case FSL_SAI_RDR1: + case FSL_SAI_RDR2: + case FSL_SAI_RDR3: + case FSL_SAI_RDR4: + case FSL_SAI_RDR5: + case FSL_SAI_RDR6: + case FSL_SAI_RDR7: return true; default: return false; @@ -750,45 +1192,77 @@ static bool fsl_sai_volatile_reg(struct device *dev, unsigned int reg) static bool fsl_sai_writeable_reg(struct device *dev, unsigned int reg) { + struct fsl_sai *sai = dev_get_drvdata(dev); + unsigned char offset = sai->soc->reg_offset; + + if (reg >= FSL_SAI_TCSR(offset) && reg <= FSL_SAI_TCR5(offset)) + return true; + + if (reg >= FSL_SAI_RCSR(offset) && reg <= FSL_SAI_RCR5(offset)) + return true; + switch (reg) { - case FSL_SAI_TCSR: - case FSL_SAI_TCR1: - case FSL_SAI_TCR2: - case FSL_SAI_TCR3: - case FSL_SAI_TCR4: - case FSL_SAI_TCR5: - case FSL_SAI_TDR: + case FSL_SAI_TDR0: + case FSL_SAI_TDR1: + case FSL_SAI_TDR2: + case FSL_SAI_TDR3: + case FSL_SAI_TDR4: + case FSL_SAI_TDR5: + case FSL_SAI_TDR6: + case FSL_SAI_TDR7: case FSL_SAI_TMR: - case FSL_SAI_RCSR: - case FSL_SAI_RCR1: - case FSL_SAI_RCR2: - case FSL_SAI_RCR3: - case FSL_SAI_RCR4: - case FSL_SAI_RCR5: case FSL_SAI_RMR: + case FSL_SAI_MCTL: + case FSL_SAI_MDIV: return true; default: return false; } } -static const struct regmap_config fsl_sai_regmap_config = { +static const struct regmap_config fsl_sai_v2_regmap_config = { .reg_bits = 32, .reg_stride = 4, .val_bits = 32, .max_register = FSL_SAI_RMR, - .reg_defaults = fsl_sai_reg_defaults, - .num_reg_defaults = ARRAY_SIZE(fsl_sai_reg_defaults), + .reg_defaults = fsl_sai_v2_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(fsl_sai_v2_reg_defaults), + .readable_reg = fsl_sai_readable_reg, + .volatile_reg = fsl_sai_volatile_reg, + .writeable_reg = fsl_sai_writeable_reg, + .cache_type = REGCACHE_FLAT, +}; + +static const struct regmap_config fsl_sai_v3_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + + .max_register = FSL_SAI_MDIV, + .reg_defaults = fsl_sai_v3_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(fsl_sai_v3_reg_defaults), .readable_reg = fsl_sai_readable_reg, .volatile_reg = fsl_sai_volatile_reg, .writeable_reg = fsl_sai_writeable_reg, .cache_type = REGCACHE_FLAT, }; +static const struct of_device_id fsl_sai_ids[] = { + { .compatible = "fsl,vf610-sai", .data = &fsl_sai_vf610 }, + { .compatible = "fsl,imx6sx-sai", .data = &fsl_sai_imx6sx }, + { .compatible = "fsl,imx6ul-sai", .data = &fsl_sai_imx6ul }, + { .compatible = "fsl,imx7ulp-sai", .data = &fsl_sai_imx7ulp }, + { .compatible = "fsl,imx8mq-sai", .data = &fsl_sai_imx8mq }, + { .compatible = "fsl,imx8qm-sai", .data = &fsl_sai_imx8qm }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, fsl_sai_ids); + static int fsl_sai_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; + const struct of_device_id *of_id; struct fsl_sai *sai; struct regmap *gpr; struct resource *res; @@ -796,6 +1270,10 @@ static int fsl_sai_probe(struct platform_device *pdev) char tmp[8]; int irq, ret, i; int index; + int firstbitidx, nextbitidx; + u32 buffer_size; + struct regmap_config fsl_sai_regmap_config = fsl_sai_v2_regmap_config; + unsigned long irqflags = 0; sai = devm_kzalloc(&pdev->dev, sizeof(*sai), GFP_KERNEL); if (!sai) @@ -803,17 +1281,21 @@ static int fsl_sai_probe(struct platform_device *pdev) sai->pdev = pdev; - if (of_device_is_compatible(np, "fsl,imx6sx-sai") || - of_device_is_compatible(np, "fsl,imx6ul-sai")) - sai->sai_on_imx = true; + of_id = of_match_device(fsl_sai_ids, &pdev->dev); + if (!of_id || !of_id->data) + return -EINVAL; sai->is_lsb_first = of_property_read_bool(np, "lsb-first"); + sai->soc = of_id->data; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(base)) return PTR_ERR(base); + if (sai->soc->reg_offset == 8) + fsl_sai_regmap_config = fsl_sai_v3_regmap_config; + sai->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "bus", base, &fsl_sai_regmap_config); @@ -834,24 +1316,98 @@ static int fsl_sai_probe(struct platform_device *pdev) sai->bus_clk = NULL; } - sai->mclk_clk[0] = sai->bus_clk; - for (i = 1; i < FSL_SAI_MCLK_MAX; i++) { + for (i = 0; i < FSL_SAI_MCLK_MAX; i++) { sprintf(tmp, "mclk%d", i); sai->mclk_clk[i] = devm_clk_get(&pdev->dev, tmp); if (IS_ERR(sai->mclk_clk[i])) { dev_err(&pdev->dev, "failed to get mclk%d clock: %ld\n", - i + 1, PTR_ERR(sai->mclk_clk[i])); + i, PTR_ERR(sai->mclk_clk[i])); sai->mclk_clk[i] = NULL; } } + sai->pll8k_clk = devm_clk_get(&pdev->dev, "pll8k"); + if (IS_ERR(sai->pll8k_clk)) + sai->pll8k_clk = NULL; + + sai->pll11k_clk = devm_clk_get(&pdev->dev, "pll11k"); + if (IS_ERR(sai->pll11k_clk)) + sai->pll11k_clk = NULL; + + if (of_find_property(np, "fsl,sai-multi-lane", NULL)) + sai->is_multi_lane = true; + + /*dataline mask for rx and tx*/ + ret = of_property_read_u32_index(np, "fsl,dataline", 0, &sai->dataline[0]); + if (ret) + sai->dataline[0] = 1; + + ret = of_property_read_u32_index(np, "fsl,dataline", 1, &sai->dataline[1]); + if (ret) + sai->dataline[1] = 1; + + if ((sai->dataline[0] & (~sai->soc->dataline)) || sai->dataline[1] & (~sai->soc->dataline)) { + dev_err(&pdev->dev, "dataline setting error, Mask is 0x%x\n", sai->soc->dataline); + return -EINVAL; + } + + for (i = 0; i < 2; i++) { + firstbitidx = find_first_bit((const unsigned long *)&sai->dataline[i], 8); + nextbitidx = find_next_bit((const unsigned long *)&sai->dataline[i], 8, firstbitidx+1); + sai->dataline_off[i] = nextbitidx - firstbitidx - 1; + + if (sai->dataline_off[i] < 0 || sai->dataline_off[i] >= 7) + sai->dataline_off[i] = 0; + } + + ret = of_property_read_u32_index(np, "fsl,dataline,dsd", 0, &sai->dataline_dsd[0]); + if (ret) + sai->dataline_dsd[0] = 1; + + ret = of_property_read_u32_index(np, "fsl,dataline,dsd", 1, &sai->dataline_dsd[1]); + if (ret) + sai->dataline_dsd[1] = 1; + + if ((sai->dataline_dsd[0] & (~sai->soc->dataline)) || sai->dataline_dsd[1] & (~sai->soc->dataline)) { + dev_err(&pdev->dev, "dataline setting error, Mask is 0x%x\n", sai->soc->dataline); + return -EINVAL; + } + + for (i = 0; i < 2; i++) { + firstbitidx = find_first_bit((const unsigned long *)&sai->dataline_dsd[i], 8); + nextbitidx = find_next_bit((const unsigned long *)&sai->dataline_dsd[i], 8, firstbitidx+1); + sai->dataline_off_dsd[i] = nextbitidx - firstbitidx - 1; + + if (sai->dataline_off_dsd[i] < 0 || sai->dataline_off_dsd[i] >= 7) + sai->dataline_off_dsd[i] = 0; + } + + if ((of_find_property(np, "fsl,i2s-xtor", NULL) != NULL) || + (of_find_property(np, "fsl,txm-rxs", NULL) != NULL)) + { + sai->masterflag[FSL_FMT_TRANSMITTER] = SND_SOC_DAIFMT_CBS_CFS; + sai->masterflag[FSL_FMT_RECEIVER] = SND_SOC_DAIFMT_CBM_CFM; + } else { + if (!of_property_read_u32(np, "fsl,txmasterflag", + &sai->masterflag[FSL_FMT_TRANSMITTER])) + sai->masterflag[FSL_FMT_TRANSMITTER] <<= 12; + if (!of_property_read_u32(np, "fsl,rxmasterflag", + &sai->masterflag[FSL_FMT_RECEIVER])) + sai->masterflag[FSL_FMT_RECEIVER] <<= 12; + } + irq = platform_get_irq(pdev, 0); if (irq < 0) { dev_err(&pdev->dev, "no irq for node %s\n", pdev->name); return irq; } - ret = devm_request_irq(&pdev->dev, irq, fsl_sai_isr, 0, np->name, sai); + /* SAI shared interrupt */ + if (of_property_read_bool(np, "fsl,shared-interrupt")) + irqflags = IRQF_SHARED; + + ret = devm_request_irq(&pdev->dev, irq, fsl_sai_isr, irqflags, np->name, + sai); if (ret) { dev_err(&pdev->dev, "failed to claim irq %u\n", irq); return ret; @@ -900,59 +1456,120 @@ static int fsl_sai_probe(struct platform_device *pdev) MCLK_DIR(index)); } - sai->dma_params_rx.addr = res->start + FSL_SAI_RDR; - sai->dma_params_tx.addr = res->start + FSL_SAI_TDR; + sai->dma_params_rx.chan_name = "rx"; + sai->dma_params_tx.chan_name = "tx"; + sai->dma_params_rx.addr = res->start + FSL_SAI_RDR0; + sai->dma_params_tx.addr = res->start + FSL_SAI_TDR0; sai->dma_params_rx.maxburst = FSL_SAI_MAXBURST_RX; sai->dma_params_tx.maxburst = FSL_SAI_MAXBURST_TX; + sai->pinctrl = devm_pinctrl_get(&pdev->dev); + platform_set_drvdata(pdev, sai); + pm_runtime_enable(&pdev->dev); + + regcache_cache_only(sai->regmap, true); + ret = devm_snd_soc_register_component(&pdev->dev, &fsl_component, &fsl_sai_dai, 1); if (ret) return ret; - if (sai->sai_on_imx) - return imx_pcm_dma_init(pdev, IMX_SAI_DMABUF_SIZE); + if (of_property_read_u32(np, "fsl,dma-buffer-size", &buffer_size)) + buffer_size = IMX_SAI_DMABUF_SIZE; + + if (sai->soc->imx) + return imx_pcm_platform_register(&pdev->dev); else return devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); } -static const struct of_device_id fsl_sai_ids[] = { - { .compatible = "fsl,vf610-sai", }, - { .compatible = "fsl,imx6sx-sai", }, - { .compatible = "fsl,imx6ul-sai", }, - { /* sentinel */ } -}; -MODULE_DEVICE_TABLE(of, fsl_sai_ids); - -#ifdef CONFIG_PM_SLEEP -static int fsl_sai_suspend(struct device *dev) +#ifdef CONFIG_PM +static int fsl_sai_runtime_resume(struct device *dev) { struct fsl_sai *sai = dev_get_drvdata(dev); + unsigned char offset = sai->soc->reg_offset; + int ret; - regcache_cache_only(sai->regmap, true); + ret = clk_prepare_enable(sai->bus_clk); + if (ret) { + dev_err(dev, "failed to enable bus clock: %d\n", ret); + return ret; + } + + if (sai->mclk_streams & BIT(SNDRV_PCM_STREAM_PLAYBACK)) { + ret = clk_prepare_enable(sai->mclk_clk[sai->mclk_id[1]]); + if (ret) + goto disable_bus_clk; + } + + if (sai->mclk_streams & BIT(SNDRV_PCM_STREAM_CAPTURE)) { + ret = clk_prepare_enable(sai->mclk_clk[sai->mclk_id[0]]); + if (ret) + goto disable_tx_clk; + } + + request_bus_freq(BUS_FREQ_AUDIO); + + if (sai->soc->flags & SAI_FLAG_PMQOS) + pm_qos_add_request(&sai->pm_qos_req, + PM_QOS_CPU_DMA_LATENCY, 0); + + regcache_cache_only(sai->regmap, false); regcache_mark_dirty(sai->regmap); + regmap_write(sai->regmap, FSL_SAI_TCSR(offset), FSL_SAI_CSR_SR); + regmap_write(sai->regmap, FSL_SAI_RCSR(offset), FSL_SAI_CSR_SR); + usleep_range(1000, 2000); + regmap_write(sai->regmap, FSL_SAI_TCSR(offset), 0); + regmap_write(sai->regmap, FSL_SAI_RCSR(offset), 0); + ret = regcache_sync(sai->regmap); + if (ret) + goto disable_rx_clk; + return 0; + +disable_rx_clk: + if (sai->mclk_streams & BIT(SNDRV_PCM_STREAM_CAPTURE)) + clk_disable_unprepare(sai->mclk_clk[sai->mclk_id[0]]); +disable_tx_clk: + if (sai->mclk_streams & BIT(SNDRV_PCM_STREAM_PLAYBACK)) + clk_disable_unprepare(sai->mclk_clk[sai->mclk_id[1]]); +disable_bus_clk: + clk_disable_unprepare(sai->bus_clk); + + return ret; } -static int fsl_sai_resume(struct device *dev) +static int fsl_sai_runtime_suspend(struct device *dev) { struct fsl_sai *sai = dev_get_drvdata(dev); - regcache_cache_only(sai->regmap, false); - regmap_write(sai->regmap, FSL_SAI_TCSR, FSL_SAI_CSR_SR); - regmap_write(sai->regmap, FSL_SAI_RCSR, FSL_SAI_CSR_SR); - usleep_range(1000, 2000); - regmap_write(sai->regmap, FSL_SAI_TCSR, 0); - regmap_write(sai->regmap, FSL_SAI_RCSR, 0); - return regcache_sync(sai->regmap); + regcache_cache_only(sai->regmap, true); + + release_bus_freq(BUS_FREQ_AUDIO); + + if (sai->soc->flags & SAI_FLAG_PMQOS) + pm_qos_remove_request(&sai->pm_qos_req); + + if (sai->mclk_streams & BIT(SNDRV_PCM_STREAM_CAPTURE)) + clk_disable_unprepare(sai->mclk_clk[sai->mclk_id[0]]); + + if (sai->mclk_streams & BIT(SNDRV_PCM_STREAM_PLAYBACK)) + clk_disable_unprepare(sai->mclk_clk[sai->mclk_id[1]]); + + clk_disable_unprepare(sai->bus_clk); + + return 0; } -#endif /* CONFIG_PM_SLEEP */ +#endif static const struct dev_pm_ops fsl_sai_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(fsl_sai_suspend, fsl_sai_resume) + SET_RUNTIME_PM_OPS(fsl_sai_runtime_suspend, + fsl_sai_runtime_resume, + NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) }; static struct platform_driver fsl_sai_driver = { diff --git a/sound/soc/fsl/fsl_sai.h b/sound/soc/fsl/fsl_sai.h index d9ed7be8cb34..eebe77efdbb9 100644 --- a/sound/soc/fsl/fsl_sai.h +++ b/sound/soc/fsl/fsl_sai.h @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 Freescale Semiconductor, Inc. + * Copyright 2012-2016 Freescale Semiconductor, Inc. * * 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 @@ -9,45 +9,91 @@ #ifndef __FSL_SAI_H #define __FSL_SAI_H +#include <linux/pm_qos.h> #include <sound/dmaengine_pcm.h> #define FSL_SAI_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ - SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE |\ - SNDRV_PCM_FMTBIT_S32_LE) + SNDRV_PCM_FMTBIT_S32_LE |\ + SNDRV_PCM_FMTBIT_DSD_U8 |\ + SNDRV_PCM_FMTBIT_DSD_U16_LE |\ + SNDRV_PCM_FMTBIT_DSD_U32_LE) /* SAI Register Map Register */ -#define FSL_SAI_TCSR 0x00 /* SAI Transmit Control */ -#define FSL_SAI_TCR1 0x04 /* SAI Transmit Configuration 1 */ -#define FSL_SAI_TCR2 0x08 /* SAI Transmit Configuration 2 */ -#define FSL_SAI_TCR3 0x0c /* SAI Transmit Configuration 3 */ -#define FSL_SAI_TCR4 0x10 /* SAI Transmit Configuration 4 */ -#define FSL_SAI_TCR5 0x14 /* SAI Transmit Configuration 5 */ -#define FSL_SAI_TDR 0x20 /* SAI Transmit Data */ -#define FSL_SAI_TFR 0x40 /* SAI Transmit FIFO */ +#define FSL_SAI_VERID 0x00 /* SAI Version ID Register */ +#define FSL_SAI_PARAM 0x04 /* SAI Parameter Register */ +#define FSL_SAI_TCSR(offset) (0x00 + offset) /* SAI Transmit Control */ +#define FSL_SAI_TCR1(offset) (0x04 + offset) /* SAI Transmit Configuration 1 */ +#define FSL_SAI_TCR2(offset) (0x08 + offset) /* SAI Transmit Configuration 2 */ +#define FSL_SAI_TCR3(offset) (0x0c + offset) /* SAI Transmit Configuration 3 */ +#define FSL_SAI_TCR4(offset) (0x10 + offset) /* SAI Transmit Configuration 4 */ +#define FSL_SAI_TCR5(offset) (0x14 + offset) /* SAI Transmit Configuration 5 */ +#define FSL_SAI_TDR0 0x20 /* SAI Transmit Data */ +#define FSL_SAI_TDR1 0x24 /* SAI Transmit Data */ +#define FSL_SAI_TDR2 0x28 /* SAI Transmit Data */ +#define FSL_SAI_TDR3 0x2C /* SAI Transmit Data */ +#define FSL_SAI_TDR4 0x30 /* SAI Transmit Data */ +#define FSL_SAI_TDR5 0x34 /* SAI Transmit Data */ +#define FSL_SAI_TDR6 0x38 /* SAI Transmit Data */ +#define FSL_SAI_TDR7 0x3C /* SAI Transmit Data */ +#define FSL_SAI_TFR0 0x40 /* SAI Transmit FIFO */ +#define FSL_SAI_TFR1 0x44 /* SAI Transmit FIFO */ +#define FSL_SAI_TFR2 0x48 /* SAI Transmit FIFO */ +#define FSL_SAI_TFR3 0x4C /* SAI Transmit FIFO */ +#define FSL_SAI_TFR4 0x50 /* SAI Transmit FIFO */ +#define FSL_SAI_TFR5 0x54 /* SAI Transmit FIFO */ +#define FSL_SAI_TFR6 0x58 /* SAI Transmit FIFO */ +#define FSL_SAI_TFR7 0x5C /* SAI Transmit FIFO */ #define FSL_SAI_TMR 0x60 /* SAI Transmit Mask */ -#define FSL_SAI_RCSR 0x80 /* SAI Receive Control */ -#define FSL_SAI_RCR1 0x84 /* SAI Receive Configuration 1 */ -#define FSL_SAI_RCR2 0x88 /* SAI Receive Configuration 2 */ -#define FSL_SAI_RCR3 0x8c /* SAI Receive Configuration 3 */ -#define FSL_SAI_RCR4 0x90 /* SAI Receive Configuration 4 */ -#define FSL_SAI_RCR5 0x94 /* SAI Receive Configuration 5 */ -#define FSL_SAI_RDR 0xa0 /* SAI Receive Data */ -#define FSL_SAI_RFR 0xc0 /* SAI Receive FIFO */ +#define FSL_SAI_TTCTL 0x70 /* SAI Transmit Timestamp Control Register */ +#define FSL_SAI_TTCTN 0x74 /* SAI Transmit Timestamp Counter Register */ +#define FSL_SAI_TBCTN 0x78 /* SAI Transmit Bit Counter Register */ +#define FSL_SAI_TTCAP 0x7C /* SAI Transmit Timestamp Capture */ + +#define FSL_SAI_RCSR(offset) (0x80 + offset) /* SAI Receive Control */ +#define FSL_SAI_RCR1(offset) (0x84 + offset) /* SAI Receive Configuration 1 */ +#define FSL_SAI_RCR2(offset) (0x88 + offset) /* SAI Receive Configuration 2 */ +#define FSL_SAI_RCR3(offset) (0x8c + offset) /* SAI Receive Configuration 3 */ +#define FSL_SAI_RCR4(offset) (0x90 + offset) /* SAI Receive Configuration 4 */ +#define FSL_SAI_RCR5(offset) (0x94 + offset) /* SAI Receive Configuration 5 */ +#define FSL_SAI_RDR0 0xa0 /* SAI Receive Data */ +#define FSL_SAI_RDR1 0xa4 /* SAI Receive Data */ +#define FSL_SAI_RDR2 0xa8 /* SAI Receive Data */ +#define FSL_SAI_RDR3 0xac /* SAI Receive Data */ +#define FSL_SAI_RDR4 0xb0 /* SAI Receive Data */ +#define FSL_SAI_RDR5 0xb4 /* SAI Receive Data */ +#define FSL_SAI_RDR6 0xb8 /* SAI Receive Data */ +#define FSL_SAI_RDR7 0xbc /* SAI Receive Data */ +#define FSL_SAI_RFR0 0xc0 /* SAI Receive FIFO */ +#define FSL_SAI_RFR1 0xc4 /* SAI Receive FIFO */ +#define FSL_SAI_RFR2 0xc8 /* SAI Receive FIFO */ +#define FSL_SAI_RFR3 0xcc /* SAI Receive FIFO */ +#define FSL_SAI_RFR4 0xd0 /* SAI Receive FIFO */ +#define FSL_SAI_RFR5 0xd4 /* SAI Receive FIFO */ +#define FSL_SAI_RFR6 0xd8 /* SAI Receive FIFO */ +#define FSL_SAI_RFR7 0xdc /* SAI Receive FIFO */ #define FSL_SAI_RMR 0xe0 /* SAI Receive Mask */ +#define FSL_SAI_RTCTL 0xf0 /* SAI Receive Timestamp Control Register */ +#define FSL_SAI_RTCTN 0xf4 /* SAI Receive Timestamp Counter Register */ +#define FSL_SAI_RBCTN 0xf8 /* SAI Receive Bit Counter Register */ +#define FSL_SAI_RTCAP 0xfc /* SAI Receive Timestamp Capture */ + +#define FSL_SAI_MCTL 0x100 /* SAI MCLK Control Register */ +#define FSL_SAI_MDIV 0x104 /* SAI MCLK Divide Register */ -#define FSL_SAI_xCSR(tx) (tx ? FSL_SAI_TCSR : FSL_SAI_RCSR) -#define FSL_SAI_xCR1(tx) (tx ? FSL_SAI_TCR1 : FSL_SAI_RCR1) -#define FSL_SAI_xCR2(tx) (tx ? FSL_SAI_TCR2 : FSL_SAI_RCR2) -#define FSL_SAI_xCR3(tx) (tx ? FSL_SAI_TCR3 : FSL_SAI_RCR3) -#define FSL_SAI_xCR4(tx) (tx ? FSL_SAI_TCR4 : FSL_SAI_RCR4) -#define FSL_SAI_xCR5(tx) (tx ? FSL_SAI_TCR5 : FSL_SAI_RCR5) +#define FSL_SAI_xCSR(tx, off) (tx ? FSL_SAI_TCSR(off) : FSL_SAI_RCSR(off)) +#define FSL_SAI_xCR1(tx, off) (tx ? FSL_SAI_TCR1(off) : FSL_SAI_RCR1(off)) +#define FSL_SAI_xCR2(tx, off) (tx ? FSL_SAI_TCR2(off) : FSL_SAI_RCR2(off)) +#define FSL_SAI_xCR3(tx, off) (tx ? FSL_SAI_TCR3(off) : FSL_SAI_RCR3(off)) +#define FSL_SAI_xCR4(tx, off) (tx ? FSL_SAI_TCR4(off) : FSL_SAI_RCR4(off)) +#define FSL_SAI_xCR5(tx, off) (tx ? FSL_SAI_TCR5(off) : FSL_SAI_RCR5(off)) #define FSL_SAI_xDR(tx) (tx ? FSL_SAI_TDR : FSL_SAI_RDR) #define FSL_SAI_xFR(tx) (tx ? FSL_SAI_TFR : FSL_SAI_RFR) #define FSL_SAI_xMR(tx) (tx ? FSL_SAI_TMR : FSL_SAI_RMR) /* SAI Transmit/Receive Control Register */ #define FSL_SAI_CSR_TERE BIT(31) +#define FSL_SAI_CSR_SE BIT(30) #define FSL_SAI_CSR_FR BIT(25) #define FSL_SAI_CSR_SR BIT(24) #define FSL_SAI_CSR_xF_SHIFT 16 @@ -81,18 +127,29 @@ #define FSL_SAI_CR2_MSEL(ID) ((ID) << 26) #define FSL_SAI_CR2_BCP BIT(25) #define FSL_SAI_CR2_BCD_MSTR BIT(24) +#define FSL_SAI_CR2_BYP BIT(23) /* BCLK bypass */ #define FSL_SAI_CR2_DIV_MASK 0xff /* SAI Transmit and Receive Configuration 3 Register */ -#define FSL_SAI_CR3_TRCE BIT(16) +#define FSL_SAI_CR3_TRCE_MASK (0xff << 16) +#define FSL_SAI_CR3_TRCE(x) (x << 16) #define FSL_SAI_CR3_WDFL(x) (x) #define FSL_SAI_CR3_WDFL_MASK 0x1f /* SAI Transmit and Receive Configuration 4 Register */ + +#define FSL_SAI_CR4_FCONT BIT(28) +#define FSL_SAI_CR4_FCOMB_SHIFT BIT(26) +#define FSL_SAI_CR4_FCOMB_SOFT BIT(27) +#define FSL_SAI_CR4_FCOMB_MASK (0x3 << 26) +#define FSL_SAI_CR4_FPACK_8 (0x2 << 24) +#define FSL_SAI_CR4_FPACK_16 (0x3 << 24) #define FSL_SAI_CR4_FRSZ(x) (((x) - 1) << 16) #define FSL_SAI_CR4_FRSZ_MASK (0x1f << 16) #define FSL_SAI_CR4_SYWD(x) (((x) - 1) << 8) #define FSL_SAI_CR4_SYWD_MASK (0x1f << 8) +#define FSL_SAI_CR4_CHMOD (1 << 5) +#define FSL_SAI_CR4_CHMOD_MASK (1 << 5) #define FSL_SAI_CR4_MF BIT(4) #define FSL_SAI_CR4_FSE BIT(3) #define FSL_SAI_CR4_FSP BIT(1) @@ -106,6 +163,33 @@ #define FSL_SAI_CR5_FBT(x) ((x) << 8) #define FSL_SAI_CR5_FBT_MASK (0x1f << 8) +/* SAI MCLK Control Register */ +#define FSL_SAI_MCTL_MCLK_EN BIT(30) /* MCLK Enable */ +#define FSL_SAI_MCTL_MSEL_MASK (0x3 << 24) +#define FSL_SAI_MCTL_MSEL(ID) ((ID) << 24) +#define FSL_SAI_MCTL_MSEL_BUS 0 +#define FSL_SAI_MCTL_MSEL_MCLK1 BIT(24) +#define FSL_SAI_MCTL_MSEL_MCLK2 BIT(25) +#define FSL_SAI_MCTL_MSEL_MCLK3 (BIT(24) | BIT(25)) +#define FSL_SAI_MCTL_DIV_EN BIT(23) +#define FSL_SAI_MCTL_DIV_MASK 0xFF + +/* SAI VERID Register */ +#define FSL_SAI_VER_ID_SHIFT 16 +#define FSL_SAI_VER_ID_MASK (0xFFFF << FSL_SAI_VER_ID_SHIFT) +#define FSL_SAI_VER_EFIFO_EN BIT(0) +#define FSL_SAI_VER_TSTMP_EN BIT(1) + +/* SAI PARAM Register */ +#define FSL_SAI_PAR_SPF_SHIFT 16 +#define FSL_SAI_PAR_SPF_MASK (0x0F << FSL_SAI_PAR_SPF_SHIFT) +#define FSL_SAI_PAR_WPF_SHIFT 8 +#define FSL_SAI_PAR_WPF_MASK (0x0F << FSL_SAI_PAR_WPF_SHIFT) +#define FSL_SAI_PAR_DLN_MASK (0x0F) + +/* SAI MCLK Divide Register */ +#define FSL_SAI_MDIV_MASK 0xFFFFF + /* SAI type */ #define FSL_SAI_DMA BIT(0) #define FSL_SAI_USE_AC97 BIT(1) @@ -129,25 +213,68 @@ #define FSL_SAI_MAXBURST_TX 6 #define FSL_SAI_MAXBURST_RX 6 +#define SAI_FLAG_PMQOS BIT(0) + +struct fsl_sai_soc_data { + unsigned int fifo_depth; + unsigned int fifos; + unsigned int dataline; + unsigned int flags; + unsigned char reg_offset; + bool imx; + /* True for EDMA because it needs period size multiple of maxburst */ + bool constrain_period_size; +}; + +struct fsl_sai_verid { + u32 id; + bool timestamp_en; + bool extfifo_en; + bool loaded; +}; + +struct fsl_sai_param { + u32 spf; /* max slots per frame */ + u32 wpf; /* words in fifo */ + u32 dln; /* number of datalines implemented */ +}; + struct fsl_sai { struct platform_device *pdev; struct regmap *regmap; struct clk *bus_clk; struct clk *mclk_clk[FSL_SAI_MCLK_MAX]; + struct clk *pll8k_clk; + struct clk *pll11k_clk; - bool is_slave_mode; + bool slave_mode[2]; bool is_lsb_first; bool is_dsp_mode; - bool sai_on_imx; + bool is_multi_lane; bool synchronous[2]; + bool is_stream_opened[2]; + bool is_dsd; + unsigned int dataline[2]; + unsigned int dataline_dsd[2]; + unsigned int dataline_off[2]; + unsigned int dataline_off_dsd[2]; + unsigned int masterflag[2]; unsigned int mclk_id[2]; unsigned int mclk_streams; unsigned int slots; unsigned int slot_width; + unsigned int bitclk_ratio; struct snd_dmaengine_dai_dma_data dma_params_rx; struct snd_dmaengine_dai_dma_data dma_params_tx; + const struct fsl_sai_soc_data *soc; + struct pm_qos_request pm_qos_req; + struct pinctrl *pinctrl; + struct pinctrl_state *pins_state; + + struct fsl_sai_verid verid; + struct fsl_sai_param param; }; #define TX 1 diff --git a/sound/soc/fsl/fsl_spdif.c b/sound/soc/fsl/fsl_spdif.c index 1ff467c9598a..a7a3639e50b2 100644 --- a/sound/soc/fsl/fsl_spdif.c +++ b/sound/soc/fsl/fsl_spdif.c @@ -1,7 +1,7 @@ /* * Freescale S/PDIF ALSA SoC Digital Audio Interface (DAI) driver * - * Copyright (C) 2013 Freescale Semiconductor, Inc. + * Copyright (C) 2013-2016 Freescale Semiconductor, Inc. * * Based on stmp3xxx_spdif_dai.c * Vladimir Barinov <vbarinov@embeddedalley.com> @@ -15,11 +15,14 @@ #include <linux/bitrev.h> #include <linux/clk.h> +#include <linux/clk-provider.h> #include <linux/module.h> #include <linux/of_address.h> #include <linux/of_device.h> #include <linux/of_irq.h> #include <linux/regmap.h> +#include <linux/pm_runtime.h> +#include <linux/busfreq-imx.h> #include <sound/asoundef.h> #include <sound/dmaengine_pcm.h> @@ -27,6 +30,7 @@ #include "fsl_spdif.h" #include "imx-pcm.h" +#include "fsl_dma_workaround.h" #define FSL_SPDIF_TXFIFO_WML 0x8 #define FSL_SPDIF_RXFIFO_WML 0x8 @@ -46,6 +50,17 @@ static u8 srpc_dpll_locked[] = { 0x0, 0x1, 0x2, 0x3, 0x4, 0xa, 0xb }; #define DEFAULT_RXCLK_SRC 1 +struct fsl_spdif_soc_data { + bool imx; + bool constrain_period_size; + bool dma_workaround; + u32 tx_burst; + u32 rx_burst; + u32 interrupts; + u64 tx_formats; + u64 rx_rates; +}; + /* * SPDIF control structure * Defines channel status, subcode and Q sub @@ -100,18 +115,82 @@ struct fsl_spdif_priv { bool dpll_locked; u32 txrate[SPDIF_TXRATE_MAX]; u8 txclk_df[SPDIF_TXRATE_MAX]; - u8 sysclk_df[SPDIF_TXRATE_MAX]; + u16 sysclk_df[SPDIF_TXRATE_MAX]; u8 txclk_src[SPDIF_TXRATE_MAX]; u8 rxclk_src; - struct clk *txclk[SPDIF_TXRATE_MAX]; + struct clk *txclk[STC_TXCLK_SRC_MAX]; struct clk *rxclk; struct clk *coreclk; struct clk *sysclk; struct clk *spbaclk; + const struct fsl_spdif_soc_data *soc; + struct fsl_dma_workaround_info *dma_info; struct snd_dmaengine_dai_dma_data dma_params_tx; struct snd_dmaengine_dai_dma_data dma_params_rx; /* regcache for SRPC */ u32 regcache_srpc; + struct clk *pll8k_clk; + struct clk *pll11k_clk; +}; + +static struct fsl_spdif_soc_data fsl_spdif_vf610 = { + .imx = false, + .dma_workaround = false, + .tx_burst = FSL_SPDIF_TXFIFO_WML, + .rx_burst = FSL_SPDIF_RXFIFO_WML, + .interrupts = 1, + .tx_formats = FSL_SPDIF_FORMATS_PLAYBACK, + .rx_rates = FSL_SPDIF_RATES_CAPTURE, + .constrain_period_size = false, +}; + +static struct fsl_spdif_soc_data fsl_spdif_imx35 = { + .imx = true, + .dma_workaround = false, + .tx_burst = FSL_SPDIF_TXFIFO_WML, + .rx_burst = FSL_SPDIF_RXFIFO_WML, + .interrupts = 1, + .tx_formats = FSL_SPDIF_FORMATS_PLAYBACK, + .rx_rates = FSL_SPDIF_RATES_CAPTURE, + .constrain_period_size = false, +}; + +/* + * In imx8qxp rev 1, the DMA request signal is not reverted. For SPDIF + * DMA request is low valid, but EDMA assert is high valid, so we + * need to use GPT to transfer the DMA request signal + */ +static struct fsl_spdif_soc_data fsl_spdif_imx8qxp_v1 = { + .imx = true, + .dma_workaround = true, + .tx_burst = 2, + .rx_burst = 2, + .interrupts = 2, + .tx_formats = SNDRV_PCM_FMTBIT_S24_LE, + .rx_rates = FSL_SPDIF_RATES_CAPTURE, + .constrain_period_size = true, +}; + +static struct fsl_spdif_soc_data fsl_spdif_imx8qm = { + .imx = true, + .dma_workaround = false, + .tx_burst = 2, + .rx_burst = 2, + .interrupts = 2, + .tx_formats = SNDRV_PCM_FMTBIT_S24_LE, + .rx_rates = (FSL_SPDIF_RATES_CAPTURE | SNDRV_PCM_RATE_192000), + .constrain_period_size = true, +}; + +static struct fsl_spdif_soc_data fsl_spdif_imx8mm = { + .imx = true, + .dma_workaround = false, + .tx_burst = FSL_SPDIF_TXFIFO_WML, + .rx_burst = FSL_SPDIF_RXFIFO_WML, + .interrupts = 1, + .tx_formats = FSL_SPDIF_FORMATS_PLAYBACK, + .rx_rates = (FSL_SPDIF_RATES_CAPTURE | SNDRV_PCM_RATE_192000), + .constrain_period_size = false, }; /* DPLL locked and lock loss interrupt handler */ @@ -380,8 +459,8 @@ static int spdif_set_sample_rate(struct snd_pcm_substream *substream, struct platform_device *pdev = spdif_priv->pdev; unsigned long csfs = 0; u32 stc, mask, rate; - u8 clk, txclk_df, sysclk_df; - int ret; + u8 clk, txclk_df; + u16 sysclk_df; switch (sample_rate) { case 32000: @@ -423,23 +502,10 @@ static int spdif_set_sample_rate(struct snd_pcm_substream *substream, sysclk_df = spdif_priv->sysclk_df[rate]; - /* Don't mess up the clocks from other modules */ - if (clk != STC_TXCLK_SPDIF_ROOT) - goto clk_set_bypass; - - /* The S/PDIF block needs a clock of 64 * fs * txclk_df */ - ret = clk_set_rate(spdif_priv->txclk[rate], - 64 * sample_rate * txclk_df); - if (ret) { - dev_err(&pdev->dev, "failed to set tx clock rate\n"); - return ret; - } - -clk_set_bypass: dev_dbg(&pdev->dev, "expected clock rate = %d\n", (64 * sample_rate * txclk_df * sysclk_df)); dev_dbg(&pdev->dev, "actual clock rate = %ld\n", - clk_get_rate(spdif_priv->txclk[rate])); + clk_get_rate(spdif_priv->txclk[clk])); /* set fs field in consumer channel status */ spdif_set_cstatus(ctrl, IEC958_AES3_CON_FS, csfs); @@ -465,25 +531,10 @@ static int fsl_spdif_startup(struct snd_pcm_substream *substream, struct platform_device *pdev = spdif_priv->pdev; struct regmap *regmap = spdif_priv->regmap; u32 scr, mask; - int i; int ret; /* Reset module and interrupts only for first initialization */ if (!cpu_dai->active) { - ret = clk_prepare_enable(spdif_priv->coreclk); - if (ret) { - dev_err(&pdev->dev, "failed to enable core clock\n"); - return ret; - } - - if (!IS_ERR(spdif_priv->spbaclk)) { - ret = clk_prepare_enable(spdif_priv->spbaclk); - if (ret) { - dev_err(&pdev->dev, "failed to enable spba clock\n"); - goto err_spbaclk; - } - } - ret = spdif_softreset(spdif_priv); if (ret) { dev_err(&pdev->dev, "failed to soft reset\n"); @@ -501,35 +552,30 @@ static int fsl_spdif_startup(struct snd_pcm_substream *substream, mask = SCR_TXFIFO_AUTOSYNC_MASK | SCR_TXFIFO_CTRL_MASK | SCR_TXSEL_MASK | SCR_USRC_SEL_MASK | SCR_TXFIFO_FSEL_MASK; - for (i = 0; i < SPDIF_TXRATE_MAX; i++) { - ret = clk_prepare_enable(spdif_priv->txclk[i]); - if (ret) - goto disable_txclk; - } } else { scr = SCR_RXFIFO_FSEL_IF8 | SCR_RXFIFO_AUTOSYNC; mask = SCR_RXFIFO_FSEL_MASK | SCR_RXFIFO_AUTOSYNC_MASK| SCR_RXFIFO_CTL_MASK | SCR_RXFIFO_OFF_MASK; - ret = clk_prepare_enable(spdif_priv->rxclk); - if (ret) - goto err; } regmap_update_bits(regmap, REG_SPDIF_SCR, mask, scr); /* Power up SPDIF module */ regmap_update_bits(regmap, REG_SPDIF_SCR, SCR_LOW_POWER, 0); + if (spdif_priv->soc->constrain_period_size) { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + spdif_priv->dma_params_tx.maxburst); + else + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + spdif_priv->dma_params_rx.maxburst); + } + return 0; -disable_txclk: - for (i--; i >= 0; i--) - clk_disable_unprepare(spdif_priv->txclk[i]); err: - if (!IS_ERR(spdif_priv->spbaclk)) - clk_disable_unprepare(spdif_priv->spbaclk); -err_spbaclk: - clk_disable_unprepare(spdif_priv->coreclk); - return ret; } @@ -539,20 +585,17 @@ static void fsl_spdif_shutdown(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = substream->private_data; struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(rtd->cpu_dai); struct regmap *regmap = spdif_priv->regmap; - u32 scr, mask, i; + u32 scr, mask; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { scr = 0; mask = SCR_TXFIFO_AUTOSYNC_MASK | SCR_TXFIFO_CTRL_MASK | SCR_TXSEL_MASK | SCR_USRC_SEL_MASK | SCR_TXFIFO_FSEL_MASK; - for (i = 0; i < SPDIF_TXRATE_MAX; i++) - clk_disable_unprepare(spdif_priv->txclk[i]); } else { scr = SCR_RXFIFO_OFF | SCR_RXFIFO_CTL_ZERO; mask = SCR_RXFIFO_FSEL_MASK | SCR_RXFIFO_AUTOSYNC_MASK| SCR_RXFIFO_CTL_MASK | SCR_RXFIFO_OFF_MASK; - clk_disable_unprepare(spdif_priv->rxclk); } regmap_update_bits(regmap, REG_SPDIF_SCR, mask, scr); @@ -561,10 +604,8 @@ static void fsl_spdif_shutdown(struct snd_pcm_substream *substream, spdif_intr_status_clear(spdif_priv); regmap_update_bits(regmap, REG_SPDIF_SCR, SCR_LOW_POWER, SCR_LOW_POWER); - if (!IS_ERR(spdif_priv->spbaclk)) - clk_disable_unprepare(spdif_priv->spbaclk); - clk_disable_unprepare(spdif_priv->coreclk); } + } static int fsl_spdif_hw_params(struct snd_pcm_substream *substream, @@ -578,6 +619,9 @@ static int fsl_spdif_hw_params(struct snd_pcm_substream *substream, u32 sample_rate = params_rate(params); int ret = 0; + if (spdif_priv->soc->dma_workaround) + configure_gpt_dma(substream, spdif_priv->dma_info); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { ret = spdif_set_sample_rate(substream, sample_rate); if (ret) { @@ -626,14 +670,190 @@ static int fsl_spdif_trigger(struct snd_pcm_substream *substream, return 0; } +static int fsl_spdif_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(rtd->cpu_dai); + + if (spdif_priv->soc->dma_workaround) + clear_gpt_dma(substream, spdif_priv->dma_info); + + return 0; +} + +static u32 fsl_spdif_txclk_caldiv(struct fsl_spdif_priv *spdif_priv, + struct clk *clk, u64 savesub, + enum spdif_txrate index, bool round) +{ + const u32 rate[] = { 32000, 44100, 48000, 96000, 192000 }; + bool is_sysclk = clk_is_match(clk, spdif_priv->sysclk); + u64 rate_actual, sub; + u32 arate; + u16 sysclk_dfmin, sysclk_dfmax, sysclk_df; + u8 txclk_df; + + /* The sysclk has an extra divisor [2, 512] */ + sysclk_dfmin = is_sysclk ? 2 : 1; + sysclk_dfmax = is_sysclk ? 512 : 1; + + for (sysclk_df = sysclk_dfmin; sysclk_df <= sysclk_dfmax; sysclk_df++) { + for (txclk_df = 1; txclk_df <= 128; txclk_df++) { + rate_actual = clk_get_rate(clk); + + arate = rate_actual / 64; + arate /= txclk_df * sysclk_df; + + if (arate == rate[index]) { + /* We are lucky */ + savesub = 0; + spdif_priv->txclk_df[index] = txclk_df; + spdif_priv->sysclk_df[index] = sysclk_df; + spdif_priv->txrate[index] = arate; + goto out; + } else if (arate / rate[index] == 1) { + /* A little bigger than expect */ + sub = (u64)(arate - rate[index]) * 100000; + do_div(sub, rate[index]); + if (sub >= savesub) + continue; + savesub = sub; + spdif_priv->txclk_df[index] = txclk_df; + spdif_priv->sysclk_df[index] = sysclk_df; + spdif_priv->txrate[index] = arate; + } else if (rate[index] / arate == 1) { + /* A little smaller than expect */ + sub = (u64)(rate[index] - arate) * 100000; + do_div(sub, rate[index]); + if (sub >= savesub) + continue; + savesub = sub; + spdif_priv->txclk_df[index] = txclk_df; + spdif_priv->sysclk_df[index] = sysclk_df; + spdif_priv->txrate[index] = arate; + } + } + } + +out: + return savesub; +} + +static int fsl_spdif_probe_txclk(struct fsl_spdif_priv *spdif_priv, + enum spdif_txrate index) +{ + const u32 rate[] = { 32000, 44100, 48000, 96000, 192000 }; + struct platform_device *pdev = spdif_priv->pdev; + struct device *dev = &pdev->dev; + u64 savesub = 100000, ret; + struct clk *clk; + int i; + + for (i = 0; i < STC_TXCLK_SRC_MAX; i++) { + clk = spdif_priv->txclk[i]; + if (IS_ERR(clk)) { + dev_err(dev, "no rxtx%d clock in devicetree\n", i); + return PTR_ERR(clk); + } + if (!clk_get_rate(clk)) + continue; + + ret = fsl_spdif_txclk_caldiv(spdif_priv, clk, savesub, index, + i == STC_TXCLK_SPDIF_ROOT); + if (savesub == ret) + continue; + + savesub = ret; + spdif_priv->txclk_src[index] = i; + + /* To quick catch a divisor, we allow a 0.1% deviation */ + if (savesub < 100) + break; + } + + dev_dbg(&pdev->dev, "use rxtx%d as tx clock source for %dHz sample rate\n", + spdif_priv->txclk_src[index], rate[index]); + dev_dbg(&pdev->dev, "use txclk df %d for %dHz sample rate\n", + spdif_priv->txclk_df[index], rate[index]); + if (clk_is_match(spdif_priv->txclk[spdif_priv->txclk_src[index]], spdif_priv->sysclk)) + dev_dbg(&pdev->dev, "use sysclk df %d for %dHz sample rate\n", + spdif_priv->sysclk_df[index], rate[index]); + dev_dbg(&pdev->dev, "the best rate for %dHz sample rate is %dHz\n", + rate[index], spdif_priv->txrate[index]); + + return 0; +} + +static int fsl_spdif_set_dai_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + struct fsl_spdif_priv *data = snd_soc_dai_get_drvdata(cpu_dai); + struct platform_device *pdev = data->pdev; + struct device *dev = &pdev->dev; + struct clk *clk, *p, *pll = 0, *npll = 0; + u64 ratio = freq; + int ret, i; + + if (dir != SND_SOC_CLOCK_OUT || freq == 0 || clk_id != STC_TXCLK_SPDIF_ROOT) + return 0; + + if (data->pll8k_clk == NULL || data->pll11k_clk == NULL) + return 0; + + clk = data->txclk[clk_id]; + if (IS_ERR_OR_NULL(clk)) { + dev_err(dev, "no rxtx%d clock in devicetree\n", clk_id); + return PTR_ERR(clk); + } + + p = clk; + while (p && data->pll8k_clk && data->pll11k_clk) { + struct clk *pp = clk_get_parent(p); + + if (clk_is_match(pp, data->pll8k_clk) || + clk_is_match(pp, data->pll11k_clk)) { + pll = pp; + break; + } + p = pp; + } + + if (pll) { + npll = (do_div(ratio, 8000) ? data->pll11k_clk : data->pll8k_clk); + if (!clk_is_match(pll, npll)) { + ret = clk_set_parent(p, npll); + if (ret < 0) + dev_warn(cpu_dai->dev, + "failed to set parent %s: %d\n", + __clk_get_name(npll), ret); + } + } + + ret = clk_set_rate(clk, freq); + if (ret < 0) { + dev_err(cpu_dai->dev, "failed to set clock rate (%u): %d\n", + freq, ret); + return ret; + } + + for (i = 0; i < SPDIF_TXRATE_MAX; i++) { + ret = fsl_spdif_probe_txclk(data, i); + if (ret) + return ret; + } + + return 0; +} + static struct snd_soc_dai_ops fsl_spdif_dai_ops = { .startup = fsl_spdif_startup, + .set_sysclk = fsl_spdif_set_dai_sysclk, .hw_params = fsl_spdif_hw_params, .trigger = fsl_spdif_trigger, .shutdown = fsl_spdif_shutdown, + .hw_free = fsl_spdif_hw_free, }; - /* * FSL SPDIF IEC958 controller(mixer) functions * @@ -772,19 +992,23 @@ static int fsl_spdif_qget(struct snd_kcontrol *kcontrol, } /* Valid bit information */ -static int fsl_spdif_vbit_info(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_info *uinfo) +/* Get valid good bit from interrupt status register */ +static int fsl_spdif_rx_vbit_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) { - uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; - uinfo->count = 1; - uinfo->value.integer.min = 0; - uinfo->value.integer.max = 1; + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct regmap *regmap = spdif_priv->regmap; + u32 val; + + regmap_read(regmap, REG_SPDIF_SIS, &val); + ucontrol->value.integer.value[0] = (val & INT_VAL_NOGOOD) != 0; + regmap_write(regmap, REG_SPDIF_SIC, INT_VAL_NOGOOD); return 0; } -/* Get valid good bit from interrupt status register */ -static int fsl_spdif_vbit_get(struct snd_kcontrol *kcontrol, +static int fsl_spdif_tx_vbit_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); @@ -792,9 +1016,56 @@ static int fsl_spdif_vbit_get(struct snd_kcontrol *kcontrol, struct regmap *regmap = spdif_priv->regmap; u32 val; - regmap_read(regmap, REG_SPDIF_SIS, &val); - ucontrol->value.integer.value[0] = (val & INT_VAL_NOGOOD) != 0; - regmap_write(regmap, REG_SPDIF_SIC, INT_VAL_NOGOOD); + regmap_read(regmap, REG_SPDIF_SCR, &val); + val = (val & SCR_VAL_MASK) >> SCR_VAL_OFFSET; + val = 1 - val; + ucontrol->value.integer.value[0] = val; + + return 0; +} + +static int fsl_spdif_tx_vbit_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct regmap *regmap = spdif_priv->regmap; + u32 val = (1 - ucontrol->value.integer.value[0]) << SCR_VAL_OFFSET; + + regmap_update_bits(regmap, REG_SPDIF_SCR, SCR_VAL_MASK, val); + + return 0; +} + +static int fsl_spdif_rx_rcm_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct regmap *regmap = spdif_priv->regmap; + u32 val; + + regmap_read(regmap, REG_SPDIF_SCR, &val); + val = (val & SCR_RAW_CAPTURE_MODE) ? 1 : 0; + ucontrol->value.integer.value[0] = val; + + return 0; +} + +static int fsl_spdif_rx_rcm_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct regmap *regmap = spdif_priv->regmap; + u32 val = (ucontrol->value.integer.value[0] ? SCR_RAW_CAPTURE_MODE : 0); + + if (val) + cpu_dai->driver->capture.formats |= SNDRV_PCM_FMTBIT_S32_LE; + else + cpu_dai->driver->capture.formats &= ~SNDRV_PCM_FMTBIT_S32_LE; + + regmap_update_bits(regmap, REG_SPDIF_SCR, SCR_RAW_CAPTURE_MODE, val); return 0; } @@ -866,18 +1137,6 @@ static int fsl_spdif_rxrate_get(struct snd_kcontrol *kcontrol, return 0; } -/* User bit sync mode info */ -static int fsl_spdif_usync_info(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_info *uinfo) -{ - uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; - uinfo->count = 1; - uinfo->value.integer.min = 0; - uinfo->value.integer.max = 1; - - return 0; -} - /* * User bit sync mode: * 1 CD User channel subcode @@ -956,11 +1215,21 @@ static struct snd_kcontrol_new fsl_spdif_ctrls[] = { /* Valid bit error controller */ { .iface = SNDRV_CTL_ELEM_IFACE_PCM, - .name = "IEC958 V-Bit Errors", + .name = "IEC958 Rx V-Bit Errors", .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, - .info = fsl_spdif_vbit_info, - .get = fsl_spdif_vbit_get, + .info = snd_ctl_boolean_mono_info, + .get = fsl_spdif_rx_vbit_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 Tx V-Bit", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_ctl_boolean_mono_info, + .get = fsl_spdif_tx_vbit_get, + .put = fsl_spdif_tx_vbit_put, }, /* DPLL lock info get controller */ { @@ -978,10 +1247,20 @@ static struct snd_kcontrol_new fsl_spdif_ctrls[] = { .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_WRITE | SNDRV_CTL_ELEM_ACCESS_VOLATILE, - .info = fsl_spdif_usync_info, + .info = snd_ctl_boolean_mono_info, .get = fsl_spdif_usync_get, .put = fsl_spdif_usync_put, }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 Rx Raw Capture Mode Bit", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_ctl_boolean_mono_info, + .get = fsl_spdif_rx_rcm_get, + .put = fsl_spdif_rx_rcm_put, + }, }; static int fsl_spdif_dai_probe(struct snd_soc_dai *dai) @@ -1106,113 +1385,15 @@ static const struct regmap_config fsl_spdif_regmap_config = { .cache_type = REGCACHE_FLAT, }; -static u32 fsl_spdif_txclk_caldiv(struct fsl_spdif_priv *spdif_priv, - struct clk *clk, u64 savesub, - enum spdif_txrate index, bool round) -{ - const u32 rate[] = { 32000, 44100, 48000, 96000, 192000 }; - bool is_sysclk = clk_is_match(clk, spdif_priv->sysclk); - u64 rate_ideal, rate_actual, sub; - u32 sysclk_dfmin, sysclk_dfmax; - u32 txclk_df, sysclk_df, arate; - - /* The sysclk has an extra divisor [2, 512] */ - sysclk_dfmin = is_sysclk ? 2 : 1; - sysclk_dfmax = is_sysclk ? 512 : 1; - - for (sysclk_df = sysclk_dfmin; sysclk_df <= sysclk_dfmax; sysclk_df++) { - for (txclk_df = 1; txclk_df <= 128; txclk_df++) { - rate_ideal = rate[index] * txclk_df * 64; - if (round) - rate_actual = clk_round_rate(clk, rate_ideal); - else - rate_actual = clk_get_rate(clk); - - arate = rate_actual / 64; - arate /= txclk_df * sysclk_df; - - if (arate == rate[index]) { - /* We are lucky */ - savesub = 0; - spdif_priv->txclk_df[index] = txclk_df; - spdif_priv->sysclk_df[index] = sysclk_df; - spdif_priv->txrate[index] = arate; - goto out; - } else if (arate / rate[index] == 1) { - /* A little bigger than expect */ - sub = (u64)(arate - rate[index]) * 100000; - do_div(sub, rate[index]); - if (sub >= savesub) - continue; - savesub = sub; - spdif_priv->txclk_df[index] = txclk_df; - spdif_priv->sysclk_df[index] = sysclk_df; - spdif_priv->txrate[index] = arate; - } else if (rate[index] / arate == 1) { - /* A little smaller than expect */ - sub = (u64)(rate[index] - arate) * 100000; - do_div(sub, rate[index]); - if (sub >= savesub) - continue; - savesub = sub; - spdif_priv->txclk_df[index] = txclk_df; - spdif_priv->sysclk_df[index] = sysclk_df; - spdif_priv->txrate[index] = arate; - } - } - } - -out: - return savesub; -} - -static int fsl_spdif_probe_txclk(struct fsl_spdif_priv *spdif_priv, - enum spdif_txrate index) -{ - const u32 rate[] = { 32000, 44100, 48000, 96000, 192000 }; - struct platform_device *pdev = spdif_priv->pdev; - struct device *dev = &pdev->dev; - u64 savesub = 100000, ret; - struct clk *clk; - char tmp[16]; - int i; - - for (i = 0; i < STC_TXCLK_SRC_MAX; i++) { - sprintf(tmp, "rxtx%d", i); - clk = devm_clk_get(&pdev->dev, tmp); - if (IS_ERR(clk)) { - dev_err(dev, "no rxtx%d clock in devicetree\n", i); - return PTR_ERR(clk); - } - if (!clk_get_rate(clk)) - continue; - - ret = fsl_spdif_txclk_caldiv(spdif_priv, clk, savesub, index, - i == STC_TXCLK_SPDIF_ROOT); - if (savesub == ret) - continue; - - savesub = ret; - spdif_priv->txclk[index] = clk; - spdif_priv->txclk_src[index] = i; - - /* To quick catch a divisor, we allow a 0.1% deviation */ - if (savesub < 100) - break; - } - - dev_dbg(&pdev->dev, "use rxtx%d as tx clock source for %dHz sample rate\n", - spdif_priv->txclk_src[index], rate[index]); - dev_dbg(&pdev->dev, "use txclk df %d for %dHz sample rate\n", - spdif_priv->txclk_df[index], rate[index]); - if (clk_is_match(spdif_priv->txclk[index], spdif_priv->sysclk)) - dev_dbg(&pdev->dev, "use sysclk df %d for %dHz sample rate\n", - spdif_priv->sysclk_df[index], rate[index]); - dev_dbg(&pdev->dev, "the best rate for %dHz sample rate is %dHz\n", - rate[index], spdif_priv->txrate[index]); - - return 0; -} +static const struct of_device_id fsl_spdif_dt_ids[] = { + { .compatible = "fsl,imx8qxp-v1-spdif", .data = &fsl_spdif_imx8qxp_v1, }, + { .compatible = "fsl,imx8mm-spdif", .data = &fsl_spdif_imx8mm, }, + { .compatible = "fsl,imx8qm-spdif", .data = &fsl_spdif_imx8qm, }, + { .compatible = "fsl,imx35-spdif", .data = &fsl_spdif_imx35, }, + { .compatible = "fsl,vf610-spdif", .data = &fsl_spdif_vf610, }, + {} +}; +MODULE_DEVICE_TABLE(of, fsl_spdif_dt_ids); static int fsl_spdif_probe(struct platform_device *pdev) { @@ -1220,8 +1401,11 @@ static int fsl_spdif_probe(struct platform_device *pdev) struct fsl_spdif_priv *spdif_priv; struct spdif_mixer_control *ctrl; struct resource *res; + const struct of_device_id *of_id; void __iomem *regs; int irq, ret, i; + u32 buffer_size; + char tmp[16]; if (!np) return -ENODEV; @@ -1232,9 +1416,19 @@ static int fsl_spdif_probe(struct platform_device *pdev) spdif_priv->pdev = pdev; + of_id = of_match_device(fsl_spdif_dt_ids, &pdev->dev); + if (!of_id || !of_id->data) + return -EINVAL; + + spdif_priv->soc = of_id->data; + /* Initialize this copy of the CPU DAI driver structure */ memcpy(&spdif_priv->cpu_dai_drv, &fsl_spdif_dai, sizeof(fsl_spdif_dai)); spdif_priv->cpu_dai_drv.name = dev_name(&pdev->dev); + spdif_priv->cpu_dai_drv.playback.formats = + spdif_priv->soc->tx_formats; + spdif_priv->cpu_dai_drv.capture.rates = + spdif_priv->soc->rx_rates; /* Get the addresses and IRQ */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); @@ -1261,9 +1455,32 @@ static int fsl_spdif_probe(struct platform_device *pdev) dev_err(&pdev->dev, "could not claim irq %u\n", irq); return ret; } + if (spdif_priv->soc->interrupts > 1) { + irq = platform_get_irq(pdev, 1); + if (irq < 0) { + dev_err(&pdev->dev, "no irq for node %s\n", pdev->name); + return irq; + } + + ret = devm_request_irq(&pdev->dev, irq, spdif_isr, 0, + dev_name(&pdev->dev), spdif_priv); + if (ret) { + dev_err(&pdev->dev, "could not claim irq %u\n", irq); + return ret; + } + } + + for (i = 0; i < STC_TXCLK_SRC_MAX; i++) { + sprintf(tmp, "rxtx%d", i); + spdif_priv->txclk[i] = devm_clk_get(&pdev->dev, tmp); + if (IS_ERR(spdif_priv->txclk[i])) { + dev_err(&pdev->dev, "no rxtx%d clock in devicetree\n", i); + return PTR_ERR(spdif_priv->txclk[i]); + } + } /* Get system clock for rx clock rate calculation */ - spdif_priv->sysclk = devm_clk_get(&pdev->dev, "rxtx5"); + spdif_priv->sysclk = spdif_priv->txclk[5]; if (IS_ERR(spdif_priv->sysclk)) { dev_err(&pdev->dev, "no sys clock (rxtx5) in devicetree\n"); return PTR_ERR(spdif_priv->sysclk); @@ -1281,13 +1498,21 @@ static int fsl_spdif_probe(struct platform_device *pdev) dev_warn(&pdev->dev, "no spba clock in devicetree\n"); /* Select clock source for rx/tx clock */ - spdif_priv->rxclk = devm_clk_get(&pdev->dev, "rxtx1"); + spdif_priv->rxclk = spdif_priv->txclk[1]; if (IS_ERR(spdif_priv->rxclk)) { dev_err(&pdev->dev, "no rxtx1 clock in devicetree\n"); return PTR_ERR(spdif_priv->rxclk); } spdif_priv->rxclk_src = DEFAULT_RXCLK_SRC; + spdif_priv->pll8k_clk = devm_clk_get(&pdev->dev, "pll8k"); + if (IS_ERR(spdif_priv->pll8k_clk)) + spdif_priv->pll8k_clk = NULL; + + spdif_priv->pll11k_clk = devm_clk_get(&pdev->dev, "pll11k"); + if (IS_ERR(spdif_priv->pll11k_clk)) + spdif_priv->pll11k_clk = NULL; + for (i = 0; i < SPDIF_TXRATE_MAX; i++) { ret = fsl_spdif_probe_txclk(spdif_priv, i); if (ret) @@ -1308,11 +1533,18 @@ static int fsl_spdif_probe(struct platform_device *pdev) spdif_priv->dpll_locked = false; - spdif_priv->dma_params_tx.maxburst = FSL_SPDIF_TXFIFO_WML; - spdif_priv->dma_params_rx.maxburst = FSL_SPDIF_RXFIFO_WML; + spdif_priv->dma_params_tx.maxburst = spdif_priv->soc->tx_burst; + spdif_priv->dma_params_rx.maxburst = spdif_priv->soc->rx_burst; spdif_priv->dma_params_tx.addr = res->start + REG_SPDIF_STL; spdif_priv->dma_params_rx.addr = res->start + REG_SPDIF_SRL; + /*Clear the val bit for Tx*/ + regmap_update_bits(spdif_priv->regmap, REG_SPDIF_SCR, + SCR_VAL_MASK, 1 << SCR_VAL_OFFSET); + + pm_runtime_enable(&pdev->dev); + + regcache_cache_only(spdif_priv->regmap, true); /* Register with ASoC */ dev_set_drvdata(&pdev->dev, spdif_priv); @@ -1323,51 +1555,112 @@ static int fsl_spdif_probe(struct platform_device *pdev) return ret; } - ret = imx_pcm_dma_init(pdev, IMX_SPDIF_DMABUF_SIZE); + if (of_property_read_u32(np, "fsl,dma-buffer-size", &buffer_size)) + buffer_size = IMX_SPDIF_DMABUF_SIZE; + + if (spdif_priv->soc->dma_workaround) + spdif_priv->dma_info = + fsl_dma_workaround_alloc_info("tcd_pool_spdif", + &pdev->dev, + "nxp,imx8qm-acm", + FSL_DMA_WORKAROUND_SPDIF); + ret = imx_pcm_dma_init(pdev, buffer_size); if (ret) dev_err(&pdev->dev, "imx_pcm_dma_init failed: %d\n", ret); return ret; } -#ifdef CONFIG_PM_SLEEP -static int fsl_spdif_suspend(struct device *dev) +static int fsl_spdif_remove(struct platform_device *pdev) { - struct fsl_spdif_priv *spdif_priv = dev_get_drvdata(dev); - - regmap_read(spdif_priv->regmap, REG_SPDIF_SRPC, - &spdif_priv->regcache_srpc); + struct fsl_spdif_priv *spdif_priv = dev_get_drvdata(&pdev->dev); - regcache_cache_only(spdif_priv->regmap, true); - regcache_mark_dirty(spdif_priv->regmap); + if (spdif_priv->soc->dma_workaround) + fsl_dma_workaround_free_info(spdif_priv->dma_info, &pdev->dev); return 0; } -static int fsl_spdif_resume(struct device *dev) +#ifdef CONFIG_PM +static int fsl_spdif_runtime_resume(struct device *dev) { struct fsl_spdif_priv *spdif_priv = dev_get_drvdata(dev); + int ret; + int i; + + ret = clk_prepare_enable(spdif_priv->coreclk); + if (ret) { + dev_err(dev, "failed to enable core clock\n"); + return ret; + } + + if (!IS_ERR(spdif_priv->spbaclk)) { + ret = clk_prepare_enable(spdif_priv->spbaclk); + if (ret) { + dev_err(dev, "failed to enable spba clock\n"); + goto disable_core_clk; + } + } + + for (i = 0; i < STC_TXCLK_SRC_MAX; i++) { + ret = clk_prepare_enable(spdif_priv->txclk[i]); + if (ret) + goto disable_spba_clk; + } + + request_bus_freq(BUS_FREQ_HIGH); regcache_cache_only(spdif_priv->regmap, false); + regcache_mark_dirty(spdif_priv->regmap); regmap_update_bits(spdif_priv->regmap, REG_SPDIF_SRPC, SRPC_CLKSRC_SEL_MASK | SRPC_GAINSEL_MASK, spdif_priv->regcache_srpc); - return regcache_sync(spdif_priv->regmap); + ret = regcache_sync(spdif_priv->regmap); + if (ret) + goto disable_tx_clk; + + return 0; + +disable_tx_clk: +disable_spba_clk: + for (i--; i >= 0; i--) + clk_disable_unprepare(spdif_priv->txclk[i]); + if (!IS_ERR(spdif_priv->spbaclk)) + clk_disable_unprepare(spdif_priv->spbaclk); +disable_core_clk: + clk_disable_unprepare(spdif_priv->coreclk); + + return ret; } -#endif /* CONFIG_PM_SLEEP */ -static const struct dev_pm_ops fsl_spdif_pm = { - SET_SYSTEM_SLEEP_PM_OPS(fsl_spdif_suspend, fsl_spdif_resume) -}; +static int fsl_spdif_runtime_suspend(struct device *dev) +{ + struct fsl_spdif_priv *spdif_priv = dev_get_drvdata(dev); + int i; -static const struct of_device_id fsl_spdif_dt_ids[] = { - { .compatible = "fsl,imx35-spdif", }, - { .compatible = "fsl,vf610-spdif", }, - {} + regmap_read(spdif_priv->regmap, REG_SPDIF_SRPC, + &spdif_priv->regcache_srpc); + regcache_cache_only(spdif_priv->regmap, true); + release_bus_freq(BUS_FREQ_HIGH); + + for (i = 0; i < STC_TXCLK_SRC_MAX; i++) + clk_disable_unprepare(spdif_priv->txclk[i]); + + if (!IS_ERR(spdif_priv->spbaclk)) + clk_disable_unprepare(spdif_priv->spbaclk); + clk_disable_unprepare(spdif_priv->coreclk); + + return 0; +} +#endif + +static const struct dev_pm_ops fsl_spdif_pm = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(fsl_spdif_runtime_suspend, fsl_spdif_runtime_resume, + NULL) }; -MODULE_DEVICE_TABLE(of, fsl_spdif_dt_ids); static struct platform_driver fsl_spdif_driver = { .driver = { @@ -1376,6 +1669,7 @@ static struct platform_driver fsl_spdif_driver = { .pm = &fsl_spdif_pm, }, .probe = fsl_spdif_probe, + .remove = fsl_spdif_remove, }; module_platform_driver(fsl_spdif_driver); diff --git a/sound/soc/fsl/fsl_spdif.h b/sound/soc/fsl/fsl_spdif.h index 00bd3514c610..e3f0fbdb9f22 100644 --- a/sound/soc/fsl/fsl_spdif.h +++ b/sound/soc/fsl/fsl_spdif.h @@ -66,6 +66,7 @@ #define SCR_TXFIFO_FSEL_IF4 (0x1 << SCR_TXFIFO_FSEL_OFFSET) #define SCR_TXFIFO_FSEL_IF8 (0x2 << SCR_TXFIFO_FSEL_OFFSET) #define SCR_TXFIFO_FSEL_IF12 (0x3 << SCR_TXFIFO_FSEL_OFFSET) +#define SCR_RAW_CAPTURE_MODE (1 << 14) #define SCR_LOW_POWER (1 << 13) #define SCR_SOFT_RESET (1 << 12) #define SCR_TXFIFO_CTRL_OFFSET 10 @@ -155,7 +156,7 @@ enum spdif_gainsel { #define STC_TXCLK_ALL_EN_MASK (1 << STC_TXCLK_ALL_EN_OFFSET) #define STC_TXCLK_ALL_EN (1 << STC_TXCLK_ALL_EN_OFFSET) #define STC_TXCLK_DF_OFFSET 0 -#define STC_TXCLK_DF_MASK (0x7ff << STC_TXCLK_DF_OFFSET) +#define STC_TXCLK_DF_MASK (0x7f << STC_TXCLK_DF_OFFSET) #define STC_TXCLK_DF(x) ((((x) - 1) << STC_TXCLK_DF_OFFSET) & STC_TXCLK_DF_MASK) #define STC_TXCLK_SRC_MAX 8 diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c index 7cd2a5aed15a..342c360f2a65 100644 --- a/sound/soc/fsl/fsl_ssi.c +++ b/sound/soc/fsl/fsl_ssi.c @@ -3,7 +3,8 @@ * * Author: Timur Tabi <timur@freescale.com> * - * Copyright 2007-2010 Freescale Semiconductor, Inc. + * Copyright 2007-2015, Freescale Semiconductor, Inc. + * Copyright 2017 NXP * * This file is licensed under the terms of the GNU General Public License * version 2. This program is licensed "as is" without any warranty of any @@ -43,6 +44,8 @@ #include <linux/of_address.h> #include <linux/of_irq.h> #include <linux/of_platform.h> +#include <linux/pm_runtime.h> +#include <linux/busfreq-imx.h> #include <sound/core.h> #include <sound/pcm.h> @@ -676,6 +679,8 @@ static int fsl_ssi_startup(struct snd_pcm_substream *substream, if (ret) return ret; + pm_runtime_get_sync(dai->dev); + /* When using dual fifo mode, it is safer to ensure an even period * size. If appearing to an odd number while DMA always starts its * task from fifo0, fifo1 would be neglected at the end of each @@ -699,6 +704,8 @@ static void fsl_ssi_shutdown(struct snd_pcm_substream *substream, struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(rtd->cpu_dai); + pm_runtime_put_sync(dai->dev); + clk_disable_unprepare(ssi_private->clk); } @@ -728,8 +735,14 @@ static int fsl_ssi_set_bclk(struct snd_pcm_substream *substream, /* Prefer the explicitly set bitclock frequency */ if (ssi_private->bitclk_freq) freq = ssi_private->bitclk_freq; - else - freq = params_channels(hw_params) * 32 * params_rate(hw_params); + else { + if (params_channels(hw_params) == 1) + freq = 2 * params_width(hw_params) * + params_rate(hw_params); + else + freq = params_channels(hw_params) * 32 * + params_rate(hw_params); + } /* Don't apply it to any non-baudclk circumstance */ if (IS_ERR(ssi_private->baudclk)) @@ -848,16 +861,7 @@ static int fsl_ssi_hw_params(struct snd_pcm_substream *substream, int ret; u32 scr_val; int enabled; - - regmap_read(regs, CCSR_SSI_SCR, &scr_val); - enabled = scr_val & CCSR_SSI_SCR_SSIEN; - - /* - * If we're in synchronous mode, and the SSI is already enabled, - * then STCCR is already set properly. - */ - if (enabled && ssi_private->cpu_dai_drv.symmetric_rates) - return 0; + u8 i2smode = ssi_private->i2s_mode; if (fsl_ssi_is_i2s_master(ssi_private)) { ret = fsl_ssi_set_bclk(substream, cpu_dai, hw_params); @@ -874,8 +878,17 @@ static int fsl_ssi_hw_params(struct snd_pcm_substream *substream, } } + regmap_read(regs, CCSR_SSI_SCR, &scr_val); + enabled = scr_val & CCSR_SSI_SCR_SSIEN; + + /* + * If we're in synchronous mode, and the SSI is already enabled, + * then STCCR is already set properly. + */ + if (enabled && ssi_private->cpu_dai_drv.symmetric_rates) + return 0; + if (!fsl_ssi_is_ac97(ssi_private)) { - u8 i2smode; /* * Switch to normal net mode in order to have a frame sync * signal every 32 bits instead of 16 bits @@ -883,14 +896,14 @@ static int fsl_ssi_hw_params(struct snd_pcm_substream *substream, if (fsl_ssi_is_i2s_cbm_cfs(ssi_private) && sample_size == 16) i2smode = CCSR_SSI_SCR_I2S_MODE_NORMAL | CCSR_SSI_SCR_NET; - else - i2smode = ssi_private->i2s_mode; - - regmap_update_bits(regs, CCSR_SSI_SCR, - CCSR_SSI_SCR_NET | CCSR_SSI_SCR_I2S_MODE_MASK, - channels == 1 ? 0 : i2smode); + if (channels == 1) + i2smode = 0; } + regmap_update_bits(regs, CCSR_SSI_SCR, + CCSR_SSI_SCR_NET | CCSR_SSI_SCR_I2S_MODE_MASK, + i2smode); + /* * FIXME: The documentation says that SxCCR[WL] should not be * modified while the SSI is enabled. The only time this can @@ -947,7 +960,7 @@ static int _fsl_ssi_set_dai_fmt(struct device *dev, fsl_ssi_setup_reg_vals(ssi_private); regmap_read(regs, CCSR_SSI_SCR, &scr); - scr &= ~(CCSR_SSI_SCR_SYN | CCSR_SSI_SCR_I2S_MODE_MASK); + scr &= ~CCSR_SSI_SCR_SYN; scr |= CCSR_SSI_SCR_SYNC_TX_FS; mask = CCSR_SSI_STCR_TXBIT0 | CCSR_SSI_STCR_TFDIR | CCSR_SSI_STCR_TXDIR | @@ -1003,7 +1016,6 @@ static int _fsl_ssi_set_dai_fmt(struct device *dev, default: return -EINVAL; } - scr |= ssi_private->i2s_mode; /* DAI clock inversion */ switch (fmt & SND_SOC_DAIFMT_INV_MASK) { @@ -1196,7 +1208,7 @@ static int fsl_ssi_dai_probe(struct snd_soc_dai *dai) static const struct snd_soc_dai_ops fsl_ssi_dai_ops = { .startup = fsl_ssi_startup, - .shutdown = fsl_ssi_shutdown, + .shutdown = fsl_ssi_shutdown, .hw_params = fsl_ssi_hw_params, .hw_free = fsl_ssi_hw_free, .set_fmt = fsl_ssi_set_dai_fmt, @@ -1341,6 +1353,7 @@ static int fsl_ssi_imx_probe(struct platform_device *pdev, struct device_node *np = pdev->dev.of_node; u32 dmas[4]; int ret; + u32 buffer_size; if (ssi_private->has_ipg_clk_name) ssi_private->clk = devm_clk_get(&pdev->dev, "ipg"); @@ -1383,6 +1396,9 @@ static int fsl_ssi_imx_probe(struct platform_device *pdev, ssi_private->dma_params_rx.maxburst &= ~0x1; } + if (of_property_read_u32(np, "fsl,dma-buffer-size", &buffer_size)) + buffer_size = IMX_SSI_DMABUF_SIZE; + if (!ssi_private->use_dma) { /* @@ -1403,7 +1419,7 @@ static int fsl_ssi_imx_probe(struct platform_device *pdev, if (ret) goto error_pcm; } else { - ret = imx_pcm_dma_init(pdev, IMX_SSI_DMABUF_SIZE); + ret = imx_pcm_dma_init(pdev, buffer_size); if (ret) goto error_pcm; } @@ -1570,6 +1586,8 @@ static int fsl_ssi_probe(struct platform_device *pdev) break; } + pm_runtime_enable(&pdev->dev); + dev_set_drvdata(&pdev->dev, ssi_private); if (ssi_private->soc->imx) { @@ -1734,8 +1752,24 @@ static int fsl_ssi_resume(struct device *dev) } #endif /* CONFIG_PM_SLEEP */ +#ifdef CONFIG_PM +static int fsl_ssi_runtime_resume(struct device *dev) +{ + request_bus_freq(BUS_FREQ_AUDIO); + return 0; +} + +static int fsl_ssi_runtime_suspend(struct device *dev) +{ + release_bus_freq(BUS_FREQ_AUDIO); + return 0; +} +#endif + static const struct dev_pm_ops fsl_ssi_pm = { SET_SYSTEM_SLEEP_PM_OPS(fsl_ssi_suspend, fsl_ssi_resume) + SET_RUNTIME_PM_OPS(fsl_ssi_runtime_suspend, fsl_ssi_runtime_resume, + NULL) }; static struct platform_driver fsl_ssi_driver = { diff --git a/sound/soc/fsl/hdmi_pcm.S b/sound/soc/fsl/hdmi_pcm.S new file mode 100644 index 000000000000..d8d95fd8f42f --- /dev/null +++ b/sound/soc/fsl/hdmi_pcm.S @@ -0,0 +1,246 @@ +/** + * Copyright (C) 2010-2014 Freescale Semiconductor, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +.section .text + +.global hdmi_dma_copy_16_neon_lut +.global hdmi_dma_copy_16_neon_fast +.global hdmi_dma_copy_24_neon_lut +.global hdmi_dma_copy_24_neon_fast + + +/** + * hdmi_dma_copy_16_neon_lut + * Convert pcm sample to iec sample. Pcm sample is 16 bits. + * Frame index's between 0 and 47 inclusively. Channel count can be 1, 2, 4, 8. + * Frame count should be multipliable by 4, and Sample count by 8. + * + * C Prototype + * void hdmi_dma_copy_16_neon_lut(unsigned short *src, unsigned int *dst, + * int samples, unsigned char *lookup_table); + * Return value + * None + * Parameters + * src Source PCM16 samples + * dst Dest buffer to store pcm with header + * samples Contains sample count (=frame_count * channel_count) + * lookup_table Preconstructed header table. Channels interleaved. + */ + +hdmi_dma_copy_16_neon_lut: + mov r12, #1 /* construct vector(1) */ + vdup.8 d6, r12 + +hdmi_dma_copy_16_neon_lut_start: + + /* get 8 samples to q0 */ + vld1.16 {d0, d1}, [r0]! /* TODO: aligned */ + + /* pld [r1, #(64*4)] */ + + /* xor every bit */ + vcnt.8 q1, q0 /* count of 1s */ + vpadd.i8 d2, d2, d3 /* only care about the LST in every element */ + vand d2, d2, d6 /* clear other bits while keep the least bit */ + vshl.u8 d2, d2, #3 /* bit p: d2 = d2 << 3 */ + + /* get packet header */ + vld1.8 {d5}, [r3]! + veor d4, d5, d2 /* xor bit c */ + + /* store: (d4 << 16 | q0) << 8 */ + vmovl.u8 q2, d4 /* expand from char to short */ + vzip.16 q0, q2 + vshl.u32 q0, q0, #8 + vshl.u32 q1, q2, #8 + vst1.32 {d0, d1, d2, d3}, [r1]! + + /* decrease sample count */ + subs r2, r2, #8 + bne hdmi_dma_copy_16_neon_lut_start + + mov pc, lr + +/** + * hdmi_dma_copy_16_neon_fast + * Convert pcm sample to iec sample. Pcm sample is 16 bits. + * Frame index's between 48 and 191 inclusively. + * Channel count can be 1, 2, 4 or 8. + * Frame count should be multipliable by 4, and Sample count by 8. + * + * C Prototype + * void hdmi_dma_copy_16_neon_fast(unsigned short *src, + * unsigned int *dst, int samples); + * Return value + * None + * Parameters + * src Source PCM16 samples + * dst Dest buffer to store pcm with header + * samples Contains sample count (=frame_count * channel_count) + */ + +hdmi_dma_copy_16_neon_fast: + mov r12, #1 /* construct vector(1) */ + vdup.8 d6, r12 + +hdmi_dma_copy_16_neon_fast_start: + /* get 8 samples to q0 */ + vld1.16 {d0, d1}, [r0]! /* TODO: aligned */ + + /* pld [r1, #(64*4)] */ + + /* xor every bit */ + vcnt.8 q1, q0 /* count of 1s */ + vpadd.i8 d2, d2, d3 + vand d2, d2, d6 /* clear other bits while keep the LST */ + /* finally we construct packet header */ + vshl.u8 d4, d2, #3 /* bit p: d2 = d2 << 3 */ + + /* get packet header: always 0 */ + + /* store: (d4 << 16 | q0) << 8 */ + vmovl.u8 q2, d4 /* expand from char to short */ + vzip.16 q0, q2 + vshl.u32 q0, q0, #8 + vshl.u32 q1, q2, #8 + vst1.32 {d0, d1, d2, d3}, [r1]! + + /* decrease sample count */ + subs r2, r2, #8 + bne hdmi_dma_copy_16_neon_fast_start + + mov pc, lr + + + +/** + * hdmi_dma_copy_24_neon_lut + * Convert pcm sample to iec sample. Pcm sample is 24 bits. + * Frame index's between 0 and 47 inclusively. Channel count can be 1, 2, 4, 8. + * Frame count should be multipliable by 4, and Sample count by 8. + * + * C Prototype + * void hdmi_dma_copy_24_neon_lut(unsigned int *src, unsigned int *dst, + * int samples, unsigned char *lookup_table); + * Return value + * None + * Parameters + * src Source PCM24 samples + * dst Dest buffer to store pcm with header + * samples Contains sample count (=frame_count * channel_count) + * lookup_table Preconstructed header table. Channels interleaved. + */ + +hdmi_dma_copy_24_neon_lut: + vpush {d8} + + mov r12, #1 /* construct vector(1) */ + vdup.8 d8, r12 + +hdmi_dma_copy_24_neon_lut_start: + + /* get 8 samples to q0 and q1 */ + vld1.32 {d0, d1, d2, d3}, [r0]! /* TODO: aligned */ + + /* pld [r1, #(64*4)] */ + + /* xor every bit */ + vcnt.8 q2, q0 /* count of 1s */ + vpadd.i8 d4, d4, d5 /* only care about the LSB in every element */ + vcnt.8 q3, q1 + vpadd.i8 d6, d6, d7 + vpadd.i8 d4, d4, d6 /* d4: contains xor result and other dirty bits */ + vand d4, d4, d8 /* clear other bits while keep the least bit */ + vshl.u8 d4, d4, #3 /* bit p: d4 = d4 << 3 */ + + /* get packet header */ + vld1.8 {d5}, [r3]!/* d5: original header */ + veor d5, d5, d4 /* fix bit p */ + + /* store: (d5 << 24 | q0) */ + vmovl.u8 q3, d5 /* expand from char to short */ + vmovl.u16 q2, d6 /* expand from short to int */ + vmovl.u16 q3, d7 + vshl.u32 q2, q2, #24 + vshl.u32 q3, q3, #24 + vorr q0, q0, q2 + vorr q1, q1, q3 + vst1.32 {d0, d1, d2, d3}, [r1]! + + /* decrease sample count */ + subs r2, r2, #8 + bne hdmi_dma_copy_24_neon_lut_start + + vpop {d8} + mov pc, lr + +/** + * hdmi_dma_copy_24_neon_fast + * Convert pcm sample to iec sample. Pcm sample is 24 bits. + * Frame index's between 48 and 191 inclusively. + * Channel count can be 1, 2, 4 or 8. + * Frame count should be multipliable by 4, and Sample count by 8. + * + * C Prototype + * void hdmi_dma_copy_24_neon_fast(unsigned int *src, + * unsigned int *dst, int samples); + * Return value + * None + * Parameters + * src Source PCM24 samples + * dst Dest buffer to store pcm with header + * samples Contains sample count (=frame_count * channel_count) + */ + +hdmi_dma_copy_24_neon_fast: + vpush {d8} + + mov r12, #1 /* construct vector(1) */ + vdup.8 d8, r12 + +hdmi_dma_copy_24_neon_fast_start: + /* get 8 samples to q0 and q1 */ + vld1.32 {d0, d1, d2, d3}, [r0]! /* TODO: aligned */ + + /* pld [r1, #(64*4)] */ + + /* xor every bit */ + vcnt.8 q2, q0 /* count of 1s */ + vpadd.i8 d4, d4, d5 /* only care about the LSB in every element */ + vcnt.8 q3, q1 + vpadd.i8 d6, d6, d7 + vpadd.i8 d4, d4, d6 /* d4: contains xor result and other dirty bits */ + vand d4, d4, d8 /* clear other bits while keep the least bit */ + vshl.u8 d4, d4, #3 /* bit p: d4 = d4 << 3 */ + + /* store: (d4 << 24 | q0) */ + vmovl.u8 q3, d4 /* expand from char to short */ + vmovl.u16 q2, d6 /* expand from short to int */ + vmovl.u16 q3, d7 + vshl.u32 q2, q2, #24 + vshl.u32 q3, q3, #24 + vorr q0, q0, q2 + vorr q1, q1, q3 + vst1.32 {d0, d1, d2, d3}, [r1]! + + /* decrease sample count */ + subs r2, r2, #8 + bne hdmi_dma_copy_24_neon_fast_start + + vpop {d8} + mov pc, lr diff --git a/sound/soc/fsl/imx-ak4458.c b/sound/soc/fsl/imx-ak4458.c new file mode 100644 index 000000000000..0dd3e8a15983 --- /dev/null +++ b/sound/soc/fsl/imx-ak4458.c @@ -0,0 +1,398 @@ +/* i.MX AK4458 audio support + * + * Copyright 2017 NXP + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/gpio/consumer.h> +#include <linux/of_device.h> +#include <linux/i2c.h> +#include <linux/of_gpio.h> +#include <linux/clk.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> +#include <sound/pcm.h> +#include <sound/soc-dapm.h> + +#include "fsl_sai.h" + +struct imx_ak4458_data { + struct snd_soc_card card; + int num_codec_conf; + struct snd_soc_codec_conf *codec_conf; + bool tdm_mode; + int pdn_gpio; + unsigned int slots; + unsigned int slot_width; + bool one2one_ratio; +}; + +static struct snd_soc_dapm_widget imx_ak4458_dapm_widgets[] = { + SND_SOC_DAPM_LINE("Line Out", NULL), +}; + +/** + * Tables 3 & 4 - mapping LRCK fs and frame width + */ +static const struct imx_ak4458_fs_map { + unsigned int rmin; + unsigned int rmax; + unsigned int wmin; + unsigned int wmax; +} fs_map[] = { + /* Normal, < 32kHz */ + { .rmin = 8000, .rmax = 24000, .wmin = 1024, .wmax = 1024, }, + /* Normal, 32kHz */ + { .rmin = 32000, .rmax = 32000, .wmin = 256, .wmax = 1024, }, + /* Normal */ + { .rmin = 44100, .rmax = 48000, .wmin = 256, .wmax = 768, }, + /* Double */ + { .rmin = 88200, .rmax = 96000, .wmin = 256, .wmax = 512, }, + /* Quad */ + { .rmin = 176400, .rmax = 192000, .wmin = 128, .wmax = 256, }, + /* Oct */ + { .rmin = 352800, .rmax = 384000, .wmin = 32, .wmax = 128, }, + /* Hex */ + { .rmin = 705600, .rmax = 768000, .wmin = 16, .wmax = 64, }, +}; + +static const struct imx_ak4458_fs_mul { + unsigned int min; + unsigned int max; + unsigned int mul; +} fs_mul_tdm[] = { + /* + * Table 13 - Audio Interface Format + * For TDM mode, MCLK should is set to + * obtained from 2 * slots * slot_width + */ + { .min = 128, .max = 128, .mul = 256 }, /* TDM128 */ + { .min = 256, .max = 256, .mul = 512 }, /* TDM256 */ + { .min = 512, .max = 512, .mul = 1024 }, /* TDM512 */ +}; + +static const u32 ak4458_rates[] = { + 8000, 11025, 16000, 22050, + 32000, 44100, 48000, 88200, + 96000, 176400, 192000, 352800, + 384000, 705600, 768000, +}; + +static const u32 ak4458_rates_tdm[] = { + 8000, 16000, 32000, + 48000, 96000, +}; + +static const u32 ak4458_channels[] = { + 1, 2, 4, 6, 8, 10, 12, 14, 16, +}; + +static const u32 ak4458_channels_tdm[] = { + 1, 2, 3, 4, 5, 6, 7, 8, +}; + +static unsigned long ak4458_get_mclk_rate(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct imx_ak4458_data *data = snd_soc_card_get_drvdata(rtd->card); + unsigned int rate = params_rate(params); + unsigned int width = data->slots * data->slot_width; + int i, mode; + + if (data->tdm_mode) { + /* can be 128, 256 or 512 */ + mode = data->slots * data->slot_width; + + for (i = 0; i < ARRAY_SIZE(fs_mul_tdm); i++) { + /* min = max = slots * slots_width */ + if (mode != fs_mul_tdm[i].min) + continue; + return rate * fs_mul_tdm[i].mul; + } + } else { + for (i = 0; i < ARRAY_SIZE(fs_map); i++) { + if (rate >= fs_map[i].rmin && rate <= fs_map[i].rmax) { + width = max(width, fs_map[i].wmin); + width = min(width, fs_map[i].wmax); + + /* Adjust SAI bclk:mclk ratio */ + width *= data->one2one_ratio ? 1 : 2; + + return rate * width; + } + } + } + + /* Let DAI manage clk frequency by default */ + return 0; +} + +static int imx_aif_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_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_card *card = rtd->card; + struct device *dev = card->dev; + struct imx_ak4458_data *data = snd_soc_card_get_drvdata(card); + unsigned int channels = params_channels(params); + unsigned int fmt = SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS; + unsigned long mclk_freq; + int ret, i; + + if (data->tdm_mode) { + data->slots = 8; + data->slot_width = 32; + } else { + data->slots = 2; + data->slot_width = params_physical_width(params); + } + + fmt |= data->tdm_mode ? SND_SOC_DAIFMT_DSP_B : SND_SOC_DAIFMT_I2S; + + ret = snd_soc_dai_set_fmt(cpu_dai, fmt); + if (ret) { + dev_err(dev, "failed to set cpu dai fmt: %d\n", ret); + return ret; + } + ret = snd_soc_dai_set_tdm_slot(cpu_dai, + BIT(channels) - 1, BIT(channels) - 1, + data->slots, data->slot_width); + if (ret) { + dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret); + return ret; + } + + for (i = 0; i < rtd->num_codecs; i++) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[i]; + + ret = snd_soc_dai_set_fmt(codec_dai, fmt); + if (ret) { + dev_err(dev, "failed to set codec dai[%d] fmt: %d\n", + i, ret); + return ret; + } + ret = snd_soc_dai_set_tdm_slot(codec_dai, + BIT(channels) - 1, BIT(channels) - 1, + data->slots, data->slot_width); + if (ret) { + dev_err(dev, "failed to set codec dai[%d] tdm slot: %d\n", + i, ret); + return ret; + } + } + + /* set MCLK freq */ + mclk_freq = ak4458_get_mclk_rate(substream, params); + ret = snd_soc_dai_set_sysclk(cpu_dai, FSL_SAI_CLK_MAST1, mclk_freq, + SND_SOC_CLOCK_OUT); + if (ret < 0) + dev_err(dev, "failed to set cpui dai mclk1 rate (%lu): %d\n", + mclk_freq, ret); + return ret; +} + +static int imx_aif_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct imx_ak4458_data *data = snd_soc_card_get_drvdata(card); + static struct snd_pcm_hw_constraint_list constraint_rates; + static struct snd_pcm_hw_constraint_list constraint_channels; + int ret; + + if (data->tdm_mode) { + constraint_channels.list = ak4458_channels_tdm; + constraint_channels.count = ARRAY_SIZE(ak4458_channels_tdm); + constraint_rates.list = ak4458_rates_tdm; + constraint_rates.count = ARRAY_SIZE(ak4458_rates_tdm); + } else { + constraint_channels.list = ak4458_channels; + constraint_channels.count = ARRAY_SIZE(ak4458_channels); + constraint_rates.list = ak4458_rates; + constraint_rates.count = ARRAY_SIZE(ak4458_rates); + } + + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraint_channels); + if (ret) + return ret; + + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraint_rates); + if (ret) + return ret; + + return 0; +} + +static struct snd_soc_ops imx_aif_ops = { + .hw_params = imx_aif_hw_params, + .startup = imx_aif_startup, +}; + +static struct snd_soc_dai_link_component ak4458_codecs[] = { + { + /* Playback */ + .dai_name = "ak4458-aif", + }, + { + /* Capture */ + .dai_name = "ak4458-aif", + }, +}; + +static struct snd_soc_dai_link imx_ak4458_dai = { + .name = "ak4458", + .stream_name = "Audio", + .codecs = ak4458_codecs, + .num_codecs = 2, + .ignore_pmdown_time = 1, + .ops = &imx_aif_ops, + .playback_only = 1, +}; + +static int imx_ak4458_probe(struct platform_device *pdev) +{ + struct imx_ak4458_data *priv; + struct device_node *cpu_np, *codec_np_0 = NULL, *codec_np_1 = NULL; + struct platform_device *cpu_pdev; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + cpu_np = of_parse_phandle(pdev->dev.of_node, "audio-cpu", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "audio dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + codec_np_0 = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); + if (!codec_np_0) { + dev_err(&pdev->dev, "audio codec phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + codec_np_1 = of_parse_phandle(pdev->dev.of_node, "audio-codec", 1); + if (!codec_np_1) { + dev_err(&pdev->dev, "audio codec phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find SAI platform device\n"); + ret = -EINVAL; + goto fail; + } + + if (of_find_property(pdev->dev.of_node, "fsl,tdm", NULL)) + priv->tdm_mode = true; + + priv->num_codec_conf = 2; + priv->codec_conf = devm_kzalloc(&pdev->dev, + priv->num_codec_conf * sizeof(struct snd_soc_codec_conf), + GFP_KERNEL); + if (!priv->codec_conf) { + ret = -ENOMEM; + goto fail; + } + + priv->codec_conf[0].name_prefix = "0"; + priv->codec_conf[0].of_node = codec_np_0; + priv->codec_conf[1].name_prefix = "1"; + priv->codec_conf[1].of_node = codec_np_1; + + ak4458_codecs[0].of_node = codec_np_0; + ak4458_codecs[1].of_node = codec_np_1; + + imx_ak4458_dai.cpu_dai_name = dev_name(&cpu_pdev->dev); + imx_ak4458_dai.platform_of_node = cpu_np; + + priv->card.num_links = 1; + priv->card.dai_link = &imx_ak4458_dai; + priv->card.dev = &pdev->dev; + priv->card.owner = THIS_MODULE; + priv->card.dapm_widgets = imx_ak4458_dapm_widgets; + priv->card.num_dapm_widgets = ARRAY_SIZE(imx_ak4458_dapm_widgets); + priv->card.codec_conf = priv->codec_conf; + priv->card.num_configs = priv->num_codec_conf; + priv->one2one_ratio = !of_device_is_compatible(pdev->dev.of_node, + "fsl,imx-audio-ak4458-mq"); + + priv->pdn_gpio = of_get_named_gpio(pdev->dev.of_node, "ak4458,pdn-gpio", 0); + if (gpio_is_valid(priv->pdn_gpio)) { + ret = devm_gpio_request_one(&pdev->dev, priv->pdn_gpio, + GPIOF_OUT_INIT_LOW, "ak4458,pdn"); + if (ret) { + dev_err(&pdev->dev, "unable to get pdn gpio\n"); + goto fail; + } + + gpio_set_value_cansleep(priv->pdn_gpio, 0); + usleep_range(1000, 2000); + gpio_set_value_cansleep(priv->pdn_gpio, 1); + usleep_range(1000, 2000); + } + + ret = snd_soc_of_parse_card_name(&priv->card, "model"); + if (ret) + goto fail; + + snd_soc_card_set_drvdata(&priv->card, priv); + + ret = devm_snd_soc_register_card(&pdev->dev, &priv->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + + ret = 0; +fail: + if (cpu_np) + of_node_put(cpu_np); + if (codec_np_0) + of_node_put(codec_np_0); + if (codec_np_1) + of_node_put(codec_np_1); + + return ret; +} + +static const struct of_device_id imx_ak4458_dt_ids[] = { + { .compatible = "fsl,imx-audio-ak4458", }, + { .compatible = "fsl,imx-audio-ak4458-mq", }, + { }, +}; +MODULE_DEVICE_TABLE(of, imx_ak4458_dt_ids); + +static struct platform_driver imx_ak4458_driver = { + .driver = { + .name = "imx-ak4458", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_ak4458_dt_ids, + }, + .probe = imx_ak4458_probe, +}; +module_platform_driver(imx_ak4458_driver); + +MODULE_AUTHOR("Mihai Serban <mihai.serban@nxp.com>"); +MODULE_DESCRIPTION("Freescale i.MX AK4458 ASoC machine driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:imx-ak4458"); diff --git a/sound/soc/fsl/imx-ak4497.c b/sound/soc/fsl/imx-ak4497.c new file mode 100644 index 000000000000..9acfa84bf3d1 --- /dev/null +++ b/sound/soc/fsl/imx-ak4497.c @@ -0,0 +1,265 @@ +/* i.MX AK4458 audio support + * + * Copyright 2017 NXP + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/gpio/consumer.h> +#include <linux/of_device.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> + +#include "../codecs/ak4497.h" +#include "fsl_sai.h" + +struct imx_ak4497_data { + struct snd_soc_card card; + bool one2one_ratio; +}; + +static struct snd_soc_dapm_widget imx_ak4497_dapm_widgets[] = { + SND_SOC_DAPM_LINE("Line Out", NULL), +}; + +static const struct imx_ak4497_fs_mul { + unsigned int min; + unsigned int max; + unsigned int mul; +} fs_mul[] = { + /** + * Table 7 - mapping multiplier and speed mode + * Tables 8 & 9 - mapping speed mode and LRCK fs + */ + { .min = 8000, .max = 32000, .mul = 1024 }, /* Normal, <= 32kHz */ + { .min = 44100, .max = 48000, .mul = 512 }, /* Normal */ + { .min = 88200, .max = 96000, .mul = 256 }, /* Double */ + { .min = 176400, .max = 192000, .mul = 128 }, /* Quad */ + { .min = 352800, .max = 384000, .mul = 2*64 }, /* Oct */ + { .min = 705600, .max = 768000, .mul = 2*32 }, /* Hex */ +}; + +static bool imx_ak4497_is_dsd(struct snd_pcm_hw_params *params) +{ + snd_pcm_format_t format = params_format(params); + + switch (format) { + case SNDRV_PCM_FORMAT_DSD_U8: + case SNDRV_PCM_FORMAT_DSD_U16_LE: + case SNDRV_PCM_FORMAT_DSD_U16_BE: + case SNDRV_PCM_FORMAT_DSD_U32_LE: + case SNDRV_PCM_FORMAT_DSD_U32_BE: + return true; + default: + return false; + } +} + +static unsigned long imx_ak4497_compute_freq(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + unsigned int rate = params_rate(params); + int i; + + /* Find the appropriate MCLK freq */ + for (i = 0; i < ARRAY_SIZE(fs_mul); i++) { + if (rate >= fs_mul[i].min && rate <= fs_mul[i].max) + return rate * fs_mul[i].mul; + } + + /* Let DAI manage MCLK frequency */ + return 0; +} + +static int imx_aif_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_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_card *card = rtd->card; + struct device *dev = card->dev; + struct imx_ak4497_data *priv = snd_soc_card_get_drvdata(card); + unsigned int channels = params_channels(params); + unsigned int fmt = SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS; + unsigned long freq = imx_ak4497_compute_freq(substream, params); + bool is_dsd = imx_ak4497_is_dsd(params); + int ret; + + fmt |= (is_dsd ? SND_SOC_DAIFMT_PDM : SND_SOC_DAIFMT_I2S); + + if (is_dsd && freq > 22579200 && priv->one2one_ratio) + freq = 22579200; + + ret = snd_soc_dai_set_sysclk(cpu_dai, FSL_SAI_CLK_MAST1, freq, + SND_SOC_CLOCK_OUT); + if (ret < 0) { + dev_err(dev, "failed to set cpu dai mclk1 rate(%lu): %d\n", + freq, ret); + return ret; + } + + ret = snd_soc_dai_set_fmt(cpu_dai, fmt); + if (ret) { + dev_err(dev, "failed to set cpu dai fmt: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_fmt(codec_dai, fmt); + if (ret) { + dev_err(dev, "failed to set codec dai fmt: %d\n", ret); + return ret; + } + + if (is_dsd) + ret = snd_soc_dai_set_tdm_slot(cpu_dai, + 0x1, 0x1, + 1, params_width(params)); + else + ret = snd_soc_dai_set_tdm_slot(cpu_dai, + BIT(channels) - 1, BIT(channels) - 1, + 2, params_physical_width(params)); + if (ret) { + dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret); + return ret; + } + + return ret; +} + +static const u32 support_rates[] = { + 8000, 11025, 16000, 22050, + 32000, 44100, 48000, 88200, + 96000, 176400, 192000, 352800, + 384000, 705600, 768000, +}; + +static int imx_aif_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int ret = 0; + static struct snd_pcm_hw_constraint_list constraint_rates; + + constraint_rates.list = support_rates; + constraint_rates.count = ARRAY_SIZE(support_rates); + + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraint_rates); + if (ret) + return ret; + + return ret; +} + +static struct snd_soc_ops imx_aif_ops = { + .startup = imx_aif_startup, + .hw_params = imx_aif_hw_params, +}; + +static struct snd_soc_dai_link imx_ak4497_dai = { + .name = "ak4497", + .stream_name = "Audio", + .codec_dai_name = "ak4497-aif", + .ops = &imx_aif_ops, + .playback_only = 1, +}; + +static int imx_ak4497_probe(struct platform_device *pdev) +{ + struct imx_ak4497_data *priv; + struct device_node *cpu_np, *codec_np = NULL; + struct platform_device *cpu_pdev; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + cpu_np = of_parse_phandle(pdev->dev.of_node, "audio-cpu", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "audio dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); + if (!codec_np) { + dev_err(&pdev->dev, "audio codec phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find SAI platform device\n"); + ret = -EINVAL; + goto fail; + } + + imx_ak4497_dai.codec_of_node = codec_np; + imx_ak4497_dai.cpu_dai_name = dev_name(&cpu_pdev->dev); + imx_ak4497_dai.platform_of_node = cpu_np; + imx_ak4497_dai.playback_only = 1; + + priv->card.dai_link = &imx_ak4497_dai; + priv->card.num_links = 1; + priv->card.dev = &pdev->dev; + priv->card.owner = THIS_MODULE; + priv->card.dapm_widgets = imx_ak4497_dapm_widgets; + priv->card.num_dapm_widgets = ARRAY_SIZE(imx_ak4497_dapm_widgets); + priv->one2one_ratio = !of_device_is_compatible(pdev->dev.of_node, + "fsl,imx-audio-ak4497-mq"); + + ret = snd_soc_of_parse_card_name(&priv->card, "model"); + if (ret) + goto fail; + + snd_soc_card_set_drvdata(&priv->card, priv); + + ret = devm_snd_soc_register_card(&pdev->dev, &priv->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + + ret = 0; +fail: + if (cpu_np) + of_node_put(cpu_np); + if (codec_np) + of_node_put(codec_np); + + return ret; +} + +static const struct of_device_id imx_ak4497_dt_ids[] = { + { .compatible = "fsl,imx-audio-ak4497", }, + { .compatible = "fsl,imx-audio-ak4497-mq", }, + { }, +}; +MODULE_DEVICE_TABLE(of, imx_ak4497_dt_ids); + +static struct platform_driver imx_ak4497_driver = { + .driver = { + .name = "imx-ak4497", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_ak4497_dt_ids, + }, + .probe = imx_ak4497_probe, +}; +module_platform_driver(imx_ak4497_driver); + +MODULE_AUTHOR("Daniel Baluta <daniel.baluta@nxp.com>"); +MODULE_DESCRIPTION("Freescale i.MX AK4497 ASoC machine driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:imx-ak4497"); diff --git a/sound/soc/fsl/imx-ak5558.c b/sound/soc/fsl/imx-ak5558.c new file mode 100644 index 000000000000..c500b75786ff --- /dev/null +++ b/sound/soc/fsl/imx-ak5558.c @@ -0,0 +1,369 @@ +/* i.MX AK5558 audio support + * + * Copyright 2017 NXP + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/gpio/consumer.h> +#include <linux/of_device.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> +#include <sound/pcm.h> +#include <sound/soc-dapm.h> + +#include "fsl_sai.h" +#include "../codecs/ak5558.h" + + +struct imx_ak5558_data { + struct snd_soc_card card; + bool tdm_mode; + unsigned long slots; + unsigned long slot_width; + bool one2one_ratio; +}; + +/* + * imx_ack5558_fs_mul - sampling frequency multiplier + * + * min <= fs <= max, MCLK = mul * LRCK + */ +struct imx_ak5558_fs_mul { + unsigned int min; + unsigned int max; + unsigned int mul; +}; + +/* + * Auto MCLK selection based on LRCK for Normal Mode + * (Table 4 from datasheet) + */ +static const struct imx_ak5558_fs_mul fs_mul[] = { + { .min = 8000, .max = 32000, .mul = 1024 }, + { .min = 44100, .max = 48000, .mul = 512 }, + { .min = 88200, .max = 96000, .mul = 256 }, + { .min = 176400, .max = 192000, .mul = 128 }, + { .min = 352800, .max = 384000, .mul = 64 }, + { .min = 705600, .max = 768000, .mul = 32 }, +}; + +/* + * MCLK and BCLK selection based on TDM mode + * because of SAI we also add the restriction: MCLK >= 2 * BCLK + * (Table 9 from datasheet) + */ +static const struct imx_ak5558_fs_mul fs_mul_tdm[] = { + { .min = 128, .max = 128, .mul = 256 }, + { .min = 256, .max = 256, .mul = 512 }, + { .min = 512, .max = 512, .mul = 1024 }, +}; + +static struct snd_soc_dapm_widget imx_ak5558_dapm_widgets[] = { + SND_SOC_DAPM_LINE("Line In", NULL), +}; + +static const u32 ak5558_rates[] = { + 8000, 11025, 16000, 22050, + 32000, 44100, 48000, 88200, + 96000, 176400, 192000, 352800, + 384000, 705600, 768000, +}; + +static const u32 ak5558_tdm_rates[] = { + 8000, 16000, 32000, + 48000, 96000 +}; + +static const u32 ak5558_channels[] = { + 1, 2, 4, 6, 8, +}; + +static unsigned long ak5558_get_mclk_rate(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct imx_ak5558_data *data = snd_soc_card_get_drvdata(rtd->card); + unsigned int rate = params_rate(params); + unsigned int freq = 0; /* Let DAI manage clk frequency by default */ + int mode; + int i; + + if (data->tdm_mode) { + mode = data->slots * data->slot_width; + + for (i = 0; i < ARRAY_SIZE(fs_mul_tdm); i++) { + /* min = max = slots * slots_width */ + if (mode != fs_mul_tdm[i].min) + continue; + freq = rate * fs_mul_tdm[i].mul; + break; + } + } else { + for (i = 0; i < ARRAY_SIZE(fs_mul); i++) { + if (rate < fs_mul[i].min || rate > fs_mul[i].max) + continue; + freq = rate * fs_mul[i].mul; + break; + } + } + + return freq; +} + +static int imx_aif_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_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_card *card = rtd->card; + struct device *dev = card->dev; + struct imx_ak5558_data *data = snd_soc_card_get_drvdata(card); + unsigned int channels = params_channels(params); + unsigned long mclk_freq; + unsigned int fmt; + int ret; + + if (data->tdm_mode) + fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + else + fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + + ret = snd_soc_dai_set_fmt(cpu_dai, fmt); + if (ret) { + dev_err(dev, "failed to set cpu dai fmt: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_fmt(codec_dai, fmt); + if (ret) { + dev_err(dev, "failed to set codec dai fmt: %d\n", ret); + return ret; + } + + if (data->tdm_mode) { + /* support TDM256 (8 slots * 32 bits/per slot) */ + data->slots = 8; + data->slot_width = 32; + + ret = snd_soc_dai_set_tdm_slot(cpu_dai, + BIT(channels) - 1, BIT(channels) - 1, + data->slots, data->slot_width); + if (ret) { + dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_tdm_slot(codec_dai, + BIT(channels) - 1, BIT(channels) - 1, + 8, 32); + if (ret) { + dev_err(dev, "failed to set codec dai fmt: %d\n", ret); + return ret; + } + } else { + /* normal mode (I2S) */ + data->slots = 2; + data->slot_width = params_physical_width(params); + + ret = snd_soc_dai_set_tdm_slot(cpu_dai, + BIT(channels) - 1, BIT(channels) - 1, + data->slots, data->slot_width); + if (ret) { + dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret); + return ret; + } + } + + mclk_freq = ak5558_get_mclk_rate(substream, params); + ret = snd_soc_dai_set_sysclk(cpu_dai, FSL_SAI_CLK_MAST1, mclk_freq, + SND_SOC_CLOCK_OUT); + if (ret < 0) + dev_err(dev, "failed to set cpu_dai mclk1 rate %lu\n", + mclk_freq); + + return ret; +} + +static int imx_ak5558_hw_rule_rate(struct snd_pcm_hw_params *p, + struct snd_pcm_hw_rule *r) +{ + struct imx_ak5558_data *data = r->private; + struct snd_interval t = { .min = 8000, .max = 8000, }; + unsigned int fs; + unsigned long mclk_freq; + int i; + + fs = hw_param_interval(p, SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->min; + fs *= data->tdm_mode ? 8 : 2; + + /* Identify maximum supported rate */ + for (i = 0; i < ARRAY_SIZE(ak5558_rates); i++) { + mclk_freq = fs * ak5558_rates[i]; + /* Adjust SAI bclk:mclk ratio */ + mclk_freq *= data->one2one_ratio ? 1 : 2; + + /* Skip rates for which MCLK is beyond supported value */ + if (mclk_freq > 36864000) + continue; + + if (t.max < ak5558_rates[i]) + t.max = ak5558_rates[i]; + } + + return snd_interval_refine(hw_param_interval(p, r->var), &t); +} + +static int imx_aif_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct imx_ak5558_data *data = snd_soc_card_get_drvdata(card); + + static struct snd_pcm_hw_constraint_list constraint_rates; + static struct snd_pcm_hw_constraint_list constraint_channels; + int ret; + + if (data->tdm_mode) { + constraint_rates.list = ak5558_tdm_rates; + constraint_rates.count = ARRAY_SIZE(ak5558_tdm_rates); + } else { + constraint_rates.list = ak5558_rates; + constraint_rates.count = ARRAY_SIZE(ak5558_rates); + } + + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraint_rates); + if (ret) + return ret; + + constraint_channels.list = ak5558_channels; + constraint_channels.count = ARRAY_SIZE(ak5558_channels); + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraint_channels); + if (ret < 0) + return ret; + + return snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, imx_ak5558_hw_rule_rate, data, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1); +} + +static struct snd_soc_ops imx_aif_ops = { + .hw_params = imx_aif_hw_params, + .startup = imx_aif_startup, +}; + +static struct snd_soc_dai_link imx_ak5558_dai = { + .name = "ak5558", + .stream_name = "Audio", + .codec_dai_name = "ak5558-aif", + .ops = &imx_aif_ops, + .capture_only = 1, +}; + +static int imx_ak5558_probe(struct platform_device *pdev) +{ + struct imx_ak5558_data *priv; + struct device_node *cpu_np, *codec_np = NULL; + struct platform_device *cpu_pdev; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + cpu_np = of_parse_phandle(pdev->dev.of_node, "audio-cpu", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "audio dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); + if (!codec_np) { + dev_err(&pdev->dev, "audio codec phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find SAI platform device\n"); + ret = -EINVAL; + goto fail; + } + + if (of_find_property(pdev->dev.of_node, "fsl,tdm", NULL)) + priv->tdm_mode = true; + + imx_ak5558_dai.codec_of_node = codec_np; + imx_ak5558_dai.cpu_dai_name = dev_name(&cpu_pdev->dev); + imx_ak5558_dai.platform_of_node = cpu_np; + imx_ak5558_dai.capture_only = 1; + + priv->card.dai_link = &imx_ak5558_dai; + priv->card.num_links = 1; + priv->card.dev = &pdev->dev; + priv->card.owner = THIS_MODULE; + priv->card.dapm_widgets = imx_ak5558_dapm_widgets; + priv->card.num_dapm_widgets = ARRAY_SIZE(imx_ak5558_dapm_widgets); + priv->one2one_ratio = !of_device_is_compatible(pdev->dev.of_node, + "fsl,imx-audio-ak5558-mq"); + + ret = snd_soc_of_parse_card_name(&priv->card, "model"); + if (ret) + goto fail; + + snd_soc_card_set_drvdata(&priv->card, priv); + + ret = devm_snd_soc_register_card(&pdev->dev, &priv->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + + ret = 0; +fail: + if (cpu_np) + of_node_put(cpu_np); + if (codec_np) + of_node_put(codec_np); + + return ret; +} + +static const struct of_device_id imx_ak5558_dt_ids[] = { + { .compatible = "fsl,imx-audio-ak5558", }, + { .compatible = "fsl,imx-audio-ak5558-mq", }, + { }, +}; +MODULE_DEVICE_TABLE(of, imx_ak5558_dt_ids); + +static struct platform_driver imx_ak5558_driver = { + .driver = { + .name = "imx-ak5558", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_ak5558_dt_ids, + }, + .probe = imx_ak5558_probe, +}; +module_platform_driver(imx_ak5558_driver); + +MODULE_AUTHOR("Mihai Serban <mihai.serban@nxp.com>"); +MODULE_DESCRIPTION("Freescale i.MX AK5558 ASoC machine driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:imx-ak5558"); diff --git a/sound/soc/fsl/imx-amix.c b/sound/soc/fsl/imx-amix.c new file mode 100644 index 000000000000..d22ff25251f5 --- /dev/null +++ b/sound/soc/fsl/imx-amix.c @@ -0,0 +1,329 @@ +/* + * Copyright 2017 NXP + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/clk.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <linux/pm_runtime.h> +#include "fsl_sai.h" +#include "fsl_amix.h" + +struct imx_amix { + struct platform_device *pdev; + struct snd_soc_card card; + struct platform_device *amix_pdev; + struct platform_device *out_pdev; + struct clk *cpu_mclk; + int num_dai; + struct snd_soc_dai_link *dai; + int num_dai_conf; + struct snd_soc_codec_conf *dai_conf; + int num_dapm_routes; + struct snd_soc_dapm_route *dapm_routes; +}; + +static const u32 imx_amix_rates[] = { + 8000, 12000, 16000, 24000, 32000, 48000, 64000, 96000, +}; + +static const struct snd_pcm_hw_constraint_list imx_amix_rate_constraints = { + .count = ARRAY_SIZE(imx_amix_rates), + .list = imx_amix_rates, +}; + +static int imx_amix_fe_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct imx_amix *priv = snd_soc_card_get_drvdata(rtd->card); + struct snd_pcm_runtime *runtime = substream->runtime; + struct device *dev = rtd->card->dev; + unsigned long clk_rate = clk_get_rate(priv->cpu_mclk); + int ret; + + if (clk_rate % 24576000 == 0) { + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &imx_amix_rate_constraints); + if (ret < 0) + return ret; + } else + dev_warn(dev, "mclk may be not supported %lu\n", clk_rate); + + ret = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, 1, 8); + if (ret < 0) + return ret; + + return snd_pcm_hw_constraint_mask64(runtime, + SNDRV_PCM_HW_PARAM_FORMAT, FSL_AMIX_FORMATS); +} + +static int imx_amix_fe_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct device *dev = rtd->card->dev; + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + unsigned int fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF; + u32 channels = params_channels(params); + int ret, dir; + + /* For playback the AMIX is slave, and for record is master */ + fmt |= tx ? SND_SOC_DAIFMT_CBS_CFS : SND_SOC_DAIFMT_CBM_CFM; + dir = tx ? SND_SOC_CLOCK_OUT : SND_SOC_CLOCK_IN; + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(rtd->cpu_dai, fmt); + if (ret) { + dev_err(dev, "failed to set cpu dai fmt: %d\n", ret); + return ret; + } + + /* Specific configurations of DAIs starts from here */ + ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, FSL_SAI_CLK_MAST1, 0, dir); + if (ret) { + dev_err(dev, "failed to set cpu sysclk: %d\n", ret); + return ret; + } + + /* + * Per datasheet, AMIX expects 8 slots and 32 bits + * for every slot in TDM mode. + */ + ret = snd_soc_dai_set_tdm_slot(rtd->cpu_dai, BIT(channels) - 1, + BIT(channels) - 1, 8, 32); + if (ret) + dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret); + + return ret; +} + +static int imx_amix_be_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct device *dev = rtd->card->dev; + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + unsigned int fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF; + int ret; + + if (!tx) + return 0; + + /* For playback the AMIX is slave, and for record is master */ + fmt |= tx ? SND_SOC_DAIFMT_CBM_CFM : SND_SOC_DAIFMT_CBS_CFS; + + /* set AMIX DAI configuration */ + ret = snd_soc_dai_set_fmt(rtd->cpu_dai, fmt); + if (ret) + dev_err(dev, "failed to set AMIX DAI fmt: %d\n", ret); + + return ret; +} + +static struct snd_soc_ops imx_amix_fe_ops = { + .startup = imx_amix_fe_startup, + .hw_params = imx_amix_fe_hw_params, +}; + +static struct snd_soc_ops imx_amix_be_ops = { + .hw_params = imx_amix_be_hw_params, +}; + +static int imx_amix_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *amix_np = NULL, *out_cpu_np = NULL; + struct platform_device *amix_pdev = NULL; + struct platform_device *cpu_pdev; + struct of_phandle_args args; + struct imx_amix *priv; + int i, num_dai, ret; + const char *fe_name_pref = "HiFi-AMIX-FE-"; + char *be_name, *be_pb, *be_cp, *dai_name, *capture_dai_name; + + num_dai = of_count_phandle_with_args(np, "dais", NULL); + if (num_dai != FSL_AMIX_MAX_DAIS) { + dev_err(&pdev->dev, "Need 2 dais to be provided for %s\n", + np->full_name); + return -EINVAL; + } + + amix_np = of_parse_phandle(np, "amix-controller", 0); + if (!amix_np) { + dev_err(&pdev->dev, "Missing amix-controller phandle at %s\n", + np->full_name); + return -EINVAL; + } + + amix_pdev = of_find_device_by_node(amix_np); + if (!amix_pdev) { + dev_err(&pdev->dev, "Missing AMIX platform device for %s\n", + np->full_name); + return -EINVAL; + } + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->num_dai = 2 * num_dai; + priv->dai = devm_kzalloc(&pdev->dev, + priv->num_dai * sizeof(struct snd_soc_dai_link), + GFP_KERNEL); + if (!priv->dai) + return -ENOMEM; + + priv->num_dai_conf = num_dai; + priv->dai_conf = devm_kzalloc(&pdev->dev, + priv->num_dai_conf * sizeof(struct snd_soc_codec_conf), + GFP_KERNEL); + if (!priv->dai_conf) + return -ENOMEM; + + priv->num_dapm_routes = 3 * num_dai; + priv->dapm_routes = devm_kzalloc(&pdev->dev, + priv->num_dapm_routes * sizeof(struct snd_soc_dapm_route), + GFP_KERNEL); + if (!priv->dapm_routes) + return -ENOMEM; + + for (i = 0; i < num_dai; i++) { + ret = of_parse_phandle_with_args(np, "dais", NULL, i, &args); + if (ret < 0) { + dev_err(&pdev->dev, + "of_parse_phandle_with_args failed (%d)\n", ret); + return ret; + } + + cpu_pdev = of_find_device_by_node(args.np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find SAI platform device\n"); + return -EINVAL; + } + + dai_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s%s", + fe_name_pref, args.np->full_name + 1); + + dev_info(&pdev->dev, "DAI FE name:%s\n", dai_name); + + if (i == 0) { + out_cpu_np = args.np; + capture_dai_name = + devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s %s", + dai_name, "CPU-Capture"); + } + + priv->dai[i].name = dai_name; + priv->dai[i].stream_name = "HiFi-AMIX-FE"; + priv->dai[i].codec_dai_name = "snd-soc-dummy-dai"; + priv->dai[i].codec_name = "snd-soc-dummy"; + priv->dai[i].cpu_of_node = args.np; + priv->dai[i].cpu_dai_name = dev_name(&cpu_pdev->dev); + priv->dai[i].platform_of_node = args.np; + priv->dai[i].dynamic = 1; + priv->dai[i].dpcm_playback = 1; + priv->dai[i].dpcm_capture = (i == 0 ? 1 : 0); + priv->dai[i].ignore_pmdown_time = 1; + priv->dai[i].ops = &imx_amix_fe_ops; + + /* Add AMIX Backend */ + be_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "amix-%d", i); + be_pb = devm_kasprintf(&pdev->dev, GFP_KERNEL, "AMIX-Playback-%d", i); + be_cp = devm_kasprintf(&pdev->dev, GFP_KERNEL, "AMIX-Capture-%d", i); + + priv->dai[num_dai+i].name = be_name; + priv->dai[num_dai+i].codec_dai_name = "snd-soc-dummy-dai"; + priv->dai[num_dai+i].codec_name = "snd-soc-dummy"; + priv->dai[num_dai+i].cpu_of_node = amix_np; + priv->dai[num_dai+i].cpu_dai_name = be_name; + priv->dai[num_dai+i].platform_name = "snd-soc-dummy"; + priv->dai[num_dai+i].no_pcm = 1; + priv->dai[num_dai+i].dpcm_playback = 1; + priv->dai[num_dai+i].dpcm_capture = 1; + priv->dai[num_dai+i].ignore_pmdown_time = 1; + priv->dai[num_dai+i].ops = &imx_amix_be_ops; + + priv->dai_conf[i].of_node = args.np; + priv->dai_conf[i].name_prefix = dai_name; + + priv->dapm_routes[i].source = devm_kasprintf(&pdev->dev, + GFP_KERNEL, "%s %s", dai_name, "CPU-Playback"); + priv->dapm_routes[i].sink = be_pb; + priv->dapm_routes[num_dai+i].source = be_pb; + priv->dapm_routes[num_dai+i].sink = be_cp; + priv->dapm_routes[2*num_dai+i].source = be_cp; + priv->dapm_routes[2*num_dai+i].sink = capture_dai_name; + } + + cpu_pdev = of_find_device_by_node(out_cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find SAI platform device\n"); + return -EINVAL; + } + priv->cpu_mclk = devm_clk_get(&cpu_pdev->dev, "mclk1"); + if (IS_ERR(priv->cpu_mclk)) { + ret = PTR_ERR(priv->cpu_mclk); + dev_err(&cpu_pdev->dev, "failed to get DAI mclk1: %d\n", ret); + return -EINVAL; + } + + priv->amix_pdev = amix_pdev; + priv->out_pdev = cpu_pdev; + + priv->card.dai_link = priv->dai; + priv->card.num_links = priv->num_dai; + priv->card.codec_conf = priv->dai_conf; + priv->card.num_configs = priv->num_dai_conf; + priv->card.dapm_routes = priv->dapm_routes; + priv->card.num_dapm_routes = priv->num_dapm_routes; + priv->card.dev = &pdev->dev; + priv->card.owner = THIS_MODULE; + + ret = snd_soc_of_parse_card_name(&priv->card, "model"); + if (ret) { + dev_err(&pdev->dev, "snd_soc_of_parse_card_name failed (%d)\n", ret); + return ret; + } + + platform_set_drvdata(pdev, &priv->card); + snd_soc_card_set_drvdata(&priv->card, priv); + + ret = devm_snd_soc_register_card(&pdev->dev, &priv->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + return ret; + } + + return ret; +} + +static const struct of_device_id imx_amix_dt_ids[] = { + { .compatible = "fsl,imx-audio-amix", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_amix_dt_ids); + +static struct platform_driver imx_amix_driver = { + .probe = imx_amix_probe, + .driver = { + .name = "imx-amix", + .of_match_table = imx_amix_dt_ids, + .pm = &snd_soc_pm_ops, + }, +}; +module_platform_driver(imx_amix_driver); + +MODULE_DESCRIPTION("NXP AMIX ASoC machine driver"); +MODULE_AUTHOR("Viorel Suman <viorel.suman@nxp.com>"); +MODULE_ALIAS("platform:imx-amix"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/imx-audmux.c b/sound/soc/fsl/imx-audmux.c index 136df38c4536..f01f7be66b37 100644 --- a/sound/soc/fsl/imx-audmux.c +++ b/sound/soc/fsl/imx-audmux.c @@ -1,5 +1,5 @@ /* - * Copyright 2012 Freescale Semiconductor, Inc. + * Copyright 2012-2015 Freescale Semiconductor, Inc. * Copyright 2012 Linaro Ltd. * Copyright 2009 Pengutronix, Sascha Hauer <s.hauer@pengutronix.de> * @@ -33,6 +33,8 @@ static struct clk *audmux_clk; static void __iomem *audmux_base; +static u32 *regcache; +static u32 reg_max; #define IMX_AUDMUX_V2_PTCR(x) ((x) * 8) #define IMX_AUDMUX_V2_PDCR(x) ((x) * 8 + 4) @@ -333,8 +335,23 @@ static int imx_audmux_probe(struct platform_device *pdev) if (of_id) pdev->id_entry = of_id->data; audmux_type = pdev->id_entry->driver_data; - if (audmux_type == IMX31_AUDMUX) + + switch (audmux_type) { + case IMX31_AUDMUX: audmux_debugfs_init(); + reg_max = 14; + break; + case IMX21_AUDMUX: + reg_max = 6; + break; + default: + dev_err(&pdev->dev, "unsupported version!\n"); + return -EINVAL; + } + + regcache = devm_kzalloc(&pdev->dev, sizeof(u32) * reg_max, GFP_KERNEL); + if (!regcache) + return -ENOMEM; if (of_id) imx_audmux_parse_dt_defaults(pdev, pdev->dev.of_node); @@ -350,12 +367,47 @@ static int imx_audmux_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM_SLEEP +static int imx_audmux_suspend(struct device *dev) +{ + int i; + + clk_prepare_enable(audmux_clk); + + for (i = 0; i < reg_max; i++) + regcache[i] = readl(audmux_base + i * 4); + + clk_disable_unprepare(audmux_clk); + + return 0; +} + +static int imx_audmux_resume(struct device *dev) +{ + int i; + + clk_prepare_enable(audmux_clk); + + for (i = 0; i < reg_max; i++) + writel(regcache[i], audmux_base + i * 4); + + clk_disable_unprepare(audmux_clk); + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops imx_audmux_pm = { + SET_SYSTEM_SLEEP_PM_OPS(imx_audmux_suspend, imx_audmux_resume) +}; + static struct platform_driver imx_audmux_driver = { .probe = imx_audmux_probe, .remove = imx_audmux_remove, .id_table = imx_audmux_ids, .driver = { .name = DRIVER_NAME, + .pm = &imx_audmux_pm, .of_match_table = imx_audmux_dt_ids, } }; diff --git a/sound/soc/fsl/imx-cdnhdmi.c b/sound/soc/fsl/imx-cdnhdmi.c new file mode 100644 index 000000000000..9b6b1f0b4b38 --- /dev/null +++ b/sound/soc/fsl/imx-cdnhdmi.c @@ -0,0 +1,538 @@ +/* + * Copyright 2017-2018 NXP + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/i2c.h> +#include <linux/of_gpio.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/clk.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <sound/control.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <sound/hdmi-codec.h> +#include "../../../drivers/gpu/drm/imx/hdp/imx-hdp.h" + +#define SUPPORT_RATE_NUM 10 +#define SUPPORT_CHANNEL_NUM 10 + +struct imx_cdnhdmi_data { + struct snd_soc_dai_link dai; + struct snd_soc_card card; + int protocol; + u32 support_rates[SUPPORT_RATE_NUM]; + u32 support_rates_num; + u32 support_channels[SUPPORT_CHANNEL_NUM]; + u32 support_channels_num; + u32 edid_rates[SUPPORT_RATE_NUM]; + u32 edid_rates_count; + u32 edid_channels[SUPPORT_CHANNEL_NUM]; + u32 edid_channels_count; + uint8_t eld[MAX_ELD_BYTES]; +}; + +static int imx_cdnhdmi_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card); + static struct snd_pcm_hw_constraint_list constraint_rates; + static struct snd_pcm_hw_constraint_list constraint_channels; + int ret; + + constraint_rates.list = data->support_rates; + constraint_rates.count = data->support_rates_num; + + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraint_rates); + if (ret) + return ret; + + constraint_channels.list = data->support_channels; + constraint_channels.count = data->support_channels_num; + + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraint_channels); + if (ret) + return ret; + + return 0; +} + +static int imx_cdnhdmi_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_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_card *card = rtd->card; + struct device *dev = card->dev; + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + int ret; + + /* set cpu DAI configuration */ + if (tx) + ret = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS); + else + ret = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); + + if (ret) { + dev_err(dev, "failed to set cpu dai fmt: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, 0, tx ? SND_SOC_CLOCK_OUT : SND_SOC_CLOCK_IN); + if (ret) { + dev_err(dev, "failed to set cpu sysclk: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, 2, 32); + if (ret) { + dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret); + return ret; + } + + return 0; +} + +static struct snd_soc_ops imx_cdnhdmi_ops = { + .startup = imx_cdnhdmi_startup, + .hw_params = imx_cdnhdmi_hw_params, +}; + +static const unsigned int eld_rates[] = { + 32000, + 44100, + 48000, + 88200, + 96000, + 176400, + 192000, +}; + +static unsigned int sad_max_channels(const u8 *sad) +{ + return 1 + (sad[0] & 7); +} + +static int get_edid_info(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd = list_first_entry( + &card->rtd_list, struct snd_soc_pcm_runtime, list); + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_codec *codec = codec_dai->codec; + struct hdmi_codec_pdata *hcd = codec->dev->platform_data; + struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card); + int i, j, ret; + const u8 *sad; + unsigned int channel_max = 0; + unsigned int rate_mask = 0; + unsigned int rate_mask_eld = 0; + + ret = hcd->ops->get_eld(codec->dev->parent, hcd->data, + data->eld, sizeof(data->eld)); + sad = drm_eld_sad(data->eld); + if (sad) { + for (j = 0; j < data->support_rates_num; j++) { + for (i = 0; i < ARRAY_SIZE(eld_rates); i++) + if (eld_rates[i] == data->support_rates[j]) + rate_mask |= BIT(i); + } + + for (i = drm_eld_sad_count(data->eld); i > 0; i--, sad += 3) { + if (rate_mask & sad[1]) + channel_max = max(channel_max, sad_max_channels(sad)); + + if (sad_max_channels(sad) >= 2) + rate_mask_eld |= sad[1]; + } + } + + rate_mask = rate_mask & rate_mask_eld; + + data->edid_rates_count = 0; + data->edid_channels_count = 0; + + for (i = 0; i < ARRAY_SIZE(eld_rates); i++) { + if (rate_mask & BIT(i)) { + data->edid_rates[data->edid_rates_count] = eld_rates[i]; + data->edid_rates_count++; + } + } + + for (i = 0; i < data->support_channels_num; i++) { + if (data->support_channels[i] <= channel_max) { + data->edid_channels[data->edid_channels_count] + = data->support_channels[i]; + data->edid_channels_count++; + } + } + + return 0; +} + +static int imx_cdnhdmi_channels_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card); + + get_edid_info(card); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = data->edid_channels_count; + + return 0; +} + +static int imx_cdnhdmi_channels_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card); + int i; + + get_edid_info(card); + + for (i = 0 ; i < data->edid_channels_count ; i++) + uvalue->value.integer.value[i] = data->edid_channels[i]; + + return 0; +} + +static int imx_cdnhdmi_rates_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card); + + get_edid_info(card); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = data->edid_rates_count; + + return 0; +} + +static int imx_cdnhdmi_rates_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card); + int i; + + get_edid_info(card); + + for (i = 0 ; i < data->edid_rates_count; i++) + uvalue->value.integer.value[i] = data->edid_rates[i]; + + return 0; +} + +static int imx_cdnhdmi_formats_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 3; + + return 0; +} + +static int imx_cdnhdmi_formats_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + uvalue->value.integer.value[0] = 16; + uvalue->value.integer.value[1] = 24; + uvalue->value.integer.value[2] = 32; + + return 0; +} + +static int get_edid_rx_info(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd = list_first_entry( + &card->rtd_list, struct snd_soc_pcm_runtime, list); + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_codec *codec = codec_dai->codec; + struct hdmi_codec_pdata *hcd = codec->dev->platform_data; + struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card); + int ret; + + ret = hcd->ops->get_eld(codec->dev->parent, hcd->data, + data->eld, sizeof(data->eld)); + + if (ret) + return -EINVAL; + + data->edid_rates[0] = data->eld[0] + + (data->eld[1] << 8) + + (data->eld[2] << 16) + + (data->eld[3] << 24); + + data->edid_channels[0] = data->eld[4] + + (data->eld[5] << 8) + + (data->eld[6] << 16) + + (data->eld[7] << 24); + + + return 0; +} + +static int imx_cdnhdmi_rx_channels_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 2; + uinfo->value.integer.max = 8; + + return 0; +} + +static int imx_cdnhdmi_rx_channels_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card); + int ret; + + ret = get_edid_rx_info(card); + if (ret) + return ret; + uvalue->value.integer.value[0] = data->edid_channels[0]; + + return 0; +} + +static int imx_cdnhdmi_rx_rates_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 16000; + uinfo->value.integer.max = 192000; + + return 0; +} + +static int imx_cdnhdmi_rx_rates_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card); + int ret; + + ret = get_edid_rx_info(card); + if (ret) + return ret; + + uvalue->value.integer.value[0] = data->edid_rates[0]; + + return 0; +} + +static struct snd_kcontrol_new imx_cdnhdmi_ctrls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HDMI Support Channels", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = imx_cdnhdmi_channels_info, + .get = imx_cdnhdmi_channels_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HDMI Support Rates", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = imx_cdnhdmi_rates_info, + .get = imx_cdnhdmi_rates_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HDMI Support Formats", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = imx_cdnhdmi_formats_info, + .get = imx_cdnhdmi_formats_get, + }, +}; + +static struct snd_kcontrol_new imx_cdnhdmi_rx_ctrls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HDMI Rx Channels", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = imx_cdnhdmi_rx_channels_info, + .get = imx_cdnhdmi_rx_channels_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HDMI Rx Rates", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = imx_cdnhdmi_rx_rates_info, + .get = imx_cdnhdmi_rx_rates_get, + }, +}; + +static int imx_cdnhdmi_probe(struct platform_device *pdev) +{ + struct device_node *cpu_np, *cdnhdmi_np = NULL; + struct platform_device *cpu_pdev; + struct imx_cdnhdmi_data *data; + int ret; + int i; + + cpu_np = of_parse_phandle(pdev->dev.of_node, "audio-cpu", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find SAI platform device\n"); + ret = -EINVAL; + goto fail; + } + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto fail; + } + + for (i = 0; i < SUPPORT_RATE_NUM; i++) { + ret = of_property_read_u32_index(pdev->dev.of_node, + "constraint-rate", + i, &data->support_rates[i]); + if (!ret) + data->support_rates_num = i + 1; + else + break; + } + + if (data->support_rates_num == 0) { + data->support_rates[0] = 48000; + data->support_rates[1] = 96000; + data->support_rates[2] = 32000; + data->support_rates[3] = 192000; + data->support_rates_num = 4; + } + + data->support_channels[0] = 2; + data->support_channels[1] = 4; + data->support_channels[2] = 8; + data->support_channels_num = 3; + + of_property_read_u32(pdev->dev.of_node, "protocol", + &data->protocol); + + data->dai.name = "imx8 hdmi"; + data->dai.stream_name = "imx8 hdmi"; + data->dai.cpu_dai_name = dev_name(&cpu_pdev->dev); + data->dai.platform_of_node = cpu_np; + data->dai.ops = &imx_cdnhdmi_ops; + data->dai.playback_only = true; + data->dai.capture_only = false; + data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + + if (of_property_read_bool(pdev->dev.of_node, "hdmi-out")) { + data->dai.playback_only = true; + data->dai.capture_only = false; + data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + data->dai.codec_dai_name = "hdmi-hifi.0"; + data->dai.codec_name = "hdmi-audio-codec.1"; + data->card.controls = imx_cdnhdmi_ctrls; + data->card.num_controls = ARRAY_SIZE(imx_cdnhdmi_ctrls); + } + + if (of_property_read_bool(pdev->dev.of_node, "hdmi-in")) { + data->dai.playback_only = false; + data->dai.capture_only = true; + data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; + data->dai.codec_dai_name = "hdmi-hifi.0"; + data->dai.codec_name = "hdmi-audio-codec.2"; + data->card.controls = imx_cdnhdmi_rx_ctrls; + data->card.num_controls = ARRAY_SIZE(imx_cdnhdmi_rx_ctrls); + } + + if ((data->dai.playback_only && data->dai.capture_only) + || (!data->dai.playback_only && !data->dai.capture_only)) { + dev_err(&pdev->dev, "Wrongly enable HDMI DAI link\n"); + goto fail; + } + + data->card.dev = &pdev->dev; + data->card.owner = THIS_MODULE; + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) + goto fail; + data->card.num_links = 1; + data->card.dai_link = &data->dai; + + + platform_set_drvdata(pdev, &data->card); + snd_soc_card_set_drvdata(&data->card, data); + ret = devm_snd_soc_register_card(&pdev->dev, &data->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + +fail: + if (cpu_np) + of_node_put(cpu_np); + if (cdnhdmi_np) + of_node_put(cdnhdmi_np); + return ret; +} + +static const struct of_device_id imx_cdnhdmi_dt_ids[] = { + { .compatible = "fsl,imx-audio-cdnhdmi", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_cdnhdmi_dt_ids); + +static struct platform_driver imx_cdnhdmi_driver = { + .driver = { + .name = "imx-cdnhdmi", + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + .of_match_table = imx_cdnhdmi_dt_ids, + }, + .probe = imx_cdnhdmi_probe, +}; +module_platform_driver(imx_cdnhdmi_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Freescale i.MX hdmi audio ASoC machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-cdnhdmi"); diff --git a/sound/soc/fsl/imx-cs42888.c b/sound/soc/fsl/imx-cs42888.c new file mode 100644 index 000000000000..39b836fe62de --- /dev/null +++ b/sound/soc/fsl/imx-cs42888.c @@ -0,0 +1,458 @@ +/* + * Copyright (C) 2010-2016 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/initval.h> +#include <sound/pcm_params.h> + +#include "fsl_esai.h" + +#define CODEC_CLK_EXTER_OSC 1 +#define CODEC_CLK_ESAI_HCKT 2 +#define SUPPORT_RATE_NUM 10 + +struct imx_priv { + struct clk *codec_clk; + struct clk *esai_clk; + unsigned int mclk_freq; + unsigned int esai_freq; + struct platform_device *pdev; + struct platform_device *asrc_pdev; + u32 asrc_rate; + u32 asrc_format; + bool is_codec_master; + bool is_codec_rpmsg; +}; + +static struct imx_priv card_priv; + +static int imx_cs42888_surround_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_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct imx_priv *priv = &card_priv; + struct device *dev = &priv->pdev->dev; + u32 channels = params_channels(params); + u32 max_tdm_rate; + + bool enable_tdm = channels > 1 && channels % 2; + u32 dai_format = SND_SOC_DAIFMT_NB_NF | + (enable_tdm ? SND_SOC_DAIFMT_DSP_A : SND_SOC_DAIFMT_LEFT_J); + + int ret = 0; + + priv->mclk_freq = clk_get_rate(priv->codec_clk); + priv->esai_freq = clk_get_rate(priv->esai_clk); + + if (priv->is_codec_master) { + /* TDM is not supported by codec in master mode */ + if (enable_tdm) { + dev_err(dev, "%d channels are not supported in codec master mode\n", + channels); + return -EINVAL; + } + dai_format |= SND_SOC_DAIFMT_CBM_CFM; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = snd_soc_dai_set_sysclk(cpu_dai, ESAI_HCKT_EXTAL, + priv->mclk_freq, SND_SOC_CLOCK_IN); + else + ret = snd_soc_dai_set_sysclk(cpu_dai, ESAI_HCKR_EXTAL, + priv->mclk_freq, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(dev, "failed to set cpu sysclk: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, 0, + priv->mclk_freq, SND_SOC_CLOCK_OUT); + if (ret) { + dev_err(dev, "failed to set codec sysclk: %d\n", ret); + return ret; + } + + } else { + dai_format |= SND_SOC_DAIFMT_CBS_CFS; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = snd_soc_dai_set_sysclk(cpu_dai, ESAI_HCKT_EXTAL, + priv->mclk_freq, SND_SOC_CLOCK_OUT); + else + ret = snd_soc_dai_set_sysclk(cpu_dai, ESAI_HCKR_EXTAL, + priv->mclk_freq, SND_SOC_CLOCK_OUT); + if (ret) { + dev_err(dev, "failed to set cpu sysclk: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, 0, + priv->mclk_freq, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(dev, "failed to set codec sysclk: %d\n", ret); + return ret; + } + } + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, dai_format); + if (ret) { + dev_err(dev, "failed to set cpu dai fmt: %d\n", ret); + return ret; + } + /* set i.MX active slot mask */ + if (enable_tdm) { + /* 2 required by ESAI BCLK divisors, 8 slots, 32 width */ + if (priv->is_codec_master) + max_tdm_rate = priv->mclk_freq / (8*32); + else + max_tdm_rate = priv->esai_freq / (2*8*32); + if (params_rate(params) > max_tdm_rate) { + dev_err(dev, + "maximum supported sampling rate for %d channels is %dKHz\n", + channels, max_tdm_rate / 1000); + return -EINVAL; + } + + /* + * Per datasheet, the codec expects 8 slots and 32 bits + * for every slot in TDM mode. + */ + snd_soc_dai_set_tdm_slot(cpu_dai, + BIT(channels) - 1, BIT(channels) - 1, + 8, 32); + } else + snd_soc_dai_set_tdm_slot(cpu_dai, 0x3, 0x3, 2, 32); + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, dai_format); + if (ret) { + dev_err(dev, "failed to set codec dai fmt: %d\n", ret); + return ret; + } + return 0; +} + +static int imx_cs42888_surround_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + static struct snd_pcm_hw_constraint_list constraint_rates; + struct imx_priv *priv = &card_priv; + struct device *dev = &priv->pdev->dev; + static u32 support_rates[SUPPORT_RATE_NUM]; + int ret; + + priv->mclk_freq = clk_get_rate(priv->codec_clk); + + if (priv->mclk_freq % 12288000 == 0) { + support_rates[0] = 48000; + support_rates[1] = 96000; + support_rates[2] = 192000; + constraint_rates.list = support_rates; + constraint_rates.count = 3; + + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraint_rates); + if (ret) + return ret; + } else + dev_warn(dev, "mclk may be not supported %d\n", priv->mclk_freq); + + return 0; +} + +static struct snd_soc_ops imx_cs42888_surround_ops = { + .startup = imx_cs42888_surround_startup, + .hw_params = imx_cs42888_surround_hw_params, +}; + +/** + * imx_cs42888_surround_startup() is to set constrain for hw parameter, but + * backend use same runtime as frontend, for p2p backend need to use different + * parameter, so backend can't use the startup. + */ +static struct snd_soc_ops imx_cs42888_surround_ops_be = { + .hw_params = imx_cs42888_surround_hw_params, +}; + + +static const struct snd_soc_dapm_widget imx_cs42888_dapm_widgets[] = { + SND_SOC_DAPM_LINE("Line Out Jack", NULL), + SND_SOC_DAPM_LINE("Line In Jack", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* Line out jack */ + {"Line Out Jack", NULL, "AOUT1L"}, + {"Line Out Jack", NULL, "AOUT1R"}, + {"Line Out Jack", NULL, "AOUT2L"}, + {"Line Out Jack", NULL, "AOUT2R"}, + {"Line Out Jack", NULL, "AOUT3L"}, + {"Line Out Jack", NULL, "AOUT3R"}, + {"Line Out Jack", NULL, "AOUT4L"}, + {"Line Out Jack", NULL, "AOUT4R"}, + {"AIN1L", NULL, "Line In Jack"}, + {"AIN1R", NULL, "Line In Jack"}, + {"AIN2L", NULL, "Line In Jack"}, + {"AIN2R", NULL, "Line In Jack"}, + {"Playback", NULL, "CPU-Playback"},/* dai route for be and fe */ + {"CPU-Capture", NULL, "Capture"}, + {"CPU-Playback", NULL, "ASRC-Playback"}, + {"ASRC-Capture", NULL, "CPU-Capture"}, +}; + +static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) { + + struct imx_priv *priv = &card_priv; + struct snd_interval *rate; + struct snd_mask *mask; + + if (!priv->asrc_pdev) + return -EINVAL; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + rate->max = rate->min = priv->asrc_rate; + + mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + snd_mask_none(mask); + snd_mask_set(mask, priv->asrc_format); + + return 0; +} + +static struct snd_soc_dai_link imx_cs42888_dai[] = { + { + .name = "HiFi", + .stream_name = "HiFi", + .codec_dai_name = "cs42888", + .ops = &imx_cs42888_surround_ops, + .ignore_pmdown_time = 1, + }, + { + .name = "HiFi-ASRC-FE", + .stream_name = "HiFi-ASRC-FE", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .dynamic = 1, + .ignore_pmdown_time = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .dpcm_merged_chan = 1, + }, + { + .name = "HiFi-ASRC-BE", + .stream_name = "HiFi-ASRC-BE", + .codec_dai_name = "cs42888", + .platform_name = "snd-soc-dummy", + .no_pcm = 1, + .ignore_pmdown_time = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &imx_cs42888_surround_ops_be, + .be_hw_params_fixup = be_hw_params_fixup, + }, +}; + +static struct snd_soc_card snd_soc_card_imx_cs42888 = { + .name = "cs42888-audio", + .dai_link = imx_cs42888_dai, + .dapm_widgets = imx_cs42888_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(imx_cs42888_dapm_widgets), + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), + .owner = THIS_MODULE, +}; + +/* + * This function will register the snd_soc_pcm_link drivers. + */ +static int imx_cs42888_probe(struct platform_device *pdev) +{ + struct device_node *esai_np, *codec_np; + struct device_node *asrc_np = NULL; + struct platform_device *esai_pdev; + struct platform_device *asrc_pdev = NULL; + struct imx_priv *priv = &card_priv; + int ret; + u32 width; + + priv->pdev = pdev; + priv->asrc_pdev = NULL; + + if (of_property_read_bool(pdev->dev.of_node, "codec-rpmsg")) + priv->is_codec_rpmsg = true; + + esai_np = of_parse_phandle(pdev->dev.of_node, "esai-controller", 0); + codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); + if (!esai_np || !codec_np) { + dev_err(&pdev->dev, "phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + asrc_np = of_parse_phandle(pdev->dev.of_node, "asrc-controller", 0); + if (asrc_np) { + asrc_pdev = of_find_device_by_node(asrc_np); + priv->asrc_pdev = asrc_pdev; + } + + esai_pdev = of_find_device_by_node(esai_np); + if (!esai_pdev) { + dev_err(&pdev->dev, "failed to find ESAI platform device\n"); + ret = -EINVAL; + goto fail; + } + + if (priv->is_codec_rpmsg) { + struct platform_device *codec_dev; + + codec_dev = of_find_device_by_node(codec_np); + if (!codec_dev || !codec_dev->dev.driver) { + dev_err(&pdev->dev, "failed to find codec platform device\n"); + ret = -EINVAL; + goto fail; + } + + priv->codec_clk = devm_clk_get(&codec_dev->dev, NULL); + if (IS_ERR(priv->codec_clk)) { + ret = PTR_ERR(priv->codec_clk); + dev_err(&codec_dev->dev, "failed to get codec clk: %d\n", ret); + goto fail; + } + + } else { + struct i2c_client *codec_dev; + + codec_dev = of_find_i2c_device_by_node(codec_np); + if (!codec_dev || !codec_dev->dev.driver) { + dev_err(&pdev->dev, "failed to find codec platform device\n"); + ret = -EINVAL; + goto fail; + } + + priv->codec_clk = devm_clk_get(&codec_dev->dev, NULL); + if (IS_ERR(priv->codec_clk)) { + ret = PTR_ERR(priv->codec_clk); + dev_err(&codec_dev->dev, "failed to get codec clk: %d\n", ret); + goto fail; + } + } + + /*if there is no asrc controller, we only enable one device*/ + if (!asrc_pdev) { + if (priv->is_codec_rpmsg) { + imx_cs42888_dai[0].codec_name = "rpmsg-audio-codec-cs42888"; + imx_cs42888_dai[0].codec_dai_name = "cs42888"; + } else { + imx_cs42888_dai[0].codec_of_node = codec_np; + } + imx_cs42888_dai[0].cpu_dai_name = dev_name(&esai_pdev->dev); + imx_cs42888_dai[0].platform_of_node = esai_np; + snd_soc_card_imx_cs42888.num_links = 1; + snd_soc_card_imx_cs42888.num_dapm_routes = + ARRAY_SIZE(audio_map) - 2; + } else { + imx_cs42888_dai[0].codec_of_node = codec_np; + imx_cs42888_dai[0].cpu_dai_name = dev_name(&esai_pdev->dev); + imx_cs42888_dai[0].platform_of_node = esai_np; + imx_cs42888_dai[1].cpu_of_node = asrc_np; + imx_cs42888_dai[1].platform_of_node = asrc_np; + imx_cs42888_dai[2].codec_of_node = codec_np; + imx_cs42888_dai[2].cpu_dai_name = dev_name(&esai_pdev->dev); + snd_soc_card_imx_cs42888.num_links = 3; + + ret = of_property_read_u32(asrc_np, "fsl,asrc-rate", + &priv->asrc_rate); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + ret = -EINVAL; + goto fail; + } + + ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + ret = -EINVAL; + goto fail; + } + + if (width == 24) + priv->asrc_format = SNDRV_PCM_FORMAT_S24_LE; + else + priv->asrc_format = SNDRV_PCM_FORMAT_S16_LE; + } + + priv->esai_clk = devm_clk_get(&esai_pdev->dev, "extal"); + if (IS_ERR(priv->esai_clk)) { + ret = PTR_ERR(priv->esai_clk); + dev_err(&esai_pdev->dev, "failed to get cpu clk: %d\n", ret); + goto fail; + } + + priv->is_codec_master = false; + if (of_property_read_bool(pdev->dev.of_node, "codec-master")) + priv->is_codec_master = true; + + snd_soc_card_imx_cs42888.dev = &pdev->dev; + + platform_set_drvdata(pdev, &snd_soc_card_imx_cs42888); + + ret = snd_soc_register_card(&snd_soc_card_imx_cs42888); + if (ret) + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); +fail: + if (asrc_np) + of_node_put(asrc_np); + if (esai_np) + of_node_put(esai_np); + if (codec_np) + of_node_put(codec_np); + return ret; +} + +static int imx_cs42888_remove(struct platform_device *pdev) +{ + snd_soc_unregister_card(&snd_soc_card_imx_cs42888); + return 0; +} + +static const struct of_device_id imx_cs42888_dt_ids[] = { + { .compatible = "fsl,imx-audio-cs42888", }, + { /* sentinel */ } +}; + +static struct platform_driver imx_cs42888_driver = { + .probe = imx_cs42888_probe, + .remove = imx_cs42888_remove, + .driver = { + .name = "imx-cs42888", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_cs42888_dt_ids, + }, +}; +module_platform_driver(imx_cs42888_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("ALSA SoC cs42888 Machine Layer Driver"); +MODULE_ALIAS("platform:imx-cs42888"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/fsl/imx-hdmi-dma.c b/sound/soc/fsl/imx-hdmi-dma.c new file mode 100644 index 000000000000..411906f9ab70 --- /dev/null +++ b/sound/soc/fsl/imx-hdmi-dma.c @@ -0,0 +1,1171 @@ +/* + * imx-hdmi-dma.c -- HDMI DMA driver for ALSA Soc Audio Layer + * + * Copyright (C) 2011-2016 Freescale Semiconductor, Inc. + * + * based on imx-pcm-dma-mx2.c + * Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de> + * + * This code is based on code copyrighted by Freescale, + * Liam Girdwood, Javier Martin and probably others. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/mfd/mxc-hdmi-core.h> +#include <linux/platform_data/dma-imx.h> + +#include <video/mxc_hdmi.h> + +#include "imx-hdmi.h" + +#define HDMI_DMA_BURST_UNSPECIFIED_LEGNTH 0 +#define HDMI_DMA_BURST_INCR4 1 +#define HDMI_DMA_BURST_INCR8 2 +#define HDMI_DMA_BURST_INCR16 3 + +#define HDMI_BASE_ADDR 0x00120000 + +struct hdmi_sdma_script { + int control_reg_addr; + int status_reg_addr; + int dma_start_addr; + u32 buffer[20]; +}; + +struct hdmi_dma_priv { + struct snd_pcm_substream *substream; + struct platform_device *pdev; + + struct snd_dma_buffer hw_buffer; + unsigned long buffer_bytes; + unsigned long appl_bytes; + + int periods; + int period_time; + int period_bytes; + int dma_period_bytes; + int buffer_ratio; + + unsigned long offset; + + snd_pcm_format_t format; + int sample_align; + int sample_bits; + int channels; + int rate; + + int frame_idx; + + bool tx_active; + spinlock_t irq_lock; + + /* SDMA part */ + dma_addr_t phy_hdmi_sdma_t; + struct hdmi_sdma_script *hdmi_sdma_t; + struct dma_chan *dma_channel; + struct dma_async_tx_descriptor *desc; + struct imx_hdmi_sdma_params sdma_params; +}; + +/* bit 0:0:0:b:p(0):c:(u)0:(v)0 */ +/* max 8 channels supported; channels are interleaved */ +static u8 g_packet_head_table[48 * 8]; + +void hdmi_dma_copy_16_neon_lut(unsigned short *src, unsigned int *dst, + int samples, unsigned char *lookup_table); +void hdmi_dma_copy_16_neon_fast(unsigned short *src, unsigned int *dst, + int samples); +void hdmi_dma_copy_24_neon_lut(unsigned int *src, unsigned int *dst, + int samples, unsigned char *lookup_table); +void hdmi_dma_copy_24_neon_fast(unsigned int *src, unsigned int *dst, + int samples); +static void hdmi_dma_irq_enable(struct hdmi_dma_priv *priv); +static void hdmi_dma_irq_disable(struct hdmi_dma_priv *priv); + +union hdmi_audio_header_t iec_header; +EXPORT_SYMBOL(iec_header); + +/* + * Note that the period size for DMA != period size for ALSA because the + * driver adds iec frame info to the audio samples (in hdmi_dma_copy). + * + * Each 4 byte subframe = 1 byte of iec data + 3 byte audio sample. + * + * A 16 bit audio sample becomes 32 bits including the frame info. Ratio=2 + * A 24 bit audio sample becomes 32 bits including the frame info. Ratio=3:4 + * If the 24 bit raw audio is in 32 bit words, the + * + * Original Packed into subframe Ratio of size Format + * sample how many size of DMA buffer + * (bits) bits to ALSA buffer + * -------- ----------- -------- -------------- ------------------------ + * 16 16 32 2 SNDRV_PCM_FORMAT_S16_LE + * 24 24 32 1.33 SNDRV_PCM_FORMAT_S24_3LE* + * 24 32 32 1 SNDRV_PCM_FORMAT_S24_LE + * + * *so SNDRV_PCM_FORMAT_S24_3LE is not supported. + */ + +/* + * The minimum dma period is one IEC audio frame (192 * 4 * channels). + * The maximum dma period for the HDMI DMA is 8K. + * + * channels minimum maximum + * dma period dma period + * -------- ------------------ ---------- + * 2 192 * 4 * 2 = 1536 * 4 = 6144 + * 4 192 * 4 * 4 = 3072 * 2 = 6144 + * 6 192 * 4 * 6 = 4608 * 1 = 4608 + * 8 192 * 4 * 8 = 6144 * 1 = 6144 + * + * Bottom line: + * 1. Must keep the ratio of DMA buffer to ALSA buffer consistent. + * 2. frame_idx is saved in the private data, so even if a frame cannot be + * transmitted in a period, it can be continued in the next period. This + * is necessary for 6 ch. + */ +#define HDMI_DMA_PERIOD_BYTES (12288) +#define HDMI_DMA_BUF_SIZE (128 * 1024) +#define HDMI_PCM_BUF_SIZE (128 * 1024) + +#define hdmi_audio_debug(dev, reg) \ + dev_dbg(dev, #reg ": 0x%02x\n", hdmi_readb(reg)) + +#ifdef DEBUG +static void dumpregs(struct device *dev) +{ + hdmi_audio_debug(dev, HDMI_AHB_DMA_CONF0); + hdmi_audio_debug(dev, HDMI_AHB_DMA_START); + hdmi_audio_debug(dev, HDMI_AHB_DMA_STOP); + hdmi_audio_debug(dev, HDMI_AHB_DMA_THRSLD); + hdmi_audio_debug(dev, HDMI_AHB_DMA_STRADDR0); + hdmi_audio_debug(dev, HDMI_AHB_DMA_STPADDR0); + hdmi_audio_debug(dev, HDMI_AHB_DMA_BSTADDR0); + hdmi_audio_debug(dev, HDMI_AHB_DMA_MBLENGTH0); + hdmi_audio_debug(dev, HDMI_AHB_DMA_MBLENGTH1); + hdmi_audio_debug(dev, HDMI_AHB_DMA_STAT); + hdmi_audio_debug(dev, HDMI_AHB_DMA_INT); + hdmi_audio_debug(dev, HDMI_AHB_DMA_MASK); + hdmi_audio_debug(dev, HDMI_AHB_DMA_POL); + hdmi_audio_debug(dev, HDMI_AHB_DMA_CONF1); + hdmi_audio_debug(dev, HDMI_AHB_DMA_BUFFSTAT); + hdmi_audio_debug(dev, HDMI_AHB_DMA_BUFFINT); + hdmi_audio_debug(dev, HDMI_AHB_DMA_BUFFMASK); + hdmi_audio_debug(dev, HDMI_AHB_DMA_BUFFPOL); + hdmi_audio_debug(dev, HDMI_IH_MUTE_AHBDMAAUD_STAT0); + hdmi_audio_debug(dev, HDMI_IH_AHBDMAAUD_STAT0); + hdmi_audio_debug(dev, HDMI_IH_MUTE); +} + +static void dumppriv(struct device *dev, struct hdmi_dma_priv *priv) +{ + dev_dbg(dev, "channels = %d\n", priv->channels); + dev_dbg(dev, "periods = %d\n", priv->periods); + dev_dbg(dev, "period_bytes = %d\n", priv->period_bytes); + dev_dbg(dev, "dma period_bytes = %d\n", priv->dma_period_bytes); + dev_dbg(dev, "buffer_ratio = %d\n", priv->buffer_ratio); + dev_dbg(dev, "hw dma buffer = 0x%08x\n", (int)priv->hw_buffer.addr); + dev_dbg(dev, "dma buf size = %d\n", (int)priv->buffer_bytes); + dev_dbg(dev, "sample_rate = %d\n", (int)priv->rate); +} +#else +static void dumpregs(struct device *dev) {} +static void dumppriv(struct device *dev, struct hdmi_dma_priv *priv) {} +#endif + +/* + * Conditions for DMA to work: + * ((final_addr - initial_addr)>>2)+1) < 2k. So max period is 8k. + * (inital_addr & 0x3) == 0 + * (final_addr & 0x3) == 0x3 + * + * The DMA Period should be an integer multiple of the IEC 60958 audio + * frame size, which is 768 bytes (192 * 4). + */ +static void hdmi_dma_set_addr(int start_addr, int dma_period_bytes) +{ + int final_addr = start_addr + dma_period_bytes - 1; + + hdmi_write4(start_addr, HDMI_AHB_DMA_STRADDR0); + hdmi_write4(final_addr, HDMI_AHB_DMA_STPADDR0); +} + +static void hdmi_dma_irq_set(bool set) +{ + u8 val = hdmi_readb(HDMI_AHB_DMA_MASK); + + if (set) + val |= HDMI_AHB_DMA_DONE; + else + val &= (u8)~HDMI_AHB_DMA_DONE; + + hdmi_writeb(val, HDMI_AHB_DMA_MASK); +} + +static void hdmi_mask(int mask) +{ + u8 regval = hdmi_readb(HDMI_AHB_DMA_MASK); + + if (mask) + regval |= HDMI_AHB_DMA_ERROR | HDMI_AHB_DMA_FIFO_EMPTY; + else + regval &= (u8)~(HDMI_AHB_DMA_ERROR | HDMI_AHB_DMA_FIFO_EMPTY); + + hdmi_writeb(regval, HDMI_AHB_DMA_MASK); +} + +int odd_ones(unsigned a) +{ + a ^= a >> 8; + a ^= a >> 4; + a ^= a >> 2; + a ^= a >> 1; + + return a & 1; +} + +/* Add frame information for one pcm subframe */ +static u32 hdmi_dma_add_frame_info(struct hdmi_dma_priv *priv, + u32 pcm_data, int subframe_idx) +{ + union hdmi_audio_dma_data_t subframe; + + subframe.U = 0; + iec_header.B.channel = subframe_idx; + + /* fill b (start-of-block) */ + subframe.B.b = (priv->frame_idx == 0) ? 1 : 0; + + /* fill c (channel status) */ + if (priv->frame_idx < 42) + subframe.B.c = (iec_header.U >> priv->frame_idx) & 0x1; + else + subframe.B.c = 0; + + subframe.B.p = odd_ones(pcm_data); + subframe.B.p ^= subframe.B.c; + subframe.B.p ^= subframe.B.u; + subframe.B.p ^= subframe.B.v; + + /* fill data */ + if (priv->sample_bits == 16) + subframe.B.data = pcm_data << 8; + else + subframe.B.data = pcm_data; + + return subframe.U; +} + +static void init_table(int channels) +{ + unsigned char *p = g_packet_head_table; + int i, ch = 0; + + for (i = 0; i < 48; i++) { + int b = 0; + if (i == 0) + b = 1; + + for (ch = 0; ch < channels; ch++) { + int c = 0; + if (i < 42) { + iec_header.B.channel = ch+1; + c = (iec_header.U >> i) & 0x1; + } + /* preset bit p as c */ + *p++ = (b << 4) | (c << 2) | (c << 3); + } + } +} + +/* + * FIXME: Disable NEON Optimization in hdmi, or it will cause crash of + * pulseaudio in the userspace. There is no issue for the Optimization + * implemenation, if only use "VPUSH, VPOP" in the function, the pulseaudio + * will crash also. So for my assumption, we can't use the NEON in the + * interrupt.(tasklet is implemented by softirq.) + * Disable SMP, preempt, change the dma buffer to nocached, add protection of + * Dn register and fpscr, all these operation have no effect to the result. + */ +#define HDMI_DMA_NO_NEON + +#ifdef HDMI_DMA_NO_NEON +/* Optimization for IEC head */ +static void hdmi_dma_copy_16_c_lut(u16 *src, u32 *dst, int samples, + u8 *lookup_table) +{ + u32 sample, head, p; + int i; + + for (i = 0; i < samples; i++) { + /* get source sample */ + sample = *src++; + + /* xor every bit */ + p = sample ^ (sample >> 8); + p ^= (p >> 4); + p ^= (p >> 2); + p ^= (p >> 1); + p &= 1; /* only want last bit */ + p <<= 3; /* bit p */ + + /* get packet header */ + head = *lookup_table++; + + /* fix head */ + head ^= p; + + /* store */ + *dst++ = (head << 24) | (sample << 8); + } +} + +static void hdmi_dma_copy_16_c_fast(u16 *src, u32 *dst, int samples) +{ + u32 sample, p; + int i; + + for (i = 0; i < samples; i++) { + /* get source sample */ + sample = *src++; + + /* xor every bit */ + p = sample ^ (sample >> 8); + p ^= (p >> 4); + p ^= (p >> 2); + p ^= (p >> 1); + p &= 1; /* only want last bit */ + p <<= 3; /* bit p */ + + /* store */ + *dst++ = (p << 24) | (sample << 8); + } +} + +static void hdmi_dma_copy_16(u16 *src, u32 *dst, int framecnt, int channelcnt) +{ + /* split input frames into 192-frame each */ + int count_in_192 = (framecnt + 191) / 192; + int i; + + for (i = 0; i < count_in_192; i++) { + int count, samples; + + /* handles frame index [0, 48) */ + count = (framecnt < 48) ? framecnt : 48; + samples = count * channelcnt; + hdmi_dma_copy_16_c_lut(src, dst, samples, g_packet_head_table); + framecnt -= count; + if (framecnt == 0) + break; + + src += samples; + dst += samples; + + /* handles frame index [48, 192) */ + count = (framecnt < 192 - 48) ? framecnt : 192 - 48; + samples = count * channelcnt; + hdmi_dma_copy_16_c_fast(src, dst, samples); + framecnt -= count; + src += samples; + dst += samples; + } +} +#else +/* NEON optimization for IEC head*/ + +/** + * Convert pcm samples to iec samples suitable for HDMI transfer. + * PCM sample is 16 bits length. + * Frame index always starts from 0. + * Channel count can be 1, 2, 4, 6, or 8 + * Sample count (frame_count * channel_count) is multipliable by 8. + */ +static void hdmi_dma_copy_16(u16 *src, u32 *dst, int framecount, int channelcount) +{ + /* split input frames into 192-frame each */ + int i, count_in_192 = (framecount + 191) / 192; + + for (i = 0; i < count_in_192; i++) { + int count, samples; + + /* handles frame index [0, 48) */ + count = (framecount < 48) ? framecount : 48; + samples = count * channelcount; + hdmi_dma_copy_16_neon_lut(src, dst, samples, g_packet_head_table); + framecount -= count; + if (framecount == 0) + break; + + src += samples; + dst += samples; + + /* handles frame index [48, 192) */ + count = (framecount < 192 - 48) ? framecount : (192 - 48); + samples = count * channelcount; + hdmi_dma_copy_16_neon_fast(src, dst, samples); + framecount -= count; + src += samples; + dst += samples; + } +} +#endif + +static void hdmi_dma_mmap_copy(struct snd_pcm_substream *substream, + int offset, int count) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct hdmi_dma_priv *priv = runtime->private_data; + struct device *dev = rtd->platform->dev; + u32 framecount, *dst; + u16 *src16; + + framecount = count / (priv->sample_align * priv->channels); + + /* hw_buffer is the destination for pcm data plus frame info. */ + dst = (u32 *)(priv->hw_buffer.area + (offset * priv->buffer_ratio)); + + switch (priv->format) { + case SNDRV_PCM_FORMAT_S16_LE: + /* dma_buffer is the mmapped buffer we are copying pcm from. */ + src16 = (u16 *)(runtime->dma_area + offset); + hdmi_dma_copy_16(src16, dst, framecount, priv->channels); + break; + default: + dev_err(dev, "unsupported sample format %s\n", + snd_pcm_format_name(priv->format)); + return; + } +} + +static void hdmi_dma_data_copy(struct snd_pcm_substream *substream, + struct hdmi_dma_priv *priv, char type) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned long offset, count, appl_bytes, space_to_end; + + if (runtime->access != SNDRV_PCM_ACCESS_MMAP_INTERLEAVED) + return; + + appl_bytes = (runtime->status->hw_ptr % (priv->buffer_bytes/(runtime->frame_bits/8))) * (runtime->frame_bits/8); + if (type == 'p') + appl_bytes += 2 * priv->period_bytes; + offset = appl_bytes % priv->buffer_bytes; + + switch (type) { + case 'p': + count = priv->period_bytes; + space_to_end = priv->period_bytes; + break; + case 'b': + count = priv->buffer_bytes; + space_to_end = priv->buffer_bytes - offset; + + break; + default: + return; + } + + if (count <= space_to_end) { + hdmi_dma_mmap_copy(substream, offset, count); + } else { + hdmi_dma_mmap_copy(substream, offset, space_to_end); + hdmi_dma_mmap_copy(substream, 0, count - space_to_end); + } +} + +static void hdmi_sdma_callback(void *data) +{ + struct hdmi_dma_priv *priv = (struct hdmi_dma_priv *)data; + struct snd_pcm_substream *substream = priv->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned long flags; + + spin_lock_irqsave(&priv->irq_lock, flags); + + if (runtime && runtime->dma_area && priv->tx_active) { + priv->offset += priv->period_bytes; + priv->offset %= priv->period_bytes * priv->periods; + + /* Copy data by period_bytes */ + hdmi_dma_data_copy(substream, priv, 'p'); + + snd_pcm_period_elapsed(substream); + } + + spin_unlock_irqrestore(&priv->irq_lock, flags); + + return; +} + +static int hdmi_dma_set_thrsld_incrtype(struct device *dev, int channels) +{ + u8 mask = HDMI_AHB_DMA_CONF0_BURST_MODE | HDMI_AHB_DMA_CONF0_INCR_TYPE_MASK; + u8 val = hdmi_readb(HDMI_AHB_DMA_CONF0) & ~mask; + int incr_type, threshold; + + switch (hdmi_readb(HDMI_REVISION_ID)) { + case 0x0a: + incr_type = HDMI_DMA_BURST_INCR4; + if (channels == 2) + threshold = 126; + else + threshold = 124; + break; + case 0x1a: + incr_type = HDMI_DMA_BURST_INCR8; + threshold = 128; + break; + default: + dev_err(dev, "unknown hdmi controller!\n"); + return -ENODEV; + } + + hdmi_writeb(threshold, HDMI_AHB_DMA_THRSLD); + + switch (incr_type) { + case HDMI_DMA_BURST_UNSPECIFIED_LEGNTH: + break; + case HDMI_DMA_BURST_INCR4: + val |= HDMI_AHB_DMA_CONF0_BURST_MODE; + break; + case HDMI_DMA_BURST_INCR8: + val |= HDMI_AHB_DMA_CONF0_BURST_MODE | + HDMI_AHB_DMA_CONF0_INCR8; + break; + case HDMI_DMA_BURST_INCR16: + val |= HDMI_AHB_DMA_CONF0_BURST_MODE | + HDMI_AHB_DMA_CONF0_INCR16; + break; + default: + dev_err(dev, "invalid increment type: %d!", incr_type); + return -EINVAL; + } + + hdmi_writeb(val, HDMI_AHB_DMA_CONF0); + + hdmi_audio_debug(dev, HDMI_AHB_DMA_THRSLD); + + return 0; +} + +static int hdmi_dma_configure_dma(struct device *dev, int channels) +{ + u8 i, val = 0; + int ret; + + if (channels <= 0 || channels > 8 || channels % 2 != 0) { + dev_err(dev, "unsupported channel number: %d\n", channels); + return -EINVAL; + } + + hdmi_audio_writeb(AHB_DMA_CONF0, EN_HLOCK, 0x1); + + ret = hdmi_dma_set_thrsld_incrtype(dev, channels); + if (ret) + return ret; + + for (i = 0; i < channels; i += 2) + val |= 0x3 << i; + + hdmi_writeb(val, HDMI_AHB_DMA_CONF1); + + return 0; +} + +static void hdmi_dma_init_iec_header(void) +{ + iec_header.U = 0; + + iec_header.B.consumer = 0; /* Consumer use */ + iec_header.B.linear_pcm = 0; /* linear pcm audio */ + iec_header.B.copyright = 1; /* no copyright */ + iec_header.B.pre_emphasis = 0; /* 2 channels without pre-emphasis */ + iec_header.B.mode = 0; /* Mode 0 */ + + iec_header.B.category_code = 0; + + iec_header.B.source = 2; /* stereo */ + iec_header.B.channel = 0; + + iec_header.B.sample_freq = 0x02; /* 48 KHz */ + iec_header.B.clock_acc = 0; /* Level II */ + + iec_header.B.word_length = 0x02; /* 16 bits */ + iec_header.B.org_sample_freq = 0x0D; /* 48 KHz */ + + iec_header.B.cgms_a = 0; /* Copying is permitted without restriction */ +} + +static int hdmi_dma_update_iec_header(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct hdmi_dma_priv *priv = runtime->private_data; + struct device *dev = rtd->platform->dev; + + iec_header.B.source = priv->channels; + + switch (priv->rate) { + case 32000: + iec_header.B.sample_freq = 0x03; + iec_header.B.org_sample_freq = 0x0C; + break; + case 44100: + iec_header.B.sample_freq = 0x00; + iec_header.B.org_sample_freq = 0x0F; + break; + case 48000: + iec_header.B.sample_freq = 0x02; + iec_header.B.org_sample_freq = 0x0D; + break; + case 88200: + iec_header.B.sample_freq = 0x08; + iec_header.B.org_sample_freq = 0x07; + break; + case 96000: + iec_header.B.sample_freq = 0x0A; + iec_header.B.org_sample_freq = 0x05; + break; + case 176400: + iec_header.B.sample_freq = 0x0C; + iec_header.B.org_sample_freq = 0x03; + break; + case 192000: + iec_header.B.sample_freq = 0x0E; + iec_header.B.org_sample_freq = 0x01; + break; + default: + dev_err(dev, "unsupported sample rate\n"); + return -EFAULT; + } + + switch (priv->format) { + case SNDRV_PCM_FORMAT_S16_LE: + iec_header.B.word_length = 0x02; + break; + case SNDRV_PCM_FORMAT_S24_LE: + iec_header.B.word_length = 0x0b; + break; + default: + return -EFAULT; + } + + return 0; +} + +/* + * The HDMI block transmits the audio data without adding any of the audio + * frame bits. So we have to copy the raw dma data from the ALSA buffer + * to the DMA buffer, adding the frame information. + */ +static int hdmi_dma_copy(struct snd_pcm_substream *substream, int channel, + snd_pcm_uframes_t pos, void __user *buf, + snd_pcm_uframes_t frames) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct hdmi_dma_priv *priv = runtime->private_data; + unsigned int count = frames_to_bytes(runtime, frames); + unsigned int pos_bytes = frames_to_bytes(runtime, pos); + u32 *hw_buf; + int subframe_idx; + u32 pcm_data; + + /* Adding frame info to pcm data from userspace and copy to hw_buffer */ + hw_buf = (u32 *)(priv->hw_buffer.area + (pos_bytes * priv->buffer_ratio)); + + while (count > 0) { + for (subframe_idx = 1 ; subframe_idx <= priv->channels ; subframe_idx++) { + if (copy_from_user(&pcm_data, buf, priv->sample_align)) + return -EFAULT; + + buf += priv->sample_align; + count -= priv->sample_align; + + /* Save the header info to the audio dma buffer */ + *hw_buf++ = hdmi_dma_add_frame_info(priv, pcm_data, subframe_idx); + } + + priv->frame_idx++; + if (priv->frame_idx == 192) + priv->frame_idx = 0; + } + + return 0; +} + +static int hdmi_sdma_initbuf(struct device *dev, struct hdmi_dma_priv *priv) +{ + struct hdmi_sdma_script *hdmi_sdma_t = priv->hdmi_sdma_t; + u32 *head, *tail, i; + + if (!hdmi_sdma_t) { + dev_err(dev, "hdmi private addr invalid!!!\n"); + return -EINVAL; + } + + hdmi_sdma_t->control_reg_addr = HDMI_BASE_ADDR + HDMI_AHB_DMA_START; + hdmi_sdma_t->status_reg_addr = HDMI_BASE_ADDR + HDMI_IH_AHBDMAAUD_STAT0; + hdmi_sdma_t->dma_start_addr = HDMI_BASE_ADDR + HDMI_AHB_DMA_STRADDR0; + + head = &hdmi_sdma_t->buffer[0]; + tail = &hdmi_sdma_t->buffer[1]; + + for (i = 0; i < priv->sdma_params.buffer_num; i++) { + *head = priv->hw_buffer.addr + i * priv->period_bytes * priv->buffer_ratio; + *tail = *head + priv->dma_period_bytes - 1; + head += 2; + tail += 2; + } + + return 0; +} + +static int hdmi_sdma_config(struct snd_pcm_substream *substream, + struct hdmi_dma_priv *priv) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct device *dai_dev = &priv->pdev->dev; + struct device *dev = rtd->platform->dev; + struct dma_slave_config slave_config; + int ret; + + priv->dma_channel = dma_request_slave_channel(dai_dev, "tx"); + if (priv->dma_channel == NULL) { + dev_err(dev, "failed to alloc dma channel\n"); + return -EBUSY; + } + + slave_config.direction = DMA_TRANS_NONE; + slave_config.src_addr = (dma_addr_t)priv->sdma_params.buffer_num; + slave_config.dst_addr = (dma_addr_t)priv->sdma_params.phyaddr; + + ret = dmaengine_slave_config(priv->dma_channel, &slave_config); + if (ret) { + dev_err(dev, "failed to config slave dma\n"); + return -EINVAL; + } + + return 0; +} + +static int hdmi_dma_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct hdmi_dma_priv *priv = runtime->private_data; + + if (priv->dma_channel) { + dma_release_channel(priv->dma_channel); + priv->dma_channel = NULL; + } + + return 0; +} + +static int hdmi_dma_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct hdmi_dma_priv *priv = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct device *dev = rtd->platform->dev; + int ret; + + priv->buffer_bytes = params_buffer_bytes(params); + priv->periods = params_periods(params); + priv->period_bytes = params_period_bytes(params); + priv->channels = params_channels(params); + priv->format = params_format(params); + priv->rate = params_rate(params); + + priv->offset = 0; + priv->period_time = HZ / (priv->rate / params_period_size(params)); + + switch (priv->format) { + case SNDRV_PCM_FORMAT_S16_LE: + priv->buffer_ratio = 2; + priv->sample_align = 2; + priv->sample_bits = 16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + /* 24 bit audio in 32 bit word */ + priv->buffer_ratio = 1; + priv->sample_align = 4; + priv->sample_bits = 24; + break; + default: + dev_err(dev, "unsupported sample format: %d\n", priv->format); + return -EINVAL; + } + + priv->dma_period_bytes = priv->period_bytes * priv->buffer_ratio; + priv->sdma_params.buffer_num = priv->periods; + priv->sdma_params.phyaddr = priv->phy_hdmi_sdma_t; + + ret = hdmi_sdma_initbuf(dev, priv); + if (ret) + return ret; + + ret = hdmi_sdma_config(substream, priv); + if (ret) + return ret; + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + ret = hdmi_dma_configure_dma(dev, priv->channels); + if (ret) + return ret; + + hdmi_dma_set_addr(priv->hw_buffer.addr, priv->dma_period_bytes); + + dumppriv(dev, priv); + + hdmi_dma_update_iec_header(substream); + + /* Init par for mmap optimizate */ + init_table(priv->channels); + + priv->appl_bytes = 0; + + return 0; +} + +static void hdmi_dma_trigger_init(struct snd_pcm_substream *substream, + struct hdmi_dma_priv *priv) +{ + unsigned long status; + + priv->offset = 0; + priv->frame_idx = 0; + + /* Copy data by buffer_bytes */ + hdmi_dma_data_copy(substream, priv, 'b'); + + hdmi_audio_writeb(AHB_DMA_CONF0, SW_FIFO_RST, 0x1); + + /* Delay after reset */ + udelay(1); + + status = hdmi_readb(HDMI_IH_AHBDMAAUD_STAT0); + hdmi_writeb(status, HDMI_IH_AHBDMAAUD_STAT0); +} + +static int hdmi_dma_prepare_and_submit(struct snd_pcm_substream *substream, + struct hdmi_dma_priv *priv) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct device *dev = rtd->platform->dev; + + priv->desc = dmaengine_prep_dma_cyclic(priv->dma_channel, 0, 0, 0, + DMA_TRANS_NONE, 0); + if (!priv->desc) { + dev_err(dev, "failed to prepare slave dma\n"); + return -EINVAL; + } + + priv->desc->callback = hdmi_sdma_callback; + priv->desc->callback_param = (void *)priv; + dmaengine_submit(priv->desc); + + return 0; +} + +static int hdmi_dma_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct hdmi_dma_priv *priv = runtime->private_data; + struct device *dev = rtd->platform->dev; + int ret; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (!check_hdmi_state()) + return 0; + hdmi_dma_trigger_init(substream, priv); + + dumpregs(dev); + + priv->tx_active = true; + hdmi_audio_writeb(AHB_DMA_START, START, 0x1); + hdmi_dma_irq_set(false); + hdmi_set_dma_mode(1); + ret = hdmi_dma_prepare_and_submit(substream, priv); + if (ret) + return ret; + dma_async_issue_pending(priv->desc->chan); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + dmaengine_terminate_all(priv->dma_channel); + hdmi_set_dma_mode(0); + hdmi_dma_irq_set(true); + hdmi_audio_writeb(AHB_DMA_STOP, STOP, 0x1); + priv->tx_active = false; + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t hdmi_dma_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct hdmi_dma_priv *priv = runtime->private_data; + + return bytes_to_frames(runtime, priv->offset); +} + +static struct snd_pcm_hardware snd_imx_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .formats = MXC_HDMI_FORMATS_PLAYBACK, + .rate_min = 32000, + .channels_min = 2, + .channels_max = 8, + .buffer_bytes_max = HDMI_PCM_BUF_SIZE, + .period_bytes_min = HDMI_DMA_PERIOD_BYTES / 2, + .period_bytes_max = HDMI_DMA_PERIOD_BYTES / 2, + .periods_min = 8, + .periods_max = 8, + .fifo_size = 0, +}; + +static void hdmi_dma_irq_enable(struct hdmi_dma_priv *priv) +{ + unsigned long flags; + + hdmi_writeb(0xff, HDMI_AHB_DMA_POL); + hdmi_writeb(0xff, HDMI_AHB_DMA_BUFFPOL); + + spin_lock_irqsave(&priv->irq_lock, flags); + + hdmi_writeb(0xff, HDMI_IH_AHBDMAAUD_STAT0); + hdmi_writeb(0xff, HDMI_IH_MUTE_AHBDMAAUD_STAT0); + hdmi_dma_irq_set(false); + hdmi_mask(0); + + spin_unlock_irqrestore(&priv->irq_lock, flags); +} + +static void hdmi_dma_irq_disable(struct hdmi_dma_priv *priv) +{ + unsigned long flags; + + spin_lock_irqsave(&priv->irq_lock, flags); + + hdmi_dma_irq_set(true); + hdmi_writeb(0x0, HDMI_IH_MUTE_AHBDMAAUD_STAT0); + hdmi_writeb(0xff, HDMI_IH_AHBDMAAUD_STAT0); + hdmi_mask(1); + + spin_unlock_irqrestore(&priv->irq_lock, flags); +} + +static int hdmi_dma_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct device *dev = rtd->platform->dev; + struct hdmi_dma_priv *priv = dev_get_drvdata(dev); + int ret; + + runtime->private_data = priv; + + ret = mxc_hdmi_register_audio(substream); + if (ret < 0) { + dev_err(dev, "HDMI Video is not ready!\n"); + return ret; + } + + hdmi_audio_writeb(AHB_DMA_CONF0, SW_FIFO_RST, 0x1); + + ret = snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; + + snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware); + + hdmi_dma_irq_enable(priv); + + return 0; +} + +static int hdmi_dma_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct hdmi_dma_priv *priv = runtime->private_data; + + hdmi_dma_irq_disable(priv); + mxc_hdmi_unregister_audio(substream); + + return 0; +} + +static struct snd_pcm_ops imx_hdmi_dma_pcm_ops = { + .open = hdmi_dma_open, + .close = hdmi_dma_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = hdmi_dma_hw_params, + .hw_free = hdmi_dma_hw_free, + .trigger = hdmi_dma_trigger, + .pointer = hdmi_dma_pointer, + .copy = hdmi_dma_copy, +}; + +static int imx_hdmi_dma_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct hdmi_dma_priv *priv = dev_get_drvdata(rtd->platform->dev); + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm_substream *substream; + struct snd_pcm *pcm = rtd->pcm; + u64 dma_mask = DMA_BIT_MASK(32); + int ret = 0; + + if (!card->dev->dma_mask) + card->dev->dma_mask = &dma_mask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + + substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev, + HDMI_PCM_BUF_SIZE, &substream->dma_buffer); + if (ret) { + dev_err(card->dev, "failed to alloc playback dma buffer\n"); + return ret; + } + + priv->substream = substream; + + /* Alloc the hw_buffer */ + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev, + HDMI_DMA_BUF_SIZE, &priv->hw_buffer); + if (ret) { + dev_err(card->dev, "failed to alloc hw dma buffer\n"); + return ret; + } + + return ret; +} + +static void imx_hdmi_dma_pcm_free(struct snd_pcm *pcm) +{ + int stream = SNDRV_PCM_STREAM_PLAYBACK; + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_soc_pcm_runtime *rtd = pcm->private_data; + struct hdmi_dma_priv *priv = dev_get_drvdata(rtd->platform->dev); + + if (substream) { + snd_dma_free_pages(&substream->dma_buffer); + substream->dma_buffer.area = NULL; + substream->dma_buffer.addr = 0; + } + + /* Free the hw_buffer */ + snd_dma_free_pages(&priv->hw_buffer); + priv->hw_buffer.area = NULL; + priv->hw_buffer.addr = 0; +} + +static struct snd_soc_platform_driver imx_hdmi_platform = { + .ops = &imx_hdmi_dma_pcm_ops, + .pcm_new = imx_hdmi_dma_pcm_new, + .pcm_free = imx_hdmi_dma_pcm_free, +}; + +static int imx_soc_platform_probe(struct platform_device *pdev) +{ + struct imx_hdmi *hdmi_drvdata = platform_get_drvdata(pdev); + struct hdmi_dma_priv *priv; + int ret = 0; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) { + dev_err(&pdev->dev, "Failed to alloc hdmi_dma\n"); + return -ENOMEM; + } + + priv->hdmi_sdma_t = dma_alloc_coherent(NULL, + sizeof(struct hdmi_sdma_script), + &priv->phy_hdmi_sdma_t, GFP_KERNEL); + if (!priv->hdmi_sdma_t) { + dev_err(&pdev->dev, "Failed to alloc hdmi_sdma_t\n"); + return -ENOMEM; + } + + priv->tx_active = false; + spin_lock_init(&priv->irq_lock); + + priv->pdev = hdmi_drvdata->pdev; + + hdmi_dma_init_iec_header(); + + dev_set_drvdata(&pdev->dev, priv); + + switch (hdmi_readb(HDMI_REVISION_ID)) { + case 0x0a: + snd_imx_hardware.period_bytes_max = HDMI_DMA_PERIOD_BYTES / 4; + snd_imx_hardware.period_bytes_min = HDMI_DMA_PERIOD_BYTES / 4; + break; + default: + break; + } + + ret = snd_soc_register_platform(&pdev->dev, &imx_hdmi_platform); + if (ret) + goto err_plat; + + return 0; + +err_plat: + dma_free_coherent(NULL, sizeof(struct hdmi_sdma_script), + priv->hdmi_sdma_t, priv->phy_hdmi_sdma_t); + + return ret; +} + +static int imx_soc_platform_remove(struct platform_device *pdev) +{ + struct hdmi_dma_priv *priv = dev_get_drvdata(&pdev->dev); + + dma_free_coherent(NULL, sizeof(struct hdmi_sdma_script), + priv->hdmi_sdma_t, priv->phy_hdmi_sdma_t); + + snd_soc_unregister_platform(&pdev->dev); + + return 0; +} + +static struct platform_driver imx_hdmi_dma_driver = { + .driver = { + .name = "imx-hdmi-audio", + .owner = THIS_MODULE, + }, + .probe = imx_soc_platform_probe, + .remove = imx_soc_platform_remove, +}; + +module_platform_driver(imx_hdmi_dma_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("i.MX HDMI audio DMA"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/fsl/imx-hdmi.c b/sound/soc/fsl/imx-hdmi.c new file mode 100644 index 000000000000..e99a1ed81956 --- /dev/null +++ b/sound/soc/fsl/imx-hdmi.c @@ -0,0 +1,114 @@ +/* + * ASoC HDMI Transmitter driver for IMX development boards + * + * Copyright (C) 2011-2016 Freescale Semiconductor, Inc. + * + * based on stmp3780_devb_hdmi.c + * + * Vladimir Barinov <vbarinov@embeddedalley.com> + * + * Copyright 2008 SigmaTel, Inc + * Copyright 2008 Embedded Alley Solutions, Inc + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/mfd/mxc-hdmi-core.h> +#include <sound/soc.h> + +#include "imx-hdmi.h" + +/* imx digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link imx_hdmi_dai_link = { + .name = "i.MX HDMI Audio Tx", + .stream_name = "i.MX HDMI Audio Tx", + .codec_dai_name = "hdmi-hifi.0", + .codec_name = "hdmi-audio-codec", + .platform_name = "imx-hdmi-audio", +}; + +static struct snd_soc_card snd_soc_card_imx_hdmi = { + .name = "imx-hdmi-soc", + .dai_link = &imx_hdmi_dai_link, + .num_links = 1, + .owner = THIS_MODULE, +}; + +static int imx_hdmi_audio_probe(struct platform_device *pdev) +{ + struct device_node *hdmi_np, *np = pdev->dev.of_node; + struct snd_soc_card *card = &snd_soc_card_imx_hdmi; + struct platform_device *hdmi_pdev; + int ret = 0; + + if (!hdmi_get_registered()) { + dev_err(&pdev->dev, "initialize HDMI-audio failed. load HDMI-video first!\n"); + return -ENODEV; + } + + hdmi_np = of_parse_phandle(np, "hdmi-controller", 0); + if (!hdmi_np) { + dev_err(&pdev->dev, "failed to find hdmi-audio cpudai\n"); + ret = -EINVAL; + goto end; + } + + hdmi_pdev = of_find_device_by_node(hdmi_np); + if (!hdmi_pdev) { + dev_err(&pdev->dev, "failed to find SSI platform device\n"); + ret = -EINVAL; + goto end; + } + + card->dev = &pdev->dev; + card->dai_link->cpu_dai_name = dev_name(&hdmi_pdev->dev); + + platform_set_drvdata(pdev, card); + + ret = snd_soc_register_card(card); + if (ret) + dev_err(&pdev->dev, "failed to register card: %d\n", ret); + +end: + if (hdmi_np) + of_node_put(hdmi_np); + + return ret; +} + +static int imx_hdmi_audio_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + snd_soc_unregister_card(card); + + return 0; +} + +static const struct of_device_id imx_hdmi_dt_ids[] = { + { .compatible = "fsl,imx-audio-hdmi", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_hdmi_dt_ids); + +static struct platform_driver imx_hdmi_audio_driver = { + .probe = imx_hdmi_audio_probe, + .remove = imx_hdmi_audio_remove, + .driver = { + .of_match_table = imx_hdmi_dt_ids, + .name = "imx-audio-hdmi", + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + }, +}; + +module_platform_driver(imx_hdmi_audio_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("IMX HDMI TX ASoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:imx-audio-hdmi"); diff --git a/sound/soc/fsl/imx-hdmi.h b/sound/soc/fsl/imx-hdmi.h new file mode 100644 index 000000000000..d06ce9c34d32 --- /dev/null +++ b/sound/soc/fsl/imx-hdmi.h @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2011-2014 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef __IMX_HDMI_H +#define __IMX_HDMI_H + +struct imx_hdmi_sdma_params { + dma_addr_t phyaddr; + u32 buffer_num; + int dma; +}; + +struct imx_hdmi { + struct snd_soc_dai_driver cpu_dai_drv; + struct platform_device *codec_dev; + struct platform_device *dma_dev; + struct platform_device *pdev; + struct clk *isfr_clk; + struct clk *iahb_clk; + struct clk *mipi_core_clk; +}; + +#define HDMI_MAX_RATES 7 +#define HDMI_MAX_SAMPLE_SIZE 3 +#define HDMI_MAX_CHANNEL_CONSTRAINTS 4 + +#define MXC_HDMI_RATES_PLAYBACK \ + (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | \ + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | \ + SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000) + +#define MXC_HDMI_FORMATS_PLAYBACK \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE) + +union hdmi_audio_header_t { + uint64_t U; + struct { + unsigned consumer:1; + unsigned linear_pcm:1; + unsigned copyright:1; + unsigned pre_emphasis:3; + unsigned mode:2; + + unsigned category_code:8; + + unsigned source:4; + unsigned channel:4; + + unsigned sample_freq:4; + unsigned clock_acc:2; + unsigned reserved0:2; + + unsigned word_length:4; + unsigned org_sample_freq:4; + + unsigned cgms_a:2; + unsigned reserved1:6; + + unsigned reserved2:8; + + unsigned reserved3:8; + } B; + unsigned char status[8]; +}; + +union hdmi_audio_dma_data_t { + uint32_t U; + struct { + unsigned data:24; + unsigned v:1; + unsigned u:1; + unsigned c:1; + unsigned p:1; + unsigned b:1; + unsigned reserved:3; + } B; +}; + +extern union hdmi_audio_header_t iec_header; + +#define hdmi_audio_writeb(reg, bit, val) \ + do { \ + hdmi_mask_writeb(val, HDMI_ ## reg, \ + HDMI_ ## reg ## _ ## bit ## _OFFSET, \ + HDMI_ ## reg ## _ ## bit ## _MASK); \ + pr_debug("Set reg: HDMI_" #reg " (0x%x) "\ + "bit: HDMI_" #reg "_" #bit " (%d) to val: %x\n", \ + HDMI_ ## reg, HDMI_ ## reg ## _ ## bit ## _OFFSET, val); \ + } while (0) + +#endif /* __IMX_HDMI_H */ diff --git a/sound/soc/fsl/imx-micfil.c b/sound/soc/fsl/imx-micfil.c new file mode 100644 index 000000000000..c4721d78d6df --- /dev/null +++ b/sound/soc/fsl/imx-micfil.c @@ -0,0 +1,172 @@ +/* + * Copyright 2018 NXP + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/i2c.h> +#include <linux/of_gpio.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/clk.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <sound/control.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <linux/pinctrl/consumer.h> +#include "fsl_micfil.h" + +#define RX 0 +#define TX 1 + +struct imx_micfil_data { + char name[32]; + struct snd_soc_dai_link dai; + struct snd_soc_card card; +}; + +static int imx_micfil_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + static struct snd_pcm_hw_constraint_list constraint_rates; + int ret; + static u32 support_rates[] = {11025, 16000, 22050, + 32000, 44100, 48000,}; + + constraint_rates.list = support_rates; + constraint_rates.count = ARRAY_SIZE(support_rates); + + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraint_rates); + if (ret) + return ret; + + return 0; +} + +static int imx_micfil_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct device *dev = rtd->card->dev; + unsigned int fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF; + int ret, dir; + + /* For playback the XTOR is slave, and for record is master */ + fmt |= tx ? SND_SOC_DAIFMT_CBS_CFS : SND_SOC_DAIFMT_CBM_CFM; + dir = tx ? SND_SOC_CLOCK_OUT : SND_SOC_CLOCK_IN; + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(rtd->cpu_dai, fmt); + if (ret) { + dev_err(dev, "failed to set cpu dai fmt: %d\n", ret); + return ret; + } + + /* Specific configurations of DAIs starts from here */ + ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, 0, + 0, dir); + if (ret) { + dev_err(dev, + "%s: failed to set cpu sysclk: %d\n", __func__, + ret); + return ret; + } + + return 0; +} + +struct snd_soc_ops imx_micfil_ops = { + .startup = imx_micfil_startup, + .hw_params = imx_micfil_hw_params, +}; + +static int imx_micfil_probe(struct platform_device *pdev) +{ + struct device_node *cpu_np; + struct platform_device *cpu_pdev; + struct imx_micfil_data *data; + int ret; + + cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto fail; + } + + strncpy(data->name, cpu_np->name, sizeof(data->name) - 1); + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find MICFIL platform device\n"); + ret = -EINVAL; + goto fail; + } + + data->dai.name = "micfil hifi"; + data->dai.stream_name = "micfil hifi"; + data->dai.codec_dai_name = "snd-soc-dummy-dai"; + data->dai.codec_name = "snd-soc-dummy"; + data->dai.cpu_dai_name = dev_name(&cpu_pdev->dev); + data->dai.platform_of_node = cpu_np; + data->dai.playback_only = false; + data->dai.ops = &imx_micfil_ops; + data->card.num_links = 1; + data->card.dai_link = &data->dai; + data->card.dev = &pdev->dev; + data->card.owner = THIS_MODULE; + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) + goto fail; + + platform_set_drvdata(pdev, &data->card); + snd_soc_card_set_drvdata(&data->card, data); + ret = devm_snd_soc_register_card(&pdev->dev, &data->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + +fail: + if (cpu_np) + of_node_put(cpu_np); + return ret; +} + +static const struct of_device_id imx_micfil_dt_ids[] = { + { .compatible = "fsl,imx-audio-micfil", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_micfil_dt_ids); + +static struct platform_driver imx_micfil_driver = { + .driver = { + .name = "imx-micfil", + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + .of_match_table = imx_micfil_dt_ids, + }, + .probe = imx_micfil_probe, +}; +module_platform_driver(imx_micfil_driver); + +MODULE_AUTHOR("Cosmin-Gabriel Samoila <cosmin.samoila@nxp.com>"); +MODULE_DESCRIPTION("NXP Micfil ASoC machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-micfil"); diff --git a/sound/soc/fsl/imx-mqs.c b/sound/soc/fsl/imx-mqs.c new file mode 100644 index 000000000000..df291a45d43d --- /dev/null +++ b/sound/soc/fsl/imx-mqs.c @@ -0,0 +1,277 @@ +/* + * Copyright 2012, 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2012 Linaro Ltd. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> + +#define SUPPORT_RATE_NUM 10 + +struct imx_priv { + struct clk *codec_clk; + unsigned int mclk_freq; + struct platform_device *pdev; + struct platform_device *asrc_pdev; + u32 asrc_rate; + u32 asrc_format; +}; + +static struct imx_priv card_priv; + +static int imx_mqs_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + static struct snd_pcm_hw_constraint_list constraint_rates; + struct imx_priv *priv = &card_priv; + struct device *dev = &priv->pdev->dev; + static u32 support_rates[SUPPORT_RATE_NUM]; + int ret; + + priv->mclk_freq = clk_get_rate(priv->codec_clk); + + if (priv->mclk_freq % 24576000 == 0) { + support_rates[0] = 48000; + constraint_rates.list = support_rates; + constraint_rates.count = 1; + + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraint_rates); + if (ret) + return ret; + } else + dev_warn(dev, "mclk may be not supported %d\n", priv->mclk_freq); + + return 0; +} + +static int imx_mqs_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_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_card *card = rtd->card; + int ret; + + ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, 2, params_width(params)); + if (ret) { + dev_err(card->dev, "failed to set cpu dai tdm slot: %d\n", ret); + return ret; + } + return 0; +} + +static struct snd_soc_ops imx_mqs_ops = { + .startup = imx_mqs_startup, + .hw_params = imx_mqs_hw_params, +}; + +static struct snd_soc_ops imx_mqs_ops_be = { + .hw_params = imx_mqs_hw_params, +}; + +static const struct snd_soc_dapm_route audio_map[] = { + {"CPU-Playback", NULL, "ASRC-Playback"}, + {"Playback", NULL, "CPU-Playback"},/* dai route for be and fe */ +}; + +static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) { + + struct imx_priv *priv = &card_priv; + struct snd_interval *rate; + struct snd_mask *mask; + + if (!priv->asrc_pdev) + return -EINVAL; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + rate->max = rate->min = priv->asrc_rate; + + mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + snd_mask_none(mask); + snd_mask_set(mask, priv->asrc_format); + + return 0; +} + +static struct snd_soc_dai_link imx_mqs_dai[] = { + { + .name = "HiFi", + .stream_name = "HiFi", + .dai_fmt = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &imx_mqs_ops, + }, + { + .name = "HiFi-ASRC-FE", + .stream_name = "HiFi-ASRC-FE", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .dynamic = 1, + .ignore_pmdown_time = 1, + .dpcm_playback = 1, + .dpcm_capture = 0, + .dpcm_merged_chan = 1, + }, + { + .name = "HiFi-ASRC-BE", + .stream_name = "HiFi-ASRC-BE", + .codec_dai_name = "fsl-mqs-dai", + .platform_name = "snd-soc-dummy", + .dai_fmt = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .no_pcm = 1, + .ignore_pmdown_time = 1, + .dpcm_playback = 1, + .dpcm_capture = 0, + .ops = &imx_mqs_ops_be, + .be_hw_params_fixup = be_hw_params_fixup, + }, +}; + +static struct snd_soc_card snd_soc_card_imx_mqs = { + .name = "mqs-audio", + .dai_link = imx_mqs_dai, + .owner = THIS_MODULE, + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), +}; + +static int imx_mqs_probe(struct platform_device *pdev) +{ + struct device_node *cpu_np, *codec_np; + struct imx_priv *priv = &card_priv; + struct platform_device *codec_dev; + struct platform_device *asrc_pdev = NULL; + struct platform_device *cpu_pdev; + struct device_node *asrc_np; + int ret; + u32 width; + + priv->pdev = pdev; + + cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0); + if (!cpu_np) { + ret = -EINVAL; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find cpu dai device\n"); + ret = -EINVAL; + goto fail; + } + + asrc_np = of_parse_phandle(pdev->dev.of_node, "asrc-controller", 0); + if (asrc_np) { + asrc_pdev = of_find_device_by_node(asrc_np); + priv->asrc_pdev = asrc_pdev; + } + + codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); + if (!codec_np) { + dev_err(&pdev->dev, "phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + codec_dev = of_find_device_by_node(codec_np); + if (!codec_dev) { + dev_err(&codec_dev->dev, "failed to find codec device\n"); + ret = -EINVAL; + goto fail; + } + + priv->codec_clk = devm_clk_get(&codec_dev->dev, "mclk"); + if (IS_ERR(priv->codec_clk)) { + ret = PTR_ERR(priv->codec_clk); + dev_err(&codec_dev->dev, "failed to get codec clk: %d\n", ret); + goto fail; + } + + imx_mqs_dai[0].codec_dai_name = "fsl-mqs-dai"; + + if (!asrc_pdev) { + imx_mqs_dai[0].codec_of_node = codec_np; + imx_mqs_dai[0].cpu_dai_name = dev_name(&cpu_pdev->dev); + imx_mqs_dai[0].platform_of_node = cpu_np; + snd_soc_card_imx_mqs.num_links = 1; + } else { + imx_mqs_dai[0].codec_of_node = codec_np; + imx_mqs_dai[0].cpu_dai_name = dev_name(&cpu_pdev->dev); + imx_mqs_dai[0].platform_of_node = cpu_np; + imx_mqs_dai[1].cpu_of_node = asrc_np; + imx_mqs_dai[1].platform_of_node = asrc_np; + imx_mqs_dai[2].codec_of_node = codec_np; + imx_mqs_dai[2].cpu_dai_name = dev_name(&cpu_pdev->dev); + snd_soc_card_imx_mqs.num_links = 3; + + ret = of_property_read_u32(asrc_np, "fsl,asrc-rate", + &priv->asrc_rate); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + ret = -EINVAL; + goto fail; + } + + ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + ret = -EINVAL; + goto fail; + } + + if (width == 24) + priv->asrc_format = SNDRV_PCM_FORMAT_S24_LE; + else + priv->asrc_format = SNDRV_PCM_FORMAT_S16_LE; + } + + snd_soc_card_imx_mqs.dev = &pdev->dev; + + ret = devm_snd_soc_register_card(&pdev->dev, &snd_soc_card_imx_mqs); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + +fail: + if (cpu_np) + of_node_put(cpu_np); + + return ret; +} + +static const struct of_device_id imx_mqs_dt_ids[] = { + { .compatible = "fsl,imx-audio-mqs", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_mqs_dt_ids); + +static struct platform_driver imx_mqs_driver = { + .driver = { + .name = "imx-mqs", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_mqs_dt_ids, + }, + .probe = imx_mqs_probe, +}; +module_platform_driver(imx_mqs_driver); + +MODULE_AUTHOR("Nicolin Chen <Guangyu.Chen@freescale.com>"); +MODULE_DESCRIPTION("Freescale i.MX MQS ASoC machine driver"); +MODULE_ALIAS("platform:imx-mqs"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/imx-pcm-dma-v2.c b/sound/soc/fsl/imx-pcm-dma-v2.c new file mode 100644 index 000000000000..e90bcc26d063 --- /dev/null +++ b/sound/soc/fsl/imx-pcm-dma-v2.c @@ -0,0 +1,301 @@ +/* + * imx-pcm-dma-v2.c -- ALSA Soc Audio Layer + * + * Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de> + * + * This code is based on code copyrighted by Freescale, + * Liam Girdwood, Javier Martin and probably others. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ +#include <linux/dma-mapping.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/dmaengine_pcm.h> +#include <sound/soc.h> + +#include "imx-pcm.h" + +static struct snd_pcm_hardware imx_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID, + .buffer_bytes_max = IMX_SSI_DMABUF_SIZE, + .period_bytes_min = 128, + .period_bytes_max = 65532, /* Limited by SDMA engine */ + .periods_min = 2, + .periods_max = 255, + .fifo_size = 0, +}; + +static bool imx_dma_filter_fn(struct dma_chan *chan, void *param) +{ + if (!imx_dma_is_general_purpose(chan)) + return false; + + chan->private = param; + + return true; +} + +static void imx_pcm_dma_v2_complete(void *arg) +{ + struct snd_pcm_substream *substream = arg; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct dmaengine_pcm_runtime_data *prtd = + substream->runtime->private_data; + struct snd_dmaengine_dai_dma_data *dma_data; + + prtd->pos += snd_pcm_lib_period_bytes(substream); + if (prtd->pos >= snd_pcm_lib_buffer_bytes(substream)) + prtd->pos = 0; + + snd_pcm_period_elapsed(substream); + + dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + if (dma_data->check_xrun && dma_data->check_xrun(substream)) + dma_data->device_reset(substream, 1); +} + +/* this may get called several times by oss emulation */ +static int imx_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_dmaengine_dai_dma_data *dma_data; + struct dma_slave_config config; + struct dma_chan *chan; + struct dmaengine_pcm_runtime_data *prtd = + substream->runtime->private_data; + int err = 0; + + prtd->callback = imx_pcm_dma_v2_complete; + + dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + + /* return if this is a bufferless transfer e.g. + * codec <--> BT codec or GSM modem -- lg FIXME + */ + if (!dma_data) + return 0; + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + runtime->dma_bytes = params_buffer_bytes(params); + + chan = snd_dmaengine_pcm_get_chan(substream); + if (!chan) + return -EINVAL; + + /* fills in addr_width and direction */ + err = snd_hwparams_to_dma_slave_config(substream, params, &config); + if (err) + return err; + + snd_dmaengine_pcm_set_config_from_dai_data(substream, + dma_data, + &config); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + config.dst_fifo_num = dma_data->fifo_num; + else + config.src_fifo_num = dma_data->fifo_num; + + return dmaengine_slave_config(chan, &config); +} + +static int imx_pcm_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_set_runtime_buffer(substream, NULL); + return 0; +} + +static snd_pcm_uframes_t imx_pcm_pointer(struct snd_pcm_substream *substream) +{ + return snd_dmaengine_pcm_pointer(substream); +} + +static int imx_pcm_preallocate_dma_buffer(struct snd_pcm_substream *substream, + struct device *dev) +{ + size_t size = imx_pcm_hardware.buffer_bytes_max; + int ret; + + ret = snd_pcm_lib_preallocate_pages(substream, + SNDRV_DMA_TYPE_DEV_IRAM, + dev, + size, + size); + if (ret) + return ret; + + return 0; +} + +static int imx_pcm_free_dma_buffers(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_preallocate_free(substream); +} + +static int imx_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_dmaengine_dai_dma_data *dma_data; + struct dma_slave_caps dma_caps; + struct dma_chan *chan; + u32 addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) | + BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | + BIT(DMA_SLAVE_BUSWIDTH_4_BYTES); + int ret; + int i; + + dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + + /* DT boot: filter_data is the DMA name */ + if (rtd->cpu_dai->dev->of_node) { + struct dma_chan *chan; + + chan = dma_request_slave_channel(rtd->cpu_dai->dev, + dma_data->chan_name); + ret = snd_dmaengine_pcm_open(substream, chan); + if (ret) + return ret; + } else { + ret = snd_dmaengine_pcm_open_request_chan(substream, + imx_dma_filter_fn, + dma_data->filter_data); + if (ret) + return ret; + } + + chan = snd_dmaengine_pcm_get_chan(substream); + + ret = dma_get_slave_caps(chan, &dma_caps); + if (ret == 0) { + if (dma_caps.cmd_pause) + imx_pcm_hardware.info |= SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME; + if (dma_caps.residue_granularity <= DMA_RESIDUE_GRANULARITY_SEGMENT) + imx_pcm_hardware.info |= SNDRV_PCM_INFO_BATCH; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + addr_widths = dma_caps.dst_addr_widths; + else + addr_widths = dma_caps.src_addr_widths; + } + + /* + * If SND_DMAENGINE_PCM_DAI_FLAG_PACK is set keep + * hw.formats set to 0, meaning no restrictions are in place. + * In this case it's the responsibility of the DAI driver to + * provide the supported format information. + */ + if (!(dma_data->flags & SND_DMAENGINE_PCM_DAI_FLAG_PACK)) + /* + * Prepare formats mask for valid/allowed sample types. If the + * dma does not have support for the given physical word size, + * it needs to be masked out so user space can not use the + * format which produces corrupted audio. + * In case the dma driver does not implement the slave_caps the + * default assumption is that it supports 1, 2 and 4 bytes + * widths. + */ + for (i = 0; i <= SNDRV_PCM_FORMAT_LAST; i++) { + int bits = snd_pcm_format_physical_width(i); + + /* + * Enable only samples with DMA supported physical + * widths + */ + switch (bits) { + case 8: + case 16: + case 24: + case 32: + case 64: + if (addr_widths & (1 << (bits / 8))) + imx_pcm_hardware.formats |= (1LL << i); + break; + default: + /* Unsupported types */ + break; + } + } + + snd_soc_set_runtime_hwparams(substream, &imx_pcm_hardware); + + ret = imx_pcm_preallocate_dma_buffer(substream, chan->device->dev); + if (ret) + return ret; + + ret = snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; + + return 0; +} + +static int imx_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + return dma_mmap_writecombine(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); +} + +static int imx_pcm_close(struct snd_pcm_substream *substream) +{ + int ret; + + ret = imx_pcm_free_dma_buffers(substream); + if (ret) + return ret; + + return snd_dmaengine_pcm_close_release_chan(substream); +} + +static struct snd_pcm_ops imx_pcm_ops = { + .open = imx_pcm_open, + .close = imx_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = imx_pcm_hw_params, + .hw_free = imx_pcm_hw_free, + .trigger = snd_dmaengine_pcm_trigger, + .pointer = imx_pcm_pointer, + .mmap = imx_pcm_mmap, +}; + +static int imx_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + int ret = 0; + + ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + return ret; +} + +static struct snd_soc_platform_driver imx_soc_platform = { + .ops = &imx_pcm_ops, + .pcm_new = imx_pcm_new, +}; + +int imx_pcm_platform_register(struct device *dev) +{ + return devm_snd_soc_register_platform(dev, &imx_soc_platform); +} +EXPORT_SYMBOL_GPL(imx_pcm_platform_register); + +MODULE_LICENSE("GPL"); diff --git a/sound/soc/fsl/imx-pcm-dma.c b/sound/soc/fsl/imx-pcm-dma.c index f3d3d1ffa84e..89be19a8e55f 100644 --- a/sound/soc/fsl/imx-pcm-dma.c +++ b/sound/soc/fsl/imx-pcm-dma.c @@ -42,15 +42,55 @@ static const struct snd_pcm_hardware imx_pcm_hardware = { SNDRV_PCM_INFO_RESUME, .buffer_bytes_max = IMX_DEFAULT_DMABUF_SIZE, .period_bytes_min = 128, - .period_bytes_max = 65535, /* Limited by SDMA engine */ + .period_bytes_max = 65532, /* Limited by SDMA engine */ .periods_min = 2, .periods_max = 255, .fifo_size = 0, }; +static void imx_pcm_dma_complete(void *arg) +{ + struct snd_pcm_substream *substream = arg; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct dmaengine_pcm_runtime_data *prtd = substream->runtime->private_data; + struct snd_dmaengine_dai_dma_data *dma_data; + + prtd->pos += snd_pcm_lib_period_bytes(substream); + if (prtd->pos >= snd_pcm_lib_buffer_bytes(substream)) + prtd->pos = 0; + + snd_pcm_period_elapsed(substream); + + dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + if (dma_data->check_xrun && dma_data->check_xrun(substream)) + dma_data->device_reset(substream, 1); +} + +static int imx_pcm_dma_prepare_slave_config(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct dma_slave_config *slave_config) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_dmaengine_dai_dma_data *dma_data; + struct dmaengine_pcm_runtime_data *prtd = substream->runtime->private_data; + int ret; + + dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + prtd->callback = imx_pcm_dma_complete; + + ret = snd_hwparams_to_dma_slave_config(substream, params, slave_config); + if (ret) + return ret; + + snd_dmaengine_pcm_set_config_from_dai_data(substream, dma_data, + slave_config); + + return 0; + +} + static const struct snd_dmaengine_pcm_config imx_dmaengine_pcm_config = { .pcm_hardware = &imx_pcm_hardware, - .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, + .prepare_slave_config = imx_pcm_dma_prepare_slave_config, .compat_filter_fn = filter, .prealloc_buffer_size = IMX_DEFAULT_DMABUF_SIZE, }; diff --git a/sound/soc/fsl/imx-pcm-rpmsg.c b/sound/soc/fsl/imx-pcm-rpmsg.c new file mode 100644 index 000000000000..d84723498641 --- /dev/null +++ b/sound/soc/fsl/imx-pcm-rpmsg.c @@ -0,0 +1,790 @@ +/* + * imx-rpmsg-platform.c -- ALSA Soc Audio Layer + * + * Copyright 2017 NXP + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ +#include <linux/dma-mapping.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/rpmsg.h> +#include <linux/imx_rpmsg.h> +#include <linux/delay.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/dmaengine_pcm.h> +#include <sound/soc.h> + +#include "imx-pcm.h" +#include "fsl_rpmsg_i2s.h" + +struct i2s_info *i2s_info_g; + +static struct snd_pcm_hardware imx_rpmsg_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .buffer_bytes_max = IMX_SAI_DMABUF_SIZE, + .period_bytes_min = 512, + .period_bytes_max = 65532, /* Limited by SDMA engine */ + .periods_min = 2, + .periods_max = 6000, + .fifo_size = 0, +}; + +static int imx_rpmsg_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev); + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + struct i2s_rpmsg *rpmsg; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg = &i2s_info->rpmsg[I2S_TX_HW_PARAM]; + else + rpmsg = &i2s_info->rpmsg[I2S_RX_HW_PARAM]; + + rpmsg->send_msg.param.rate = params_rate(params); + + if (params_format(params) == SNDRV_PCM_FORMAT_S16_LE) + rpmsg->send_msg.param.format = RPMSG_S16_LE; + else if (params_format(params) == SNDRV_PCM_FORMAT_S24_LE) + rpmsg->send_msg.param.format = RPMSG_S24_LE; + else + rpmsg->send_msg.param.format = RPMSG_S32_LE; + + if (params_channels(params) == 1) + rpmsg->send_msg.param.channels = RPMSG_CH_LEFT; + else + rpmsg->send_msg.param.channels = RPMSG_CH_STEREO; + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + runtime->dma_bytes = params_buffer_bytes(params); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg->send_msg.header.cmd = I2S_TX_HW_PARAM; + else + rpmsg->send_msg.header.cmd = I2S_RX_HW_PARAM; + + i2s_info->send_message(rpmsg, i2s_info); + + return 0; +} + +static int imx_rpmsg_pcm_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_set_runtime_buffer(substream, NULL); + return 0; +} + +static snd_pcm_uframes_t imx_rpmsg_pcm_pointer( + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev); + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + unsigned int pos = 0; + struct i2s_rpmsg *rpmsg; + int buffer_tail = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg = &i2s_info->rpmsg[I2S_TX_POINTER]; + else + rpmsg = &i2s_info->rpmsg[I2S_RX_POINTER]; + + buffer_tail = rpmsg->recv_msg.param.buffer_offset / + snd_pcm_lib_period_bytes(substream); + pos = buffer_tail * snd_pcm_lib_period_bytes(substream); + + return bytes_to_frames(substream->runtime, pos); +} + +static void imx_rpmsg_timer_callback(unsigned long data) +{ + struct snd_pcm_substream *substream = (struct snd_pcm_substream *)data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev); + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + struct i2s_rpmsg *rpmsg; + u8 index = i2s_info->work_index; + int time_msec; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg = &i2s_info->rpmsg[I2S_TX_POINTER]; + else + rpmsg = &i2s_info->rpmsg[I2S_RX_POINTER]; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg->send_msg.header.cmd = I2S_TX_POINTER; + else + rpmsg->send_msg.header.cmd = I2S_RX_POINTER; + + memcpy(&i2s_info->work_list[index].msg, rpmsg, + sizeof(struct i2s_rpmsg_s)); + queue_work(i2s_info->rpmsg_wq, &i2s_info->work_list[index].work); + i2s_info->work_index++; + i2s_info->work_index %= WORK_MAX_NUM; + + if (rpmsg_i2s->force_lpa) { + time_msec = min(500, + (int)(runtime->period_size*1000/runtime->rate)); + mod_timer(&i2s_info->stream_timer[substream->stream], + jiffies + msecs_to_jiffies(time_msec)); + } +} + +static int imx_rpmsg_pcm_open(struct snd_pcm_substream *substream) +{ + int ret = 0; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev); + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + struct i2s_rpmsg *rpmsg; + struct dmaengine_pcm_runtime_data *prtd; + int cmd; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg = &i2s_info->rpmsg[I2S_TX_OPEN]; + else + rpmsg = &i2s_info->rpmsg[I2S_RX_OPEN]; + + imx_rpmsg_pcm_hardware.buffer_bytes_max = + i2s_info->prealloc_buffer_size; + imx_rpmsg_pcm_hardware.period_bytes_max = + imx_rpmsg_pcm_hardware.buffer_bytes_max / 2; + + snd_soc_set_runtime_hwparams(substream, &imx_rpmsg_pcm_hardware); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg->send_msg.header.cmd = I2S_TX_OPEN; + else + rpmsg->send_msg.header.cmd = I2S_RX_OPEN; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + cmd = I2S_TX_PERIOD_DONE + I2S_TYPE_A_NUM; + i2s_info->rpmsg[cmd].send_msg.param.buffer_tail = 0; + i2s_info->rpmsg[cmd].recv_msg.param.buffer_tail = 0; + i2s_info->rpmsg[I2S_TX_POINTER].recv_msg.param.buffer_offset = 0; + } else { + cmd = I2S_RX_PERIOD_DONE + I2S_TYPE_A_NUM; + i2s_info->rpmsg[cmd].send_msg.param.buffer_tail = 0; + i2s_info->rpmsg[cmd].recv_msg.param.buffer_tail = 0; + i2s_info->rpmsg[I2S_RX_POINTER].recv_msg.param.buffer_offset = 0; + } + + i2s_info->send_message(rpmsg, i2s_info); + + prtd = kzalloc(sizeof(*prtd), GFP_KERNEL); + if (!prtd) + return -ENOMEM; + + substream->runtime->private_data = prtd; + + ret = snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; + + + /*create thread*/ + setup_timer(&i2s_info->stream_timer[substream->stream], + imx_rpmsg_timer_callback, (unsigned long)substream); + + return ret; +} + +static int imx_rpmsg_pcm_close(struct snd_pcm_substream *substream) +{ + int ret = 0; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev); + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + struct i2s_rpmsg *rpmsg; + struct dmaengine_pcm_runtime_data *prtd = + substream->runtime->private_data; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg = &i2s_info->rpmsg[I2S_TX_CLOSE]; + else + rpmsg = &i2s_info->rpmsg[I2S_RX_CLOSE]; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg->send_msg.header.cmd = I2S_TX_CLOSE; + else + rpmsg->send_msg.header.cmd = I2S_RX_CLOSE; + flush_workqueue(i2s_info->rpmsg_wq); + i2s_info->send_message(rpmsg, i2s_info); + + del_timer(&i2s_info->stream_timer[substream->stream]); + + kfree(prtd); + + return ret; +} + +static int imx_rpmsg_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev); + + /* NON-MMAP mode, NONBLOCK, Version 2, enable lpa in dts + * four condition to determine the lpa is enabled. + */ + if ((runtime->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED || + runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) && + (substream->f_flags & O_NONBLOCK) && + rpmsg_i2s->version == 2 && + rpmsg_i2s->enable_lpa) + rpmsg_i2s->force_lpa = 1; + else + rpmsg_i2s->force_lpa = 0; + + return 0; +} + +static int imx_rpmsg_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + return dma_mmap_writecombine(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); +} + +static void imx_rpmsg_pcm_dma_complete(void *arg) +{ + struct snd_pcm_substream *substream = arg; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev); + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + struct i2s_rpmsg *rpmsg, *rpmsg2; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + rpmsg = &i2s_info->rpmsg[I2S_TX_POINTER]; + rpmsg2 = &i2s_info->rpmsg[I2S_TX_PERIOD_DONE + I2S_TYPE_A_NUM]; + } else { + rpmsg = &i2s_info->rpmsg[I2S_RX_POINTER]; + rpmsg2 = &i2s_info->rpmsg[I2S_RX_PERIOD_DONE + I2S_TYPE_A_NUM]; + } + + rpmsg->recv_msg.param.buffer_offset = + rpmsg2->recv_msg.param.buffer_tail + * snd_pcm_lib_period_bytes(substream); + + snd_pcm_period_elapsed(substream); +} + +static int imx_rpmsg_pcm_prepare_and_submit(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev); + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + struct i2s_rpmsg *rpmsg; + u8 index = i2s_info->work_index; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg = &i2s_info->rpmsg[I2S_TX_BUFFER]; + else + rpmsg = &i2s_info->rpmsg[I2S_RX_BUFFER]; + + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg->send_msg.header.cmd = I2S_TX_BUFFER; + else + rpmsg->send_msg.header.cmd = I2S_RX_BUFFER; + + rpmsg->send_msg.param.buffer_addr = substream->runtime->dma_addr; + rpmsg->send_msg.param.buffer_size = snd_pcm_lib_buffer_bytes(substream); + rpmsg->send_msg.param.period_size = snd_pcm_lib_period_bytes(substream); + rpmsg->send_msg.param.buffer_tail = 0; + + i2s_info->num_period[substream->stream] = + rpmsg->send_msg.param.buffer_size / + rpmsg->send_msg.param.period_size; + + memcpy(&i2s_info->work_list[index].msg, rpmsg, + sizeof(struct i2s_rpmsg_s)); + queue_work(i2s_info->rpmsg_wq, &i2s_info->work_list[index].work); + i2s_info->work_index++; + i2s_info->work_index %= WORK_MAX_NUM; + + i2s_info->callback[substream->stream] = imx_rpmsg_pcm_dma_complete; + i2s_info->callback_param[substream->stream] = substream; + return 0; +} + +static void imx_rpmsg_async_issue_pending(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev); + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + struct i2s_rpmsg *rpmsg; + u8 index = i2s_info->work_index; + int time_msec; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg = &i2s_info->rpmsg[I2S_TX_START]; + else + rpmsg = &i2s_info->rpmsg[I2S_RX_START]; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg->send_msg.header.cmd = I2S_TX_START; + else + rpmsg->send_msg.header.cmd = I2S_RX_START; + + memcpy(&i2s_info->work_list[index].msg, rpmsg, + sizeof(struct i2s_rpmsg_s)); + queue_work(i2s_info->rpmsg_wq, &i2s_info->work_list[index].work); + i2s_info->work_index++; + i2s_info->work_index %= WORK_MAX_NUM; + + if (rpmsg_i2s->force_lpa) { + time_msec = min(500, + (int)(runtime->period_size*1000/runtime->rate)); + mod_timer(&i2s_info->stream_timer[substream->stream], + jiffies + msecs_to_jiffies(time_msec)); + } +} + +static int imx_rpmsg_restart(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev); + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + struct i2s_rpmsg *rpmsg; + u8 index = i2s_info->work_index; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg = &i2s_info->rpmsg[I2S_TX_RESTART]; + else + rpmsg = &i2s_info->rpmsg[I2S_RX_RESTART]; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg->send_msg.header.cmd = I2S_TX_RESTART; + else + rpmsg->send_msg.header.cmd = I2S_RX_RESTART; + + memcpy(&i2s_info->work_list[index].msg, rpmsg, + sizeof(struct i2s_rpmsg_s)); + queue_work(i2s_info->rpmsg_wq, &i2s_info->work_list[index].work); + i2s_info->work_index++; + i2s_info->work_index %= WORK_MAX_NUM; + + return 0; +} + +static int imx_rpmsg_pause(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev); + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + struct i2s_rpmsg *rpmsg; + u8 index = i2s_info->work_index; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg = &i2s_info->rpmsg[I2S_TX_PAUSE]; + else + rpmsg = &i2s_info->rpmsg[I2S_RX_PAUSE]; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg->send_msg.header.cmd = I2S_TX_PAUSE; + else + rpmsg->send_msg.header.cmd = I2S_RX_PAUSE; + + memcpy(&i2s_info->work_list[index].msg, rpmsg, + sizeof(struct i2s_rpmsg_s)); + queue_work(i2s_info->rpmsg_wq, &i2s_info->work_list[index].work); + i2s_info->work_index++; + i2s_info->work_index %= WORK_MAX_NUM; + return 0; +} + +static int imx_rpmsg_terminate_all(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev); + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + struct i2s_rpmsg *rpmsg; + u8 index = i2s_info->work_index; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg = &i2s_info->rpmsg[I2S_TX_TERMINATE]; + else + rpmsg = &i2s_info->rpmsg[I2S_RX_TERMINATE]; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg->send_msg.header.cmd = I2S_TX_TERMINATE; + else + rpmsg->send_msg.header.cmd = I2S_RX_TERMINATE; + + memcpy(&i2s_info->work_list[index].msg, rpmsg, + sizeof(struct i2s_rpmsg_s)); + queue_work(i2s_info->rpmsg_wq, &i2s_info->work_list[index].work); + i2s_info->work_index++; + i2s_info->work_index %= WORK_MAX_NUM; + + del_timer(&i2s_info->stream_timer[substream->stream]); + return 0; +} + +int imx_rpmsg_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev); + int ret; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + ret = imx_rpmsg_pcm_prepare_and_submit(substream); + if (ret) + return ret; + imx_rpmsg_async_issue_pending(substream); + break; + case SNDRV_PCM_TRIGGER_RESUME: + if (rpmsg_i2s->force_lpa) + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + imx_rpmsg_restart(substream); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + if (!rpmsg_i2s->force_lpa) { + if (runtime->info & SNDRV_PCM_INFO_PAUSE) + imx_rpmsg_pause(substream); + else + imx_rpmsg_terminate_all(substream); + } + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + imx_rpmsg_pause(substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + imx_rpmsg_terminate_all(substream); + break; + default: + return -EINVAL; + } + + return 0; +} + +int imx_rpmsg_pcm_ack(struct snd_pcm_substream *substream) +{ + /*send the hw_avail size through rpmsg*/ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev); + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + struct i2s_rpmsg *rpmsg; + u8 index = i2s_info->work_index; + int buffer_tail = 0; + + if (!rpmsg_i2s->force_lpa) + return 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg = &i2s_info->rpmsg[I2S_TX_PERIOD_DONE + I2S_TYPE_A_NUM]; + else + rpmsg = &i2s_info->rpmsg[I2S_RX_PERIOD_DONE + I2S_TYPE_A_NUM]; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg->send_msg.header.cmd = I2S_TX_PERIOD_DONE; + else + rpmsg->send_msg.header.cmd = I2S_RX_PERIOD_DONE; + + rpmsg->send_msg.header.type = I2S_TYPE_C; + + buffer_tail = (frames_to_bytes(runtime, runtime->control->appl_ptr) % + snd_pcm_lib_buffer_bytes(substream)); + buffer_tail = buffer_tail / snd_pcm_lib_period_bytes(substream); + + if (buffer_tail != rpmsg->send_msg.param.buffer_tail) { + rpmsg->send_msg.param.buffer_tail = buffer_tail; + memcpy(&i2s_info->work_list[index].msg, rpmsg, + sizeof(struct i2s_rpmsg_s)); + queue_work(i2s_info->rpmsg_wq, + &i2s_info->work_list[index].work); + i2s_info->work_index++; + i2s_info->work_index %= WORK_MAX_NUM; + } + + return 0; +} + +static struct snd_pcm_ops imx_rpmsg_pcm_ops = { + .open = imx_rpmsg_pcm_open, + .close = imx_rpmsg_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = imx_rpmsg_pcm_hw_params, + .hw_free = imx_rpmsg_pcm_hw_free, + .trigger = imx_rpmsg_pcm_trigger, + .pointer = imx_rpmsg_pcm_pointer, + .mmap = imx_rpmsg_pcm_mmap, + .ack = imx_rpmsg_pcm_ack, + .prepare = imx_rpmsg_pcm_prepare, +}; + +static int imx_rpmsg_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, + int stream, int size) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->area = dma_alloc_writecombine(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + if (!buf->area) + return -ENOMEM; + + buf->bytes = size; + return 0; +} + +static void imx_rpmsg_pcm_free_dma_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + for (stream = SNDRV_PCM_STREAM_PLAYBACK; + stream < SNDRV_PCM_STREAM_LAST; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + + buf = &substream->dma_buffer; + if (!buf->area) + continue; + + dma_free_writecombine(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; + } +} + +static int imx_rpmsg_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev); + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + int ret; + + ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { + ret = imx_rpmsg_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK, + i2s_info->prealloc_buffer_size); + if (ret) + goto out; + } + + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + ret = imx_rpmsg_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE, + i2s_info->prealloc_buffer_size); + if (ret) + goto out; + } + +out: + /* free preallocated buffers in case of error */ + if (ret) + imx_rpmsg_pcm_free_dma_buffers(pcm); + + return ret; +} + +static struct snd_soc_platform_driver imx_rpmsg_soc_platform = { + .ops = &imx_rpmsg_pcm_ops, + .pcm_new = imx_rpmsg_pcm_new, + .pcm_free = imx_rpmsg_pcm_free_dma_buffers, +}; + +int imx_rpmsg_platform_register(struct device *dev) +{ + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(dev); + + i2s_info_g = &rpmsg_i2s->i2s_info; + + return devm_snd_soc_register_platform(dev, &imx_rpmsg_soc_platform); +} +EXPORT_SYMBOL_GPL(imx_rpmsg_platform_register); + +static int i2s_rpmsg_cb(struct rpmsg_device *rpdev, void *data, int len, + void *priv, u32 src) +{ + struct i2s_rpmsg_r *msg = (struct i2s_rpmsg_r *)data; + struct i2s_rpmsg *rpmsg; + unsigned long flags; + + dev_dbg(&rpdev->dev, "get from%d: cmd:%d. %d\n", + src, msg->header.cmd, msg->param.resp); + + if (msg->header.type == I2S_TYPE_C) { + if (msg->header.cmd == I2S_TX_PERIOD_DONE) { + spin_lock_irqsave(&i2s_info_g->lock[0], flags); + rpmsg = &i2s_info_g->rpmsg[I2S_TX_PERIOD_DONE + I2S_TYPE_A_NUM]; + + if (msg->header.major == 1 && msg->header.minor == 2) + rpmsg->recv_msg.param.buffer_tail = + msg->param.buffer_tail; + else + rpmsg->recv_msg.param.buffer_tail++; + + rpmsg->recv_msg.param.buffer_tail %= + i2s_info_g->num_period[0]; + + spin_unlock_irqrestore(&i2s_info_g->lock[0], flags); + i2s_info_g->callback[0](i2s_info_g->callback_param[0]); + + } else if (msg->header.cmd == I2S_RX_PERIOD_DONE) { + spin_lock_irqsave(&i2s_info_g->lock[1], flags); + rpmsg = &i2s_info_g->rpmsg[I2S_RX_PERIOD_DONE + I2S_TYPE_A_NUM]; + + if (msg->header.major == 1 && msg->header.minor == 2) + rpmsg->recv_msg.param.buffer_tail = + msg->param.buffer_tail; + else + rpmsg->recv_msg.param.buffer_tail++; + + rpmsg->recv_msg.param.buffer_tail %= + i2s_info_g->num_period[1]; + spin_unlock_irqrestore(&i2s_info_g->lock[1], flags); + i2s_info_g->callback[1](i2s_info_g->callback_param[1]); + } + } + + if (msg->header.type == I2S_TYPE_B) { + memcpy(&i2s_info_g->recv_msg, msg, sizeof(struct i2s_rpmsg_r)); + complete(&i2s_info_g->cmd_complete); + } + + return 0; +} + +static int i2s_rpmsg_probe(struct rpmsg_device *rpdev) +{ + struct platform_device *codec_pdev; + struct fsl_rpmsg_i2s *rpmsg_i2s = NULL; + struct fsl_rpmsg_codec rpmsg_codec[2]; + int ret; + + if (!i2s_info_g) + return 0; + + i2s_info_g->rpdev = rpdev; + + init_completion(&i2s_info_g->cmd_complete); + + dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x!\n", + rpdev->src, rpdev->dst); + + rpmsg_i2s = container_of(i2s_info_g, struct fsl_rpmsg_i2s, i2s_info); + + if (rpmsg_i2s->codec_wm8960) { + rpmsg_codec[0].audioindex = rpmsg_i2s->codec_wm8960 >> 16; + rpmsg_codec[0].shared_lrclk = true; + rpmsg_codec[0].capless = false; + codec_pdev = platform_device_register_data( + &rpmsg_i2s->pdev->dev, + RPMSG_CODEC_DRV_NAME_WM8960, + PLATFORM_DEVID_NONE, + &rpmsg_codec[0], sizeof(struct fsl_rpmsg_codec)); + if (IS_ERR(codec_pdev)) { + dev_err(&rpdev->dev, + "failed to register rpmsg audio codec\n"); + ret = PTR_ERR(codec_pdev); + return ret; + } + } + + if (rpmsg_i2s->codec_cs42888) { + rpmsg_codec[1].audioindex = rpmsg_i2s->codec_cs42888 >> 16; + strcpy(rpmsg_codec[1].name, "cs42888"); + rpmsg_codec[1].num_adcs = 2; + + codec_pdev = platform_device_register_data( + &rpmsg_i2s->pdev->dev, + RPMSG_CODEC_DRV_NAME_CS42888, + PLATFORM_DEVID_NONE, + &rpmsg_codec[1], sizeof(struct fsl_rpmsg_codec)); + if (IS_ERR(codec_pdev)) { + dev_err(&rpdev->dev, + "failed to register rpmsg audio codec\n"); + ret = PTR_ERR(codec_pdev); + return ret; + } + } + return 0; +} + +static void i2s_rpmsg_remove(struct rpmsg_device *rpdev) +{ + dev_info(&rpdev->dev, "i2s rpmsg driver is removed\n"); +} + +static struct rpmsg_device_id i2s_rpmsg_id_table[] = { + { .name = "rpmsg-audio-channel" }, + { }, +}; + +static struct rpmsg_driver i2s_rpmsg_driver = { + .drv.name = "i2s_rpmsg", + .drv.owner = THIS_MODULE, + .id_table = i2s_rpmsg_id_table, + .probe = i2s_rpmsg_probe, + .callback = i2s_rpmsg_cb, + .remove = i2s_rpmsg_remove, +}; + +static int __init i2s_rpmsg_init(void) +{ + return register_rpmsg_driver(&i2s_rpmsg_driver); +} + +static void __exit i2s_rpmsg_exit(void) +{ + unregister_rpmsg_driver(&i2s_rpmsg_driver); +} +module_init(i2s_rpmsg_init); +module_exit(i2s_rpmsg_exit); + +MODULE_LICENSE("GPL"); diff --git a/sound/soc/fsl/imx-pcm.h b/sound/soc/fsl/imx-pcm.h index 133c4470acad..c9c03d2b4aa5 100644 --- a/sound/soc/fsl/imx-pcm.h +++ b/sound/soc/fsl/imx-pcm.h @@ -43,13 +43,27 @@ struct imx_pcm_fiq_params { struct snd_dmaengine_dai_dma_data *dma_params_tx; }; +#if IS_ENABLED(CONFIG_SND_SOC_IMX_PCM_RPMSG) +int imx_rpmsg_platform_register(struct device *dev); +#else +static inline int imx_rpmsg_platform_register(struct device *dev) +{ + return -ENODEV; +} +#endif + #if IS_ENABLED(CONFIG_SND_SOC_IMX_PCM_DMA) int imx_pcm_dma_init(struct platform_device *pdev, size_t size); +int imx_pcm_platform_register(struct device *dev); #else static inline int imx_pcm_dma_init(struct platform_device *pdev, size_t size) { return -ENODEV; } +static inline int imx_pcm_platform_register(struct device *dev) +{ + return -ENODEV; +} #endif #if IS_ENABLED(CONFIG_SND_SOC_IMX_PCM_FIQ) @@ -68,4 +82,39 @@ static inline void imx_pcm_fiq_exit(struct platform_device *pdev) } #endif +static inline void imx_pcm_stream_trigger(struct snd_pcm_substream *s, int tr) +{ + if (s->runtime->status->state == SNDRV_PCM_STATE_RUNNING && s->ops) + s->ops->trigger(s, tr); +} + +static inline void imx_stop_lock_pcm_streams(struct snd_pcm_substream **s, + int count, unsigned long *flags) +{ + int i; + + local_irq_save(*flags); + for (i = 0; i < count; i++) { + if (!s[i]) + continue; + snd_pcm_stream_lock(s[i]); + imx_pcm_stream_trigger(s[i], SNDRV_PCM_TRIGGER_STOP); + } +} + +static inline void imx_start_unlock_pcm_streams(struct snd_pcm_substream **s, + int count, unsigned long *flags) +{ + int i; + + for (i = count - 1; i >= 0; i--) { + if (!s[i]) + continue; + imx_pcm_stream_trigger(s[i], SNDRV_PCM_TRIGGER_START); + snd_pcm_stream_unlock(s[i]); + } + local_irq_restore(*flags); +} + + #endif /* _IMX_PCM_H */ diff --git a/sound/soc/fsl/imx-pdm.c b/sound/soc/fsl/imx-pdm.c new file mode 100644 index 000000000000..d69e27ec50c8 --- /dev/null +++ b/sound/soc/fsl/imx-pdm.c @@ -0,0 +1,195 @@ +/* + * Copyright 2017 NXP. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <sound/soc.h> + +#include "fsl_sai.h" + +struct imx_pdm_data { + struct snd_soc_dai_link dai; + struct snd_soc_card card; + unsigned int decimation; +}; + +static u32 imx_pdm_mic_rates[] = { 8000, 16000, 32000, 48000, 64000 }; +static struct snd_pcm_hw_constraint_list imx_pdm_mic_rate_constrains = { + .count = ARRAY_SIZE(imx_pdm_mic_rates), + .list = imx_pdm_mic_rates, +}; +static u32 imx_pdm_mic_channels[] = { 1 }; +static struct snd_pcm_hw_constraint_list imx_pdm_mic_channels_constrains = { + .count = ARRAY_SIZE(imx_pdm_mic_channels), + .list = imx_pdm_mic_channels, +}; + +static int imx_pdm_mic_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_card *card = rtd->card; + int ret; + + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &imx_pdm_mic_rate_constrains); + if (ret) { + dev_err(card->dev, + "fail to set pcm hw rate constrains: %d\n", ret); + return ret; + } + + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, &imx_pdm_mic_channels_constrains); + if (ret) { + dev_err(card->dev, + "fail to set pcm hw channels constrains: %d", ret); + return ret; + } + + return 0; +} + +static int imx_pdm_mic_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_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_card *card = rtd->card; + struct imx_pdm_data *data = snd_soc_card_get_drvdata(card); + int ret; + + /* set cpu dai format configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); + if (ret) { + dev_err(card->dev, "fail to set cpu dai fmt: %d\n", ret); + return ret; + } + /* set tdm slots only one for now */ + snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, 1, 32); + /* Set clock out */ + ret = snd_soc_dai_set_bclk_ratio(cpu_dai, data->decimation); + if (ret) { + dev_err(card->dev, "fail to set cpu sysclk: %d\n", ret); + return ret; + } + + return 0; +} + +static const struct snd_soc_ops imx_pdm_mic_ops = { + .startup = imx_pdm_mic_startup, + .hw_params = imx_pdm_mic_hw_params, +}; + +static int imx_pdm_mic_probe(struct platform_device *pdev) +{ + struct device_node *cpu_np = NULL; + struct device_node *np = pdev->dev.of_node; + struct platform_device *cpu_pdev; + struct imx_pdm_data *data; + int ret; + + cpu_np = of_parse_phandle(np, "audio-cpu", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "fail to find SAI platform device\n"); + ret = -EINVAL; + goto fail; + } + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto fail; + } + + ret = of_property_read_u32(np, "decimation", &data->decimation); + if (ret < 0) { + ret = -EINVAL; + goto fail; + } + + data->dai.name = "pdm hifi"; + data->dai.stream_name = "pdm hifi"; + data->dai.codec_dai_name = "snd-soc-dummy-dai"; + data->dai.codec_name = "snd-soc-dummy"; + data->dai.cpu_dai_name = dev_name(&cpu_pdev->dev); + data->dai.platform_of_node = cpu_np; + data->dai.capture_only = "true"; + data->dai.ops = &imx_pdm_mic_ops; + + data->card.dev = &pdev->dev; + data->card.owner = THIS_MODULE; + + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) { + dev_err(&pdev->dev, "fail to find card model name\n"); + goto fail; + } + + data->card.num_links = 1; + data->card.dai_link = &data->dai; + + platform_set_drvdata(pdev, &data->card); + snd_soc_card_set_drvdata(&data->card, data); + + ret = devm_snd_soc_register_card(&pdev->dev, &data->card); + if (ret) { + dev_err(&pdev->dev, "snd soc register card failed: %d\n", ret); + goto fail; + } + + ret = 0; +fail: + if (cpu_np) + of_node_put(cpu_np); + + return ret; +} + +static int imx_pdm_mic_remove(struct platform_device *pdev) +{ + struct imx_pdm_data *data = platform_get_drvdata(pdev); + /* unregister card */ + snd_soc_unregister_card(&data->card); + return 0; +} + +static const struct of_device_id imx_pdm_mic_dt_ids[] = { + { .compatible = "fsl,imx-pdm-mic", }, + { /* sentinel*/ } +}; +MODULE_DEVICE_TABLE(of, imx_pdm_mic_dt_ids); + +static struct platform_driver imx_pdm_mic_driver = { + .driver = { + .name = "imx-pdm-mic", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_pdm_mic_dt_ids, + }, + .probe = imx_pdm_mic_probe, + .remove = imx_pdm_mic_remove, +}; +module_platform_driver(imx_pdm_mic_driver); + +MODULE_DESCRIPTION("NXP i.MX PDM mic ASoC machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-pdm-mic"); diff --git a/sound/soc/fsl/imx-rpmsg.c b/sound/soc/fsl/imx-rpmsg.c new file mode 100644 index 000000000000..7648e88bbced --- /dev/null +++ b/sound/soc/fsl/imx-rpmsg.c @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2017 NXP + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/i2c.h> +#include <linux/of_gpio.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/clk.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <sound/control.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <linux/pinctrl/consumer.h> +#include "fsl_rpmsg_i2s.h" + +struct imx_rpmsg_data { + struct snd_soc_dai_link dai[1]; + struct snd_soc_card card; +}; + +static int imx_rpmsg_probe(struct platform_device *pdev) +{ + struct device_node *cpu_np; + struct platform_device *cpu_pdev; + struct imx_rpmsg_data *data; + struct fsl_rpmsg_i2s *rpmsg_i2s; + int ret; + + cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find rpmsg platform device\n"); + ret = -EINVAL; + goto fail; + } + + rpmsg_i2s = platform_get_drvdata(cpu_pdev); + + data->dai[0].name = "rpmsg hifi"; + data->dai[0].stream_name = "rpmsg hifi"; + if (rpmsg_i2s->codec_wm8960) { + data->dai[0].codec_dai_name = "rpmsg-wm8960-hifi"; + data->dai[0].codec_name = "rpmsg-audio-codec-wm8960"; + } else { + data->dai[0].codec_dai_name = "snd-soc-dummy-dai"; + data->dai[0].codec_name = "snd-soc-dummy"; + } + data->dai[0].cpu_dai_name = dev_name(&cpu_pdev->dev); + data->dai[0].platform_of_node = cpu_np; + data->dai[0].playback_only = true; + data->dai[0].capture_only = true; + data->dai[0].dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; + data->card.num_links = 1; + data->card.dai_link = data->dai; + + if (of_property_read_bool(pdev->dev.of_node, "rpmsg-out")) + data->dai[0].capture_only = false; + + if (of_property_read_bool(pdev->dev.of_node, "rpmsg-in")) + data->dai[0].playback_only = false; + + if (data->dai[0].playback_only && data->dai[0].capture_only) { + dev_err(&pdev->dev, "no enabled rpmsg DAI link\n"); + ret = -EINVAL; + goto fail; + } + + data->card.dev = &pdev->dev; + data->card.owner = THIS_MODULE; + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) + goto fail; + + platform_set_drvdata(pdev, &data->card); + snd_soc_card_set_drvdata(&data->card, data); + ret = devm_snd_soc_register_card(&pdev->dev, &data->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + +fail: + if (cpu_np) + of_node_put(cpu_np); + return ret; +} + +static const struct of_device_id imx_rpmsg_dt_ids[] = { + { .compatible = "fsl,imx-audio-rpmsg", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_rpmsg_dt_ids); + +static struct platform_driver imx_rpmsg_driver = { + .driver = { + .name = "imx-audio-rpmsg", + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + .of_match_table = imx_rpmsg_dt_ids, + }, + .probe = imx_rpmsg_probe, +}; +module_platform_driver(imx_rpmsg_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Freescale i.MX rpmsg audio ASoC machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-rpmsg"); diff --git a/sound/soc/fsl/imx-si476x.c b/sound/soc/fsl/imx-si476x.c new file mode 100644 index 000000000000..767667f135a0 --- /dev/null +++ b/sound/soc/fsl/imx-si476x.c @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2008-2016 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/i2c.h> +#include <sound/soc.h> + +#include "imx-audmux.h" + +static int imx_audmux_config(int slave, int master) +{ + unsigned int ptcr, pdcr; + slave = slave - 1; + master = master - 1; + + ptcr = IMX_AUDMUX_V2_PTCR_SYN | + IMX_AUDMUX_V2_PTCR_TFSDIR | + IMX_AUDMUX_V2_PTCR_TFSEL(slave) | + IMX_AUDMUX_V2_PTCR_TCLKDIR | + IMX_AUDMUX_V2_PTCR_TCSEL(slave); + pdcr = IMX_AUDMUX_V2_PDCR_RXDSEL(slave); + imx_audmux_v2_configure_port(master, ptcr, pdcr); + + /* + * According to RM, RCLKDIR and SYN should not be changed at same time. + * So separate to two step for configuring this port. + */ + ptcr |= IMX_AUDMUX_V2_PTCR_RFSDIR | + IMX_AUDMUX_V2_PTCR_RFSEL(slave) | + IMX_AUDMUX_V2_PTCR_RCLKDIR | + IMX_AUDMUX_V2_PTCR_RCSEL(slave); + imx_audmux_v2_configure_port(master, ptcr, pdcr); + + ptcr = IMX_AUDMUX_V2_PTCR_SYN; + pdcr = IMX_AUDMUX_V2_PDCR_RXDSEL(master); + imx_audmux_v2_configure_port(slave, ptcr, pdcr); + + return 0; +} + +static int imx_si476x_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_dai *cpu_dai = rtd->cpu_dai; + u32 channels = params_channels(params); + u32 rate = params_rate(params); + u32 bclk = rate * channels * 32; + int ret = 0; + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); + if (ret) { + dev_err(cpu_dai->dev, "failed to set dai fmt\n"); + return ret; + } + + ret = snd_soc_dai_set_tdm_slot(cpu_dai, + channels == 1 ? 1 : 0x3, + channels == 1 ? 1 : 0x3, + 2, 32); + if (ret) { + dev_err(cpu_dai->dev, "failed to set dai tdm slot\n"); + return ret; + } + + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, bclk, SND_SOC_CLOCK_OUT); + if (ret) + dev_err(cpu_dai->dev, "failed to set sysclk\n"); + + return ret; +} + +static struct snd_soc_ops imx_si476x_ops = { + .hw_params = imx_si476x_hw_params, +}; + +static struct snd_soc_dai_link imx_dai = { + .name = "imx-si476x", + .stream_name = "imx-si476x", + .codec_dai_name = "si476x-codec", + .ops = &imx_si476x_ops, +}; + +static struct snd_soc_card snd_soc_card_imx_3stack = { + .name = "imx-audio-si476x", + .dai_link = &imx_dai, + .num_links = 1, + .owner = THIS_MODULE, +}; + +static int imx_si476x_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &snd_soc_card_imx_3stack; + struct device_node *ssi_np, *np = pdev->dev.of_node; + struct platform_device *ssi_pdev; + struct i2c_client *fm_dev; + struct device_node *fm_np = NULL; + int int_port, ext_port, ret; + + ret = of_property_read_u32(np, "mux-int-port", &int_port); + if (ret) { + dev_err(&pdev->dev, "mux-int-port missing or invalid\n"); + return ret; + } + + ret = of_property_read_u32(np, "mux-ext-port", &ext_port); + if (ret) { + dev_err(&pdev->dev, "mux-ext-port missing or invalid\n"); + return ret; + } + + imx_audmux_config(int_port, ext_port); + + ssi_np = of_parse_phandle(pdev->dev.of_node, "ssi-controller", 0); + if (!ssi_np) { + dev_err(&pdev->dev, "phandle missing or invalid\n"); + return -EINVAL; + } + + ssi_pdev = of_find_device_by_node(ssi_np); + if (!ssi_pdev) { + dev_err(&pdev->dev, "failed to find SSI platform device\n"); + ret = -EINVAL; + goto end; + } + + fm_np = of_parse_phandle(pdev->dev.of_node, "fm-controller", 0); + if (!fm_np) { + dev_err(&pdev->dev, "phandle missing or invalid\n"); + ret = -EINVAL; + goto end; + } + + fm_dev = of_find_i2c_device_by_node(fm_np->parent); + if (!fm_dev || !fm_dev->dev.driver) { + dev_err(&pdev->dev, "failed to find FM platform device\n"); + ret = -EINVAL; + goto end; + } + + card->dev = &pdev->dev; + card->dai_link->cpu_dai_name = dev_name(&ssi_pdev->dev); + card->dai_link->platform_of_node = ssi_np; + card->dai_link->codec_of_node = fm_np; + + platform_set_drvdata(pdev, card); + + ret = snd_soc_register_card(card); + if (ret) + dev_err(&pdev->dev, "Failed to register card: %d\n", ret); + +end: + if (ssi_np) + of_node_put(ssi_np); + if (fm_np) + of_node_put(fm_np); + + return ret; +} + +static int imx_si476x_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = &snd_soc_card_imx_3stack; + + snd_soc_unregister_card(card); + + return 0; +} + +static const struct of_device_id imx_si476x_dt_ids[] = { + { .compatible = "fsl,imx-audio-si476x", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_si476x_dt_ids); + +static struct platform_driver imx_si476x_driver = { + .driver = { + .name = "imx-tuner-si476x", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_si476x_dt_ids, + }, + .probe = imx_si476x_probe, + .remove = imx_si476x_remove, +}; + +module_platform_driver(imx_si476x_driver); + +/* Module information */ +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("ALSA SoC i.MX si476x"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:imx-tuner-si476x"); diff --git a/sound/soc/fsl/imx-sii902x.c b/sound/soc/fsl/imx-sii902x.c new file mode 100644 index 000000000000..b9059920415f --- /dev/null +++ b/sound/soc/fsl/imx-sii902x.c @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2015-2016 Freescale Semiconductor, Inc. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/i2c.h> +#include <linux/of_gpio.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/clk.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <sound/control.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <linux/pinctrl/consumer.h> +#include "fsl_sai.h" + +#define SUPPORT_RATE_NUM 10 + +struct imx_sii902x_data { + struct snd_soc_dai_link dai; + struct snd_soc_card card; + struct i2c_client *sii902x; + bool is_stream_opened[2]; +}; + +static int imx_sii902x_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + static struct snd_pcm_hw_constraint_list constraint_rates; + static u32 support_rates[SUPPORT_RATE_NUM]; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_card *card = rtd->card; + struct imx_sii902x_data *data = snd_soc_card_get_drvdata(card); + struct fsl_sai *sai = dev_get_drvdata(cpu_dai->dev); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + int ret; + + data->is_stream_opened[tx] = true; + if (data->is_stream_opened[tx] != sai->is_stream_opened[tx] || + data->is_stream_opened[!tx] != sai->is_stream_opened[!tx]) { + data->is_stream_opened[tx] = false; + return -EBUSY; + } + + support_rates[0] = 32000; + support_rates[1] = 48000; + support_rates[2] = 96000; + support_rates[3] = 192000; + constraint_rates.list = support_rates; + constraint_rates.count = 4; + + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraint_rates); + if (ret) + return ret; + + ret = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_CHANNELS, + 1, 2); + if (ret) + return ret; + + return 0; +} + +static int imx_sii902x_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_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_card *card = rtd->card; + struct device *dev = card->dev; + struct imx_sii902x_data *data = snd_soc_card_get_drvdata(card); + int ret; + unsigned char reg; + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_LEFT_J | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS); + if (ret) { + dev_err(dev, "failed to set cpu dai fmt: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_OUT); + if (ret) { + dev_err(dev, "failed to set cpu sysclk: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, 2, 24); + if (ret) { + dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret); + return ret; + } + + /* sii90sx hdmi audio setup */ + i2c_smbus_write_byte_data(data->sii902x, 0x26, 0x90); + i2c_smbus_write_byte_data(data->sii902x, 0x20, 0x2d); + i2c_smbus_write_byte_data(data->sii902x, 0x1f, 0x88); + i2c_smbus_write_byte_data(data->sii902x, 0x1f, 0x91); + i2c_smbus_write_byte_data(data->sii902x, 0x1f, 0xa2); + i2c_smbus_write_byte_data(data->sii902x, 0x1f, 0xb3); + i2c_smbus_write_byte_data(data->sii902x, 0x27, 0); + switch (params_rate(params)) { + case 44100: + reg = 0; + break; + case 48000: + reg = 0x2; + break; + case 32000: + reg = 0x3; + break; + case 88200: + reg = 0x8; + break; + case 96000: + reg = 0xa; + break; + case 176400: + reg = 0xc; + break; + case 192000: + reg = 0xe; + break; + default: + reg = 0x1; + break; + } + i2c_smbus_write_byte_data(data->sii902x, 0x24, reg); + i2c_smbus_write_byte_data(data->sii902x, 0x25, 0x0b); + i2c_smbus_write_byte_data(data->sii902x, 0x26, 0x80); + + return 0; +} + +static int imx_sii902x_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct imx_sii902x_data *data = snd_soc_card_get_drvdata(card); + + i2c_smbus_write_byte_data(data->sii902x, 0x26, 0x10); + + return 0; +} + +static void imx_sii902x_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct imx_sii902x_data *data = snd_soc_card_get_drvdata(card); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + + data->is_stream_opened[tx] = false; +} + +static struct snd_soc_ops imx_sii902x_ops = { + .startup = imx_sii902x_startup, + .shutdown = imx_sii902x_shutdown, + .hw_params = imx_sii902x_hw_params, + .hw_free = imx_sii902x_hw_free, +}; + +static int imx_sii902x_probe(struct platform_device *pdev) +{ + struct device_node *cpu_np, *sii902x_np = NULL; + struct platform_device *cpu_pdev; + struct imx_sii902x_data *data; + int ret; + + cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + sii902x_np = of_parse_phandle(pdev->dev.of_node, "hdmi-controler", 0); + if (!sii902x_np) { + dev_err(&pdev->dev, "sii902x phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find SAI platform device\n"); + ret = -EINVAL; + goto fail; + } + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto fail; + } + + data->sii902x = of_find_i2c_device_by_node(sii902x_np); + if (!data->sii902x) { + dev_err(&pdev->dev, "failed to find sii902x i2c client\n"); + ret = -EPROBE_DEFER; + goto fail; + } + + data->dai.name = "sii902x hdmi"; + data->dai.stream_name = "sii902x hdmi"; + data->dai.codec_dai_name = "snd-soc-dummy-dai"; + data->dai.codec_name = "snd-soc-dummy"; + data->dai.cpu_dai_name = dev_name(&cpu_pdev->dev); + data->dai.platform_of_node = cpu_np; + data->dai.ops = &imx_sii902x_ops; + data->dai.playback_only = true; + data->dai.capture_only = false; + data->dai.dai_fmt = SND_SOC_DAIFMT_LEFT_J | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + + data->card.dev = &pdev->dev; + data->card.owner = THIS_MODULE; + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) + goto fail; + data->card.num_links = 1; + data->card.dai_link = &data->dai; + + platform_set_drvdata(pdev, &data->card); + snd_soc_card_set_drvdata(&data->card, data); + ret = devm_snd_soc_register_card(&pdev->dev, &data->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + +fail: + if (cpu_np) + of_node_put(cpu_np); + if (sii902x_np) + of_node_put(sii902x_np); + return ret; +} + +static const struct of_device_id imx_sii902x_dt_ids[] = { + { .compatible = "fsl,imx-audio-sii902x", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_sii902x_dt_ids); + +static struct platform_driver imx_sii902x_driver = { + .driver = { + .name = "imx-sii902x", + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + .of_match_table = imx_sii902x_dt_ids, + }, + .probe = imx_sii902x_probe, +}; +module_platform_driver(imx_sii902x_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Freescale i.MX SII902X hdmi audio ASoC machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-sii902x"); diff --git a/sound/soc/fsl/imx-spdif.c b/sound/soc/fsl/imx-spdif.c index fb896b2c9ba3..80a2dbdf272b 100644 --- a/sound/soc/fsl/imx-spdif.c +++ b/sound/soc/fsl/imx-spdif.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Freescale Semiconductor, Inc. + * Copyright (C) 2013-2015 Freescale Semiconductor, Inc. * * The code contained herein is licensed under the GNU General Public * License. You may obtain a copy of the GNU General Public License @@ -12,12 +12,40 @@ #include <linux/module.h> #include <linux/of_platform.h> #include <sound/soc.h> +#include "fsl_spdif.h" struct imx_spdif_data { struct snd_soc_dai_link dai; struct snd_soc_card card; }; +#define CLK_8K_FREQ 24576000 +#define CLK_11K_FREQ 22579200 + +static int imx_spdif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct device *dev = rtd->card->dev; + int ret = 0; + u64 rate = params_rate(params); + unsigned int freq; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + freq = do_div(rate, 8000) ? CLK_11K_FREQ : CLK_8K_FREQ; + ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, STC_TXCLK_SPDIF_ROOT, + freq, SND_SOC_CLOCK_OUT); + if (ret) + dev_err(dev, "failed to set cpu sysclk: %d\n", ret); + } + + return ret; +} + +static struct snd_soc_ops imx_spdif_ops = { + .hw_params = imx_spdif_hw_params, +}; + static int imx_spdif_audio_probe(struct platform_device *pdev) { struct device_node *spdif_np, *np = pdev->dev.of_node; @@ -45,6 +73,7 @@ static int imx_spdif_audio_probe(struct platform_device *pdev) data->dai.platform_of_node = spdif_np; data->dai.playback_only = true; data->dai.capture_only = true; + data->dai.ops = &imx_spdif_ops; if (of_property_read_bool(np, "spdif-out")) data->dai.capture_only = false; diff --git a/sound/soc/fsl/imx-wm8524.c b/sound/soc/fsl/imx-wm8524.c new file mode 100644 index 000000000000..59978a6a80be --- /dev/null +++ b/sound/soc/fsl/imx-wm8524.c @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2015-2016 Freescale Semiconductor, Inc. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/of_gpio.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/clk.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <sound/control.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <linux/pinctrl/consumer.h> +#include <linux/mfd/syscon.h> + +struct imx_priv { + struct platform_device *pdev; + struct snd_soc_card card; + struct clk *codec_clk; + unsigned int clk_frequency; +}; + +static const struct snd_soc_dapm_widget imx_wm8524_dapm_widgets[] = { + SND_SOC_DAPM_LINE("Line Out Jack", NULL), + SND_SOC_DAPM_LINE("Line In Jack", NULL), +}; + +static int imx_hifi_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_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_card *card = rtd->card; + struct device *dev = card->dev; + unsigned int fmt; + int ret = 0; + + fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + + ret = snd_soc_dai_set_fmt(cpu_dai, fmt); + if (ret) { + dev_err(dev, "failed to set cpu dai fmt: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, 2, + params_physical_width(params)); + if (ret) { + dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret); + return ret; + } + + return ret; +} + +static struct snd_soc_ops imx_hifi_ops = { + .hw_params = imx_hifi_hw_params, +}; + +static struct snd_soc_dai_link imx_wm8524_dai[] = { + { + .name = "HiFi", + .stream_name = "HiFi", + .codec_dai_name = "wm8524-hifi", + .ops = &imx_hifi_ops, + }, +}; + +static int imx_wm8524_late_probe(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd = list_first_entry( + &card->rtd_list, struct snd_soc_pcm_runtime, list); + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct imx_priv *priv = snd_soc_card_get_drvdata(card); + int ret; + + priv->clk_frequency = clk_get_rate(priv->codec_clk); + + ret = snd_soc_dai_set_sysclk(codec_dai, 0, priv->clk_frequency, + SND_SOC_CLOCK_IN); + + return 0; +} + +static int imx_wm8524_probe(struct platform_device *pdev) +{ + struct device_node *cpu_np, *codec_np = NULL; + struct platform_device *cpu_pdev; + struct imx_priv *priv; + struct platform_device *codec_pdev = NULL; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->pdev = pdev; + + cpu_np = of_parse_phandle(pdev->dev.of_node, "audio-cpu", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); + if (!codec_np) { + dev_err(&pdev->dev, "phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find SAI platform device\n"); + ret = -EINVAL; + goto fail; + } + + codec_pdev = of_find_device_by_node(codec_np); + if (!codec_pdev || !codec_pdev->dev.driver) { + dev_err(&pdev->dev, "failed to find codec platform device\n"); + ret = -EINVAL; + goto fail; + } + + priv->codec_clk = devm_clk_get(&codec_pdev->dev, "mclk"); + if (IS_ERR(priv->codec_clk)) { + ret = PTR_ERR(priv->codec_clk); + dev_err(&pdev->dev, "failed to get codec clk: %d\n", ret); + goto fail; + } + + priv->card.dai_link = imx_wm8524_dai; + + imx_wm8524_dai[0].codec_of_node = codec_np; + imx_wm8524_dai[0].cpu_dai_name = dev_name(&cpu_pdev->dev); + imx_wm8524_dai[0].platform_of_node = cpu_np; + imx_wm8524_dai[0].playback_only = 1; + + priv->card.late_probe = imx_wm8524_late_probe; + priv->card.num_links = 1; + priv->card.dev = &pdev->dev; + priv->card.owner = THIS_MODULE; + priv->card.dapm_widgets = imx_wm8524_dapm_widgets; + priv->card.num_dapm_widgets = ARRAY_SIZE(imx_wm8524_dapm_widgets); + + ret = snd_soc_of_parse_card_name(&priv->card, "model"); + if (ret) + goto fail; + + ret = snd_soc_of_parse_audio_routing(&priv->card, "audio-routing"); + if (ret) + goto fail; + + snd_soc_card_set_drvdata(&priv->card, priv); + + ret = devm_snd_soc_register_card(&pdev->dev, &priv->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + + ret = 0; +fail: + if (cpu_np) + of_node_put(cpu_np); + if (codec_np) + of_node_put(codec_np); + + return ret; +} + +static const struct of_device_id imx_wm8524_dt_ids[] = { + { .compatible = "fsl,imx-audio-wm8524", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_wm8524_dt_ids); + +static struct platform_driver imx_wm8524_driver = { + .driver = { + .name = "imx-wm8524", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_wm8524_dt_ids, + }, + .probe = imx_wm8524_probe, +}; +module_platform_driver(imx_wm8524_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Freescale i.MX WM8524 ASoC machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-wm8524"); diff --git a/sound/soc/fsl/imx-wm8958.c b/sound/soc/fsl/imx-wm8958.c new file mode 100644 index 000000000000..09b8187f94af --- /dev/null +++ b/sound/soc/fsl/imx-wm8958.c @@ -0,0 +1,587 @@ +/* + * Copyright (C) 2015-2016 Freescale Semiconductor, Inc. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/i2c.h> +#include <linux/of_gpio.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/clk.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <sound/control.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <linux/pinctrl/consumer.h> +#include <linux/mfd/wm8994/registers.h> +#include <linux/mfd/syscon.h> +#include "../fsl/fsl_sai.h" +#include "../codecs/wm8994.h" + +#define DAI_NAME_SIZE 32 + +struct imx_wm8958_data { + struct snd_soc_dai_link dai; + struct snd_soc_card card; + char codec_dai_name[DAI_NAME_SIZE]; + char platform_name[DAI_NAME_SIZE]; + struct clk *mclk; + unsigned int clk_frequency; + bool is_codec_master; + int sr_stream[2]; + struct regmap *gpr; +}; + +struct imx_priv { + int hp_gpio; + int hp_active_low; + struct snd_soc_codec *codec; + struct platform_device *pdev; + struct snd_kcontrol *headphone_kctl; + struct snd_card *snd_card; +}; + +static struct imx_priv card_priv; + +static struct snd_soc_jack imx_hp_jack; + +static struct snd_soc_jack_pin imx_hp_jack_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, +}; + +static struct snd_soc_jack_gpio imx_hp_jack_gpio = { + .name = "headphone detect", + .report = SND_JACK_HEADPHONE, + .debounce_time = 250, + .invert = 1, +}; + +static int hpjack_status_check(void *data) +{ + struct imx_priv *priv = &card_priv; + struct platform_device *pdev = priv->pdev; + char *envp[3], *buf; + int hp_status, ret; + + if (!gpio_is_valid(priv->hp_gpio)) + return 0; + + hp_status = gpio_get_value(priv->hp_gpio); + buf = kmalloc(32, GFP_ATOMIC); + if (!buf) { + dev_err(&pdev->dev, "%s kmalloc failed\n", __func__); + return -ENOMEM; + } + + if (hp_status != priv->hp_active_low) { + snprintf(buf, 32, "STATE=%d", 2); + snd_soc_dapm_disable_pin(snd_soc_codec_get_dapm(priv->codec), "Ext Spk"); + ret = imx_hp_jack_gpio.report; + snd_kctl_jack_report(priv->snd_card, priv->headphone_kctl, 1); + } else { + snprintf(buf, 32, "STATE=%d", 0); + snd_soc_dapm_enable_pin(snd_soc_codec_get_dapm(priv->codec), "Ext Spk"); + ret = 0; + snd_kctl_jack_report(priv->snd_card, priv->headphone_kctl, 0); + } + + envp[0] = "NAME=headphone"; + envp[1] = buf; + envp[2] = NULL; + kobject_uevent_env(&pdev->dev.kobj, KOBJ_CHANGE, envp); + kfree(buf); + + return ret; +} + +static const struct snd_soc_dapm_widget imx_wm8958_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), +}; + +static const struct snd_soc_dapm_route imx_wm8958_dapm_route[] = { + {"Headphone Jack", NULL, "HPOUT1L"}, + {"Headphone Jack", NULL, "HPOUT1R"}, + {"Ext Spk", NULL, "SPKOUTLP"}, + {"Ext Spk", NULL, "SPKOUTLN"}, + {"Ext Spk", NULL, "SPKOUTRP"}, + {"Ext Spk", NULL, "SPKOUTRN"}, + {"IN1LN", NULL, "MICBIAS2"}, +}; + +static int imx_hifi_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_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_codec *codec = codec_dai->codec; + struct snd_soc_card *card = rtd->card; + struct device *dev = card->dev; + struct imx_wm8958_data *data = snd_soc_card_get_drvdata(card); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + unsigned int sample_rate = params_rate(params); + unsigned int pll_out; + int ret; + + if (tx && params_width(params) == 24) { + if (sample_rate == 88200 || sample_rate == 96000 || + sample_rate == 48000 || sample_rate == 44100) { + dev_err(dev, "Can't support sample rate %dHZ\n", sample_rate); + return -EINVAL; + } + } else if (!tx && params_width(params) == 24) { + if (sample_rate == 44100 || sample_rate == 48000) { + dev_err(dev, "Can't support sample rate %dHZ\n", sample_rate); + return -EINVAL; + } + } + + ret = snd_soc_dai_set_fmt(codec_dai, data->dai.dai_fmt); + if (ret) { + dev_err(dev, "failed to set codec dai fmt: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_fmt(cpu_dai, data->dai.dai_fmt); + if (ret) { + dev_err(dev, "failed to set cpu dai fmt: %d\n", ret); + return ret; + } + + data->clk_frequency = clk_get_rate(data->mclk); + + if (!data->is_codec_master) { + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_OUT); + if (ret) { + dev_err(dev, "failed to set cpu sysclk: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_FLL_SRC_MCLK1, + data->clk_frequency, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(dev, "failed to set codec sysclk: %d\n", ret); + return ret; + } + } else { + data->sr_stream[tx] = sample_rate; + + if (params_width(params) == 24) + pll_out = data->sr_stream[tx] * 384; + else + pll_out = data->sr_stream[tx] * 256; + + ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, + WM8994_FLL_SRC_MCLK1, + data->clk_frequency, + pll_out); + if (ret) { + dev_err(dev, "failed to set codec pll: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(dev, "failed to set cpu sysclk: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1, + pll_out, SND_SOC_CLOCK_OUT); + if (ret) { + dev_err(dev, "failed to set codec sysclk: %d\n", ret); + return ret; + } + } + + /* + * Set GPIO1 pin function to reserve, so that DAC1 and ADC1 using shared + * LRCLK from DACLRCK1. + */ + snd_soc_update_bits(codec, WM8994_GPIO_1, 0x1f, 0x2); + + /* + * Clear ADC_OSR128 bit to support slower SYSCLK, and support ADC sample + * rate 8K, 11.025K and 12K. + */ + snd_soc_update_bits(codec, WM8994_OVERSAMPLING, 1<<1, 0); + return 0; +} + +static int imx_hifi_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_card *card = rtd->card; + struct imx_wm8958_data *data = snd_soc_card_get_drvdata(card); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + + if (data->is_codec_master && + data->sr_stream[!tx] == 0 && data->sr_stream[tx]) { + /* + * We should connect AIF1CLK source to FLL after enable FLL, and + * disconnet AIF1CLK source to FLL before disable FLL, otherwise + * FLL worked abnormal. + */ + snd_soc_dai_set_sysclk(codec_dai, WM8994_FLL_SRC_MCLK1, + data->clk_frequency, SND_SOC_CLOCK_OUT); + + /* Disable FLL1 after all stream finished. */ + snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, 0, 0, 0); + } + + data->sr_stream[tx] = 0; + + return 0; +} + +static u32 imx_wm8958_adc_rates[] = { + 8000, 11025, 12000, 16000, 22050, + 24000, 32000, 44100, 48000 +}; + +static u32 imx_wm8958_dac_rates[] = { + 8000, 11025, 12000, 16000, 22050, + 24000, 32000, 44100, 48000, 88200, 96000 +}; + +static struct snd_pcm_hw_constraint_list imx_wm8958_adc_rate_constraints = { + .count = ARRAY_SIZE(imx_wm8958_adc_rates), + .list = imx_wm8958_adc_rates, +}; + +static struct snd_pcm_hw_constraint_list imx_wm8958_dac_rate_constraints = { + .count = ARRAY_SIZE(imx_wm8958_dac_rates), + .list = imx_wm8958_dac_rates, +}; + +static int imx_hifi_startup(struct snd_pcm_substream *substream) +{ + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + int ret = 0; + + if (!tx) + ret = snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &imx_wm8958_adc_rate_constraints); + else + ret = snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &imx_wm8958_dac_rate_constraints); + return ret; +} + +static struct snd_soc_ops imx_hifi_ops = { + .hw_params = imx_hifi_hw_params, + .hw_free = imx_hifi_hw_free, + .startup = imx_hifi_startup, +}; + +static int imx_wm8958_gpio_init(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd = list_first_entry( + &card->rtd_list, struct snd_soc_pcm_runtime, list); + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_codec *codec = codec_dai->codec; + struct imx_priv *priv = &card_priv; + int ret; + priv->codec = codec; + + if (gpio_is_valid(priv->hp_gpio)) { + imx_hp_jack_gpio.gpio = priv->hp_gpio; + imx_hp_jack_gpio.jack_status_check = hpjack_status_check; + + ret = snd_soc_card_jack_new(card, "Headphone Jack", + SND_JACK_HEADPHONE, &imx_hp_jack, + imx_hp_jack_pins, ARRAY_SIZE(imx_hp_jack_pins)); + if (ret) + return ret; + + ret = snd_soc_jack_add_gpios(&imx_hp_jack, 1, + &imx_hp_jack_gpio); + if (ret) + return ret; + } + + return 0; +} + +static ssize_t show_headphone(struct device_driver *dev, char *buf) +{ + struct imx_priv *priv = &card_priv; + int hp_status; + + if (!gpio_is_valid(priv->hp_gpio)) { + strcpy(buf, "no detect gpio connected\n"); + return strlen(buf); + } + + /* Check if headphone is plugged in */ + hp_status = gpio_get_value(priv->hp_gpio); + + if (hp_status != priv->hp_active_low) + strcpy(buf, "headphone\n"); + else + strcpy(buf, "speaker\n"); + + return strlen(buf); +} + +static DRIVER_ATTR(headphone, S_IRUGO | S_IWUSR, show_headphone, NULL); + +static int imx_wm8958_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 *rtd = list_first_entry( + &card->rtd_list, struct snd_soc_pcm_runtime, list); + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct imx_wm8958_data *data = snd_soc_card_get_drvdata(card); + int ret; + + if (dapm->dev != codec_dai->dev) + return 0; + + switch (level) { + case SND_SOC_BIAS_STANDBY: + if (card->dapm.bias_level == SND_SOC_BIAS_OFF) { + if (!IS_ERR(data->mclk)) { + ret = clk_prepare_enable(data->mclk); + if (ret) { + dev_err(card->dev, + "Failed to enable MCLK: %d\n", + ret); + return ret; + } + } + } + break; + default: + break; + } + + return 0; +} + +static int imx_wm8958_set_bias_level_post(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, + enum snd_soc_bias_level level) +{ + struct snd_soc_pcm_runtime *rtd = list_first_entry( + &card->rtd_list, struct snd_soc_pcm_runtime, list); + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct imx_wm8958_data *data = snd_soc_card_get_drvdata(card); + + if (dapm->dev != codec_dai->dev) + return 0; + + switch (level) { + case SND_SOC_BIAS_OFF: + if (card->dapm.bias_level == SND_SOC_BIAS_STANDBY) + if (!IS_ERR(data->mclk)) + clk_disable_unprepare(data->mclk); + break; + default: + break; + } + + card->dapm.bias_level = level; + + return 0; +} + +static int of_parse_gpr(struct platform_device *pdev, + struct imx_wm8958_data *data) +{ + int ret; + struct of_phandle_args args; + + if (of_device_is_compatible(pdev->dev.of_node, + "fsl,imx7d-12x12-lpddr3-arm2-wm8958")) + return 0; + + ret = of_parse_phandle_with_fixed_args(pdev->dev.of_node, + "gpr", 3, 0, &args); + if (ret) { + dev_warn(&pdev->dev, "failed to get gpr property\n"); + return ret; + } + + data->gpr = syscon_node_to_regmap(args.np); + if (IS_ERR(data->gpr)) { + ret = PTR_ERR(data->gpr); + dev_err(&pdev->dev, "failed to get gpr regmap\n"); + return ret; + } + + regmap_update_bits(data->gpr, args.args[0], args.args[1], + args.args[2]); + + return 0; +} + +static int imx_wm8958_probe(struct platform_device *pdev) +{ + struct device_node *cpu_np, *codec_np = NULL; + struct device_node *np = pdev->dev.of_node; + struct platform_device *cpu_pdev; + struct imx_priv *priv = &card_priv; + struct i2c_client *codec_dev; + struct imx_wm8958_data *data; + int ret; + + priv->pdev = pdev; + + cpu_np = of_parse_phandle(np, "cpu-dai", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + codec_np = of_parse_phandle(np, "audio-codec", 0); + if (!codec_np) { + dev_err(&pdev->dev, "phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find SAI platform device\n"); + ret = -EINVAL; + goto fail; + } + + codec_dev = of_find_i2c_device_by_node(codec_np); + if (!codec_dev || !codec_dev->dev.driver) { + dev_err(&pdev->dev, "failed to find codec platform device\n"); + ret = -EINVAL; + goto fail; + } + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto fail; + } + + ret = of_parse_gpr(pdev, data); + if (ret) + goto fail; + + if (of_property_read_bool(np, "codec-master")) { + data->dai.dai_fmt = SND_SOC_DAIFMT_CBM_CFM; + data->is_codec_master = true; + } else + data->dai.dai_fmt = SND_SOC_DAIFMT_CBS_CFS; + + data->mclk = devm_clk_get(&codec_dev->dev, "mclk1"); + if (IS_ERR(data->mclk)) { + ret = PTR_ERR(data->mclk); + dev_err(&pdev->dev, "failed to get codec clk: %d\n", ret); + goto fail; + } + + priv->hp_gpio = of_get_named_gpio_flags(np, "hp-det-gpios", 0, + (enum of_gpio_flags *)&priv->hp_active_low); + + data->dai.name = "HiFi"; + data->dai.stream_name = "HiFi"; + data->dai.codec_dai_name = "wm8994-aif1"; + data->dai.codec_name = "wm8994-codec"; + data->dai.cpu_dai_name = dev_name(&cpu_pdev->dev); + data->dai.platform_of_node = cpu_np; + data->dai.ops = &imx_hifi_ops; + data->dai.dai_fmt |= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF; + data->card.set_bias_level = imx_wm8958_set_bias_level; + data->card.set_bias_level_post = imx_wm8958_set_bias_level_post; + data->card.owner = THIS_MODULE; + + data->card.dev = &pdev->dev; + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) + goto fail; + + data->card.num_links = 1; + data->card.dai_link = &data->dai; + data->card.dapm_widgets = imx_wm8958_dapm_widgets; + data->card.num_dapm_widgets = ARRAY_SIZE(imx_wm8958_dapm_widgets); + data->card.dapm_routes = imx_wm8958_dapm_route; + data->card.num_dapm_routes = ARRAY_SIZE(imx_wm8958_dapm_route); + platform_set_drvdata(pdev, &data->card); + snd_soc_card_set_drvdata(&data->card, data); + + ret = devm_snd_soc_register_card(&pdev->dev, &data->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + + priv->snd_card = data->card.snd_card; + + priv->headphone_kctl = snd_kctl_jack_new("Headphone", NULL); + ret = snd_ctl_add(data->card.snd_card, priv->headphone_kctl); + if (ret) + goto fail; + + ret = imx_wm8958_gpio_init(&data->card); + + if (gpio_is_valid(priv->hp_gpio)) { + ret = driver_create_file(pdev->dev.driver, + &driver_attr_headphone); + if (ret) { + dev_err(&pdev->dev, + "create hp attr failed (%d)\n", ret); + goto fail; + } + } + +fail: + if (cpu_np) + of_node_put(cpu_np); + if (codec_np) + of_node_put(codec_np); + + return ret; +} + +static int imx_wm8958_remove(struct platform_device *pdev) +{ + driver_remove_file(pdev->dev.driver, &driver_attr_headphone); + return 0; +} + +static const struct of_device_id imx_wm8958_dt_ids[] = { + { .compatible = "fsl,imx-audio-wm8958", }, + { .compatible = "fsl,imx7d-12x12-lpddr3-arm2-wm8958", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_wm8958_dt_ids); + +static struct platform_driver imx_wm8958_driver = { + .driver = { + .name = "imx-wm8958", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_wm8958_dt_ids, + }, + .probe = imx_wm8958_probe, + .remove = imx_wm8958_remove, +}; +module_platform_driver(imx_wm8958_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Freescale i.MX WM8958 ASoC machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-wm8958"); diff --git a/sound/soc/fsl/imx-wm8960.c b/sound/soc/fsl/imx-wm8960.c new file mode 100644 index 000000000000..276f10a7a81a --- /dev/null +++ b/sound/soc/fsl/imx-wm8960.c @@ -0,0 +1,699 @@ +/* + * Copyright (C) 2015-2016 Freescale Semiconductor, Inc. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/i2c.h> +#include <linux/of_gpio.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/clk.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <sound/control.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <linux/pinctrl/consumer.h> +#include <linux/mfd/syscon.h> +#include "../codecs/wm8960.h" +#include "fsl_sai.h" + +struct imx_wm8960_data { + struct snd_soc_card card; + struct clk *codec_clk; + unsigned int clk_frequency; + bool is_codec_master; + bool is_codec_rpmsg; + bool is_stream_in_use[2]; + bool is_stream_opened[2]; + struct regmap *gpr; + unsigned int hp_det[2]; + u32 asrc_rate; + u32 asrc_format; +}; + +struct imx_priv { + enum of_gpio_flags hp_active_low; + enum of_gpio_flags mic_active_low; + bool is_headset_jack; + struct snd_kcontrol *headphone_kctl; + struct platform_device *pdev; + struct platform_device *asrc_pdev; + struct snd_card *snd_card; +}; + +static struct imx_priv card_priv; + +static struct snd_soc_jack imx_hp_jack; +static struct snd_soc_jack_pin imx_hp_jack_pin = { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, +}; +static struct snd_soc_jack_gpio imx_hp_jack_gpio = { + .name = "headphone detect", + .report = SND_JACK_HEADPHONE, + .debounce_time = 250, + .invert = 0, +}; + +static struct snd_soc_jack imx_mic_jack; +static struct snd_soc_jack_pin imx_mic_jack_pins = { + .pin = "Mic Jack", + .mask = SND_JACK_MICROPHONE, +}; +static struct snd_soc_jack_gpio imx_mic_jack_gpio = { + .name = "mic detect", + .report = SND_JACK_MICROPHONE, + .debounce_time = 250, + .invert = 0, +}; + +static int hp_jack_status_check(void *data) +{ + struct imx_priv *priv = &card_priv; + struct snd_soc_jack *jack = data; + struct snd_soc_dapm_context *dapm = &jack->card->dapm; + int hp_status, ret; + + hp_status = gpio_get_value(imx_hp_jack_gpio.gpio); + + if (hp_status != priv->hp_active_low) { + snd_soc_dapm_disable_pin(dapm, "Ext Spk"); + if (priv->is_headset_jack) { + snd_soc_dapm_enable_pin(dapm, "Mic Jack"); + snd_soc_dapm_disable_pin(dapm, "Main MIC"); + } + ret = imx_hp_jack_gpio.report; + snd_kctl_jack_report(priv->snd_card, priv->headphone_kctl, 1); + } else { + snd_soc_dapm_enable_pin(dapm, "Ext Spk"); + if (priv->is_headset_jack) { + snd_soc_dapm_disable_pin(dapm, "Mic Jack"); + snd_soc_dapm_enable_pin(dapm, "Main MIC"); + } + ret = 0; + snd_kctl_jack_report(priv->snd_card, priv->headphone_kctl, 0); + } + + return ret; +} + +static int mic_jack_status_check(void *data) +{ + struct imx_priv *priv = &card_priv; + struct snd_soc_jack *jack = data; + struct snd_soc_dapm_context *dapm = &jack->card->dapm; + int mic_status, ret; + + mic_status = gpio_get_value(imx_mic_jack_gpio.gpio); + + if (mic_status != priv->mic_active_low) { + snd_soc_dapm_disable_pin(dapm, "Main MIC"); + ret = imx_mic_jack_gpio.report; + } else { + snd_soc_dapm_enable_pin(dapm, "Main MIC"); + ret = 0; + } + + return ret; +} + +static const struct snd_soc_dapm_widget imx_wm8960_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_MIC("Main MIC", NULL), +}; + +static int imx_wm8960_jack_init(struct snd_soc_card *card, + struct snd_soc_jack *jack, struct snd_soc_jack_pin *pin, + struct snd_soc_jack_gpio *gpio) +{ + int ret; + + ret = snd_soc_card_jack_new(card, pin->pin, pin->mask, jack, pin, 1); + if (ret) { + return ret; + } + + ret = snd_soc_jack_add_gpios(jack, 1, gpio); + if (ret) + return ret; + + return 0; +} + +static ssize_t show_headphone(struct device_driver *dev, char *buf) +{ + struct imx_priv *priv = &card_priv; + int hp_status; + + /* Check if headphone is plugged in */ + hp_status = gpio_get_value(imx_hp_jack_gpio.gpio); + + if (hp_status != priv->hp_active_low) + strcpy(buf, "Headphone\n"); + else + strcpy(buf, "Speaker\n"); + + return strlen(buf); +} + +static ssize_t show_micphone(struct device_driver *dev, char *buf) +{ + struct imx_priv *priv = &card_priv; + int mic_status; + + /* Check if headphone is plugged in */ + mic_status = gpio_get_value(imx_mic_jack_gpio.gpio); + + if (mic_status != priv->mic_active_low) + strcpy(buf, "Mic Jack\n"); + else + strcpy(buf, "Main MIC\n"); + + return strlen(buf); +} +static DRIVER_ATTR(headphone, S_IRUGO | S_IWUSR, show_headphone, NULL); +static DRIVER_ATTR(micphone, S_IRUGO | S_IWUSR, show_micphone, NULL); + +static int imx_hifi_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_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_card *card = rtd->card; + struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct device *dev = card->dev; + unsigned int sample_rate = params_rate(params); + unsigned int pll_out; + unsigned int fmt; + int ret = 0; + + data->is_stream_in_use[tx] = true; + + if (data->is_stream_in_use[!tx]) + return 0; + + if (data->is_codec_master) + fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; + else + fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, fmt); + if (ret) { + dev_err(dev, "failed to set cpu dai fmt: %d\n", ret); + return ret; + } + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, fmt); + if (ret) { + dev_err(dev, "failed to set codec dai fmt: %d\n", ret); + return ret; + } + + if (!data->is_codec_master) { + ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, 2, + params_physical_width(params)); + if (ret) { + dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_OUT); + if (ret) { + dev_err(dev, "failed to set cpu sysclk: %d\n", ret); + return ret; + } + return 0; + } else { + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(dev, "failed to set cpu sysclk: %d\n", ret); + return ret; + } + } + + data->clk_frequency = clk_get_rate(data->codec_clk); + + /* Set codec pll */ + if (params_width(params) == 24) + pll_out = sample_rate * 768; + else + pll_out = sample_rate * 512; + + ret = snd_soc_dai_set_pll(codec_dai, WM8960_SYSCLK_AUTO, 0, data->clk_frequency, pll_out); + if (ret) + return ret; + ret = snd_soc_dai_set_sysclk(codec_dai, WM8960_SYSCLK_AUTO, pll_out, 0); + + return ret; +} + +static int imx_hifi_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_card *card = rtd->card; + struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct device *dev = card->dev; + int ret; + + data->is_stream_in_use[tx] = false; + + if (data->is_codec_master && !data->is_stream_in_use[!tx]) { + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_CBS_CFS | SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF); + if (ret) + dev_warn(dev, "failed to set codec dai fmt: %d\n", ret); + } + + return 0; +} + +static u32 imx_wm8960_rates[] = { 8000, 16000, 32000, 48000 }; +static struct snd_pcm_hw_constraint_list imx_wm8960_rate_constraints = { + .count = ARRAY_SIZE(imx_wm8960_rates), + .list = imx_wm8960_rates, +}; + +static int imx_hifi_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_card *card = rtd->card; + struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct fsl_sai *sai = dev_get_drvdata(cpu_dai->dev); + int ret = 0; + + data->is_stream_opened[tx] = true; + if (data->is_stream_opened[tx] != sai->is_stream_opened[tx] || + data->is_stream_opened[!tx] != sai->is_stream_opened[!tx]) { + data->is_stream_opened[tx] = false; + return -EBUSY; + } + + if (!data->is_codec_master) { + ret = snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &imx_wm8960_rate_constraints); + if (ret) + return ret; + } + + return ret; +} + +static void imx_hifi_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + + data->is_stream_opened[tx] = false; +} + +static struct snd_soc_ops imx_hifi_ops = { + .hw_params = imx_hifi_hw_params, + .hw_free = imx_hifi_hw_free, + .startup = imx_hifi_startup, + .shutdown = imx_hifi_shutdown, +}; + +static int imx_wm8960_late_probe(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd = list_first_entry( + &card->rtd_list, struct snd_soc_pcm_runtime, list); + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_codec *codec = codec_dai->codec; + struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card); + + /* + * codec ADCLRC pin configured as GPIO, DACLRC pin is used as a frame + * clock for ADCs and DACs + */ + snd_soc_update_bits(codec, WM8960_IFACE2, 1<<6, 1<<6); + + /* GPIO1 used as headphone detect output */ + snd_soc_update_bits(codec, WM8960_ADDCTL4, 7<<4, 3<<4); + + /* Enable headphone jack detect */ + snd_soc_update_bits(codec, WM8960_ADDCTL2, 1<<6, 1<<6); + snd_soc_update_bits(codec, WM8960_ADDCTL2, 1<<5, data->hp_det[1]<<5); + snd_soc_update_bits(codec, WM8960_ADDCTL4, 3<<2, data->hp_det[0]<<2); + snd_soc_update_bits(codec, WM8960_ADDCTL1, 3, 3); + + return 0; +} + +static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_card *card = rtd->card; + struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card); + struct imx_priv *priv = &card_priv; + struct snd_interval *rate; + struct snd_mask *mask; + + if (!priv->asrc_pdev) + return -EINVAL; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + rate->max = rate->min = data->asrc_rate; + + mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + snd_mask_none(mask); + snd_mask_set(mask, data->asrc_format); + + return 0; +} + +static struct snd_soc_dai_link imx_wm8960_dai[] = { + { + .name = "HiFi", + .stream_name = "HiFi", + .codec_dai_name = "wm8960-hifi", + .ops = &imx_hifi_ops, + }, + { + .name = "HiFi-ASRC-FE", + .stream_name = "HiFi-ASRC-FE", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .dynamic = 1, + .ignore_pmdown_time = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .dpcm_merged_chan = 1, + }, + { + .name = "HiFi-ASRC-BE", + .stream_name = "HiFi-ASRC-BE", + .codec_dai_name = "wm8960-hifi", + .platform_name = "snd-soc-dummy", + .no_pcm = 1, + .ignore_pmdown_time = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &imx_hifi_ops, + .be_hw_params_fixup = be_hw_params_fixup, + }, +}; + +static int of_parse_gpr(struct platform_device *pdev, + struct imx_wm8960_data *data) +{ + int ret; + struct of_phandle_args args; + + if (of_device_is_compatible(pdev->dev.of_node, + "fsl,imx7d-evk-wm8960")) + return 0; + + ret = of_parse_phandle_with_fixed_args(pdev->dev.of_node, + "gpr", 3, 0, &args); + if (ret) { + dev_warn(&pdev->dev, "failed to get gpr property\n"); + return ret; + } + + data->gpr = syscon_node_to_regmap(args.np); + if (IS_ERR(data->gpr)) { + ret = PTR_ERR(data->gpr); + dev_err(&pdev->dev, "failed to get gpr regmap\n"); + return ret; + } + + regmap_update_bits(data->gpr, args.args[0], args.args[1], + args.args[2]); + + return 0; +} + +static int imx_wm8960_probe(struct platform_device *pdev) +{ + struct device_node *cpu_np = NULL, *codec_np = NULL; + struct platform_device *cpu_pdev; + struct imx_priv *priv = &card_priv; + struct imx_wm8960_data *data; + struct platform_device *asrc_pdev = NULL; + struct device_node *asrc_np; + u32 width; + int ret; + + priv->pdev = pdev; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto fail; + } + + if (of_property_read_bool(pdev->dev.of_node, "codec-rpmsg")) + data->is_codec_rpmsg = true; + + cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find SAI platform device\n"); + ret = -EINVAL; + goto fail; + } + + codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); + if (!codec_np) { + dev_err(&pdev->dev, "phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + if (data->is_codec_rpmsg) { + struct platform_device *codec_dev; + + codec_dev = of_find_device_by_node(codec_np); + if (!codec_dev || !codec_dev->dev.driver) { + dev_err(&pdev->dev, "failed to find codec platform device\n"); + ret = -EINVAL; + goto fail; + } + + data->codec_clk = devm_clk_get(&codec_dev->dev, "mclk"); + if (IS_ERR(data->codec_clk)) { + ret = PTR_ERR(data->codec_clk); + dev_err(&pdev->dev, "failed to get codec clk: %d\n", ret); + goto fail; + } + } else { + struct i2c_client *codec_dev; + + codec_dev = of_find_i2c_device_by_node(codec_np); + if (!codec_dev || !codec_dev->dev.driver) { + dev_err(&pdev->dev, "failed to find codec platform device\n"); + ret = -EINVAL; + goto fail; + } + + data->codec_clk = devm_clk_get(&codec_dev->dev, "mclk"); + if (IS_ERR(data->codec_clk)) { + ret = PTR_ERR(data->codec_clk); + dev_err(&pdev->dev, "failed to get codec clk: %d\n", ret); + goto fail; + } + } + + if (of_property_read_bool(pdev->dev.of_node, "codec-master")) + data->is_codec_master = true; + + ret = of_parse_gpr(pdev, data); + if (ret) + goto fail; + + of_property_read_u32_array(pdev->dev.of_node, "hp-det", data->hp_det, 2); + + asrc_np = of_parse_phandle(pdev->dev.of_node, "asrc-controller", 0); + if (asrc_np) { + asrc_pdev = of_find_device_by_node(asrc_np); + priv->asrc_pdev = asrc_pdev; + } + + data->card.dai_link = imx_wm8960_dai; + + if (data->is_codec_rpmsg) { + imx_wm8960_dai[0].codec_name = "rpmsg-audio-codec-wm8960"; + imx_wm8960_dai[0].codec_dai_name = "rpmsg-wm8960-hifi"; + } else + imx_wm8960_dai[0].codec_of_node = codec_np; + + imx_wm8960_dai[0].cpu_dai_name = dev_name(&cpu_pdev->dev); + imx_wm8960_dai[0].platform_of_node = cpu_np; + + if (!asrc_pdev) { + data->card.num_links = 1; + } else { + imx_wm8960_dai[1].cpu_of_node = asrc_np; + imx_wm8960_dai[1].platform_of_node = asrc_np; + if (data->is_codec_rpmsg) { + imx_wm8960_dai[2].codec_name = "rpmsg-audio-codec-wm8960"; + imx_wm8960_dai[2].codec_dai_name = "rpmsg-wm8960-hifi"; + } else + imx_wm8960_dai[2].codec_of_node = codec_np; + imx_wm8960_dai[2].cpu_dai_name = dev_name(&cpu_pdev->dev); + data->card.num_links = 3; + + ret = of_property_read_u32(asrc_np, "fsl,asrc-rate", + &data->asrc_rate); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + ret = -EINVAL; + goto fail; + } + + ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + ret = -EINVAL; + goto fail; + } + + if (width == 24) + data->asrc_format = SNDRV_PCM_FORMAT_S24_LE; + else + data->asrc_format = SNDRV_PCM_FORMAT_S16_LE; + } + + data->card.dev = &pdev->dev; + data->card.owner = THIS_MODULE; + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) + goto fail; + data->card.dapm_widgets = imx_wm8960_dapm_widgets; + data->card.num_dapm_widgets = ARRAY_SIZE(imx_wm8960_dapm_widgets); + + ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing"); + if (ret) + goto fail; + + data->card.late_probe = imx_wm8960_late_probe; + + platform_set_drvdata(pdev, &data->card); + snd_soc_card_set_drvdata(&data->card, data); + ret = devm_snd_soc_register_card(&pdev->dev, &data->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + + priv->snd_card = data->card.snd_card; + + imx_hp_jack_gpio.gpio = of_get_named_gpio_flags(pdev->dev.of_node, + "hp-det-gpios", 0, &priv->hp_active_low); + + imx_mic_jack_gpio.gpio = of_get_named_gpio_flags(pdev->dev.of_node, + "mic-det-gpios", 0, &priv->mic_active_low); + + if (gpio_is_valid(imx_hp_jack_gpio.gpio) && + gpio_is_valid(imx_mic_jack_gpio.gpio) && + imx_hp_jack_gpio.gpio == imx_mic_jack_gpio.gpio) + priv->is_headset_jack = true; + + if (gpio_is_valid(imx_hp_jack_gpio.gpio)) { + priv->headphone_kctl = snd_kctl_jack_new("Headphone", NULL); + ret = snd_ctl_add(priv->snd_card, priv->headphone_kctl); + if (ret) + dev_warn(&pdev->dev, "failed to create headphone jack kctl\n"); + + if (priv->is_headset_jack) { + imx_hp_jack_pin.mask |= SND_JACK_MICROPHONE; + imx_hp_jack_gpio.report |= SND_JACK_MICROPHONE; + } + imx_hp_jack_gpio.jack_status_check = hp_jack_status_check; + imx_hp_jack_gpio.data = &imx_hp_jack; + ret = imx_wm8960_jack_init(&data->card, &imx_hp_jack, + &imx_hp_jack_pin, &imx_hp_jack_gpio); + if (ret) { + dev_warn(&pdev->dev, "hp jack init failed (%d)\n", ret); + goto out; + } + + ret = driver_create_file(pdev->dev.driver, &driver_attr_headphone); + if (ret) + dev_warn(&pdev->dev, "create hp attr failed (%d)\n", ret); + } + + if (gpio_is_valid(imx_mic_jack_gpio.gpio)) { + if (!priv->is_headset_jack) { + imx_mic_jack_gpio.jack_status_check = mic_jack_status_check; + imx_mic_jack_gpio.data = &imx_mic_jack; + ret = imx_wm8960_jack_init(&data->card, &imx_mic_jack, + &imx_mic_jack_pins, &imx_mic_jack_gpio); + if (ret) { + dev_warn(&pdev->dev, "mic jack init failed (%d)\n", ret); + goto out; + } + } + ret = driver_create_file(pdev->dev.driver, &driver_attr_micphone); + if (ret) + dev_warn(&pdev->dev, "create mic attr failed (%d)\n", ret); + } + +out: + ret = 0; +fail: + if (cpu_np) + of_node_put(cpu_np); + if (codec_np) + of_node_put(codec_np); + + return ret; +} + +static int imx_wm8960_remove(struct platform_device *pdev) +{ + driver_remove_file(pdev->dev.driver, &driver_attr_micphone); + driver_remove_file(pdev->dev.driver, &driver_attr_headphone); + + return 0; +} + +static const struct of_device_id imx_wm8960_dt_ids[] = { + { .compatible = "fsl,imx-audio-wm8960", }, + { .compatible = "fsl,imx7d-evk-wm8960" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_wm8960_dt_ids); + +static struct platform_driver imx_wm8960_driver = { + .driver = { + .name = "imx-wm8960", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_wm8960_dt_ids, + }, + .probe = imx_wm8960_probe, + .remove = imx_wm8960_remove, +}; +module_platform_driver(imx_wm8960_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Freescale i.MX WM8960 ASoC machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-wm8960"); diff --git a/sound/soc/fsl/imx-wm8962.c b/sound/soc/fsl/imx-wm8962.c index 201a70d1027a..8ac9ff530829 100644 --- a/sound/soc/fsl/imx-wm8962.c +++ b/sound/soc/fsl/imx-wm8962.c @@ -1,9 +1,10 @@ /* - * Copyright 2013 Freescale Semiconductor, Inc. + * Copyright (C) 2013-2016 Freescale Semiconductor, Inc. * * Based on imx-sgtl5000.c - * Copyright 2012 Freescale Semiconductor, Inc. - * Copyright 2012 Linaro Ltd. + * Copyright (C) 2012 Freescale Semiconductor, Inc. + * Copyright (C) 2012 Linaro Ltd. + * Copyright 2017 NXP * * The code contained herein is licensed under the GNU General Public * License. You may obtain a copy of the GNU General Public License @@ -16,9 +17,13 @@ #include <linux/module.h> #include <linux/of_platform.h> #include <linux/i2c.h> +#include <linux/of_gpio.h> #include <linux/slab.h> +#include <linux/gpio.h> #include <linux/clk.h> #include <sound/soc.h> +#include <sound/jack.h> +#include <sound/control.h> #include <sound/pcm_params.h> #include <sound/soc-dapm.h> #include <linux/pinctrl/consumer.h> @@ -29,19 +34,152 @@ #define DAI_NAME_SIZE 32 struct imx_wm8962_data { - struct snd_soc_dai_link dai; + struct snd_soc_dai_link dai[3]; struct snd_soc_card card; char codec_dai_name[DAI_NAME_SIZE]; char platform_name[DAI_NAME_SIZE]; struct clk *codec_clk; unsigned int clk_frequency; + bool is_codec_master; }; struct imx_priv { + int hp_gpio; + int hp_active_low; + int mic_gpio; + int mic_active_low; + bool amic_mono; + bool dmic_mono; + struct snd_soc_codec *codec; struct platform_device *pdev; + struct snd_pcm_substream *first_stream; + struct snd_pcm_substream *second_stream; + struct snd_kcontrol *headphone_kctl; + struct snd_card *snd_card; + struct platform_device *asrc_pdev; + u32 asrc_rate; + u32 asrc_format; }; static struct imx_priv card_priv; +#ifdef CONFIG_SND_SOC_IMX_WM8962_ANDROID +static int sample_rate = 44100; +static snd_pcm_format_t sample_format = SNDRV_PCM_FORMAT_S16_LE; +#endif + +static struct snd_soc_jack imx_hp_jack; +static struct snd_soc_jack_pin imx_hp_jack_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, +}; +static struct snd_soc_jack_gpio imx_hp_jack_gpio = { + .name = "headphone detect", + .report = SND_JACK_HEADPHONE, + .debounce_time = 250, + .invert = 0, +}; + +static struct snd_soc_jack imx_mic_jack; +static struct snd_soc_jack_pin imx_mic_jack_pins[] = { + { + .pin = "AMIC", + .mask = SND_JACK_MICROPHONE, + }, +}; +static struct snd_soc_jack_gpio imx_mic_jack_gpio = { + .name = "microphone detect", + .report = SND_JACK_MICROPHONE, + .debounce_time = 250, + .invert = 0, +}; + +static int hpjack_status_check(void *data) +{ + struct imx_priv *priv = &card_priv; + struct platform_device *pdev = priv->pdev; + char *envp[3], *buf; + int hp_status, ret; + + if (!gpio_is_valid(priv->hp_gpio)) + return 0; + + hp_status = gpio_get_value(priv->hp_gpio) ? 1 : 0; + + buf = kmalloc(32, GFP_ATOMIC); + if (!buf) { + dev_err(&pdev->dev, "%s kmalloc failed\n", __func__); + return -ENOMEM; + } + + if (hp_status != priv->hp_active_low) { + snprintf(buf, 32, "STATE=%d", 2); + snd_soc_dapm_disable_pin(snd_soc_codec_get_dapm(priv->codec), "Ext Spk"); + ret = imx_hp_jack_gpio.report; + snd_kctl_jack_report(priv->snd_card, priv->headphone_kctl, 1); + } else { + snprintf(buf, 32, "STATE=%d", 0); + snd_soc_dapm_enable_pin(snd_soc_codec_get_dapm(priv->codec), "Ext Spk"); + ret = 0; + snd_kctl_jack_report(priv->snd_card, priv->headphone_kctl, 0); + } + + envp[0] = "NAME=headphone"; + envp[1] = buf; + envp[2] = NULL; + kobject_uevent_env(&pdev->dev.kobj, KOBJ_CHANGE, envp); + kfree(buf); + + return ret; +} + +static int micjack_status_check(void *data) +{ + struct imx_priv *priv = &card_priv; + struct platform_device *pdev = priv->pdev; + char *envp[3], *buf; + int mic_status, ret; + + if (!gpio_is_valid(priv->mic_gpio)) + return 0; + + mic_status = gpio_get_value(priv->mic_gpio) ? 1 : 0; + + if ((mic_status != priv->mic_active_low && priv->amic_mono) + || (mic_status == priv->mic_active_low && priv->dmic_mono)) + snd_soc_update_bits(priv->codec, WM8962_THREED1, + WM8962_ADC_MONOMIX_MASK, WM8962_ADC_MONOMIX); + else + snd_soc_update_bits(priv->codec, WM8962_THREED1, + WM8962_ADC_MONOMIX_MASK, 0); + + buf = kmalloc(32, GFP_ATOMIC); + if (!buf) { + dev_err(&pdev->dev, "%s kmalloc failed\n", __func__); + return -ENOMEM; + } + + if (mic_status != priv->mic_active_low) { + snprintf(buf, 32, "STATE=%d", 2); + snd_soc_dapm_disable_pin(snd_soc_codec_get_dapm(priv->codec), "DMIC"); + ret = imx_mic_jack_gpio.report; + } else { + snprintf(buf, 32, "STATE=%d", 0); + snd_soc_dapm_enable_pin(snd_soc_codec_get_dapm(priv->codec), "DMIC"); + ret = 0; + } + + envp[0] = "NAME=microphone"; + envp[1] = buf; + envp[2] = NULL; + kobject_uevent_env(&pdev->dev.kobj, KOBJ_CHANGE, envp); + kfree(buf); + + return ret; +} + + static const struct snd_soc_dapm_widget imx_wm8962_dapm_widgets[] = { SND_SOC_DAPM_HP("Headphone Jack", NULL), SND_SOC_DAPM_SPK("Ext Spk", NULL), @@ -49,19 +187,64 @@ static const struct snd_soc_dapm_widget imx_wm8962_dapm_widgets[] = { SND_SOC_DAPM_MIC("DMIC", NULL), }; -static int sample_rate = 44100; -static snd_pcm_format_t sample_format = SNDRV_PCM_FORMAT_S16_LE; +static u32 imx_wm8962_rates[] = {32000, 48000, 96000}; +static struct snd_pcm_hw_constraint_list imx_wm8962_rate_constraints = { + .count = ARRAY_SIZE(imx_wm8962_rates), + .list = imx_wm8962_rates, +}; + +static int imx_hifi_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct imx_wm8962_data *data = snd_soc_card_get_drvdata(card); + int ret; + + if (!data->is_codec_master) { + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &imx_wm8962_rate_constraints); + if (ret) + return ret; + } + + return 0; +} +#ifdef CONFIG_SND_SOC_IMX_WM8962_ANDROID static int imx_hifi_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) + struct snd_pcm_hw_params *params) { + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct imx_priv *priv = &card_priv; + struct device *dev = &priv->pdev->dev; + u32 dai_format; + int ret = 0; + sample_rate = params_rate(params); sample_format = params_format(params); + if (data->is_codec_master) + dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; + else + dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, dai_format); + if (ret) { + dev_err(dev, "failed to set codec dai fmt: %d\n", ret); + return ret; + } + return 0; } static struct snd_soc_ops imx_hifi_ops = { + .startup = imx_hifi_startup, .hw_params = imx_hifi_hw_params, }; @@ -69,23 +252,23 @@ static int imx_wm8962_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 *rtd; - struct snd_soc_dai *codec_dai; + struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai; struct imx_priv *priv = &card_priv; struct imx_wm8962_data *data = snd_soc_card_get_drvdata(card); struct device *dev = &priv->pdev->dev; unsigned int pll_out; int ret; - rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name); - codec_dai = rtd->codec_dai; if (dapm->dev != codec_dai->dev) return 0; + data->clk_frequency = clk_get_rate(data->codec_clk); + switch (level) { case SND_SOC_BIAS_PREPARE: if (dapm->bias_level == SND_SOC_BIAS_STANDBY) { - if (sample_format == SNDRV_PCM_FORMAT_S24_LE) + if (sample_format == SNDRV_PCM_FORMAT_S24_LE + || sample_format == SNDRV_PCM_FORMAT_S20_3LE) pll_out = sample_rate * 384; else pll_out = sample_rate * 256; @@ -136,6 +319,212 @@ static int imx_wm8962_set_bias_level(struct snd_soc_card *card, return 0; } +#else + +static int imx_hifi_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_dai *codec_dai = rtd->codec_dai; + struct imx_priv *priv = &card_priv; + struct device *dev = &priv->pdev->dev; + struct snd_soc_card *card = platform_get_drvdata(priv->pdev); + struct imx_wm8962_data *data = snd_soc_card_get_drvdata(card); + unsigned int sample_rate = params_rate(params); + snd_pcm_format_t sample_format = params_format(params); + u32 dai_format, pll_out; + int ret = 0; + + if (!priv->first_stream) { + priv->first_stream = substream; + } else { + priv->second_stream = substream; + + /* We suppose the two substream are using same params */ + return 0; + } + + data->clk_frequency = clk_get_rate(data->codec_clk); + + if (data->is_codec_master) + dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; + else + dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, dai_format); + if (ret) { + dev_err(dev, "failed to set codec dai fmt: %d\n", ret); + return ret; + } + + if (sample_format == SNDRV_PCM_FORMAT_S24_LE + || sample_format == SNDRV_PCM_FORMAT_S20_3LE) + pll_out = sample_rate * 384; + else + pll_out = sample_rate * 256; + + ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL, WM8962_FLL_MCLK, + data->clk_frequency, pll_out); + if (ret) { + dev_err(dev, "failed to start FLL: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_FLL, + pll_out, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(dev, "failed to set SYSCLK: %d\n", ret); + return ret; + } + + return 0; +} + +static int imx_hifi_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct imx_priv *priv = &card_priv; + struct device *dev = &priv->pdev->dev; + int ret; + + /* We don't need to handle anything if there's no substream running */ + if (!priv->first_stream) + return 0; + + if (priv->first_stream == substream) + priv->first_stream = priv->second_stream; + priv->second_stream = NULL; + + if (!priv->first_stream) { + /* + * Continuously setting FLL would cause playback distortion. + * We can fix it just by mute codec after playback. + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + snd_soc_dai_digital_mute(codec_dai, 1, substream->stream); + + /* + * WM8962 doesn't allow us to continuously setting FLL, + * So we set MCLK as sysclk once, which'd remove the limitation. + */ + ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK, + 0, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(dev, "failed to switch away from FLL: %d\n", ret); + return ret; + } + + /* Disable FLL and let codec do pm_runtime_put() */ + ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL, + WM8962_FLL_MCLK, 0, 0); + if (ret < 0) { + dev_err(dev, "failed to stop FLL: %d\n", ret); + return ret; + } + } + + return 0; +} + +static struct snd_soc_ops imx_hifi_ops = { + .startup = imx_hifi_startup, + .hw_params = imx_hifi_hw_params, + .hw_free = imx_hifi_hw_free, +}; +#endif /* CONFIG_SND_SOC_IMX_WM8962_ANDROID */ + +static int imx_wm8962_gpio_init(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd = list_first_entry( + &card->rtd_list, struct snd_soc_pcm_runtime, list); + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_codec *codec = codec_dai->codec; + struct imx_priv *priv = &card_priv; + + priv->codec = codec; + + if (gpio_is_valid(priv->hp_gpio)) { + imx_hp_jack_gpio.gpio = priv->hp_gpio; + imx_hp_jack_gpio.jack_status_check = hpjack_status_check; + + snd_soc_card_jack_new(card, "Headphone Jack", + SND_JACK_HEADPHONE, &imx_hp_jack, + imx_hp_jack_pins, ARRAY_SIZE(imx_hp_jack_pins)); + + snd_soc_jack_add_gpios(&imx_hp_jack, 1, &imx_hp_jack_gpio); + } + + if (gpio_is_valid(priv->mic_gpio)) { + imx_mic_jack_gpio.gpio = priv->mic_gpio; + imx_mic_jack_gpio.jack_status_check = micjack_status_check; + + snd_soc_card_jack_new(card, "AMIC", + SND_JACK_MICROPHONE, &imx_mic_jack, + imx_mic_jack_pins, ARRAY_SIZE(imx_mic_jack_pins)); + + snd_soc_jack_add_gpios(&imx_mic_jack, 1, &imx_mic_jack_gpio); + } else if (priv->amic_mono || priv->dmic_mono) { + /* + * Permanent set monomix bit if only one microphone + * is present on the board while it needs monomix. + */ + snd_soc_update_bits(priv->codec, WM8962_THREED1, + WM8962_ADC_MONOMIX_MASK, WM8962_ADC_MONOMIX); + } + + return 0; +} + +static ssize_t show_headphone(struct device_driver *dev, char *buf) +{ + struct imx_priv *priv = &card_priv; + int hp_status; + + if (!gpio_is_valid(priv->hp_gpio)) { + strcpy(buf, "no detect gpio connected\n"); + return strlen(buf); + } + + /* Check if headphone is plugged in */ + hp_status = gpio_get_value(priv->hp_gpio) ? 1 : 0; + + if (hp_status != priv->hp_active_low) + strcpy(buf, "headphone\n"); + else + strcpy(buf, "speaker\n"); + + return strlen(buf); +} + +static DRIVER_ATTR(headphone, S_IRUGO | S_IWUSR, show_headphone, NULL); + +static ssize_t show_mic(struct device_driver *dev, char *buf) +{ + struct imx_priv *priv = &card_priv; + int mic_status; + + if (!gpio_is_valid(priv->mic_gpio)) { + strcpy(buf, "no detect gpio connected\n"); + return strlen(buf); + } + + /* Check if analog microphone is plugged in */ + mic_status = gpio_get_value(priv->mic_gpio) ? 1 : 0; + + if (mic_status != priv->mic_active_low) + strcpy(buf, "amic\n"); + else + strcpy(buf, "dmic\n"); + + return strlen(buf); +} + +static DRIVER_ATTR(microphone, S_IRUGO | S_IWUSR, show_mic, NULL); + static int imx_wm8962_late_probe(struct snd_soc_card *card) { struct snd_soc_pcm_runtime *rtd; @@ -145,6 +534,7 @@ static int imx_wm8962_late_probe(struct snd_soc_card *card) struct device *dev = &priv->pdev->dev; int ret; + data->clk_frequency = clk_get_rate(data->codec_clk); rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name); codec_dai = rtd->codec_dai; ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK, @@ -155,28 +545,70 @@ static int imx_wm8962_late_probe(struct snd_soc_card *card) return ret; } +static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) { + struct imx_priv *priv = &card_priv; + struct snd_interval *rate; + struct snd_mask *mask; + + if (!priv->asrc_pdev) + return -EINVAL; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + rate->max = rate->min = priv->asrc_rate; + + mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + snd_mask_none(mask); + snd_mask_set(mask, priv->asrc_format); + + return 0; +} + static int imx_wm8962_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; - struct device_node *ssi_np, *codec_np; - struct platform_device *ssi_pdev; + struct device_node *cpu_np = NULL, *codec_np = NULL; + struct platform_device *cpu_pdev; struct imx_priv *priv = &card_priv; struct i2c_client *codec_dev; struct imx_wm8962_data *data; - int int_port, ext_port; + int int_port, ext_port, tmp_port; int ret; + struct platform_device *asrc_pdev = NULL; + struct device_node *asrc_np; + u32 width; priv->pdev = pdev; + priv->asrc_pdev = NULL; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto fail; + } + + if (of_property_read_bool(pdev->dev.of_node, "codec-master")) + data->is_codec_master = true; + + cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + if (!strstr(cpu_np->name, "ssi")) + goto audmux_bypass; ret = of_property_read_u32(np, "mux-int-port", &int_port); if (ret) { dev_err(&pdev->dev, "mux-int-port missing or invalid\n"); - return ret; + goto fail; } ret = of_property_read_u32(np, "mux-ext-port", &ext_port); if (ret) { dev_err(&pdev->dev, "mux-ext-port missing or invalid\n"); - return ret; + goto fail; } /* @@ -185,35 +617,41 @@ static int imx_wm8962_probe(struct platform_device *pdev) */ int_port--; ext_port--; - ret = imx_audmux_v2_configure_port(int_port, + if (data->is_codec_master) { + tmp_port = int_port; + int_port = ext_port; + ext_port = tmp_port; + } + + ret = imx_audmux_v2_configure_port(ext_port, IMX_AUDMUX_V2_PTCR_SYN | - IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) | - IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) | + IMX_AUDMUX_V2_PTCR_TFSEL(int_port) | + IMX_AUDMUX_V2_PTCR_TCSEL(int_port) | IMX_AUDMUX_V2_PTCR_TFSDIR | IMX_AUDMUX_V2_PTCR_TCLKDIR, - IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port)); + IMX_AUDMUX_V2_PDCR_RXDSEL(int_port)); if (ret) { dev_err(&pdev->dev, "audmux internal port setup failed\n"); - return ret; + goto fail; } - ret = imx_audmux_v2_configure_port(ext_port, + ret = imx_audmux_v2_configure_port(int_port, IMX_AUDMUX_V2_PTCR_SYN, - IMX_AUDMUX_V2_PDCR_RXDSEL(int_port)); + IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port)); if (ret) { dev_err(&pdev->dev, "audmux external port setup failed\n"); - return ret; + goto fail; } - ssi_np = of_parse_phandle(pdev->dev.of_node, "ssi-controller", 0); +audmux_bypass: codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); - if (!ssi_np || !codec_np) { + if (!codec_np) { dev_err(&pdev->dev, "phandle missing or invalid\n"); ret = -EINVAL; goto fail; } - ssi_pdev = of_find_device_by_node(ssi_np); - if (!ssi_pdev) { + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { dev_err(&pdev->dev, "failed to find SSI platform device\n"); ret = -EINVAL; goto fail; @@ -225,12 +663,15 @@ static int imx_wm8962_probe(struct platform_device *pdev) goto fail; } - data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); - if (!data) { - ret = -ENOMEM; - goto fail; + asrc_np = of_parse_phandle(pdev->dev.of_node, "asrc-controller", 0); + if (asrc_np) { + asrc_pdev = of_find_device_by_node(asrc_np); + priv->asrc_pdev = asrc_pdev; } + priv->first_stream = NULL; + priv->second_stream = NULL; + data->codec_clk = devm_clk_get(&codec_dev->dev, NULL); if (IS_ERR(data->codec_clk)) { ret = PTR_ERR(data->codec_clk); @@ -238,57 +679,135 @@ static int imx_wm8962_probe(struct platform_device *pdev) goto fail; } - data->clk_frequency = clk_get_rate(data->codec_clk); - ret = clk_prepare_enable(data->codec_clk); - if (ret) { - dev_err(&codec_dev->dev, "failed to enable codec clk: %d\n", ret); - goto fail; - } + priv->amic_mono = of_property_read_bool(codec_np, "amic-mono"); + priv->dmic_mono = of_property_read_bool(codec_np, "dmic-mono"); + + priv->hp_gpio = of_get_named_gpio_flags(np, "hp-det-gpios", 0, + (enum of_gpio_flags *)&priv->hp_active_low); + priv->mic_gpio = of_get_named_gpio_flags(np, "mic-det-gpios", 0, + (enum of_gpio_flags *)&priv->mic_active_low); + + data->dai[0].name = "HiFi"; + data->dai[0].stream_name = "HiFi"; + data->dai[0].codec_dai_name = "wm8962"; + data->dai[0].codec_of_node = codec_np; + data->dai[0].cpu_dai_name = dev_name(&cpu_pdev->dev); + data->dai[0].platform_of_node = cpu_np; + data->dai[0].ops = &imx_hifi_ops; + data->dai[0].dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF; + if (data->is_codec_master) + data->dai[0].dai_fmt |= SND_SOC_DAIFMT_CBM_CFM; + else + data->dai[0].dai_fmt |= SND_SOC_DAIFMT_CBS_CFS; + + data->card.num_links = 1; + + if (asrc_pdev) { + data->dai[0].ignore_pmdown_time = 1; + data->dai[1].name = "HiFi-ASRC-FE"; + data->dai[1].stream_name = "HiFi-ASRC-FE"; + data->dai[1].codec_name = "snd-soc-dummy"; + data->dai[1].codec_dai_name = "snd-soc-dummy-dai"; + data->dai[1].cpu_of_node = asrc_np; + data->dai[1].platform_of_node = asrc_np; + data->dai[1].dynamic = 1; + data->dai[1].ignore_pmdown_time = 1; + data->dai[1].dpcm_playback = 1; + data->dai[1].dpcm_capture = 1; + data->dai[1].dpcm_merged_chan = 1; + + data->dai[2].name = "HiFi-ASRC-BE"; + data->dai[2].stream_name = "HiFi-ASRC-BE"; + data->dai[2].codec_dai_name = "wm8962"; + data->dai[2].codec_of_node = codec_np; + data->dai[2].cpu_dai_name = dev_name(&cpu_pdev->dev); + data->dai[2].platform_name = "snd-soc-dummy"; + data->dai[2].ops = &imx_hifi_ops; + data->dai[2].be_hw_params_fixup = be_hw_params_fixup; + data->dai[2].no_pcm = 1; + data->dai[2].ignore_pmdown_time = 1; + data->dai[2].dpcm_playback = 1; + data->dai[2].dpcm_capture = 1; + data->card.num_links = 3; + + ret = of_property_read_u32(asrc_np, "fsl,asrc-rate", + &priv->asrc_rate); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + ret = -EINVAL; + goto fail; + } + + ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + ret = -EINVAL; + goto fail; + } - data->dai.name = "HiFi"; - data->dai.stream_name = "HiFi"; - data->dai.codec_dai_name = "wm8962"; - data->dai.codec_of_node = codec_np; - data->dai.cpu_dai_name = dev_name(&ssi_pdev->dev); - data->dai.platform_of_node = ssi_np; - data->dai.ops = &imx_hifi_ops; - data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBM_CFM; + if (width == 24) + priv->asrc_format = SNDRV_PCM_FORMAT_S24_LE; + else + priv->asrc_format = SNDRV_PCM_FORMAT_S16_LE; + } data->card.dev = &pdev->dev; ret = snd_soc_of_parse_card_name(&data->card, "model"); if (ret) - goto clk_fail; + goto fail; ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing"); if (ret) - goto clk_fail; - data->card.num_links = 1; + goto fail; data->card.owner = THIS_MODULE; - data->card.dai_link = &data->dai; + data->card.dai_link = data->dai; data->card.dapm_widgets = imx_wm8962_dapm_widgets; data->card.num_dapm_widgets = ARRAY_SIZE(imx_wm8962_dapm_widgets); data->card.late_probe = imx_wm8962_late_probe; - data->card.set_bias_level = imx_wm8962_set_bias_level; +#ifdef CONFIG_SND_SOC_IMX_WM8962_ANDROID + data->card.set_bias_level = imx_wm8962_set_bias_level; +#endif platform_set_drvdata(pdev, &data->card); snd_soc_card_set_drvdata(&data->card, data); ret = devm_snd_soc_register_card(&pdev->dev, &data->card); if (ret) { dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); - goto clk_fail; + goto fail; } - of_node_put(ssi_np); - of_node_put(codec_np); + priv->snd_card = data->card.snd_card; + priv->headphone_kctl = snd_kctl_jack_new("Headphone", NULL); + ret = snd_ctl_add(data->card.snd_card, priv->headphone_kctl); + if (ret) + goto fail; - return 0; + imx_wm8962_gpio_init(&data->card); + + if (gpio_is_valid(priv->hp_gpio)) { + ret = driver_create_file(pdev->dev.driver, &driver_attr_headphone); + if (ret) { + dev_err(&pdev->dev, "create hp attr failed (%d)\n", ret); + goto fail_hp; + } + } -clk_fail: - clk_disable_unprepare(data->codec_clk); + if (gpio_is_valid(priv->mic_gpio)) { + ret = driver_create_file(pdev->dev.driver, &driver_attr_microphone); + if (ret) { + dev_err(&pdev->dev, "create mic attr failed (%d)\n", ret); + goto fail_mic; + } + } + + goto fail; + +fail_mic: + driver_remove_file(pdev->dev.driver, &driver_attr_headphone); +fail_hp: fail: - of_node_put(ssi_np); + of_node_put(cpu_np); of_node_put(codec_np); return ret; @@ -296,11 +815,8 @@ fail: static int imx_wm8962_remove(struct platform_device *pdev) { - struct snd_soc_card *card = platform_get_drvdata(pdev); - struct imx_wm8962_data *data = snd_soc_card_get_drvdata(card); - - if (!IS_ERR(data->codec_clk)) - clk_disable_unprepare(data->codec_clk); + driver_remove_file(pdev->dev.driver, &driver_attr_microphone); + driver_remove_file(pdev->dev.driver, &driver_attr_headphone); return 0; } diff --git a/sound/soc/fsl/imx-xtor.c b/sound/soc/fsl/imx-xtor.c new file mode 100644 index 000000000000..39e3a8e87fb2 --- /dev/null +++ b/sound/soc/fsl/imx-xtor.c @@ -0,0 +1,318 @@ +/* + * Copyright (C) 2015-2016 Freescale Semiconductor, Inc. + * Copyright 2017 NXP + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/i2c.h> +#include <linux/of_gpio.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/clk.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <sound/control.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <linux/pinctrl/consumer.h> +#include "fsl_sai.h" +#include "fsl_esai.h" + +#define RX 0 +#define TX 1 + +/** + * CPU private data + * + * @sysclk_id[2]: SYSCLK ids for set_sysclk() + * @slots: number of slots supported by DAI + * + * Note: [0] for rx and [1] for tx + */ +struct cpu_priv { + u32 sysclk_id[2]; + u32 slots; +}; + +struct imx_xtor_data { + struct snd_soc_dai_link dai[3]; + struct snd_soc_card card; + struct cpu_priv cpu_priv; + bool is_stream_opened[2]; + struct platform_device *asrc_pdev; + u32 asrc_rate; + u32 asrc_format; +}; + +static int imx_xtor_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + static struct snd_pcm_hw_constraint_list constraint_rates; + int ret; + static u32 support_rates[] = { 8000, 32000, 48000, 96000, 192000, }; + + constraint_rates.list = support_rates; + constraint_rates.count = ARRAY_SIZE(support_rates); + + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraint_rates); + if (ret) + return ret; + + return 0; +} + +static int imx_xtor_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct imx_xtor_data *data = snd_soc_card_get_drvdata(rtd->card); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct cpu_priv *cpu_priv = &data->cpu_priv; + struct device *dev = rtd->card->dev; + u32 channels = params_channels(params); + unsigned int fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF; + int ret, dir; + + /* For playback the XTOR is slave, and for record is master */ + fmt |= tx ? SND_SOC_DAIFMT_CBS_CFS : SND_SOC_DAIFMT_CBM_CFM; + dir = tx ? SND_SOC_CLOCK_OUT : SND_SOC_CLOCK_IN; + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(rtd->cpu_dai, fmt); + if (ret) { + dev_err(dev, "failed to set cpu dai fmt: %d\n", ret); + return ret; + } + + /* Specific configurations of DAIs starts from here */ + ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, cpu_priv->sysclk_id[tx], + 0, dir); + if (ret) { + dev_err(dev, "failed to set cpu sysclk: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_tdm_slot(rtd->cpu_dai, BIT(channels) - 1, + BIT(channels) - 1, cpu_priv->slots, params_width(params)); + if (ret) { + dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret); + return ret; + } + + return 0; +} + +static int imx_xtor_hw_free(struct snd_pcm_substream *substream) +{ + return 0; +} + +static void imx_xtor_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct imx_xtor_data *data = snd_soc_card_get_drvdata(card); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + + data->is_stream_opened[tx] = false; +} + +static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_card *card = rtd->card; + struct imx_xtor_data *data = snd_soc_card_get_drvdata(card); + struct snd_interval *rate; + struct snd_mask *mask; + + if (!data->asrc_pdev) + return -EINVAL; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + rate->max = rate->min = data->asrc_rate; + + mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + snd_mask_none(mask); + snd_mask_set(mask, data->asrc_format); + + return 0; +} + +static const struct snd_soc_dapm_route audio_map[] = { + {"Playback", NULL, "CPU-Playback"}, + {"CPU-Capture", NULL, "Capture"}, + {"CPU-Playback", NULL, "ASRC-Playback"}, + {"ASRC-Capture", NULL, "CPU-Capture"}, +}; + +static struct snd_soc_ops imx_xtor_ops = { + .startup = imx_xtor_startup, + .shutdown = imx_xtor_shutdown, + .hw_params = imx_xtor_hw_params, + .hw_free = imx_xtor_hw_free, +}; + +static struct snd_soc_ops imx_xtor_be_ops = { + .hw_params = imx_xtor_hw_params, + .hw_free = imx_xtor_hw_free, +}; + +static int imx_xtor_probe(struct platform_device *pdev) +{ + struct device_node *cpu_np, *xtor_np = NULL; + struct device_node *asrc_np = NULL; + struct platform_device *asrc_pdev = NULL; + struct platform_device *cpu_pdev; + struct imx_xtor_data *data; + int ret; + u32 width; + + cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto fail; + } + + asrc_np = of_parse_phandle(pdev->dev.of_node, "asrc-controller", 0); + if (asrc_np) { + asrc_pdev = of_find_device_by_node(asrc_np); + data->asrc_pdev = asrc_pdev; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find SAI platform device\n"); + ret = -EINVAL; + goto fail; + } + + if (strstr(cpu_np->name, "esai")) { + data->cpu_priv.sysclk_id[TX] = ESAI_HCKT_EXTAL; + data->cpu_priv.sysclk_id[RX] = ESAI_HCKR_EXTAL; + } else if (strstr(cpu_np->name, "sai")) { + data->cpu_priv.sysclk_id[TX] = FSL_SAI_CLK_MAST1; + data->cpu_priv.sysclk_id[RX] = FSL_SAI_CLK_MAST1; + } + data->cpu_priv.slots = 2; + + data->dai[0].name = "xtor hifi"; + data->dai[0].stream_name = "xtor hifi"; + data->dai[0].codec_dai_name = "snd-soc-dummy-dai"; + data->dai[0].codec_name = "snd-soc-dummy"; + data->dai[0].cpu_dai_name = dev_name(&cpu_pdev->dev); + data->dai[0].platform_of_node = cpu_np; + data->dai[0].ops = &imx_xtor_ops; + data->dai[0].playback_only = false; + data->dai[0].capture_only = false; + data->card.num_links = 1; + data->card.dai_link = data->dai; + data->card.dapm_routes = audio_map; + data->card.num_dapm_routes = 2; + + /*if there is no asrc controller, we only enable one device*/ + if (asrc_pdev) { + data->dai[1].name = "HiFi-ASRC-FE"; + data->dai[1].stream_name = "HiFi-ASRC-FE"; + data->dai[1].codec_dai_name = "snd-soc-dummy-dai"; + data->dai[1].codec_name = "snd-soc-dummy"; + data->dai[1].cpu_of_node = asrc_np; + data->dai[1].platform_of_node = asrc_np; + data->dai[1].dynamic = 1; + data->dai[1].dpcm_playback = 1; + data->dai[1].dpcm_capture = 1; + + data->dai[2].name = "HiFi-ASRC-BE"; + data->dai[2].stream_name = "HiFi-ASRC-BE"; + data->dai[2].codec_dai_name = "snd-soc-dummy-dai"; + data->dai[2].codec_name = "snd-soc-dummy"; + data->dai[2].cpu_of_node = cpu_np; + data->dai[2].platform_name = "snd-soc-dummy"; + data->dai[2].no_pcm = 1; + data->dai[2].dpcm_playback = 1; + data->dai[2].dpcm_capture = 1; + data->dai[2].ops = &imx_xtor_be_ops, + data->dai[2].be_hw_params_fixup = be_hw_params_fixup, + data->card.num_links = 3; + data->card.dai_link = &data->dai[0]; + data->card.num_dapm_routes += 2; + + ret = of_property_read_u32(asrc_np, "fsl,asrc-rate", + &data->asrc_rate); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + ret = -EINVAL; + goto fail; + } + + ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + ret = -EINVAL; + goto fail; + } + + if (width == 24) + data->asrc_format = SNDRV_PCM_FORMAT_S24_LE; + else + data->asrc_format = SNDRV_PCM_FORMAT_S16_LE; + } + + data->card.dev = &pdev->dev; + data->card.owner = THIS_MODULE; + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) + goto fail; + + platform_set_drvdata(pdev, &data->card); + snd_soc_card_set_drvdata(&data->card, data); + ret = devm_snd_soc_register_card(&pdev->dev, &data->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + +fail: + if (cpu_np) + of_node_put(cpu_np); + if (xtor_np) + of_node_put(xtor_np); + return ret; +} + +static const struct of_device_id imx_xtor_dt_ids[] = { + { .compatible = "fsl,imx-audio-xtor", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_xtor_dt_ids); + +static struct platform_driver imx_xtor_driver = { + .driver = { + .name = "imx-xtor", + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + .of_match_table = imx_xtor_dt_ids, + }, + .probe = imx_xtor_probe, +}; +module_platform_driver(imx_xtor_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Freescale i.MX Dummy audio ASoC machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-xtor"); diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index b67d105b76e4..c1d41d048dba 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -1267,7 +1267,7 @@ static struct snd_soc_pcm_runtime *dpcm_get_be(struct snd_soc_card *card, } } - dev_err(card->dev, "ASoC: can't get %s BE for %s\n", + dev_dbg(card->dev, "ASoC: can't get %s BE for %s\n", stream ? "capture" : "playback", widget->name); return NULL; } @@ -1418,7 +1418,7 @@ static int dpcm_add_paths(struct snd_soc_pcm_runtime *fe, int stream, /* is there a valid BE rtd for this widget */ be = dpcm_get_be(card, list->widgets[i], stream); if (!be) { - dev_err(fe->dev, "ASoC: no BE found for %s\n", + dev_dbg(fe->dev, "ASoC: no BE found for %s\n", list->widgets[i]->name); continue; } @@ -1645,6 +1645,43 @@ static u64 dpcm_runtime_base_format(struct snd_pcm_substream *substream) return formats; } +static void dpcm_runtime_base_chan(struct snd_pcm_substream *substream, + unsigned int *channels_min, + unsigned int *channels_max) +{ + struct snd_soc_pcm_runtime *fe = substream->private_data; + struct snd_soc_dpcm *dpcm; + int stream = substream->stream; + *channels_min = 0; + *channels_max = UINT_MAX; + + if (!fe->dai_link->dpcm_merged_chan) + return; + + /* + * It returns merged BE codec channel; + * if FE want to use it (= dpcm_merged_chan) + */ + + list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { + struct snd_soc_pcm_runtime *be = dpcm->be; + struct snd_soc_dai_driver *codec_dai_drv; + struct snd_soc_pcm_stream *codec_stream; + int i; + + for (i = 0; i < be->num_codecs; i++) { + codec_dai_drv = be->codec_dais[i]->driver; + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + codec_stream = &codec_dai_drv->playback; + else + codec_stream = &codec_dai_drv->capture; + + *channels_min = max(*channels_min, codec_stream->channels_min); + *channels_max = min(*channels_max, codec_stream->channels_max); + } + } +} + static void dpcm_set_fe_runtime(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; @@ -1652,11 +1689,17 @@ static void dpcm_set_fe_runtime(struct snd_pcm_substream *substream) struct snd_soc_dai *cpu_dai = rtd->cpu_dai; struct snd_soc_dai_driver *cpu_dai_drv = cpu_dai->driver; u64 format = dpcm_runtime_base_format(substream); + unsigned int channels_min = 0, channels_max = UINT_MAX; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) dpcm_init_runtime_hw(runtime, &cpu_dai_drv->playback, format); else dpcm_init_runtime_hw(runtime, &cpu_dai_drv->capture, format); + + dpcm_runtime_base_chan(substream, &channels_min, &channels_max); + + runtime->hw.channels_min = max(channels_min, runtime->hw.channels_min); + runtime->hw.channels_max = min(channels_max, runtime->hw.channels_max); } static int dpcm_fe_dai_do_trigger(struct snd_pcm_substream *substream, int cmd); |