Devicetree
 help / color / mirror / Atom feed
* [PATCH v2 2/2] ASoC: cs35l35: Add device tree documentation for CS35L35
From: Li Xu @ 2016-12-13 16:26 UTC (permalink / raw)
  To: alsa-devel, devicetree
  Cc: mark.rutland, brian.austin, tiwai, robh+dt, lgirdwood, broonie,
	Paul.Handrigan, Li Xu
In-Reply-To: <1481646404-19012-1-git-send-email-li.xu@cirrus.com>

Add device tree documentation for Cirrus Logic CS35L35
speaker amplifier

Signed-off-by: Li Xu <li.xu@cirrus.com>
---
 .../devicetree/bindings/sound/cs35l35.txt          | 172 +++++++++++++++++++++
 1 file changed, 172 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/cs35l35.txt

diff --git a/Documentation/devicetree/bindings/sound/cs35l35.txt b/Documentation/devicetree/bindings/sound/cs35l35.txt
new file mode 100644
index 0000000..8b13f67
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/cs35l35.txt
@@ -0,0 +1,172 @@
+CS35L35 Speaker Amplifier
+
+Required properties:
+
+  - compatible : "cirrus,cs35l35"
+
+  - reg : the I2C address of the device for I2C
+
+  - VA-supply, VP-supply : power supplies for the device,
+    as covered in
+    Documentation/devicetree/bindings/regulator/regulator.txt.
+
+  - interrupt-parent : Specifies the phandle of the interrupt controller to
+    which the IRQs from CS35L35 are delivered to.
+  - interrupts : IRQ line info CS35L35.
+    (See Documentation/devicetree/bindings/interrupt-controller/interrupts.txt
+    for further information relating to interrupt properties)
+
+Optional properties:
+  - cirrus,reset-gpios : Active low GPIO used to reset the amplifier
+
+  - cirrus,stereo-config : Boolean to determine if there are 2 AMPs for a
+  Stereo configuration
+
+  - cirrus,audio-channel : Set Location of Audio Signal on Serial Port
+  0 = Data Packet received on Left I2S Channel
+  1 = Data Packet received on Right I2S Channel
+
+  - cirrus,advisory-channel : Set Location of Advisory Signal on Serial Port
+  0 = Data Packet received on Left I2S Channel
+  1 = Data Packet received on Right I2S Channel
+
+  - cirrus,shared-boost : Boolean to enable ClassH tracking of Advisory Signal
+  if 2 Devices share Boost BST_CTL
+
+  - cirrus,sp-drv-strength : Value for setting the Serial Port drive strength
+  Table 3-10 of the datasheet lists drive-strength specifications
+  0 = 1x (Default)
+  1 = .5x
+
+  - cirrus,bst-pdn-fet-on : Boolean to determine if the Boost PDN control
+  powers down with a rectification FET On or Off. If VSPK is supplied
+  externally then FET is off.
+
+  - cirrus,boost-ctl-millivolt : Boost Converter control word. Step Size of 100mV
+  0x00 = (Default) VP
+  0x01 = 2600mV
+  0x41 = 9000mV
+
+  - cirrus,boost-ipk-milliamp : Boost-converter peak current limit.
+  Configures the peak current by monitoring the current through the boost FET.
+  Step size: 112mA
+  0x00 = 1680mA
+  0x19 = 4480mA
+
+  - cirrus,amp-gain-zc : Boolean to determine if to use Amplifier gain-change
+  zero-cross
+
+Optional H/G Algorithm sub-node:
+
+  The cs35l35 node can have a single "cirrus,classh-internal-algo" sub-node
+  that will disable automatic control of the internal H/G Algorithm.
+
+  It is strongly recommended that the Datasheet be referenced when adjusting
+  or using these Class H Algorithm controls over the internal Algorithm.
+  Serious damage can occur to the Device and surrounding components.
+
+  - cirrus,classh-internal-algo : Sub-node for the Internal Class H Algorithm
+  See Section 4.3 Internal Class H Algorithm in the Datasheet.
+  If not used, the device manages the ClassH Algorithm internally.
+
+Optional properties for the "cirrus,classh-internal-algo" Sub-node
+
+  Section 7.29 Class H Control
+  - cirrus,classh-bst-overide : Boolean
+  - cirrus,classh-bst-max-limit
+  - cirrus,classh-mem-depth
+
+  Section 7.30 Class H Headroom Control
+  - cirrus,classh-headroom
+
+  Section 7.31 Class H Release Rate
+  - cirrus,classh-release-rate
+
+  Section 7.32 Class H Weak FET Drive Control
+  - cirrus,classh-wk-fet-disable
+  - cirrus,classh-wk-fet-delay
+  - cirrus,classh-wk-fet-thld
+
+  Section 7.34 Class H VP Control
+  - cirrus,classh-vpch-auto
+  - cirrus,classh-vpch-rate
+  - cirrus,classh-vpch-man
+
+Optional Monitor Signal Format sub-node:
+
+  The cs35l35 node can have a single "cirrus,monitor-signal-format" sub-node
+  for adjusting the Depth, Location and Frame of the Monitoring Signals
+  for Algorithms.
+
+  See Sections 4.8.2 through 4.8.4 Serial-Port Control in the Datasheet
+
+  -cirrus,monitor-signal-format : Sub-node for the Monitor Signaling Formating
+  on the I2S Port. Each of the 3 8 bit values in the array contain the settings
+  for depth, location, and frame.
+
+  If not used, the defaults for the 6 monitor signals is used.
+
+  Sections 7.44 - 7.53 lists values for the depth, location, and frame
+  for each monitoring signal.
+
+  - cirrus,imon : 3 8 bit values to set the depth, location, and frame
+  of the IMON monitor signal.
+
+  - cirrus,vmon : 3 8 bit values to set the depth, location, and frame
+  of the VMON monitor signal.
+
+  - cirrus,vpmon : 3 8 bit values to set the depth, location, and frame
+  of the VPMON monitor signal.
+
+  - cirrus,vbstmon : 3 8 bit values to set the depth, location, and frame
+  of the VBSTMON monitor signal
+
+  - cirrus,vpbrstat : 3 8 bit values to set the depth, location, and frame
+  of the VPBRSTAT monitor signal
+
+  - cirrus,zerofill : 3 8 bit values to set the depth, location, and frame\
+  of the ZEROFILL packet in the monitor signal
+
+Example:
+
+cs35l35: audio-codec@20 {
+	compatible = "cirrus,cs35l35";
+	reg = <0x20>;
+	VA-supply = <&dummy_vreg>;
+	VP-supply = <&dummy_vreg>;
+	reset-gpios = <&axi_gpio 54 1>;
+	interrupt-parent = <&gpio8>;
+	interrupts = <3 IRQ_TYPE_LEVEL_LOW>;
+	cirrus,boost-ctl = <0x41>;
+
+	cirrus,stereo-config {
+		cirrus,audio-channel = <0x00>;
+		cirrus,advisory-channel = <0x01>;
+		cirrus,shared-boost;
+	};
+
+	cirrus,classh-internal-algo {
+		cirrus,classh-bst-overide;
+		cirrus,classh-bst-max-limit = <0x01>;
+		cirrus,classh-mem-depth = <0x01>;
+		cirrus,classh-release-rate = <0x08>;
+		cirrus,classh-headroom-millivolt = <0x0B>;
+		cirrus,classh-wk-fet-disable = <0x01>;
+		cirrus,classh-wk-fet-delay = <0x04>;
+		cirrus,classh-wk-fet-thld = <0x01>;
+		cirrus,classh-vpch-auto = <0x01>;
+		cirrus,classh-vpch-rate = <0x02>;
+		cirrus,classh-vpch-man = <0x05>;
+	};
+
+	/* Depth, Location, Frame */
+	cirrus,monitor-signal-format {
+		cirrus,imon = /bits/ 8 <0x03 0x00 0x01>;
+		cirrus,vmon = /bits/ 8 <0x03 0x00 0x00>;
+		cirrus,vpmon = /bits/ 8 <0x03 0x04 0x00>;
+		cirrus,vbstmon = /bits/ 8 <0x03 0x04 0x01>;
+		cirrus,vpbrstat = /bits/ 8 <0x00 0x04 0x00>;
+		cirrus,zerofill = /bits/ 8 <0x00 0x00 0x00>;
+	};
+
+};
-- 
1.9.1

^ permalink raw reply related

* [PATCH v2 1/2] ASoC: cs35l35: Add support for Cirrus CS35L35 Amplifier
From: Li Xu @ 2016-12-13 16:26 UTC (permalink / raw)
  To: alsa-devel, devicetree
  Cc: mark.rutland, brian.austin, tiwai, robh+dt, lgirdwood, broonie,
	Paul.Handrigan, Li Xu

Add driver support for Cirrus Logic CS35L35 boosted
speaker amplifier

Signed-off-by: Li Xu <li.xu@cirrus.com>
---
 include/sound/cs35l35.h    |  104 ++++
 sound/soc/codecs/Kconfig   |    5 +
 sound/soc/codecs/Makefile  |    2 +
 sound/soc/codecs/cs35l35.c | 1357 ++++++++++++++++++++++++++++++++++++++++++++
 sound/soc/codecs/cs35l35.h |  285 ++++++++++
 5 files changed, 1753 insertions(+)
 create mode 100644 include/sound/cs35l35.h
 create mode 100644 sound/soc/codecs/cs35l35.c
 create mode 100644 sound/soc/codecs/cs35l35.h

diff --git a/include/sound/cs35l35.h b/include/sound/cs35l35.h
new file mode 100644
index 0000000..005a813
--- /dev/null
+++ b/include/sound/cs35l35.h
@@ -0,0 +1,104 @@
+/*
+ * linux/sound/cs35l35.h -- Platform data for CS35l35
+ *
+ * Copyright (c) 2016 Cirrus Logic 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.
+ */
+
+#ifndef __CS35L35_H
+#define __CS35L35_H
+
+struct classh_cfg {
+	/*
+	 * Class H Algorithm Control Variables
+	 * You can either have it done
+	 * automatically or you can adjust
+	 * these variables for tuning
+	 *
+	 * if you do not enable the internal algorithm
+	 * you will get a set of mixer controls for
+	 * Class H tuning
+	 *
+	 * Section 4.3 of the datasheet
+	 */
+	/* Internal ClassH Algorithm  */
+	bool classh_bst_override;
+	bool classh_algo_enable;
+	int classh_bst_max_limit;
+	int classh_mem_depth;
+	int classh_release_rate;
+	int classh_headroom;
+	int classh_wk_fet_disable;
+	int classh_wk_fet_delay;
+	int classh_wk_fet_thld;
+	int classh_vpch_auto;
+	int classh_vpch_rate;
+	int classh_vpch_man;
+};
+
+struct monitor_cfg {
+	/*
+	 * Signal Monitor Data
+	 * highly configurable signal monitoring
+	 * data positioning and different types of
+	 * monitoring data.
+	 *
+	 * Section 4.8.2 - 4.8.4 of the datasheet
+	 */
+	bool is_present;
+	bool imon_specs;
+	bool vmon_specs;
+	bool vpmon_specs;
+	bool vbstmon_specs;
+	bool vpbrstat_specs;
+	bool zerofill_specs;
+	u8 imon_dpth;
+	u8 imon_loc;
+	u8 imon_frm;
+	u8 vmon_dpth;
+	u8 vmon_loc;
+	u8 vmon_frm;
+	u8 vpmon_dpth;
+	u8 vpmon_loc;
+	u8 vpmon_frm;
+	u8 vbstmon_dpth;
+	u8 vbstmon_loc;
+	u8 vbstmon_frm;
+	u8 vpbrstat_dpth;
+	u8 vpbrstat_loc;
+	u8 vpbrstat_frm;
+	u8 zerofill_dpth;
+	u8 zerofill_loc;
+	u8 zerofill_frm;
+};
+
+struct cs35l35_platform_data {
+
+	/* Stereo (2 Device) */
+	bool stereo;
+	/* serial port drive strength */
+	int sp_drv_str;
+	/* Boost Power Down with FET */
+	bool bst_pdn_fet_on;
+	/* Boost Voltage : used if ClassH Algo Enabled */
+	int bst_vctl;
+	/* Boost Converter Peak Current CTRL */
+	int bst_ipk;
+	/* Amp Gain Zero Cross */
+	bool gain_zc;
+	/* Audio Input Location */
+	int aud_channel;
+	/* Advisory Input Location */
+	int adv_channel;
+	/* Shared Boost for stereo */
+	bool shared_bst;
+	/* ClassH Algorithm */
+	struct classh_cfg classh_algo;
+	/* Monitor Config */
+	struct monitor_cfg mon_cfg;
+};
+
+#endif /* __CS35L35_H */
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 9e1718a..3fd0a08 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -49,6 +49,7 @@ config SND_SOC_ALL_CODECS
 	select SND_SOC_CS35L32 if I2C
 	select SND_SOC_CS35L33 if I2C
 	select SND_SOC_CS35L34 if I2C
+	select SND_SOC_CS35L35 if I2C
 	select SND_SOC_CS42L42 if I2C
 	select SND_SOC_CS42L51_I2C if I2C
 	select SND_SOC_CS42L52 if I2C && INPUT
@@ -407,6 +408,10 @@ config SND_SOC_CS35L34
 	tristate "Cirrus Logic CS35L34 CODEC"
 	depends on I2C
 
+config SND_SOC_CS35L35
+	tristate "Cirrus Logic CS35L35 CODEC"
+	depends on I2C
+
 config SND_SOC_CS42L42
 	tristate "Cirrus Logic CS42L42 CODEC"
 	depends on I2C
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 7e1dad7..a5622f6 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -39,6 +39,7 @@ snd-soc-cq93vc-objs := cq93vc.o
 snd-soc-cs35l32-objs := cs35l32.o
 snd-soc-cs35l33-objs := cs35l33.o
 snd-soc-cs35l34-objs := cs35l34.o
+snd-soc-cs35l35-objs := cs35l35.o
 snd-soc-cs42l42-objs := cs42l42.o
 snd-soc-cs42l51-objs := cs42l51.o
 snd-soc-cs42l51-i2c-objs := cs42l51-i2c.o
@@ -268,6 +269,7 @@ obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o
 obj-$(CONFIG_SND_SOC_CS35L32)	+= snd-soc-cs35l32.o
 obj-$(CONFIG_SND_SOC_CS35L33)	+= snd-soc-cs35l33.o
 obj-$(CONFIG_SND_SOC_CS35L34)	+= snd-soc-cs35l34.o
+obj-$(CONFIG_SND_SOC_CS35L35)	+= snd-soc-cs35l35.o
 obj-$(CONFIG_SND_SOC_CS42L42)	+= snd-soc-cs42l42.o
 obj-$(CONFIG_SND_SOC_CS42L51)	+= snd-soc-cs42l51.o
 obj-$(CONFIG_SND_SOC_CS42L51_I2C)	+= snd-soc-cs42l51-i2c.o
diff --git a/sound/soc/codecs/cs35l35.c b/sound/soc/codecs/cs35l35.c
new file mode 100644
index 0000000..ea84430
--- /dev/null
+++ b/sound/soc/codecs/cs35l35.c
@@ -0,0 +1,1357 @@
+/*
+ * cs35l35.c -- CS35L35 ALSA SoC audio driver
+ *
+ * Copyright 2016 Cirrus Logic, Inc.
+ *
+ * Author: Brian Austin <brian.austin@cirrus.com>
+ *         Li Xu <li.xu@cirrus.com>
+ *
+ * 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/version.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/gpio/consumer.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/regmap.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <linux/gpio.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <sound/cs35l35.h>
+#include <linux/of_irq.h>
+#include <linux/completion.h>
+
+#include "cs35l35.h"
+
+static const struct reg_default cs35l35_reg[] = {
+	{CS35L35_PWRCTL1,		0x01},
+	{CS35L35_PWRCTL2,		0x11},
+	{CS35L35_PWRCTL3,		0x00},
+	{CS35L35_CLK_CTL1,		0x04},
+	{CS35L35_CLK_CTL2,		0x10},
+	{CS35L35_CLK_CTL3,		0xCF},
+	{CS35L35_SP_FMT_CTL1,		0x20},
+	{CS35L35_SP_FMT_CTL2,		0x00},
+	{CS35L35_SP_FMT_CTL3,		0x02},
+	{CS35L35_MAG_COMP_CTL,		0x00},
+	{CS35L35_AMP_INP_DRV_CTL,	0x01},
+	{CS35L35_AMP_DIG_VOL_CTL,	0x12},
+	{CS35L35_AMP_DIG_VOL,		0x00},
+	{CS35L35_ADV_DIG_VOL,		0x00},
+	{CS35L35_PROTECT_CTL,		0x06},
+	{CS35L35_AMP_GAIN_AUD_CTL,	0x13},
+	{CS35L35_AMP_GAIN_PDM_CTL,	0x00},
+	{CS35L35_AMP_GAIN_ADV_CTL,	0x00},
+	{CS35L35_GPI_CTL,		0x00},
+	{CS35L35_BST_CVTR_V_CTL,	0x00},
+	{CS35L35_BST_PEAK_I,		0x07},
+	{CS35L35_BST_RAMP_CTL,		0x85},
+	{CS35L35_BST_CONV_COEF_1,	0x20},
+	{CS35L35_BST_CONV_COEF_2,	0x20},
+	{CS35L35_BST_CONV_SLOPE_COMP,	0x47},
+	{CS35L35_BST_CONV_SW_FREQ,	0x04},
+	{CS35L35_CLASS_H_CTL,		0x0B},
+	{CS35L35_CLASS_H_HEADRM_CTL,	0x0B},
+	{CS35L35_CLASS_H_RELEASE_RATE,	0x08},
+	{CS35L35_CLASS_H_FET_DRIVE_CTL, 0x41},
+	{CS35L35_CLASS_H_VP_CTL,	0xC5},
+	{CS35L35_VPBR_CTL,		0x0A},
+	{CS35L35_VPBR_VOL_CTL,		0x09},
+	{CS35L35_VPBR_TIMING_CTL,	0x6A},
+	{CS35L35_VPBR_MODE_VOL_CTL,	0x00},
+	{CS35L35_SPKR_MON_CTL,		0xC0},
+	{CS35L35_IMON_SCALE_CTL,	0x30},
+	{CS35L35_AUDIN_RXLOC_CTL,	0x00},
+	{CS35L35_ADVIN_RXLOC_CTL,	0x80},
+	{CS35L35_VMON_TXLOC_CTL,	0x00},
+	{CS35L35_IMON_TXLOC_CTL,	0x80},
+	{CS35L35_VPMON_TXLOC_CTL,	0x04},
+	{CS35L35_VBSTMON_TXLOC_CTL,	0x84},
+	{CS35L35_VPBR_STATUS_TXLOC_CTL,	0x04},
+	{CS35L35_ZERO_FILL_LOC_CTL,	0x00},
+	{CS35L35_AUDIN_DEPTH_CTL,	0x0F},
+	{CS35L35_SPKMON_DEPTH_CTL,	0x0F},
+	{CS35L35_SUPMON_DEPTH_CTL,	0x0F},
+	{CS35L35_ZEROFILL_DEPTH_CTL,	0x00},
+	{CS35L35_MULT_DEV_SYNCH1,	0x02},
+	{CS35L35_MULT_DEV_SYNCH2,	0x80},
+	{CS35L35_PROT_RELEASE_CTL,	0x00},
+	{CS35L35_DIAG_MODE_REG_LOCK,	0x00},
+	{CS35L35_DIAG_MODE_CTL_1,	0x40},
+	{CS35L35_DIAG_MODE_CTL_2,	0x00},
+	{CS35L35_INT_MASK_1,		0xFF},
+	{CS35L35_INT_MASK_2,		0xFF},
+	{CS35L35_INT_MASK_3,		0xFF},
+	{CS35L35_INT_MASK_4,		0xFF},
+
+};
+
+static bool cs35l35_volatile_register(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case CS35L35_DEVID_AB ... CS35L35_REV_ID:
+	case CS35L35_INT_STATUS_1:
+	case CS35L35_INT_STATUS_2:
+	case CS35L35_INT_STATUS_3:
+	case CS35L35_INT_STATUS_4:
+	case CS35L35_PLL_STATUS:
+	case CS35L35_OTP_TRIM_STATUS:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool cs35l35_readable_register(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case CS35L35_DEVID_AB ... CS35L35_PWRCTL3:
+	case CS35L35_CLK_CTL1 ... CS35L35_SP_FMT_CTL3:
+	case CS35L35_MAG_COMP_CTL ... CS35L35_AMP_GAIN_AUD_CTL:
+	case CS35L35_AMP_GAIN_PDM_CTL ... CS35L35_BST_PEAK_I:
+	case CS35L35_BST_RAMP_CTL ... CS35L35_BST_CONV_SW_FREQ:
+	case CS35L35_CLASS_H_CTL ... CS35L35_CLASS_H_VP_CTL:
+	case CS35L35_CLASS_H_STATUS:
+	case CS35L35_VPBR_CTL ... CS35L35_VPBR_MODE_VOL_CTL:
+	case CS35L35_VPBR_ATTEN_STATUS:
+	case CS35L35_SPKR_MON_CTL:
+	case CS35L35_IMON_SCALE_CTL ... CS35L35_ZEROFILL_DEPTH_CTL:
+	case CS35L35_MULT_DEV_SYNCH1 ... CS35L35_PROT_RELEASE_CTL:
+	case CS35L35_DIAG_MODE_REG_LOCK ... CS35L35_DIAG_MODE_CTL_2:
+	case CS35L35_INT_MASK_1 ... CS35L35_PLL_STATUS:
+	case CS35L35_OTP_TRIM_STATUS:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool cs35l35_precious_register(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case CS35L35_INT_STATUS_1:
+	case CS35L35_INT_STATUS_2:
+	case CS35L35_INT_STATUS_3:
+	case CS35L35_INT_STATUS_4:
+	case CS35L35_PLL_STATUS:
+	case CS35L35_OTP_TRIM_STATUS:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static int cs35l35_sdin_event(struct snd_soc_dapm_widget *w,
+		struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm);
+	struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec);
+	int ret = 0;
+
+	switch (event) {
+	case SND_SOC_DAPM_PRE_PMU:
+		regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1,
+			CS35L35_MCLK_DIS_MASK, 0 << CS35L35_MCLK_DIS_SHIFT);
+		regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1,
+			CS35L35_DISCHG_FILT_MASK, 0 << CS35L35_DISCHG_FILT_SHIFT);
+		regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1,
+			CS35L35_PDN_ALL_MASK, 0);
+	break;
+	case SND_SOC_DAPM_POST_PMD:
+		regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1,
+			CS35L35_PDN_ALL_MASK, 1);
+		regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1,
+			CS35L35_DISCHG_FILT_MASK, 1 << CS35L35_DISCHG_FILT_SHIFT);
+
+		ret = wait_for_completion_timeout(&cs35l35->pdn_done,
+							msecs_to_jiffies(100));
+		if (ret == 0) {
+			pr_err("TIMEOUT PDN_DONE did not complete in 100ms\n");
+			ret = -ETIMEDOUT;
+		}
+
+		regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1,
+			CS35L35_MCLK_DIS_MASK, 1 << CS35L35_MCLK_DIS_SHIFT);
+	break;
+	default:
+		pr_err("Invalid event = 0x%x\n", event);
+		ret = -EINVAL;
+	}
+	return ret;
+}
+
+static int cs35l35_main_amp_event(struct snd_soc_dapm_widget *w,
+		struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm);
+	struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec);
+	unsigned int reg[4];
+	int i;
+
+	switch (event) {
+	case SND_SOC_DAPM_PRE_PMU:
+		if (cs35l35->pdata.bst_pdn_fet_on)
+			regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2,
+				CS35L35_PDN_BST_MASK, 0 << CS35L35_PDN_BST_FETON_SHIFT);
+		else
+			regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2,
+				CS35L35_PDN_BST_MASK, 0 << CS35L35_PDN_BST_FETOFF_SHIFT);
+			regmap_update_bits(cs35l35->regmap, CS35L35_PROTECT_CTL,
+				CS35L35_AMP_MUTE_MASK, 0 << CS35L35_AMP_MUTE_SHIFT);
+		break;
+	case SND_SOC_DAPM_POST_PMU:
+		usleep_range(5000, 5100);
+		/* If PDM mode we must use VP
+		 * for Voltage control
+		 */
+		if (cs35l35->pdm_mode)
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_BST_CVTR_V_CTL, CS35L35_BST_CTL_MASK,
+				0 << CS35L35_BST_CTL_SHIFT);
+		for (i = 0; i < 2; i++)
+			regmap_bulk_read(cs35l35->regmap, CS35L35_INT_STATUS_1,
+				&reg, ARRAY_SIZE(reg));
+		break;
+	case SND_SOC_DAPM_PRE_PMD:
+		regmap_update_bits(cs35l35->regmap, CS35L35_PROTECT_CTL,
+			CS35L35_AMP_MUTE_MASK, 1 << CS35L35_AMP_MUTE_SHIFT);
+		if (cs35l35->pdata.bst_pdn_fet_on)
+			regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2,
+				CS35L35_PDN_BST_MASK, 1 << CS35L35_PDN_BST_FETON_SHIFT);
+		else
+			regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2,
+				CS35L35_PDN_BST_MASK, 1 << CS35L35_PDN_BST_FETOFF_SHIFT);
+		break;
+	case SND_SOC_DAPM_POST_PMD:
+		usleep_range(5000, 5100);
+		/* If PDM mode we should switch back to pdata value
+		 * for Voltage control when we go down
+		 */
+		if (cs35l35->pdm_mode)
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_BST_CVTR_V_CTL, CS35L35_BST_CTL_MASK,
+				cs35l35->pdata.bst_vctl << CS35L35_BST_CTL_SHIFT);
+
+		break;
+	default:
+		pr_err("Invalid event = 0x%x\n", event);
+	}
+	return 0;
+}
+
+static DECLARE_TLV_DB_SCALE(amp_gain_tlv, 0, 1, 1);
+static DECLARE_TLV_DB_SCALE(dig_vol_tlv, -10200, 50, 0);
+
+static const struct snd_kcontrol_new cs35l35_aud_controls[] = {
+	SOC_SINGLE_SX_TLV("Digital Audio Volume", CS35L35_AMP_DIG_VOL,
+		      0, 0x34, 0xE4, dig_vol_tlv),
+	SOC_SINGLE_TLV("AMP Audio Gain", CS35L35_AMP_GAIN_AUD_CTL, 0, 19, 0,
+			amp_gain_tlv),
+	SOC_SINGLE_TLV("AMP PDM Gain", CS35L35_AMP_GAIN_PDM_CTL, 0, 19, 0,
+			amp_gain_tlv),
+};
+
+static const struct snd_kcontrol_new cs35l35_adv_controls[] = {
+	SOC_SINGLE_SX_TLV("Digital Advisory Volume", CS35L35_ADV_DIG_VOL,
+		      0, 0x34, 0xE4, dig_vol_tlv),
+	SOC_SINGLE_TLV("AMP Advisory Gain", CS35L35_AMP_GAIN_ADV_CTL, 0, 19, 0,
+			amp_gain_tlv),
+};
+
+static const struct snd_soc_dapm_widget cs35l35_dapm_widgets[] = {
+	SND_SOC_DAPM_AIF_IN_E("SDIN", NULL, 0, CS35L35_PWRCTL3, 1, 1,
+				cs35l35_sdin_event, SND_SOC_DAPM_PRE_PMU |
+				SND_SOC_DAPM_POST_PMD),
+	SND_SOC_DAPM_AIF_OUT("SDOUT", NULL, 0, CS35L35_PWRCTL3, 2, 1),
+
+	SND_SOC_DAPM_OUTPUT("SPK"),
+
+	SND_SOC_DAPM_INPUT("VP"),
+	SND_SOC_DAPM_INPUT("VBST"),
+	SND_SOC_DAPM_INPUT("ISENSE"),
+	SND_SOC_DAPM_INPUT("VSENSE"),
+
+	SND_SOC_DAPM_ADC("VMON ADC", NULL, CS35L35_PWRCTL2, 7, 1),
+	SND_SOC_DAPM_ADC("IMON ADC", NULL, CS35L35_PWRCTL2, 6, 1),
+	SND_SOC_DAPM_ADC("VPMON ADC", NULL, CS35L35_PWRCTL3, 3, 1),
+	SND_SOC_DAPM_ADC("VBSTMON ADC", NULL, CS35L35_PWRCTL3, 4, 1),
+	SND_SOC_DAPM_ADC("CLASS H", NULL, CS35L35_PWRCTL2, 5, 1),
+
+	SND_SOC_DAPM_OUT_DRV_E("Main AMP", CS35L35_PWRCTL2, 0, 1, NULL, 0,
+		cs35l35_main_amp_event, SND_SOC_DAPM_PRE_PMU |
+				SND_SOC_DAPM_POST_PMD | SND_SOC_DAPM_POST_PMU |
+				SND_SOC_DAPM_PRE_PMD),
+};
+
+static const struct snd_soc_dapm_route cs35l35_audio_map[] = {
+	{"VPMON ADC", NULL, "VP"},
+	{"VBSTMON ADC", NULL, "VBST"},
+	{"IMON ADC", NULL, "ISENSE"},
+	{"VMON ADC", NULL, "VSENSE"},
+	{"SDOUT", NULL, "IMON ADC"},
+	{"SDOUT", NULL, "VMON ADC"},
+	{"SDOUT", NULL, "VBSTMON ADC"},
+	{"SDOUT", NULL, "VPMON ADC"},
+	{"AMP Capture", NULL, "SDOUT"},
+
+	{"SDIN", NULL, "AMP Playback"},
+	{"CLASS H", NULL, "SDIN"},
+	{"Main AMP", NULL, "CLASS H"},
+	{"SPK", NULL, "Main AMP"},
+};
+
+static int cs35l35_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec);
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1,
+				    CS35L35_MS_MASK, 1 << CS35L35_MS_SHIFT);
+		cs35l35->slave_mode = false;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1,
+				    CS35L35_MS_MASK, 0 << CS35L35_MS_SHIFT);
+		cs35l35->slave_mode = true;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		cs35l35->i2s_mode = true;
+		cs35l35->pdm_mode = false;
+		break;
+	case SND_SOC_DAIFMT_PDM:
+		cs35l35->pdm_mode = true;
+		cs35l35->i2s_mode = false;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+struct cs35l35_sysclk_config {
+	int sysclk;
+	int srate;
+	u8 clk_cfg;
+};
+
+static struct cs35l35_sysclk_config cs35l35_clk_ctl[] = {
+
+	/* SYSCLK, Sample Rate, Serial Port Cfg */
+	{5644800, 44100, 0x00},
+	{5644800, 88200, 0x40},
+	{6144000, 48000, 0x10},
+	{6144000, 96000, 0x50},
+	{11289600, 44100, 0x01},
+	{11289600, 88200, 0x41},
+	{11289600, 176400, 0x81},
+	{12000000, 44100, 0x03},
+	{12000000, 48000, 0x13},
+	{12000000, 88200, 0x43},
+	{12000000, 96000, 0x53},
+	{12000000, 176400, 0x83},
+	{12000000, 192000, 0x93},
+	{12288000, 48000, 0x11},
+	{12288000, 96000, 0x51},
+	{12288000, 192000, 0x91},
+	{13000000, 44100, 0x07},
+	{13000000, 48000, 0x17},
+	{13000000, 88200, 0x47},
+	{13000000, 96000, 0x57},
+	{13000000, 176400, 0x87},
+	{13000000, 192000, 0x97},
+	{22579200, 44100, 0x02},
+	{22579200, 88200, 0x42},
+	{22579200, 176400, 0x82},
+	{24000000, 44100, 0x0B},
+	{24000000, 48000, 0x1B},
+	{24000000, 88200, 0x4B},
+	{24000000, 96000, 0x5B},
+	{24000000, 176400, 0x8B},
+	{24000000, 192000, 0x9B},
+	{24576000, 48000, 0x12},
+	{24576000, 96000, 0x52},
+	{24576000, 192000, 0x92},
+	{26000000, 44100, 0x0F},
+	{26000000, 48000, 0x1F},
+	{26000000, 88200, 0x4F},
+	{26000000, 96000, 0x5F},
+	{26000000, 176400, 0x8F},
+	{26000000, 192000, 0x9F},
+};
+
+static int cs35l35_get_clk_config(int sysclk, int srate)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(cs35l35_clk_ctl); i++) {
+		if (cs35l35_clk_ctl[i].sysclk == sysclk &&
+			cs35l35_clk_ctl[i].srate == srate)
+			return cs35l35_clk_ctl[i].clk_cfg;
+	}
+	return -EINVAL;
+}
+
+static int cs35l35_pcm_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 cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec);
+	struct classh_cfg *classh = &cs35l35->pdata.classh_algo;
+	int srate = params_rate(params);
+	int ret = 0;
+	u8 sp_sclks;
+	int audin_format;
+	int errata_chk;
+
+	int clk_ctl = cs35l35_get_clk_config(cs35l35->sysclk, srate);
+
+	if (clk_ctl < 0) {
+		dev_err(codec->dev, "Invalid CLK:Rate %d:%d\n",
+			cs35l35->sysclk, srate);
+		return -EINVAL;
+	}
+
+	ret = regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL2,
+			  CS35L35_CLK_CTL2_MASK, clk_ctl);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to set port config %d\n", ret);
+		return ret;
+	}
+
+	/* Rev A0 Errata
+	 *
+	 * When configured for the weak-drive detection path (CH_WKFET_DIS = 0)
+	 * the Class H algorithm does not enable weak-drive operation for
+	 * nonzero values of CH_WKFET_DELAY if SP_RATE = 01 or 10
+	 *
+	 */
+	errata_chk = clk_ctl & CS35L35_SP_RATE_MASK;
+
+	if (classh->classh_wk_fet_disable == 0x00 &&
+		(errata_chk == 0x01 || errata_chk == 0x03)) {
+		ret = regmap_update_bits(cs35l35->regmap,
+			CS35L35_CLASS_H_FET_DRIVE_CTL, CS35L35_CH_WKFET_DEL_MASK,
+			0 << CS35L35_CH_WKFET_DEL_SHIFT);
+		if (ret != 0) {
+			dev_err(codec->dev, "Failed to set fet config %d\n",
+				ret);
+			return ret;
+		}
+	}
+
+/*
+ * You can pull more Monitor data from the SDOUT pin than going to SDIN
+ * Just make sure your SCLK is fast enough to fill the frame
+ */
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		switch (params_width(params)) {
+		case 8:
+			audin_format = CS35L35_SDIN_DEPTH_8;
+			break;
+		case 16:
+			audin_format = CS35L35_SDIN_DEPTH_16;
+			break;
+		case 24:
+			audin_format = CS35L35_SDIN_DEPTH_24;
+			break;
+		default:
+			dev_err(codec->dev, "Unsupported Width %d\n",
+				params_width(params));
+		}
+		regmap_update_bits(cs35l35->regmap,
+			CS35L35_AUDIN_DEPTH_CTL, CS35L35_AUDIN_DEPTH_MASK,
+			audin_format << CS35L35_AUDIN_DEPTH_SHIFT);
+		if (cs35l35->pdata.stereo) {
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_AUDIN_DEPTH_CTL, CS35L35_ADVIN_DEPTH_MASK,
+				audin_format << CS35L35_ADVIN_DEPTH_SHIFT);
+		}
+	}
+/* We have to take the SCLK to derive num sclks
+ * to configure the CLOCK_CTL3 register correctly
+ */
+	if ((cs35l35->sclk / srate) % 4) {
+		dev_err(codec->dev, "Unsupported sclk/fs ratio %d:%d\n",
+					cs35l35->sclk, srate);
+		return -EINVAL;
+	}
+	sp_sclks = ((cs35l35->sclk / srate) / 4) - 1;
+
+	if (cs35l35->i2s_mode) {
+		/* Only certain ratios are supported in I2S Slave Mode */
+		if (cs35l35->slave_mode) {
+			switch (sp_sclks) {
+			case CS35L35_SP_SCLKS_32FS:
+			case CS35L35_SP_SCLKS_48FS:
+			case CS35L35_SP_SCLKS_64FS:
+			break;
+			default:
+				dev_err(codec->dev, "ratio not supported\n");
+				return -EINVAL;
+			};
+		} else {
+			/* Only certain ratios supported in I2S MASTER Mode */
+			switch (sp_sclks) {
+			case CS35L35_SP_SCLKS_32FS:
+			case CS35L35_SP_SCLKS_64FS:
+			break;
+			default:
+				dev_err(codec->dev, "ratio not supported\n");
+				return -EINVAL;
+			};
+		}
+		ret = regmap_update_bits(cs35l35->regmap,
+			CS35L35_CLK_CTL3, CS35L35_SP_SCLKS_MASK,
+			sp_sclks << CS35L35_SP_SCLKS_SHIFT);
+		if (ret != 0) {
+			dev_err(codec->dev, "Failed to set fsclk %d\n", ret);
+			return ret;
+		}
+	}
+	if (cs35l35->pdm_mode) {
+		regmap_update_bits(cs35l35->regmap, CS35L35_AMP_INP_DRV_CTL,
+			CS35L35_PDM_MODE_MASK, 1 << CS35L35_PDM_MODE_SHIFT);
+	} else {
+		regmap_update_bits(cs35l35->regmap, CS35L35_AMP_INP_DRV_CTL,
+			CS35L35_PDM_MODE_MASK, 0 << CS35L35_PDM_MODE_SHIFT);
+	}
+	return ret;
+}
+
+static const unsigned int cs35l35_src_rates[] = {
+	44100, 48000, 88200, 96000, 176400, 192000
+};
+
+static const struct snd_pcm_hw_constraint_list cs35l35_constraints = {
+	.count  = ARRAY_SIZE(cs35l35_src_rates),
+	.list   = cs35l35_src_rates,
+};
+
+static int cs35l35_pcm_startup(struct snd_pcm_substream *substream,
+			       struct snd_soc_dai *dai)
+{
+	snd_pcm_hw_constraint_list(substream->runtime, 0,
+				SNDRV_PCM_HW_PARAM_RATE, &cs35l35_constraints);
+	return 0;
+}
+
+static const unsigned int cs35l35_pdm_rates[] = {
+	44100, 48000, 88200, 96000
+};
+
+static const struct snd_pcm_hw_constraint_list cs35l35_pdm_constraints = {
+	.count  = ARRAY_SIZE(cs35l35_pdm_rates),
+	.list   = cs35l35_pdm_rates,
+};
+
+static int cs35l35_pdm_startup(struct snd_pcm_substream *substream,
+			       struct snd_soc_dai *dai)
+{
+	snd_pcm_hw_constraint_list(substream->runtime, 0,
+				SNDRV_PCM_HW_PARAM_RATE,
+				&cs35l35_pdm_constraints);
+	return 0;
+}
+
+static int cs35l35_dai_set_sysclk(struct snd_soc_dai *dai,
+				int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec);
+
+	/* Need the SCLK Frequency */
+	cs35l35->sclk = freq;
+
+	return 0;
+}
+
+static const struct snd_soc_dai_ops cs35l35_ops = {
+	.startup = cs35l35_pcm_startup,
+	.set_fmt = cs35l35_set_dai_fmt,
+	.hw_params = cs35l35_pcm_hw_params,
+	.set_sysclk = cs35l35_dai_set_sysclk,
+};
+
+static const struct snd_soc_dai_ops cs35l35_pdm_ops = {
+	.startup = cs35l35_pdm_startup,
+	.set_fmt = cs35l35_set_dai_fmt,
+	.hw_params = cs35l35_pcm_hw_params,
+	.set_sysclk = cs35l35_dai_set_sysclk,
+};
+
+static struct snd_soc_dai_driver cs35l35_dai[] = {
+	{
+		.name = "cs35l35-pcm",
+		.id = 0,
+		.playback = {
+			.stream_name = "AMP Playback",
+			.channels_min = 1,
+			.channels_max = 8,
+			.rates = SNDRV_PCM_RATE_KNOT,
+			.formats = CS35L35_FORMATS,
+		},
+		.capture = {
+			.stream_name = "AMP Capture",
+			.channels_min = 1,
+			.channels_max = 8,
+			.rates = SNDRV_PCM_RATE_KNOT,
+			.formats = CS35L35_FORMATS,
+		},
+		.ops = &cs35l35_ops,
+		.symmetric_rates = 1,
+	},
+	{
+		.name = "cs35l35-pdm",
+		.id = 1,
+		.playback = {
+			.stream_name = "PDM Playback",
+			.channels_min = 1,
+			.channels_max = 2,
+			.rates = SNDRV_PCM_RATE_KNOT,
+			.formats = CS35L35_FORMATS,
+		},
+		.ops = &cs35l35_pdm_ops,
+	},
+};
+
+static int cs35l35_codec_set_sysclk(struct snd_soc_codec *codec,
+				int clk_id, int source, unsigned int freq,
+				int dir)
+{
+	struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec);
+	int clksrc;
+	int ret = 0;
+
+	switch (clk_id) {
+	case 0:
+		clksrc = CS35L35_CLK_SOURCE_MCLK;
+		break;
+	case 1:
+		clksrc = CS35L35_CLK_SOURCE_SCLK;
+		break;
+	case 2:
+		clksrc = CS35L35_CLK_SOURCE_PDM;
+		break;
+	default:
+		dev_err(codec->dev, "Invalid CLK Source\n");
+		return -EINVAL;
+	};
+
+	switch (freq) {
+	case 5644800:
+	case 6144000:
+	case 11289600:
+	case 12000000:
+	case 12288000:
+	case 13000000:
+	case 22579200:
+	case 24000000:
+	case 24576000:
+	case 26000000:
+		cs35l35->sysclk = freq;
+		break;
+	default:
+		dev_err(codec->dev, "Invalid CLK Frequency\n");
+		return -EINVAL;
+	}
+
+	ret = regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1,
+		CS35L35_CLK_SOURCE_MASK, clksrc << CS35L35_CLK_SOURCE_SHIFT);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to set sysclk %d\n", ret);
+		return ret;
+	}
+
+	return ret;
+}
+
+static int cs35l35_codec_probe(struct snd_soc_codec *codec)
+{
+	struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec);
+	struct classh_cfg *classh = &cs35l35->pdata.classh_algo;
+	struct monitor_cfg *monitor_config = &cs35l35->pdata.mon_cfg;
+	int ret;
+
+	/* Set Platform Data */
+	if (cs35l35->pdata.bst_vctl)
+		regmap_update_bits(cs35l35->regmap, CS35L35_BST_CVTR_V_CTL,
+			CS35L35_BST_CTL_MASK, cs35l35->pdata.bst_vctl);
+
+	if (cs35l35->pdata.bst_ipk)
+		regmap_update_bits(cs35l35->regmap, CS35L35_BST_PEAK_I,
+			CS35L35_BST_IPK_MASK,
+			cs35l35->pdata.bst_ipk << CS35L35_BST_IPK_SHIFT);
+
+	if (cs35l35->pdata.gain_zc)
+		regmap_update_bits(cs35l35->regmap, CS35L35_PROTECT_CTL,
+			CS35L35_AMP_GAIN_ZC_MASK,
+			cs35l35->pdata.gain_zc << CS35L35_AMP_GAIN_ZC_SHIFT);
+
+	if (cs35l35->pdata.aud_channel)
+		regmap_update_bits(cs35l35->regmap,
+			CS35L35_AUDIN_RXLOC_CTL,
+			CS35L35_AUD_IN_LR_MASK,
+			cs35l35->pdata.aud_channel << CS35L35_AUD_IN_LR_SHIFT);
+
+	if (cs35l35->pdata.stereo) {
+		regmap_update_bits(cs35l35->regmap,
+			CS35L35_ADVIN_RXLOC_CTL, CS35L35_ADV_IN_LR_MASK,
+			cs35l35->pdata.adv_channel << CS35L35_ADV_IN_LR_SHIFT);
+		if (cs35l35->pdata.shared_bst)
+			regmap_update_bits(cs35l35->regmap, CS35L35_CLASS_H_CTL,
+				CS35L35_CH_STEREO_MASK, 1 << CS35L35_CH_STEREO_SHIFT);
+		ret = snd_soc_add_codec_controls(codec, cs35l35_adv_controls,
+					ARRAY_SIZE(cs35l35_adv_controls));
+		if (ret)
+			return ret;
+	}
+
+	if (cs35l35->pdata.sp_drv_str)
+		regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1,
+			CS35L35_SP_DRV_MASK,
+			cs35l35->pdata.sp_drv_str << CS35L35_SP_DRV_SHIFT);
+
+	if (classh->classh_algo_enable) {
+		if (classh->classh_bst_override)
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_CLASS_H_CTL, CS35L35_CH_BST_OVR_MASK,
+				classh->classh_bst_override << CS35L35_CH_BST_OVR_SHIFT);
+		if (classh->classh_bst_max_limit)
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_CLASS_H_CTL, CS35L35_CH_BST_LIM_MASK,
+				classh->classh_bst_max_limit << CS35L35_CH_BST_LIM_SHIFT);
+		if (classh->classh_mem_depth)
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_CLASS_H_CTL, CS35L35_CH_MEM_DEPTH_MASK,
+				classh->classh_mem_depth << CS35L35_CH_MEM_DEPTH_SHIFT);
+		if (classh->classh_headroom)
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_CLASS_H_HEADRM_CTL, CS35L35_CH_HDRM_CTL_MASK,
+				classh->classh_headroom << CS35L35_CH_HDRM_CTL_SHIFT);
+		if (classh->classh_release_rate)
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_CLASS_H_RELEASE_RATE, CS35L35_CH_REL_RATE_MASK,
+				classh->classh_release_rate << CS35L35_CH_REL_RATE_SHIFT);
+		if (classh->classh_wk_fet_disable)
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_CLASS_H_FET_DRIVE_CTL, CS35L35_CH_WKFET_DIS_MASK,
+				classh->classh_wk_fet_disable << CS35L35_CH_WKFET_DIS_SHIFT);
+		if (classh->classh_wk_fet_delay)
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_CLASS_H_FET_DRIVE_CTL, CS35L35_CH_WKFET_DEL_MASK,
+				classh->classh_wk_fet_delay << CS35L35_CH_WKFET_DEL_SHIFT);
+		if (classh->classh_wk_fet_thld)
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_CLASS_H_FET_DRIVE_CTL, CS35L35_CH_WKFET_THLD_MASK,
+				classh->classh_wk_fet_thld << CS35L35_CH_WKFET_THLD_SHIFT);
+		if (classh->classh_vpch_auto)
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_CLASS_H_VP_CTL, CS35L35_CH_VP_AUTO_MASK,
+				classh->classh_vpch_auto << CS35L35_CH_VP_AUTO_SHIFT);
+		if (classh->classh_vpch_rate)
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_CLASS_H_VP_CTL, CS35L35_CH_VP_RATE_MASK,
+				classh->classh_vpch_rate << CS35L35_CH_VP_RATE_SHIFT);
+		if (classh->classh_vpch_man)
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_CLASS_H_VP_CTL, CS35L35_CH_VP_MAN_MASK,
+				classh->classh_vpch_man << CS35L35_CH_VP_MAN_SHIFT);
+	}
+
+	if (monitor_config->is_present) {
+		if (monitor_config->vmon_specs) {
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_SPKMON_DEPTH_CTL, CS35L35_VMON_DEPTH_MASK,
+				monitor_config->vmon_dpth << CS35L35_VMON_DEPTH_SHIFT);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_VMON_TXLOC_CTL, CS35L35_MON_TXLOC_MASK,
+				monitor_config->vmon_loc << CS35L35_MON_TXLOC_SHIFT);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_VMON_TXLOC_CTL, CS35L35_MON_FRM_MASK,
+				monitor_config->vmon_frm << CS35L35_MON_FRM_SHIFT);
+		}
+		if (monitor_config->imon_specs) {
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_SPKMON_DEPTH_CTL, CS35L35_IMON_DEPTH_MASK,
+				monitor_config->imon_dpth << CS35L35_IMON_DEPTH_SHIFT);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_IMON_TXLOC_CTL, CS35L35_MON_TXLOC_MASK,
+				monitor_config->imon_loc << CS35L35_MON_TXLOC_SHIFT);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_IMON_TXLOC_CTL, CS35L35_MON_FRM_MASK,
+				monitor_config->imon_frm << CS35L35_MON_FRM_SHIFT);
+		}
+		if (monitor_config->vpmon_specs) {
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_SUPMON_DEPTH_CTL, CS35L35_VPMON_DEPTH_MASK,
+				monitor_config->vpmon_dpth << CS35L35_VPMON_DEPTH_SHIFT);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_VPMON_TXLOC_CTL, CS35L35_MON_TXLOC_MASK,
+				monitor_config->vpmon_loc << CS35L35_MON_TXLOC_SHIFT);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_VPMON_TXLOC_CTL, CS35L35_MON_FRM_MASK,
+				monitor_config->vpmon_frm << CS35L35_MON_FRM_SHIFT);
+		}
+		if (monitor_config->vbstmon_specs) {
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_SUPMON_DEPTH_CTL, CS35L35_VBSTMON_DEPTH_MASK,
+				monitor_config->vpmon_dpth << CS35L35_VBSTMON_DEPTH_SHIFT);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_VBSTMON_TXLOC_CTL, CS35L35_MON_TXLOC_MASK,
+				monitor_config->vbstmon_loc << CS35L35_MON_TXLOC_SHIFT);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_VBSTMON_TXLOC_CTL, CS35L35_MON_FRM_MASK,
+				monitor_config->vbstmon_frm << CS35L35_MON_FRM_SHIFT);
+		}
+		if (monitor_config->vpbrstat_specs) {
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_SUPMON_DEPTH_CTL, CS35L35_VPBRSTAT_DEPTH_MASK,
+				monitor_config->vpbrstat_dpth << CS35L35_VPBRSTAT_DEPTH_SHIFT);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_VPBR_STATUS_TXLOC_CTL, CS35L35_MON_TXLOC_MASK,
+				monitor_config->vpbrstat_loc << CS35L35_MON_TXLOC_SHIFT);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_VPBR_STATUS_TXLOC_CTL, CS35L35_MON_FRM_MASK,
+				monitor_config->vpbrstat_frm << CS35L35_MON_FRM_SHIFT);
+		}
+		if (monitor_config->zerofill_specs) {
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_SUPMON_DEPTH_CTL, CS35L35_ZEROFILL_DEPTH_MASK,
+				monitor_config->zerofill_dpth << CS35L35_ZEROFILL_DEPTH_SHIFT);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_ZERO_FILL_LOC_CTL, CS35L35_MON_TXLOC_MASK,
+				monitor_config->zerofill_loc << CS35L35_MON_TXLOC_SHIFT);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_ZERO_FILL_LOC_CTL, CS35L35_MON_FRM_MASK,
+				monitor_config->zerofill_frm << CS35L35_MON_FRM_SHIFT);
+		}
+	}
+
+	return ret;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_cs35l35 = {
+	.probe = cs35l35_codec_probe,
+	.set_sysclk = cs35l35_codec_set_sysclk,
+	.component_driver = {
+		.controls = cs35l35_aud_controls,
+		.num_controls = ARRAY_SIZE(cs35l35_aud_controls),
+		.dapm_widgets = cs35l35_dapm_widgets,
+		.num_dapm_widgets = ARRAY_SIZE(cs35l35_dapm_widgets),
+
+		.dapm_routes = cs35l35_audio_map,
+		.num_dapm_routes = ARRAY_SIZE(cs35l35_audio_map),
+	},
+};
+
+static struct regmap_config cs35l35_regmap = {
+	.reg_bits = 8,
+	.val_bits = 8,
+
+	.max_register = CS35L35_MAX_REGISTER,
+	.reg_defaults = cs35l35_reg,
+	.num_reg_defaults = ARRAY_SIZE(cs35l35_reg),
+	.volatile_reg = cs35l35_volatile_register,
+	.readable_reg = cs35l35_readable_register,
+	.precious_reg = cs35l35_precious_register,
+	.cache_type = REGCACHE_RBTREE,
+};
+
+static irqreturn_t cs35l35_irq(int irq, void *data)
+{
+	struct cs35l35_private *cs35l35 = data;
+	unsigned int sticky1, sticky2, sticky3, sticky4;
+	unsigned int mask1, mask2, mask3, mask4, current1;
+
+	/* ack the irq by reading all status registers */
+	regmap_read(cs35l35->regmap, CS35L35_INT_STATUS_4, &sticky4);
+	regmap_read(cs35l35->regmap, CS35L35_INT_STATUS_3, &sticky3);
+	regmap_read(cs35l35->regmap, CS35L35_INT_STATUS_2, &sticky2);
+	regmap_read(cs35l35->regmap, CS35L35_INT_STATUS_1, &sticky1);
+
+	regmap_read(cs35l35->regmap, CS35L35_INT_MASK_4, &mask4);
+	regmap_read(cs35l35->regmap, CS35L35_INT_MASK_3, &mask3);
+	regmap_read(cs35l35->regmap, CS35L35_INT_MASK_2, &mask2);
+	regmap_read(cs35l35->regmap, CS35L35_INT_MASK_1, &mask1);
+
+	/* Check to see if unmasked bits are active */
+	if (!(sticky1 & ~mask1) && !(sticky2 & ~mask2) && !(sticky3 & ~mask3)
+			&& !(sticky4 & ~mask4))
+		return IRQ_NONE;
+
+	if (sticky2 & CS35L35_PDN_DONE)
+		complete(&cs35l35->pdn_done);
+
+	/* read the current values */
+	regmap_read(cs35l35->regmap, CS35L35_INT_STATUS_1, &current1);
+
+	/* handle the interrupts */
+	if (sticky1 & CS35L35_CAL_ERR) {
+		pr_err("%s : Calibration Error\n", __func__);
+
+		/* error is no longer asserted; safe to reset */
+		if (!(current1 & CS35L35_CAL_ERR)) {
+			pr_debug("%s : Cal error release\n", __func__);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_PROT_RELEASE_CTL, CS35L35_CAL_ERR_RLS, 0);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_PROT_RELEASE_CTL, CS35L35_CAL_ERR_RLS,
+				CS35L35_CAL_ERR_RLS);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_PROT_RELEASE_CTL, CS35L35_CAL_ERR_RLS, 0);
+		}
+	}
+
+	if (sticky1 & CS35L35_AMP_SHORT) {
+		/* error is no longer asserted; safe to reset */
+		if (!(current1 & CS35L35_AMP_SHORT)) {
+			pr_debug("%s :Amp short error release\n", __func__);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_PROT_RELEASE_CTL, CS35L35_SHORT_RLS, 0);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_PROT_RELEASE_CTL, CS35L35_SHORT_RLS,
+				CS35L35_SHORT_RLS);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_PROT_RELEASE_CTL, CS35L35_SHORT_RLS, 0);
+		}
+	}
+
+	if (sticky1 & CS35L35_OTW) {
+		pr_err("%s : Over temperature warning\n", __func__);
+
+		/* error is no longer asserted; safe to reset */
+		if (!(current1 & CS35L35_OTW)) {
+			pr_debug("%s : Over temperature warning release\n",
+				__func__);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_PROT_RELEASE_CTL, CS35L35_OTW_RLS, 0);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_PROT_RELEASE_CTL, CS35L35_OTW_RLS,
+				CS35L35_OTW_RLS);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_PROT_RELEASE_CTL, CS35L35_OTW_RLS, 0);
+		}
+	}
+
+	if (sticky1 & CS35L35_OTE) {
+		pr_crit("%s : Over temperature error\n", __func__);
+
+		/* error is no longer asserted; safe to reset */
+		if (!(current1 & CS35L35_OTE)) {
+			pr_debug("%s : Over temperature error release\n",
+				__func__);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_PROT_RELEASE_CTL, CS35L35_OTE_RLS, 0);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_PROT_RELEASE_CTL, CS35L35_OTE_RLS,
+				CS35L35_OTE_RLS);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_PROT_RELEASE_CTL, CS35L35_OTE_RLS, 0);
+		}
+	}
+
+	if (sticky3 & CS35L35_BST_HIGH) {
+		pr_crit("%s : VBST error: powering off!\n", __func__);
+		regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2,
+			CS35L35_PDN_AMP, CS35L35_PDN_AMP);
+		regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1,
+			CS35L35_PDN_ALL, CS35L35_PDN_ALL);
+	}
+
+	if (sticky3 & CS35L35_LBST_SHORT) {
+		pr_crit("%s : LBST error: powering off!\n", __func__);
+		regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2,
+			CS35L35_PDN_AMP, CS35L35_PDN_AMP);
+		regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1,
+			CS35L35_PDN_ALL, CS35L35_PDN_ALL);
+	}
+
+	if (sticky2 & CS35L35_VPBR_ERR)
+		pr_err("%s : Error: Reactive Brownout\n", __func__);
+
+	if (sticky4 & CS35L35_VMON_OVFL)
+		pr_err("%s : Error: VMON overflow\n", __func__);
+
+	if (sticky4 & CS35L35_IMON_OVFL)
+		pr_err("%s : Error: IMON overflow\n", __func__);
+
+	return IRQ_HANDLED;
+}
+
+
+static int cs35l35_handle_of_data(struct i2c_client *i2c_client,
+				struct cs35l35_platform_data *pdata)
+{
+	struct device_node *np = i2c_client->dev.of_node;
+	struct device_node *classh, *signal_format;
+	struct classh_cfg *classh_config = &pdata->classh_algo;
+	struct monitor_cfg *monitor_config = &pdata->mon_cfg;
+	unsigned int val32 = 0;
+	u8 monitor_array[3];
+	int ret = 0;
+
+	if (!np)
+		return 0;
+
+	pdata->bst_pdn_fet_on = of_property_read_bool(np,
+					"cirrus,boost-pdn-fet-on");
+
+	if (of_property_read_u32(np, "cirrus,boost-ctl-millivolt", &val32) >= 0)
+		pdata->bst_vctl = val32;
+
+	if (of_property_read_u32(np, "cirrus,boost-ipk-milliamp", &val32) >= 0)
+		pdata->bst_ipk = val32;
+
+	if (of_property_read_u32(np, "cirrus,sp-drv-strength", &val32) >= 0)
+		pdata->sp_drv_str = val32;
+
+	pdata->stereo = of_property_read_bool(np, "cirrus,stereo-config");
+
+	if (pdata->stereo) {
+		if (of_property_read_u32(np, "cirrus,audio-channel", &val32) >= 0)
+			pdata->aud_channel = val32;
+		if (of_property_read_u32(np, "cirrus,advisory-channel",
+					&val32) >= 0)
+			pdata->adv_channel = val32;
+		pdata->shared_bst = of_property_read_bool(np,
+						"cirrus,shared-boost");
+	}
+
+	pdata->gain_zc = of_property_read_bool(np, "cirrus,amp-gain-zc");
+
+	classh = of_get_child_by_name(np, "cirrus,classh-internal-algo");
+	classh_config->classh_algo_enable = classh ? true : false;
+
+	if (classh_config->classh_algo_enable) {
+		classh_config->classh_bst_override =
+			of_property_read_bool(np, "cirrus,classh-bst-overide");
+
+		if (of_property_read_u32(classh, "cirrus,classh-bst-max-limit",
+					&val32) >= 0)
+			classh_config->classh_bst_max_limit = val32;
+		if (of_property_read_u32(classh, "cirrus,classh-mem-depth",
+					&val32) >= 0)
+			classh_config->classh_mem_depth = val32;
+		if (of_property_read_u32(classh, "cirrus,classh-release-rate",
+					&val32) >= 0)
+			classh_config->classh_release_rate = val32;
+		if (of_property_read_u32(classh, "cirrus,classh-headroom",
+					&val32) >= 0)
+			classh_config->classh_headroom = val32;
+		if (of_property_read_u32(classh, "cirrus,classh-wk-fet-disable",
+					&val32) >= 0)
+			classh_config->classh_wk_fet_disable = val32;
+		if (of_property_read_u32(classh, "cirrus,classh-wk-fet-delay",
+					&val32) >= 0)
+			classh_config->classh_wk_fet_delay = val32;
+		if (of_property_read_u32(classh, "cirrus,classh-wk-fet-thld",
+					&val32) >= 0)
+			classh_config->classh_wk_fet_thld = val32;
+		if (of_property_read_u32(classh, "cirrus,classh-vpch-auto",
+					&val32) >= 0)
+			classh_config->classh_vpch_auto = val32;
+		if (of_property_read_u32(classh, "cirrus,classh-vpch-rate",
+					&val32) >= 0)
+			classh_config->classh_vpch_rate = val32;
+		if (of_property_read_u32(classh, "cirrus,classh-vpch-man",
+					&val32) >= 0)
+			classh_config->classh_vpch_man = val32;
+	}
+	of_node_put(classh);
+
+	/* frame depth location */
+	signal_format = of_get_child_by_name(np, "cirrus,monitor-signal-format");
+	monitor_config->is_present = signal_format ? true : false;
+	if (monitor_config->is_present) {
+		ret = of_property_read_u8_array(signal_format, "cirrus,imon",
+				   monitor_array, ARRAY_SIZE(monitor_array));
+		if (!ret) {
+			monitor_config->imon_specs = true;
+			monitor_config->imon_dpth = monitor_array[0];
+			monitor_config->imon_loc = monitor_array[1];
+			monitor_config->imon_frm = monitor_array[2];
+		}
+		ret = of_property_read_u8_array(signal_format, "cirrus,vmon",
+				   monitor_array, ARRAY_SIZE(monitor_array));
+		if (!ret) {
+			monitor_config->vmon_specs = true;
+			monitor_config->vmon_dpth = monitor_array[0];
+			monitor_config->vmon_loc = monitor_array[1];
+			monitor_config->vmon_frm = monitor_array[2];
+		}
+		ret = of_property_read_u8_array(signal_format, "cirrus,vpmon",
+				   monitor_array, ARRAY_SIZE(monitor_array));
+		if (!ret) {
+			monitor_config->vpmon_specs = true;
+			monitor_config->vpmon_dpth = monitor_array[0];
+			monitor_config->vpmon_loc = monitor_array[1];
+			monitor_config->vpmon_frm = monitor_array[2];
+		}
+		ret = of_property_read_u8_array(signal_format, "cirrus,vbstmon",
+				   monitor_array, ARRAY_SIZE(monitor_array));
+		if (!ret) {
+			monitor_config->vbstmon_specs = true;
+			monitor_config->vbstmon_dpth = monitor_array[0];
+			monitor_config->vbstmon_loc = monitor_array[1];
+			monitor_config->vbstmon_frm = monitor_array[2];
+		}
+		ret = of_property_read_u8_array(signal_format, "cirrus,vpbrstat",
+				   monitor_array, ARRAY_SIZE(monitor_array));
+		if (!ret) {
+			monitor_config->vpbrstat_specs = true;
+			monitor_config->vpbrstat_dpth = monitor_array[0];
+			monitor_config->vpbrstat_loc = monitor_array[1];
+			monitor_config->vpbrstat_frm = monitor_array[2];
+		}
+		ret = of_property_read_u8_array(signal_format, "cirrus,zerofill",
+				   monitor_array, ARRAY_SIZE(monitor_array));
+		if (!ret) {
+			monitor_config->zerofill_specs = true;
+			monitor_config->zerofill_dpth = monitor_array[0];
+			monitor_config->zerofill_loc = monitor_array[1];
+			monitor_config->zerofill_frm = monitor_array[2];
+		}
+	}
+	of_node_put(signal_format);
+
+	return 0;
+}
+
+/* Errata Rev A0 */
+static const struct reg_sequence cs35l35_errata_patch[] = {
+
+	{ 0x7F, 0x99 },
+	{ 0x00, 0x99 },
+	{ 0x52, 0x22 },
+	{ 0x04, 0x14 },
+	{ 0x6D, 0x44 },
+	{ 0x24, 0x10 },
+	{ 0x58, 0xC4 },
+	{ 0x00, 0x98 },
+	{ 0x18, 0x08 },
+	{ 0x00, 0x00 },
+	{ 0x7F, 0x00 },
+};
+
+static int cs35l35_i2c_probe(struct i2c_client *i2c_client,
+			      const struct i2c_device_id *id)
+{
+	struct cs35l35_private *cs35l35;
+	struct cs35l35_platform_data *pdata =
+		dev_get_platdata(&i2c_client->dev);
+	int i;
+	int ret;
+	unsigned int devid = 0;
+	unsigned int reg;
+
+	cs35l35 = devm_kzalloc(&i2c_client->dev,
+			       sizeof(struct cs35l35_private),
+			       GFP_KERNEL);
+	if (!cs35l35) {
+		dev_err(&i2c_client->dev, "could not allocate codec\n");
+		return -ENOMEM;
+	}
+
+	i2c_set_clientdata(i2c_client, cs35l35);
+	cs35l35->regmap = devm_regmap_init_i2c(i2c_client, &cs35l35_regmap);
+	if (IS_ERR(cs35l35->regmap)) {
+		ret = PTR_ERR(cs35l35->regmap);
+		dev_err(&i2c_client->dev, "regmap_init() failed: %d\n", ret);
+		goto err;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(cs35l35_supplies); i++)
+		cs35l35->supplies[i].supply = cs35l35_supplies[i];
+		cs35l35->num_supplies = ARRAY_SIZE(cs35l35_supplies);
+
+	ret = devm_regulator_bulk_get(&i2c_client->dev,
+			cs35l35->num_supplies,
+			cs35l35->supplies);
+	if (ret != 0) {
+		dev_err(&i2c_client->dev,
+			"Failed to request core supplies: %d\n",
+			ret);
+		return ret;
+	}
+
+	if (pdata) {
+		cs35l35->pdata = *pdata;
+	} else {
+		pdata = devm_kzalloc(&i2c_client->dev,
+				     sizeof(struct cs35l35_platform_data),
+				GFP_KERNEL);
+		if (!pdata) {
+			dev_err(&i2c_client->dev,
+				"could not allocate pdata\n");
+			return -ENOMEM;
+		}
+		if (i2c_client->dev.of_node) {
+			ret = cs35l35_handle_of_data(i2c_client, pdata);
+			if (ret != 0)
+				return ret;
+
+		}
+		cs35l35->pdata = *pdata;
+	}
+
+	ret = regulator_bulk_enable(cs35l35->num_supplies,
+					cs35l35->supplies);
+	if (ret != 0) {
+		dev_err(&i2c_client->dev,
+			"Failed to enable core supplies: %d\n",
+			ret);
+		return ret;
+	}
+
+	/* returning NULL can be an option if in stereo mode */
+	cs35l35->reset_gpio = devm_gpiod_get_optional(&i2c_client->dev,
+		"reset", GPIOD_OUT_LOW);
+	if (IS_ERR(cs35l35->reset_gpio))
+		return PTR_ERR(cs35l35->reset_gpio);
+
+	if (cs35l35->reset_gpio)
+		gpiod_set_value_cansleep(cs35l35->reset_gpio, 1);
+
+	init_completion(&cs35l35->pdn_done);
+
+	ret = regmap_register_patch(cs35l35->regmap, cs35l35_errata_patch,
+				    ARRAY_SIZE(cs35l35_errata_patch));
+	if (ret < 0) {
+		dev_err(&i2c_client->dev, "Failed to apply errata patch\n");
+		return ret;
+	}
+
+	ret = devm_request_threaded_irq(&i2c_client->dev, i2c_client->irq, NULL,
+			cs35l35_irq, IRQF_ONESHOT | IRQF_TRIGGER_LOW,
+			"cs35l35", cs35l35);
+	if (ret != 0) {
+		dev_err(&i2c_client->dev, "Failed to request IRQ: %d\n", ret);
+		goto err;
+	}
+	/* initialize codec */
+	ret = regmap_read(cs35l35->regmap, CS35L35_DEVID_AB, &reg);
+
+	devid = (reg & 0xFF) << 12;
+	ret = regmap_read(cs35l35->regmap, CS35L35_DEVID_CD, &reg);
+	devid |= (reg & 0xFF) << 4;
+	ret = regmap_read(cs35l35->regmap, CS35L35_DEVID_E, &reg);
+	devid |= (reg & 0xF0) >> 4;
+
+	if (devid != CS35L35_CHIP_ID) {
+		dev_err(&i2c_client->dev,
+			"CS35L35 Device ID (%X). Expected ID %X\n",
+			devid, CS35L35_CHIP_ID);
+		ret = -ENODEV;
+		goto err;
+	}
+
+	ret = regmap_read(cs35l35->regmap, CS35L35_REV_ID, &reg);
+	if (ret < 0) {
+		dev_err(&i2c_client->dev, "Get Revision ID failed\n");
+		goto err;
+	}
+
+	dev_info(&i2c_client->dev,
+		 "Cirrus Logic CS35L35 (%x), Revision: %02X\n", devid,
+		ret & 0xFF);
+
+	/* Set the INT Masks for critical errors */
+	regmap_write(cs35l35->regmap, CS35L35_INT_MASK_1, CS35L35_INT1_CRIT_MASK);
+	regmap_write(cs35l35->regmap, CS35L35_INT_MASK_2, CS35L35_INT2_CRIT_MASK);
+	regmap_write(cs35l35->regmap, CS35L35_INT_MASK_3, CS35L35_INT3_CRIT_MASK);
+	regmap_write(cs35l35->regmap, CS35L35_INT_MASK_4, CS35L35_INT4_CRIT_MASK);
+
+	regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2,
+		CS35L35_PWR2_PDN_MASK, CS35L35_PWR2_PDN_MASK);
+
+	if (cs35l35->pdata.bst_pdn_fet_on)
+		regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2,
+			CS35L35_PDN_BST_MASK, 1 << CS35L35_PDN_BST_FETON_SHIFT);
+	else
+		regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2,
+			CS35L35_PDN_BST_MASK, 1 << CS35L35_PDN_BST_FETOFF_SHIFT);
+
+	regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL3,
+		CS35L35_PWR3_PDN_MASK, CS35L35_PWR3_PDN_MASK);
+
+	regmap_update_bits(cs35l35->regmap, CS35L35_PROTECT_CTL,
+		CS35L35_AMP_MUTE_MASK, 1 << CS35L35_AMP_MUTE_SHIFT);
+
+	ret =  snd_soc_register_codec(&i2c_client->dev,
+			&soc_codec_dev_cs35l35, cs35l35_dai,
+			ARRAY_SIZE(cs35l35_dai));
+	if (ret < 0) {
+		dev_err(&i2c_client->dev,
+			"%s: Register codec failed\n", __func__);
+		goto err;
+	}
+
+err:
+	regulator_bulk_disable(cs35l35->num_supplies,
+			       cs35l35->supplies);
+	return ret;
+}
+
+static int cs35l35_i2c_remove(struct i2c_client *client)
+{
+	snd_soc_unregister_codec(&client->dev);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+static const struct of_device_id cs35l35_of_match[] = {
+	{.compatible = "cirrus,cs35l35"},
+	{},
+};
+MODULE_DEVICE_TABLE(of, cs35l35_of_match);
+
+static const struct i2c_device_id cs35l35_id[] = {
+	{"cs35l35", 0},
+	{}
+};
+
+MODULE_DEVICE_TABLE(i2c, cs35l35_id);
+
+static struct i2c_driver cs35l35_i2c_driver = {
+	.driver = {
+		.name = "cs35l35",
+		.of_match_table = cs35l35_of_match,
+	},
+	.id_table = cs35l35_id,
+	.probe = cs35l35_i2c_probe,
+	.remove = cs35l35_i2c_remove,
+};
+
+module_i2c_driver(cs35l35_i2c_driver);
+
+MODULE_DESCRIPTION("ASoC CS35L35 driver");
+MODULE_AUTHOR("Brian Austin, Cirrus Logic Inc, <brian.austin@cirrus.com>");
+MODULE_AUTHOR("Li Xu, Cirrus Logic Inc, <li.xu@cirrus.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/cs35l35.h b/sound/soc/codecs/cs35l35.h
new file mode 100644
index 0000000..767227e
--- /dev/null
+++ b/sound/soc/codecs/cs35l35.h
@@ -0,0 +1,285 @@
+/*
+ * cs35l35.h -- CS35L35 ALSA SoC audio driver
+ *
+ * Copyright 2016 Cirrus Logic, Inc.
+ *
+ * Author: Brian Austin <brian.austin@cirrus.com>
+ *         Li Xu <li.xu@cirrus.com>
+ *
+ * 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 __CS35L35_H__
+#define __CS35L35_H__
+
+#define CS35L35_FIRSTREG		0x01
+#define CS35L35_LASTREG			0x7E
+#define CS35L35_CHIP_ID			0x00035A35
+#define CS35L35_DEVID_AB		0x01	/* Device ID A & B [RO] */
+#define CS35L35_DEVID_CD		0x02    /* Device ID C & D [RO] */
+#define CS35L35_DEVID_E			0x03    /* Device ID E [RO] */
+#define CS35L35_FAB_ID			0x04	/* Fab ID [RO] */
+#define CS35L35_REV_ID			0x05	/* Revision ID [RO] */
+#define CS35L35_PWRCTL1			0x06    /* Power Ctl 1 */
+#define CS35L35_PWRCTL2			0x07    /* Power Ctl 2 */
+#define CS35L35_PWRCTL3			0x08	/* Power Ctl 3 */
+#define CS35L35_CLK_CTL1		0x0A	/* Clocking Ctl 1 */
+#define CS35L35_CLK_CTL2		0x0B	/* Clocking Ctl 2 */
+#define CS35L35_CLK_CTL3		0x0C	/* Clocking Ctl 3 */
+#define CS35L35_SP_FMT_CTL1		0x0D	/* Serial Port Format CTL1 */
+#define CS35L35_SP_FMT_CTL2		0x0E	/* Serial Port Format CTL2 */
+#define CS35L35_SP_FMT_CTL3		0x0F	/* Serial Port Format CTL3 */
+#define CS35L35_MAG_COMP_CTL		0x13	/* Magnitude Comp CTL */
+#define CS35L35_AMP_INP_DRV_CTL		0x14	/* Amp Input Drive Ctl */
+#define CS35L35_AMP_DIG_VOL_CTL		0x15	/* Amplifier Dig Volume Ctl */
+#define CS35L35_AMP_DIG_VOL		0x16	/* Amplifier Dig Volume */
+#define CS35L35_ADV_DIG_VOL		0x17	/* Advisory Digital Volume */
+#define CS35L35_PROTECT_CTL		0x18	/* Amp Gain - Prot Ctl Param */
+#define CS35L35_AMP_GAIN_AUD_CTL	0x19	/* Amp Serial Port Gain Ctl */
+#define CS35L35_AMP_GAIN_PDM_CTL	0x1A	/* Amplifier Gain PDM Ctl */
+#define CS35L35_AMP_GAIN_ADV_CTL	0x1B	/* Amplifier Gain Ctl */
+#define CS35L35_GPI_CTL			0x1C	/* GPI Ctl */
+#define CS35L35_BST_CVTR_V_CTL		0x1D	/* Boost Conv Voltage Ctl */
+#define CS35L35_BST_PEAK_I		0x1E	/* Boost Conv Peak Current */
+#define CS35L35_BST_RAMP_CTL		0x20	/* Boost Conv Soft Ramp Ctl */
+#define CS35L35_BST_CONV_COEF_1		0x21	/* Boost Conv Coefficients 1 */
+#define CS35L35_BST_CONV_COEF_2		0x22	/* Boost Conv Coefficients 2 */
+#define CS35L35_BST_CONV_SLOPE_COMP	0x23	/* Boost Conv Slope Comp */
+#define CS35L35_BST_CONV_SW_FREQ	0x24	/* Boost Conv L BST SW Freq */
+#define CS35L35_CLASS_H_CTL		0x30	/* CLS H Control */
+#define CS35L35_CLASS_H_HEADRM_CTL	0x31	/* CLS H Headroom Ctl */
+#define CS35L35_CLASS_H_RELEASE_RATE	0x32	/* CLS H Release Rate */
+#define CS35L35_CLASS_H_FET_DRIVE_CTL	0x33	/* CLS H Weak FET Drive Ctl */
+#define CS35L35_CLASS_H_VP_CTL		0x34	/* CLS H VP Ctl */
+#define CS35L35_CLASS_H_STATUS		0x38	/* CLS H Status */
+#define CS35L35_VPBR_CTL		0x3A	/* VPBR Ctl */
+#define CS35L35_VPBR_VOL_CTL		0x3B	/* VPBR Volume Ctl */
+#define CS35L35_VPBR_TIMING_CTL		0x3C	/* VPBR Timing Ctl */
+#define CS35L35_VPBR_MODE_VOL_CTL	0x3D	/* VPBR Mode/Attack Vol Ctl */
+#define CS35L35_VPBR_ATTEN_STATUS	0x4B	/* VPBR Attenuation Status */
+#define CS35L35_SPKR_MON_CTL		0x4E	/* Speaker Monitoring Ctl */
+#define CS35L35_IMON_SCALE_CTL		0x51	/* IMON Scale Ctl */
+#define CS35L35_AUDIN_RXLOC_CTL		0x52	/* Audio Input RX Loc Ctl */
+#define CS35L35_ADVIN_RXLOC_CTL		0x53	/* Advisory Input RX Loc Ctl */
+#define CS35L35_VMON_TXLOC_CTL		0x54	/* VMON TX Loc Ctl */
+#define CS35L35_IMON_TXLOC_CTL		0x55	/* IMON TX Loc Ctl */
+#define CS35L35_VPMON_TXLOC_CTL		0x56	/* VPMON TX Loc Ctl */
+#define CS35L35_VBSTMON_TXLOC_CTL	0x57	/* VBSTMON TX Loc Ctl */
+#define CS35L35_VPBR_STATUS_TXLOC_CTL	0x58	/* VPBR Status TX Loc Ctl */
+#define CS35L35_ZERO_FILL_LOC_CTL	0x59	/* Zero Fill Loc Ctl */
+#define CS35L35_AUDIN_DEPTH_CTL		0x5A	/* Audio Input Depth Ctl */
+#define CS35L35_SPKMON_DEPTH_CTL	0x5B	/* SPK Mon Output Depth Ctl */
+#define CS35L35_SUPMON_DEPTH_CTL	0x5C	/* Supply Mon Out Depth Ctl */
+#define CS35L35_ZEROFILL_DEPTH_CTL	0x5D	/* Zero Fill Mon Output Ctl */
+#define CS35L35_MULT_DEV_SYNCH1		0x62	/* Multidevice Synch */
+#define CS35L35_MULT_DEV_SYNCH2		0x63	/* Multidevice Synch 2 */
+#define CS35L35_PROT_RELEASE_CTL	0x64	/* Protection Release Ctl */
+#define CS35L35_DIAG_MODE_REG_LOCK	0x68	/* Diagnostic Mode Reg Lock */
+#define CS35L35_DIAG_MODE_CTL_1		0x69	/* Diagnostic Mode Ctl 1 */
+#define CS35L35_DIAG_MODE_CTL_2		0x6A	/* Diagnostic Mode Ctl 2 */
+#define CS35L35_INT_MASK_1		0x70	/* Interrupt Mask 1 */
+#define CS35L35_INT_MASK_2		0x71	/* Interrupt Mask 2 */
+#define CS35L35_INT_MASK_3		0x72	/* Interrupt Mask 3 */
+#define CS35L35_INT_MASK_4		0x73	/* Interrupt Mask 4 */
+#define CS35L35_INT_STATUS_1		0x74	/* Interrupt Status 1 */
+#define CS35L35_INT_STATUS_2		0x75	/* Interrupt Status 2 */
+#define CS35L35_INT_STATUS_3		0x76	/* Interrupt Status 3 */
+#define CS35L35_INT_STATUS_4		0x77	/* Interrupt Status 4 */
+#define CS35L35_PLL_STATUS		0x78	/* PLL Status */
+#define CS35L35_OTP_TRIM_STATUS		0x7E	/* OTP Trim Status */
+
+#define CS35L35_MAX_REGISTER		0x7F
+
+/* CS35L35_PWRCTL1 */
+#define CS35L35_SFT_RST			0x80
+#define CS35L35_DISCHG_FLT		0x02
+#define CS35L35_PDN_ALL			0x01
+
+/* CS35L35_PWRCTL2 */
+#define CS35L35_PDN_VMON		0x80
+#define CS35L35_PDN_IMON		0x40
+#define CS35L35_PDN_CLASSH		0x20
+#define CS35L35_PDN_VPBR		0x10
+#define CS35L35_PDN_BST			0x04
+#define CS35L35_PDN_AMP			0x01
+
+/* CS35L35_PWRCTL3 */
+#define CS35L35_PDN_VBSTMON_OUT		0x10
+#define CS35L35_PDN_VMON_OUT		0x08
+
+#define CS35L35_AUDIN_DEPTH_MASK	0x03
+#define CS35L35_AUDIN_DEPTH_SHIFT	0
+#define CS35L35_ADVIN_DEPTH_MASK	0x12
+#define CS35L35_ADVIN_DEPTH_SHIFT	2
+#define CS35L35_SDIN_DEPTH_8		0x01
+#define CS35L35_SDIN_DEPTH_16		0x02
+#define CS35L35_SDIN_DEPTH_24		0x03
+
+#define CS35L35_SDOUT_DEPTH_8		0x01
+#define CS35L35_SDOUT_DEPTH_12		0x02
+#define CS35L35_SDOUT_DEPTH_16		0x03
+
+#define CS35L35_AUD_IN_LR_MASK		0x80
+#define CS35L35_AUD_IN_LR_SHIFT		7
+#define CS35L35_ADV_IN_LR_MASK		0x80
+#define CS35L35_ADV_IN_LR_SHIFT		7
+#define CS35L35_AUD_IN_LOC_MASK		0x0F
+#define CS35L35_AUD_IN_LOC_SHIFT	0
+#define CS35L35_ADV_IN_LOC_MASK		0x0F
+#define CS35L35_ADV_IN_LOC_SHIFT	0
+
+#define CS35L35_IMON_DEPTH_MASK		0x03
+#define CS35L35_IMON_DEPTH_SHIFT	0
+#define CS35L35_VMON_DEPTH_MASK		0x0C
+#define CS35L35_VMON_DEPTH_SHIFT	2
+#define CS35L35_VBSTMON_DEPTH_MASK	0x03
+#define CS35L35_VBSTMON_DEPTH_SHIFT	0
+#define CS35L35_VPMON_DEPTH_MASK	0x0C
+#define CS35L35_VPMON_DEPTH_SHIFT	2
+#define CS35L35_VPBRSTAT_DEPTH_MASK	0x18
+#define CS35L35_VPBRSTAT_DEPTH_SHIFT	4
+#define CS35L35_ZEROFILL_DEPTH_MASK	0x03
+#define CS35L35_ZEROFILL_DEPTH_SHIFT	0x00
+
+#define CS35L35_MON_TXLOC_MASK		0x3F
+#define CS35L35_MON_TXLOC_SHIFT		0
+#define CS35L35_MON_FRM_MASK		0x80
+#define CS35L35_MON_FRM_SHIFT		7
+
+#define CS35L35_MS_MASK			0x80
+#define CS35L35_MS_SHIFT		7
+#define CS35L35_SPMODE_MASK		0x40
+#define CS35L35_SP_DRV_MASK		0x10
+#define CS35L35_SP_DRV_SHIFT		4
+#define CS35L35_CLK_CTL2_MASK		0xFF
+#define CS35L35_PDM_MODE_MASK		0x40
+#define CS35L35_PDM_MODE_SHIFT		6
+#define CS35L35_CLK_SOURCE_MASK		0x03
+#define CS35L35_CLK_SOURCE_SHIFT	0
+#define CS35L35_CLK_SOURCE_MCLK		0
+#define CS35L35_CLK_SOURCE_SCLK		1
+#define CS35L35_CLK_SOURCE_PDM		2
+
+#define CS35L35_SP_SCLKS_MASK		0x0F
+#define CS35L35_SP_SCLKS_SHIFT		0x00
+#define CS35L35_SP_SCLKS_16FS		0x03
+#define CS35L35_SP_SCLKS_32FS		0x07
+#define CS35L35_SP_SCLKS_48FS		0x0B
+#define CS35L35_SP_SCLKS_64FS		0x0F
+#define CS35L35_SP_RATE_MASK		0xC0
+
+#define CS35L35_PDN_BST_MASK		0x06
+#define CS35L35_PDN_BST_FETON_SHIFT	1
+#define CS35L35_PDN_BST_FETOFF_SHIFT	2
+#define CS35L35_PWR2_PDN_MASK		0xE0
+#define CS35L35_PWR3_PDN_MASK		0x1E
+#define CS35L35_PDN_ALL_MASK		0x01
+#define CS35L35_DISCHG_FILT_MASK	0x02
+#define CS35L35_DISCHG_FILT_SHIFT	1
+#define CS35L35_MCLK_DIS_MASK		0x04
+#define CS35L35_MCLK_DIS_SHIFT		2
+
+#define CS35L35_BST_CTL_MASK		0x7F
+#define CS35L35_BST_CTL_SHIFT		0
+#define CS35L35_BST_IPK_MASK		0x1F
+#define CS35L35_BST_IPK_SHIFT		0
+#define CS35L35_AMP_MUTE_MASK		0x20
+#define CS35L35_AMP_MUTE_SHIFT		5
+#define CS35L35_AMP_GAIN_ZC_MASK	0x10
+#define CS35L35_AMP_GAIN_ZC_SHIFT	4
+
+/* Class H Algorithm Control */
+#define CS35L35_CH_STEREO_MASK		0x40
+#define CS35L35_CH_STEREO_SHIFT		6
+#define CS35L35_CH_BST_OVR_MASK		0x04
+#define CS35L35_CH_BST_OVR_SHIFT	2
+#define CS35L35_CH_BST_LIM_MASK		0x08
+#define CS35L35_CH_BST_LIM_SHIFT	3
+#define CS35L35_CH_MEM_DEPTH_MASK	0x01
+#define CS35L35_CH_MEM_DEPTH_SHIFT	0
+#define CS35L35_CH_HDRM_CTL_MASK	0x3F
+#define CS35L35_CH_HDRM_CTL_SHIFT	0
+#define CS35L35_CH_REL_RATE_MASK	0xFF
+#define CS35L35_CH_REL_RATE_SHIFT	0
+#define CS35L35_CH_WKFET_DIS_MASK	0x80
+#define CS35L35_CH_WKFET_DIS_SHIFT	7
+#define CS35L35_CH_WKFET_DEL_MASK	0x70
+#define CS35L35_CH_WKFET_DEL_SHIFT	4
+#define CS35L35_CH_WKFET_THLD_MASK	0x0F
+#define CS35L35_CH_WKFET_THLD_SHIFT	0
+#define CS35L35_CH_VP_AUTO_MASK		0x80
+#define CS35L35_CH_VP_AUTO_SHIFT	7
+#define CS35L35_CH_VP_RATE_MASK		0x60
+#define CS35L35_CH_VP_RATE_SHIFT	5
+#define CS35L35_CH_VP_MAN_MASK		0x1F
+#define CS35L35_CH_VP_MAN_SHIFT		0
+
+/* CS35L35_PROT_RELEASE_CTL */
+#define CS35L35_CAL_ERR_RLS		0x80
+#define CS35L35_SHORT_RLS		0x04
+#define CS35L35_OTW_RLS			0x02
+#define CS35L35_OTE_RLS			0x01
+
+/* INT Mask Registers */
+#define CS35L35_INT1_CRIT_MASK		0x38
+#define CS35L35_INT2_CRIT_MASK		0xEF
+#define CS35L35_INT3_CRIT_MASK		0xEE
+#define CS35L35_INT4_CRIT_MASK		0xFF
+
+/* PDN DONE Masks */
+#define CS35L35_M_PDN_DONE_SHIFT	4
+#define CS35L35_M_PDN_DONE_MASK		0x10
+
+/* CS35L35_INT_1 */
+#define CS35L35_CAL_ERR			0x80
+#define CS35L35_OTP_ERR			0x40
+#define CS35L35_LRCLK_ERR		0x20
+#define CS35L35_SPCLK_ERR		0x10
+#define CS35L35_MCLK_ERR		0x08
+#define CS35L35_AMP_SHORT		0x04
+#define CS35L35_OTW			0x02
+#define CS35L35_OTE			0x01
+
+/* CS35L35_INT_2 */
+#define CS35L35_PDN_DONE		0x10
+#define CS35L35_VPBR_ERR		0x02
+#define CS35L35_VPBR_CLR		0x01
+
+/* CS35L35_INT_3 */
+#define CS35L35_BST_HIGH		0x10
+#define CS35L35_BST_HIGH_FLAG		0x08
+#define CS35L35_BST_IPK_FLAG		0x04
+#define CS35L35_LBST_SHORT		0x01
+
+/* CS35L35_INT_4 */
+#define CS35L35_VMON_OVFL		0x08
+#define CS35L35_IMON_OVFL		0x04
+
+#define CS35L35_FORMATS (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE | \
+			SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+struct  cs35l35_private {
+	struct snd_soc_codec *codec;
+	struct cs35l35_platform_data pdata;
+	struct regmap *regmap;
+	struct regulator_bulk_data supplies[2];
+	int num_supplies;
+	int sysclk;
+	int sclk;
+	bool pdm_mode;
+	bool i2s_mode;
+	bool slave_mode;
+	/* GPIO for /RST */
+	struct gpio_desc *reset_gpio;
+	struct completion pdn_done;
+};
+
+static const char * const cs35l35_supplies[] = {
+	"VA",
+	"VP",
+};
+
+#endif
-- 
1.9.1

^ permalink raw reply related

* Re: [PATCH 3/6] ARM: dts: sun8i: add a cpu0 label to cpu@0 node on A23/33
From: Sudeep Holla @ 2016-12-13 16:09 UTC (permalink / raw)
  To: Icenowy Zheng
  Cc: devicetree, Quentin Schulz, Michael Turquette, Stephen Boyd,
	Russell King, linux-kernel, Hans de Goede, Chen-Yu Tsai,
	Sudeep Holla, Maxime Ripard, linux-clk, linux-arm-kernel,
	Jorik Jonker
In-Reply-To: <20161213152252.53749-4-icenowy@aosc.xyz>



On 13/12/16 15:22, Icenowy Zheng wrote:
> A "cpu0" label is needed on cpu@0 for cpufreq-dt to work.
> 

IIUC any label should be fine and I don't see anything in the driver
looking for such label name. All I see is it looks for cpu0 regulator
for *legacy* DTs

> Add such a label, in order to prepare for cpufreq support of A23/33.
> 

You need this as you add the same label in the following patches. The
commit message sounds like cpufreq-dt search for that label by name.

-- 
Regards,
Sudeep

^ permalink raw reply

* [PATCH 2/2] ASoC: cs35l35: Add device tree documentation for CS35L35
From: Li Xu @ 2016-12-13 16:08 UTC (permalink / raw)
  To: alsa-devel-K7yf7f+aM1XWsZ/bQMPhNw,
	devicetree-u79uwXL29TY76Z2rM5mHXA
  Cc: lgirdwood-Re5JQEeQqe8AvxtiuMwx3w, broonie-DgEjT+Ai2ygdnm+yROfE0A,
	robh+dt-DgEjT+Ai2ygdnm+yROfE0A, mark.rutland-5wv7dgnIgG8,
	perex-/Fr2/VpizcU, tiwai-IBi9RG/b67k,
	brian.austin-jGc1dHjMKG3QT0dZR+AlfA,
	Paul.Handrigan-jGc1dHjMKG3QT0dZR+AlfA, Li Xu
In-Reply-To: <1481645331-15114-1-git-send-email-li.xu-jGc1dHjMKG3QT0dZR+AlfA@public.gmane.org>

Add device tree documentation for Cirrus Logic CS35L35
speaker amplifier

Signed-off-by: Li Xu <li.xu-jGc1dHjMKG3QT0dZR+AlfA@public.gmane.org>
---
 .../devicetree/bindings/sound/cs35l35.txt          | 172 +++++++++++++++++++++
 1 file changed, 172 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/cs35l35.txt

diff --git a/Documentation/devicetree/bindings/sound/cs35l35.txt b/Documentation/devicetree/bindings/sound/cs35l35.txt
new file mode 100644
index 0000000..8b13f67
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/cs35l35.txt
@@ -0,0 +1,172 @@
+CS35L35 Speaker Amplifier
+
+Required properties:
+
+  - compatible : "cirrus,cs35l35"
+
+  - reg : the I2C address of the device for I2C
+
+  - VA-supply, VP-supply : power supplies for the device,
+    as covered in
+    Documentation/devicetree/bindings/regulator/regulator.txt.
+
+  - interrupt-parent : Specifies the phandle of the interrupt controller to
+    which the IRQs from CS35L35 are delivered to.
+  - interrupts : IRQ line info CS35L35.
+    (See Documentation/devicetree/bindings/interrupt-controller/interrupts.txt
+    for further information relating to interrupt properties)
+
+Optional properties:
+  - cirrus,reset-gpios : Active low GPIO used to reset the amplifier
+
+  - cirrus,stereo-config : Boolean to determine if there are 2 AMPs for a
+  Stereo configuration
+
+  - cirrus,audio-channel : Set Location of Audio Signal on Serial Port
+  0 = Data Packet received on Left I2S Channel
+  1 = Data Packet received on Right I2S Channel
+
+  - cirrus,advisory-channel : Set Location of Advisory Signal on Serial Port
+  0 = Data Packet received on Left I2S Channel
+  1 = Data Packet received on Right I2S Channel
+
+  - cirrus,shared-boost : Boolean to enable ClassH tracking of Advisory Signal
+  if 2 Devices share Boost BST_CTL
+
+  - cirrus,sp-drv-strength : Value for setting the Serial Port drive strength
+  Table 3-10 of the datasheet lists drive-strength specifications
+  0 = 1x (Default)
+  1 = .5x
+
+  - cirrus,bst-pdn-fet-on : Boolean to determine if the Boost PDN control
+  powers down with a rectification FET On or Off. If VSPK is supplied
+  externally then FET is off.
+
+  - cirrus,boost-ctl-millivolt : Boost Converter control word. Step Size of 100mV
+  0x00 = (Default) VP
+  0x01 = 2600mV
+  0x41 = 9000mV
+
+  - cirrus,boost-ipk-milliamp : Boost-converter peak current limit.
+  Configures the peak current by monitoring the current through the boost FET.
+  Step size: 112mA
+  0x00 = 1680mA
+  0x19 = 4480mA
+
+  - cirrus,amp-gain-zc : Boolean to determine if to use Amplifier gain-change
+  zero-cross
+
+Optional H/G Algorithm sub-node:
+
+  The cs35l35 node can have a single "cirrus,classh-internal-algo" sub-node
+  that will disable automatic control of the internal H/G Algorithm.
+
+  It is strongly recommended that the Datasheet be referenced when adjusting
+  or using these Class H Algorithm controls over the internal Algorithm.
+  Serious damage can occur to the Device and surrounding components.
+
+  - cirrus,classh-internal-algo : Sub-node for the Internal Class H Algorithm
+  See Section 4.3 Internal Class H Algorithm in the Datasheet.
+  If not used, the device manages the ClassH Algorithm internally.
+
+Optional properties for the "cirrus,classh-internal-algo" Sub-node
+
+  Section 7.29 Class H Control
+  - cirrus,classh-bst-overide : Boolean
+  - cirrus,classh-bst-max-limit
+  - cirrus,classh-mem-depth
+
+  Section 7.30 Class H Headroom Control
+  - cirrus,classh-headroom
+
+  Section 7.31 Class H Release Rate
+  - cirrus,classh-release-rate
+
+  Section 7.32 Class H Weak FET Drive Control
+  - cirrus,classh-wk-fet-disable
+  - cirrus,classh-wk-fet-delay
+  - cirrus,classh-wk-fet-thld
+
+  Section 7.34 Class H VP Control
+  - cirrus,classh-vpch-auto
+  - cirrus,classh-vpch-rate
+  - cirrus,classh-vpch-man
+
+Optional Monitor Signal Format sub-node:
+
+  The cs35l35 node can have a single "cirrus,monitor-signal-format" sub-node
+  for adjusting the Depth, Location and Frame of the Monitoring Signals
+  for Algorithms.
+
+  See Sections 4.8.2 through 4.8.4 Serial-Port Control in the Datasheet
+
+  -cirrus,monitor-signal-format : Sub-node for the Monitor Signaling Formating
+  on the I2S Port. Each of the 3 8 bit values in the array contain the settings
+  for depth, location, and frame.
+
+  If not used, the defaults for the 6 monitor signals is used.
+
+  Sections 7.44 - 7.53 lists values for the depth, location, and frame
+  for each monitoring signal.
+
+  - cirrus,imon : 3 8 bit values to set the depth, location, and frame
+  of the IMON monitor signal.
+
+  - cirrus,vmon : 3 8 bit values to set the depth, location, and frame
+  of the VMON monitor signal.
+
+  - cirrus,vpmon : 3 8 bit values to set the depth, location, and frame
+  of the VPMON monitor signal.
+
+  - cirrus,vbstmon : 3 8 bit values to set the depth, location, and frame
+  of the VBSTMON monitor signal
+
+  - cirrus,vpbrstat : 3 8 bit values to set the depth, location, and frame
+  of the VPBRSTAT monitor signal
+
+  - cirrus,zerofill : 3 8 bit values to set the depth, location, and frame\
+  of the ZEROFILL packet in the monitor signal
+
+Example:
+
+cs35l35: audio-codec@20 {
+	compatible = "cirrus,cs35l35";
+	reg = <0x20>;
+	VA-supply = <&dummy_vreg>;
+	VP-supply = <&dummy_vreg>;
+	reset-gpios = <&axi_gpio 54 1>;
+	interrupt-parent = <&gpio8>;
+	interrupts = <3 IRQ_TYPE_LEVEL_LOW>;
+	cirrus,boost-ctl = <0x41>;
+
+	cirrus,stereo-config {
+		cirrus,audio-channel = <0x00>;
+		cirrus,advisory-channel = <0x01>;
+		cirrus,shared-boost;
+	};
+
+	cirrus,classh-internal-algo {
+		cirrus,classh-bst-overide;
+		cirrus,classh-bst-max-limit = <0x01>;
+		cirrus,classh-mem-depth = <0x01>;
+		cirrus,classh-release-rate = <0x08>;
+		cirrus,classh-headroom-millivolt = <0x0B>;
+		cirrus,classh-wk-fet-disable = <0x01>;
+		cirrus,classh-wk-fet-delay = <0x04>;
+		cirrus,classh-wk-fet-thld = <0x01>;
+		cirrus,classh-vpch-auto = <0x01>;
+		cirrus,classh-vpch-rate = <0x02>;
+		cirrus,classh-vpch-man = <0x05>;
+	};
+
+	/* Depth, Location, Frame */
+	cirrus,monitor-signal-format {
+		cirrus,imon = /bits/ 8 <0x03 0x00 0x01>;
+		cirrus,vmon = /bits/ 8 <0x03 0x00 0x00>;
+		cirrus,vpmon = /bits/ 8 <0x03 0x04 0x00>;
+		cirrus,vbstmon = /bits/ 8 <0x03 0x04 0x01>;
+		cirrus,vpbrstat = /bits/ 8 <0x00 0x04 0x00>;
+		cirrus,zerofill = /bits/ 8 <0x00 0x00 0x00>;
+	};
+
+};
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related

* [PATCH 1/2] ASoC: cs35l35: Add support for Cirrus CS35L35 Amplifier
From: Li Xu @ 2016-12-13 16:08 UTC (permalink / raw)
  To: alsa-devel, devicetree
  Cc: mark.rutland, brian.austin, tiwai, robh+dt, lgirdwood, broonie,
	Paul.Handrigan, Li Xu

Add driver support for Cirrus Logic CS35L35 boosted
speaker amplifier

Signed-off-by: Li Xu <li.xu@cirrus.com>
---
 include/sound/cs35l35.h    |  104 ++++
 sound/soc/codecs/Kconfig   |    5 +
 sound/soc/codecs/Makefile  |    2 +
 sound/soc/codecs/cs35l35.c | 1355 ++++++++++++++++++++++++++++++++++++++++++++
 sound/soc/codecs/cs35l35.h |  284 ++++++++++
 5 files changed, 1750 insertions(+)
 create mode 100644 include/sound/cs35l35.h
 create mode 100644 sound/soc/codecs/cs35l35.c
 create mode 100644 sound/soc/codecs/cs35l35.h

diff --git a/include/sound/cs35l35.h b/include/sound/cs35l35.h
new file mode 100644
index 0000000..005a813
--- /dev/null
+++ b/include/sound/cs35l35.h
@@ -0,0 +1,104 @@
+/*
+ * linux/sound/cs35l35.h -- Platform data for CS35l35
+ *
+ * Copyright (c) 2016 Cirrus Logic 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.
+ */
+
+#ifndef __CS35L35_H
+#define __CS35L35_H
+
+struct classh_cfg {
+	/*
+	 * Class H Algorithm Control Variables
+	 * You can either have it done
+	 * automatically or you can adjust
+	 * these variables for tuning
+	 *
+	 * if you do not enable the internal algorithm
+	 * you will get a set of mixer controls for
+	 * Class H tuning
+	 *
+	 * Section 4.3 of the datasheet
+	 */
+	/* Internal ClassH Algorithm  */
+	bool classh_bst_override;
+	bool classh_algo_enable;
+	int classh_bst_max_limit;
+	int classh_mem_depth;
+	int classh_release_rate;
+	int classh_headroom;
+	int classh_wk_fet_disable;
+	int classh_wk_fet_delay;
+	int classh_wk_fet_thld;
+	int classh_vpch_auto;
+	int classh_vpch_rate;
+	int classh_vpch_man;
+};
+
+struct monitor_cfg {
+	/*
+	 * Signal Monitor Data
+	 * highly configurable signal monitoring
+	 * data positioning and different types of
+	 * monitoring data.
+	 *
+	 * Section 4.8.2 - 4.8.4 of the datasheet
+	 */
+	bool is_present;
+	bool imon_specs;
+	bool vmon_specs;
+	bool vpmon_specs;
+	bool vbstmon_specs;
+	bool vpbrstat_specs;
+	bool zerofill_specs;
+	u8 imon_dpth;
+	u8 imon_loc;
+	u8 imon_frm;
+	u8 vmon_dpth;
+	u8 vmon_loc;
+	u8 vmon_frm;
+	u8 vpmon_dpth;
+	u8 vpmon_loc;
+	u8 vpmon_frm;
+	u8 vbstmon_dpth;
+	u8 vbstmon_loc;
+	u8 vbstmon_frm;
+	u8 vpbrstat_dpth;
+	u8 vpbrstat_loc;
+	u8 vpbrstat_frm;
+	u8 zerofill_dpth;
+	u8 zerofill_loc;
+	u8 zerofill_frm;
+};
+
+struct cs35l35_platform_data {
+
+	/* Stereo (2 Device) */
+	bool stereo;
+	/* serial port drive strength */
+	int sp_drv_str;
+	/* Boost Power Down with FET */
+	bool bst_pdn_fet_on;
+	/* Boost Voltage : used if ClassH Algo Enabled */
+	int bst_vctl;
+	/* Boost Converter Peak Current CTRL */
+	int bst_ipk;
+	/* Amp Gain Zero Cross */
+	bool gain_zc;
+	/* Audio Input Location */
+	int aud_channel;
+	/* Advisory Input Location */
+	int adv_channel;
+	/* Shared Boost for stereo */
+	bool shared_bst;
+	/* ClassH Algorithm */
+	struct classh_cfg classh_algo;
+	/* Monitor Config */
+	struct monitor_cfg mon_cfg;
+};
+
+#endif /* __CS35L35_H */
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 9e1718a..3fd0a08 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -49,6 +49,7 @@ config SND_SOC_ALL_CODECS
 	select SND_SOC_CS35L32 if I2C
 	select SND_SOC_CS35L33 if I2C
 	select SND_SOC_CS35L34 if I2C
+	select SND_SOC_CS35L35 if I2C
 	select SND_SOC_CS42L42 if I2C
 	select SND_SOC_CS42L51_I2C if I2C
 	select SND_SOC_CS42L52 if I2C && INPUT
@@ -407,6 +408,10 @@ config SND_SOC_CS35L34
 	tristate "Cirrus Logic CS35L34 CODEC"
 	depends on I2C
 
+config SND_SOC_CS35L35
+	tristate "Cirrus Logic CS35L35 CODEC"
+	depends on I2C
+
 config SND_SOC_CS42L42
 	tristate "Cirrus Logic CS42L42 CODEC"
 	depends on I2C
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 7e1dad7..a5622f6 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -39,6 +39,7 @@ snd-soc-cq93vc-objs := cq93vc.o
 snd-soc-cs35l32-objs := cs35l32.o
 snd-soc-cs35l33-objs := cs35l33.o
 snd-soc-cs35l34-objs := cs35l34.o
+snd-soc-cs35l35-objs := cs35l35.o
 snd-soc-cs42l42-objs := cs42l42.o
 snd-soc-cs42l51-objs := cs42l51.o
 snd-soc-cs42l51-i2c-objs := cs42l51-i2c.o
@@ -268,6 +269,7 @@ obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o
 obj-$(CONFIG_SND_SOC_CS35L32)	+= snd-soc-cs35l32.o
 obj-$(CONFIG_SND_SOC_CS35L33)	+= snd-soc-cs35l33.o
 obj-$(CONFIG_SND_SOC_CS35L34)	+= snd-soc-cs35l34.o
+obj-$(CONFIG_SND_SOC_CS35L35)	+= snd-soc-cs35l35.o
 obj-$(CONFIG_SND_SOC_CS42L42)	+= snd-soc-cs42l42.o
 obj-$(CONFIG_SND_SOC_CS42L51)	+= snd-soc-cs42l51.o
 obj-$(CONFIG_SND_SOC_CS42L51_I2C)	+= snd-soc-cs42l51-i2c.o
diff --git a/sound/soc/codecs/cs35l35.c b/sound/soc/codecs/cs35l35.c
new file mode 100644
index 0000000..b05e900
--- /dev/null
+++ b/sound/soc/codecs/cs35l35.c
@@ -0,0 +1,1355 @@
+/*
+ * cs35l35.c -- CS35L35 ALSA SoC audio driver
+ *
+ * Copyright 2016 Cirrus Logic, Inc.
+ *
+ * Author: Li Xu <li.xu@cirrus.com>
+ *
+ * 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/version.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/gpio/consumer.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/regmap.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <linux/gpio.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <sound/cs35l35.h>
+#include <linux/of_irq.h>
+#include <linux/completion.h>
+
+#include "cs35l35.h"
+
+static const struct reg_default cs35l35_reg[] = {
+	{CS35L35_PWRCTL1,		0x01},
+	{CS35L35_PWRCTL2,		0x11},
+	{CS35L35_PWRCTL3,		0x00},
+	{CS35L35_CLK_CTL1,		0x04},
+	{CS35L35_CLK_CTL2,		0x10},
+	{CS35L35_CLK_CTL3,		0xCF},
+	{CS35L35_SP_FMT_CTL1,		0x20},
+	{CS35L35_SP_FMT_CTL2,		0x00},
+	{CS35L35_SP_FMT_CTL3,		0x02},
+	{CS35L35_MAG_COMP_CTL,		0x00},
+	{CS35L35_AMP_INP_DRV_CTL,	0x01},
+	{CS35L35_AMP_DIG_VOL_CTL,	0x12},
+	{CS35L35_AMP_DIG_VOL,		0x00},
+	{CS35L35_ADV_DIG_VOL,		0x00},
+	{CS35L35_PROTECT_CTL,		0x06},
+	{CS35L35_AMP_GAIN_AUD_CTL,	0x13},
+	{CS35L35_AMP_GAIN_PDM_CTL,	0x00},
+	{CS35L35_AMP_GAIN_ADV_CTL,	0x00},
+	{CS35L35_GPI_CTL,		0x00},
+	{CS35L35_BST_CVTR_V_CTL,	0x00},
+	{CS35L35_BST_PEAK_I,		0x07},
+	{CS35L35_BST_RAMP_CTL,		0x85},
+	{CS35L35_BST_CONV_COEF_1,	0x20},
+	{CS35L35_BST_CONV_COEF_2,	0x20},
+	{CS35L35_BST_CONV_SLOPE_COMP,	0x47},
+	{CS35L35_BST_CONV_SW_FREQ,	0x04},
+	{CS35L35_CLASS_H_CTL,		0x0B},
+	{CS35L35_CLASS_H_HEADRM_CTL,	0x0B},
+	{CS35L35_CLASS_H_RELEASE_RATE,	0x08},
+	{CS35L35_CLASS_H_FET_DRIVE_CTL, 0x41},
+	{CS35L35_CLASS_H_VP_CTL,	0xC5},
+	{CS35L35_VPBR_CTL,		0x0A},
+	{CS35L35_VPBR_VOL_CTL,		0x09},
+	{CS35L35_VPBR_TIMING_CTL,	0x6A},
+	{CS35L35_VPBR_MODE_VOL_CTL,	0x00},
+	{CS35L35_SPKR_MON_CTL,		0xC0},
+	{CS35L35_IMON_SCALE_CTL,	0x30},
+	{CS35L35_AUDIN_RXLOC_CTL,	0x00},
+	{CS35L35_ADVIN_RXLOC_CTL,	0x80},
+	{CS35L35_VMON_TXLOC_CTL,	0x00},
+	{CS35L35_IMON_TXLOC_CTL,	0x80},
+	{CS35L35_VPMON_TXLOC_CTL,	0x04},
+	{CS35L35_VBSTMON_TXLOC_CTL,	0x84},
+	{CS35L35_VPBR_STATUS_TXLOC_CTL,	0x04},
+	{CS35L35_ZERO_FILL_LOC_CTL,	0x00},
+	{CS35L35_AUDIN_DEPTH_CTL,	0x0F},
+	{CS35L35_SPKMON_DEPTH_CTL,	0x0F},
+	{CS35L35_SUPMON_DEPTH_CTL,	0x0F},
+	{CS35L35_ZEROFILL_DEPTH_CTL,	0x00},
+	{CS35L35_MULT_DEV_SYNCH1,	0x02},
+	{CS35L35_MULT_DEV_SYNCH2,	0x80},
+	{CS35L35_PROT_RELEASE_CTL,	0x00},
+	{CS35L35_DIAG_MODE_REG_LOCK,	0x00},
+	{CS35L35_DIAG_MODE_CTL_1,	0x40},
+	{CS35L35_DIAG_MODE_CTL_2,	0x00},
+	{CS35L35_INT_MASK_1,		0xFF},
+	{CS35L35_INT_MASK_2,		0xFF},
+	{CS35L35_INT_MASK_3,		0xFF},
+	{CS35L35_INT_MASK_4,		0xFF},
+
+};
+
+static bool cs35l35_volatile_register(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case CS35L35_DEVID_AB ... CS35L35_REV_ID:
+	case CS35L35_INT_STATUS_1:
+	case CS35L35_INT_STATUS_2:
+	case CS35L35_INT_STATUS_3:
+	case CS35L35_INT_STATUS_4:
+	case CS35L35_PLL_STATUS:
+	case CS35L35_OTP_TRIM_STATUS:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool cs35l35_readable_register(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case CS35L35_DEVID_AB ... CS35L35_PWRCTL3:
+	case CS35L35_CLK_CTL1 ... CS35L35_SP_FMT_CTL3:
+	case CS35L35_MAG_COMP_CTL ... CS35L35_AMP_GAIN_AUD_CTL:
+	case CS35L35_AMP_GAIN_PDM_CTL ... CS35L35_BST_PEAK_I:
+	case CS35L35_BST_RAMP_CTL ... CS35L35_BST_CONV_SW_FREQ:
+	case CS35L35_CLASS_H_CTL ... CS35L35_CLASS_H_VP_CTL:
+	case CS35L35_CLASS_H_STATUS:
+	case CS35L35_VPBR_CTL ... CS35L35_VPBR_MODE_VOL_CTL:
+	case CS35L35_VPBR_ATTEN_STATUS:
+	case CS35L35_SPKR_MON_CTL:
+	case CS35L35_IMON_SCALE_CTL ... CS35L35_ZEROFILL_DEPTH_CTL:
+	case CS35L35_MULT_DEV_SYNCH1 ... CS35L35_PROT_RELEASE_CTL:
+	case CS35L35_DIAG_MODE_REG_LOCK ... CS35L35_DIAG_MODE_CTL_2:
+	case CS35L35_INT_MASK_1 ... CS35L35_PLL_STATUS:
+	case CS35L35_OTP_TRIM_STATUS:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool cs35l35_precious_register(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case CS35L35_INT_STATUS_1:
+	case CS35L35_INT_STATUS_2:
+	case CS35L35_INT_STATUS_3:
+	case CS35L35_INT_STATUS_4:
+	case CS35L35_PLL_STATUS:
+	case CS35L35_OTP_TRIM_STATUS:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static int cs35l35_sdin_event(struct snd_soc_dapm_widget *w,
+		struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm);
+	struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec);
+	int ret = 0;
+
+	switch (event) {
+	case SND_SOC_DAPM_PRE_PMU:
+		regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1,
+			CS35L35_MCLK_DIS_MASK, 0 << CS35L35_MCLK_DIS_SHIFT);
+		regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1,
+			CS35L35_DISCHG_FILT_MASK, 0 << CS35L35_DISCHG_FILT_SHIFT);
+		regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1,
+			CS35L35_PDN_ALL_MASK, 0);
+	break;
+	case SND_SOC_DAPM_POST_PMD:
+		regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1,
+			CS35L35_PDN_ALL_MASK, 1);
+		regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1,
+			CS35L35_DISCHG_FILT_MASK, 1 << CS35L35_DISCHG_FILT_SHIFT);
+
+		ret = wait_for_completion_timeout(&cs35l35->pdn_done,
+							msecs_to_jiffies(100));
+		if (ret == 0) {
+			pr_err("TIMEOUT PDN_DONE did not complete in 100ms\n");
+			ret = -ETIMEDOUT;
+		}
+
+		regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1,
+			CS35L35_MCLK_DIS_MASK, 1 << CS35L35_MCLK_DIS_SHIFT);
+	break;
+	default:
+		pr_err("Invalid event = 0x%x\n", event);
+		ret = -EINVAL;
+	}
+	return ret;
+}
+
+static int cs35l35_main_amp_event(struct snd_soc_dapm_widget *w,
+		struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm);
+	struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec);
+	unsigned int reg[4];
+	int i;
+
+	switch (event) {
+	case SND_SOC_DAPM_PRE_PMU:
+		if (cs35l35->pdata.bst_pdn_fet_on)
+			regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2,
+				CS35L35_PDN_BST_MASK, 0 << CS35L35_PDN_BST_FETON_SHIFT);
+		else
+			regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2,
+				CS35L35_PDN_BST_MASK, 0 << CS35L35_PDN_BST_FETOFF_SHIFT);
+			regmap_update_bits(cs35l35->regmap, CS35L35_PROTECT_CTL,
+				CS35L35_AMP_MUTE_MASK, 0 << CS35L35_AMP_MUTE_SHIFT);
+		break;
+	case SND_SOC_DAPM_POST_PMU:
+		usleep_range(5000, 5100);
+		/* If PDM mode we must use VP
+		 * for Voltage control
+		 */
+		if (cs35l35->pdm_mode)
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_BST_CVTR_V_CTL, CS35L35_BST_CTL_MASK,
+				0 << CS35L35_BST_CTL_SHIFT);
+		for (i = 0; i < 2; i++)
+			regmap_bulk_read(cs35l35->regmap, CS35L35_INT_STATUS_1,
+				&reg, ARRAY_SIZE(reg));
+		break;
+	case SND_SOC_DAPM_PRE_PMD:
+		regmap_update_bits(cs35l35->regmap, CS35L35_PROTECT_CTL,
+			CS35L35_AMP_MUTE_MASK, 1 << CS35L35_AMP_MUTE_SHIFT);
+		if (cs35l35->pdata.bst_pdn_fet_on)
+			regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2,
+				CS35L35_PDN_BST_MASK, 1 << CS35L35_PDN_BST_FETON_SHIFT);
+		else
+			regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2,
+				CS35L35_PDN_BST_MASK, 1 << CS35L35_PDN_BST_FETOFF_SHIFT);
+		break;
+	case SND_SOC_DAPM_POST_PMD:
+		usleep_range(5000, 5100);
+		/* If PDM mode we should switch back to pdata value
+		 * for Voltage control when we go down
+		 */
+		if (cs35l35->pdm_mode)
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_BST_CVTR_V_CTL, CS35L35_BST_CTL_MASK,
+				cs35l35->pdata.bst_vctl << CS35L35_BST_CTL_SHIFT);
+
+		break;
+	default:
+		pr_err("Invalid event = 0x%x\n", event);
+	}
+	return 0;
+}
+
+static DECLARE_TLV_DB_SCALE(amp_gain_tlv, 0, 1, 1);
+static DECLARE_TLV_DB_SCALE(dig_vol_tlv, -10200, 50, 0);
+
+static const struct snd_kcontrol_new cs35l35_aud_controls[] = {
+	SOC_SINGLE_SX_TLV("Digital Audio Volume", CS35L35_AMP_DIG_VOL,
+		      0, 0x34, 0xE4, dig_vol_tlv),
+	SOC_SINGLE_TLV("AMP Audio Gain", CS35L35_AMP_GAIN_AUD_CTL, 0, 19, 0,
+			amp_gain_tlv),
+	SOC_SINGLE_TLV("AMP PDM Gain", CS35L35_AMP_GAIN_PDM_CTL, 0, 19, 0,
+			amp_gain_tlv),
+};
+
+static const struct snd_kcontrol_new cs35l35_adv_controls[] = {
+	SOC_SINGLE_SX_TLV("Digital Advisory Volume", CS35L35_ADV_DIG_VOL,
+		      0, 0x34, 0xE4, dig_vol_tlv),
+	SOC_SINGLE_TLV("AMP Advisory Gain", CS35L35_AMP_GAIN_ADV_CTL, 0, 19, 0,
+			amp_gain_tlv),
+};
+
+static const struct snd_soc_dapm_widget cs35l35_dapm_widgets[] = {
+	SND_SOC_DAPM_AIF_IN_E("SDIN", NULL, 0, CS35L35_PWRCTL3, 1, 1,
+				cs35l35_sdin_event, SND_SOC_DAPM_PRE_PMU |
+				SND_SOC_DAPM_POST_PMD),
+	SND_SOC_DAPM_AIF_OUT("SDOUT", NULL, 0, CS35L35_PWRCTL3, 2, 1),
+
+	SND_SOC_DAPM_OUTPUT("SPK"),
+
+	SND_SOC_DAPM_INPUT("VP"),
+	SND_SOC_DAPM_INPUT("VBST"),
+	SND_SOC_DAPM_INPUT("ISENSE"),
+	SND_SOC_DAPM_INPUT("VSENSE"),
+
+	SND_SOC_DAPM_ADC("VMON ADC", NULL, CS35L35_PWRCTL2, 7, 1),
+	SND_SOC_DAPM_ADC("IMON ADC", NULL, CS35L35_PWRCTL2, 6, 1),
+	SND_SOC_DAPM_ADC("VPMON ADC", NULL, CS35L35_PWRCTL3, 3, 1),
+	SND_SOC_DAPM_ADC("VBSTMON ADC", NULL, CS35L35_PWRCTL3, 4, 1),
+	SND_SOC_DAPM_ADC("CLASS H", NULL, CS35L35_PWRCTL2, 5, 1),
+
+	SND_SOC_DAPM_OUT_DRV_E("Main AMP", CS35L35_PWRCTL2, 0, 1, NULL, 0,
+		cs35l35_main_amp_event, SND_SOC_DAPM_PRE_PMU |
+				SND_SOC_DAPM_POST_PMD | SND_SOC_DAPM_POST_PMU |
+				SND_SOC_DAPM_PRE_PMD),
+};
+
+static const struct snd_soc_dapm_route cs35l35_audio_map[] = {
+	{"VPMON ADC", NULL, "VP"},
+	{"VBSTMON ADC", NULL, "VBST"},
+	{"IMON ADC", NULL, "ISENSE"},
+	{"VMON ADC", NULL, "VSENSE"},
+	{"SDOUT", NULL, "IMON ADC"},
+	{"SDOUT", NULL, "VMON ADC"},
+	{"SDOUT", NULL, "VBSTMON ADC"},
+	{"SDOUT", NULL, "VPMON ADC"},
+	{"AMP Capture", NULL, "SDOUT"},
+
+	{"SDIN", NULL, "AMP Playback"},
+	{"CLASS H", NULL, "SDIN"},
+	{"Main AMP", NULL, "CLASS H"},
+	{"SPK", NULL, "Main AMP"},
+};
+
+static int cs35l35_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec);
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1,
+				    CS35L35_MS_MASK, 1 << CS35L35_MS_SHIFT);
+		cs35l35->slave_mode = false;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1,
+				    CS35L35_MS_MASK, 0 << CS35L35_MS_SHIFT);
+		cs35l35->slave_mode = true;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		cs35l35->i2s_mode = true;
+		cs35l35->pdm_mode = false;
+		break;
+	case SND_SOC_DAIFMT_PDM:
+		cs35l35->pdm_mode = true;
+		cs35l35->i2s_mode = false;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+struct cs35l35_sysclk_config {
+	int sysclk;
+	int srate;
+	u8 clk_cfg;
+};
+
+static struct cs35l35_sysclk_config cs35l35_clk_ctl[] = {
+
+	/* SYSCLK, Sample Rate, Serial Port Cfg */
+	{5644800, 44100, 0x00},
+	{5644800, 88200, 0x40},
+	{6144000, 48000, 0x10},
+	{6144000, 96000, 0x50},
+	{11289600, 44100, 0x01},
+	{11289600, 88200, 0x41},
+	{11289600, 176400, 0x81},
+	{12000000, 44100, 0x03},
+	{12000000, 48000, 0x13},
+	{12000000, 88200, 0x43},
+	{12000000, 96000, 0x53},
+	{12000000, 176400, 0x83},
+	{12000000, 192000, 0x93},
+	{12288000, 48000, 0x11},
+	{12288000, 96000, 0x51},
+	{12288000, 192000, 0x91},
+	{13000000, 44100, 0x07},
+	{13000000, 48000, 0x17},
+	{13000000, 88200, 0x47},
+	{13000000, 96000, 0x57},
+	{13000000, 176400, 0x87},
+	{13000000, 192000, 0x97},
+	{22579200, 44100, 0x02},
+	{22579200, 88200, 0x42},
+	{22579200, 176400, 0x82},
+	{24000000, 44100, 0x0B},
+	{24000000, 48000, 0x1B},
+	{24000000, 88200, 0x4B},
+	{24000000, 96000, 0x5B},
+	{24000000, 176400, 0x8B},
+	{24000000, 192000, 0x9B},
+	{24576000, 48000, 0x12},
+	{24576000, 96000, 0x52},
+	{24576000, 192000, 0x92},
+	{26000000, 44100, 0x0F},
+	{26000000, 48000, 0x1F},
+	{26000000, 88200, 0x4F},
+	{26000000, 96000, 0x5F},
+	{26000000, 176400, 0x8F},
+	{26000000, 192000, 0x9F},
+};
+
+static int cs35l35_get_clk_config(int sysclk, int srate)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(cs35l35_clk_ctl); i++) {
+		if (cs35l35_clk_ctl[i].sysclk == sysclk &&
+			cs35l35_clk_ctl[i].srate == srate)
+			return cs35l35_clk_ctl[i].clk_cfg;
+	}
+	return -EINVAL;
+}
+
+static int cs35l35_pcm_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 cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec);
+	struct classh_cfg *classh = &cs35l35->pdata.classh_algo;
+	int srate = params_rate(params);
+	int ret = 0;
+	u8 sp_sclks;
+	int audin_format;
+	int errata_chk;
+
+	int clk_ctl = cs35l35_get_clk_config(cs35l35->sysclk, srate);
+
+	if (clk_ctl < 0) {
+		dev_err(codec->dev, "Invalid CLK:Rate %d:%d\n",
+			cs35l35->sysclk, srate);
+		return -EINVAL;
+	}
+
+	ret = regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL2,
+			  CS35L35_CLK_CTL2_MASK, clk_ctl);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to set port config %d\n", ret);
+		return ret;
+	}
+
+	/* Rev A0 Errata
+	 *
+	 * When configured for the weak-drive detection path (CH_WKFET_DIS = 0)
+	 * the Class H algorithm does not enable weak-drive operation for
+	 * nonzero values of CH_WKFET_DELAY if SP_RATE = 01 or 10
+	 *
+	 */
+	errata_chk = clk_ctl & CS35L35_SP_RATE_MASK;
+
+	if (classh->classh_wk_fet_disable == 0x00 &&
+		(errata_chk == 0x01 || errata_chk == 0x03)) {
+		ret = regmap_update_bits(cs35l35->regmap,
+			CS35L35_CLASS_H_FET_DRIVE_CTL, CS35L35_CH_WKFET_DEL_MASK,
+			0 << CS35L35_CH_WKFET_DEL_SHIFT);
+		if (ret != 0) {
+			dev_err(codec->dev, "Failed to set fet config %d\n",
+				ret);
+			return ret;
+		}
+	}
+
+/*
+ * You can pull more Monitor data from the SDOUT pin than going to SDIN
+ * Just make sure your SCLK is fast enough to fill the frame
+ */
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		switch (params_width(params)) {
+		case 8:
+			audin_format = CS35L35_SDIN_DEPTH_8;
+			break;
+		case 16:
+			audin_format = CS35L35_SDIN_DEPTH_16;
+			break;
+		case 24:
+			audin_format = CS35L35_SDIN_DEPTH_24;
+			break;
+		default:
+			dev_err(codec->dev, "Unsupported Width %d\n",
+				params_width(params));
+		}
+		regmap_update_bits(cs35l35->regmap,
+			CS35L35_AUDIN_DEPTH_CTL, CS35L35_AUDIN_DEPTH_MASK,
+			audin_format << CS35L35_AUDIN_DEPTH_SHIFT);
+		if (cs35l35->pdata.stereo) {
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_AUDIN_DEPTH_CTL, CS35L35_ADVIN_DEPTH_MASK,
+				audin_format << CS35L35_ADVIN_DEPTH_SHIFT);
+		}
+	}
+/* We have to take the SCLK to derive num sclks
+ * to configure the CLOCK_CTL3 register correctly
+ */
+	if ((cs35l35->sclk / srate) % 4) {
+		dev_err(codec->dev, "Unsupported sclk/fs ratio %d:%d\n",
+					cs35l35->sclk, srate);
+		return -EINVAL;
+	}
+	sp_sclks = ((cs35l35->sclk / srate) / 4) - 1;
+
+	if (cs35l35->i2s_mode) {
+		/* Only certain ratios are supported in I2S Slave Mode */
+		if (cs35l35->slave_mode) {
+			switch (sp_sclks) {
+			case CS35L35_SP_SCLKS_32FS:
+			case CS35L35_SP_SCLKS_48FS:
+			case CS35L35_SP_SCLKS_64FS:
+			break;
+			default:
+				dev_err(codec->dev, "ratio not supported\n");
+				return -EINVAL;
+			};
+		} else {
+			/* Only certain ratios supported in I2S MASTER Mode */
+			switch (sp_sclks) {
+			case CS35L35_SP_SCLKS_32FS:
+			case CS35L35_SP_SCLKS_64FS:
+			break;
+			default:
+				dev_err(codec->dev, "ratio not supported\n");
+				return -EINVAL;
+			};
+		}
+		ret = regmap_update_bits(cs35l35->regmap,
+			CS35L35_CLK_CTL3, CS35L35_SP_SCLKS_MASK,
+			sp_sclks << CS35L35_SP_SCLKS_SHIFT);
+		if (ret != 0) {
+			dev_err(codec->dev, "Failed to set fsclk %d\n", ret);
+			return ret;
+		}
+	}
+	if (cs35l35->pdm_mode) {
+		regmap_update_bits(cs35l35->regmap, CS35L35_AMP_INP_DRV_CTL,
+			CS35L35_PDM_MODE_MASK, 1 << CS35L35_PDM_MODE_SHIFT);
+	} else {
+		regmap_update_bits(cs35l35->regmap, CS35L35_AMP_INP_DRV_CTL,
+			CS35L35_PDM_MODE_MASK, 0 << CS35L35_PDM_MODE_SHIFT);
+	}
+	return ret;
+}
+
+static const unsigned int cs35l35_src_rates[] = {
+	44100, 48000, 88200, 96000, 176400, 192000
+};
+
+static const struct snd_pcm_hw_constraint_list cs35l35_constraints = {
+	.count  = ARRAY_SIZE(cs35l35_src_rates),
+	.list   = cs35l35_src_rates,
+};
+
+static int cs35l35_pcm_startup(struct snd_pcm_substream *substream,
+			       struct snd_soc_dai *dai)
+{
+	snd_pcm_hw_constraint_list(substream->runtime, 0,
+				SNDRV_PCM_HW_PARAM_RATE, &cs35l35_constraints);
+	return 0;
+}
+
+static const unsigned int cs35l35_pdm_rates[] = {
+	44100, 48000, 88200, 96000
+};
+
+static const struct snd_pcm_hw_constraint_list cs35l35_pdm_constraints = {
+	.count  = ARRAY_SIZE(cs35l35_pdm_rates),
+	.list   = cs35l35_pdm_rates,
+};
+
+static int cs35l35_pdm_startup(struct snd_pcm_substream *substream,
+			       struct snd_soc_dai *dai)
+{
+	snd_pcm_hw_constraint_list(substream->runtime, 0,
+				SNDRV_PCM_HW_PARAM_RATE,
+				&cs35l35_pdm_constraints);
+	return 0;
+}
+
+static int cs35l35_dai_set_sysclk(struct snd_soc_dai *dai,
+				int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec);
+
+	/* Need the SCLK Frequency */
+	cs35l35->sclk = freq;
+
+	return 0;
+}
+
+static const struct snd_soc_dai_ops cs35l35_ops = {
+	.startup = cs35l35_pcm_startup,
+	.set_fmt = cs35l35_set_dai_fmt,
+	.hw_params = cs35l35_pcm_hw_params,
+	.set_sysclk = cs35l35_dai_set_sysclk,
+};
+
+static const struct snd_soc_dai_ops cs35l35_pdm_ops = {
+	.startup = cs35l35_pdm_startup,
+	.set_fmt = cs35l35_set_dai_fmt,
+	.hw_params = cs35l35_pcm_hw_params,
+	.set_sysclk = cs35l35_dai_set_sysclk,
+};
+
+static struct snd_soc_dai_driver cs35l35_dai[] = {
+	{
+		.name = "cs35l35-pcm",
+		.id = 0,
+		.playback = {
+			.stream_name = "AMP Playback",
+			.channels_min = 1,
+			.channels_max = 8,
+			.rates = SNDRV_PCM_RATE_KNOT,
+			.formats = CS35L35_FORMATS,
+		},
+		.capture = {
+			.stream_name = "AMP Capture",
+			.channels_min = 1,
+			.channels_max = 8,
+			.rates = SNDRV_PCM_RATE_KNOT,
+			.formats = CS35L35_FORMATS,
+		},
+		.ops = &cs35l35_ops,
+		.symmetric_rates = 1,
+	},
+	{
+		.name = "cs35l35-pdm",
+		.id = 1,
+		.playback = {
+			.stream_name = "PDM Playback",
+			.channels_min = 1,
+			.channels_max = 2,
+			.rates = SNDRV_PCM_RATE_KNOT,
+			.formats = CS35L35_FORMATS,
+		},
+		.ops = &cs35l35_pdm_ops,
+	},
+};
+
+static int cs35l35_codec_set_sysclk(struct snd_soc_codec *codec,
+				int clk_id, int source, unsigned int freq,
+				int dir)
+{
+	struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec);
+	int clksrc;
+	int ret = 0;
+
+	switch (clk_id) {
+	case 0:
+		clksrc = CS35L35_CLK_SOURCE_MCLK;
+		break;
+	case 1:
+		clksrc = CS35L35_CLK_SOURCE_SCLK;
+		break;
+	case 2:
+		clksrc = CS35L35_CLK_SOURCE_PDM;
+		break;
+	default:
+		dev_err(codec->dev, "Invalid CLK Source\n");
+		return -EINVAL;
+	};
+
+	switch (freq) {
+	case 5644800:
+	case 6144000:
+	case 11289600:
+	case 12000000:
+	case 12288000:
+	case 13000000:
+	case 22579200:
+	case 24000000:
+	case 24576000:
+	case 26000000:
+		cs35l35->sysclk = freq;
+		break;
+	default:
+		dev_err(codec->dev, "Invalid CLK Frequency\n");
+		return -EINVAL;
+	}
+
+	ret = regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1,
+		CS35L35_CLK_SOURCE_MASK, clksrc << CS35L35_CLK_SOURCE_SHIFT);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to set sysclk %d\n", ret);
+		return ret;
+	}
+
+	return ret;
+}
+
+static int cs35l35_codec_probe(struct snd_soc_codec *codec)
+{
+	struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec);
+	struct classh_cfg *classh = &cs35l35->pdata.classh_algo;
+	struct monitor_cfg *monitor_config = &cs35l35->pdata.mon_cfg;
+	int ret;
+
+	/* Set Platform Data */
+	if (cs35l35->pdata.bst_vctl)
+		regmap_update_bits(cs35l35->regmap, CS35L35_BST_CVTR_V_CTL,
+			CS35L35_BST_CTL_MASK, cs35l35->pdata.bst_vctl);
+
+	if (cs35l35->pdata.bst_ipk)
+		regmap_update_bits(cs35l35->regmap, CS35L35_BST_PEAK_I,
+			CS35L35_BST_IPK_MASK,
+			cs35l35->pdata.bst_ipk << CS35L35_BST_IPK_SHIFT);
+
+	if (cs35l35->pdata.gain_zc)
+		regmap_update_bits(cs35l35->regmap, CS35L35_PROTECT_CTL,
+			CS35L35_AMP_GAIN_ZC_MASK,
+			cs35l35->pdata.gain_zc << CS35L35_AMP_GAIN_ZC_SHIFT);
+
+	if (cs35l35->pdata.aud_channel)
+		regmap_update_bits(cs35l35->regmap,
+			CS35L35_AUDIN_RXLOC_CTL,
+			CS35L35_AUD_IN_LR_MASK,
+			cs35l35->pdata.aud_channel << CS35L35_AUD_IN_LR_SHIFT);
+
+	if (cs35l35->pdata.stereo) {
+		regmap_update_bits(cs35l35->regmap,
+			CS35L35_ADVIN_RXLOC_CTL, CS35L35_ADV_IN_LR_MASK,
+			cs35l35->pdata.adv_channel << CS35L35_ADV_IN_LR_SHIFT);
+		if (cs35l35->pdata.shared_bst)
+			regmap_update_bits(cs35l35->regmap, CS35L35_CLASS_H_CTL,
+				CS35L35_CH_STEREO_MASK, 1 << CS35L35_CH_STEREO_SHIFT);
+		ret = snd_soc_add_codec_controls(codec, cs35l35_adv_controls,
+					ARRAY_SIZE(cs35l35_adv_controls));
+		if (ret)
+			return ret;
+	}
+
+	if (cs35l35->pdata.sp_drv_str)
+		regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1,
+			CS35L35_SP_DRV_MASK,
+			cs35l35->pdata.sp_drv_str << CS35L35_SP_DRV_SHIFT);
+
+	if (classh->classh_algo_enable) {
+		if (classh->classh_bst_override)
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_CLASS_H_CTL, CS35L35_CH_BST_OVR_MASK,
+				classh->classh_bst_override << CS35L35_CH_BST_OVR_SHIFT);
+		if (classh->classh_bst_max_limit)
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_CLASS_H_CTL, CS35L35_CH_BST_LIM_MASK,
+				classh->classh_bst_max_limit << CS35L35_CH_BST_LIM_SHIFT);
+		if (classh->classh_mem_depth)
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_CLASS_H_CTL, CS35L35_CH_MEM_DEPTH_MASK,
+				classh->classh_mem_depth << CS35L35_CH_MEM_DEPTH_SHIFT);
+		if (classh->classh_headroom)
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_CLASS_H_HEADRM_CTL, CS35L35_CH_HDRM_CTL_MASK,
+				classh->classh_headroom << CS35L35_CH_HDRM_CTL_SHIFT);
+		if (classh->classh_release_rate)
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_CLASS_H_RELEASE_RATE, CS35L35_CH_REL_RATE_MASK,
+				classh->classh_release_rate << CS35L35_CH_REL_RATE_SHIFT);
+		if (classh->classh_wk_fet_disable)
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_CLASS_H_FET_DRIVE_CTL, CS35L35_CH_WKFET_DIS_MASK,
+				classh->classh_wk_fet_disable << CS35L35_CH_WKFET_DIS_SHIFT);
+		if (classh->classh_wk_fet_delay)
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_CLASS_H_FET_DRIVE_CTL, CS35L35_CH_WKFET_DEL_MASK,
+				classh->classh_wk_fet_delay << CS35L35_CH_WKFET_DEL_SHIFT);
+		if (classh->classh_wk_fet_thld)
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_CLASS_H_FET_DRIVE_CTL, CS35L35_CH_WKFET_THLD_MASK,
+				classh->classh_wk_fet_thld << CS35L35_CH_WKFET_THLD_SHIFT);
+		if (classh->classh_vpch_auto)
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_CLASS_H_VP_CTL, CS35L35_CH_VP_AUTO_MASK,
+				classh->classh_vpch_auto << CS35L35_CH_VP_AUTO_SHIFT);
+		if (classh->classh_vpch_rate)
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_CLASS_H_VP_CTL, CS35L35_CH_VP_RATE_MASK,
+				classh->classh_vpch_rate << CS35L35_CH_VP_RATE_SHIFT);
+		if (classh->classh_vpch_man)
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_CLASS_H_VP_CTL, CS35L35_CH_VP_MAN_MASK,
+				classh->classh_vpch_man << CS35L35_CH_VP_MAN_SHIFT);
+	}
+
+	if (monitor_config->is_present) {
+		if (monitor_config->vmon_specs) {
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_SPKMON_DEPTH_CTL, CS35L35_VMON_DEPTH_MASK,
+				monitor_config->vmon_dpth << CS35L35_VMON_DEPTH_SHIFT);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_VMON_TXLOC_CTL, CS35L35_MON_TXLOC_MASK,
+				monitor_config->vmon_loc << CS35L35_MON_TXLOC_SHIFT);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_VMON_TXLOC_CTL, CS35L35_MON_FRM_MASK,
+				monitor_config->vmon_frm << CS35L35_MON_FRM_SHIFT);
+		}
+		if (monitor_config->imon_specs) {
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_SPKMON_DEPTH_CTL, CS35L35_IMON_DEPTH_MASK,
+				monitor_config->imon_dpth << CS35L35_IMON_DEPTH_SHIFT);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_IMON_TXLOC_CTL, CS35L35_MON_TXLOC_MASK,
+				monitor_config->imon_loc << CS35L35_MON_TXLOC_SHIFT);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_IMON_TXLOC_CTL, CS35L35_MON_FRM_MASK,
+				monitor_config->imon_frm << CS35L35_MON_FRM_SHIFT);
+		}
+		if (monitor_config->vpmon_specs) {
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_SUPMON_DEPTH_CTL, CS35L35_VPMON_DEPTH_MASK,
+				monitor_config->vpmon_dpth << CS35L35_VPMON_DEPTH_SHIFT);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_VPMON_TXLOC_CTL, CS35L35_MON_TXLOC_MASK,
+				monitor_config->vpmon_loc << CS35L35_MON_TXLOC_SHIFT);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_VPMON_TXLOC_CTL, CS35L35_MON_FRM_MASK,
+				monitor_config->vpmon_frm << CS35L35_MON_FRM_SHIFT);
+		}
+		if (monitor_config->vbstmon_specs) {
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_SUPMON_DEPTH_CTL, CS35L35_VBSTMON_DEPTH_MASK,
+				monitor_config->vpmon_dpth << CS35L35_VBSTMON_DEPTH_SHIFT);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_VBSTMON_TXLOC_CTL, CS35L35_MON_TXLOC_MASK,
+				monitor_config->vbstmon_loc << CS35L35_MON_TXLOC_SHIFT);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_VBSTMON_TXLOC_CTL, CS35L35_MON_FRM_MASK,
+				monitor_config->vbstmon_frm << CS35L35_MON_FRM_SHIFT);
+		}
+		if (monitor_config->vpbrstat_specs) {
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_SUPMON_DEPTH_CTL, CS35L35_VPBRSTAT_DEPTH_MASK,
+				monitor_config->vpbrstat_dpth << CS35L35_VPBRSTAT_DEPTH_SHIFT);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_VPBR_STATUS_TXLOC_CTL, CS35L35_MON_TXLOC_MASK,
+				monitor_config->vpbrstat_loc << CS35L35_MON_TXLOC_SHIFT);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_VPBR_STATUS_TXLOC_CTL, CS35L35_MON_FRM_MASK,
+				monitor_config->vpbrstat_frm << CS35L35_MON_FRM_SHIFT);
+		}
+		if (monitor_config->zerofill_specs) {
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_SUPMON_DEPTH_CTL, CS35L35_ZEROFILL_DEPTH_MASK,
+				monitor_config->zerofill_dpth << CS35L35_ZEROFILL_DEPTH_SHIFT);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_ZERO_FILL_LOC_CTL, CS35L35_MON_TXLOC_MASK,
+				monitor_config->zerofill_loc << CS35L35_MON_TXLOC_SHIFT);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_ZERO_FILL_LOC_CTL, CS35L35_MON_FRM_MASK,
+				monitor_config->zerofill_frm << CS35L35_MON_FRM_SHIFT);
+		}
+	}
+
+	return ret;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_cs35l35 = {
+	.probe = cs35l35_codec_probe,
+	.set_sysclk = cs35l35_codec_set_sysclk,
+	.component_driver = {
+		.controls = cs35l35_aud_controls,
+		.num_controls = ARRAY_SIZE(cs35l35_aud_controls),
+		.dapm_widgets = cs35l35_dapm_widgets,
+		.num_dapm_widgets = ARRAY_SIZE(cs35l35_dapm_widgets),
+
+		.dapm_routes = cs35l35_audio_map,
+		.num_dapm_routes = ARRAY_SIZE(cs35l35_audio_map),
+	},
+};
+
+static struct regmap_config cs35l35_regmap = {
+	.reg_bits = 8,
+	.val_bits = 8,
+
+	.max_register = CS35L35_MAX_REGISTER,
+	.reg_defaults = cs35l35_reg,
+	.num_reg_defaults = ARRAY_SIZE(cs35l35_reg),
+	.volatile_reg = cs35l35_volatile_register,
+	.readable_reg = cs35l35_readable_register,
+	.precious_reg = cs35l35_precious_register,
+	.cache_type = REGCACHE_RBTREE,
+};
+
+static irqreturn_t cs35l35_irq(int irq, void *data)
+{
+	struct cs35l35_private *cs35l35 = data;
+	unsigned int sticky1, sticky2, sticky3, sticky4;
+	unsigned int mask1, mask2, mask3, mask4, current1;
+
+	/* ack the irq by reading all status registers */
+	regmap_read(cs35l35->regmap, CS35L35_INT_STATUS_4, &sticky4);
+	regmap_read(cs35l35->regmap, CS35L35_INT_STATUS_3, &sticky3);
+	regmap_read(cs35l35->regmap, CS35L35_INT_STATUS_2, &sticky2);
+	regmap_read(cs35l35->regmap, CS35L35_INT_STATUS_1, &sticky1);
+
+	regmap_read(cs35l35->regmap, CS35L35_INT_MASK_4, &mask4);
+	regmap_read(cs35l35->regmap, CS35L35_INT_MASK_3, &mask3);
+	regmap_read(cs35l35->regmap, CS35L35_INT_MASK_2, &mask2);
+	regmap_read(cs35l35->regmap, CS35L35_INT_MASK_1, &mask1);
+
+	/* Check to see if unmasked bits are active */
+	if (!(sticky1 & ~mask1) && !(sticky2 & ~mask2) && !(sticky3 & ~mask3)
+			&& !(sticky4 & ~mask4))
+		return IRQ_NONE;
+
+	if (sticky2 & CS35L35_PDN_DONE)
+		complete(&cs35l35->pdn_done);
+
+	/* read the current values */
+	regmap_read(cs35l35->regmap, CS35L35_INT_STATUS_1, &current1);
+
+	/* handle the interrupts */
+	if (sticky1 & CS35L35_CAL_ERR) {
+		pr_err("%s : Calibration Error\n", __func__);
+
+		/* error is no longer asserted; safe to reset */
+		if (!(current1 & CS35L35_CAL_ERR)) {
+			pr_debug("%s : Cal error release\n", __func__);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_PROT_RELEASE_CTL, CS35L35_CAL_ERR_RLS, 0);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_PROT_RELEASE_CTL, CS35L35_CAL_ERR_RLS,
+				CS35L35_CAL_ERR_RLS);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_PROT_RELEASE_CTL, CS35L35_CAL_ERR_RLS, 0);
+		}
+	}
+
+	if (sticky1 & CS35L35_AMP_SHORT) {
+		/* error is no longer asserted; safe to reset */
+		if (!(current1 & CS35L35_AMP_SHORT)) {
+			pr_debug("%s :Amp short error release\n", __func__);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_PROT_RELEASE_CTL, CS35L35_SHORT_RLS, 0);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_PROT_RELEASE_CTL, CS35L35_SHORT_RLS,
+				CS35L35_SHORT_RLS);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_PROT_RELEASE_CTL, CS35L35_SHORT_RLS, 0);
+		}
+	}
+
+	if (sticky1 & CS35L35_OTW) {
+		pr_err("%s : Over temperature warning\n", __func__);
+
+		/* error is no longer asserted; safe to reset */
+		if (!(current1 & CS35L35_OTW)) {
+			pr_debug("%s : Over temperature warning release\n",
+				__func__);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_PROT_RELEASE_CTL, CS35L35_OTW_RLS, 0);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_PROT_RELEASE_CTL, CS35L35_OTW_RLS,
+				CS35L35_OTW_RLS);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_PROT_RELEASE_CTL, CS35L35_OTW_RLS, 0);
+		}
+	}
+
+	if (sticky1 & CS35L35_OTE) {
+		pr_crit("%s : Over temperature error\n", __func__);
+
+		/* error is no longer asserted; safe to reset */
+		if (!(current1 & CS35L35_OTE)) {
+			pr_debug("%s : Over temperature error release\n",
+				__func__);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_PROT_RELEASE_CTL, CS35L35_OTE_RLS, 0);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_PROT_RELEASE_CTL, CS35L35_OTE_RLS,
+				CS35L35_OTE_RLS);
+			regmap_update_bits(cs35l35->regmap,
+				CS35L35_PROT_RELEASE_CTL, CS35L35_OTE_RLS, 0);
+		}
+	}
+
+	if (sticky3 & CS35L35_BST_HIGH) {
+		pr_crit("%s : VBST error: powering off!\n", __func__);
+		regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2,
+			CS35L35_PDN_AMP, CS35L35_PDN_AMP);
+		regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1,
+			CS35L35_PDN_ALL, CS35L35_PDN_ALL);
+	}
+
+	if (sticky3 & CS35L35_LBST_SHORT) {
+		pr_crit("%s : LBST error: powering off!\n", __func__);
+		regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2,
+			CS35L35_PDN_AMP, CS35L35_PDN_AMP);
+		regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1,
+			CS35L35_PDN_ALL, CS35L35_PDN_ALL);
+	}
+
+	if (sticky2 & CS35L35_VPBR_ERR)
+		pr_err("%s : Error: Reactive Brownout\n", __func__);
+
+	if (sticky4 & CS35L35_VMON_OVFL)
+		pr_err("%s : Error: VMON overflow\n", __func__);
+
+	if (sticky4 & CS35L35_IMON_OVFL)
+		pr_err("%s : Error: IMON overflow\n", __func__);
+
+	return IRQ_HANDLED;
+}
+
+
+static int cs35l35_handle_of_data(struct i2c_client *i2c_client,
+				struct cs35l35_platform_data *pdata)
+{
+	struct device_node *np = i2c_client->dev.of_node;
+	struct device_node *classh, *signal_format;
+	struct classh_cfg *classh_config = &pdata->classh_algo;
+	struct monitor_cfg *monitor_config = &pdata->mon_cfg;
+	unsigned int val32 = 0;
+	u8 monitor_array[3];
+	int ret = 0;
+
+	if (!np)
+		return 0;
+
+	pdata->bst_pdn_fet_on = of_property_read_bool(np,
+					"cirrus,boost-pdn-fet-on");
+
+	if (of_property_read_u32(np, "cirrus,boost-ctl-millivolt", &val32) >= 0)
+		pdata->bst_vctl = val32;
+
+	if (of_property_read_u32(np, "cirrus,boost-ipk-milliamp", &val32) >= 0)
+		pdata->bst_ipk = val32;
+
+	if (of_property_read_u32(np, "cirrus,sp-drv-strength", &val32) >= 0)
+		pdata->sp_drv_str = val32;
+
+	pdata->stereo = of_property_read_bool(np, "cirrus,stereo-config");
+
+	if (pdata->stereo) {
+		if (of_property_read_u32(np, "cirrus,audio-channel", &val32) >= 0)
+			pdata->aud_channel = val32;
+		if (of_property_read_u32(np, "cirrus,advisory-channel",
+					&val32) >= 0)
+			pdata->adv_channel = val32;
+		pdata->shared_bst = of_property_read_bool(np,
+						"cirrus,shared-boost");
+	}
+
+	pdata->gain_zc = of_property_read_bool(np, "cirrus,amp-gain-zc");
+
+	classh = of_get_child_by_name(np, "cirrus,classh-internal-algo");
+	classh_config->classh_algo_enable = classh ? true : false;
+
+	if (classh_config->classh_algo_enable) {
+		classh_config->classh_bst_override =
+			of_property_read_bool(np, "cirrus,classh-bst-overide");
+
+		if (of_property_read_u32(classh, "cirrus,classh-bst-max-limit",
+					&val32) >= 0)
+			classh_config->classh_bst_max_limit = val32;
+		if (of_property_read_u32(classh, "cirrus,classh-mem-depth",
+					&val32) >= 0)
+			classh_config->classh_mem_depth = val32;
+		if (of_property_read_u32(classh, "cirrus,classh-release-rate",
+					&val32) >= 0)
+			classh_config->classh_release_rate = val32;
+		if (of_property_read_u32(classh, "cirrus,classh-headroom",
+					&val32) >= 0)
+			classh_config->classh_headroom = val32;
+		if (of_property_read_u32(classh, "cirrus,classh-wk-fet-disable",
+					&val32) >= 0)
+			classh_config->classh_wk_fet_disable = val32;
+		if (of_property_read_u32(classh, "cirrus,classh-wk-fet-delay",
+					&val32) >= 0)
+			classh_config->classh_wk_fet_delay = val32;
+		if (of_property_read_u32(classh, "cirrus,classh-wk-fet-thld",
+					&val32) >= 0)
+			classh_config->classh_wk_fet_thld = val32;
+		if (of_property_read_u32(classh, "cirrus,classh-vpch-auto",
+					&val32) >= 0)
+			classh_config->classh_vpch_auto = val32;
+		if (of_property_read_u32(classh, "cirrus,classh-vpch-rate",
+					&val32) >= 0)
+			classh_config->classh_vpch_rate = val32;
+		if (of_property_read_u32(classh, "cirrus,classh-vpch-man",
+					&val32) >= 0)
+			classh_config->classh_vpch_man = val32;
+	}
+	of_node_put(classh);
+
+	/* frame depth location */
+	signal_format = of_get_child_by_name(np, "cirrus,monitor-signal-format");
+	monitor_config->is_present = signal_format ? true : false;
+	if (monitor_config->is_present) {
+		ret = of_property_read_u8_array(signal_format, "cirrus,imon",
+				   monitor_array, ARRAY_SIZE(monitor_array));
+		if (!ret) {
+			monitor_config->imon_specs = true;
+			monitor_config->imon_dpth = monitor_array[0];
+			monitor_config->imon_loc = monitor_array[1];
+			monitor_config->imon_frm = monitor_array[2];
+		}
+		ret = of_property_read_u8_array(signal_format, "cirrus,vmon",
+				   monitor_array, ARRAY_SIZE(monitor_array));
+		if (!ret) {
+			monitor_config->vmon_specs = true;
+			monitor_config->vmon_dpth = monitor_array[0];
+			monitor_config->vmon_loc = monitor_array[1];
+			monitor_config->vmon_frm = monitor_array[2];
+		}
+		ret = of_property_read_u8_array(signal_format, "cirrus,vpmon",
+				   monitor_array, ARRAY_SIZE(monitor_array));
+		if (!ret) {
+			monitor_config->vpmon_specs = true;
+			monitor_config->vpmon_dpth = monitor_array[0];
+			monitor_config->vpmon_loc = monitor_array[1];
+			monitor_config->vpmon_frm = monitor_array[2];
+		}
+		ret = of_property_read_u8_array(signal_format, "cirrus,vbstmon",
+				   monitor_array, ARRAY_SIZE(monitor_array));
+		if (!ret) {
+			monitor_config->vbstmon_specs = true;
+			monitor_config->vbstmon_dpth = monitor_array[0];
+			monitor_config->vbstmon_loc = monitor_array[1];
+			monitor_config->vbstmon_frm = monitor_array[2];
+		}
+		ret = of_property_read_u8_array(signal_format, "cirrus,vpbrstat",
+				   monitor_array, ARRAY_SIZE(monitor_array));
+		if (!ret) {
+			monitor_config->vpbrstat_specs = true;
+			monitor_config->vpbrstat_dpth = monitor_array[0];
+			monitor_config->vpbrstat_loc = monitor_array[1];
+			monitor_config->vpbrstat_frm = monitor_array[2];
+		}
+		ret = of_property_read_u8_array(signal_format, "cirrus,zerofill",
+				   monitor_array, ARRAY_SIZE(monitor_array));
+		if (!ret) {
+			monitor_config->zerofill_specs = true;
+			monitor_config->zerofill_dpth = monitor_array[0];
+			monitor_config->zerofill_loc = monitor_array[1];
+			monitor_config->zerofill_frm = monitor_array[2];
+		}
+	}
+	of_node_put(signal_format);
+
+	return 0;
+}
+
+/* Errata Rev A0 */
+static const struct reg_sequence cs35l35_errata_patch[] = {
+
+	{ 0x7F, 0x99 },
+	{ 0x00, 0x99 },
+	{ 0x52, 0x22 },
+	{ 0x04, 0x14 },
+	{ 0x6D, 0x44 },
+	{ 0x24, 0x10 },
+	{ 0x58, 0xC4 },
+	{ 0x00, 0x98 },
+	{ 0x18, 0x08 },
+	{ 0x00, 0x00 },
+	{ 0x7F, 0x00 },
+};
+
+static int cs35l35_i2c_probe(struct i2c_client *i2c_client,
+			      const struct i2c_device_id *id)
+{
+	struct cs35l35_private *cs35l35;
+	struct cs35l35_platform_data *pdata =
+		dev_get_platdata(&i2c_client->dev);
+	int i;
+	int ret;
+	unsigned int devid = 0;
+	unsigned int reg;
+
+	cs35l35 = devm_kzalloc(&i2c_client->dev,
+			       sizeof(struct cs35l35_private),
+			       GFP_KERNEL);
+	if (!cs35l35) {
+		dev_err(&i2c_client->dev, "could not allocate codec\n");
+		return -ENOMEM;
+	}
+
+	i2c_set_clientdata(i2c_client, cs35l35);
+	cs35l35->regmap = devm_regmap_init_i2c(i2c_client, &cs35l35_regmap);
+	if (IS_ERR(cs35l35->regmap)) {
+		ret = PTR_ERR(cs35l35->regmap);
+		dev_err(&i2c_client->dev, "regmap_init() failed: %d\n", ret);
+		goto err;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(cs35l35_supplies); i++)
+		cs35l35->supplies[i].supply = cs35l35_supplies[i];
+		cs35l35->num_supplies = ARRAY_SIZE(cs35l35_supplies);
+
+	ret = devm_regulator_bulk_get(&i2c_client->dev,
+			cs35l35->num_supplies,
+			cs35l35->supplies);
+	if (ret != 0) {
+		dev_err(&i2c_client->dev,
+			"Failed to request core supplies: %d\n",
+			ret);
+		return ret;
+	}
+
+	if (pdata) {
+		cs35l35->pdata = *pdata;
+	} else {
+		pdata = devm_kzalloc(&i2c_client->dev,
+				     sizeof(struct cs35l35_platform_data),
+				GFP_KERNEL);
+		if (!pdata) {
+			dev_err(&i2c_client->dev,
+				"could not allocate pdata\n");
+			return -ENOMEM;
+		}
+		if (i2c_client->dev.of_node) {
+			ret = cs35l35_handle_of_data(i2c_client, pdata);
+			if (ret != 0)
+				return ret;
+
+		}
+		cs35l35->pdata = *pdata;
+	}
+
+	ret = regulator_bulk_enable(cs35l35->num_supplies,
+					cs35l35->supplies);
+	if (ret != 0) {
+		dev_err(&i2c_client->dev,
+			"Failed to enable core supplies: %d\n",
+			ret);
+		return ret;
+	}
+
+	/* returning NULL can be an option if in stereo mode */
+	cs35l35->reset_gpio = devm_gpiod_get_optional(&i2c_client->dev,
+		"reset", GPIOD_OUT_LOW);
+	if (IS_ERR(cs35l35->reset_gpio))
+		return PTR_ERR(cs35l35->reset_gpio);
+
+	if (cs35l35->reset_gpio)
+		gpiod_set_value_cansleep(cs35l35->reset_gpio, 1);
+
+	init_completion(&cs35l35->pdn_done);
+
+	ret = regmap_register_patch(cs35l35->regmap, cs35l35_errata_patch,
+				    ARRAY_SIZE(cs35l35_errata_patch));
+	if (ret < 0) {
+		dev_err(&i2c_client->dev, "Failed to apply errata patch\n");
+		return ret;
+	}
+
+	ret = devm_request_threaded_irq(&i2c_client->dev, i2c_client->irq, NULL,
+			cs35l35_irq, IRQF_ONESHOT | IRQF_TRIGGER_LOW,
+			"cs35l35", cs35l35);
+	if (ret != 0) {
+		dev_err(&i2c_client->dev, "Failed to request IRQ: %d\n", ret);
+		goto err;
+	}
+	/* initialize codec */
+	ret = regmap_read(cs35l35->regmap, CS35L35_DEVID_AB, &reg);
+
+	devid = (reg & 0xFF) << 12;
+	ret = regmap_read(cs35l35->regmap, CS35L35_DEVID_CD, &reg);
+	devid |= (reg & 0xFF) << 4;
+	ret = regmap_read(cs35l35->regmap, CS35L35_DEVID_E, &reg);
+	devid |= (reg & 0xF0) >> 4;
+
+	if (devid != CS35L35_CHIP_ID) {
+		dev_err(&i2c_client->dev,
+			"CS35L35 Device ID (%X). Expected ID %X\n",
+			devid, CS35L35_CHIP_ID);
+		ret = -ENODEV;
+		goto err;
+	}
+
+	ret = regmap_read(cs35l35->regmap, CS35L35_REV_ID, &reg);
+	if (ret < 0) {
+		dev_err(&i2c_client->dev, "Get Revision ID failed\n");
+		goto err;
+	}
+
+	dev_info(&i2c_client->dev,
+		 "Cirrus Logic CS35L35 (%x), Revision: %02X\n", devid,
+		ret & 0xFF);
+
+	/* Set the INT Masks for critical errors */
+	regmap_write(cs35l35->regmap, CS35L35_INT_MASK_1, CS35L35_INT1_CRIT_MASK);
+	regmap_write(cs35l35->regmap, CS35L35_INT_MASK_2, CS35L35_INT2_CRIT_MASK);
+	regmap_write(cs35l35->regmap, CS35L35_INT_MASK_3, CS35L35_INT3_CRIT_MASK);
+	regmap_write(cs35l35->regmap, CS35L35_INT_MASK_4, CS35L35_INT4_CRIT_MASK);
+
+	regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2,
+		CS35L35_PWR2_PDN_MASK, CS35L35_PWR2_PDN_MASK);
+
+	if (cs35l35->pdata.bst_pdn_fet_on)
+		regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2,
+			CS35L35_PDN_BST_MASK, 1 << CS35L35_PDN_BST_FETON_SHIFT);
+	else
+		regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2,
+			CS35L35_PDN_BST_MASK, 1 << CS35L35_PDN_BST_FETOFF_SHIFT);
+
+	regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL3,
+		CS35L35_PWR3_PDN_MASK, CS35L35_PWR3_PDN_MASK);
+
+	regmap_update_bits(cs35l35->regmap, CS35L35_PROTECT_CTL,
+		CS35L35_AMP_MUTE_MASK, 1 << CS35L35_AMP_MUTE_SHIFT);
+
+	ret =  snd_soc_register_codec(&i2c_client->dev,
+			&soc_codec_dev_cs35l35, cs35l35_dai,
+			ARRAY_SIZE(cs35l35_dai));
+	if (ret < 0) {
+		dev_err(&i2c_client->dev,
+			"%s: Register codec failed\n", __func__);
+		goto err;
+	}
+
+err:
+	regulator_bulk_disable(cs35l35->num_supplies,
+			       cs35l35->supplies);
+	return ret;
+}
+
+static int cs35l35_i2c_remove(struct i2c_client *client)
+{
+	snd_soc_unregister_codec(&client->dev);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+static const struct of_device_id cs35l35_of_match[] = {
+	{.compatible = "cirrus,cs35l35"},
+	{},
+};
+MODULE_DEVICE_TABLE(of, cs35l35_of_match);
+
+static const struct i2c_device_id cs35l35_id[] = {
+	{"cs35l35", 0},
+	{}
+};
+
+MODULE_DEVICE_TABLE(i2c, cs35l35_id);
+
+static struct i2c_driver cs35l35_i2c_driver = {
+	.driver = {
+		.name = "cs35l35",
+		.of_match_table = cs35l35_of_match,
+	},
+	.id_table = cs35l35_id,
+	.probe = cs35l35_i2c_probe,
+	.remove = cs35l35_i2c_remove,
+};
+
+module_i2c_driver(cs35l35_i2c_driver);
+
+MODULE_DESCRIPTION("ASoC CS35L35 driver");
+MODULE_AUTHOR("Li Xu, Cirrus Logic Inc, <li.xu@cirrus.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/cs35l35.h b/sound/soc/codecs/cs35l35.h
new file mode 100644
index 0000000..fb02785
--- /dev/null
+++ b/sound/soc/codecs/cs35l35.h
@@ -0,0 +1,284 @@
+/*
+ * cs35l35.h -- CS35L35 ALSA SoC audio driver
+ *
+ * Copyright 2016 Cirrus Logic, Inc.
+ *
+ * Author: Li Xu <li.xu@cirrus.com>
+ *
+ * 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 __CS35L35_H__
+#define __CS35L35_H__
+
+#define CS35L35_FIRSTREG		0x01
+#define CS35L35_LASTREG			0x7E
+#define CS35L35_CHIP_ID			0x00035A35
+#define CS35L35_DEVID_AB		0x01	/* Device ID A & B [RO] */
+#define CS35L35_DEVID_CD		0x02    /* Device ID C & D [RO] */
+#define CS35L35_DEVID_E			0x03    /* Device ID E [RO] */
+#define CS35L35_FAB_ID			0x04	/* Fab ID [RO] */
+#define CS35L35_REV_ID			0x05	/* Revision ID [RO] */
+#define CS35L35_PWRCTL1			0x06    /* Power Ctl 1 */
+#define CS35L35_PWRCTL2			0x07    /* Power Ctl 2 */
+#define CS35L35_PWRCTL3			0x08	/* Power Ctl 3 */
+#define CS35L35_CLK_CTL1		0x0A	/* Clocking Ctl 1 */
+#define CS35L35_CLK_CTL2		0x0B	/* Clocking Ctl 2 */
+#define CS35L35_CLK_CTL3		0x0C	/* Clocking Ctl 3 */
+#define CS35L35_SP_FMT_CTL1		0x0D	/* Serial Port Format CTL1 */
+#define CS35L35_SP_FMT_CTL2		0x0E	/* Serial Port Format CTL2 */
+#define CS35L35_SP_FMT_CTL3		0x0F	/* Serial Port Format CTL3 */
+#define CS35L35_MAG_COMP_CTL		0x13	/* Magnitude Comp CTL */
+#define CS35L35_AMP_INP_DRV_CTL		0x14	/* Amp Input Drive Ctl */
+#define CS35L35_AMP_DIG_VOL_CTL		0x15	/* Amplifier Dig Volume Ctl */
+#define CS35L35_AMP_DIG_VOL		0x16	/* Amplifier Dig Volume */
+#define CS35L35_ADV_DIG_VOL		0x17	/* Advisory Digital Volume */
+#define CS35L35_PROTECT_CTL		0x18	/* Amp Gain - Prot Ctl Param */
+#define CS35L35_AMP_GAIN_AUD_CTL	0x19	/* Amp Serial Port Gain Ctl */
+#define CS35L35_AMP_GAIN_PDM_CTL	0x1A	/* Amplifier Gain PDM Ctl */
+#define CS35L35_AMP_GAIN_ADV_CTL	0x1B	/* Amplifier Gain Ctl */
+#define CS35L35_GPI_CTL			0x1C	/* GPI Ctl */
+#define CS35L35_BST_CVTR_V_CTL		0x1D	/* Boost Conv Voltage Ctl */
+#define CS35L35_BST_PEAK_I		0x1E	/* Boost Conv Peak Current */
+#define CS35L35_BST_RAMP_CTL		0x20	/* Boost Conv Soft Ramp Ctl */
+#define CS35L35_BST_CONV_COEF_1		0x21	/* Boost Conv Coefficients 1 */
+#define CS35L35_BST_CONV_COEF_2		0x22	/* Boost Conv Coefficients 2 */
+#define CS35L35_BST_CONV_SLOPE_COMP	0x23	/* Boost Conv Slope Comp */
+#define CS35L35_BST_CONV_SW_FREQ	0x24	/* Boost Conv L BST SW Freq */
+#define CS35L35_CLASS_H_CTL		0x30	/* CLS H Control */
+#define CS35L35_CLASS_H_HEADRM_CTL	0x31	/* CLS H Headroom Ctl */
+#define CS35L35_CLASS_H_RELEASE_RATE	0x32	/* CLS H Release Rate */
+#define CS35L35_CLASS_H_FET_DRIVE_CTL	0x33	/* CLS H Weak FET Drive Ctl */
+#define CS35L35_CLASS_H_VP_CTL		0x34	/* CLS H VP Ctl */
+#define CS35L35_CLASS_H_STATUS		0x38	/* CLS H Status */
+#define CS35L35_VPBR_CTL		0x3A	/* VPBR Ctl */
+#define CS35L35_VPBR_VOL_CTL		0x3B	/* VPBR Volume Ctl */
+#define CS35L35_VPBR_TIMING_CTL		0x3C	/* VPBR Timing Ctl */
+#define CS35L35_VPBR_MODE_VOL_CTL	0x3D	/* VPBR Mode/Attack Vol Ctl */
+#define CS35L35_VPBR_ATTEN_STATUS	0x4B	/* VPBR Attenuation Status */
+#define CS35L35_SPKR_MON_CTL		0x4E	/* Speaker Monitoring Ctl */
+#define CS35L35_IMON_SCALE_CTL		0x51	/* IMON Scale Ctl */
+#define CS35L35_AUDIN_RXLOC_CTL		0x52	/* Audio Input RX Loc Ctl */
+#define CS35L35_ADVIN_RXLOC_CTL		0x53	/* Advisory Input RX Loc Ctl */
+#define CS35L35_VMON_TXLOC_CTL		0x54	/* VMON TX Loc Ctl */
+#define CS35L35_IMON_TXLOC_CTL		0x55	/* IMON TX Loc Ctl */
+#define CS35L35_VPMON_TXLOC_CTL		0x56	/* VPMON TX Loc Ctl */
+#define CS35L35_VBSTMON_TXLOC_CTL	0x57	/* VBSTMON TX Loc Ctl */
+#define CS35L35_VPBR_STATUS_TXLOC_CTL	0x58	/* VPBR Status TX Loc Ctl */
+#define CS35L35_ZERO_FILL_LOC_CTL	0x59	/* Zero Fill Loc Ctl */
+#define CS35L35_AUDIN_DEPTH_CTL		0x5A	/* Audio Input Depth Ctl */
+#define CS35L35_SPKMON_DEPTH_CTL	0x5B	/* SPK Mon Output Depth Ctl */
+#define CS35L35_SUPMON_DEPTH_CTL	0x5C	/* Supply Mon Out Depth Ctl */
+#define CS35L35_ZEROFILL_DEPTH_CTL	0x5D	/* Zero Fill Mon Output Ctl */
+#define CS35L35_MULT_DEV_SYNCH1		0x62	/* Multidevice Synch */
+#define CS35L35_MULT_DEV_SYNCH2		0x63	/* Multidevice Synch 2 */
+#define CS35L35_PROT_RELEASE_CTL	0x64	/* Protection Release Ctl */
+#define CS35L35_DIAG_MODE_REG_LOCK	0x68	/* Diagnostic Mode Reg Lock */
+#define CS35L35_DIAG_MODE_CTL_1		0x69	/* Diagnostic Mode Ctl 1 */
+#define CS35L35_DIAG_MODE_CTL_2		0x6A	/* Diagnostic Mode Ctl 2 */
+#define CS35L35_INT_MASK_1		0x70	/* Interrupt Mask 1 */
+#define CS35L35_INT_MASK_2		0x71	/* Interrupt Mask 2 */
+#define CS35L35_INT_MASK_3		0x72	/* Interrupt Mask 3 */
+#define CS35L35_INT_MASK_4		0x73	/* Interrupt Mask 4 */
+#define CS35L35_INT_STATUS_1		0x74	/* Interrupt Status 1 */
+#define CS35L35_INT_STATUS_2		0x75	/* Interrupt Status 2 */
+#define CS35L35_INT_STATUS_3		0x76	/* Interrupt Status 3 */
+#define CS35L35_INT_STATUS_4		0x77	/* Interrupt Status 4 */
+#define CS35L35_PLL_STATUS		0x78	/* PLL Status */
+#define CS35L35_OTP_TRIM_STATUS		0x7E	/* OTP Trim Status */
+
+#define CS35L35_MAX_REGISTER		0x7F
+
+/* CS35L35_PWRCTL1 */
+#define CS35L35_SFT_RST			0x80
+#define CS35L35_DISCHG_FLT		0x02
+#define CS35L35_PDN_ALL			0x01
+
+/* CS35L35_PWRCTL2 */
+#define CS35L35_PDN_VMON		0x80
+#define CS35L35_PDN_IMON		0x40
+#define CS35L35_PDN_CLASSH		0x20
+#define CS35L35_PDN_VPBR		0x10
+#define CS35L35_PDN_BST			0x04
+#define CS35L35_PDN_AMP			0x01
+
+/* CS35L35_PWRCTL3 */
+#define CS35L35_PDN_VBSTMON_OUT		0x10
+#define CS35L35_PDN_VMON_OUT		0x08
+
+#define CS35L35_AUDIN_DEPTH_MASK	0x03
+#define CS35L35_AUDIN_DEPTH_SHIFT	0
+#define CS35L35_ADVIN_DEPTH_MASK	0x12
+#define CS35L35_ADVIN_DEPTH_SHIFT	2
+#define CS35L35_SDIN_DEPTH_8		0x01
+#define CS35L35_SDIN_DEPTH_16		0x02
+#define CS35L35_SDIN_DEPTH_24		0x03
+
+#define CS35L35_SDOUT_DEPTH_8		0x01
+#define CS35L35_SDOUT_DEPTH_12		0x02
+#define CS35L35_SDOUT_DEPTH_16		0x03
+
+#define CS35L35_AUD_IN_LR_MASK		0x80
+#define CS35L35_AUD_IN_LR_SHIFT		7
+#define CS35L35_ADV_IN_LR_MASK		0x80
+#define CS35L35_ADV_IN_LR_SHIFT		7
+#define CS35L35_AUD_IN_LOC_MASK		0x0F
+#define CS35L35_AUD_IN_LOC_SHIFT	0
+#define CS35L35_ADV_IN_LOC_MASK		0x0F
+#define CS35L35_ADV_IN_LOC_SHIFT	0
+
+#define CS35L35_IMON_DEPTH_MASK		0x03
+#define CS35L35_IMON_DEPTH_SHIFT	0
+#define CS35L35_VMON_DEPTH_MASK		0x0C
+#define CS35L35_VMON_DEPTH_SHIFT	2
+#define CS35L35_VBSTMON_DEPTH_MASK	0x03
+#define CS35L35_VBSTMON_DEPTH_SHIFT	0
+#define CS35L35_VPMON_DEPTH_MASK	0x0C
+#define CS35L35_VPMON_DEPTH_SHIFT	2
+#define CS35L35_VPBRSTAT_DEPTH_MASK	0x18
+#define CS35L35_VPBRSTAT_DEPTH_SHIFT	4
+#define CS35L35_ZEROFILL_DEPTH_MASK	0x03
+#define CS35L35_ZEROFILL_DEPTH_SHIFT	0x00
+
+#define CS35L35_MON_TXLOC_MASK		0x3F
+#define CS35L35_MON_TXLOC_SHIFT		0
+#define CS35L35_MON_FRM_MASK		0x80
+#define CS35L35_MON_FRM_SHIFT		7
+
+#define CS35L35_MS_MASK			0x80
+#define CS35L35_MS_SHIFT		7
+#define CS35L35_SPMODE_MASK		0x40
+#define CS35L35_SP_DRV_MASK		0x10
+#define CS35L35_SP_DRV_SHIFT		4
+#define CS35L35_CLK_CTL2_MASK		0xFF
+#define CS35L35_PDM_MODE_MASK		0x40
+#define CS35L35_PDM_MODE_SHIFT		6
+#define CS35L35_CLK_SOURCE_MASK		0x03
+#define CS35L35_CLK_SOURCE_SHIFT	0
+#define CS35L35_CLK_SOURCE_MCLK		0
+#define CS35L35_CLK_SOURCE_SCLK		1
+#define CS35L35_CLK_SOURCE_PDM		2
+
+#define CS35L35_SP_SCLKS_MASK		0x0F
+#define CS35L35_SP_SCLKS_SHIFT		0x00
+#define CS35L35_SP_SCLKS_16FS		0x03
+#define CS35L35_SP_SCLKS_32FS		0x07
+#define CS35L35_SP_SCLKS_48FS		0x0B
+#define CS35L35_SP_SCLKS_64FS		0x0F
+#define CS35L35_SP_RATE_MASK		0xC0
+
+#define CS35L35_PDN_BST_MASK		0x06
+#define CS35L35_PDN_BST_FETON_SHIFT	1
+#define CS35L35_PDN_BST_FETOFF_SHIFT	2
+#define CS35L35_PWR2_PDN_MASK		0xE0
+#define CS35L35_PWR3_PDN_MASK		0x1E
+#define CS35L35_PDN_ALL_MASK		0x01
+#define CS35L35_DISCHG_FILT_MASK	0x02
+#define CS35L35_DISCHG_FILT_SHIFT	1
+#define CS35L35_MCLK_DIS_MASK		0x04
+#define CS35L35_MCLK_DIS_SHIFT		2
+
+#define CS35L35_BST_CTL_MASK		0x7F
+#define CS35L35_BST_CTL_SHIFT		0
+#define CS35L35_BST_IPK_MASK		0x1F
+#define CS35L35_BST_IPK_SHIFT		0
+#define CS35L35_AMP_MUTE_MASK		0x20
+#define CS35L35_AMP_MUTE_SHIFT		5
+#define CS35L35_AMP_GAIN_ZC_MASK	0x10
+#define CS35L35_AMP_GAIN_ZC_SHIFT	4
+
+/* Class H Algorithm Control */
+#define CS35L35_CH_STEREO_MASK		0x40
+#define CS35L35_CH_STEREO_SHIFT		6
+#define CS35L35_CH_BST_OVR_MASK		0x04
+#define CS35L35_CH_BST_OVR_SHIFT	2
+#define CS35L35_CH_BST_LIM_MASK		0x08
+#define CS35L35_CH_BST_LIM_SHIFT	3
+#define CS35L35_CH_MEM_DEPTH_MASK	0x01
+#define CS35L35_CH_MEM_DEPTH_SHIFT	0
+#define CS35L35_CH_HDRM_CTL_MASK	0x3F
+#define CS35L35_CH_HDRM_CTL_SHIFT	0
+#define CS35L35_CH_REL_RATE_MASK	0xFF
+#define CS35L35_CH_REL_RATE_SHIFT	0
+#define CS35L35_CH_WKFET_DIS_MASK	0x80
+#define CS35L35_CH_WKFET_DIS_SHIFT	7
+#define CS35L35_CH_WKFET_DEL_MASK	0x70
+#define CS35L35_CH_WKFET_DEL_SHIFT	4
+#define CS35L35_CH_WKFET_THLD_MASK	0x0F
+#define CS35L35_CH_WKFET_THLD_SHIFT	0
+#define CS35L35_CH_VP_AUTO_MASK		0x80
+#define CS35L35_CH_VP_AUTO_SHIFT	7
+#define CS35L35_CH_VP_RATE_MASK		0x60
+#define CS35L35_CH_VP_RATE_SHIFT	5
+#define CS35L35_CH_VP_MAN_MASK		0x1F
+#define CS35L35_CH_VP_MAN_SHIFT		0
+
+/* CS35L35_PROT_RELEASE_CTL */
+#define CS35L35_CAL_ERR_RLS		0x80
+#define CS35L35_SHORT_RLS		0x04
+#define CS35L35_OTW_RLS			0x02
+#define CS35L35_OTE_RLS			0x01
+
+/* INT Mask Registers */
+#define CS35L35_INT1_CRIT_MASK		0x38
+#define CS35L35_INT2_CRIT_MASK		0xEF
+#define CS35L35_INT3_CRIT_MASK		0xEE
+#define CS35L35_INT4_CRIT_MASK		0xFF
+
+/* PDN DONE Masks */
+#define CS35L35_M_PDN_DONE_SHIFT	4
+#define CS35L35_M_PDN_DONE_MASK		0x10
+
+/* CS35L35_INT_1 */
+#define CS35L35_CAL_ERR			0x80
+#define CS35L35_OTP_ERR			0x40
+#define CS35L35_LRCLK_ERR		0x20
+#define CS35L35_SPCLK_ERR		0x10
+#define CS35L35_MCLK_ERR		0x08
+#define CS35L35_AMP_SHORT		0x04
+#define CS35L35_OTW			0x02
+#define CS35L35_OTE			0x01
+
+/* CS35L35_INT_2 */
+#define CS35L35_PDN_DONE		0x10
+#define CS35L35_VPBR_ERR		0x02
+#define CS35L35_VPBR_CLR		0x01
+
+/* CS35L35_INT_3 */
+#define CS35L35_BST_HIGH		0x10
+#define CS35L35_BST_HIGH_FLAG		0x08
+#define CS35L35_BST_IPK_FLAG		0x04
+#define CS35L35_LBST_SHORT		0x01
+
+/* CS35L35_INT_4 */
+#define CS35L35_VMON_OVFL		0x08
+#define CS35L35_IMON_OVFL		0x04
+
+#define CS35L35_FORMATS (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE | \
+			SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+struct  cs35l35_private {
+	struct snd_soc_codec *codec;
+	struct cs35l35_platform_data pdata;
+	struct regmap *regmap;
+	struct regulator_bulk_data supplies[2];
+	int num_supplies;
+	int sysclk;
+	int sclk;
+	bool pdm_mode;
+	bool i2s_mode;
+	bool slave_mode;
+	/* GPIO for /RST */
+	struct gpio_desc *reset_gpio;
+	struct completion pdn_done;
+};
+
+static const char * const cs35l35_supplies[] = {
+	"VA",
+	"VP",
+};
+
+#endif
-- 
1.9.1

^ permalink raw reply related

* Re: [PATCH v6 3/8] PWM: add pwm-stm32 DT bindings
From: Rob Herring @ 2016-12-13 15:57 UTC (permalink / raw)
  To: Lee Jones
  Cc: Mark Rutland, devicetree@vger.kernel.org,
	linaro-kernel@lists.linaro.org, Lars-Peter Clausen,
	Alexandre Torgue, Linux PWM List, linux-iio@vger.kernel.org,
	Peter Meerwald, Arnaud POULIQUEN, linux-kernel@vger.kernel.org,
	Thierry Reding, linux-arm-kernel@lists.infradead.org,
	Benjamin Gaignard, Hartmut Knaack, Gerald Baeza, Fabrice Gasnier,
	Linus Walleij, Jonathan Cameron
In-Reply-To: <20161213111137.GW3625@dell.home>

On Tue, Dec 13, 2016 at 5:11 AM, Lee Jones <lee.jones@linaro.org> wrote:
> On Mon, 12 Dec 2016, Rob Herring wrote:
>
>> On Fri, Dec 09, 2016 at 03:15:14PM +0100, Benjamin Gaignard wrote:
>> > Define bindings for pwm-stm32
>> >
>> > version 6:
>> > - change st,breakinput parameter format to make it usuable on stm32f7 too.
>> >
>> > version 2:
>> > - use parameters instead of compatible of handle the hardware configuration
>> >
>> > Signed-off-by: Benjamin Gaignard <benjamin.gaignard@st.com>
>> > ---
>> >  .../devicetree/bindings/pwm/pwm-stm32.txt          | 33 ++++++++++++++++++++++
>> >  1 file changed, 33 insertions(+)
>> >  create mode 100644 Documentation/devicetree/bindings/pwm/pwm-stm32.txt
>> >
>> > diff --git a/Documentation/devicetree/bindings/pwm/pwm-stm32.txt b/Documentation/devicetree/bindings/pwm/pwm-stm32.txt
>> > new file mode 100644
>> > index 0000000..866f222
>> > --- /dev/null
>> > +++ b/Documentation/devicetree/bindings/pwm/pwm-stm32.txt
>> > @@ -0,0 +1,33 @@
>> > +STMicroelectronics STM32 Timers PWM bindings
>> > +
>> > +Must be a sub-node of an STM32 Timers device tree node.
>> > +See ../mfd/stm32-timers.txt for details about the parent node.
>> > +
>> > +Required parameters:
>> > +- compatible:              Must be "st,stm32-pwm".
>> > +- pinctrl-names:   Set to "default".
>> > +- pinctrl-0:               List of phandles pointing to pin configuration nodes for PWM module.
>> > +                   For Pinctrl properties see ../pinctrl/pinctrl-bindings.txt
>> > +
>> > +Optional parameters:
>> > +- st,breakinput:   Arrays of three u32 <index level filter> to describe break input configurations.
>> > +                   "index" indicates on which break input the configuration should be applied.
>> > +                   "level" gives the active level (0=low or 1=high) for this configuration.
>> > +                   "filter" gives the filtering value to be applied.
>> > +
>> > +Example:
>> > +   timers@40010000 {
>>
>> timer@...
>
> No, it should be timers.

Read the spec. "timer" is a generic node name. "timers" is not. How
many is not relevant.

> The 's' is intentional, since this parent (MFD) device houses 3
> different types of timers.  The "timer" node is a child of this one.

^ permalink raw reply

* Re: [PATCH 3/6] ARM: dts: sun8i: add a cpu0 label to cpu@0 node on A23/33
From: Maxime Ripard @ 2016-12-13 15:45 UTC (permalink / raw)
  To: Icenowy Zheng
  Cc: devicetree, Quentin Schulz, Michael Turquette, Stephen Boyd,
	Russell King, linux-kernel, Hans de Goede, Chen-Yu Tsai,
	linux-clk, linux-arm-kernel, Jorik Jonker
In-Reply-To: <20161213152252.53749-4-icenowy@aosc.xyz>


[-- Attachment #1.1: Type: text/plain, Size: 388 bytes --]

On Tue, Dec 13, 2016 at 11:22:49PM +0800, Icenowy Zheng wrote:
> A "cpu0" label is needed on cpu@0 for cpufreq-dt to work.
> 
> Add such a label, in order to prepare for cpufreq support of A23/33.
> 
> Signed-off-by: Icenowy Zheng <icenowy@aosc.xyz>

Applied, thanks!
Maxime

-- 
Maxime Ripard, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com

[-- Attachment #1.2: signature.asc --]
[-- Type: application/pgp-signature, Size: 801 bytes --]

[-- Attachment #2: Type: text/plain, Size: 176 bytes --]

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* Re: [PATCH 2/6] clk: sunxi-ng: set the parent rate when adjustin CPUX clock on A33
From: Maxime Ripard @ 2016-12-13 15:44 UTC (permalink / raw)
  To: Icenowy Zheng
  Cc: devicetree, Quentin Schulz, Michael Turquette, Stephen Boyd,
	Russell King, linux-kernel, Hans de Goede, Chen-Yu Tsai,
	linux-clk, linux-arm-kernel, Jorik Jonker
In-Reply-To: <20161213152252.53749-3-icenowy@aosc.xyz>


[-- Attachment #1.1: Type: text/plain, Size: 481 bytes --]

On Tue, Dec 13, 2016 at 11:22:48PM +0800, Icenowy Zheng wrote:
> The CPUX clock on A33, which is for the Cortex-A7 cores, is designed to
> be changeable by changing the rate of PLL_CPUX.
> 
> Add CLK_SET_RATE_PARENT flag to this clock.
> 
> Signed-off-by: Icenowy Zheng <icenowy@aosc.xyz>

Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com>

Thanks!
Maxime

-- 
Maxime Ripard, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com

[-- Attachment #1.2: signature.asc --]
[-- Type: application/pgp-signature, Size: 801 bytes --]

[-- Attachment #2: Type: text/plain, Size: 176 bytes --]

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* Re: [PATCH 1/6] clk: sunxi-ng: fix PLL_CPUX adjusting on A33
From: Maxime Ripard @ 2016-12-13 15:44 UTC (permalink / raw)
  To: Icenowy Zheng
  Cc: devicetree, Quentin Schulz, Michael Turquette, Stephen Boyd,
	Russell King, linux-kernel, Hans de Goede, Chen-Yu Tsai,
	linux-clk, linux-arm-kernel, Jorik Jonker
In-Reply-To: <20161213152252.53749-2-icenowy@aosc.xyz>


[-- Attachment #1.1: Type: text/plain, Size: 462 bytes --]

On Tue, Dec 13, 2016 at 11:22:47PM +0800, Icenowy Zheng wrote:
> When adjusting PLL_CPUX on A33, the PLL is temporarily driven too high,
> and the system hangs.
> 
> Add a notifier to avoid this situation by temporarily switching to a
> known stable 24 MHz oscillator.
> 
> Signed-off-by: Icenowy Zheng <icenowy@aosc.xyz>

Applied, thanks!
Maxime

-- 
Maxime Ripard, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com

[-- Attachment #1.2: signature.asc --]
[-- Type: application/pgp-signature, Size: 801 bytes --]

[-- Attachment #2: Type: text/plain, Size: 176 bytes --]

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* [PATCH 6/6] ARM: dts: sun8i: raise the max voltage of DCDC2 in sun8i reference tablets
From: Icenowy Zheng @ 2016-12-13 15:22 UTC (permalink / raw)
  To: Russell King, Maxime Ripard, Chen-Yu Tsai, Michael Turquette,
	Stephen Boyd, Jorik Jonker, Hans de Goede, Quentin Schulz
  Cc: devicetree, Icenowy Zheng, linux-kernel, linux-arm-kernel,
	linux-clk
In-Reply-To: <20161213152252.53749-1-icenowy@aosc.xyz>

The "extremity_freq" frequency described in the original FEX files uses
a voltage of 1.46v, which is beyond the current maximum voltage value of
DCDC2 (Cortex-A7 supply) in the sun8i reference tablet DTSI file.

Raise the maximum value to 1.46v.

Signed-off-by: Icenowy Zheng <icenowy@aosc.xyz>
---
 arch/arm/boot/dts/sun8i-reference-design-tablet.dtsi | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/arch/arm/boot/dts/sun8i-reference-design-tablet.dtsi b/arch/arm/boot/dts/sun8i-reference-design-tablet.dtsi
index 7ac8bb4bc95a..325ca5bd67a5 100644
--- a/arch/arm/boot/dts/sun8i-reference-design-tablet.dtsi
+++ b/arch/arm/boot/dts/sun8i-reference-design-tablet.dtsi
@@ -180,7 +180,7 @@
 &reg_dcdc2 {
 	regulator-always-on;
 	regulator-min-microvolt = <900000>;
-	regulator-max-microvolt = <1400000>;
+	regulator-max-microvolt = <1460000>;
 	regulator-name = "vdd-sys";
 };
 
-- 
2.11.0

^ permalink raw reply related

* [PATCH 5/6] ARM: dts: sun8i: set cpu-supply in reference tablet DTSI
From: Icenowy Zheng @ 2016-12-13 15:22 UTC (permalink / raw)
  To: Russell King, Maxime Ripard, Chen-Yu Tsai, Michael Turquette,
	Stephen Boyd, Jorik Jonker, Hans de Goede, Quentin Schulz
  Cc: devicetree, Icenowy Zheng, linux-kernel, linux-arm-kernel,
	linux-clk
In-Reply-To: <20161213152252.53749-1-icenowy@aosc.xyz>

All reference design A33 tablets uses DCDC2 of AXP223 as the power
supply of the Cortex-A7 cores.

Set the cpu-supply in the DTSI of sun8i reference tablets.

Signed-off-by: Icenowy Zheng <icenowy@aosc.xyz>
---
 arch/arm/boot/dts/sun8i-reference-design-tablet.dtsi | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/arch/arm/boot/dts/sun8i-reference-design-tablet.dtsi b/arch/arm/boot/dts/sun8i-reference-design-tablet.dtsi
index 08cd00143635..7ac8bb4bc95a 100644
--- a/arch/arm/boot/dts/sun8i-reference-design-tablet.dtsi
+++ b/arch/arm/boot/dts/sun8i-reference-design-tablet.dtsi
@@ -213,6 +213,10 @@
 	regulator-name = "vcc-rtc";
 };
 
+&cpu0 {
+	cpu-supply = <&reg_dcdc2>;
+};
+
 &r_uart {
 	pinctrl-names = "default";
 	pinctrl-0 = <&r_uart_pins_a>;
-- 
2.11.0

^ permalink raw reply related

* [PATCH 4/6] ARM: dts: sun8i: add opp-v2 table for A33
From: Icenowy Zheng @ 2016-12-13 15:22 UTC (permalink / raw)
  To: Russell King, Maxime Ripard, Chen-Yu Tsai, Michael Turquette,
	Stephen Boyd, Jorik Jonker, Hans de Goede, Quentin Schulz
  Cc: devicetree, Icenowy Zheng, linux-kernel, linux-arm-kernel,
	linux-clk
In-Reply-To: <20161213152252.53749-1-icenowy@aosc.xyz>

An operating point table is needed for the cpu frequency adjusting to
work.

The operating point table is converted from the common value in
extracted script.fex from many A33 board/tablets.

1.344GHz is set as a turbo-mode operating point, as it's described as
"extremity_freq" in the FEX file. (the "max_freq" is 1.2GHz)

Signed-off-by: Icenowy Zheng <icenowy@aosc.xyz>
---
 arch/arm/boot/dts/sun8i-a33.dtsi | 38 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 38 insertions(+)

diff --git a/arch/arm/boot/dts/sun8i-a33.dtsi b/arch/arm/boot/dts/sun8i-a33.dtsi
index 504996cbee29..035c058324b8 100644
--- a/arch/arm/boot/dts/sun8i-a33.dtsi
+++ b/arch/arm/boot/dts/sun8i-a33.dtsi
@@ -46,7 +46,45 @@
 #include <dt-bindings/dma/sun4i-a10.h>
 
 / {
+	cpu0_opp_table: opp_table0 {
+		compatible = "operating-points-v2";
+		opp-shared;
+
+		opp@648000000 {
+			opp-hz = /bits/ 64 <648000000>;
+			opp-microvolt = <1040000>;
+			clock-latency-ns = <244144>; /* 8 32k periods */
+		};
+		opp@816000000 {
+			opp-hz = /bits/ 64 <816000000>;
+			opp-microvolt = <1100000>;
+			clock-latency-ns = <244144>; /* 8 32k periods */
+		};
+		opp@1008000000 {
+			opp-hz = /bits/ 64 <1008000000>;
+			opp-microvolt = <1200000>;
+			clock-latency-ns = <244144>; /* 8 32k periods */
+		};
+		opp@1200000000 {
+			opp-hz = /bits/ 64 <1200000000>;
+			opp-microvolt = <1320000>;
+			clock-latency-ns = <244144>; /* 8 32k periods */
+		};
+		opp@1344000000 {
+			opp-hz = /bits/ 64 <1344000000>;
+			opp-microvolt = <1460000>;
+			clock-latency-ns = <244144>; /* 8 32k periods */
+			turbo-mode;
+		};
+	};
+
 	cpus {
+		cpu0: cpu@0 {
+			clocks = <&ccu CLK_CPUX>;
+			clock-names = "cpu";
+			operating-points-v2 = <&cpu0_opp_table>;
+		};
+
 		cpu@2 {
 			compatible = "arm,cortex-a7";
 			device_type = "cpu";
-- 
2.11.0

^ permalink raw reply related

* [PATCH 3/6] ARM: dts: sun8i: add a cpu0 label to cpu@0 node on A23/33
From: Icenowy Zheng @ 2016-12-13 15:22 UTC (permalink / raw)
  To: Russell King, Maxime Ripard, Chen-Yu Tsai, Michael Turquette,
	Stephen Boyd, Jorik Jonker, Hans de Goede, Quentin Schulz
  Cc: devicetree, Icenowy Zheng, linux-kernel, linux-arm-kernel,
	linux-clk
In-Reply-To: <20161213152252.53749-1-icenowy@aosc.xyz>

A "cpu0" label is needed on cpu@0 for cpufreq-dt to work.

Add such a label, in order to prepare for cpufreq support of A23/33.

Signed-off-by: Icenowy Zheng <icenowy@aosc.xyz>
---
 arch/arm/boot/dts/sun8i-a23-a33.dtsi | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/arch/arm/boot/dts/sun8i-a23-a33.dtsi b/arch/arm/boot/dts/sun8i-a23-a33.dtsi
index 817747f41288..5931cc4d1567 100644
--- a/arch/arm/boot/dts/sun8i-a23-a33.dtsi
+++ b/arch/arm/boot/dts/sun8i-a23-a33.dtsi
@@ -84,7 +84,7 @@
 		#address-cells = <1>;
 		#size-cells = <0>;
 
-		cpu@0 {
+		cpu0: cpu@0 {
 			compatible = "arm,cortex-a7";
 			device_type = "cpu";
 			reg = <0>;
-- 
2.11.0

^ permalink raw reply related

* [PATCH 2/6] clk: sunxi-ng: set the parent rate when adjustin CPUX clock on A33
From: Icenowy Zheng @ 2016-12-13 15:22 UTC (permalink / raw)
  To: Russell King, Maxime Ripard, Chen-Yu Tsai, Michael Turquette,
	Stephen Boyd, Jorik Jonker, Hans de Goede, Quentin Schulz
  Cc: devicetree, Icenowy Zheng, linux-kernel, linux-arm-kernel,
	linux-clk
In-Reply-To: <20161213152252.53749-1-icenowy@aosc.xyz>

The CPUX clock on A33, which is for the Cortex-A7 cores, is designed to
be changeable by changing the rate of PLL_CPUX.

Add CLK_SET_RATE_PARENT flag to this clock.

Signed-off-by: Icenowy Zheng <icenowy@aosc.xyz>
---
 drivers/clk/sunxi-ng/ccu-sun8i-a33.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/clk/sunxi-ng/ccu-sun8i-a33.c b/drivers/clk/sunxi-ng/ccu-sun8i-a33.c
index 0f3e7d2dc19a..0d513d2674cb 100644
--- a/drivers/clk/sunxi-ng/ccu-sun8i-a33.c
+++ b/drivers/clk/sunxi-ng/ccu-sun8i-a33.c
@@ -170,7 +170,7 @@ static SUNXI_CCU_N_WITH_GATE_LOCK(pll_ddr1_clk, "pll-ddr1",
 static const char * const cpux_parents[] = { "osc32k", "osc24M",
 					     "pll-cpux" , "pll-cpux" };
 static SUNXI_CCU_MUX(cpux_clk, "cpux", cpux_parents,
-		     0x050, 16, 2, CLK_IS_CRITICAL);
+		     0x050, 16, 2, CLK_IS_CRITICAL | CLK_SET_RATE_PARENT);
 
 static SUNXI_CCU_M(axi_clk, "axi", "cpux", 0x050, 0, 2, 0);
 
-- 
2.11.0

^ permalink raw reply related

* [PATCH 1/6] clk: sunxi-ng: fix PLL_CPUX adjusting on A33
From: Icenowy Zheng @ 2016-12-13 15:22 UTC (permalink / raw)
  To: Russell King, Maxime Ripard, Chen-Yu Tsai, Michael Turquette,
	Stephen Boyd, Jorik Jonker, Hans de Goede, Quentin Schulz
  Cc: devicetree, Icenowy Zheng, linux-kernel, linux-arm-kernel,
	linux-clk
In-Reply-To: <20161213152252.53749-1-icenowy@aosc.xyz>

When adjusting PLL_CPUX on A33, the PLL is temporarily driven too high,
and the system hangs.

Add a notifier to avoid this situation by temporarily switching to a
known stable 24 MHz oscillator.

Signed-off-by: Icenowy Zheng <icenowy@aosc.xyz>
---
 drivers/clk/sunxi-ng/ccu-sun8i-a33.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/drivers/clk/sunxi-ng/ccu-sun8i-a33.c b/drivers/clk/sunxi-ng/ccu-sun8i-a33.c
index 3cd4190ccd59..0f3e7d2dc19a 100644
--- a/drivers/clk/sunxi-ng/ccu-sun8i-a33.c
+++ b/drivers/clk/sunxi-ng/ccu-sun8i-a33.c
@@ -752,6 +752,13 @@ static const struct sunxi_ccu_desc sun8i_a33_ccu_desc = {
 	.num_resets	= ARRAY_SIZE(sun8i_a33_ccu_resets),
 };
 
+static struct ccu_mux_nb sun8i_a33_cpu_nb = {
+	.common		= &cpux_clk.common,
+	.cm		= &cpux_clk.mux,
+	.delay_us	= 1, /* > 8 clock cycles at 24 MHz */
+	.bypass_index	= 1, /* index of 24 MHz oscillator */
+};
+
 static void __init sun8i_a33_ccu_setup(struct device_node *node)
 {
 	void __iomem *reg;
@@ -775,6 +782,9 @@ static void __init sun8i_a33_ccu_setup(struct device_node *node)
 	writel(val, reg + SUN8I_A33_PLL_MIPI_REG);
 
 	sunxi_ccu_probe(node, reg, &sun8i_a33_ccu_desc);
+
+	ccu_mux_notifier_register(pll_cpux_clk.common.hw.clk,
+				  &sun8i_a33_cpu_nb);
 }
 CLK_OF_DECLARE(sun8i_a33_ccu, "allwinner,sun8i-a33-ccu",
 	       sun8i_a33_ccu_setup);
-- 
2.11.0

^ permalink raw reply related

* [PATCH 0/6] Allwinner A33 CPU frequency scaling support
From: Icenowy Zheng @ 2016-12-13 15:22 UTC (permalink / raw)
  To: Russell King, Maxime Ripard, Chen-Yu Tsai, Michael Turquette,
	Stephen Boyd, Jorik Jonker, Hans de Goede, Quentin Schulz
  Cc: devicetree, linux-kernel, linux-arm-kernel, linux-clk

This series of patch adds frequency scaling support to Allwinner A33 SoC.

The first two patches fixes some bugs in the A33 CCU code.

The patch 3 and 4 is for enabling the cpufreq-dt driver to work.

The patch 5 is for enabling the voltage adjusting on reference design tablets.

The patch 6 is for enabling the "turbo-mode" of A33. (According to the
"extremity_freq" property in the FEX file. When I tested it with 3.4 BSP, it
really performs as a turbo mode.)

If there's any doubt of safety, the patch 6 can be ignored.

If there's any problem in patch 3, 4 and 5, they can also be temporarily
ignored, but finally we need them ;-)

Although there's now currently no thermal support for A33, many A33 devices
are tablets with battery, and it will be valuable to save some power energy,
so cpufreq support is also useful.

P.S.

Chen-Yu,

Do you want to test the CCU fix and the operating point table on A23?

Regards,
Icenowy

^ permalink raw reply

* Re: [PATCH resend] ARM: dts: sun8i: Support DTB build for NanoPi M1
From: Maxime Ripard @ 2016-12-13 15:15 UTC (permalink / raw)
  To: Milo Kim; +Cc: Chen-Yu Tsai, devicetree, linux-arm-kernel, linux-kernel
In-Reply-To: <20161212231815.6114-1-woogyom.kim@gmail.com>

[-- Attachment #1: Type: text/plain, Size: 399 bytes --]

On Tue, Dec 13, 2016 at 08:18:15AM +0900, Milo Kim wrote:
> The commit 10efbf5f1633 ("ARM: dts: sun8i: Add dts file for NanoPi M1 SBC")
> introduced NanoPi M1 board but it's missing in Allwinner H3 DTB build.
> 
> Signed-off-by: Milo Kim <woogyom.kim@gmail.com>

Applied, thanks!
Maxime

-- 
Maxime Ripard, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 801 bytes --]

^ permalink raw reply

* Re: [PATCH v4 5/5] i2c: designware: Cleaning comments and formatation
From: Jarkko Nikula @ 2016-12-13 15:08 UTC (permalink / raw)
  To: Luis Oliveira, wsa, robh+dt, mark.rutland, andriy.shevchenko,
	mika.westerberg, linux-i2c, devicetree, linux-kernel
  Cc: Ramiro.Oliveira, Joao.Pinto, CARLOS.PALMINHA
In-Reply-To: <02856fdb6ce3230a4ac1ba0958938ad51e763205.1481131072.git.lolivei@synopsys.com>

On 12/07/2016 07:55 PM, Luis Oliveira wrote:
> - Missspelling, comment formatation and fix a string of
>   the existing code
>
> Signed-off-by: Luis Oliveira <lolivei@synopsys.com>
> ---
> Changes V3->V4: (Andy Shevchenko)
> - created a commit message
>
>  drivers/i2c/busses/i2c-designware-common.c |  2 +-
>  drivers/i2c/busses/i2c-designware-slave.c  | 10 ++++++----
>  2 files changed, 7 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/i2c/busses/i2c-designware-common.c b/drivers/i2c/busses/i2c-designware-common.c
> index 41b38d8b8732..838ef662d2c8 100644
> --- a/drivers/i2c/busses/i2c-designware-common.c
> +++ b/drivers/i2c/busses/i2c-designware-common.c
> @@ -42,7 +42,7 @@ static char *abort_sources[] = {
>  	[ABRT_TXDATA_NOACK] =
>  		"data not acknowledged",
>  	[ABRT_GCALL_NOACK] =
> -		"no acknowledgement for a general call",
> +		"no acknowledgment for a general call",

I'm not a native speaker but are both acknowledgement and acknowledgment 
ok? I.e. is there need to change?

> diff --git a/drivers/i2c/busses/i2c-designware-slave.c b/drivers/i2c/busses/i2c-designware-slave.c
> index 1c7f82bb2513..442bc5ce6d47 100644
> --- a/drivers/i2c/busses/i2c-designware-slave.c
> +++ b/drivers/i2c/busses/i2c-designware-slave.c
> @@ -70,8 +70,8 @@ int i2c_dw_init_slave(struct dw_i2c_dev *dev)
>  		/* Configure register access mode 16bit */
>  		dev->accessor_flags |= ACCESS_16BIT;
>  	} else if (reg != DW_IC_COMP_TYPE_VALUE) {
> -		dev_err(dev->dev, "Unknown Synopsys component type: "
> -			"0x%08x\n", reg);
> +		dev_err(dev->dev,
> +		 "Unknown Synopsys component type: 0x%08x\n", reg);
>  		i2c_dw_release_lock(dev);
>  		return -ENODEV;
>  	}
> @@ -181,8 +181,10 @@ int i2c_dw_reg_slave(struct i2c_client *slave)
>  		return -EBUSY;
>  	if (slave->flags & I2C_CLIENT_TEN)
>  		return -EAFNOSUPPORT;
> -		/* set slave address in the IC_SAR register,
> -		* the address to which the DW_apb_i2c responds */
> +		/*
> +		 * set slave address in the IC_SAR register,
> +		 * the address to which the DW_apb_i2c responds
> +		 */

These two should be done already in the patch 4/5 since it introduced 
the lines that you are changing here.

-- 
Jarkko

^ permalink raw reply

* Re: [PATCH v2 3/9] ARM: dts: dra72: Add separate dtsi for tps65917
From: Roger Quadros @ 2016-12-13 14:55 UTC (permalink / raw)
  To: Lokesh Vutla, Tony Lindgren, Linux OMAP Mailing List,
	Tomi Valkeinen, KISHON VIJAY ABRAHAM
  Cc: Tero Kristo, Sekhar Nori, Nishanth Menon,
	Device Tree Mailing List, Rob Herring, Linux ARM Mailing List,
	Carlos Hernandez
In-Reply-To: <91b4075f-2dfb-0d17-592d-f99b91ace590-l0cyMroinI0@public.gmane.org>

Lokesh,

On 13/12/16 15:08, Lokesh Vutla wrote:
> 
> 
> On Tuesday 13 December 2016 06:10 PM, Roger Quadros wrote:
>> +Tomi, Kishon, Carlos
>>  
>> Hi,
>>
>> On 21/10/16 13:38, Lokesh Vutla wrote:
>>> dra72-evm-common.dtsi consolidates dra72-evm.dts and dra72-evm-revc.dts
>>> which also include tps65917 pmic support as both the evms uses the same
>>> pmic. But, dra71-evm has mostly similar features with a different pmic.
>>> In order to exploit dra72-evm-common.dtsi, creating a separate dtsi
>>> for tps65915 support and including it in respective board files.
>>>
>>> Signed-off-by: Lokesh Vutla <lokeshvutla-l0cyMroinI0@public.gmane.org>
>>> ---
>>>  arch/arm/boot/dts/dra72-evm-common.dtsi   | 128 ----------------------------
>>>  arch/arm/boot/dts/dra72-evm-revc.dts      |  21 +++--
>>>  arch/arm/boot/dts/dra72-evm-tps65917.dtsi | 134 ++++++++++++++++++++++++++++++
>>>  arch/arm/boot/dts/dra72-evm.dts           |  14 ++--
>>>  4 files changed, 154 insertions(+), 143 deletions(-)
>>>  create mode 100644 arch/arm/boot/dts/dra72-evm-tps65917.dtsi
>>>
>>
>> This patch breaks USB XHCI and boot on dra72-evm (both revC and non revC)
>>
>> I'll explain why below.
>>
>> [   13.625167] Unhandled fault: imprecise external abort (0x1406) at 0x00000000
>> [   13.632557] pgd = ede10000
>> [   13.635390] [00000000] *pgd=00000000
>> [   13.639145] Internal error: : 1406 [#1] SMP ARM
>> [   13.643893] Modules linked in: xhci_plat_hcd(+) xhci_hcd usbcore omapfb dwc3 cfbfillrect snd_soc_davinci_mcasp cfbimgblt cfbcopyarea udc_core connector_hdmi encoder_tpd12s015 snd_soc_edma m25p80 snd_soc_simpe
>> [   13.695557] CPU: 0 PID: 440 Comm: modprobe Not tainted 4.9.0-rc1 #1050
>> [   13.702399] Hardware name: Generic DRA72X (Flattened Device Tree)
>> [   13.708786] task: edb5c040 task.stack: edd10000
>> [   13.713540] PC is at _raw_spin_unlock_irqrestore+0x0/0x44
>> [   13.719219] LR is at xhci_hub_control+0xc2c/0x15e0 [xhci_hcd]
>> [   13.725242] pc : [<c07df718>]    lr : [<bf486300>]    psr: a0000093
>> [   13.725242] sp : edd118c0  ip : c0e306b4  fp : 00000000
>> [   13.737278] r10: 00000000  r9 : 60000013  r8 : edf28218
>> [   13.742753] r7 : edf28000  r6 : 00000000  r5 : 00000000  r4 : edf2a000
>> [   13.749593] r3 : 00000000  r2 : 00000000  r1 : 60000013  r0 : edf28218
> 
> Hmm.. Thanks for catching it. I remember it was booting when I tested,
> not sure how I missed this :(. usb2_phy1 & 2, mmc and dss supplies needs
> to be added in dra72-evm-tps65917.dtsi file.

OK.
> 
> Tony,
> 	Do you want me to resend this patch or fixup patch on top of this?

In case you send a new patch. please add

Reported-by: Carlos Hernandez <ceh-l0cyMroinI0@public.gmane.org>

Thanks.

cheers,
-roger
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply

* [PATCH v6 2/2] Add support for OV5647 sensor.
From: Ramiro Oliveira @ 2016-12-13 14:32 UTC (permalink / raw)
  To: mchehab, linux-kernel, linux-media, robh+dt, devicetree
  Cc: davem, gregkh, geert+renesas, akpm, linux, hverkuil, dheitmueller,
	slongerbeam, lars, robert.jarzmik, pavel, pali.rohar,
	sakari.ailus, mark.rutland, Ramiro.Oliveira, CARLOS.PALMINHA
In-Reply-To: <cover.1481639091.git.roliveir@synopsys.com>

Modes supported:
 - 640x480 RAW 8

Signed-off-by: Ramiro Oliveira <roliveir@synopsys.com>
---
 MAINTAINERS                |   7 +
 drivers/media/i2c/Kconfig  |  12 +
 drivers/media/i2c/Makefile |   1 +
 drivers/media/i2c/ov5647.c | 718 +++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 738 insertions(+)
 create mode 100644 drivers/media/i2c/ov5647.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 52cc077..72e828a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8923,6 +8923,13 @@ M:	Harald Welte <laforge@gnumonks.org>
 S:	Maintained
 F:	drivers/char/pcmcia/cm4040_cs.*
 
+OMNIVISION OV5647 SENSOR DRIVER
+M:	Ramiro Oliveira <roliveir@synopsys.com>
+L:	linux-media@vger.kernel.org
+T:	git git://linuxtv.org/media_tree.git
+S:	Maintained
+F:	drivers/media/i2c/ov5647.c
+
 OMNIVISION OV7670 SENSOR DRIVER
 M:	Jonathan Corbet <corbet@lwn.net>
 L:	linux-media@vger.kernel.org
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index b31fa6f..c1b78e5 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -531,6 +531,18 @@ config VIDEO_OV2659
 	  To compile this driver as a module, choose M here: the
 	  module will be called ov2659.
 
+config VIDEO_OV5647
+	tristate "OmniVision OV5647 sensor support"
+	depends on OF
+	depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
+	depends on MEDIA_CAMERA_SUPPORT
+	---help---
+	  This is a Video4Linux2 sensor-level driver for the OmniVision
+	  OV5647 camera.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called ov5647.
+
 config VIDEO_OV7640
 	tristate "OmniVision OV7640 sensor support"
 	depends on I2C && VIDEO_V4L2
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index 92773b2..0d9014c 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -82,3 +82,4 @@ obj-$(CONFIG_VIDEO_IR_I2C)  += ir-kbd-i2c.o
 obj-$(CONFIG_VIDEO_ML86V7667)	+= ml86v7667.o
 obj-$(CONFIG_VIDEO_OV2659)	+= ov2659.o
 obj-$(CONFIG_VIDEO_TC358743)	+= tc358743.o
+obj-$(CONFIG_VIDEO_OV5647)	+= ov5647.o
diff --git a/drivers/media/i2c/ov5647.c b/drivers/media/i2c/ov5647.c
new file mode 100644
index 0000000..c282865
--- /dev/null
+++ b/drivers/media/i2c/ov5647.c
@@ -0,0 +1,718 @@
+/*
+ * A V4L2 driver for OmniVision OV5647 cameras.
+ *
+ * Based on Samsung S5K6AAFX SXGA 1/6" 1.3M CMOS Image Sensor driver
+ * Copyright (C) 2011 Sylwester Nawrocki <s.nawrocki@samsung.com>
+ *
+ * Based on Omnivision OV7670 Camera Driver
+ * Copyright (C) 2006-7 Jonathan Corbet <corbet@lwn.net>
+ *
+ * Copyright (C) 2016, Synopsys, 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 version 2.
+ *
+ * This program is distributed .as is. WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; 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/delay.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-image-sizes.h>
+#include <media/v4l2-mediabus.h>
+#include <media/v4l2-of.h>
+
+#define SENSOR_NAME "ov5647"
+
+#define OV5647_SW_RESET		0x1003
+#define OV5647_REG_CHIPID_H	0x300A
+#define OV5647_REG_CHIPID_L	0x300B
+
+#define REG_TERM 0xfffe
+#define VAL_TERM 0xfe
+#define REG_DLY  0xffff
+
+#define OV5647_ROW_START		0x01
+#define OV5647_ROW_START_MIN		0
+#define OV5647_ROW_START_MAX		2004
+#define OV5647_ROW_START_DEF		54
+
+#define OV5647_COLUMN_START		0x02
+#define OV5647_COLUMN_START_MIN		0
+#define OV5647_COLUMN_START_MAX		2750
+#define OV5647_COLUMN_START_DEF		16
+
+#define OV5647_WINDOW_HEIGHT		0x03
+#define OV5647_WINDOW_HEIGHT_MIN	2
+#define OV5647_WINDOW_HEIGHT_MAX	2006
+#define OV5647_WINDOW_HEIGHT_DEF	1944
+
+#define OV5647_WINDOW_WIDTH		0x04
+#define OV5647_WINDOW_WIDTH_MIN		2
+#define OV5647_WINDOW_WIDTH_MAX		2752
+#define OV5647_WINDOW_WIDTH_DEF		2592
+
+struct regval_list {
+	u16 addr;
+	u8 data;
+};
+
+struct cfg_array {
+	struct regval_list *regs;
+	int size;
+};
+
+struct ov5647 {
+	struct device			*dev;
+	struct v4l2_subdev		sd;
+	struct media_pad		pad;
+	struct mutex			lock;
+	struct v4l2_mbus_framefmt	format;
+	unsigned int			width;
+	unsigned int			height;
+	int				power_count;
+	struct clk			*xclk;
+	/* External clock frequency currently supported is 30MHz */
+	u32				xclk_freq;
+};
+
+static inline struct ov5647 *to_state(struct v4l2_subdev *sd)
+{
+	return container_of(sd, struct ov5647, sd);
+}
+
+static struct regval_list sensor_oe_disable_regs[] = {
+	{0x3000, 0x00},
+	{0x3001, 0x00},
+	{0x3002, 0x00},
+};
+
+static struct regval_list sensor_oe_enable_regs[] = {
+	{0x3000, 0x0f},
+	{0x3001, 0xff},
+	{0x3002, 0xe4},
+};
+
+static struct regval_list ov5647_640x480[] = {
+	{0x0100, 0x00},
+	{0x0103, 0x01},
+	{0x3034, 0x08},
+	{0x3035, 0x21},
+	{0x3036, 0x46},
+	{0x303c, 0x11},
+	{0x3106, 0xf5},
+	{0x3821, 0x07},
+	{0x3820, 0x41},
+	{0x3827, 0xec},
+	{0x370c, 0x0f},
+	{0x3612, 0x59},
+	{0x3618, 0x00},
+	{0x5000, 0x06},
+	{0x5001, 0x01},
+	{0x5002, 0x41},
+	{0x5003, 0x08},
+	{0x5a00, 0x08},
+	{0x3000, 0x00},
+	{0x3001, 0x00},
+	{0x3002, 0x00},
+	{0x3016, 0x08},
+	{0x3017, 0xe0},
+	{0x3018, 0x44},
+	{0x301c, 0xf8},
+	{0x301d, 0xf0},
+	{0x3a18, 0x00},
+	{0x3a19, 0xf8},
+	{0x3c01, 0x80},
+	{0x3b07, 0x0c},
+	{0x380c, 0x07},
+	{0x380d, 0x68},
+	{0x380e, 0x03},
+	{0x380f, 0xd8},
+	{0x3814, 0x31},
+	{0x3815, 0x31},
+	{0x3708, 0x64},
+	{0x3709, 0x52},
+	{0x3808, 0x02},
+	{0x3809, 0x80},
+	{0x380a, 0x01},
+	{0x380b, 0xE0},
+	{0x3801, 0x00},
+	{0x3802, 0x00},
+	{0x3803, 0x00},
+	{0x3804, 0x0a},
+	{0x3805, 0x3f},
+	{0x3806, 0x07},
+	{0x3807, 0xa1},
+	{0x3811, 0x08},
+	{0x3813, 0x02},
+	{0x3630, 0x2e},
+	{0x3632, 0xe2},
+	{0x3633, 0x23},
+	{0x3634, 0x44},
+	{0x3636, 0x06},
+	{0x3620, 0x64},
+	{0x3621, 0xe0},
+	{0x3600, 0x37},
+	{0x3704, 0xa0},
+	{0x3703, 0x5a},
+	{0x3715, 0x78},
+	{0x3717, 0x01},
+	{0x3731, 0x02},
+	{0x370b, 0x60},
+	{0x3705, 0x1a},
+	{0x3f05, 0x02},
+	{0x3f06, 0x10},
+	{0x3f01, 0x0a},
+	{0x3a08, 0x01},
+	{0x3a09, 0x27},
+	{0x3a0a, 0x00},
+	{0x3a0b, 0xf6},
+	{0x3a0d, 0x04},
+	{0x3a0e, 0x03},
+	{0x3a0f, 0x58},
+	{0x3a10, 0x50},
+	{0x3a1b, 0x58},
+	{0x3a1e, 0x50},
+	{0x3a11, 0x60},
+	{0x3a1f, 0x28},
+	{0x4001, 0x02},
+	{0x4004, 0x02},
+	{0x4000, 0x09},
+	{0x4837, 0x24},
+	{0x4050, 0x6e},
+	{0x4051, 0x8f},
+	{0x0100, 0x01},
+};
+
+/**
+ * @short I2C Write operation
+ * @param[in] i2c_client I2C client
+ * @param[in] reg register address
+ * @param[in] val value to write
+ * @return Error code
+ */
+static int ov5647_write(struct v4l2_subdev *sd, u16 reg, u8 val)
+{
+	int ret;
+	unsigned char data[3] = { reg >> 8, reg & 0xff, val};
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+
+	ret = i2c_master_send(client, data, 3);
+	if (ret != 3) {
+		dev_dbg(&client->dev, "%s: i2c write error, reg: %x\n",
+				__func__, reg);
+		return ret < 0 ? ret : -EIO;
+	}
+	return 0;
+}
+
+/**
+ * @short I2C Read operation
+ * @param[in] i2c_client I2C client
+ * @param[in] reg register address
+ * @param[out] val value read
+ * @return Error code
+ */
+static int ov5647_read(struct v4l2_subdev *sd, u16 reg, u8 *val)
+{
+	int ret;
+	unsigned char data_w[2] = { reg >> 8, reg & 0xff };
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+
+
+	ret = i2c_master_send(client, data_w, 2);
+
+	if (ret < 2) {
+		dev_dbg(&client->dev, "%s: i2c read error, reg: %x\n",
+			__func__, reg);
+		return ret < 0 ? ret : -EIO;
+	}
+
+
+	ret = i2c_master_recv(client, val, 1);
+
+	if (ret < 1) {
+		dev_dbg(&client->dev, "%s: i2c read error, reg: %x\n",
+				__func__, reg);
+		return ret < 0 ? ret : -EIO;
+	}
+	return 0;
+}
+
+static int ov5647_write_array(struct v4l2_subdev *sd,
+				struct regval_list *regs, int array_size)
+{
+	int i = 0;
+	int ret = 0;
+
+	if (!regs)
+		return -EINVAL;
+
+	while (i < array_size) {
+		ret = ov5647_write(sd, regs->addr, regs->data);
+		if (ret < 0)
+			return ret;
+		i++;
+		regs++;
+	}
+	return 0;
+}
+
+static void ov5647_set_virtual_channel(struct v4l2_subdev *sd, int channel)
+{
+	u8 channel_id;
+
+	ov5647_read(sd, 0x4814, &channel_id);
+	channel_id &= ~(3 << 6);
+	ov5647_write(sd, 0x4814, channel_id | (channel << 6));
+}
+
+void ov5647_stream_on(struct v4l2_subdev *sd)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+
+	ov5647_write(sd, 0x4202, 0x00);
+	dev_dbg(&client->dev, "Stream on");
+	ov5647_write(sd, 0x300D, 0x00);
+}
+
+void ov5647_stream_off(struct v4l2_subdev *sd)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+
+	ov5647_write(sd, 0x4202, 0x0f);
+	dev_dbg(&client->dev, "Stream off");
+	ov5647_write(sd, 0x300D, 0x01);
+}
+
+/**
+ * @short Set SW standby
+ * @param[in] sd v4l2 sd
+ * @param[in] stanby standby mode status (on or off)
+ * @return Error code
+ */
+static int set_sw_standby(struct v4l2_subdev *sd, bool standby)
+{
+	int ret;
+	unsigned char rdval;
+
+	ret = ov5647_read(sd, 0x0100, &rdval);
+	if (ret != 0)
+		return ret;
+
+	if (standby)
+		rdval &= 0xfe;
+	else
+		rdval |= 0x01;
+
+	ret = ov5647_write(sd, 0x0100, rdval);
+
+	return ret;
+}
+
+/**
+ * @short Initialize sensor
+ * @param[in] sd v4l2 subdev
+ * @param[in] val not used
+ * @return Error code
+ */
+static int __sensor_init(struct v4l2_subdev *sd)
+{
+	int ret;
+	u8 resetval;
+	u8 rdval;
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+
+	dev_dbg(&client->dev, "sensor init\n");
+
+	ret = ov5647_read(sd, 0x0100, &rdval);
+	if (ret != 0)
+		return ret;
+
+	ov5647_write(sd, 0x4800, 0x25);
+	ov5647_stream_off(sd);
+
+	ret = ov5647_write_array(sd, ov5647_640x480,
+					ARRAY_SIZE(ov5647_640x480));
+	if (ret < 0) {
+		dev_err(&client->dev, "write sensor_default_regs error\n");
+		return ret;
+	}
+
+	ov5647_set_virtual_channel(sd, 0);
+
+	ov5647_read(sd, 0x0100, &resetval);
+	if (!(resetval & 0x01)) {
+		dev_err(&client->dev, "Device was in SW standby");
+		ov5647_write(sd, 0x0100, 0x01);
+	}
+
+	ov5647_write(sd, 0x4800, 0x04);
+	ov5647_stream_on(sd);
+
+	return 0;
+}
+
+/**
+ * @short Control sensor power state
+ * @param[in] sd v4l2 subdev
+ * @param[in] on Sensor power
+ * @return Error code
+ */
+static int sensor_power(struct v4l2_subdev *sd, int on)
+{
+	int ret;
+	struct ov5647 *ov5647 = to_state(sd);
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+
+	ret = 0;
+	mutex_lock(&ov5647->lock);
+
+	if (on && !ov5647->power_count)	{
+		dev_dbg(&client->dev, "OV5647 power on\n");
+
+		clk_set_rate(ov5647->xclk, ov5647->xclk_freq);
+
+		ret = clk_prepare_enable(ov5647->xclk);
+		if (ret < 0) {
+			dev_err(ov5647->dev, "clk prepare enable failed\n");
+			goto out;
+		}
+
+		ret = ov5647_write_array(sd, sensor_oe_enable_regs,
+				ARRAY_SIZE(sensor_oe_enable_regs));
+		if (ret < 0) {
+			clk_disable_unprepare(ov5647->xclk);
+			dev_err(&client->dev,
+				"write sensor_oe_enable_regs error\n");
+			goto out;
+		}
+
+		ret = __sensor_init(sd);
+		if (ret < 0) {
+			clk_disable_unprepare(ov5647->xclk);
+			dev_err(&client->dev,
+				"Camera not available, check Power\n");
+			goto out;
+		}
+	} else if (!on && ov5647->power_count == 1) {
+		dev_dbg(&client->dev, "OV5647 power off\n");
+
+		dev_dbg(&client->dev, "disable oe\n");
+		ret = ov5647_write_array(sd, sensor_oe_disable_regs,
+				ARRAY_SIZE(sensor_oe_disable_regs));
+
+		if (ret < 0)
+			dev_dbg(&client->dev, "disable oe failed\n");
+
+		ret = set_sw_standby(sd, true);
+
+		if (ret < 0)
+			dev_dbg(&client->dev, "soft stby failed\n");
+
+		clk_disable_unprepare(ov5647->xclk);
+	}
+
+	/* Update the power count. */
+	ov5647->power_count += on ? 1 : -1;
+	WARN_ON(ov5647->power_count < 0);
+
+out:
+	mutex_unlock(&ov5647->lock);
+
+	return ret;
+}
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+/**
+ * @short Get register value
+ * @param[in] sd v4l2 subdev
+ * @param[in] reg register struct
+ * @return Error code
+ */
+static int sensor_get_register(struct v4l2_subdev *sd,
+				struct v4l2_dbg_register *reg)
+{
+	unsigned char val = 0;
+	int ret;
+
+	ret = ov5647_read(sd, reg->reg & 0xff, &val);
+	if (ret != 0)
+		return ret;
+
+	reg->val = val;
+	reg->size = 1;
+
+	return ret;
+}
+
+/**
+ * @short Set register value
+ * @param[in] sd v4l2 subdev
+ * @param[in] reg register struct
+ * @return Error code
+ */
+static int sensor_set_register(struct v4l2_subdev *sd,
+				const struct v4l2_dbg_register *reg)
+{
+	return ov5647_write(sd, reg->reg & 0xff, reg->val & 0xff);
+}
+#endif
+
+/**
+ * @short Subdev core operations registration
+ */
+static const struct v4l2_subdev_core_ops sensor_core_ops = {
+	.s_power		= sensor_power,
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+	.g_register		= sensor_get_register,
+	.s_register		= sensor_set_register,
+#endif
+};
+
+static int enum_mbus_code(struct v4l2_subdev *sd,
+				struct v4l2_subdev_pad_config *cfg,
+				struct v4l2_subdev_mbus_code_enum *code)
+{
+	if (code->index > 0)
+		return -EINVAL;
+
+	code->code = MEDIA_BUS_FMT_SBGGR8_1X8;
+
+	return 0;
+}
+
+static const struct v4l2_subdev_pad_ops subdev_pad_ops = {
+	.enum_mbus_code = enum_mbus_code,
+};
+
+
+/**
+ * @short Subdev operations registration
+ *
+ */
+static const struct v4l2_subdev_ops subdev_ops = {
+	.core		= &sensor_core_ops,
+	.pad		= &subdev_pad_ops,
+};
+
+/**
+ * @short Detect camera version and model
+ * @param[in] sd v4l2 subdev
+ * @return Error code
+ */
+static int ov5647_detect(struct v4l2_subdev *sd)
+{
+	unsigned char v;
+	int ret;
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+
+	ret = ov5647_write(sd, OV5647_SW_RESET, 0x01);
+	if (ret < 0)
+		return ret;
+	ret = ov5647_read(sd, OV5647_REG_CHIPID_H, &v);
+	if (ret < 0)
+		return ret;
+	if (v != 0x56) {
+		dev_err(&client->dev, "Wrong model version detected");
+		return -ENODEV;
+	}
+	ret = ov5647_read(sd, OV5647_REG_CHIPID_L, &v);
+	if (ret < 0)
+		return ret;
+	if (v != 0x47) {
+		dev_err(&client->dev, "Wrong model version detected");
+		return -ENODEV;
+	}
+
+	ret = ov5647_write(sd, OV5647_SW_RESET, 0x00);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+/**
+ * @short Detect if camera is registered
+ * @param[in] sd v4l2 subdev
+ * @return Error code
+ */
+static int ov5647_registered(struct v4l2_subdev *sd)
+{
+	return 0;
+}
+
+/**
+ * @short Open device
+ * @param[in] sd v4l2 subdev
+ * @param[in] fh v4l2 file handler
+ * @return Error code
+ */
+static int ov5647_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
+{
+	struct v4l2_mbus_framefmt *format =
+				v4l2_subdev_get_try_format(sd, fh->pad, 0);
+	struct v4l2_rect *crop =
+				v4l2_subdev_get_try_crop(sd, fh->pad, 0);
+
+	crop->left = OV5647_COLUMN_START_DEF;
+	crop->top = OV5647_ROW_START_DEF;
+	crop->width = OV5647_WINDOW_WIDTH_DEF;
+	crop->height = OV5647_WINDOW_HEIGHT_DEF;
+
+	format->code = MEDIA_BUS_FMT_SBGGR8_1X8;
+
+	format->width = OV5647_WINDOW_WIDTH_DEF;
+	format->height = OV5647_WINDOW_HEIGHT_DEF;
+	format->field = V4L2_FIELD_NONE;
+	format->colorspace = V4L2_COLORSPACE_SRGB;
+
+	return sensor_power(sd, true);
+}
+
+/**
+ * @short Open device
+ * @param[in] sd v4l2 subdev
+ * @param[in] fh v4l2 file handler
+ * @return Error code
+ */
+static int ov5647_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
+{
+	return sensor_power(sd, false);
+}
+
+/**
+ * @short Subdev internal operations registration
+ *
+ */
+static const struct v4l2_subdev_internal_ops ov5647_subdev_internal_ops = {
+	.registered = ov5647_registered,
+	.open = ov5647_open,
+	.close = ov5647_close,
+};
+
+/**
+ * @short Initialization routine - Entry point of the driver
+ * @param[in] client pointer to the i2c client structure
+ * @param[in] id pointer to the i2c device id structure
+ * @return 0 on success and a negative number on failure
+ */
+static int ov5647_probe(struct i2c_client *client,
+			const struct i2c_device_id *id)
+{
+	struct device *dev = &client->dev;
+	struct ov5647 *sensor;
+	int ret;
+	struct v4l2_subdev *sd;
+
+	dev_info(&client->dev, "Installing OmniVision OV5647 camera driver\n");
+
+	sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL);
+	if (sensor == NULL)
+		return -ENOMEM;
+
+	/* get system clock (xclk) */
+	sensor->xclk = devm_clk_get(dev, "xclk");
+	if (IS_ERR(sensor->xclk)) {
+		dev_err(dev, "could not get xclk");
+		return PTR_ERR(sensor->xclk);
+	}
+
+	ret = of_property_read_u32(dev->of_node, "clock-frequency",
+				    &sensor->xclk_freq);
+	if (ret) {
+		dev_err(dev, "could not get xclk frequency\n");
+		return ret;
+	}
+
+	mutex_init(&sensor->lock);
+	sensor->dev = dev;
+
+	sd = &sensor->sd;
+	v4l2_i2c_subdev_init(sd, client, &subdev_ops);
+	sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+
+	sensor->pad.flags = MEDIA_PAD_FL_SOURCE;
+	sd->entity.function = MEDIA_ENT_F_CAM_SENSOR;
+	ret = media_entity_pads_init(&sd->entity, 1, &sensor->pad);
+	if (ret < 0)
+		goto mutex_remove;
+
+	ret = ov5647_detect(sd);
+	if (ret < 0)
+		goto error;
+
+	ret = v4l2_async_register_subdev(sd);
+	if (ret < 0)
+		goto error;
+
+	return 0;
+error:
+	media_entity_cleanup(&sd->entity);
+mutex_remove:
+	mutex_destroy(&sensor->lock);
+	return ret;
+}
+
+/**
+ * @short Exit routine - Exit point of the driver
+ * @param[in] client pointer to the i2c client structure
+ * @return 0 on success and a negative number on failure
+ */
+static int ov5647_remove(struct i2c_client *client)
+{
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct ov5647 *ov5647 = to_state(sd);
+
+	v4l2_async_unregister_subdev(&ov5647->sd);
+	media_entity_cleanup(&ov5647->sd.entity);
+	v4l2_device_unregister_subdev(sd);
+	mutex_destroy(&ov5647->lock);
+
+	return 0;
+}
+
+static const struct i2c_device_id ov5647_id[] = {
+	{ "ov5647", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, ov5647_id);
+
+#if IS_ENABLED(CONFIG_OF)
+static const struct of_device_id ov5647_of_match[] = {
+	{ .compatible = "ovti,ov5647" },
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, ov5647_of_match);
+#endif
+
+/**
+ * @short i2c driver structure
+ */
+static struct i2c_driver ov5647_driver = {
+	.driver = {
+		.of_match_table = of_match_ptr(ov5647_of_match),
+		.owner	= THIS_MODULE,
+		.name	= "ov5647",
+	},
+	.probe		= ov5647_probe,
+	.remove		= ov5647_remove,
+	.id_table	= ov5647_id,
+};
+
+module_i2c_driver(ov5647_driver);
+
+MODULE_AUTHOR("Ramiro Oliveira <roliveir@synopsys.com>");
+MODULE_DESCRIPTION("A low-level driver for OmniVision ov5647 sensors");
+MODULE_LICENSE("GPL v2");
-- 
2.10.2

^ permalink raw reply related

* [PATCH v6 1/2] Add OV5647 device tree documentation
From: Ramiro Oliveira @ 2016-12-13 14:32 UTC (permalink / raw)
  To: mchehab, linux-kernel, linux-media, robh+dt, devicetree
  Cc: davem, gregkh, geert+renesas, akpm, linux, hverkuil, dheitmueller,
	slongerbeam, lars, robert.jarzmik, pavel, pali.rohar,
	sakari.ailus, mark.rutland, Ramiro.Oliveira, CARLOS.PALMINHA
In-Reply-To: <cover.1481639091.git.roliveir@synopsys.com>

Create device tree bindings documentation.

Signed-off-by: Ramiro Oliveira <roliveir@synopsys.com>
---
 .../devicetree/bindings/media/i2c/ov5647.txt       | 35 ++++++++++++++++++++++
 1 file changed, 35 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/media/i2c/ov5647.txt

diff --git a/Documentation/devicetree/bindings/media/i2c/ov5647.txt b/Documentation/devicetree/bindings/media/i2c/ov5647.txt
new file mode 100644
index 0000000..46e5e30
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/i2c/ov5647.txt
@@ -0,0 +1,35 @@
+Omnivision OV5647 raw image sensor
+---------------------------------
+
+OV5647 is a raw image sensor with MIPI CSI-2 and CCP2 image data interfaces
+and CCI (I2C compatible) control bus.
+
+Required properties:
+
+- compatible	: "ovti,ov5647";
+- reg		: I2C slave address of the sensor;
+- clocks	: Reference to the xclk clock.
+- clock-names	: Should be "xclk".
+- clock-frequency: Frequency of the xclk clock
+
+The common video interfaces bindings (see video-interfaces.txt) should be
+used to specify link to the image data receiver. The OV5647 device
+node should contain one 'port' child node with an 'endpoint' subnode.
+
+Example:
+
+	i2c@0x02000 {
+		...
+		ov: camera@0x36 {
+			compatible = "ovti,ov5647";
+			reg = <0x36>;
+			clocks = <&camera_clk>;
+			clock-names = "xclk";
+			clock-frequency = <30000000>;
+			port {
+				camera_1: endpoint {
+					remote-endpoint = <&csi1_ep1>;
+				};
+			};
+		};
+	};
-- 
2.10.2

^ permalink raw reply related

* [PATCH v6 0/2] Add support for Omnivision OV5647
From: Ramiro Oliveira @ 2016-12-13 14:32 UTC (permalink / raw)
  To: mchehab, linux-kernel, linux-media, robh+dt, devicetree
  Cc: davem, gregkh, geert+renesas, akpm, linux, hverkuil, dheitmueller,
	slongerbeam, lars, robert.jarzmik, pavel, pali.rohar,
	sakari.ailus, mark.rutland, Ramiro.Oliveira, CARLOS.PALMINHA

Hello,

This patch adds support for the Omnivision OV5647 sensor.

At the moment it only supports 640x480 in Raw 8.

This is the sixth version of the OV5647 camera driver patchset.

v6:
 - Add example to DT documentation
 - Remove data-lanes and clock-lane property from DT
 - Add external clock property to DT
 - Order includes
 - Remove unused variables and functions
 - Add external clock handling
 - Add power on counter
 - Change from g/s_parm to g/s_frame_interval

v5:
 - Refactor code 
 - Change comments
 - Add missing error handling in some functions

v4: 
 - Add correct license
 - Revert debugging info to generic infrastructure
 - Turn defines into enums
 - Correct code style issues
 - Remove unused defines
 - Make sure all errors where being handled
 - Rename some functions to make code more readable
 - Add some debugging info

v3: 
 - No changes. Re-submitted due to lack of responses

v2: 
 - Corrections in DT documentation

Ramiro Oliveira (2):
  Add OV5647 device tree documentation
  Add support for OV5647 sensor.

 .../devicetree/bindings/media/i2c/ov5647.txt       |  35 +
 MAINTAINERS                                        |   7 +
 drivers/media/i2c/Kconfig                          |  12 +
 drivers/media/i2c/Makefile                         |   1 +
 drivers/media/i2c/ov5647.c                         | 718 +++++++++++++++++++++
 5 files changed, 773 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/media/i2c/ov5647.txt
 create mode 100644 drivers/media/i2c/ov5647.c

-- 
2.10.2

^ permalink raw reply

* Re: [PATCH] iio: misc: add a generic regulator driver
From: Bartosz Golaszewski @ 2016-12-13 14:28 UTC (permalink / raw)
  To: Lars-Peter Clausen, Linus Walleij
  Cc: Jonathan Cameron, Hartmut Knaack, Peter Meerwald-Stadler,
	Rob Herring, Mark Rutland, linux-iio-u79uwXL29TY76Z2rM5mHXA,
	linux-devicetree, LKML, Kevin Hilman, Patrick Titiano,
	Neil Armstrong, Liam Girdwood, Mark Brown
In-Reply-To: <c670d597-46b6-235f-545f-7136a3abff7f-Qo5EllUWu/uELgA04lAiVw@public.gmane.org>

2016-12-12 18:15 GMT+01:00 Lars-Peter Clausen <lars-Qo5EllUWu/uELgA04lAiVw@public.gmane.org>:
> On 12/06/2016 12:12 PM, Bartosz Golaszewski wrote:

[snip!]

>>
>> So the problem we have is not power-cycling the adc - it's
>> power-cycling the device connected to a probe on which there's an adc.
>> What I was trying to do was adding support for the power-switch on
>> baylibre-acme[1] probes.
>>
>> For example: we have a USB probe on which the VBUS signal goes through
>> a power load switch and than through the adc. The adc (in this case
>> ina226) is always powered on, while the fixed regulator I wanted to
>> enable/disable actually drives the power switch to cut/restore power
>> to the connected USB device i.e. there's no real regulator - just a
>> GPIO driving the power switch.
>>
>> A typical use case is measuring the power consumption of development
>> boards[2]. Rebooting them remotely using acme probes is already done,
>> but we're using the obsolete /sys/class/gpio interface.
>>
>> We're already using libiio to read the measured data from the power
>> monitor, that's why we'd like to use the iio framework for
>> power-cycling the devices as well. My question is: would bridging the
>> regulator framework be the right solution? Should we look for
>> something else? Bridge the GPIO framework instead?
>
> I wouldn't necessaries create bridge, but instead just use the GPIO
> framework directly.
>
> We now have the GPIO chardev interface which meant to be used to support
> application specific logic that control the GPIOs, but where you don't want
> to write a kernel driver.
>
> My idea was to add GPIOs and GPIO chips as high level object inside libiio
> that can be accessed through the same context as the IIO devices. Similar to
> the current IIO API you have a API for gpios that allows to enumerate the
> GPIO devices and their pins as well as modify the pin state.
>

+ Linus

While the new GPIO interface would be very convenient - in our case we
could simply name the lines appropriately in the device tree - I'm not
sure this would be the correct approach.

>From this year's ELCE in Berlin I remember Linus suggested during his
talk that it's always better to write a kernel driver. Also: this way
the relevant GPIO lines would not be reserved for exclusive use by
power switches.

Linus - do you have any thoughts/suggestions on that subject?

Best regards,
Bartosz Golaszewski

^ permalink raw reply

* [PATCH v4 2/2] eeprom: Add IDT 89HPESx driver bindings file
From: Serge Semin @ 2016-12-13 14:22 UTC (permalink / raw)
  To: gregkh, srinivas.kandagatla, andrew, robh+dt, mark.rutland
  Cc: Sergey.Semin, linux-kernel, devicetree, Serge Semin
In-Reply-To: <1481638971-6247-1-git-send-email-fancer.lancer@gmail.com>

IDT 89HPESx PCIe-switches exposes SMBus interface to have an access to
the device CSRs and EEPROM. So to properly utilize the interface
functionality, developer should declare a valid dts-file node, which
would refer to the corresponding 89HPESx device.

Signed-off-by: Serge Semin <fancer.lancer@gmail.com>
---
 .../devicetree/bindings/misc/idt_89hpesx.txt       | 44 ++++++++++++++++++++++
 1 file changed, 44 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/misc/idt_89hpesx.txt

diff --git a/Documentation/devicetree/bindings/misc/idt_89hpesx.txt b/Documentation/devicetree/bindings/misc/idt_89hpesx.txt
new file mode 100644
index 0000000..b9093b7
--- /dev/null
+++ b/Documentation/devicetree/bindings/misc/idt_89hpesx.txt
@@ -0,0 +1,44 @@
+EEPROM / CSR SMBus-slave interface of IDT 89HPESx devices
+
+Required properties:
+  - compatible : should be "<manufacturer>,<type>"
+		 Basically there is only one manufacturer: idt, but some
+		 compatible devices may be produced in future. Following devices
+		 are supported: 89hpes8nt2, 89hpes12nt3, 89hpes24nt6ag2,
+		 89hpes32nt8ag2, 89hpes32nt8bg2, 89hpes12nt12g2, 89hpes16nt16g2,
+		 89hpes24nt24g2, 89hpes32nt24ag2, 89hpes32nt24bg2;
+		 89hpes12n3, 89hpes12n3a, 89hpes24n3, 89hpes24n3a;
+		 89hpes32h8, 89hpes32h8g2, 89hpes48h12, 89hpes48h12g2,
+		 89hpes48h12ag2, 89hpes16h16, 89hpes22h16, 89hpes22h16g2,
+		 89hpes34h16, 89hpes34h16g2, 89hpes64h16, 89hpes64h16g2,
+		 89hpes64h16ag2;
+		 89hpes12t3g2, 89hpes24t3g2, 89hpes16t4, 89hpes4t4g2,
+		 89hpes10t4g2, 89hpes16t4g2, 89hpes16t4ag2, 89hpes5t5,
+		 89hpes6t5, 89hpes8t5, 89hpes8t5a, 89hpes24t6, 89hpes6t6g2,
+		 89hpes24t6g2, 89hpes16t7, 89hpes32t8, 89hpes32t8g2,
+		 89hpes48t12, 89hpes48t12g2.
+  - reg :	 I2C address of the IDT 89HPESx device.
+
+Optionally there can be EEPROM-compatible subnode:
+  - compatible:  There are five EEPROM devices supported: 24c32, 24c64, 24c128,
+		 24c256 and 24c512 differed by size.
+  - reg:         Custom address of EEPROM device (If not specified IDT 89HPESx
+    (optional)	 device will try to communicate with EEPROM sited by default
+		 address - 0x50)
+  - read-only :	 Parameterless property disables writes to the EEPROM
+    (optional)
+
+Example:
+	idt@60 {
+		compatible = "idt,89hpes32nt8ag2";
+		reg = <0x74>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		eeprom@50 {
+			compatible = "onsemi,24c64";
+			reg = <0x50>;
+			read-only;
+		};
+	};
+
-- 
2.6.6

^ permalink raw reply related

* [PATCH v4 1/2] eeprom: Add IDT 89HPESx EEPROM/CSR driver
From: Serge Semin @ 2016-12-13 14:22 UTC (permalink / raw)
  To: gregkh, srinivas.kandagatla, andrew, robh+dt, mark.rutland
  Cc: Sergey.Semin, linux-kernel, devicetree, Serge Semin
In-Reply-To: <1481638971-6247-1-git-send-email-fancer.lancer@gmail.com>

  This driver provides an access to EEPROM of IDT PCIe-switches. IDT PCIe-
switches expose a simple SMBus interface to perform IO-operations from/to
EEPROM, which is located at private (so called Master) SMBus. The driver
creates a simple binary sysfs-file to have an access to the EEPROM using
the SMBus-slave interface in the i2c-device susfs-directory:
     /sys/bus/i2c/devices/<bus>-<devaddr>/eeprom
In case if read-only flag is specified at dts-node of the device, User-space
applications won't be able to write to the EEPROM sysfs-node.

  Additionally IDT 89HPESx SMBus interface has an ability to read/write
values of device CSRs. This driver exposes debugfs-file to perform simple
IO-operations using that ability for just basic debug purpose. Particularly
the next file is created in the specific debugfs-directory:
     /sys/kernel/debug/idt_csr/
Format of the debugfs-file value is:
     $ cat /sys/kernel/debug/idt_csr/<bus>-<devaddr>/<devname>;
     <CSR address>:<CSR value>
So reading the content of the file gives current CSR address and it value.
If User-space application wishes to change current CSR address, it can just
write a proper value to the sysfs-file:
     $ echo "<CSR address>" >
         /sys/kernel/debug/idt_csr/<bus>-<devaddr>/<devname>
If it wants to change the CSR value as well, the format of the write
operation is:
     $ echo "<CSR address>:<CSR value>" > \
         /sys/kernel/debug/idt_csr/<bus>-<devaddr>/<devname>;
CSR address and value can be any of hexadecimal, decimal or octal format.

Signed-off-by: Serge Semin <fancer.lancer@gmail.com>
---
 drivers/misc/eeprom/Kconfig       |   10 +
 drivers/misc/eeprom/Makefile      |    1 +
 drivers/misc/eeprom/idt_89hpesx.c | 1634 +++++++++++++++++++++++++++++++++++++
 3 files changed, 1645 insertions(+)
 create mode 100644 drivers/misc/eeprom/idt_89hpesx.c

diff --git a/drivers/misc/eeprom/Kconfig b/drivers/misc/eeprom/Kconfig
index c4e41c2..de58762 100644
--- a/drivers/misc/eeprom/Kconfig
+++ b/drivers/misc/eeprom/Kconfig
@@ -100,4 +100,14 @@ config EEPROM_DIGSY_MTC_CFG
 
 	  If unsure, say N.
 
+config EEPROM_IDT_89HPESX
+	tristate "IDT 89HPESx PCIe-swtiches EEPROM / CSR support"
+	depends on I2C && SYSFS
+	help
+	  Enable this driver to get read/write access to EEPROM / CSRs
+	  over IDT PCIe-swtich i2c-slave interface.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called idt_89hpesx.
+
 endmenu
diff --git a/drivers/misc/eeprom/Makefile b/drivers/misc/eeprom/Makefile
index fc1e81d..90a5262 100644
--- a/drivers/misc/eeprom/Makefile
+++ b/drivers/misc/eeprom/Makefile
@@ -5,3 +5,4 @@ obj-$(CONFIG_EEPROM_MAX6875)	+= max6875.o
 obj-$(CONFIG_EEPROM_93CX6)	+= eeprom_93cx6.o
 obj-$(CONFIG_EEPROM_93XX46)	+= eeprom_93xx46.o
 obj-$(CONFIG_EEPROM_DIGSY_MTC_CFG) += digsy_mtc_eeprom.o
+obj-$(CONFIG_EEPROM_IDT_89HPESX) += idt_89hpesx.o
diff --git a/drivers/misc/eeprom/idt_89hpesx.c b/drivers/misc/eeprom/idt_89hpesx.c
new file mode 100644
index 0000000..664e315
--- /dev/null
+++ b/drivers/misc/eeprom/idt_89hpesx.c
@@ -0,0 +1,1634 @@
+/*
+ *   This file is provided under a GPLv2 license.  When using or
+ *   redistributing this file, you may do so under that license.
+ *
+ *   GPL LICENSE SUMMARY
+ *
+ *   Copyright (C) 2016 T-Platforms. All Rights Reserved.
+ *
+ *   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.
+ *
+ *   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, it can be found <http://www.gnu.org/licenses/>.
+ *
+ *   The full GNU General Public License is included in this distribution in
+ *   the file called "COPYING".
+ *
+ *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ *   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ *   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ *   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ *   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ *   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * IDT PCIe-switch NTB Linux driver
+ *
+ * Contact Information:
+ * Serge Semin <fancer.lancer@gmail.com>, <Sergey.Semin@t-platforms.ru>
+ */
+/*
+ *           NOTE of the IDT 89HPESx SMBus-slave interface driver
+ *    This driver primarily is developed to have an access to EEPROM device of
+ * IDT PCIe-switches. IDT provides a simple SMBus interface to perform IO-
+ * operations from/to EEPROM, which is located at private (so called Master)
+ * SMBus of switches. Using that interface this the driver creates a simple
+ * binary sysfs-file in the device directory:
+ * /sys/bus/i2c/devices/<bus>-<devaddr>/eeprom
+ * In case if read-only flag is specified in the dts-node of device desription,
+ * User-space applications won't be able to write to the EEPROM sysfs-node.
+ *    Additionally IDT 89HPESx SMBus interface has an ability to write/read
+ * data of device CSRs. This driver exposes debugf-file to perform simple IO
+ * operations using that ability for just basic debug purpose. Particularly
+ * next file is created in the specific debugfs-directory:
+ * /sys/kernel/debug/idt_csr/
+ * Format of the debugfs-node is:
+ * $ cat /sys/kernel/debug/idt_csr/<bus>-<devaddr>/<devname>;
+ * <CSR address>:<CSR value>
+ * So reading the content of the file gives current CSR address and it value.
+ * If User-space application wishes to change current CSR address,
+ * it can just write a proper value to the sysfs-file:
+ * $ echo "<CSR address>" > /sys/kernel/debug/idt_csr/<bus>-<devaddr>/<devname>
+ * If it wants to change the CSR value as well, the format of the write
+ * operation is:
+ * $ echo "<CSR address>:<CSR value>" > \
+ *        /sys/kernel/debug/idt_csr/<bus>-<devaddr>/<devname>;
+ * CSR address and value can be any of hexadecimal, decimal or octal format.
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/sizes.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/sysfs.h>
+#include <linux/debugfs.h>
+#include <linux/mod_devicetable.h>
+#include <linux/of.h>
+#include <linux/i2c.h>
+#include <linux/pci_ids.h>
+#include <linux/delay.h>
+
+#define IDT_NAME		"89hpesx"
+#define IDT_89HPESX_DESC	"IDT 89HPESx SMBus-slave interface driver"
+#define IDT_89HPESX_VER		"1.0"
+
+MODULE_DESCRIPTION(IDT_89HPESX_DESC);
+MODULE_VERSION(IDT_89HPESX_VER);
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("T-platforms");
+
+/*
+ * csr_dbgdir - CSR read/write operations Debugfs directory
+ */
+static struct dentry *csr_dbgdir;
+
+/*
+ * struct idt_89hpesx_dev - IDT 89HPESx device data structure
+ * @eesize:	Size of EEPROM in bytes (calculated from "idt,eecompatible")
+ * @eero:	EEPROM Read-only flag
+ * @eeaddr:	EEPROM custom address
+ *
+ * @inieecmd:	Initial cmd value for EEPROM read/write operations
+ * @inicsrcmd:	Initial cmd value for CSR read/write operations
+ * @iniccode:	Initialial command code value for IO-operations
+ *
+ * @csr:	CSR address to perform read operation
+ *
+ * @smb_write:	SMBus write method
+ * @smb_read:	SMBus read method
+ * @smb_mtx:	SMBus mutex
+ *
+ * @client:	i2c client used to perform IO operations
+ *
+ * @ee_file:	EEPROM read/write sysfs-file
+ * @csr_file:	CSR read/write debugfs-node
+ */
+struct idt_smb_seq;
+struct idt_89hpesx_dev {
+	u32 eesize;
+	bool eero;
+	u8 eeaddr;
+
+	u8 inieecmd;
+	u8 inicsrcmd;
+	u8 iniccode;
+
+	atomic_t csr;
+
+	int (*smb_write)(struct idt_89hpesx_dev *, const struct idt_smb_seq *);
+	int (*smb_read)(struct idt_89hpesx_dev *, struct idt_smb_seq *);
+	struct mutex smb_mtx;
+
+	struct i2c_client *client;
+
+	struct bin_attribute *ee_file;
+	struct dentry *csr_dir;
+	struct dentry *csr_file;
+};
+
+/*
+ * struct idt_smb_seq - sequence of data to be read/written from/to IDT 89HPESx
+ * @ccode:	SMBus command code
+ * @bytecnt:	Byte count of operation
+ * @data:	Data to by written
+ */
+struct idt_smb_seq {
+	u8 ccode;
+	u8 bytecnt;
+	u8 *data;
+};
+
+/*
+ * struct idt_eeprom_seq - sequence of data to be read/written from/to EEPROM
+ * @cmd:	Transaction CMD
+ * @eeaddr:	EEPROM custom address
+ * @memaddr:	Internal memory address of EEPROM
+ * @data:	Data to be written at the memory address
+ */
+struct idt_eeprom_seq {
+	u8 cmd;
+	u8 eeaddr;
+	u16 memaddr;
+	u8 data;
+} __packed;
+
+/*
+ * struct idt_csr_seq - sequence of data to be read/written from/to CSR
+ * @cmd:	Transaction CMD
+ * @csraddr:	Internal IDT device CSR address
+ * @data:	Data to be read/written from/to the CSR address
+ */
+struct idt_csr_seq {
+	u8 cmd;
+	u16 csraddr;
+	u32 data;
+} __packed;
+
+/*
+ * SMBus command code macros
+ * @CCODE_END:		Indicates the end of transaction
+ * @CCODE_START:	Indicates the start of transaction
+ * @CCODE_CSR:		CSR read/write transaction
+ * @CCODE_EEPROM:	EEPROM read/write transaction
+ * @CCODE_BYTE:		Supplied data has BYTE length
+ * @CCODE_WORD:		Supplied data has WORD length
+ * @CCODE_BLOCK:	Supplied data has variable length passed in bytecnt
+ *			byte right following CCODE byte
+ */
+#define CCODE_END	((u8)0x01)
+#define CCODE_START	((u8)0x02)
+#define CCODE_CSR	((u8)0x00)
+#define CCODE_EEPROM	((u8)0x04)
+#define CCODE_BYTE	((u8)0x00)
+#define CCODE_WORD	((u8)0x20)
+#define CCODE_BLOCK	((u8)0x40)
+#define CCODE_PEC	((u8)0x80)
+
+/*
+ * EEPROM command macros
+ * @EEPROM_OP_WRITE:	EEPROM write operation
+ * @EEPROM_OP_READ:	EEPROM read operation
+ * @EEPROM_USA:		Use specified address of EEPROM
+ * @EEPROM_NAERR:	EEPROM device is not ready to respond
+ * @EEPROM_LAERR:	EEPROM arbitration loss error
+ * @EEPROM_MSS:		EEPROM misplace start & stop bits error
+ * @EEPROM_WR_CNT:	Bytes count to perform write operation
+ * @EEPROM_WRRD_CNT:	Bytes count to write before reading
+ * @EEPROM_RD_CNT:	Bytes count to perform read operation
+ * @EEPROM_DEF_SIZE:	Fall back size of EEPROM
+ * @EEPROM_DEF_ADDR:	Defatul EEPROM address
+ * @EEPROM_TOUT:	Timeout before retry read operation if eeprom is busy
+ */
+#define EEPROM_OP_WRITE	((u8)0x00)
+#define EEPROM_OP_READ	((u8)0x01)
+#define EEPROM_USA	((u8)0x02)
+#define EEPROM_NAERR	((u8)0x08)
+#define EEPROM_LAERR    ((u8)0x10)
+#define EEPROM_MSS	((u8)0x20)
+#define EEPROM_WR_CNT	((u8)5)
+#define EEPROM_WRRD_CNT	((u8)4)
+#define EEPROM_RD_CNT	((u8)5)
+#define EEPROM_DEF_SIZE	((u16)4096)
+#define EEPROM_DEF_ADDR	((u8)0x50)
+#define EEPROM_TOUT	(100)
+
+/*
+ * CSR command macros
+ * @CSR_DWE:		Enable all four bytes of the operation
+ * @CSR_OP_WRITE:	CSR write operation
+ * @CSR_OP_READ:	CSR read operation
+ * @CSR_RERR:		Read operation error
+ * @CSR_WERR:		Write operation error
+ * @CSR_WR_CNT:		Bytes count to perform write operation
+ * @CSR_WRRD_CNT:	Bytes count to write before reading
+ * @CSR_RD_CNT:		Bytes count to perform read operation
+ * @CSR_MAX:		Maximum CSR address
+ * @CSR_DEF:		Default CSR address
+ * @CSR_REAL_ADDR:	CSR real unshifted address
+ */
+#define CSR_DWE			((u8)0x0F)
+#define CSR_OP_WRITE		((u8)0x00)
+#define CSR_OP_READ		((u8)0x10)
+#define CSR_RERR		((u8)0x40)
+#define CSR_WERR		((u8)0x80)
+#define CSR_WR_CNT		((u8)7)
+#define CSR_WRRD_CNT		((u8)3)
+#define CSR_RD_CNT		((u8)7)
+#define CSR_MAX			((u32)0x3FFFF)
+#define CSR_DEF			((u16)0x0000)
+#define CSR_REAL_ADDR(val)	((unsigned int)val << 2)
+
+/*
+ * IDT 89HPESx basic register
+ * @IDT_VIDDID_CSR:	PCIe VID and DID of IDT 89HPESx
+ * @IDT_VID_MASK:	Mask of VID
+ */
+#define IDT_VIDDID_CSR	((u32)0x0000)
+#define IDT_VID_MASK	((u32)0xFFFF)
+
+/*
+ * IDT 89HPESx can send NACK when new command is sent before previous one
+ * fininshed execution. In this case driver retries operation
+ * certain times.
+ * @RETRY_CNT:		Number of retries before giving up and fail
+ * @idt_smb_safe:	Generate a retry loop on corresponding SMBus method
+ */
+#define RETRY_CNT (128)
+#define idt_smb_safe(ops, args...) ({ \
+	int __retry = RETRY_CNT; \
+	s32 __sts; \
+	do { \
+		__sts = i2c_smbus_ ## ops ## _data(args); \
+	} while (__retry-- && __sts < 0); \
+	__sts; \
+})
+
+/*===========================================================================
+ *                         i2c bus level IO-operations
+ *===========================================================================
+ */
+
+/*
+ * idt_smb_write_byte() - SMBus write method when I2C_SMBUS_BYTE_DATA operation
+ *                        is only available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Sequence of data to be written
+ */
+static int idt_smb_write_byte(struct idt_89hpesx_dev *pdev,
+			      const struct idt_smb_seq *seq)
+{
+	s32 sts;
+	u8 ccode;
+	int idx;
+
+	/* Loop over the supplied data sending byte one-by-one */
+	for (idx = 0; idx < seq->bytecnt; idx++) {
+		/* Collect the command code byte */
+		ccode = seq->ccode | CCODE_BYTE;
+		if (idx == 0)
+			ccode |= CCODE_START;
+		if (idx == seq->bytecnt - 1)
+			ccode |= CCODE_END;
+
+		/* Send data to the device */
+		sts = idt_smb_safe(write_byte, pdev->client, ccode,
+			seq->data[idx]);
+		if (sts != 0)
+			return (int)sts;
+	}
+
+	return 0;
+}
+
+/*
+ * idt_smb_read_byte() - SMBus read method when I2C_SMBUS_BYTE_DATA operation
+ *                        is only available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Buffer to read data to
+ */
+static int idt_smb_read_byte(struct idt_89hpesx_dev *pdev,
+			     struct idt_smb_seq *seq)
+{
+	s32 sts;
+	u8 ccode;
+	int idx;
+
+	/* Loop over the supplied buffer receiving byte one-by-one */
+	for (idx = 0; idx < seq->bytecnt; idx++) {
+		/* Collect the command code byte */
+		ccode = seq->ccode | CCODE_BYTE;
+		if (idx == 0)
+			ccode |= CCODE_START;
+		if (idx == seq->bytecnt - 1)
+			ccode |= CCODE_END;
+
+		/* Read data from the device */
+		sts = idt_smb_safe(read_byte, pdev->client, ccode);
+		if (sts < 0)
+			return (int)sts;
+
+		seq->data[idx] = (u8)sts;
+	}
+
+	return 0;
+}
+
+/*
+ * idt_smb_write_word() - SMBus write method when I2C_SMBUS_BYTE_DATA and
+ *                        I2C_FUNC_SMBUS_WORD_DATA operations are available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Sequence of data to be written
+ */
+static int idt_smb_write_word(struct idt_89hpesx_dev *pdev,
+			      const struct idt_smb_seq *seq)
+{
+	s32 sts;
+	u8 ccode;
+	int idx, evencnt;
+
+	/* Calculate the even count of data to send */
+	evencnt = seq->bytecnt - (seq->bytecnt % 2);
+
+	/* Loop over the supplied data sending two bytes at a time */
+	for (idx = 0; idx < evencnt; idx += 2) {
+		/* Collect the command code byte */
+		ccode = seq->ccode | CCODE_WORD;
+		if (idx == 0)
+			ccode |= CCODE_START;
+		if (idx == evencnt - 2)
+			ccode |= CCODE_END;
+
+		/* Send word data to the device */
+		sts = idt_smb_safe(write_word, pdev->client, ccode,
+			*(u16 *)&seq->data[idx]);
+		if (sts != 0)
+			return (int)sts;
+	}
+
+	/* If there is odd number of bytes then send just one last byte */
+	if (seq->bytecnt != evencnt) {
+		/* Collect the command code byte */
+		ccode = seq->ccode | CCODE_BYTE | CCODE_END;
+		if (idx == 0)
+			ccode |= CCODE_START;
+
+		/* Send byte data to the device */
+		sts = idt_smb_safe(write_byte, pdev->client, ccode,
+			seq->data[idx]);
+		if (sts != 0)
+			return (int)sts;
+	}
+
+	return 0;
+}
+
+/*
+ * idt_smb_read_word() - SMBus read method when I2C_SMBUS_BYTE_DATA and
+ *                       I2C_FUNC_SMBUS_WORD_DATA operations are available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Buffer to read data to
+ */
+static int idt_smb_read_word(struct idt_89hpesx_dev *pdev,
+			     struct idt_smb_seq *seq)
+{
+	s32 sts;
+	u8 ccode;
+	int idx, evencnt;
+
+	/* Calculate the even count of data to send */
+	evencnt = seq->bytecnt - (seq->bytecnt % 2);
+
+	/* Loop over the supplied data reading two bytes at a time */
+	for (idx = 0; idx < evencnt; idx += 2) {
+		/* Collect the command code byte */
+		ccode = seq->ccode | CCODE_WORD;
+		if (idx == 0)
+			ccode |= CCODE_START;
+		if (idx == evencnt - 2)
+			ccode |= CCODE_END;
+
+		/* Read word data from the device */
+		sts = idt_smb_safe(read_word, pdev->client, ccode);
+		if (sts < 0)
+			return (int)sts;
+
+		*(u16 *)&seq->data[idx] = (u16)sts;
+	}
+
+	/* If there is odd number of bytes then receive just one last byte */
+	if (seq->bytecnt != evencnt) {
+		/* Collect the command code byte */
+		ccode = seq->ccode | CCODE_BYTE | CCODE_END;
+		if (idx == 0)
+			ccode |= CCODE_START;
+
+		/* Read last data byte from the device */
+		sts = idt_smb_safe(read_byte, pdev->client, ccode);
+		if (sts < 0)
+			return (int)sts;
+
+		seq->data[idx] = (u8)sts;
+	}
+
+	return 0;
+}
+
+/*
+ * idt_smb_write_block() - SMBus write method when I2C_SMBUS_BLOCK_DATA
+ *                         operation is available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Sequence of data to be written
+ */
+static int idt_smb_write_block(struct idt_89hpesx_dev *pdev,
+			       const struct idt_smb_seq *seq)
+{
+	u8 ccode;
+
+	/* Return error if too much data passed to send */
+	if (seq->bytecnt > I2C_SMBUS_BLOCK_MAX)
+		return -EINVAL;
+
+	/* Collect the command code byte */
+	ccode = seq->ccode | CCODE_BLOCK | CCODE_START | CCODE_END;
+
+	/* Send block of data to the device */
+	return idt_smb_safe(write_block, pdev->client, ccode, seq->bytecnt,
+		seq->data);
+}
+
+/*
+ * idt_smb_read_block() - SMBus read method when I2C_SMBUS_BLOCK_DATA
+ *                        operation is available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Buffer to read data to
+ */
+static int idt_smb_read_block(struct idt_89hpesx_dev *pdev,
+			      struct idt_smb_seq *seq)
+{
+	s32 sts;
+	u8 ccode;
+
+	/* Return error if too much data passed to send */
+	if (seq->bytecnt > I2C_SMBUS_BLOCK_MAX)
+		return -EINVAL;
+
+	/* Collect the command code byte */
+	ccode = seq->ccode | CCODE_BLOCK | CCODE_START | CCODE_END;
+
+	/* Read block of data from the device */
+	sts = idt_smb_safe(read_block, pdev->client, ccode, seq->data);
+	if (sts != seq->bytecnt)
+		return (sts < 0 ? sts : -ENODATA);
+
+	return 0;
+}
+
+/*
+ * idt_smb_write_i2c_block() - SMBus write method when I2C_SMBUS_I2C_BLOCK_DATA
+ *                             operation is available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Sequence of data to be written
+ *
+ * NOTE It's usual SMBus write block operation, except the actual data length is
+ * sent as first byte of data
+ */
+static int idt_smb_write_i2c_block(struct idt_89hpesx_dev *pdev,
+				   const struct idt_smb_seq *seq)
+{
+	u8 ccode, buf[I2C_SMBUS_BLOCK_MAX + 1];
+
+	/* Return error if too much data passed to send */
+	if (seq->bytecnt > I2C_SMBUS_BLOCK_MAX)
+		return -EINVAL;
+
+	/* Collect the data to send. Length byte must be added prior the data */
+	buf[0] = seq->bytecnt;
+	memcpy(&buf[1], seq->data, seq->bytecnt);
+
+	/* Collect the command code byte */
+	ccode = seq->ccode | CCODE_BLOCK | CCODE_START | CCODE_END;
+
+	/* Send length and block of data to the device */
+	return idt_smb_safe(write_i2c_block, pdev->client, ccode,
+		seq->bytecnt + 1, buf);
+}
+
+/*
+ * idt_smb_read_i2c_block() - SMBus read method when I2C_SMBUS_I2C_BLOCK_DATA
+ *                            operation is available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Buffer to read data to
+ *
+ * NOTE It's usual SMBus read block operation, except the actual data length is
+ * retrieved as first byte of data
+ */
+static int idt_smb_read_i2c_block(struct idt_89hpesx_dev *pdev,
+				  struct idt_smb_seq *seq)
+{
+	u8 ccode, buf[I2C_SMBUS_BLOCK_MAX + 1];
+	s32 sts;
+
+	/* Return error if too much data passed to send */
+	if (seq->bytecnt > I2C_SMBUS_BLOCK_MAX)
+		return -EINVAL;
+
+	/* Collect the command code byte */
+	ccode = seq->ccode | CCODE_BLOCK | CCODE_START | CCODE_END;
+
+	/* Read length and block of data from the device */
+	sts = idt_smb_safe(read_i2c_block, pdev->client, ccode,
+		seq->bytecnt + 1, buf);
+	if (sts != seq->bytecnt + 1)
+		return (sts < 0 ? sts : -ENODATA);
+	if (buf[0] != seq->bytecnt)
+		return -ENODATA;
+
+	/* Copy retrieved data to the output data buffer */
+	memcpy(seq->data, &buf[1], seq->bytecnt);
+
+	return 0;
+}
+
+/*===========================================================================
+ *                          EEPROM IO-operations
+ *===========================================================================
+ */
+
+/*
+ * idt_eeprom_read_byte() - read just one byte from EEPROM
+ * @pdev:	Pointer to the driver data
+ * @memaddr:	Start EEPROM memory address
+ * @data:	Data to be written to EEPROM
+ */
+static int idt_eeprom_read_byte(struct idt_89hpesx_dev *pdev, u16 memaddr,
+				u8 *data)
+{
+	struct device *dev = &pdev->client->dev;
+	struct idt_eeprom_seq eeseq;
+	struct idt_smb_seq smbseq;
+	int ret, retry;
+
+	/* Initialize SMBus sequence fields */
+	smbseq.ccode = pdev->iniccode | CCODE_EEPROM;
+	smbseq.data = (u8 *)&eeseq;
+
+	/*
+	 * Sometimes EEPROM may respond with NACK if it's busy with previous
+	 * operation, so we need to perform a few attempts of read cycle
+	 */
+	retry = RETRY_CNT;
+	do {
+		/* Send EEPROM memory address to read data from */
+		smbseq.bytecnt = EEPROM_WRRD_CNT;
+		eeseq.cmd = pdev->inieecmd | EEPROM_OP_READ;
+		eeseq.eeaddr = pdev->eeaddr;
+		eeseq.memaddr = cpu_to_le16(memaddr);
+		ret = pdev->smb_write(pdev, &smbseq);
+		if (ret != 0) {
+			dev_err(dev, "Failed to init eeprom addr 0x%02hhx",
+				memaddr);
+			break;
+		}
+
+		/* Perform read operation */
+		smbseq.bytecnt = EEPROM_RD_CNT;
+		ret = pdev->smb_read(pdev, &smbseq);
+		if (ret != 0) {
+			dev_err(dev, "Failed to read eeprom data 0x%02hhx",
+				memaddr);
+			break;
+		}
+
+		/* Restart read operation if the device is busy */
+		if (retry && (eeseq.cmd & EEPROM_NAERR)) {
+			dev_dbg(dev, "EEPROM busy, retry reading after %d ms",
+				EEPROM_TOUT);
+			msleep(EEPROM_TOUT);
+			continue;
+		}
+
+		/* Check whether IDT successfully read data from EEPROM */
+		if (eeseq.cmd & (EEPROM_NAERR | EEPROM_LAERR | EEPROM_MSS)) {
+			dev_err(dev,
+				"Communication with eeprom failed, cmd 0x%hhx",
+				eeseq.cmd);
+			ret = -EREMOTEIO;
+			break;
+		}
+
+		/* Save retrieved data and exit the loop */
+		*data = eeseq.data;
+		break;
+	} while (retry--);
+
+	/* Return the status of operation */
+	return ret;
+}
+
+/*
+ * idt_eeprom_write() - EEPROM write operation
+ * @pdev:	Pointer to the driver data
+ * @memaddr:	Start EEPROM memory address
+ * @len:	Length of data to be written
+ * @data:	Data to be written to EEPROM
+ */
+static int idt_eeprom_write(struct idt_89hpesx_dev *pdev, u16 memaddr, u16 len,
+			    const u8 *data)
+{
+	struct device *dev = &pdev->client->dev;
+	struct idt_eeprom_seq eeseq;
+	struct idt_smb_seq smbseq;
+	int ret;
+	u16 idx;
+
+	/* Initialize SMBus sequence fields */
+	smbseq.ccode = pdev->iniccode | CCODE_EEPROM;
+	smbseq.data = (u8 *)&eeseq;
+
+	/* Send data byte-by-byte, checking if it is successfully written */
+	for (idx = 0; idx < len; idx++, memaddr++) {
+		/* Lock IDT SMBus device */
+		mutex_lock(&pdev->smb_mtx);
+
+		/* Perform write operation */
+		smbseq.bytecnt = EEPROM_WR_CNT;
+		eeseq.cmd = pdev->inieecmd | EEPROM_OP_WRITE;
+		eeseq.eeaddr = pdev->eeaddr;
+		eeseq.memaddr = cpu_to_le16(memaddr);
+		eeseq.data = data[idx];
+		ret = pdev->smb_write(pdev, &smbseq);
+		if (ret != 0) {
+			dev_err(dev,
+				"Failed to write 0x%04hx:0x%02hhx to eeprom",
+				memaddr, data[idx]);
+			goto err_mutex_unlock;
+		}
+
+		/*
+		 * Check whether the data is successfully written by reading
+		 * from the same EEPROM memory address.
+		 */
+		eeseq.data = ~data[idx];
+		ret = idt_eeprom_read_byte(pdev, memaddr, &eeseq.data);
+		if (ret != 0)
+			goto err_mutex_unlock;
+
+		/* Check whether the read byte is the same as written one */
+		if (eeseq.data != data[idx]) {
+			dev_err(dev, "Values don't match 0x%02hhx != 0x%02hhx",
+				eeseq.data, data[idx]);
+			ret = -EREMOTEIO;
+			goto err_mutex_unlock;
+		}
+
+		/* Unlock IDT SMBus device */
+err_mutex_unlock:
+		mutex_unlock(&pdev->smb_mtx);
+		if (ret != 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+/*
+ * idt_eeprom_read() - EEPROM read operation
+ * @pdev:	Pointer to the driver data
+ * @memaddr:	Start EEPROM memory address
+ * @len:	Length of data to read
+ * @buf:	Buffer to read data to
+ */
+static int idt_eeprom_read(struct idt_89hpesx_dev *pdev, u16 memaddr, u16 len,
+			   u8 *buf)
+{
+	int ret;
+	u16 idx;
+
+	/* Read data byte-by-byte, retrying if it wasn't successful */
+	for (idx = 0; idx < len; idx++, memaddr++) {
+		/* Lock IDT SMBus device */
+		mutex_lock(&pdev->smb_mtx);
+
+		/* Just read the byte to the buffer */
+		ret = idt_eeprom_read_byte(pdev, memaddr, &buf[idx]);
+
+		/* Unlock IDT SMBus device */
+		mutex_unlock(&pdev->smb_mtx);
+
+		/* Return error if read operation failed */
+		if (ret != 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+/*===========================================================================
+ *                          CSR IO-operations
+ *===========================================================================
+ */
+
+/*
+ * idt_csr_write() - CSR write operation
+ * @pdev:	Pointer to the driver data
+ * @csraddr:	CSR address (with no two LS bits)
+ * @data:	Data to be written to CSR
+ */
+static int idt_csr_write(struct idt_89hpesx_dev *pdev, u16 csraddr,
+			 const u32 data)
+{
+	struct device *dev = &pdev->client->dev;
+	struct idt_csr_seq csrseq;
+	struct idt_smb_seq smbseq;
+	int ret;
+
+	/* Initialize SMBus sequence fields */
+	smbseq.ccode = pdev->iniccode | CCODE_CSR;
+	smbseq.data = (u8 *)&csrseq;
+
+	/* Lock IDT SMBus device */
+	mutex_lock(&pdev->smb_mtx);
+
+	/* Perform write operation */
+	smbseq.bytecnt = CSR_WR_CNT;
+	csrseq.cmd = pdev->inicsrcmd | CSR_OP_WRITE;
+	csrseq.csraddr = cpu_to_le16(csraddr);
+	csrseq.data = cpu_to_le32(data);
+	ret = pdev->smb_write(pdev, &smbseq);
+	if (ret != 0) {
+		dev_err(dev, "Failed to write 0x%04x: 0x%04x to csr",
+			CSR_REAL_ADDR(csraddr), data);
+		goto err_mutex_unlock;
+	}
+
+	/* Send CSR address to read data from */
+	smbseq.bytecnt = CSR_WRRD_CNT;
+	csrseq.cmd = pdev->inicsrcmd | CSR_OP_READ;
+	ret = pdev->smb_write(pdev, &smbseq);
+	if (ret != 0) {
+		dev_err(dev, "Failed to init csr address 0x%04x",
+			CSR_REAL_ADDR(csraddr));
+		goto err_mutex_unlock;
+	}
+
+	/* Perform read operation */
+	smbseq.bytecnt = CSR_RD_CNT;
+	ret = pdev->smb_read(pdev, &smbseq);
+	if (ret != 0) {
+		dev_err(dev, "Failed to read csr 0x%04x",
+			CSR_REAL_ADDR(csraddr));
+		goto err_mutex_unlock;
+	}
+
+	/* Check whether IDT successfully retrieved CSR data */
+	if (csrseq.cmd & (CSR_RERR | CSR_WERR)) {
+		dev_err(dev, "IDT failed to perform CSR r/w");
+		ret = -EREMOTEIO;
+		goto err_mutex_unlock;
+	}
+
+	/* Unlock IDT SMBus device */
+err_mutex_unlock:
+	mutex_unlock(&pdev->smb_mtx);
+
+	return ret;
+}
+
+/*
+ * idt_csr_read() - CSR read operation
+ * @pdev:	Pointer to the driver data
+ * @csraddr:	CSR address (with no two LS bits)
+ * @data:	Data to be written to CSR
+ */
+static int idt_csr_read(struct idt_89hpesx_dev *pdev, u16 csraddr, u32 *data)
+{
+	struct device *dev = &pdev->client->dev;
+	struct idt_csr_seq csrseq;
+	struct idt_smb_seq smbseq;
+	int ret;
+
+	/* Initialize SMBus sequence fields */
+	smbseq.ccode = pdev->iniccode | CCODE_CSR;
+	smbseq.data = (u8 *)&csrseq;
+
+	/* Lock IDT SMBus device */
+	mutex_lock(&pdev->smb_mtx);
+
+	/* Send CSR register address before reading it */
+	smbseq.bytecnt = CSR_WRRD_CNT;
+	csrseq.cmd = pdev->inicsrcmd | CSR_OP_READ;
+	csrseq.csraddr = cpu_to_le16(csraddr);
+	ret = pdev->smb_write(pdev, &smbseq);
+	if (ret != 0) {
+		dev_err(dev, "Failed to init csr address 0x%04x",
+			CSR_REAL_ADDR(csraddr));
+		goto err_mutex_unlock;
+	}
+
+	/* Perform read operation */
+	smbseq.bytecnt = CSR_RD_CNT;
+	ret = pdev->smb_read(pdev, &smbseq);
+	if (ret != 0) {
+		dev_err(dev, "Failed to read csr 0x%04hx",
+			CSR_REAL_ADDR(csraddr));
+		goto err_mutex_unlock;
+	}
+
+	/* Check whether IDT successfully retrieved CSR data */
+	if (csrseq.cmd & (CSR_RERR | CSR_WERR)) {
+		dev_err(dev, "IDT failed to perform CSR r/w");
+		ret = -EREMOTEIO;
+		goto err_mutex_unlock;
+	}
+
+	/* Save data retrieved from IDT */
+	*data = le32_to_cpu(csrseq.data);
+
+	/* Unlock IDT SMBus device */
+err_mutex_unlock:
+	mutex_unlock(&pdev->smb_mtx);
+
+	return ret;
+}
+
+/*===========================================================================
+ *                          Sysfs/debugfs-nodes IO-operations
+ *===========================================================================
+ */
+
+/*
+ * eeprom_write() - EEPROM sysfs-node write callback
+ * @filep:	Pointer to the file system node
+ * @kobj:	Pointer to the kernel object related to the sysfs-node
+ * @attr:	Attributes of the file
+ * @buf:	Buffer to write data to
+ * @off:	Offset at which data should be written to
+ * @count:	Number of bytes to write
+ */
+static ssize_t eeprom_write(struct file *filp, struct kobject *kobj,
+			    struct bin_attribute *attr,
+			    char *buf, loff_t off, size_t count)
+{
+	struct idt_89hpesx_dev *pdev;
+	int ret;
+
+	/* Retrieve driver data */
+	pdev = dev_get_drvdata(kobj_to_dev(kobj));
+
+	/* Perform EEPROM write operation */
+	ret = idt_eeprom_write(pdev, (u16)off, (u16)count, (u8 *)buf);
+	return (ret != 0 ? ret : count);
+}
+
+/*
+ * eeprom_read() - EEPROM sysfs-node read callback
+ * @filep:	Pointer to the file system node
+ * @kobj:	Pointer to the kernel object related to the sysfs-node
+ * @attr:	Attributes of the file
+ * @buf:	Buffer to write data to
+ * @off:	Offset at which data should be written to
+ * @count:	Number of bytes to write
+ */
+static ssize_t eeprom_read(struct file *filp, struct kobject *kobj,
+			   struct bin_attribute *attr,
+			   char *buf, loff_t off, size_t count)
+{
+	struct idt_89hpesx_dev *pdev;
+	int ret;
+
+	/* Retrieve driver data */
+	pdev = dev_get_drvdata(kobj_to_dev(kobj));
+
+	/* Perform EEPROM read operation */
+	ret = idt_eeprom_read(pdev, (u16)off, (u16)count, (u8 *)buf);
+	return (ret != 0 ? ret : count);
+}
+
+/*
+ * idt_dbgfs_csr_write() - CSR debugfs-node write callback
+ * @filep:	Pointer to the file system file descriptor
+ * @buf:	Buffer to read data from
+ * @count:	Size of the buffer
+ * @offp:	Offset within the file
+ *
+ * It accepts either "0x<reg addr>:0x<value>" for saving register address
+ * and writing value to specified DWORD register or "0x<reg addr>" for
+ * just saving register address in order to perform next read operation.
+ *
+ * WARNING No spaces are allowed. Incoming string must be strictly formated as:
+ * "<reg addr>:<value>". Register address must be aligned within 4 bytes
+ * (one DWORD).
+ */
+static ssize_t idt_dbgfs_csr_write(struct file *filep, const char __user *ubuf,
+				   size_t count, loff_t *offp)
+{
+	struct idt_89hpesx_dev *pdev = filep->private_data;
+	char *colon_ch, *csraddr_str, *csrval_str;
+	int ret, csraddr_len, csrval_len;
+	u32 csraddr, csrval;
+	char *buf;
+
+	/* Copy data from User-space */
+	buf = kmalloc(count + 1, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	ret = simple_write_to_buffer(buf, count, offp, ubuf, count);
+	if (ret < 0)
+		goto free_buf;
+	buf[count] = 0;
+
+	/* Find position of colon in the buffer */
+	colon_ch = strnchr(buf, count, ':');
+
+	/*
+	 * If there is colon passed then new CSR value should be parsed as
+	 * well, so allocate buffer for CSR address substring.
+	 * If no colon is found, then string must have just one number with
+	 * no new CSR value
+	 */
+	if (colon_ch != NULL) {
+		csraddr_len = colon_ch - buf;
+		csraddr_str =
+			kmalloc(sizeof(char)*(csraddr_len + 1), GFP_KERNEL);
+		if (csraddr_str == NULL)
+			return -ENOMEM;
+		/* Copy the register address to the substring buffer */
+		strncpy(csraddr_str, buf, csraddr_len);
+		csraddr_str[csraddr_len] = '\0';
+		/* Register value must follow the colon */
+		csrval_str = colon_ch + 1;
+		csrval_len = strnlen(csrval_str, count - csraddr_len);
+	} else /* if (str_colon == NULL) */ {
+		csraddr_str = (char *)buf; /* Just to shut warning up */
+		csraddr_len = strnlen(csraddr_str, count);
+		csrval_str = NULL;
+		csrval_len = 0;
+	}
+
+	/* Convert CSR address to u32 value */
+	ret = kstrtou32(csraddr_str, 0, &csraddr);
+	if (ret != 0)
+		goto free_csraddr_str;
+
+	/* Check whether passed register address is valid */
+	if (csraddr > CSR_MAX || !IS_ALIGNED(csraddr, SZ_4)) {
+		ret = -EINVAL;
+		goto free_csraddr_str;
+	}
+
+	/* Shift register address to the right so to have u16 address */
+	csraddr >>= 2;
+
+	/* Parse new CSR value and send it to IDT, if colon has been found */
+	if (colon_ch != NULL) {
+		ret = kstrtou32(csrval_str, 0, &csrval);
+		if (ret != 0)
+			goto free_csraddr_str;
+
+		ret = idt_csr_write(pdev, (u16)csraddr, csrval);
+		if (ret != 0)
+			goto free_csraddr_str;
+	}
+
+	/* Save CSR address in the data structure for future read operations */
+	atomic_set(&pdev->csr, (int)csraddr);
+
+	/* Free memory only if colon has been found */
+free_csraddr_str:
+	if (colon_ch != NULL)
+		kfree(csraddr_str);
+
+	/* Free buffer allocated for data retrieved from User-space */
+free_buf:
+	kfree(buf);
+
+	return (ret != 0 ? ret : count);
+}
+
+/*
+ * idt_dbgfs_csr_read() - CSR debugfs-node read callback
+ * @filep:	Pointer to the file system file descriptor
+ * @buf:	Buffer to write data to
+ * @count:	Size of the buffer
+ * @offp:	Offset within the file
+ *
+ * It just prints the pair "0x<reg addr>:0x<value>" to passed buffer.
+ */
+#define CSRBUF_SIZE	((size_t)32)
+static ssize_t idt_dbgfs_csr_read(struct file *filep, char __user *ubuf,
+				  size_t count, loff_t *offp)
+{
+	struct idt_89hpesx_dev *pdev = filep->private_data;
+	u32 csraddr, csrval;
+	char buf[CSRBUF_SIZE];
+	int ret, size;
+
+	/* Read current CSR address */
+	csraddr = atomic_read(&pdev->csr);
+
+	/* Perform CSR read operation */
+	ret = idt_csr_read(pdev, (u16)csraddr, &csrval);
+	if (ret != 0)
+		return ret;
+
+	/* Shift register address to the left so to have real address */
+	csraddr <<= 2;
+
+	/* Print the "0x<reg addr>:0x<value>" to buffer */
+	size = snprintf(buf, CSRBUF_SIZE, "0x%05x:0x%08x\n",
+		(unsigned int)csraddr, (unsigned int)csrval);
+
+	/* Copy data to User-space */
+	return simple_read_from_buffer(ubuf, count, offp, buf, size);
+}
+
+/*
+ * eeprom_attribute - EEPROM sysfs-node attributes
+ *
+ * NOTE Size will be changed in compliance with OF node. EEPROM attribute will
+ * be read-only as well if the corresponding flag is specified in OF node.
+ */
+static BIN_ATTR_RW(eeprom, EEPROM_DEF_SIZE);
+
+/*
+ * csr_dbgfs_ops - CSR debugfs-node read/write operations
+ */
+static const struct file_operations csr_dbgfs_ops = {
+	.owner = THIS_MODULE,
+	.open = simple_open,
+	.write = idt_dbgfs_csr_write,
+	.read = idt_dbgfs_csr_read
+};
+
+/*===========================================================================
+ *                       Driver init/deinit methods
+ *===========================================================================
+ */
+
+/*
+ * idt_set_defval() - disable EEPROM access by default
+ * @pdev:	Pointer to the driver data
+ */
+static void idt_set_defval(struct idt_89hpesx_dev *pdev)
+{
+	/* If OF info is missing then use next values */
+	pdev->eesize = 0;
+	pdev->eero = true;
+	pdev->inieecmd = 0;
+	pdev->eeaddr = 0;
+}
+
+#ifdef CONFIG_OF
+static const struct i2c_device_id ee_ids[];
+/*
+ * idt_ee_match_id() - check whether the node belongs to compatible EEPROMs
+ */
+static const struct i2c_device_id *idt_ee_match_id(struct device_node *node)
+{
+	const struct i2c_device_id *id = ee_ids;
+	char devname[I2C_NAME_SIZE];
+
+	/* Retrieve the device name without manufacturer name */
+	if (of_modalias_node(node, devname, sizeof(devname)))
+		return NULL;
+
+	/* Search through the device name */
+        while (id->name[0]) {
+                if (strcmp(devname, id->name) == 0)
+                        return id;
+                id++;
+        }
+        return NULL;
+}
+
+/*
+ * idt_get_ofdata() - get IDT i2c-device parameters from device tree
+ * @pdev:	Pointer to the driver data
+ */
+static void idt_get_ofdata(struct idt_89hpesx_dev *pdev)
+{
+	const struct device_node *node = pdev->client->dev.of_node;
+	struct device *dev = &pdev->client->dev;
+
+	/* Read dts node parameters */
+	if (node) {
+		const struct i2c_device_id *ee_id = NULL;
+		struct device_node *child;
+		const __be32 *addr_be;
+		int len;
+
+		/* Walk through all child nodes looking for compatible one */
+		for_each_available_child_of_node(node, child) {
+			ee_id = idt_ee_match_id(child);
+			if (IS_ERR_OR_NULL(ee_id)) {
+				dev_warn(dev, "Skip unsupported child node %s",
+					child->full_name);
+				continue;
+			} else
+				break;
+		}
+
+		/* If there is no child EEPROM device, then set zero size */
+		if (!ee_id) {
+			idt_set_defval(pdev);
+			return;
+		}
+
+		/* Retrieve EEPROM size */
+		pdev->eesize = (u32)ee_id->driver_data;
+
+		/* Get custom EEPROM address from 'reg' attribute */
+		addr_be = of_get_property(child, "reg", &len);
+		if (!addr_be || (len < sizeof(*addr_be))) {
+			dev_warn(dev, "No reg on %s, use default address %d",
+				child->full_name, EEPROM_DEF_ADDR);
+			pdev->inieecmd = 0;
+			pdev->eeaddr = EEPROM_DEF_ADDR << 1;
+		} else {
+			pdev->inieecmd = EEPROM_USA;
+			pdev->eeaddr = be32_to_cpup(addr_be) << 1;
+		}
+
+		/* Check EEPROM 'read-only' flag */
+		if (of_get_property(child, "read-only", NULL))
+			pdev->eero = true;
+		else /* if (!of_get_property(node, "read-only", NULL)) */
+			pdev->eero = false;
+
+		dev_dbg(dev, "EEPROM of %u bytes found by %hhu",
+			pdev->eesize, pdev->eeaddr);
+	} else {
+		dev_warn(dev, "No dts node, EEPROM access disabled");
+		idt_set_defval(pdev);
+	}
+}
+#else
+static void idt_get_ofdata(struct idt_89hpesx_dev *pdev)
+{
+	struct device *dev = &pdev->client->dev;
+
+	dev_warn(dev, "OF table is unsupported, EEPROM access disabled");
+
+	/* Nothing we can do, just set the default values */
+	idt_set_defval(pdev);
+}
+#endif /* CONFIG_OF */
+
+/*
+ * idt_create_pdev() - create and init data structure of the driver
+ * @client:	i2c client of IDT PCIe-switch device
+ */
+static struct idt_89hpesx_dev *idt_create_pdev(struct i2c_client *client)
+{
+	struct idt_89hpesx_dev *pdev;
+
+	/* Allocate memory for driver data */
+	pdev = devm_kmalloc(&client->dev, sizeof(struct idt_89hpesx_dev),
+		GFP_KERNEL);
+	if (pdev == NULL)
+		return ERR_PTR(-ENOMEM);
+
+	/* Initialize basic fields of the data */
+	pdev->client = client;
+	i2c_set_clientdata(client, pdev);
+
+	/* Read OF nodes information */
+	idt_get_ofdata(pdev);
+
+	/* Initialize basic CSR CMD field - use full DWORD-sized r/w ops */
+	pdev->inicsrcmd = CSR_DWE;
+	atomic_set(&pdev->csr, CSR_DEF);
+
+	/* Enable Packet Error Checking if it's supported by adapter */
+	if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_PEC)) {
+		pdev->iniccode = CCODE_PEC;
+		client->flags |= I2C_CLIENT_PEC;
+	} else /* PEC is unsupported */ {
+		pdev->iniccode = 0;
+	}
+
+	dev_dbg(&client->dev, "IDT 89HPESx data created");
+
+	return pdev;
+}
+
+/*
+ * idt_free_pdev() - free data structure of the driver
+ * @pdev:	Pointer to the driver data
+ */
+static void idt_free_pdev(struct idt_89hpesx_dev *pdev)
+{
+	/* Clear driver data from device private field */
+	i2c_set_clientdata(pdev->client, NULL);
+
+	/* Just free memory allocated for data */
+	devm_kfree(&pdev->client->dev, pdev);
+
+	dev_dbg(&pdev->client->dev, "IDT 89HPESx data discarded");
+}
+
+/*
+ * idt_set_smbus_ops() - set supported SMBus operations
+ * @pdev:	Pointer to the driver data
+ * Return status of smbus check operations
+ */
+static int idt_set_smbus_ops(struct idt_89hpesx_dev *pdev)
+{
+	struct i2c_adapter *adapter = pdev->client->adapter;
+	struct device *dev = &pdev->client->dev;
+
+	/* Check i2c adapter read functionality */
+	if (i2c_check_functionality(adapter,
+				    I2C_FUNC_SMBUS_READ_BLOCK_DATA)) {
+		pdev->smb_read = idt_smb_read_block;
+		dev_dbg(dev, "SMBus block-read op chosen");
+	} else if (i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_READ_I2C_BLOCK)) {
+		pdev->smb_read = idt_smb_read_i2c_block;
+		dev_dbg(dev, "SMBus i2c-block-read op chosen");
+	} else if (i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_READ_WORD_DATA) &&
+		   i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_READ_BYTE_DATA)) {
+		pdev->smb_read = idt_smb_read_word;
+		dev_warn(dev, "Use slow word/byte SMBus read ops");
+	} else if (i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_READ_BYTE_DATA)) {
+		pdev->smb_read = idt_smb_read_byte;
+		dev_warn(dev, "Use slow byte SMBus read op");
+	} else /* no supported smbus read operations */ {
+		dev_err(dev, "No supported SMBus read op");
+		return -EPFNOSUPPORT;
+	}
+
+	/* Check i2c adapter write functionality */
+	if (i2c_check_functionality(adapter,
+				    I2C_FUNC_SMBUS_WRITE_BLOCK_DATA)) {
+		pdev->smb_write = idt_smb_write_block;
+		dev_dbg(dev, "SMBus block-write op chosen");
+	} else if (i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_WRITE_I2C_BLOCK)) {
+		pdev->smb_write = idt_smb_write_i2c_block;
+		dev_dbg(dev, "SMBus i2c-block-write op chosen");
+	} else if (i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_WRITE_WORD_DATA) &&
+		   i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) {
+		pdev->smb_write = idt_smb_write_word;
+		dev_warn(dev, "Use slow word/byte SMBus write op");
+	} else if (i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) {
+		pdev->smb_write = idt_smb_write_byte;
+		dev_warn(dev, "Use slow byte SMBus write op");
+	} else /* no supported smbus write operations */ {
+		dev_err(dev, "No supported SMBus write op");
+		return -EPFNOSUPPORT;
+	}
+
+	/* Initialize IDT SMBus slave interface mutex */
+	mutex_init(&pdev->smb_mtx);
+
+	dev_dbg(dev, "SMBus functionality successfully checked");
+
+	return 0;
+}
+
+/*
+ * idt_check_dev() - check whether it's really IDT 89HPESx device
+ * @pdev:	Pointer to the driver data
+ * Return status of i2c adapter check operation
+ */
+static int idt_check_dev(struct idt_89hpesx_dev *pdev)
+{
+	struct device *dev = &pdev->client->dev;
+	u32 viddid;
+	int ret;
+
+	/* Read VID and DID directly from IDT memory space */
+	ret = idt_csr_read(pdev, IDT_VIDDID_CSR, &viddid);
+	if (ret != 0) {
+		dev_err(dev, "Failed to read VID/DID");
+		return ret;
+	}
+
+	/* Check whether it's IDT device */
+	if ((viddid & IDT_VID_MASK) != PCI_VENDOR_ID_IDT) {
+		dev_err(dev, "Got unsupported VID/DID: 0x%08x", viddid);
+		return -ENODEV;
+	}
+
+	dev_info(dev, "Found IDT 89HPES device VID:0x%04x, DID:0x%04x",
+		(viddid & IDT_VID_MASK), (viddid >> 16));
+
+	return 0;
+}
+
+/*
+ * idt_create_sysfs_files() - create sysfs attribute files
+ * @pdev:	Pointer to the driver data
+ * Return status of operation
+ */
+static int idt_create_sysfs_files(struct idt_89hpesx_dev *pdev)
+{
+	struct device *dev = &pdev->client->dev;
+	int ret;
+
+	/* Don't do anything if EEPROM isn't accessible */
+	if (pdev->eesize == 0) {
+		dev_dbg(dev, "Skip creating sysfs-files");
+		return 0;
+	}
+
+	/* Allocate memory for attribute file */
+	pdev->ee_file = devm_kmalloc(dev, sizeof(*pdev->ee_file), GFP_KERNEL);
+	if (!pdev->ee_file)
+		return -ENOMEM;
+
+	/* Copy the declared EEPROM attr structure to change some of fields */
+	memcpy(pdev->ee_file, &bin_attr_eeprom, sizeof(*pdev->ee_file));
+
+	/* In case of read-only EEPROM get rid of write ability */
+	if (pdev->eero) {
+		pdev->ee_file->attr.mode &= ~0200;
+		pdev->ee_file->write = NULL;
+	}
+	/* Create EEPROM sysfs file */
+	pdev->ee_file->size = pdev->eesize;
+	ret = sysfs_create_bin_file(&dev->kobj, pdev->ee_file);
+	if (ret != 0) {
+		kfree(pdev->ee_file);
+		dev_err(dev, "Failed to create EEPROM sysfs-node");
+		return ret;
+	}
+
+	dev_dbg(dev, "Sysfs-files created");
+
+	return 0;
+}
+
+/*
+ * idt_remove_sysfs_files() - remove sysfs attribute files
+ * @pdev:	Pointer to the driver data
+ */
+static void idt_remove_sysfs_files(struct idt_89hpesx_dev *pdev)
+{
+	struct device *dev = &pdev->client->dev;
+
+	/* Don't do anything if EEPROM wasn't accessible */
+	if (pdev->eesize == 0)
+		return;
+
+	/* Remove EEPROM sysfs file */
+	sysfs_remove_bin_file(&dev->kobj, pdev->ee_file);
+
+	/* Free memory allocated for bin_attribute structure */
+	kfree(pdev->ee_file);
+
+	dev_dbg(dev, "Sysfs-files removed");
+}
+
+/*
+ * idt_create_dbgfs_files() - create debugfs files
+ * @pdev:	Pointer to the driver data
+ * Return status of operation
+ */
+#define CSRNAME_LEN	((size_t)32)
+static int idt_create_dbgfs_files(struct idt_89hpesx_dev *pdev)
+{
+	struct device *dev = &pdev->client->dev;
+	struct i2c_client *cli = pdev->client;
+	char fname[CSRNAME_LEN];
+
+	/* Initialize basic value of CSR debugfs dentries */
+	pdev->csr_dir = NULL;
+	pdev->csr_file = NULL;
+
+	/* Return failure if root directory doesn't exist */
+	if (!csr_dbgdir) {
+		dev_dbg(dev, "No Debugfs root directory");
+		return -EINVAL;
+	}
+
+	/* Create Debugfs directory for CSR file */
+	snprintf(fname, CSRNAME_LEN, "%d-%04hx", cli->adapter->nr, cli->addr);
+	pdev->csr_dir = debugfs_create_dir(fname, csr_dbgdir);
+	if (IS_ERR_OR_NULL(pdev->csr_dir)) {
+		dev_err(dev, "Failed to create CSR node directory");
+		return -EINVAL;
+	}
+
+	/* Create Debugfs file for CSR read/write operations */
+	pdev->csr_file = debugfs_create_file(cli->name, 0600,
+		pdev->csr_dir, pdev, &csr_dbgfs_ops);
+	if (IS_ERR_OR_NULL(pdev->csr_file)) {
+		dev_err(dev, "Failed to create CSR dbgfs-node");
+		debugfs_remove_recursive(pdev->csr_dir);
+		return -EINVAL;
+	}
+
+	dev_dbg(dev, "Debugfs-files created");
+
+	return 0;
+}
+
+/*
+ * idt_remove_dbgfs_files() - remove debugfs files
+ * @pdev:	Pointer to the driver data
+ */
+static void idt_remove_dbgfs_files(struct idt_89hpesx_dev *pdev)
+{
+	/* Remove CSR directory and it sysfs-node */
+	debugfs_remove_recursive(pdev->csr_dir);
+
+	dev_dbg(&pdev->client->dev, "Debugfs-files removed");
+}
+
+/*
+ * idt_probe() - IDT 89HPESx driver probe() callback method
+ */
+static int idt_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+	struct idt_89hpesx_dev *pdev;
+	int ret;
+
+	/* Create driver data */
+	pdev = idt_create_pdev(client);
+	if (IS_ERR(pdev))
+		return PTR_ERR(pdev);
+
+	/* Set SMBus operations */
+	ret = idt_set_smbus_ops(pdev);
+	if (ret != 0)
+		goto err_free_pdev;
+
+	/* Check whether it is truly IDT 89HPESx device */
+	ret = idt_check_dev(pdev);
+	if (ret != 0)
+		goto err_free_pdev;
+
+	/* Create sysfs files */
+	ret = idt_create_sysfs_files(pdev);
+	if (ret != 0)
+		goto err_free_pdev;
+
+	/* Create debugfs files */
+	(void)idt_create_dbgfs_files(pdev);
+
+	dev_dbg(&client->dev, "IDT %s device probed", id->name);
+
+	return 0;
+
+err_free_pdev:
+	idt_free_pdev(pdev);
+
+	return ret;
+}
+
+/*
+ * idt_remove() - IDT 89HPESx driver remove() callback method
+ */
+static int idt_remove(struct i2c_client *client)
+{
+	struct idt_89hpesx_dev *pdev = i2c_get_clientdata(client);
+
+	/* Remove debugfs files first */
+	idt_remove_dbgfs_files(pdev);
+
+	/* Remove sysfs files */
+	idt_remove_sysfs_files(pdev);
+
+	/* Discard driver data structure */
+	idt_free_pdev(pdev);
+
+	dev_dbg(&client->dev, "IDT 89HPESx device removed");
+
+	return 0;
+}
+
+/*
+ * ee_ids - array of supported EEPROMs
+ */
+static const struct i2c_device_id ee_ids[] = {
+	{ "24c32",  4096},
+	{ "24c64",  8192},
+	{ "24c128", 16384},
+	{ "24c256", 32768},
+	{ "24c512", 65536},
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, ee_ids);
+
+/*
+ * idt_ids - supported IDT 89HPESx devices
+ */
+static const struct i2c_device_id idt_ids[] = {
+	{ "89hpes8nt2", 0 },
+	{ "89hpes12nt3", 0 },
+
+	{ "89hpes24nt6ag2", 0 },
+	{ "89hpes32nt8ag2", 0 },
+	{ "89hpes32nt8bg2", 0 },
+	{ "89hpes12nt12g2", 0 },
+	{ "89hpes16nt16g2", 0 },
+	{ "89hpes24nt24g2", 0 },
+	{ "89hpes32nt24ag2", 0 },
+	{ "89hpes32nt24bg2", 0 },
+
+	{ "89hpes12n3", 0 },
+	{ "89hpes12n3a", 0 },
+	{ "89hpes24n3", 0 },
+	{ "89hpes24n3a", 0 },
+
+	{ "89hpes32h8", 0 },
+	{ "89hpes32h8g2", 0 },
+	{ "89hpes48h12", 0 },
+	{ "89hpes48h12g2", 0 },
+	{ "89hpes48h12ag2", 0 },
+	{ "89hpes16h16", 0 },
+	{ "89hpes22h16", 0 },
+	{ "89hpes22h16g2", 0 },
+	{ "89hpes34h16", 0 },
+	{ "89hpes34h16g2", 0 },
+	{ "89hpes64h16", 0 },
+	{ "89hpes64h16g2", 0 },
+	{ "89hpes64h16ag2", 0 },
+
+	/* { "89hpes3t3", 0 }, // No SMBus-slave iface */
+	{ "89hpes12t3g2", 0 },
+	{ "89hpes24t3g2", 0 },
+	/* { "89hpes4t4", 0 }, // No SMBus-slave iface */
+	{ "89hpes16t4", 0 },
+	{ "89hpes4t4g2", 0 },
+	{ "89hpes10t4g2", 0 },
+	{ "89hpes16t4g2", 0 },
+	{ "89hpes16t4ag2", 0 },
+	{ "89hpes5t5", 0 },
+	{ "89hpes6t5", 0 },
+	{ "89hpes8t5", 0 },
+	{ "89hpes8t5a", 0 },
+	{ "89hpes24t6", 0 },
+	{ "89hpes6t6g2", 0 },
+	{ "89hpes24t6g2", 0 },
+	{ "89hpes16t7", 0 },
+	{ "89hpes32t8", 0 },
+	{ "89hpes32t8g2", 0 },
+	{ "89hpes48t12", 0 },
+	{ "89hpes48t12g2", 0 },
+	{ /* END OF LIST */ }
+};
+MODULE_DEVICE_TABLE(i2c, idt_ids);
+
+/*
+ * idt_driver - IDT 89HPESx driver structure
+ */
+static struct i2c_driver idt_driver = {
+	.driver = {
+		.name = IDT_NAME,
+		.owner = THIS_MODULE,
+	},
+	.probe = idt_probe,
+	.remove = idt_remove,
+	.id_table = idt_ids,
+};
+
+/*
+ * idt_init() - IDT 89HPESx driver init() callback method
+ */
+static int __init idt_init(void)
+{
+	/* Create Debugfs directory first */
+	if (debugfs_initialized())
+		csr_dbgdir = debugfs_create_dir("idt_csr", NULL);
+
+	/* Add new i2c-device driver */
+	return i2c_add_driver(&idt_driver);
+}
+module_init(idt_init);
+
+/*
+ * idt_exit() - IDT 89HPESx driver exit() callback method
+ */
+static void __exit idt_exit(void)
+{
+	/* Discard debugfs directory and all files if any */
+	debugfs_remove_recursive(csr_dbgdir);
+
+	/* Unregister i2c-device driver */
+	i2c_del_driver(&idt_driver);
+}
+module_exit(idt_exit);
-- 
2.6.6

^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox