From patchwork Wed May 24 10:35:47 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Julian Scheel X-Patchwork-Id: 9745745 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 9F3F7601C2 for ; Wed, 24 May 2017 10:35:58 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 9C43A2892C for ; Wed, 24 May 2017 10:35:58 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 8DCCF2894A; Wed, 24 May 2017 10:35:58 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-1.8 required=2.0 tests=BAYES_00,DKIM_SIGNED, RCVD_IN_DNSWL_NONE,T_DKIM_INVALID autolearn=no version=3.3.1 Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id BCCB72892C for ; Wed, 24 May 2017 10:35:54 +0000 (UTC) Received: from alsa0.perex.cz (localhost [127.0.0.1]) by alsa0.perex.cz (Postfix) with ESMTP id 7B4EA26734B; Wed, 24 May 2017 12:35:53 +0200 (CEST) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id 519B126734C; Wed, 24 May 2017 12:35:52 +0200 (CEST) Received: from web01.jusst.de (jusst.de [188.40.114.84]) by alsa0.perex.cz (Postfix) with ESMTP id 21C7B267333 for ; Wed, 24 May 2017 12:35:48 +0200 (CEST) Received: from localhost (localhost [127.0.0.1]) by web01.jusst.de (Postfix) with ESMTP id BD2C11B01E82; Wed, 24 May 2017 12:35:48 +0200 (CEST) Authentication-Results: web01.jusst.de (amavisd-new); dkim=pass (1024-bit key) reason="pass (just generated, assumed good)" header.d=jusst.de DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=jusst.de; h= x-mailer:message-id:date:date:subject:subject:from:from; s= default; t=1495622147; x=1497436548; bh=lidf5Pm9i2wzquP83ivhy+f5 xSOx4vvR+5L71Oyx9V8=; b=89ommfT3ZbaGeIlIYScsy2EyImkYr4Ut2az39I82 1y3ekF08ltIJKdUfMhDGFyq8yvS0mNPOnj7TlsXyU8f3gtZ7hixlFr9O/oYSV08d szehIL68NC4hvjgvSOcu4pP/X9L/WnE2+rBFW5+7TQVfdWv8FzCOri3W3CUgjvhE Ht8= X-Virus-Scanned: Debian amavisd-new at web01.jusst.de Received: from web01.jusst.de ([127.0.0.1]) by localhost (web01.jusst.de [127.0.0.1]) (amavisd-new, port 10026) with ESMTP id yN75wjVnntB3; Wed, 24 May 2017 12:35:47 +0200 (CEST) Received: from julian-macbook.net.jusst.de (unknown [87.253.171.135]) (Authenticated sender: julian@jusst.de) by web01.jusst.de (Postfix) with ESMTPSA id B52301B01E7E; Wed, 24 May 2017 12:35:46 +0200 (CEST) From: Julian Scheel To: alsa-devel@alsa-project.org, devicetree@vger.kernel.org, broonie@kernel.org, lars@metafoo.de, ckeepax@opensource.wolfsonmicro.com, quozl@laptop.org Date: Wed, 24 May 2017 12:35:47 +0200 Message-Id: <20170524103547.9252-1-julian@jusst.de> X-Mailer: git-send-email 2.12.2 Cc: Julian Scheel Subject: [alsa-devel] [PATCHv2] ASoC: Add pcm9211 driver X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP This adds a driver for the TI PCM9211 digital audio interface transceiver. The driver currently only handles the receiver aspect of the chip. Some extra device-tree fields are introduced to allow configuration of pin functions directly out of this driver. Signed-off-by: Julian Scheel --- Changes in v2: - Fix checkpatch errors (long lines, * association) - Fix typos in device-tree documentation - Fix group-function dt. Mask was applied on unshifted data - Fix error check for int0 gpio lookup - Fix ADC rate list (92->96kHz) - Add support for AUXOUT as second DAI .../devicetree/bindings/sound/pcm9211.txt | 119 ++ MAINTAINERS | 6 + include/dt-bindings/sound/pcm9211.h | 55 + sound/soc/codecs/Kconfig | 10 + sound/soc/codecs/Makefile | 4 + sound/soc/codecs/pcm9211-i2c.c | 65 + sound/soc/codecs/pcm9211.c | 1446 ++++++++++++++++++++ sound/soc/codecs/pcm9211.h | 206 +++ 8 files changed, 1911 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/pcm9211.txt create mode 100644 include/dt-bindings/sound/pcm9211.h create mode 100644 sound/soc/codecs/pcm9211-i2c.c create mode 100644 sound/soc/codecs/pcm9211.c create mode 100644 sound/soc/codecs/pcm9211.h diff --git a/Documentation/devicetree/bindings/sound/pcm9211.txt b/Documentation/devicetree/bindings/sound/pcm9211.txt new file mode 100644 index 000000000000..df3974368125 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/pcm9211.txt @@ -0,0 +1,119 @@ +PCM9211 audio CODEC + +This device supports both I2C and SPI (configured with pin strapping +on the board). The driver is currently implementing i2c only. + +Required properties: + + - compatible : "ti,pcm9211" + + - reg : the I2C address of the device for I2C, the chip select + number for SPI. + + - VCCAD-supply, VCC-supply, VDDRX-supply and AVDD-supply : + power supplies for the device, as covered in + bindings/regulator/regulator.txt + +Optional properties: + + - clocks : A clock specifier for the clock connected to XTI. While the + device could work without it, this driver currently relies on it being + available + + - clock-names : Must specify "xti" to match the requested clock. + + - reset-gpios : GPIO wired to the reset pin, if not supplied a soft-reset is + used as fallback + + - int0-gpios : GPIO connected to interrupt0 output of the PCM9211. The + line can be configured to any of MPO0,1 and MPIOA,B,C0-4 and error/int0. + If this is specified it is used as an interrupt to notify alsa controls + about changes of the incoming sampling rate on DIR (spdif) as well as NPCM + status. + + - ti,group-function: An array with size 3 specifying function for pingroups + A, B and C. Possible values are defined in dt-bindings/sound/pcm9211.h + + - ti,mpio-a-flags-gpio: An array with size 4 specifying flags out or gpio + mode per pin, when function DIR_FLAGS_GPIO is selected for group a. + Possible values are defined in dt-bindings/sound/pcm9211.h + + - ti,mpio-b-flags-gpio: An array with size 4 specifying flags out or gpio + mode per pin, when function DIR_FLAGS_GPIO is selected for group b. + Possible values are defined in dt-bindings/sound/pcm9211.h + + - ti,mpio-c-flags-gpio: An array with size 4 specifying flags out or gpio + mode per pin, when function DIR_FLAGS_GPIO is selected for group c. + Possible values are defined in dt-bindings/sound/pcm9211.h + + - ti,mpio-a-flag: An array with size 4 specifying flag assigned per pin, + when function DIR_FLAGS_GPIO is selected for group a and pin is set to + flags mode. + Possible values are defined in dt-bindings/sound/pcm9211.h + + - ti,mpio-b-flag: An array with size 4 specifying flag assigned per pin, + when function DIR_FLAGS_GPIO is selected for group b and pin is set to + flags mode. + Possible values are defined in dt-bindings/sound/pcm9211.h + + - ti,mpio-c-flag: An array with size 4 specifying flag assigned per pin, + when function DIR_FLAGS_GPIO is selected for group c and pin is set to + flags mode. + Possible values are defined in dt-bindings/sound/pcm9211.h + + - ti,mpo-function: An array with size 2 specifying flag assigned per mpo + pin. + Possible values are defined in dt-bindings/sound/pcm9211.h + + - ti,int0-function: Selects error/int0 pin function. + Possible values are defined in dt-bindings/sound/pcm9211.h + + - ti,int1-function: Selects error/int1 pin function. + Possible values are defined in dt-bindings/sound/pcm9211.h + +Examples: + + pcm9211: pcm9211@43 { + compatible = "ti,pcm9211"; + reg = <0x43>; + + VCCAD-supply = <®_5v0_analog>; + VCC-supply = <®_3v3_pll_analog>; + VDDRX-supply = <®_3v3>; + DVDD-supply = <®_3v3>; + + clocks = <&xti_clk>; + clock-names = "xti"; + }; + + + pcm9211: pcm9211@43 { + compatible = "ti,pcm9211"; + reg = <0x43>; + + VCCAD-supply = <®_5v0_analog>; + VCC-supply = <®_3v3_pll_analog>; + VDDRX-supply = <®_3v3>; + DVDD-supply = <®_3v3>; + + clocks = <&xti_clk>; + clock-names = "xti"; + + reset-gpios = <&gpio1 0 GPIO_ACTIVE_HIGH>; + int-gpios = <&gpio1 0 GPIO_ACTIVE_HIGH>; + + ti,group-function = /bits/ 8 + ; + ti,mpio-b-flags-gpio = /bits/ 8 + ; + ti,mpio-b-flag = /bits/ 8 + ; + }; diff --git a/MAINTAINERS b/MAINTAINERS index c2ffc96ccf4c..1f3b2e98dd9d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9985,6 +9985,12 @@ S: Supported F: Documentation/devicetree/bindings/pci/pci-thunder-* F: drivers/pci/host/pci-thunder-* +PCM9211 AUDIO CODEC DRIVER +M: Julian Scheel +L: alsa-devel@alsa-project.org (moderated for non-subscribers) +S: Maintained +F: sound/soc/codecs/pcm9211* + PCMCIA SUBSYSTEM P: Linux PCMCIA Team L: linux-pcmcia@lists.infradead.org diff --git a/include/dt-bindings/sound/pcm9211.h b/include/dt-bindings/sound/pcm9211.h new file mode 100644 index 000000000000..6d1367f21d25 --- /dev/null +++ b/include/dt-bindings/sound/pcm9211.h @@ -0,0 +1,55 @@ +#ifndef __DT_PCM9211_H +#define __DT_PCM9211_H + +/* INT0/ERROR select */ +#define PCM9211_ERROR_INT0_ERROR 0 +#define PCM9211_ERROR_INT0_INT0 1 + +/* INT1/NPCM select */ +#define PCM9211_NPCM_INT1_NPCM 0 +#define PCM9211_NPCM_INT1_INT1 1 + +/* MPIO group functions */ +#define PCM9211_MPIO_A_GROUP_BIPHASE_INPUT 0 +#define PCM9211_MPIO_A_GROUP_CLKST_VOUT_XMCK_INT0 1 +#define PCM9211_MPIO_A_GROUP_SEC_BCK_LRCK_XMCKO_INT0 2 +#define PCM9211_MPIO_A_GROUP_DIR_FLAGS_GPIO 3 + +#define PCM9211_MPIO_B_GROUP_AUXIN2 0 +#define PCM9211_MPIO_B_GROUP_AUXOUT 1 +#define PCM9211_MPIO_B_GROUP_SAMPLING_FREQ_RES 2 +#define PCM9211_MPIO_B_GROUP_DIR_FLAGS_GPIO 3 +#define PCM9211_MPIO_B_GROUP_DIR_BCUV_BFRAME_VUC 4 +#define PCM9211_MPIO_B_GROUP_EXT_ADC_IN 5 +#define PCM9211_MPIO_B_GROUP_TEST_MODE 7 + +#define PCM9211_MPIO_C_GROUP_AUXIN1 0 +#define PCM9211_MPIO_C_GROUP_ADC_CLOCK_DATA 1 +#define PCM9211_MPIO_C_GROUP_SAMPLING_FREQ_RES 2 +#define PCM9211_MPIO_C_GROUP_DIR_FLAGS_GPIO 3 +#define PCM9211_MPIO_C_GROUP_DIR_BCUV_BFRAME_VUC 4 +#define PCM9211_MPIO_C_GROUP_DIT_CLOCK_DATA 5 + +/* MPIO flags/gpio select */ +#define PCM9211_MPIO_DIR_FLAGS 0 +#define PCM9211_MPIO_GPIO 1 + +/* MPIO flags */ +#define PCM9211_MPIO_FLAG_CLKST 0 +#define PCM9211_MPIO_FLAG_EMPH 1 +#define PCM9211_MPIO_FLAG_BPSYNC 2 +#define PCM9211_MPIO_FLAG_DTSCD 3 +#define PCM9211_MPIO_FLAG_PARITY 4 +#define PCM9211_MPIO_FLAG_LOCK 5 +#define PCM9211_MPIO_FLAG_VOUT 6 +#define PCM9211_MPIO_FLAG_UOUT 7 +#define PCM9211_MPIO_FLAG_COUT 8 +#define PCM9211_MPIO_FLAG_BFRAME 9 +#define PCM9211_MPIO_FLAG_FSOUT0 10 +#define PCM9211_MPIO_FLAG_FSOUT1 11 +#define PCM9211_MPIO_FLAG_FSOUT2 12 +#define PCM9211_MPIO_FLAG_FSOUT3 13 +#define PCM9211_MPIO_FLAG_INT0 14 +#define PCM9211_MPIO_FLAG_INT1 15 + +#endif diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 883ed4c8a551..d7cf85ba020f 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -113,6 +113,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_PCM5102A select SND_SOC_PCM512x_I2C if I2C select SND_SOC_PCM512x_SPI if SPI_MASTER + select SND_SOC_PCM9211_I2C if I2C select SND_SOC_RT286 if I2C select SND_SOC_RT298 if I2C select SND_SOC_RT5514 if I2C @@ -684,6 +685,15 @@ config SND_SOC_PCM512x_SPI select SND_SOC_PCM512x select REGMAP_SPI +config SND_SOC_PCM9211 + tristate + +config SND_SOC_PCM9211_I2C + tristate "Texas Instruments PCM9211 CODEC - I2C" + depends on I2C + select SND_SOC_PCM9211 + select REGMAP_I2C + config SND_SOC_RL6231 tristate default y if SND_SOC_RT5514=y diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 28a63fdaf982..a8eecee5fcfc 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -110,6 +110,8 @@ snd-soc-pcm5102a-objs := pcm5102a.o snd-soc-pcm512x-objs := pcm512x.o snd-soc-pcm512x-i2c-objs := pcm512x-i2c.o snd-soc-pcm512x-spi-objs := pcm512x-spi.o +snd-soc-pcm9211-objs := pcm9211.o +snd-soc-pcm9211-i2c-objs := pcm9211-i2c.o snd-soc-rl6231-objs := rl6231.o snd-soc-rl6347a-objs := rl6347a.o snd-soc-rt286-objs := rt286.o @@ -344,6 +346,8 @@ obj-$(CONFIG_SND_SOC_PCM5102A) += snd-soc-pcm5102a.o obj-$(CONFIG_SND_SOC_PCM512x) += snd-soc-pcm512x.o obj-$(CONFIG_SND_SOC_PCM512x_I2C) += snd-soc-pcm512x-i2c.o obj-$(CONFIG_SND_SOC_PCM512x_SPI) += snd-soc-pcm512x-spi.o +obj-$(CONFIG_SND_SOC_PCM9211) += snd-soc-pcm9211.o +obj-$(CONFIG_SND_SOC_PCM9211_I2C) += snd-soc-pcm9211-i2c.o obj-$(CONFIG_SND_SOC_RL6231) += snd-soc-rl6231.o obj-$(CONFIG_SND_SOC_RL6347A) += snd-soc-rl6347a.o obj-$(CONFIG_SND_SOC_RT286) += snd-soc-rt286.o diff --git a/sound/soc/codecs/pcm9211-i2c.c b/sound/soc/codecs/pcm9211-i2c.c new file mode 100644 index 000000000000..e0678b280f11 --- /dev/null +++ b/sound/soc/codecs/pcm9211-i2c.c @@ -0,0 +1,65 @@ +/* + * PCM9211 codec i2c driver + * + * Copyright (C) 2017 jusst technologies GmbH / jusst.engineering + * + * Author: Julian Scheel + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + */ + +#include +#include + +#include + +#include "pcm9211.h" + +static int pcm9211_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct regmap *regmap; + + regmap = devm_regmap_init_i2c(i2c, &pcm9211_regmap); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return pcm9211_probe(&i2c->dev, regmap); +} + +static int pcm9211_i2c_remove(struct i2c_client *i2c) +{ + pcm9211_remove(&i2c->dev); + + return 0; +} + +static const struct i2c_device_id pcm9211_i2c_id[] = { + { "pcm9211", }, + { } +}; +MODULE_DEVICE_TABLE(i2c, pcm9211_i2c_id); + +static const struct of_device_id pcm9211_of_match[] = { + { .compatible = "ti,pcm9211", }, + { } +}; +MODULE_DEVICE_TABLE(of, pcm9211_of_match); + +static struct i2c_driver pcm9211_i2c_driver = { + .probe = pcm9211_i2c_probe, + .remove = pcm9211_i2c_remove, + .id_table = pcm9211_i2c_id, + .driver = { + .name = "pcm9211", + .of_match_table = pcm9211_of_match, + .pm = &pcm9211_pm_ops, + }, +}; +module_i2c_driver(pcm9211_i2c_driver); + +MODULE_DESCRIPTION("PCM9211 I2C codec driver"); +MODULE_AUTHOR("Julian Scheel "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/pcm9211.c b/sound/soc/codecs/pcm9211.c new file mode 100644 index 000000000000..9ccf3a09b24c --- /dev/null +++ b/sound/soc/codecs/pcm9211.c @@ -0,0 +1,1446 @@ +/* + * PCM9211 codec driver + * + * Copyright (C) 2017 jusst technologies GmbH / jusst.engineering + * + * Author; Julian Scheel + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "pcm9211.h" + +#define PCM9211_MAX_SYSCLK 24576000 +#define PCM9211_DAI_MAIN 0 +#define PCM9211_DAI_AUX 1 + +#define PCM9211_SUPPLIES 4 +static const char *const pcm9211_supply_names[PCM9211_SUPPLIES] = { + "VCCAD", + "VCC", + "VDDRX", + "DVDD", +}; + +struct pcm9211_priv { + struct regulator_bulk_data supplies[PCM9211_SUPPLIES]; + struct snd_pcm_hw_constraint_list rate_constraints; + struct delayed_work npcm_clear_work; + struct snd_kcontrol *preamble_ctl; + struct snd_kcontrol *npcm_ctl; + struct snd_kcontrol *rate_ctl; + struct snd_kcontrol *dts_ctl; + struct snd_soc_codec *codec; + struct gpio_desc *reset; + struct gpio_desc *int0; + struct regmap *regmap; + struct device *dev; + struct clk *xti; + + unsigned int dai_format; + unsigned int dir_rate; + unsigned int adc_rate; + unsigned long sysclk; + u8 burst_preamble[4]; + u8 npcm_state; +}; + +static const struct regmap_range pcm9211_reg_rd_range[] = { + regmap_reg_range(PCM9211_ERR_OUT, PCM9211_PD_BUF1), + regmap_reg_range(PCM9211_SYS_RESET, PCM9211_SYS_RESET), + regmap_reg_range(PCM9211_ADC_CTRL1, PCM9211_ADC_CTRL1), + regmap_reg_range(PCM9211_ADC_L_CH_ATT, PCM9211_ADC_CTRL3), + regmap_reg_range(PCM9211_DIR_STATUS1, PCM9211_DIT_STATUS6), + regmap_reg_range(PCM9211_MAIN_AUX_MUTE, PCM9211_MPIO_C_DATA_IN), +}; + +static const struct regmap_access_table pcm9211_reg_rd_table = { + .yes_ranges = pcm9211_reg_rd_range, + .n_yes_ranges = ARRAY_SIZE(pcm9211_reg_rd_range), +}; + +static const struct regmap_range pcm9211_reg_wr_range[] = { + regmap_reg_range(PCM9211_ERR_OUT, PCM9211_INT1_CAUSE), + regmap_reg_range(PCM9211_INT_POLARITY, PCM9211_FS_CALC_TARGET), + regmap_reg_range(PCM9211_SYS_RESET, PCM9211_SYS_RESET), + regmap_reg_range(PCM9211_ADC_CTRL1, PCM9211_ADC_CTRL1), + regmap_reg_range(PCM9211_ADC_L_CH_ATT, PCM9211_ADC_CTRL3), + regmap_reg_range(PCM9211_DIT_CTRL1, PCM9211_DIT_STATUS6), + regmap_reg_range(PCM9211_MAIN_AUX_MUTE, PCM9211_MPIO_C_DATA_OUT), +}; + +static const struct regmap_access_table pcm9211_reg_wr_table = { + .yes_ranges = pcm9211_reg_wr_range, + .n_yes_ranges = ARRAY_SIZE(pcm9211_reg_wr_range), +}; + +static const struct regmap_range pcm9211_reg_volatile_range[] = { + regmap_reg_range(PCM9211_INT0_OUT, PCM9211_INT1_OUT), + regmap_reg_range(PCM9211_BIPHASE_INFO, PCM9211_PD_BUF1), + regmap_reg_range(PCM9211_DIR_STATUS1, PCM9211_DIR_STATUS6), +}; + +static const struct regmap_access_table pcm9211_reg_volatile_table = { + .yes_ranges = pcm9211_reg_volatile_range, + .n_yes_ranges = ARRAY_SIZE(pcm9211_reg_volatile_range), +}; + +static const struct reg_default pcm9211_reg_defaults[] = { + { PCM9211_ERR_OUT, 0x00 }, + { PCM9211_DIR_INITIAL1, 0x00 }, + { PCM9211_DIR_INITIAL2, 0x01 }, + { PCM9211_DIR_INITIAL3, 0x04 }, + { PCM9211_OSC_CTRL, 0x00 }, + { PCM9211_ERR_CAUSE, 0x01 }, + { PCM9211_AUTO_SEL_CAUSE, 0x01 }, + { PCM9211_DIR_FS_RANGE, 0x00 }, + { PCM9211_NON_PCM_DEF, 0x03 }, + { PCM9211_DTS_CD_LD, 0x0c }, + { PCM9211_INT0_CAUSE, 0xff }, + { PCM9211_INT1_CAUSE, 0xff }, + { PCM9211_INT0_OUT, 0x00 }, + { PCM9211_INT1_OUT, 0x00 }, + { PCM9211_INT_POLARITY, 0x00 }, + { PCM9211_DIR_OUT_FMT, 0x04 }, + { PCM9211_DIR_RSCLK_RATIO, 0x02 }, + { PCM9211_XTI_SCLK_FREQ, 0x1a }, + { PCM9211_DIR_SOURCE_BIT2, 0x22 }, + { PCM9211_XTI_SOURCE_BIT2, 0x22 }, + { PCM9211_DIR_INP_BIPHASE, 0xc2 }, + { PCM9211_RECOUT0_BIPHASE, 0x02 }, + { PCM9211_RECOUT1_BIPHASE, 0x02 }, + { PCM9211_FS_CALC_TARGET, 0x00 }, + { PCM9211_FS_CALC_RESULT, 0x08 }, + { PCM9211_BIPHASE_INFO, 0x08 }, + { PCM9211_PC_BUF0, 0x01 }, + { PCM9211_PC_BUF1, 0x00 }, + { PCM9211_PD_BUF0, 0x20 }, + { PCM9211_PD_BUF1, 0x57 }, + { PCM9211_SYS_RESET, 0x40 }, + { PCM9211_ADC_CTRL1, 0x02 }, + { PCM9211_ADC_L_CH_ATT, 0xd7 }, + { PCM9211_ADC_R_CH_ATT, 0xd7 }, + { PCM9211_ADC_CTRL2, 0x00 }, + { PCM9211_ADC_CTRL3, 0x00 }, + { PCM9211_DIR_STATUS1, 0x04 }, + { PCM9211_DIR_STATUS2, 0x00 }, + { PCM9211_DIR_STATUS3, 0x00 }, + { PCM9211_DIR_STATUS4, 0x00 }, + { PCM9211_DIR_STATUS5, 0x00 }, + { PCM9211_DIR_STATUS6, 0x00 }, + { PCM9211_DIT_CTRL1, 0x44 }, + { PCM9211_DIT_CTRL2, 0x10 }, + { PCM9211_DIT_CTRL3, 0x00 }, + { PCM9211_DIT_STATUS1, 0x00 }, + { PCM9211_DIT_STATUS2, 0x00 }, + { PCM9211_DIT_STATUS3, 0x00 }, + { PCM9211_DIT_STATUS4, 0x00 }, + { PCM9211_DIT_STATUS5, 0x00 }, + { PCM9211_DIT_STATUS6, 0x00 }, + { PCM9211_MAIN_AUX_MUTE, 0x00 }, + { PCM9211_MAIN_OUT_SOURCE, 0x00 }, + { PCM9211_AUX_OUT_SOURCE, 0x00 }, + { PCM9211_MPIO_B_MAIN_HIZ, 0x00 }, + { PCM9211_MPIO_C_MPIO_A_HIZ, 0x0f }, + { PCM9211_MPIO_GROUP, 0x40 }, + { PCM9211_MPIO_A_FLAGS, 0x00 }, + { PCM9211_MPIO_B_MPIO_C_FLAGS, 0x00 }, + { PCM9211_MPIO_A1_A0_OUT_FLAG, 0x00 }, + { PCM9211_MPIO_A3_A2_OUT_FLAG, 0x00 }, + { PCM9211_MPIO_B1_B0_OUT_FLAG, 0x00 }, + { PCM9211_MPIO_B3_B2_OUT_FLAG, 0x00 }, + { PCM9211_MPIO_C1_C0_OUT_FLAG, 0x00 }, + { PCM9211_MPIO_C3_C2_OUT_FLAG, 0x00 }, + { PCM9211_MPO_1_0_FUNC, 0x3d }, + { PCM9211_MPIO_A_B_DIR, 0x00 }, + { PCM9211_MPIO_C_DIR, 0x00 }, + { PCM9211_MPIO_A_B_DATA_OUT, 0x00 }, + { PCM9211_MPIO_C_DATA_OUT, 0x00 }, + { PCM9211_MPIO_A_B_DATA_IN, 0x00 }, + { PCM9211_MPIO_C_DATA_IN, 0x02 }, +}; + +const struct regmap_config pcm9211_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = PCM9211_MPIO_C_DATA_IN, + .wr_table = &pcm9211_reg_wr_table, + .rd_table = &pcm9211_reg_rd_table, + .volatile_table = &pcm9211_reg_volatile_table, + .reg_defaults = pcm9211_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(pcm9211_reg_defaults), + .cache_type = REGCACHE_RBTREE, +}; +EXPORT_SYMBOL_GPL(pcm9211_regmap); + +static const u32 adc_rates[] = { 48000, 96000 }; +static const struct snd_pcm_hw_constraint_list adc_rate_constraints = { + .count = ARRAY_SIZE(adc_rates), + .list = adc_rates, +}; + +static const int biphase_rates[] = { + 0, 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, + 48000, 64000, 88200, 96000, 128000, 176400, 192000 +}; + +static const unsigned int pcm9211_sck_ratios[] = { 1, 2, 4, 8 }; +static const unsigned int pcm9211_bck_ratios[] = { 2, 4, 8, 16 }; +static const unsigned int pcm9211_lrck_ratios[] = { 128, 256, 512, 1024 }; +#define PCM9211_NUM_SCK_RATIOS ARRAY_SIZE(pcm9211_sck_ratios) +#define PCM9211_NUM_BCK_RATIOS ARRAY_SIZE(pcm9211_bck_ratios) +#define PCM9211_NUM_LRCK_RATIOS ARRAY_SIZE(pcm9211_lrck_ratios) + +static int pcm9211_get_output_port(struct device *dev, int dai_id) +{ + struct pcm9211_priv *priv = dev_get_drvdata(dev); + unsigned int port; + unsigned int val; + int reg = 0; + int ret; + + switch (dai_id) { + case PCM9211_DAI_MAIN: + reg = PCM9211_MAIN_OUT_SOURCE; + break; + case PCM9211_DAI_AUX: + reg = PCM9211_AUX_OUT_SOURCE; + break; + default: + return -EINVAL; + } + + ret = regmap_read(priv->regmap, reg, &val); + if (ret) { + dev_err(dev, "Failed to read selected source: %d\n", ret); + return ret; + } + + port = (val & PCM9211_MOPSRC_MASK) >> PCM9211_MOPSRC_SHIFT; + if (port == PCM9211_MOSRC_AUTO) { + ret = regmap_read(priv->regmap, PCM9211_BIPHASE_INFO, &val); + if (ret) { + dev_err(dev, "Failed to read biphase information: %d\n", + ret); + return ret; + } + + /* Assumes that Sampling Frequency Status calculation + * corresponds with DIR Lock, which seems to to be exposed to + * any register directly + */ + if ((val & PCM9211_BIPHASE_SFSST_MASK) == 0) + port = PCM9211_MOSRC_DIR; + else + port = PCM9211_MOSRC_ADC; + } + + return port; +} + +static int pcm9211_dir_rate(struct device *dev) +{ + struct pcm9211_priv *priv = dev_get_drvdata(dev); + unsigned int val; + int ret; + + ret = regmap_read(priv->regmap, PCM9211_BIPHASE_INFO, &val); + if (ret) { + dev_err(dev, "Failed to read biphase information: %d\n", ret); + return ret; + } + + if ((val & PCM9211_BIPHASE_SFSST_MASK)) { + dev_dbg(dev, "Biphase Fs calculation not locked\n"); + return 0; + } + + return biphase_rates[(val & PCM9211_BIPHASE_SFSOUT_MASK) + >> PCM9211_BIPHASE_SFSOUT_SHIFT]; +} + +static int pcm9211_read_burst_preamble(struct device *dev) +{ + struct pcm9211_priv *priv = dev_get_drvdata(dev); + int ret; + + ret = regmap_raw_read(priv->regmap, PCM9211_PC_BUF0, + priv->burst_preamble, 4); + if (ret) { + dev_err(dev, "Failed to read burst preamble: %d\n", ret); + return ret; + } + + dev_dbg(dev, "Burst preamble: 0x%02x 0x%02x 0x%02x 0x%02x\n", + priv->burst_preamble[0], priv->burst_preamble[1], + priv->burst_preamble[2], priv->burst_preamble[3]); + + return 0; +} + +static int pcm9211_dir_rate_kctl_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 = 8000; + uinfo->value.integer.min = 96000; + + return 0; +} + +static int pcm9211_dir_rate_kctl(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct pcm9211_priv *priv = snd_soc_component_get_drvdata(component); + struct device *dev = priv->dev; + + /* If we have an interrupt connected dir_rate is up-to-date */ + if (!priv->int0) + priv->dir_rate = pcm9211_dir_rate(dev); + + ucontrol->value.integer.value[0] = priv->dir_rate; + + return 0; +} + +#define pcm9211_dir_npcm_kctl_info snd_ctl_boolean_mono_info +#define pcm9211_dir_npcm_kctl pcm9211_int0_kctl + +#define pcm9211_dir_dtscd_kctl_info snd_ctl_boolean_mono_info +#define pcm9211_dir_dtscd_kctl pcm9211_int0_kctl + +static int pcm9211_int0_kctl(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct pcm9211_priv *priv = snd_soc_component_get_drvdata(component); + u8 mask = kcontrol->private_value & 0xff; + struct device *dev = priv->dev; + unsigned int cause; + int ret; + + /* If interrupt line is not connected read the last interrupt state */ + if (!priv->int0) { + ret = regmap_read(priv->regmap, PCM9211_INT0_OUT, &cause); + if (ret) { + dev_err(dev, "Failed to read int0 cause: %d\n", ret); + return IRQ_HANDLED; + } + priv->npcm_state = cause & (PCM9211_INT0_MNPCM0_MASK | + PCM9211_INT0_MDTSCD0_MASK); + } + + ucontrol->value.integer.value[0] = (priv->npcm_state & mask) == mask; + + return 0; +} + +static int pcm9211_dir_preamble_kctl_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; + uinfo->count = 4; + + return 0; +} + +static int pcm9211_dir_preamble_kctl(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct pcm9211_priv *priv = snd_soc_component_get_drvdata(component); + struct device *dev = priv->dev; + + /* If we have an interrupt connected preamble is up-to-date */ + if (!priv->int0) + priv->dir_rate = pcm9211_read_burst_preamble(dev); + + memcpy(ucontrol->value.bytes.data, priv->burst_preamble, 4); + + return 0; +} + +static struct snd_kcontrol *pcm9211_get_ctl(struct device *dev, + const char *name) +{ + struct pcm9211_priv *priv = dev_get_drvdata(dev); + struct snd_ctl_elem_id elem_id; + + memset(&elem_id, 0, sizeof(elem_id)); + elem_id.iface = SNDRV_CTL_ELEM_IFACE_PCM; + strcpy(elem_id.name, name); + return snd_ctl_find_id(priv->codec->component.card->snd_card, &elem_id); +} + +static struct snd_kcontrol *pcm9211_get_rate_ctl(struct device *dev) +{ + struct pcm9211_priv *priv = dev_get_drvdata(dev); + + if (!priv->rate_ctl) + priv->rate_ctl = pcm9211_get_ctl(dev, "DIR Sample Rate"); + + return priv->rate_ctl; +} + +static struct snd_kcontrol *pcm9211_get_npcm_ctl(struct device *dev) +{ + struct pcm9211_priv *priv = dev_get_drvdata(dev); + + if (!priv->npcm_ctl) + priv->npcm_ctl = pcm9211_get_ctl(dev, "DIR Non-PCM Bitstream"); + + return priv->npcm_ctl; +} + +static struct snd_kcontrol *pcm9211_get_dtscd_ctl(struct device *dev) +{ + struct pcm9211_priv *priv = dev_get_drvdata(dev); + + if (!priv->dts_ctl) + priv->dts_ctl = pcm9211_get_ctl(dev, "DIR DTS Bitstream"); + + return priv->dts_ctl; +} + +static struct snd_kcontrol *pcm9211_get_burst_preamble_ctl(struct device *dev) +{ + struct pcm9211_priv *priv = dev_get_drvdata(dev); + + if (!priv->preamble_ctl) + priv->preamble_ctl = pcm9211_get_ctl(dev, "DIR Burst Preamble"); + + return priv->preamble_ctl; +} + +static irqreturn_t pcm9211_interrupt(int irq, void *data) +{ + struct pcm9211_priv *priv = data; + struct device *dev = priv->dev; + + unsigned int cause; + int rate; + int ret; + + ret = regmap_read(priv->regmap, PCM9211_INT0_OUT, &cause); + if (ret) { + dev_err(dev, "Failed to read int0 cause: %d\n", ret); + return IRQ_HANDLED; + } + + if (cause & PCM9211_INT0_MFSCHG0_MASK) { + /* Interrupt is generated before the Fs calculation has + * finished. Give it time to settle. + */ + usleep_range(15000, 16000); + rate = pcm9211_dir_rate(dev); + + if (rate < 0) { + dev_err(dev, "Failed to retrieve DIR rate: %d\n", rate); + goto preamble; + } + + if (rate == priv->dir_rate) + goto preamble; + + priv->dir_rate = rate; + dev_dbg(dev, "DIR sampling rate changed to: %d\n", rate); + + if (priv->codec == NULL || pcm9211_get_rate_ctl(dev) == NULL) + goto preamble; + + snd_ctl_notify(priv->codec->component.card->snd_card, + SNDRV_CTL_EVENT_MASK_VALUE, + &pcm9211_get_rate_ctl(dev)->id); + } + +preamble: + if ((cause & PCM9211_INT0_MPCRNW0_MASK)) { + if (pcm9211_read_burst_preamble(dev) < 0) + goto npcm; + + if (priv->codec == NULL || + pcm9211_get_burst_preamble_ctl(dev) == NULL) + goto dts; + + snd_ctl_notify(priv->codec->component.card->snd_card, + SNDRV_CTL_EVENT_MASK_VALUE, + &pcm9211_get_burst_preamble_ctl(dev)->id); + } + +npcm: + if ((cause & PCM9211_INT0_MNPCM0_MASK)) { + /* PCM9211 does not generate an interrupt for NPCM0 1->0 + * transition, but continuously generates interrupts as long as + * NPCM0 is high, so use a timeout to clear + */ + cancel_delayed_work_sync(&priv->npcm_clear_work); + queue_delayed_work(system_wq, &priv->npcm_clear_work, + msecs_to_jiffies(100)); + + + if ((cause & PCM9211_INT0_MNPCM0_MASK) != + (priv->npcm_state & PCM9211_INT0_MNPCM0_MASK)) + dev_dbg(dev, "NPCM status on interrupt: %d\n", + (cause & PCM9211_INT0_MNPCM0_MASK) == + PCM9211_INT0_MNPCM0_MASK); + + priv->npcm_state = (priv->npcm_state & + ~PCM9211_INT0_MNPCM0_MASK) | + (cause & PCM9211_INT0_MNPCM0_MASK); + + if (priv->codec == NULL || pcm9211_get_npcm_ctl(dev) == NULL) + goto dts; + + snd_ctl_notify(priv->codec->component.card->snd_card, + SNDRV_CTL_EVENT_MASK_VALUE, + &pcm9211_get_npcm_ctl(dev)->id); + } + +dts: + if (cause & PCM9211_INT0_MDTSCD0_MASK) { + dev_dbg(dev, "DTSCD status on interrupt: %d\n", + (cause & PCM9211_INT0_MDTSCD0_MASK) == + PCM9211_INT0_MDTSCD0_MASK); + priv->npcm_state |= PCM9211_INT0_MDTSCD0_MASK; + + if (priv->codec == NULL || pcm9211_get_dtscd_ctl(dev) == NULL) + return IRQ_HANDLED; + + snd_ctl_notify(priv->codec->component.card->snd_card, + SNDRV_CTL_EVENT_MASK_VALUE, + &pcm9211_get_dtscd_ctl(dev)->id); + } + + return IRQ_HANDLED; +} + +static int pcm9211_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct pcm9211_priv *priv = snd_soc_codec_get_drvdata(codec); + struct snd_soc_component *component = &codec->component; + struct device *dev = codec->dev; + struct snd_soc_dai *other_dai; + int port; + + dev_dbg(dev, "Startup on dai %d\n", dai->id); + port = pcm9211_get_output_port(dev, dai->id); + if (port < 0) { + dev_err(dev, "Failed to read selected port: %d\n", port); + return port; + } + + if (port == PCM9211_MOSRC_ADC) { + dev_dbg(dev, "ADC capture on dai %d\n", dai->id); + /* Check if other DAI uses ADC, if so limit available rates */ + list_for_each_entry(other_dai, &component->dai_list, list) { + if (!other_dai->capture_active) + continue; + + if (pcm9211_get_output_port(dev, other_dai->id) != port) + continue; + + priv->rate_constraints.count = 1; + priv->rate_constraints.list = &priv->adc_rate; + priv->rate_constraints.mask = 0; + + dev_dbg(dev, "Active ADC rate is %d Hz\n", + priv->adc_rate); + + return snd_pcm_hw_constraint_list(substream->runtime, + 0, SNDRV_PCM_HW_PARAM_RATE, + &priv->rate_constraints); + } + + return snd_pcm_hw_constraint_list(substream->runtime, + 0, SNDRV_PCM_HW_PARAM_RATE, + &adc_rate_constraints); + } + + priv->dir_rate = pcm9211_dir_rate(dev); + priv->rate_constraints.count = 1; + priv->rate_constraints.list = &priv->dir_rate; + priv->rate_constraints.mask = 0; + + dev_dbg(dev, "Detected biphase rate is %d Hz\n", priv->dir_rate); + + return snd_pcm_hw_constraint_list(substream->runtime, + 0, SNDRV_PCM_HW_PARAM_RATE, &priv->rate_constraints); +} + +static int pcm9211_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = dai->codec; + struct pcm9211_priv *priv = snd_soc_codec_get_drvdata(codec); + struct device *dev = codec->dev; + + int ret; + + if (freq > PCM9211_MAX_SYSCLK) { + dev_err(dev, "System clock greater %d is not supported\n", + PCM9211_MAX_SYSCLK); + return -EINVAL; + } + + ret = clk_set_rate(priv->xti, freq); + if (ret) + return ret; + + priv->sysclk = freq; + + return 0; +} + +static int pcm9211_set_dai_fmt(struct snd_soc_dai *dai, + unsigned int format) +{ + struct snd_soc_codec *codec = dai->codec; + struct pcm9211_priv *priv = snd_soc_codec_get_drvdata(codec); + struct device *dev = codec->dev; + u32 adfmt, dirfmt; + int ret; + + if (priv->dai_format != 0 && priv->dai_format != format) { + dev_err(dev, "Can not use different dai formats for dai links.\n"); + return -EINVAL; + } + + /* Configure format for ADC and DIR block, if main output source is + * set to AUTO the output port may switch between them at any time + */ + switch (format & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + adfmt = PCM9211_ADFMT_I2S; + dirfmt = PCM9211_DIR_FMT_I2S; + break; + case SND_SOC_DAIFMT_RIGHT_J: + adfmt = PCM9211_ADFMT_RIGHT_J; + dirfmt = PCM9211_DIR_FMT_RIGHT_J; + break; + case SND_SOC_DAIFMT_LEFT_J: + adfmt = PCM9211_ADFMT_LEFT_J; + dirfmt = PCM9211_DIR_FMT_LEFT_J; + break; + default: + dev_err(dev, "Unsupported DAI format\n"); + return -EINVAL; + } + + ret = regmap_update_bits(priv->regmap, PCM9211_ADC_CTRL2, + PCM9211_ADFMT_MASK, adfmt << PCM9211_ADFMT_SHIFT); + if (ret) { + dev_err(dev, "Failed to update ADC format: %d\n", ret); + return ret; + } + + ret = regmap_update_bits(priv->regmap, PCM9211_DIR_OUT_FMT, + PCM9211_DIR_FMT_MASK, dirfmt << PCM9211_DIR_FMT_SHIFT); + if (ret) { + dev_err(dev, "Failed to update ADC format: %d\n", ret); + return ret; + } + + priv->dai_format = format; + + return 0; +} + +static int pcm9211_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 pcm9211_priv *priv = snd_soc_codec_get_drvdata(codec); + struct device *dev = codec->dev; + unsigned int sclk = 1; + unsigned int rate; + unsigned int bck; + unsigned int ratio; + unsigned int port; + int ret; + int i; + + rate = params_rate(params); + bck = rate * 64; + + port = pcm9211_get_output_port(dev, dai->id); + if (port == PCM9211_MOSRC_ADC) { + switch (rate) { + case 48000: + sclk = 12288000; + break; + case 96000: + sclk = 24576000; + break; + default: + dev_err(dev, "Rate %d unsupported.\n", rate); + return -EINVAL; + } + + /* Systemclock setup */ + ratio = priv->sysclk / sclk; + for (i = 0; i < PCM9211_NUM_SCK_RATIOS; i++) { + if (pcm9211_sck_ratios[i] == ratio) + break; + } + if (i == PCM9211_NUM_SCK_RATIOS) { + dev_err(dev, "SCK divider %d is not supported\n", + ratio); + return -EINVAL; + } + ret = regmap_update_bits(priv->regmap, PCM9211_XTI_SCLK_FREQ, + PCM9211_XTI_XSCK_MASK, + i << PCM9211_XTI_XSCK_SHIFT); + + if (ret) { + dev_err(dev, "Failed to configure SCK divider: %d\n", + ret); + return ret; + } + + /* Bitclock setup */ + ratio = priv->sysclk / bck; + for (i = 0; i < PCM9211_NUM_BCK_RATIOS; i++) { + if (pcm9211_bck_ratios[i] == ratio) + break; + } + if (i == PCM9211_NUM_BCK_RATIOS) { + dev_err(dev, "BCK divider %d is not supported\n", + ratio); + return -EINVAL; + } + ret = regmap_update_bits(priv->regmap, PCM9211_XTI_SCLK_FREQ, + PCM9211_XTI_BCK_MASK, + i << PCM9211_XTI_BCK_SHIFT); + if (ret) { + dev_err(dev, "Failed to configure BCK divider: %d\n", + ret); + return ret; + } + + /* Frameclock setup */ + ratio = priv->sysclk / rate; + for (i = 0; i < PCM9211_NUM_LRCK_RATIOS; i++) { + if (pcm9211_lrck_ratios[i] == ratio) + break; + } + if (i == PCM9211_NUM_LRCK_RATIOS) { + dev_err(dev, "LRCK divider %d is not supported\n", + ratio); + return -EINVAL; + } + ret = regmap_update_bits(priv->regmap, PCM9211_XTI_SCLK_FREQ, + PCM9211_XTI_LRCK_MASK, + i << PCM9211_XTI_LRCK_SHIFT); + if (ret) { + dev_err(dev, "Failed to configure LRCK divider: %d\n", + ret); + return ret; + } + + priv->adc_rate = rate; + } + + return 0; +} + +static int pcm9211_reset(struct device *dev) +{ + struct pcm9211_priv *priv = dev_get_drvdata(dev); + int ret; + + /* Use reset gpio if available, otherwise soft-reset */ + if (priv->reset) { + gpiod_set_value_cansleep(priv->reset, 0); + usleep_range(500, 1000); + gpiod_set_value_cansleep(priv->reset, 1); + } else { + ret = regmap_update_bits(priv->regmap, PCM9211_SYS_RESET, + PCM9211_SYS_RESET_MRST, 0); + if (ret) { + dev_err(dev, "Could not reset device: %d\n", ret); + return ret; + } + usleep_range(10000, 15000); + } + + regcache_mark_dirty(priv->regmap); + + return 0; +} + +static int pcm9211_write_pinconfig(struct device *dev) +{ + struct pcm9211_priv *priv = dev_get_drvdata(dev); + u8 values[4]; + int val; + int ret; + int i; + + ret = of_property_read_u8_array(dev->of_node, "ti,group-function", + values, 3); + if (!ret) { + val = (values[0] << PCM9211_MPASEL_SHIFT & + PCM9211_MPASEL_MASK) | + (values[1] << PCM9211_MPBSEL_SHIFT & + PCM9211_MPBSEL_MASK) | + (values[2] << PCM9211_MPCSEL_SHIFT & + PCM9211_MPCSEL_MASK); + ret = regmap_write(priv->regmap, PCM9211_MPIO_GROUP, val); + if (ret) { + dev_err(dev, "Failed to write mpio group functions: %d\n", + ret); + return ret; + } + } + + ret = of_property_read_u8_array(dev->of_node, "ti,mpio-a-flags-gpio", + values, 4); + if (!ret) { + /* Write MPIO A flags/gpio selection */ + for (i = 0, val = 0; i < 4; i++) + val |= (values[i] << PCM9211_MPAxSEL_SHIFT(i)) & + PCM9211_MPAxSEL_MASK(i); + + ret = regmap_update_bits(priv->regmap, PCM9211_MPIO_A_FLAGS, + PCM9211_MPAxSEL_MASK(0) | + PCM9211_MPAxSEL_MASK(1) | + PCM9211_MPAxSEL_MASK(2), val); + if (ret) { + dev_err(dev, "Failed to update mpio_a flags: %d\n", + ret); + return ret; + } + } + + ret = of_property_read_u8_array(dev->of_node, "ti,mpio-b-flags-gpio", + values, 4); + if (!ret) { + /* Write MPIO B flags/gpio selection */ + for (i = 0, val = 0; i < 4; i++) + val |= (values[i] << PCM9211_MPBxSEL_SHIFT(i)) & + PCM9211_MPBxSEL_MASK(i); + + ret = regmap_update_bits(priv->regmap, + PCM9211_MPIO_B_MPIO_C_FLAGS, + PCM9211_MPBxSEL_MASK(0) | + PCM9211_MPBxSEL_MASK(1) | + PCM9211_MPBxSEL_MASK(2), val); + if (ret) { + dev_err(dev, "Failed to update mpio_a flags: %d\n", + ret); + return ret; + } + } + + ret = of_property_read_u8_array(dev->of_node, "ti,mpio-c-flags-gpio", + values, 4); + if (!ret) { + /* Write MPIO B flags/gpio selection */ + for (i = 0, val = 0; i < 4; i++) + val |= (values[i] << PCM9211_MPCxSEL_SHIFT(i)) & + PCM9211_MPCxSEL_MASK(i); + + ret = regmap_update_bits(priv->regmap, + PCM9211_MPIO_B_MPIO_C_FLAGS, + PCM9211_MPCxSEL_MASK(0) | + PCM9211_MPCxSEL_MASK(1) | + PCM9211_MPCxSEL_MASK(2), val); + if (ret) { + dev_err(dev, "Failed to update mpio_a flags: %d\n", + ret); + return ret; + } + } + + ret = of_property_read_u8_array(dev->of_node, "ti,mpio-a-flag", + values, 4); + if (!ret) { + /* Write MPIO A flag selection */ + for (i = 0, val = 0; i < 2; i++) + val |= (values[i] << + PCM9211_MPIO_ABCx_FLAG_SHIFT(i)) & + PCM9211_MPIO_ABCx_FLAG_MASK(i); + ret = regmap_write(priv->regmap, PCM9211_MPIO_A1_A0_OUT_FLAG, + val); + if (ret) { + dev_err(dev, "Failed to update mpio_a1/0 flags: %d\n", + ret); + return ret; + } + + for (i = 2, val = 0; i < 4; i++) + val |= (values[i] << + PCM9211_MPIO_ABCx_FLAG_SHIFT(i)) & + PCM9211_MPIO_ABCx_FLAG_MASK(i); + ret = regmap_write(priv->regmap, PCM9211_MPIO_A3_A2_OUT_FLAG, + val); + if (ret) { + dev_err(dev, "Failed to update mpio_a3/2 flags: %d\n", + ret); + return ret; + } + } + + ret = of_property_read_u8_array(dev->of_node, "ti,mpio-b-flag", + values, 4); + if (!ret) { + /* Write MPIO B flag selection */ + for (i = 0, val = 0; i < 2; i++) + val |= (values[i] << PCM9211_MPIO_ABCx_FLAG_SHIFT(i)) & + PCM9211_MPIO_ABCx_FLAG_MASK(i); + ret = regmap_write(priv->regmap, PCM9211_MPIO_B1_B0_OUT_FLAG, + val); + if (ret) { + dev_err(dev, "Failed to update mpio_b1/0 flags: %d\n", + ret); + return ret; + } + + for (i = 2, val = 0; i < 4; i++) + val |= (values[i] << PCM9211_MPIO_ABCx_FLAG_SHIFT(i)) & + PCM9211_MPIO_ABCx_FLAG_MASK(i); + ret = regmap_write(priv->regmap, PCM9211_MPIO_B3_B2_OUT_FLAG, + val); + if (ret) { + dev_err(dev, "Failed to update mpio_b3/2 flags: %d\n", + ret); + return ret; + } + } + + ret = of_property_read_u8_array(dev->of_node, "ti,mpio-c-flag", + values, 4); + if (!ret) { + /* Write MPIO C flag selection */ + for (i = 0, val = 0; i < 2; i++) + val |= (values[i] << PCM9211_MPIO_ABCx_FLAG_SHIFT(i)) & + PCM9211_MPIO_ABCx_FLAG_MASK(i); + ret = regmap_write(priv->regmap, PCM9211_MPIO_C1_C0_OUT_FLAG, + val); + if (ret) { + dev_err(dev, "Failed to update mpio_c1/0 flags: %d\n", + ret); + return ret; + } + + for (i = 2, val = 0; i < 4; i++) + val |= (values[i] << PCM9211_MPIO_ABCx_FLAG_SHIFT(i)) & + PCM9211_MPIO_ABCx_FLAG_MASK(i); + ret = regmap_write(priv->regmap, PCM9211_MPIO_C3_C2_OUT_FLAG, + val); + if (ret) { + dev_err(dev, "Failed to update mpio_c3/2 flags: %d\n", + ret); + return ret; + } + } + + ret = of_property_read_u8_array(dev->of_node, "ti,mpo-function", + values, 2); + if (!ret) { + /* Write MPO function selection */ + for (i = 0, val = 0; i < 2; i++) + val |= (values[i] << PCM9211_MPOxOUT_SHIFT(i)) & + PCM9211_MPOxOUT_MASK(i); + ret = regmap_write(priv->regmap, PCM9211_MPO_1_0_FUNC, val); + if (ret) { + dev_err(dev, "Failed to update mpo function selection: %d\n", + ret); + return ret; + } + } + + ret = of_property_read_u8(dev->of_node, "ti,int0-function", values); + if (!ret) { + val = values[0] ? PCM9211_ERROR_INT0_MASK : 0; + ret = regmap_update_bits(priv->regmap, PCM9211_ERR_OUT, + PCM9211_ERROR_INT0_MASK, val); + if (ret) { + dev_err(dev, "Failed to update int0 function selection: %d\n", + ret); + return ret; + } + } + + ret = of_property_read_u8(dev->of_node, "ti,int1-function", values); + if (!ret) { + val = values[0] ? PCM9211_NPCM_INT1_MASK : 0; + ret = regmap_update_bits(priv->regmap, PCM9211_ERR_OUT, + PCM9211_NPCM_INT1_MASK, val); + if (ret) { + dev_err(dev, "Failed to update int1 function selection: %d\n", + ret); + return ret; + } + } + + return 0; +} + +static void pcm9211_npcm_clear_work(struct work_struct *work) +{ + struct pcm9211_priv *priv = container_of(work, struct pcm9211_priv, + npcm_clear_work.work); + u8 old_state = priv->npcm_state; + struct device *dev = priv->dev; + + /* Clear NPCM & DTSCD, as DTSCD is only valid as long as NPCM is */ + priv->npcm_state &= ~(PCM9211_INT0_MNPCM0_MASK | + PCM9211_INT0_MDTSCD0_MASK); + + dev_dbg(dev, "Clear NPCM flag after timeout\n"); + + if (priv->codec == NULL || pcm9211_get_dtscd_ctl(dev) == NULL || + pcm9211_get_npcm_ctl(dev) == NULL) + return; + + if (old_state & PCM9211_INT0_MNPCM0_MASK) + snd_ctl_notify(priv->codec->component.card->snd_card, + SNDRV_CTL_EVENT_MASK_VALUE, + &pcm9211_get_npcm_ctl(dev)->id); + + if (old_state & PCM9211_INT0_MDTSCD0_MASK) + snd_ctl_notify(priv->codec->component.card->snd_card, + SNDRV_CTL_EVENT_MASK_VALUE, + &pcm9211_get_dtscd_ctl(dev)->id); +} + +static int pcm9211_soc_probe(struct snd_soc_codec *codec) +{ + struct pcm9211_priv *priv = snd_soc_codec_get_drvdata(codec); + + priv->codec = codec; + + return 0; +} + +/* Simple Controls */ +static const DECLARE_TLV_DB_SCALE(pcm9211_adc_tlv, -10050, 50, 1); +static const char *const pcm9211_main_outputs[] = { "AUTO", "DIR", "ADC", + "AUXIN0", "AUXIN1", "AUXIN2" }; +static const struct soc_enum pcm9211_main_sclk_enum = + SOC_ENUM_SINGLE(PCM9211_MAIN_OUT_SOURCE, 4, 6, pcm9211_main_outputs); +static const struct soc_enum pcm9211_aux_sclk_enum = + SOC_ENUM_SINGLE(PCM9211_AUX_OUT_SOURCE, 4, 5, pcm9211_main_outputs); + +static const struct snd_kcontrol_new pcm9211_snd_controls[] = { + SOC_DOUBLE_R_RANGE_TLV("ADC Attenuation", + PCM9211_ADC_L_CH_ATT, + PCM9211_ADC_R_CH_ATT, + 0, 14, 255, 0, pcm9211_adc_tlv), + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "DIR Sample Rate", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = pcm9211_dir_rate_kctl_info, + .get = pcm9211_dir_rate_kctl, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "DIR Non-PCM Bitstream", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = pcm9211_dir_npcm_kctl_info, + .get = pcm9211_dir_npcm_kctl, + .private_value = PCM9211_INT0_MNPCM0_MASK, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "DIR DTS Bitstream", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = pcm9211_dir_dtscd_kctl_info, + .get = pcm9211_dir_dtscd_kctl, + .private_value = PCM9211_INT0_MDTSCD0_MASK, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "DIR Burst Preamble", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = pcm9211_dir_preamble_kctl_info, + .get = pcm9211_dir_preamble_kctl, + }, + SOC_ENUM("MAIN SCLK Output Select", pcm9211_main_sclk_enum), + SOC_ENUM("AUX SCLK Output Select", pcm9211_aux_sclk_enum), +}; + +/* DAPM Controls */ +static const char *const pcm9211_dir_inputs[] = { "RXIN0", "RXIN1", "RXIN2", + "RXIN3", "RXIN4", "RXIN5", "RXIN6", "RXIN7" }; +static const struct soc_enum pcm9211_dir_mux_enum = + SOC_ENUM_SINGLE(PCM9211_DIR_INP_BIPHASE, 0, 8, pcm9211_dir_inputs); +static const struct snd_kcontrol_new pcm9211_dir_mux_control = + SOC_DAPM_ENUM("DIR Input Select", pcm9211_dir_mux_enum); + +static const struct soc_enum pcm9211_main_out_enum = + SOC_ENUM_SINGLE(PCM9211_MAIN_OUT_SOURCE, 0, 6, pcm9211_main_outputs); +static const struct snd_kcontrol_new pcm9211_main_out_control = + SOC_DAPM_ENUM("MAIN Output Select", pcm9211_main_out_enum); + +static const struct soc_enum pcm9211_aux_out_enum = + SOC_ENUM_SINGLE(PCM9211_AUX_OUT_SOURCE, 0, 5, pcm9211_main_outputs); +static const struct snd_kcontrol_new pcm9211_aux_out_control = + SOC_DAPM_ENUM("AUX Output Select", pcm9211_aux_out_enum); + +/* DAPM widgets */ +static const struct snd_soc_dapm_widget pcm9211_dapm_widgets[] = { + /* Inputs */ + SND_SOC_DAPM_INPUT("RXIN0"), + SND_SOC_DAPM_INPUT("RXIN1"), + SND_SOC_DAPM_INPUT("RXIN2"), + SND_SOC_DAPM_INPUT("RXIN3"), + SND_SOC_DAPM_INPUT("RXIN4"), + SND_SOC_DAPM_INPUT("RXIN5"), + SND_SOC_DAPM_INPUT("RXIN6"), + SND_SOC_DAPM_INPUT("RXIN7"), + SND_SOC_DAPM_INPUT("VINL"), + SND_SOC_DAPM_INPUT("VINR"), + + SND_SOC_DAPM_ADC("ADC", NULL, PCM9211_SYS_RESET, + PCM9211_SYS_RESET_ADDIS_SHIFT, 1), + + /* Processing */ + SND_SOC_DAPM_AIF_IN("DIR", NULL, 0, PCM9211_SYS_RESET, + PCM9211_SYS_RESET_RXDIS_SHIFT, 1), + SND_SOC_DAPM_MIXER("AUTO", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* Internal routing */ + SND_SOC_DAPM_MUX("DIR Input Mux", SND_SOC_NOPM, 0, 0, + &pcm9211_dir_mux_control), + SND_SOC_DAPM_MUX("MAIN Output Mux", SND_SOC_NOPM, 0, 0, + &pcm9211_main_out_control), + SND_SOC_DAPM_MUX("AUX Output Mux", SND_SOC_NOPM, 0, 0, + &pcm9211_aux_out_control), + + /* Outputs */ + SND_SOC_DAPM_OUTPUT("MAIN"), + SND_SOC_DAPM_OUTPUT("AUX"), +}; + +/* DAPM Routing */ +static const struct snd_soc_dapm_route pcm9211_dapm_routes[] = { + { "DIR Input Mux", "RXIN0", "RXIN0" }, + { "DIR Input Mux", "RXIN1", "RXIN1" }, + { "DIR Input Mux", "RXIN2", "RXIN2" }, + { "DIR Input Mux", "RXIN3", "RXIN3" }, + { "DIR Input Mux", "RXIN4", "RXIN4" }, + { "DIR Input Mux", "RXIN5", "RXIN5" }, + { "DIR Input Mux", "RXIN6", "RXIN6" }, + { "DIR Input Mux", "RXIN7", "RXIN7" }, + + { "ADC", NULL, "VINL" }, + { "ADC", NULL, "VINR" }, + + { "DIR", NULL, "DIR Input Mux" }, + { "AUTO", NULL, "DIR" }, + { "AUTO", NULL, "ADC" }, + + { "MAIN Output Mux", "DIR", "DIR" }, + { "MAIN Output Mux", "ADC", "ADC" }, + { "MAIN Output Mux", "AUTO", "AUTO" }, + + { "AUX Output Mux", "DIR", "DIR" }, + { "AUX Output Mux", "ADC", "ADC" }, + { "AUX Output Mux", "AUTO", "AUTO" }, + + { "MAIN", NULL, "MAIN Output Mux" }, + { "AUX", NULL, "AUX Output Mux" }, + + { "MAIN Capture", NULL, "MAIN" }, + { "AUX Capture", NULL, "AUX" }, +}; + +static struct snd_soc_dai_ops pcm9211_dai_ops = { + .startup = pcm9211_startup, + .hw_params = pcm9211_hw_params, + .set_sysclk = pcm9211_set_dai_sysclk, + .set_fmt = pcm9211_set_dai_fmt, +}; + +/* BCLK is always 64 * FS == 32 bit/channel */ +#define PCM9211_FORMATS SNDRV_PCM_FMTBIT_S32_LE +struct snd_soc_dai_driver pcm9211_dai[] = { + { + .name = "pcm9211-main-hifi", + .id = PCM9211_DAI_MAIN, + .capture = { + .stream_name = "MAIN Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = PCM9211_FORMATS, + }, + .ops = &pcm9211_dai_ops, + }, + { + .name = "pcm9211-aux-hifi", + .id = PCM9211_DAI_AUX, + .capture = { + .stream_name = "AUX Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = PCM9211_FORMATS, + }, + .ops = &pcm9211_dai_ops, + }, +}; + +static const struct snd_soc_codec_driver pcm9211_driver = { + .probe = pcm9211_soc_probe, + .component_driver = { + .controls = pcm9211_snd_controls, + .num_controls = ARRAY_SIZE(pcm9211_snd_controls), + .dapm_widgets = pcm9211_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(pcm9211_dapm_widgets), + .dapm_routes = pcm9211_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(pcm9211_dapm_routes), + }, +}; + +int pcm9211_probe(struct device *dev, struct regmap *regmap) +{ + struct pcm9211_priv *priv; + unsigned int cause; + int ret; + int i; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + + dev_set_drvdata(dev, priv); + priv->dev = dev; + priv->regmap = regmap; + + priv->xti = devm_clk_get(dev, "xti"); + if (IS_ERR(priv->xti)) { + ret = PTR_ERR(priv->xti); + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to get clock 'xti': %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(priv->xti); + if (ret) { + dev_err(dev, "Failed to enable xti clock: %d\n", ret); + return ret; + } + + priv->sysclk = clk_get_rate(priv->xti); + if (priv->sysclk > PCM9211_MAX_SYSCLK) { + dev_err(dev, "xti clock rate (%lu) exceeds supported max %u\n", + priv->sysclk, PCM9211_MAX_SYSCLK); + return -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(priv->supplies); i++) + priv->supplies[i].supply = pcm9211_supply_names[i]; + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(priv->supplies), + priv->supplies); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to get supplies: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(priv->supplies), + priv->supplies); + if (ret) { + dev_err(dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + priv->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(priv->reset)) { + ret = PTR_ERR(priv->reset); + dev_err(dev, "Failed to get reset gpio: %d\n", ret); + return ret; + } + + pcm9211_reset(dev); + + priv->int0 = devm_gpiod_get_optional(dev, "int0", GPIOD_IN); + if (IS_ERR(priv->int0)) { + ret = PTR_ERR(priv->int0); + dev_err(dev, "Failed to get int0 gpio: %d\n", ret); + return ret; + } + + if (priv->int0) { + int irq = gpiod_to_irq(priv->int0); + + if (irq < 0) { + dev_err(dev, "Configured 'int0' gpio cannot be used as IRQ: %d\n", + irq); + return irq; + } + + INIT_DELAYED_WORK(&priv->npcm_clear_work, + pcm9211_npcm_clear_work); + ret = devm_request_threaded_irq(dev, irq, NULL, + pcm9211_interrupt, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "pcm9211", priv); + if (ret) { + dev_err(dev, "Failed to request irq: %d\n", ret); + return ret; + } + + /* Set interrupt to use positive polarity */ + ret = regmap_update_bits(priv->regmap, PCM9211_INT_POLARITY, + PCM9211_INT0_POLARITY_POS_MASK, + PCM9211_INT0_POLARITY_POS_MASK); + if (ret) { + dev_err(dev, "Failed to configure int0 polaroty: %d\n", + ret); + return ret; + } + } + + ret = pcm9211_write_pinconfig(dev); + if (ret) + return ret; + + /* Unmap NPCM, DTS, Burst Preamble and Fs change interrupt */ + ret = regmap_update_bits(priv->regmap, PCM9211_INT0_CAUSE, + PCM9211_INT0_MNPCM0_MASK | PCM9211_INT0_MDTSCD0_MASK | + PCM9211_INT0_MPCRNW0_MASK | PCM9211_INT0_MFSCHG0_MASK, + 0); + if (ret) { + dev_err(dev, "Failed to unmask interrupt causes: %d\n", ret); + return ret; + } + + /* Enable DTSCD detection */ + ret = regmap_update_bits(priv->regmap, PCM9211_NON_PCM_DEF, + PCM9211_NON_PCM_DTS_CD_DET_MASK, + PCM9211_NON_PCM_DTS_CD_DET_MASK); + if (ret) { + dev_err(dev, "Failed to enable DTSCD detection: %d\n", ret); + return ret; + } + + /* Read initial sampling rate and npcm state */ + priv->dir_rate = pcm9211_dir_rate(dev); + ret = regmap_read(priv->regmap, PCM9211_INT0_OUT, &cause); + if (ret) { + dev_err(dev, "Failed to read int0 cause: %d\n", ret); + return IRQ_HANDLED; + } + priv->npcm_state = cause; + + ret = snd_soc_register_codec(dev, &pcm9211_driver, pcm9211_dai, + ARRAY_SIZE(pcm9211_dai)); + if (ret) { + dev_err(dev, "Failed to register codec: %d\n", ret); + return ret; + } + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); + + return 0; +} +EXPORT_SYMBOL_GPL(pcm9211_probe); + +void pcm9211_remove(struct device *dev) +{ + struct pcm9211_priv *priv = dev_get_drvdata(dev); + + snd_soc_unregister_codec(dev); + pm_runtime_disable(dev); + regulator_bulk_disable(ARRAY_SIZE(priv->supplies), priv->supplies); + clk_disable_unprepare(priv->xti); +} +EXPORT_SYMBOL_GPL(pcm9211_remove); + +#ifdef CONFIG_PM +static int pcm9211_runtime_resume(struct device *dev) +{ + struct pcm9211_priv *priv = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(priv->xti); + if (ret) { + dev_err(dev, "Failed to enable xti clock: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(priv->supplies), priv->supplies); + if (ret) { + dev_err(dev, "Failed to enable supplies: %d\n", ret); + goto err_reg; + } + + ret = pcm9211_reset(dev); + if (ret) { + dev_err(dev, "Failed to reset device: %d\n", ret); + goto err; + } + + regcache_cache_only(priv->regmap, false); + regcache_mark_dirty(priv->regmap); + + ret = regcache_sync(priv->regmap); + if (ret) { + dev_err(dev, "Failed to sync regmap: %d\n", ret); + goto err; + } + + return 0; + +err: + regulator_bulk_disable(ARRAY_SIZE(priv->supplies), priv->supplies); +err_reg: + clk_disable_unprepare(priv->xti); + + return ret; +} + +static int pcm9211_runtime_suspend(struct device *dev) +{ + struct pcm9211_priv *priv = dev_get_drvdata(dev); + + regcache_cache_only(priv->regmap, true); + regulator_bulk_disable(ARRAY_SIZE(priv->supplies), priv->supplies); + clk_disable_unprepare(priv->xti); + + return 0; +} +#endif + +const struct dev_pm_ops pcm9211_pm_ops = { + SET_RUNTIME_PM_OPS(pcm9211_runtime_suspend, pcm9211_runtime_resume, + NULL) +}; +EXPORT_SYMBOL_GPL(pcm9211_pm_ops); + +MODULE_DESCRIPTION("PCM9211 codec driver"); +MODULE_AUTHOR("Julian Scheel "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/pcm9211.h b/sound/soc/codecs/pcm9211.h new file mode 100644 index 000000000000..3ad9e9e9419a --- /dev/null +++ b/sound/soc/codecs/pcm9211.h @@ -0,0 +1,206 @@ +/* + * PCM9211 codec driver header + * + * Copyright (C) 2017 jusst technologies GmbH / jusst.engineering + * + * Author: Julian Scheel + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + */ + +#ifndef __PCM9211_H_ +#define __PCM9211_H_ + +extern const struct dev_pm_ops pcm9211_pm_ops; +extern const struct regmap_config pcm9211_regmap; + +extern int pcm9211_probe(struct device *dev, struct regmap *regmap); +extern void pcm9211_remove(struct device *dev); + +/* Register definitions */ +#define PCM9211_ERR_OUT 0x20 +#define PCM9211_DIR_INITIAL1 0x21 +#define PCM9211_DIR_INITIAL2 0x22 +#define PCM9211_DIR_INITIAL3 0x23 +#define PCM9211_OSC_CTRL 0x24 +#define PCM9211_ERR_CAUSE 0x25 +#define PCM9211_AUTO_SEL_CAUSE 0x26 +#define PCM9211_DIR_FS_RANGE 0x27 +#define PCM9211_NON_PCM_DEF 0x28 +#define PCM9211_DTS_CD_LD 0x29 +#define PCM9211_INT0_CAUSE 0x2a +#define PCM9211_INT1_CAUSE 0x2b +#define PCM9211_INT0_OUT 0x2c +#define PCM9211_INT1_OUT 0x2d +#define PCM9211_INT_POLARITY 0x2e +#define PCM9211_DIR_OUT_FMT 0x2f +#define PCM9211_DIR_RSCLK_RATIO 0x30 +#define PCM9211_XTI_SCLK_FREQ 0x31 +#define PCM9211_DIR_SOURCE_BIT2 0x32 +#define PCM9211_XTI_SOURCE_BIT2 0x33 +#define PCM9211_DIR_INP_BIPHASE 0x34 +#define PCM9211_RECOUT0_BIPHASE 0x35 +#define PCM9211_RECOUT1_BIPHASE 0x36 +#define PCM9211_FS_CALC_TARGET 0x37 +#define PCM9211_FS_CALC_RESULT 0x38 +#define PCM9211_BIPHASE_INFO 0x39 +#define PCM9211_PC_BUF0 0x3a +#define PCM9211_PC_BUF1 0x3b +#define PCM9211_PD_BUF0 0x3c +#define PCM9211_PD_BUF1 0x3d + +#define PCM9211_SYS_RESET 0x40 + +#define PCM9211_ADC_CTRL1 0x42 + +#define PCM9211_ADC_L_CH_ATT 0x46 +#define PCM9211_ADC_R_CH_ATT 0x47 +#define PCM9211_ADC_CTRL2 0x48 +#define PCM9211_ADC_CTRL3 0x49 + +#define PCM9211_DIR_STATUS1 0x5a +#define PCM9211_DIR_STATUS2 0x5b +#define PCM9211_DIR_STATUS3 0x5c +#define PCM9211_DIR_STATUS4 0x5d +#define PCM9211_DIR_STATUS5 0x5e +#define PCM9211_DIR_STATUS6 0x5f +#define PCM9211_DIT_CTRL1 0x60 +#define PCM9211_DIT_CTRL2 0x61 +#define PCM9211_DIT_CTRL3 0x62 +#define PCM9211_DIT_STATUS1 0x63 +#define PCM9211_DIT_STATUS2 0x64 +#define PCM9211_DIT_STATUS3 0x65 +#define PCM9211_DIT_STATUS4 0x66 +#define PCM9211_DIT_STATUS5 0x67 +#define PCM9211_DIT_STATUS6 0x68 + +#define PCM9211_MAIN_AUX_MUTE 0x6a +#define PCM9211_MAIN_OUT_SOURCE 0x6b +#define PCM9211_AUX_OUT_SOURCE 0x6c +#define PCM9211_MPIO_B_MAIN_HIZ 0x6d +#define PCM9211_MPIO_C_MPIO_A_HIZ 0x6e +#define PCM9211_MPIO_GROUP 0x6f +#define PCM9211_MPIO_A_FLAGS 0x70 +#define PCM9211_MPIO_B_MPIO_C_FLAGS 0x71 +#define PCM9211_MPIO_A1_A0_OUT_FLAG 0x72 +#define PCM9211_MPIO_A3_A2_OUT_FLAG 0x73 +#define PCM9211_MPIO_B1_B0_OUT_FLAG 0x74 +#define PCM9211_MPIO_B3_B2_OUT_FLAG 0x75 +#define PCM9211_MPIO_C1_C0_OUT_FLAG 0x76 +#define PCM9211_MPIO_C3_C2_OUT_FLAG 0x77 +#define PCM9211_MPO_1_0_FUNC 0x78 +#define PCM9211_MPIO_A_B_DIR 0x79 +#define PCM9211_MPIO_C_DIR 0x7a +#define PCM9211_MPIO_A_B_DATA_OUT 0x7b +#define PCM9211_MPIO_C_DATA_OUT 0x7c +#define PCM9211_MPIO_A_B_DATA_IN 0x7d +#define PCM9211_MPIO_C_DATA_IN 0x7e + + +/* Register field values (only used ones) */ + +/* PCM9211_ERR_OUT */ +#define PCM9211_ERROR_INT0_MASK BIT(2) +#define PCM9211_NPCM_INT1_MASK BIT(0) + +/* PCM9211_NON_PCM_DEF */ +#define PCM9211_NON_PCM_DTS_CD_DET_MASK BIT(2) + +/* PCM9211_INT0_CAUSE */ +#define PCM9211_INT0_MNPCM0_MASK BIT(6) +#define PCM9211_INT0_MDTSCD0_MASK BIT(4) +#define PCM9211_INT0_MPCRNW0_MASK BIT(2) +#define PCM9211_INT0_MFSCHG0_MASK BIT(1) + +/* PCM9211_INT_POLARITY */ +#define PCM9211_INT0_POLARITY_POS_MASK BIT(2) + +/* PCM9211_DIR_OUT_FMT */ +#define PCM9211_DIR_FMT_MASK 0x7 +#define PCM9211_DIR_FMT_SHIFT 0 + +#define PCM9211_DIR_FMT_I2S 4 +#define PCM9211_DIR_FMT_LEFT_J 5 +#define PCM9211_DIR_FMT_RIGHT_J 0 + +/* PCM9211_XTI_SCLK_FREQ */ +#define PCM9211_XTI_XSCK_SHIFT 4 +#define PCM9211_XTI_XSCK_MASK (0x3 << PCM9211_XTI_XSCK_SHIFT) +#define PCM9211_XTI_BCK_SHIFT 2 +#define PCM9211_XTI_BCK_MASK (0x3 << PCM9211_XTI_BCK_SHIFT) +#define PCM9211_XTI_LRCK_SHIFT 0 +#define PCM9211_XTI_LRCK_MASK (0x3 << PCM9211_XTI_LRCK_SHIFT) + + +/* PCM9211_BIPHASE_INFO */ +#define PCM9211_BIPHASE_SFSST_SHIFT 7 +#define PCM9211_BIPHASE_SFSST_MASK BIT(PCM9211_BIPHASE_SFSST_SHIFT) +#define PCM9211_BIPHASE_SFSOUT_SHIFT 0 +#define PCM9211_BIPHASE_SFSOUT_MASK (0xf << PCM9211_BIPHASE_SFSOUT_SHIFT) + + +/* PCM9211_SYS_RESET */ +#define PCM9211_SYS_RESET_MRST BIT(7) +#define PCM9211_SYS_RESET_RXDIS_SHIFT 4 +#define PCM9211_SYS_RESET_ADDIS_SHIFT 5 + + +/* PCM9211_ADC_CTRL2 */ +#define PCM9211_ADFMT_MASK 0x3 +#define PCM9211_ADFMT_SHIFT 0 + +#define PCM9211_ADFMT_I2S 0 +#define PCM9211_ADFMT_LEFT_J 1 +#define PCM9211_ADFMT_RIGHT_J 2 + + +/* PCM9211_MAIN_OUT_SOURCE */ +#define PCM9211_MOSSRC_SHIFT 4 +#define PCM9211_MOPSRC_SHIFT 0 +#define PCM9211_MOSRC_MASK 0x7 +#define PCM9211_MOSSRC_MASK \ + (PCM9211_MOSRC_MASK << PCM9211_MOSSRC_SHIFT) +#define PCM9211_MOPSRC_MASK \ + (PCM9211_MOSRC_MASK << PCM9211_MOPSRC_SHIFT) + +#define PCM9211_MOSRC_AUTO 0 +#define PCM9211_MOSRC_DIR 1 +#define PCM9211_MOSRC_ADC 2 +#define PCM9211_MOSRC_AUXIN0 3 +#define PCM9211_MOSRC_AUXIN1 4 +#define PCM9211_MOSRC_AUXIN2 5 + + +/* PCM9211_MPIO_GROUP */ +#define PCM9211_MPASEL_SHIFT 6 +#define PCM9211_MPASEL_MASK (0x2 << PCM9211_MPASEL_SHIFT) +#define PCM9211_MPBSEL_SHIFT 3 +#define PCM9211_MPBSEL_MASK (0x7 << PCM9211_MPBSEL_SHIFT) +#define PCM9211_MPCSEL_SHIFT 0 +#define PCM9211_MPCSEL_MASK (0x7 << PCM9211_MPCSEL_SHIFT) + + +/* PCM9211_MPIO_A_FLAGS */ +#define PCM9211_MPAxSEL_SHIFT(x) (x) +#define PCM9211_MPAxSEL_MASK(x) BIT(PCM9211_MPAxSEL_SHIFT(x)) + + +/* PCM9211_MPIO_B_MPIO_C_FLAGS */ +#define PCM9211_MPBxSEL_SHIFT(x) ((x) + 4) +#define PCM9211_MPBxSEL_MASK(x) BIT(PCM9211_MPBxSEL_SHIFT(x)) +#define PCM9211_MPCxSEL_SHIFT(x) (x) +#define PCM9211_MPCxSEL_MASK(x) BIT(PCM9211_MPCxSEL_SHIFT(x)) + + +/* PCM9211_MPIO_A1_A0_OUT_FLAG..PCM9211_MPIO_C3_C2_OUT_FLAG */ +#define PCM9211_MPIO_ABCx_FLAG_SHIFT(x) (((x) % 2) * 4) +#define PCM9211_MPIO_ABCx_FLAG_MASK(x) (0xf << PCM9211_MPIO_ABCx_FLAG_SHIFT(x)) + + +/* PCM9211_MPO_1_0_FUNC */ +#define PCM9211_MPOxOUT_SHIFT(x) ((x) * 4) +#define PCM9211_MPOxOUT_MASK(x) (0xf << PCM9211_MPOxOUT_SHIFT(x)) + +#endif