Devicetree
 help / color / mirror / Atom feed
* Re: [PATCH 1/7] Documentation: dt: net: Update the ath9k binding for SoC devices
From: Andrew Lunn @ 2017-03-28 15:18 UTC (permalink / raw)
  To: Christian Lamparter
  Cc: Alban, QCA ath9k Development, John Crispin, Kalle Valo,
	Rob Herring, Mark Rutland, linux-wireless-u79uwXL29TY76Z2rM5mHXA,
	netdev-u79uwXL29TY76Z2rM5mHXA, devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA, m
In-Reply-To: <2357006.NGKQYCh4BG@debian64>

> But LEDE/OpenWRT rely on the firmware loading API more than ever and 
> currently there is not a replacement for it.

....


> I looked into 10-ath9k-eeprom [0] of LEDE's AR71XX target and I noticed
> that quite a few devices patch the MACs of the wifi.
> If you look at the code for the Airtight C-55 and C-60, Meraki MR18,
> Meraki Z1, you'll notice that each one has to add a fixed value (+1,
> +2, ...) to the extraced MAC-Address. So how would you replicate this,
> with "nvmem-cell-names = address" without some sort of 
> nvmem-provider-processor?

...
 
> https://github.com/lede-project/source/blob/master/target/linux/ar71xx/files/arch/mips/ath79/dev-eth.c#L1204
> 
> and grep lists the following devices:
> mach-dgl-5500-a1.c, mach-dhp-1565-a1.c, mach-dir-505-a1.c, mach-dir-615-c1.c
> mach-dir-615-i1.c, mach-dir-825-b1.c, mach-dir-825-c1.c, mach-tew-673gru.c
> mach-tew-712br.c, mach-tew-732br.c, mach-tew-823dru.c

I would say a big part of the problem is that all of these use cases
are outside of mainline. Why should mainline support something which
is not actually used in mainline.

So i would suggest your first step is to bring some of these devices
into mainline. Once in mainline, it becomes a mainline issue, and
people will help get it solved.

       Andrew
--
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

* Re: [PATCH] clk: stm32h7: Add stm32h743 clock driver
From: Rob Herring @ 2017-03-28 15:19 UTC (permalink / raw)
  To: Gabriel Fernandez
  Cc: Mark Rutland, Russell King, Maxime Coquelin, Alexandre Torgue,
	Michael Turquette, Stephen Boyd, Nicolas Pitre, Arnd Bergmann,
	Daniel Thompson, Andrea Merello, Radosław Pietrzyk,
	Lee Jones, devicetree@vger.kernel.org,
	linux-arm-kernel@lists.infradead.org,
	linux-kernel@vger.kernel.org, linux-clk
In-Reply-To: <461b2321-c43c-6d88-c805-97a887cc431d@st.com>

On Tue, Mar 28, 2017 at 1:20 AM, Gabriel Fernandez
<gabriel.fernandez@st.com> wrote:
>
>
> On 03/27/2017 09:04 PM, Rob Herring wrote:
>>
>> On Fri, Mar 24, 2017 at 4:41 AM, Gabriel Fernandez
>> <gabriel.fernandez@st.com> wrote:
>>>
>>> Hi Rob,
>>>
>>> Thanks for reviewing
>>>
>>>
>>>
>>> On 03/24/2017 03:06 AM, Rob Herring wrote:
>>>>
>>>> On Wed, Mar 15, 2017 at 10:23:30AM +0100, gabriel.fernandez@st.com
>>>> wrote:
>>>>>
>>>>> From: Gabriel Fernandez <gabriel.fernandez@st.com>
>>>>>
>>>>> This patch enables clocks for STM32H743 boards.
>>>>>
>>>>> Signed-off-by: Gabriel Fernandez <gabriel.fernandez@st.com>
>>>>> ---
>>>>>    .../devicetree/bindings/clock/st,stm32h7-rcc.txt   |  152 ++
>>>>>    drivers/clk/Makefile                               |    1 +
>>>>>    drivers/clk/clk-stm32h7.c                          | 1586
>>>>> ++++++++++++++++++++
>>>>>    include/dt-bindings/clock/stm32h7-clks.h           |  165 ++
>>>>>    include/dt-bindings/mfd/stm32h7-rcc.h              |  138 ++
>>>>>    5 files changed, 2042 insertions(+)
>>>>>    create mode 100644
>>>>> Documentation/devicetree/bindings/clock/st,stm32h7-rcc.txt
>>>>>    create mode 100644 drivers/clk/clk-stm32h7.c
>>>>>    create mode 100644 include/dt-bindings/clock/stm32h7-clks.h
>>>>>    create mode 100644 include/dt-bindings/mfd/stm32h7-rcc.h
>>>>>
>>>>> diff --git a/Documentation/devicetree/bindings/clock/st,stm32h7-rcc.txt
>>>>> b/Documentation/devicetree/bindings/clock/st,stm32h7-rcc.txt
>>>>> new file mode 100644
>>>>> index 0000000..9d4b587
>>>>> --- /dev/null
>>>>> +++ b/Documentation/devicetree/bindings/clock/st,stm32h7-rcc.txt
>>>>> @@ -0,0 +1,152 @@
>>>>> +STMicroelectronics STM32H7 Reset and Clock Controller
>>>>> +=====================================================
>>>>> +
>>>>> +The RCC IP is both a reset and a clock controller.
>>>>> +
>>>>> +Please refer to clock-bindings.txt for common clock controller binding
>>>>> usage.
>>>>> +Please also refer to reset.txt for common reset controller binding
>>>>> usage.
>>>>> +
>>>>> +Required properties:
>>>>> +- compatible: Should be:
>>>>> +  "st,stm32h743-rcc"
>>>>> +
>>>>> +- reg: should be register base and length as documented in the
>>>>> +  datasheet
>>>>> +
>>>>> +- #reset-cells: 1, see below
>>>>> +
>>>>> +- #clock-cells : from common clock binding; shall be set to 1
>>>>> +
>>>>> +- clocks: External oscillator clock phandle
>>>>> +  - high speed external clock signal (HSE)
>>>>> +  - low speed external clock signal (LSE)
>>>>> +  - external I2S clock (I2S_CKIN)
>>>>> +
>>>>> +- st,syscfg: phandle for pwrcfg, mandatory to disable/enable backup
>>>>> domain
>>>>> +  write protection (RTC clock).
>>>>> +
>>>>> +- pll x node: Allow to register a pll with specific parameters.
>>>>> +  Please see PLL section below.
>>>>> +
>>>>> +Example:
>>>>> +
>>>>> +       rcc: rcc@58024400 {
>>>>> +               #reset-cells = <1>;
>>>>> +               #clock-cells = <2>
>>>>> +               compatible = "st,stm32h743-rcc", "st,stm32-rcc";
>>>>> +               reg = <0x58024400 0x400>;
>>>>> +               clocks = <&clk_hse>, <&clk_lse>, <&clk_i2s_ckin>;
>>>>> +
>>>>> +               st,syscfg = <&pwrcfg>;
>>>>> +
>>>>> +               #address-cells = <1>;
>>>>> +               #size-cells = <0>;
>>>>> +
>>>>> +               vco1@58024430 {
>>>>> +                       #clock-cells = <0>;
>>>>> +                       compatible = "stm32,pll";
>>>>> +                       reg = <0>;
>>>>
>>>> The reg addr value and unit address don't match.
>>>
>>> ok il will change into
>>>
>>>          vco1@0 {
>>>                  reg = <0>;
>>>
>>>
>>>>> +               };
>>>>> +
>>>>> +               vco2@58024438 {
>>>>> +                       #clock-cells = <0>;
>>>>> +                       compatible = "stm32,pll";
>>>>> +                       reg = <1>;
>>>>> +                       st,clock-div = <2>;
>>>>> +                       st,clock-mult = <40>;
>>>>> +                       st,frac-status = <0>;
>>>>> +                       st,frac = <0>;
>>>>> +                       st,vcosel = <1>;
>>>>> +                       st,pllrge = <2>;
>>>>> +               };
>>>>> +       };
>>>>> +
>>>>> +
>>>>> +STM32H7 PLL
>>>>> +-----------
>>>>> +
>>>>> +The VCO of STM32 PLL could be reprensented like this:
>>>>> +
>>>>> +  Vref    ---------       --------
>>>>> +    ---->| / DIVM  |---->| x DIVN | ------> VCO
>>>>> +          ---------       --------
>>>>> +                            ^
>>>>> +                            |
>>>>> +                         -------
>>>>> +                        | FRACN |
>>>>> +                         -------
>>>>> +
>>>>> +When the PLL is configured in integer mode:
>>>>> +- VCO = ( Vref / DIVM ) * DIVN
>>>>> +
>>>>> +When the PLL is configured in fractional mode:
>>>>> +- VCO = ( Vref / DIVM ) * ( DIVN + FRACN / 2^13)
>>>>> +
>>>>> +
>>>>> +Required properties for pll node:
>>>>> +- compatible: Should be:
>>>>> +  "stm32,pll"
>>>>
>>>> Only 1 single PLL design for all STM32 chips ever?
>>>
>>> no, i can change into "stm32h7,pll"
>>
>> Actually, should be "st,stm32h7-pll".
>
> ok
>
>>>>> +
>>>>> +- #clock-cells: from common clock binding; shall be set to 0
>>>>> +- reg: Should be the pll number.
>>>>> +
>>>>> +Optional properties:
>>>>> +- st,clock-div:  DIVM division factor       : <1..63>
>>>>> +- st,clock-mult: DIVN multiplication factor : <4..512>
>>>>> +
>>>>> +- st,frac-status:
>>>>> +   - 0 Pll is configured in integer mode
>>>>> +   - 1 Pll is configure in fractional mode
>>>>
>>>> Isn't this implied by the presence of the next property?
>>>
>>> do you prefer this ?
>>>
>>> - st,frac :
>>> 0 : pll is configured in integer mode
>>> 1..8191 : Fractional part of the multiplication factor and pll is
>>> configured
>>> in fractional mode
>>
>> Why not no st,frac property means integer mode?
>
> No
>
> no st,frac property means no change (keep default values from bootloader)

Okay, then 0 for integer mode is fine. Also state what the default means.

Rob

^ permalink raw reply

* [PATCH v8 1/2] dt-bindings: Add GPCv2 power gating driver
From: Andrey Smirnov @ 2017-03-28 15:19 UTC (permalink / raw)
  To: Shawn Guo
  Cc: Andrey Smirnov, yurovsky, Lucas Stach, Rob Herring, Mark Rutland,
	Fabio Estevam, Dong Aisheng, devicetree, linux-arm-kernel,
	linux-kernel
In-Reply-To: <20170328151945.32665-1-andrew.smirnov@gmail.com>

Add DT bindings for power domain driver for GPCv2 IP block found in
i.MX7 SoCs.

Cc: yurovsky@gmail.com
Cc: Lucas Stach <l.stach@pengutronix.de>
Cc: Rob Herring <robh+dt@kernel.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Fabio Estevam <fabio.estevam@nxp.com>
Cc: Dong Aisheng <dongas86@gmail.com>
Cc: devicetree@vger.kernel.org
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-kernel@vger.kernel.org
Acked-by: Rob Herring <robh@kernel.org>
Signed-off-by: Andrey Smirnov <andrew.smirnov@gmail.com>
---
 .../devicetree/bindings/power/fsl,imx-gpcv2.txt    | 71 ++++++++++++++++++++++
 include/dt-bindings/power/imx7-power.h             | 16 +++++
 2 files changed, 87 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/power/fsl,imx-gpcv2.txt
 create mode 100644 include/dt-bindings/power/imx7-power.h

diff --git a/Documentation/devicetree/bindings/power/fsl,imx-gpcv2.txt b/Documentation/devicetree/bindings/power/fsl,imx-gpcv2.txt
new file mode 100644
index 0000000..02f45c6
--- /dev/null
+++ b/Documentation/devicetree/bindings/power/fsl,imx-gpcv2.txt
@@ -0,0 +1,71 @@
+Freescale i.MX General Power Controller v2
+==========================================
+
+The i.MX7S/D General Power Control (GPC) block contains Power Gating
+Control (PGC) for various power domains.
+
+Required properties:
+
+- compatible: Should be "fsl,imx7d-gpc"
+
+- reg: should be register base and length as documented in the
+  datasheet
+
+- interrupts: Should contain GPC interrupt request 1
+
+Power domains contained within GPC node are generic power domain
+providers, documented in
+Documentation/devicetree/bindings/power/power_domain.txt, which are
+described as subnodes of the power gating controller 'pgc' node,
+which, in turn, is expected to contain the following:
+
+Required properties:
+
+- reg: Power domain index. Valid values are defined in
+  include/dt-bindings/power/imx7-power.h
+
+- #power-domain-cells: Should be 0
+
+Optional properties:
+
+- power-supply: Power supply used to power the domain
+
+Example:
+
+	gpc: gpc@303a0000 {
+		compatible = "fsl,imx7d-gpc";
+		reg = <0x303a0000 0x1000>;
+		interrupt-controller;
+		interrupts = <GIC_SPI 87 IRQ_TYPE_LEVEL_HIGH>;
+		#interrupt-cells = <3>;
+		interrupt-parent = <&intc>;
+
+		pgc {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			pgc_pcie_phy: power-domain@3 {
+				#power-domain-cells = <0>;
+
+				reg = <IMX7_POWER_DOMAIN_PCIE_PHY>;
+				power-supply = <&reg_1p0d>;
+			};
+		};
+	};
+
+
+Specifying power domain for IP modules
+======================================
+
+IP cores belonging to a power domain should contain a 'power-domains'
+property that is a phandle for PGC node representing the domain.
+
+Example of a device that is part of the PCIE_PHY power domain:
+
+	pcie: pcie@33800000 {
+	      reg = <0x33800000 0x4000>,
+	            <0x4ff00000 0x80000>;
+		/* ... */
+		power-domains = <&pgc_pcie_phy>;
+		/* ... */
+	};
diff --git a/include/dt-bindings/power/imx7-power.h b/include/dt-bindings/power/imx7-power.h
new file mode 100644
index 0000000..3a181e4
--- /dev/null
+++ b/include/dt-bindings/power/imx7-power.h
@@ -0,0 +1,16 @@
+/*
+ *  Copyright (C) 2017 Impinj
+ *
+ * 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 __DT_BINDINGS_IMX7_POWER_H__
+#define __DT_BINDINGS_IMX7_POWER_H__
+
+#define IMX7_POWER_DOMAIN_MIPI_PHY		0
+#define IMX7_POWER_DOMAIN_PCIE_PHY		1
+#define IMX7_POWER_DOMAIN_USB_HSIC_PHY		2
+
+#endif
-- 
2.9.3

^ permalink raw reply related

* Re: [PATCH] arm64: tegra: Add CPU and PSCI nodes for NVIDIA Tegra210 platforms
From: Jon Hunter @ 2017-03-28 15:32 UTC (permalink / raw)
  To: Sudeep Holla, Thierry Reding, Stephen Warren
  Cc: devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-tegra-u79uwXL29TY76Z2rM5mHXA, Martin Michlmayr,
	Vagrant Cascadian
In-Reply-To: <bf527c4b-f17c-bfcf-e7fa-dab64b131996-5wv7dgnIgG8@public.gmane.org>


On 28/03/17 15:55, Sudeep Holla wrote:
> On 28/03/17 12:48, Jon Hunter wrote:
>> Add the CPU and PSCI nodes for the NVIDIA Tegra210 platforms so that
>> all CPUs can be enabled on boot. This assumes that the PSCI firmware
>> has been loaded during the initial bootstrap on the device before the
>> kernel starts (which is typically the case for these platforms). The
>> PSCI firmware version is set to v0.2 which aligns with the current
>> shipping version for Tegra.
>>
>> Reported-by: Martin Michlmayr <tbm-R+vWnYXSFMfQT0dZR+AlfA@public.gmane.org>
>> Signed-off-by: Jon Hunter <jonathanh-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>
>> ---
>>  arch/arm64/boot/dts/nvidia/tegra210-p2180.dtsi | 23 +++++++++++++++++++++++
>>  arch/arm64/boot/dts/nvidia/tegra210-p2530.dtsi | 24 ++++++++++++++++++++++++
>>  2 files changed, 47 insertions(+)
>>
>> diff --git a/arch/arm64/boot/dts/nvidia/tegra210-p2180.dtsi b/arch/arm64/boot/dts/nvidia/tegra210-p2180.dtsi
>> index 906fb836d241..de1696c28140 100644
>> --- a/arch/arm64/boot/dts/nvidia/tegra210-p2180.dtsi
>> +++ b/arch/arm64/boot/dts/nvidia/tegra210-p2180.dtsi
>> @@ -296,6 +296,29 @@
>>  		};
>>  	};
>>  
>> +	cpus {
>> +		cpu@0 {
>> +			enable-method = "psci";
> 
> Is some firmware expected to populate other required properties ?
> 
> If yes, why not populate everything in the firmware ?
> 
> If no, how does it even boot secondaries without the "reg" property
> that usually contains MPIDR.

Please see 'arch/arm64/boot/dts/nvidia/tegra210.dtsi' which has these
fields. So far we have been populating the psci information at the board
level and so I have kept with that here and so the above is just the
delta. Maybe the $subject could be a little clearer!

Jon

-- 
nvpublic
--
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 v3] ASoC: Add support for Maxim Integrated MAX98927 Amplifier
From: Ryan Lee @ 2017-03-28 15:32 UTC (permalink / raw)
  To: lgirdwood, broonie, robh+dt, mark.rutland, perex, tiwai,
	kuninori.morimoto.gx, arnd, ckeepax, lars, bardliao, nh6z, KCHSU0,
	axel.lin, romain.perier, srinivas.kandagatla, oder_chiou,
	Paul.Handrigan, ryans.lee, alsa-devel, devicetree, linux-kernel
  Cc: ryan.lee.maxim, dgreid
In-Reply-To: <20170324152613.qzvsviznhiz5f3sj@rob-hp-laptop>

Signed-off-by: Ryan Lee <ryans.lee@maximintegrated.com>
---
v2: Removed local register read/write function to avoid duplication of ASoC core function.
v3: Combined MAX98926 and MAX98927 binding. Kept existing property name.
 .../devicetree/bindings/sound/max98925.txt         |  22 -
 .../devicetree/bindings/sound/max98926.txt         |  32 -
 .../devicetree/bindings/sound/max9892x.txt         |  41 +
 sound/soc/codecs/Kconfig                           |   5 +
 sound/soc/codecs/Makefile                          |   2 +
 sound/soc/codecs/max98927.c                        | 826 +++++++++++++++++++++
 sound/soc/codecs/max98927.h                        | 267 +++++++
 7 files changed, 1141 insertions(+), 54 deletions(-)
 delete mode 100644 Documentation/devicetree/bindings/sound/max98925.txt
 delete mode 100644 Documentation/devicetree/bindings/sound/max98926.txt
 create mode 100755 Documentation/devicetree/bindings/sound/max9892x.txt
 mode change 100644 => 100755 sound/soc/codecs/Kconfig
 mode change 100644 => 100755 sound/soc/codecs/Makefile
 create mode 100755 sound/soc/codecs/max98927.c
 create mode 100755 sound/soc/codecs/max98927.h

diff --git a/Documentation/devicetree/bindings/sound/max98925.txt b/Documentation/devicetree/bindings/sound/max98925.txt
deleted file mode 100644
index 27be63e..0000000
--- a/Documentation/devicetree/bindings/sound/max98925.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-max98925 audio CODEC
-
-This device supports I2C.
-
-Required properties:
-
-  - compatible : "maxim,max98925"
-
-  - vmon-slot-no : slot number used to send voltage information
-
-  - imon-slot-no : slot number used to send current information
-
-  - reg : the I2C address of the device for I2C
-
-Example:
-
-codec: max98925@1a {
-	compatible = "maxim,max98925";
-	vmon-slot-no = <0>;
-	imon-slot-no = <2>;
-	reg = <0x1a>;
-};
diff --git a/Documentation/devicetree/bindings/sound/max98926.txt b/Documentation/devicetree/bindings/sound/max98926.txt
deleted file mode 100644
index 0b7f4e4..0000000
--- a/Documentation/devicetree/bindings/sound/max98926.txt
+++ /dev/null
@@ -1,32 +0,0 @@
-max98926 audio CODEC
-
-This device supports I2C.
-
-Required properties:
-
-  - compatible : "maxim,max98926"
-
-  - vmon-slot-no : slot number used to send voltage information
-                   or in inteleave mode this will be used as
-                   interleave slot.
-
-  - imon-slot-no : slot number used to send current information
-
-  - interleave-mode : When using two MAX98926 in a system it is
-                      possible to create ADC data that that will
-                      overflow the frame size. Digital Audio Interleave
-                      mode provides a means to output VMON and IMON data
-                      from two devices on a single DOUT line when running
-                      smaller frames sizes such as 32 BCLKS per LRCLK or
-                      48 BCLKS per LRCLK.
-
-  - reg : the I2C address of the device for I2C
-
-Example:
-
-codec: max98926@1a {
-   compatible = "maxim,max98926";
-   vmon-slot-no = <0>;
-   imon-slot-no = <2>;
-   reg = <0x1a>;
-};
diff --git a/Documentation/devicetree/bindings/sound/max9892x.txt b/Documentation/devicetree/bindings/sound/max9892x.txt
new file mode 100755
index 0000000..f617159
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/max9892x.txt
@@ -0,0 +1,41 @@
+Maxim Integrated MAX98925/MAX98926/MAX98927 Speaker Amplifier
+
+This device supports I2C.
+
+Required properties:
+
+  - compatible : should be one of the following
+    - "maxim,max98925"
+    - "maxim,max98926"
+    - "maxim,max98927"
+
+  - vmon-slot-no : slot number used to send voltage information
+                   or in inteleave mode this will be used as
+                   interleave slot.
+                   MAX98925/MAX98926 slot range : 0 ~ 30,  Default : 0
+                   MAX98927 slot range : 0 ~ 15,  Default : 0
+
+  - imon-slot-no : slot number used to send current information
+                   MAX98925/MAX98926 slot range : 0 ~ 30,  Default : 0
+                   MAX98927 slot range : 0 ~ 15,  Default : 0
+
+  - interleave-mode : When using two MAX9892X in a system it is
+                   possible to create ADC data that that will
+                   overflow the frame size. Digital Audio Interleave
+                   mode provides a means to output VMON and IMON data
+                   from two devices on a single DOUT line when running
+                   smaller frames sizes such as 32 BCLKS per LRCLK or
+                   48 BCLKS per LRCLK.
+                   Range : 0 (off), 1 (on),  Default : 0
+
+  - reg : the I2C address of the device for I2C
+
+Example:
+
+codec: max98927@3a {
+   compatible = "maxim,max98927";
+   vmon-slot-no = <0>;
+   imon-slot-no = <1>;
+   interleave-mode = <0>;
+   reg = <0x3a>;
+};
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
old mode 100644
new mode 100755
index e49e9da..cbdc8aa
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -89,6 +89,7 @@ config SND_SOC_ALL_CODECS
 	select SND_SOC_MAX9867 if I2C
 	select SND_SOC_MAX98925 if I2C
 	select SND_SOC_MAX98926 if I2C
+	select SND_SOC_MAX98927 if I2C
 	select SND_SOC_MAX9850 if I2C
 	select SND_SOC_MAX9860 if I2C
 	select SND_SOC_MAX9768 if I2C
@@ -588,6 +589,10 @@ config SND_SOC_MAX98925
 config SND_SOC_MAX98926
 	tristate
 
+config SND_SOC_MAX98927
+	tristate "Maxim Integrated MAX98927 Speaker Amplifier"
+	depends on I2C
+
 config SND_SOC_MAX9850
 	tristate
 
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
old mode 100644
new mode 100755
index 1796cb9..187a639
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -84,6 +84,7 @@ snd-soc-max98371-objs := max98371.o
 snd-soc-max9867-objs := max9867.o
 snd-soc-max98925-objs := max98925.o
 snd-soc-max98926-objs := max98926.o
+snd-soc-max98927-objs := max98927.o
 snd-soc-max9850-objs := max9850.o
 snd-soc-max9860-objs := max9860.o
 snd-soc-mc13783-objs := mc13783.o
@@ -313,6 +314,7 @@ obj-$(CONFIG_SND_SOC_MAX98357A)	+= snd-soc-max98357a.o
 obj-$(CONFIG_SND_SOC_MAX9867)	+= snd-soc-max9867.o
 obj-$(CONFIG_SND_SOC_MAX98925)	+= snd-soc-max98925.o
 obj-$(CONFIG_SND_SOC_MAX98926)	+= snd-soc-max98926.o
+obj-$(CONFIG_SND_SOC_MAX98927)	+= snd-soc-max98927.o
 obj-$(CONFIG_SND_SOC_MAX9850)	+= snd-soc-max9850.o
 obj-$(CONFIG_SND_SOC_MAX9860)	+= snd-soc-max9860.o
 obj-$(CONFIG_SND_SOC_MC13783)	+= snd-soc-mc13783.o
diff --git a/sound/soc/codecs/max98927.c b/sound/soc/codecs/max98927.c
new file mode 100755
index 0000000..15f2192
--- /dev/null
+++ b/sound/soc/codecs/max98927.c
@@ -0,0 +1,826 @@
+/*
+ * max98927.c  --  MAX98927 ALSA Soc Audio driver
+ *
+ * Copyright (C) 2016 Maxim Integrated Products
+ * Author: Ryan Lee <ryans.lee@maximintegrated.com>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ */
+
+#include <linux/acpi.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/cdev.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <linux/gpio.h>
+#include <linux/of_gpio.h>
+#include <sound/tlv.h>
+#include "max98927.h"
+
+static struct reg_default max98927_reg[] = {
+	{MAX98927_R0001_INT_RAW1,  0x00},
+	{MAX98927_R0002_INT_RAW2,  0x00},
+	{MAX98927_R0003_INT_RAW3,  0x00},
+	{MAX98927_R0004_INT_STATE1,  0x00},
+	{MAX98927_R0005_INT_STATE2,  0x00},
+	{MAX98927_R0006_INT_STATE3,  0x00},
+	{MAX98927_R0007_INT_FLAG1,  0x00},
+	{MAX98927_R0008_INT_FLAG2,  0x00},
+	{MAX98927_R0009_INT_FLAG3,  0x00},
+	{MAX98927_R000A_INT_EN1,  0x00},
+	{MAX98927_R000B_INT_EN2,  0x00},
+	{MAX98927_R000C_INT_EN3,  0x00},
+	{MAX98927_R000D_INT_FLAG_CLR1,  0x00},
+	{MAX98927_R000E_INT_FLAG_CLR2,  0x00},
+	{MAX98927_R000F_INT_FLAG_CLR3,  0x00},
+	{MAX98927_R0010_IRQ_CTRL,  0x00},
+	{MAX98927_R0011_CLK_MON,  0x00},
+	{MAX98927_R0012_WDOG_CTRL,  0x00},
+	{MAX98927_R0013_WDOG_RST,  0x00},
+	{MAX98927_R0014_MEAS_ADC_THERM_WARN_THRESH,  0x00},
+	{MAX98927_R0015_MEAS_ADC_THERM_SHDN_THRESH,  0x00},
+	{MAX98927_R0016_MEAS_ADC_THERM_HYSTERESIS,  0x00},
+	{MAX98927_R0017_PIN_CFG,  0x55},
+	{MAX98927_R0018_PCM_RX_EN_A,  0x00},
+	{MAX98927_R0019_PCM_RX_EN_B,  0x00},
+	{MAX98927_R001A_PCM_TX_EN_A,  0x00},
+	{MAX98927_R001B_PCM_TX_EN_B,  0x00},
+	{MAX98927_R001C_PCM_TX_HIZ_CTRL_A,  0x00},
+	{MAX98927_R001D_PCM_TX_HIZ_CTRL_B,  0x00},
+	{MAX98927_R001E_PCM_TX_CH_SRC_A,  0x00},
+	{MAX98927_R001F_PCM_TX_CH_SRC_B,  0x00},
+	{MAX98927_R0020_PCM_MODE_CFG,  0x40},
+	{MAX98927_R0021_PCM_MASTER_MODE,  0x00},
+	{MAX98927_R0022_PCM_CLK_SETUP,  0x22},
+	{MAX98927_R0023_PCM_SR_SETUP1,  0x00},
+	{MAX98927_R0024_PCM_SR_SETUP2,  0x00},
+	{MAX98927_R0025_PCM_TO_SPK_MONOMIX_A,  0x00},
+	{MAX98927_R0026_PCM_TO_SPK_MONOMIX_B,  0x00},
+	{MAX98927_R0027_ICC_RX_EN_A,  0x00},
+	{MAX98927_R0028_ICC_RX_EN_B,  0x00},
+	{MAX98927_R002B_ICC_TX_EN_A,  0x00},
+	{MAX98927_R002C_ICC_TX_EN_B,  0x00},
+	{MAX98927_R002E_ICC_HIZ_MANUAL_MODE,  0x00},
+	{MAX98927_R002F_ICC_TX_HIZ_EN_A,  0x00},
+	{MAX98927_R0030_ICC_TX_HIZ_EN_B,  0x00},
+	{MAX98927_R0031_ICC_LNK_EN,  0x00},
+	{MAX98927_R0032_PDM_TX_EN,  0x00},
+	{MAX98927_R0033_PDM_TX_HIZ_CTRL,  0x00},
+	{MAX98927_R0034_PDM_TX_CTRL,  0x00},
+	{MAX98927_R0035_PDM_RX_CTRL,  0x00},
+	{MAX98927_R0036_AMP_VOL_CTRL,  0x00},
+	{MAX98927_R0037_AMP_DSP_CFG,  0x02},
+	{MAX98927_R0038_TONE_GEN_DC_CFG,  0x00},
+	{MAX98927_R0039_DRE_CTRL,  0x01},
+	{MAX98927_R003A_AMP_EN,  0x00},
+	{MAX98927_R003B_SPK_SRC_SEL,  0x00},
+	{MAX98927_R003C_SPK_GAIN,  0x00},
+	{MAX98927_R003D_SSM_CFG,  0x01},
+	{MAX98927_R003E_MEAS_EN,  0x00},
+	{MAX98927_R003F_MEAS_DSP_CFG,  0x04},
+	{MAX98927_R0040_BOOST_CTRL0,  0x00},
+	{MAX98927_R0041_BOOST_CTRL3,  0x00},
+	{MAX98927_R0042_BOOST_CTRL1,  0x00},
+	{MAX98927_R0043_MEAS_ADC_CFG,  0x00},
+	{MAX98927_R0044_MEAS_ADC_BASE_MSB,  0x00},
+	{MAX98927_R0045_MEAS_ADC_BASE_LSB,  0x00},
+	{MAX98927_R0046_ADC_CH0_DIVIDE,  0x00},
+	{MAX98927_R0047_ADC_CH1_DIVIDE,  0x00},
+	{MAX98927_R0048_ADC_CH2_DIVIDE,  0x00},
+	{MAX98927_R0049_ADC_CH0_FILT_CFG,  0x00},
+	{MAX98927_R004A_ADC_CH1_FILT_CFG,  0x00},
+	{MAX98927_R004B_ADC_CH2_FILT_CFG,  0x00},
+	{MAX98927_R004C_MEAS_ADC_CH0_READ,  0x00},
+	{MAX98927_R004D_MEAS_ADC_CH1_READ,  0x00},
+	{MAX98927_R004E_MEAS_ADC_CH2_READ,  0x00},
+	{MAX98927_R0051_BROWNOUT_STATUS,  0x00},
+	{MAX98927_R0052_BROWNOUT_EN,  0x00},
+	{MAX98927_R0053_BROWNOUT_INFINITE_HOLD,  0x00},
+	{MAX98927_R0054_BROWNOUT_INFINITE_HOLD_CLR,  0x00},
+	{MAX98927_R0055_BROWNOUT_LVL_HOLD,  0x00},
+	{MAX98927_R005A_BROWNOUT_LVL1_THRESH,  0x00},
+	{MAX98927_R005B_BROWNOUT_LVL2_THRESH,  0x00},
+	{MAX98927_R005C_BROWNOUT_LVL3_THRESH,  0x00},
+	{MAX98927_R005D_BROWNOUT_LVL4_THRESH,  0x00},
+	{MAX98927_R005E_BROWNOUT_THRESH_HYSTERYSIS,  0x00},
+	{MAX98927_R005F_BROWNOUT_AMP_LIMITER_ATK_REL,  0x00},
+	{MAX98927_R0060_BROWNOUT_AMP_GAIN_ATK_REL,  0x00},
+	{MAX98927_R0061_BROWNOUT_AMP1_CLIP_MODE,  0x00},
+	{MAX98927_R0072_BROWNOUT_LVL1_CUR_LIMIT,  0x00},
+	{MAX98927_R0073_BROWNOUT_LVL1_AMP1_CTRL1,  0x00},
+	{MAX98927_R0074_BROWNOUT_LVL1_AMP1_CTRL2,  0x00},
+	{MAX98927_R0075_BROWNOUT_LVL1_AMP1_CTRL3,  0x00},
+	{MAX98927_R0076_BROWNOUT_LVL2_CUR_LIMIT,  0x00},
+	{MAX98927_R0077_BROWNOUT_LVL2_AMP1_CTRL1,  0x00},
+	{MAX98927_R0078_BROWNOUT_LVL2_AMP1_CTRL2,  0x00},
+	{MAX98927_R0079_BROWNOUT_LVL2_AMP1_CTRL3,  0x00},
+	{MAX98927_R007A_BROWNOUT_LVL3_CUR_LIMIT,  0x00},
+	{MAX98927_R007B_BROWNOUT_LVL3_AMP1_CTRL1,  0x00},
+	{MAX98927_R007C_BROWNOUT_LVL3_AMP1_CTRL2,  0x00},
+	{MAX98927_R007D_BROWNOUT_LVL3_AMP1_CTRL3,  0x00},
+	{MAX98927_R007E_BROWNOUT_LVL4_CUR_LIMIT,  0x00},
+	{MAX98927_R007F_BROWNOUT_LVL4_AMP1_CTRL1,  0x00},
+	{MAX98927_R0080_BROWNOUT_LVL4_AMP1_CTRL2,  0x00},
+	{MAX98927_R0081_BROWNOUT_LVL4_AMP1_CTRL3,  0x00},
+	{MAX98927_R0082_ENV_TRACK_VOUT_HEADROOM,  0x00},
+	{MAX98927_R0083_ENV_TRACK_BOOST_VOUT_DELAY,  0x00},
+	{MAX98927_R0084_ENV_TRACK_REL_RATE,  0x00},
+	{MAX98927_R0085_ENV_TRACK_HOLD_RATE,  0x00},
+	{MAX98927_R0086_ENV_TRACK_CTRL,  0x00},
+	{MAX98927_R0087_ENV_TRACK_BOOST_VOUT_READ,  0x00},
+	{MAX98927_R00FF_GLOBAL_SHDN,  0x00},
+	{MAX98927_R0100_SOFT_RESET,  0x00},
+	{MAX98927_R01FF_REV_ID,  0x40},
+};
+
+static int max98927_dai_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct max98927_priv *max98927 = snd_soc_codec_get_drvdata(codec);
+	unsigned int mode = 0;
+	unsigned int invert = 0;
+
+	dev_dbg(codec->dev, "%s: fmt 0x%08X\n", __func__, fmt);
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBS_CFS:
+		mode = MAX98927_PCM_MASTER_MODE_SLAVE;
+		break;
+	case SND_SOC_DAIFMT_CBM_CFM:
+		max98927->master = true;
+		mode = MAX98927_PCM_MASTER_MODE_MASTER;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFM:
+		mode = MAX98927_PCM_MASTER_MODE_HYBRID;
+	default:
+		dev_err(codec->dev, "DAI clock mode unsupported");
+		return -EINVAL;
+	}
+
+	regmap_update_bits(max98927->regmap,
+		MAX98927_R0021_PCM_MASTER_MODE,
+		MAX98927_PCM_MASTER_MODE_MASK,
+		mode);
+
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		invert = MAX98927_PCM_MODE_CFG_PCM_BCLKEDGE;
+		break;
+	default:
+		dev_err(codec->dev, "DAI invert mode unsupported");
+		return -EINVAL;
+	}
+
+	regmap_update_bits(max98927->regmap,
+		MAX98927_R0020_PCM_MODE_CFG,
+		MAX98927_PCM_MODE_CFG_PCM_BCLKEDGE,
+		invert);
+
+	/* interface format */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		max98927->iface |= SND_SOC_DAIFMT_I2S;
+
+	break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		max98927->iface |= SND_SOC_DAIFMT_LEFT_J;
+	break;
+	default:
+		return -EINVAL;
+	}
+
+	regmap_update_bits(max98927->regmap,
+		MAX98927_R0020_PCM_MODE_CFG,
+		max98927->iface, max98927->iface);
+
+	/* pcm channel configuration */
+	if (max98927->iface & (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_LEFT_J))
+		regmap_write(max98927->regmap,
+			MAX98927_R0018_PCM_RX_EN_A,
+			MAX98927_PCM_RX_CH0_EN|
+			MAX98927_PCM_RX_CH1_EN);
+
+	return 0;
+}
+
+/* codec MCLK rate in master mode */
+static const int rate_table[] = {
+	5644800, 6000000, 6144000, 6500000,
+	9600000, 11289600, 12000000, 12288000,
+	13000000, 19200000,
+};
+
+static int max98927_set_clock(struct max98927_priv *max98927,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_codec *codec = max98927->codec;
+	/* BCLK/LRCLK ratio calculation */
+	int blr_clk_ratio = params_channels(params) * max98927->ch_size;
+	int reg = MAX98927_R0022_PCM_CLK_SETUP;
+	int mask = MAX98927_PCM_CLK_SETUP_BSEL_MASK;
+	int value;
+
+	if (max98927->master) {
+		int i;
+		/* match rate to closest value */
+		for (i = 0; i < ARRAY_SIZE(rate_table); i++) {
+			if (rate_table[i] >= max98927->sysclk)
+				break;
+		}
+		if (i == ARRAY_SIZE(rate_table)) {
+			dev_err(codec->dev, "failed to find proper clock rate.\n");
+			return -EINVAL;
+		}
+		regmap_update_bits(max98927->regmap,
+			MAX98927_R0021_PCM_MASTER_MODE,
+			MAX98927_PCM_MASTER_MODE_MCLK_MASK,
+			i << MAX98927_PCM_MASTER_MODE_MCLK_RATE_SHIFT);
+	}
+
+	switch (blr_clk_ratio) {
+	case 32:
+		value = 2;
+		break;
+	case 48:
+		value = 3;
+		break;
+	case 64:
+		value = 4;
+		break;
+	default:
+		return -EINVAL;
+	}
+	regmap_update_bits(max98927->regmap, reg, mask, value);
+	return 0;
+}
+
+static int max98927_dai_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 max98927_priv *max98927 = snd_soc_codec_get_drvdata(codec);
+	unsigned int sampling_rate = 0;
+	unsigned int chan_sz = 0;
+
+	/* pcm mode configuration */
+	switch (snd_pcm_format_width(params_format(params))) {
+	case 16:
+		chan_sz = MAX98927_PCM_MODE_CFG_CHANSZ_16;
+		max98927->ch_size = 16;
+		break;
+	case 24:
+		chan_sz = MAX98927_PCM_MODE_CFG_CHANSZ_24;
+		max98927->ch_size = 24;
+		break;
+	case 32:
+		chan_sz = MAX98927_PCM_MODE_CFG_CHANSZ_32;
+		max98927->ch_size = 32;
+		break;
+	default:
+		dev_err(codec->dev, "format unsupported %d",
+			params_format(params));
+		goto err;
+	}
+
+	regmap_update_bits(max98927->regmap,
+		MAX98927_R0020_PCM_MODE_CFG,
+		MAX98927_PCM_MODE_CFG_CHANSZ_MASK, chan_sz);
+
+	dev_dbg(codec->dev, "format supported %d",
+		params_format(params));
+
+	/* sampling rate configuration */
+	switch (params_rate(params)) {
+	case 8000:
+		sampling_rate |= MAX98927_PCM_SR_SET1_SR_8000;
+		break;
+	case 11025:
+		sampling_rate |= MAX98927_PCM_SR_SET1_SR_11025;
+		break;
+	case 12000:
+		sampling_rate |= MAX98927_PCM_SR_SET1_SR_12000;
+		break;
+	case 16000:
+		sampling_rate |= MAX98927_PCM_SR_SET1_SR_16000;
+		break;
+	case 22050:
+		sampling_rate |= MAX98927_PCM_SR_SET1_SR_22050;
+		break;
+	case 24000:
+		sampling_rate |= MAX98927_PCM_SR_SET1_SR_24000;
+		break;
+	case 32000:
+		sampling_rate |= MAX98927_PCM_SR_SET1_SR_32000;
+		break;
+	case 44100:
+		sampling_rate |= MAX98927_PCM_SR_SET1_SR_44100;
+		break;
+	case 48000:
+		sampling_rate |= MAX98927_PCM_SR_SET1_SR_48000;
+		break;
+	default:
+		dev_err(codec->dev, "rate %d not supported\n",
+			params_rate(params));
+		goto err;
+	}
+	/* set DAI_SR to correct LRCLK frequency */
+	regmap_update_bits(max98927->regmap,
+		MAX98927_R0023_PCM_SR_SETUP1,
+		MAX98927_PCM_SR_SET1_SR_MASK,
+		sampling_rate);
+	regmap_update_bits(max98927->regmap,
+		MAX98927_R0024_PCM_SR_SETUP2,
+		MAX98927_PCM_SR_SET2_SR_MASK,
+		sampling_rate << MAX98927_PCM_SR_SET2_SR_SHIFT);
+
+	/* set sampling rate of IV */
+	if (max98927->interleave_mode &&
+		sampling_rate > MAX98927_PCM_SR_SET1_SR_16000)
+		regmap_update_bits(max98927->regmap,
+			MAX98927_R0024_PCM_SR_SETUP2,
+			MAX98927_PCM_SR_SET2_IVADC_SR_MASK,
+			sampling_rate - 3);
+	else
+		regmap_update_bits(max98927->regmap,
+			MAX98927_R0024_PCM_SR_SETUP2,
+			MAX98927_PCM_SR_SET2_IVADC_SR_MASK,
+			sampling_rate);
+	return max98927_set_clock(max98927, params);
+err:
+	return -EINVAL;
+}
+
+#define MAX98927_RATES SNDRV_PCM_RATE_8000_48000
+
+#define MAX98927_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
+	SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static int max98927_dai_set_sysclk(struct snd_soc_dai *dai,
+	int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct max98927_priv *max98927 = snd_soc_codec_get_drvdata(codec);
+
+	max98927->sysclk = freq;
+	return 0;
+}
+
+static const struct snd_soc_dai_ops max98927_dai_ops = {
+	.set_sysclk = max98927_dai_set_sysclk,
+	.set_fmt = max98927_dai_set_fmt,
+	.hw_params = max98927_dai_hw_params,
+};
+
+static int max98927_dac_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 max98927_priv *max98927 = snd_soc_codec_get_drvdata(codec);
+
+	switch (event) {
+	case SND_SOC_DAPM_POST_PMU:
+		regmap_update_bits(max98927->regmap,
+			MAX98927_R003A_AMP_EN,
+			MAX98927_AMP_EN_MASK, 1);
+		/* enable VMON and IMON */
+		regmap_update_bits(max98927->regmap,
+			MAX98927_R003E_MEAS_EN,
+			MAX98927_MEAS_V_EN | MAX98927_MEAS_I_EN,
+			MAX98927_MEAS_V_EN | MAX98927_MEAS_I_EN);
+		regmap_update_bits(max98927->regmap,
+			MAX98927_R00FF_GLOBAL_SHDN,
+			MAX98927_GLOBAL_EN_MASK, 1);
+		break;
+	case SND_SOC_DAPM_POST_PMD:
+		regmap_update_bits(max98927->regmap,
+			MAX98927_R00FF_GLOBAL_SHDN,
+			MAX98927_GLOBAL_EN_MASK, 0);
+		regmap_update_bits(max98927->regmap,
+			MAX98927_R003A_AMP_EN,
+			MAX98927_AMP_EN_MASK, 0);
+		/* disable VMON and IMON */
+		regmap_update_bits(max98927->regmap,
+			MAX98927_R003E_MEAS_EN,
+			MAX98927_MEAS_V_EN | MAX98927_MEAS_I_EN, 0);
+		break;
+	default:
+		return 0;
+	}
+	return 0;
+}
+
+static const char * const max98927_switch_text[] = {
+	"Left", "Right", "LeftRight"};
+
+static const struct soc_enum dai_sel_enum =
+	SOC_ENUM_SINGLE(MAX98927_R0025_PCM_TO_SPK_MONOMIX_A,
+		MAX98927_PCM_TO_SPK_MONOMIX_CFG_SHIFT,
+		3, max98927_switch_text);
+
+static const struct snd_kcontrol_new max98927_dai_controls =
+	SOC_DAPM_ENUM("DAI Sel", dai_sel_enum);
+
+static const struct snd_soc_dapm_widget max98927_dapm_widgets[] = {
+	SND_SOC_DAPM_AIF_IN("DAI_OUT", "HiFi Playback", 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_DAC_E("Amp Enable", "HiFi Playback", MAX98927_R003A_AMP_EN,
+		0, 0, max98927_dac_event,
+		SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+	SND_SOC_DAPM_MUX("DAI Sel Mux", SND_SOC_NOPM, 0, 0,
+		&max98927_dai_controls),
+	SND_SOC_DAPM_OUTPUT("BE_OUT"),
+};
+
+static DECLARE_TLV_DB_SCALE(max98927_spk_tlv, 300, 300, 0);
+static DECLARE_TLV_DB_SCALE(max98927_digital_tlv, -1600, 25, 0);
+
+static bool max98927_readable_register(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case MAX98927_R0001_INT_RAW1 ... MAX98927_R0028_ICC_RX_EN_B:
+	case MAX98927_R002B_ICC_TX_EN_A ... MAX98927_R002C_ICC_TX_EN_B:
+	case MAX98927_R002E_ICC_HIZ_MANUAL_MODE
+		... MAX98927_R004E_MEAS_ADC_CH2_READ:
+	case MAX98927_R0051_BROWNOUT_STATUS
+		... MAX98927_R0055_BROWNOUT_LVL_HOLD:
+	case MAX98927_R005A_BROWNOUT_LVL1_THRESH
+		... MAX98927_R0061_BROWNOUT_AMP1_CLIP_MODE:
+	case MAX98927_R0072_BROWNOUT_LVL1_CUR_LIMIT
+		... MAX98927_R0087_ENV_TRACK_BOOST_VOUT_READ:
+	case MAX98927_R00FF_GLOBAL_SHDN:
+	case MAX98927_R0100_SOFT_RESET:
+	case MAX98927_R01FF_REV_ID:
+		return true;
+	}
+	return false;
+};
+
+static bool max98927_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case MAX98927_R0001_INT_RAW1 ... MAX98927_R0009_INT_FLAG3:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static const char * const max98927_boost_voltage_text[] = {
+	"6.5V", "6.625V", "6.75V", "6.875V", "7V", "7.125V", "7.25V", "7.375V",
+	"7.5V", "7.625V", "7.75V", "7.875V", "8V", "8.125V", "8.25V", "8.375V",
+	"8.5V", "8.625V", "8.75V", "8.875V", "9V", "9.125V", "9.25V", "9.375V",
+	"9.5V", "9.625V", "9.75V", "9.875V", "10V"
+};
+
+static const char * const max98927_speaker_source_text[] = {
+	"i2s", "reserved", "tone", "pdm"
+};
+
+static const char * const max98927_monomix_output_text[] = {
+	"ch_0", "ch_1", "ch_1_2_div"
+};
+
+static SOC_ENUM_SINGLE_DECL(max98927_boost_voltage,
+		MAX98927_R0040_BOOST_CTRL0, 0,
+		max98927_boost_voltage_text);
+
+static SOC_ENUM_SINGLE_DECL(max98927_spk_source,
+		MAX98927_R003B_SPK_SRC_SEL, 0,
+		max98927_speaker_source_text);
+
+static SOC_ENUM_SINGLE_DECL(max98927_monomix_output,
+		MAX98927_R0025_PCM_TO_SPK_MONOMIX_A,
+		MAX98927_PCM_TO_SPK_MONOMIX_CFG_SHIFT,
+		max98927_monomix_output_text);
+
+static const struct snd_kcontrol_new max98927_snd_controls[] = {
+	SOC_SINGLE_TLV("Speaker Volume", MAX98927_R003C_SPK_GAIN,
+		0, 6, 0,
+		max98927_spk_tlv),
+	SOC_SINGLE_TLV("Digital Gain", MAX98927_R0036_AMP_VOL_CTRL,
+		0, (1<<MAX98927_AMP_VOL_WIDTH)-1, 0,
+		max98927_digital_tlv),
+	SOC_SINGLE("Amp DSP Enable", MAX98927_R0052_BROWNOUT_EN,
+		MAX98927_BROWNOUT_DSP_SHIFT, 1, 0),
+	SOC_SINGLE("Ramp Switch", MAX98927_R0037_AMP_DSP_CFG,
+		MAX98927_AMP_DSP_CFG_RMP_SHIFT, 1, 0),
+	SOC_SINGLE("DRE EN", MAX98927_R0039_DRE_CTRL,
+		MAX98927_DRE_EN_SHIFT, 1, 0),
+	SOC_SINGLE("Amp Volume Location", MAX98927_R0036_AMP_VOL_CTRL,
+		MAX98927_AMP_VOL_SEL_SHIFT, 1, 0),
+	SOC_ENUM("Boost Output Voltage", max98927_boost_voltage),
+	SOC_ENUM("Speaker Source", max98927_spk_source),
+	SOC_ENUM("Monomix Output", max98927_monomix_output),
+};
+
+static const struct snd_soc_dapm_route max98927_audio_map[] = {
+	{"Amp Enable", NULL, "DAI_OUT"},
+	{"DAI Sel Mux", "Left", "Amp Enable"},
+	{"DAI Sel Mux", "Right", "Amp Enable"},
+	{"DAI Sel Mux", "LeftRight", "Amp Enable"},
+	{"BE_OUT", NULL, "DAI Sel Mux"},
+};
+
+static struct snd_soc_dai_driver max98927_dai[] = {
+	{
+		.name = "max98927-aif1",
+		.playback = {
+			.stream_name = "HiFi Playback",
+			.channels_min = 1,
+			.channels_max = 2,
+			.rates = MAX98927_RATES,
+			.formats = MAX98927_FORMATS,
+		},
+		.capture = {
+			.stream_name = "HiFi Capture",
+			.channels_min = 1,
+			.channels_max = 2,
+			.rates = MAX98927_RATES,
+			.formats = MAX98927_FORMATS,
+		},
+		.ops = &max98927_dai_ops,
+	}
+};
+
+static int max98927_probe(struct snd_soc_codec *codec)
+{
+	struct max98927_priv *max98927 = snd_soc_codec_get_drvdata(codec);
+	int ret = 0, reg = 0;
+
+	max98927->codec = codec;
+	codec->control_data = max98927->regmap;
+	codec->cache_bypass = 1;
+
+	/* Software Reset */
+	regmap_write(max98927->regmap,
+		MAX98927_R0100_SOFT_RESET, MAX98927_SOFT_RESET);
+
+	/* Check Revision ID */
+	ret = regmap_read(max98927->regmap,
+		MAX98927_R01FF_REV_ID, &reg);
+	if (ret < 0)
+		dev_err(codec->dev,
+			"Failed to read: 0x%02X\n", MAX98927_R01FF_REV_ID);
+	else
+		dev_info(codec->dev,
+			"MAX98927 revisionID: 0x%02X\n", reg);
+
+	/* IV default slot configuration */
+	regmap_write(max98927->regmap,
+		MAX98927_R001C_PCM_TX_HIZ_CTRL_A,
+		0xFF);
+	regmap_write(max98927->regmap,
+		MAX98927_R001D_PCM_TX_HIZ_CTRL_B,
+		0xFF);
+	regmap_write(max98927->regmap,
+		MAX98927_R0025_PCM_TO_SPK_MONOMIX_A,
+		0x80);
+	regmap_write(max98927->regmap,
+		MAX98927_R0026_PCM_TO_SPK_MONOMIX_B,
+		0x1);
+	/* Set inital volume (+13dB) */
+	regmap_write(max98927->regmap,
+		MAX98927_R0036_AMP_VOL_CTRL,
+		0x38);
+	regmap_write(max98927->regmap,
+		MAX98927_R003C_SPK_GAIN,
+		0x05);
+	/* Enable DC blocker */
+	regmap_write(max98927->regmap,
+		MAX98927_R0037_AMP_DSP_CFG,
+		0x03);
+	/* Enable IMON VMON DC blocker */
+	regmap_write(max98927->regmap,
+		MAX98927_R003F_MEAS_DSP_CFG,
+		0xF7);
+	/* Boost Output Voltage & Current limit */
+	regmap_write(max98927->regmap,
+		MAX98927_R0040_BOOST_CTRL0,
+		0x1C);
+	regmap_write(max98927->regmap,
+		MAX98927_R0042_BOOST_CTRL1,
+		0x3E);
+	/* Measurement ADC config */
+	regmap_write(max98927->regmap,
+		MAX98927_R0043_MEAS_ADC_CFG,
+		0x04);
+	regmap_write(max98927->regmap,
+		MAX98927_R0044_MEAS_ADC_BASE_MSB,
+		0x00);
+	regmap_write(max98927->regmap,
+		MAX98927_R0045_MEAS_ADC_BASE_LSB,
+		0x24);
+	/* Brownout Level */
+	regmap_write(max98927->regmap,
+		MAX98927_R007F_BROWNOUT_LVL4_AMP1_CTRL1,
+		0x06);
+	/* Envelope Tracking configuration */
+	regmap_write(max98927->regmap,
+		MAX98927_R0082_ENV_TRACK_VOUT_HEADROOM,
+		0x08);
+	regmap_write(max98927->regmap,
+		MAX98927_R0086_ENV_TRACK_CTRL,
+		0x01);
+	regmap_write(max98927->regmap,
+		MAX98927_R0087_ENV_TRACK_BOOST_VOUT_READ,
+		0x10);
+
+	/* voltage, current slot configuration */
+	regmap_write(max98927->regmap,
+		MAX98927_R001E_PCM_TX_CH_SRC_A,
+		(max98927->i_l_slot<<MAX98927_PCM_TX_CH_SRC_A_I_SHIFT|
+		max98927->v_l_slot)&0xFF);
+
+	if (max98927->v_l_slot < 8) {
+		regmap_update_bits(max98927->regmap,
+			MAX98927_R001C_PCM_TX_HIZ_CTRL_A,
+			1 << max98927->v_l_slot, 0);
+		regmap_update_bits(max98927->regmap,
+			MAX98927_R001A_PCM_TX_EN_A,
+			1 << max98927->v_l_slot,
+			1 << max98927->v_l_slot);
+	} else {
+		regmap_update_bits(max98927->regmap,
+			MAX98927_R001D_PCM_TX_HIZ_CTRL_B,
+			1 << (max98927->v_l_slot - 8), 0);
+		regmap_update_bits(max98927->regmap,
+			MAX98927_R001B_PCM_TX_EN_B,
+			1 << (max98927->v_l_slot - 8),
+			1 << (max98927->v_l_slot - 8));
+	}
+
+	if (max98927->i_l_slot < 8) {
+		regmap_update_bits(max98927->regmap,
+			MAX98927_R001C_PCM_TX_HIZ_CTRL_A,
+			1 << max98927->i_l_slot, 0);
+		regmap_update_bits(max98927->regmap,
+			MAX98927_R001A_PCM_TX_EN_A,
+			1 << max98927->i_l_slot,
+			1 << max98927->i_l_slot);
+	} else {
+		regmap_update_bits(max98927->regmap,
+			MAX98927_R001D_PCM_TX_HIZ_CTRL_B,
+			1 << (max98927->i_l_slot - 8), 0);
+		regmap_update_bits(max98927->regmap,
+			MAX98927_R001B_PCM_TX_EN_B,
+			1 << (max98927->i_l_slot - 8),
+			1 << (max98927->i_l_slot - 8));
+	}
+
+	/* Set interleave mode */
+	if (max98927->interleave_mode)
+		regmap_update_bits(max98927->regmap,
+			MAX98927_R001F_PCM_TX_CH_SRC_B,
+			MAX98927_PCM_TX_CH_INTERLEAVE_MASK,
+			MAX98927_PCM_TX_CH_INTERLEAVE_MASK);
+	return ret;
+}
+
+static const struct snd_soc_codec_driver soc_codec_dev_max98927 = {
+	.probe = max98927_probe,
+	.component_driver = {
+		.controls = max98927_snd_controls,
+		.num_controls = ARRAY_SIZE(max98927_snd_controls),
+		.dapm_widgets = max98927_dapm_widgets,
+		.num_dapm_widgets = ARRAY_SIZE(max98927_dapm_widgets),
+		.dapm_routes = max98927_audio_map,
+		.num_dapm_routes = ARRAY_SIZE(max98927_audio_map),
+	},
+};
+
+static const struct regmap_config max98927_regmap = {
+	.reg_bits         = 16,
+	.val_bits         = 8,
+	.max_register     = MAX98927_R01FF_REV_ID,
+	.reg_defaults     = max98927_reg,
+	.num_reg_defaults = ARRAY_SIZE(max98927_reg),
+	.readable_reg	  = max98927_readable_register,
+	.volatile_reg	  = max98927_volatile_reg,
+	.cache_type       = REGCACHE_RBTREE,
+};
+
+static void max98927_slot_config(struct i2c_client *i2c,
+	struct max98927_priv *max98927)
+{
+	int value;
+
+	if (!of_property_read_u32(i2c->dev.of_node,
+		"vmon-slot-no", &value))
+		max98927->v_l_slot = value & 0xF;
+	else
+		max98927->v_l_slot = 0;
+	if (!of_property_read_u32(i2c->dev.of_node,
+		"imon-slot-no", &value))
+		max98927->i_l_slot = value & 0xF;
+	else
+		max98927->i_l_slot = 1;
+}
+
+static int max98927_i2c_probe(struct i2c_client *i2c,
+	const struct i2c_device_id *id)
+{
+
+	int ret = 0, value;
+	struct max98927_priv *max98927 = NULL;
+
+	max98927 = devm_kzalloc(&i2c->dev,
+		sizeof(*max98927), GFP_KERNEL);
+
+	if (!max98927) {
+		ret = -ENOMEM;
+		goto err;
+	}
+	i2c_set_clientdata(i2c, max98927);
+
+	/* update interleave mode info */
+	if (!of_property_read_u32(i2c->dev.of_node,
+		"interleave_mode", &value)) {
+		if (value > 0)
+			max98927->interleave_mode = 1;
+		else
+			max98927->interleave_mode = 0;
+	} else
+		max98927->interleave_mode = 0;
+
+	/* regmap initialization */
+	max98927->regmap
+		= devm_regmap_init_i2c(i2c, &max98927_regmap);
+	if (IS_ERR(max98927->regmap)) {
+		ret = PTR_ERR(max98927->regmap);
+		dev_err(&i2c->dev,
+			"Failed to allocate regmap: %d\n", ret);
+		goto err;
+	}
+
+	/* voltage/current slot configuration */
+	max98927_slot_config(i2c, max98927);
+
+	/* codec registeration */
+	ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_max98927,
+		max98927_dai, ARRAY_SIZE(max98927_dai));
+	if (ret < 0)
+		dev_err(&i2c->dev, "Failed to register codec: %d\n", ret);
+
+	return ret;
+
+err:
+	if (max98927)
+		devm_kfree(&i2c->dev, max98927);
+	return ret;
+}
+
+static int max98927_i2c_remove(struct i2c_client *client)
+{
+	snd_soc_unregister_codec(&client->dev);
+	return 0;
+}
+
+static const struct i2c_device_id max98927_i2c_id[] = {
+	{ "max98927", 0},
+	{ },
+};
+
+MODULE_DEVICE_TABLE(i2c, max98927_i2c_id);
+
+#if defined(CONFIG_OF)
+static const struct of_device_id max98927_of_match[] = {
+	{ .compatible = "maxim,max98927", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, max98927_of_match);
+#endif
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id max98927_acpi_match[] = {
+	{ "MX98927", 0 },
+	{},
+};
+MODULE_DEVICE_TABLE(acpi, max98927_acpi_match);
+#endif
+
+static struct i2c_driver max98927_i2c_driver = {
+	.driver = {
+		.name = "max98927",
+		.of_match_table = of_match_ptr(max98927_of_match),
+		.acpi_match_table = ACPI_PTR(max98927_acpi_match),
+		.pm = NULL,
+	},
+	.probe  = max98927_i2c_probe,
+	.remove = max98927_i2c_remove,
+	.id_table = max98927_i2c_id,
+};
+
+module_i2c_driver(max98927_i2c_driver)
+
+MODULE_DESCRIPTION("ALSA SoC MAX98927 driver");
+MODULE_AUTHOR("Ryan Lee <ryans.lee@maximintegrated.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/max98927.h b/sound/soc/codecs/max98927.h
new file mode 100755
index 0000000..e13e18a
--- /dev/null
+++ b/sound/soc/codecs/max98927.h
@@ -0,0 +1,267 @@
+/*
+ * max98927.h  --  MAX98927 ALSA Soc Audio driver
+ *
+ * Copyright 2013-15 Maxim Integrated Products
+ * Author: Ryan Lee <ryans.lee@maximintegrated.com>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+#ifndef _MAX98927_H
+#define _MAX98927_H
+
+/* Register Values */
+#define MAX98927_R0001_INT_RAW1 0x0001
+#define MAX98927_R0002_INT_RAW2 0x0002
+#define MAX98927_R0003_INT_RAW3 0x0003
+#define MAX98927_R0004_INT_STATE1 0x0004
+#define MAX98927_R0005_INT_STATE2 0x0005
+#define MAX98927_R0006_INT_STATE3 0x0006
+#define MAX98927_R0007_INT_FLAG1 0x0007
+#define MAX98927_R0008_INT_FLAG2 0x0008
+#define MAX98927_R0009_INT_FLAG3 0x0009
+#define MAX98927_R000A_INT_EN1 0x000A
+#define MAX98927_R000B_INT_EN2 0x000B
+#define MAX98927_R000C_INT_EN3 0x000C
+#define MAX98927_R000D_INT_FLAG_CLR1	0x000D
+#define MAX98927_R000E_INT_FLAG_CLR2	0x000E
+#define MAX98927_R000F_INT_FLAG_CLR3	0x000F
+#define MAX98927_R0010_IRQ_CTRL 0x0010
+#define MAX98927_R0011_CLK_MON 0x0011
+#define MAX98927_R0012_WDOG_CTRL 0x0012
+#define MAX98927_R0013_WDOG_RST 0x0013
+#define MAX98927_R0014_MEAS_ADC_THERM_WARN_THRESH 0x0014
+#define MAX98927_R0015_MEAS_ADC_THERM_SHDN_THRESH 0x0015
+#define MAX98927_R0016_MEAS_ADC_THERM_HYSTERESIS 0x0016
+#define MAX98927_R0017_PIN_CFG 0x0017
+#define MAX98927_R0018_PCM_RX_EN_A 0x0018
+#define MAX98927_R0019_PCM_RX_EN_B 0x0019
+#define MAX98927_R001A_PCM_TX_EN_A 0x001A
+#define MAX98927_R001B_PCM_TX_EN_B 0x001B
+#define MAX98927_R001C_PCM_TX_HIZ_CTRL_A 0x001C
+#define MAX98927_R001D_PCM_TX_HIZ_CTRL_B 0x001D
+#define MAX98927_R001E_PCM_TX_CH_SRC_A 0x001E
+#define MAX98927_R001F_PCM_TX_CH_SRC_B 0x001F
+#define MAX98927_R0020_PCM_MODE_CFG 0x0020
+#define MAX98927_R0021_PCM_MASTER_MODE 0x0021
+#define MAX98927_R0022_PCM_CLK_SETUP 0x0022
+#define MAX98927_R0023_PCM_SR_SETUP1 0x0023
+#define MAX98927_R0024_PCM_SR_SETUP2	0x0024
+#define MAX98927_R0025_PCM_TO_SPK_MONOMIX_A 0x0025
+#define MAX98927_R0026_PCM_TO_SPK_MONOMIX_B 0x0026
+#define MAX98927_R0027_ICC_RX_EN_A 0x0027
+#define MAX98927_R0028_ICC_RX_EN_B 0x0028
+#define MAX98927_R002B_ICC_TX_EN_A 0x002B
+#define MAX98927_R002C_ICC_TX_EN_B 0x002C
+#define MAX98927_R002E_ICC_HIZ_MANUAL_MODE 0x002E
+#define MAX98927_R002F_ICC_TX_HIZ_EN_A 0x002F
+#define MAX98927_R0030_ICC_TX_HIZ_EN_B 0x0030
+#define MAX98927_R0031_ICC_LNK_EN 0x0031
+#define MAX98927_R0032_PDM_TX_EN 0x0032
+#define MAX98927_R0033_PDM_TX_HIZ_CTRL 0x0033
+#define MAX98927_R0034_PDM_TX_CTRL 0x0034
+#define MAX98927_R0035_PDM_RX_CTRL 0x0035
+#define MAX98927_R0036_AMP_VOL_CTRL 0x0036
+#define MAX98927_R0037_AMP_DSP_CFG 0x0037
+#define MAX98927_R0038_TONE_GEN_DC_CFG 0x0038
+#define MAX98927_R0039_DRE_CTRL 0x0039
+#define MAX98927_R003A_AMP_EN 0x003A
+#define MAX98927_R003B_SPK_SRC_SEL 0x003B
+#define MAX98927_R003C_SPK_GAIN 0x003C
+#define MAX98927_R003D_SSM_CFG 0x003D
+#define MAX98927_R003E_MEAS_EN 0x003E
+#define MAX98927_R003F_MEAS_DSP_CFG 0x003F
+#define MAX98927_R0040_BOOST_CTRL0 0x0040
+#define MAX98927_R0041_BOOST_CTRL3 0x0041
+#define MAX98927_R0042_BOOST_CTRL1 0x0042
+#define MAX98927_R0043_MEAS_ADC_CFG 0x0043
+#define MAX98927_R0044_MEAS_ADC_BASE_MSB 0x0044
+#define MAX98927_R0045_MEAS_ADC_BASE_LSB 0x0045
+#define MAX98927_R0046_ADC_CH0_DIVIDE 0x0046
+#define MAX98927_R0047_ADC_CH1_DIVIDE 0x0047
+#define MAX98927_R0048_ADC_CH2_DIVIDE 0x0048
+#define MAX98927_R0049_ADC_CH0_FILT_CFG 0x0049
+#define MAX98927_R004A_ADC_CH1_FILT_CFG 0x004A
+#define MAX98927_R004B_ADC_CH2_FILT_CFG 0x004B
+#define MAX98927_R004C_MEAS_ADC_CH0_READ 0x004C
+#define MAX98927_R004D_MEAS_ADC_CH1_READ 0x004D
+#define MAX98927_R004E_MEAS_ADC_CH2_READ 0x004E
+#define MAX98927_R0051_BROWNOUT_STATUS 0x0051
+#define MAX98927_R0052_BROWNOUT_EN 0x0052
+#define MAX98927_R0053_BROWNOUT_INFINITE_HOLD 0x0053
+#define MAX98927_R0054_BROWNOUT_INFINITE_HOLD_CLR 0x0054
+#define MAX98927_R0055_BROWNOUT_LVL_HOLD 0x0055
+#define MAX98927_R005A_BROWNOUT_LVL1_THRESH 0x005A
+#define MAX98927_R005B_BROWNOUT_LVL2_THRESH 0x005B
+#define MAX98927_R005C_BROWNOUT_LVL3_THRESH 0x005C
+#define MAX98927_R005D_BROWNOUT_LVL4_THRESH 0x005D
+#define MAX98927_R005E_BROWNOUT_THRESH_HYSTERYSIS 0x005E
+#define MAX98927_R005F_BROWNOUT_AMP_LIMITER_ATK_REL 0x005F
+#define MAX98927_R0060_BROWNOUT_AMP_GAIN_ATK_REL 0x0060
+#define MAX98927_R0061_BROWNOUT_AMP1_CLIP_MODE 0x0061
+#define MAX98927_R0072_BROWNOUT_LVL1_CUR_LIMIT 0x0072
+#define MAX98927_R0073_BROWNOUT_LVL1_AMP1_CTRL1 0x0073
+#define MAX98927_R0074_BROWNOUT_LVL1_AMP1_CTRL2 0x0074
+#define MAX98927_R0075_BROWNOUT_LVL1_AMP1_CTRL3 0x0075
+#define MAX98927_R0076_BROWNOUT_LVL2_CUR_LIMIT 0x0076
+#define MAX98927_R0077_BROWNOUT_LVL2_AMP1_CTRL1 0x0077
+#define MAX98927_R0078_BROWNOUT_LVL2_AMP1_CTRL2 0x0078
+#define MAX98927_R0079_BROWNOUT_LVL2_AMP1_CTRL3 0x0079
+#define MAX98927_R007A_BROWNOUT_LVL3_CUR_LIMIT 0x007A
+#define MAX98927_R007B_BROWNOUT_LVL3_AMP1_CTRL1 0x007B
+#define MAX98927_R007C_BROWNOUT_LVL3_AMP1_CTRL2 0x007C
+#define MAX98927_R007D_BROWNOUT_LVL3_AMP1_CTRL3 0x007D
+#define MAX98927_R007E_BROWNOUT_LVL4_CUR_LIMIT 0x007E
+#define MAX98927_R007F_BROWNOUT_LVL4_AMP1_CTRL1 0x007F
+#define MAX98927_R0080_BROWNOUT_LVL4_AMP1_CTRL2 0x0080
+#define MAX98927_R0081_BROWNOUT_LVL4_AMP1_CTRL3 0x0081
+#define MAX98927_R0082_ENV_TRACK_VOUT_HEADROOM 0x0082
+#define MAX98927_R0083_ENV_TRACK_BOOST_VOUT_DELAY 0x0083
+#define MAX98927_R0084_ENV_TRACK_REL_RATE 0x0084
+#define MAX98927_R0085_ENV_TRACK_HOLD_RATE 0x0085
+#define MAX98927_R0086_ENV_TRACK_CTRL 0x0086
+#define MAX98927_R0087_ENV_TRACK_BOOST_VOUT_READ 0x0087
+#define MAX98927_R00FF_GLOBAL_SHDN 0x00FF
+#define MAX98927_R0100_SOFT_RESET 0x0100
+#define MAX98927_R01FF_REV_ID 0x01FF
+
+/* MAX98927_R0018_PCM_RX_EN_A */
+#define MAX98927_PCM_RX_CH0_EN (0x1 << 0)
+#define MAX98927_PCM_RX_CH1_EN (0x1 << 1)
+#define MAX98927_PCM_RX_CH2_EN (0x1 << 2)
+#define MAX98927_PCM_RX_CH3_EN (0x1 << 3)
+#define MAX98927_PCM_RX_CH4_EN (0x1 << 4)
+#define MAX98927_PCM_RX_CH5_EN (0x1 << 5)
+#define MAX98927_PCM_RX_CH6_EN (0x1 << 6)
+#define MAX98927_PCM_RX_CH7_EN (0x1 << 7)
+
+/* MAX98927_R001A_PCM_TX_EN_A */
+#define MAX98927_PCM_TX_CH0_EN (0x1 << 0)
+#define MAX98927_PCM_TX_CH1_EN (0x1 << 1)
+#define MAX98927_PCM_TX_CH2_EN (0x1 << 2)
+#define MAX98927_PCM_TX_CH3_EN (0x1 << 3)
+#define MAX98927_PCM_TX_CH4_EN (0x1 << 4)
+#define MAX98927_PCM_TX_CH5_EN (0x1 << 5)
+#define MAX98927_PCM_TX_CH6_EN (0x1 << 6)
+#define MAX98927_PCM_TX_CH7_EN (0x1 << 7)
+
+/* MAX98927_R001E_PCM_TX_CH_SRC_A */
+#define MAX98927_PCM_TX_CH_SRC_A_V_SHIFT (0)
+#define MAX98927_PCM_TX_CH_SRC_A_I_SHIFT (4)
+
+/* MAX98927_R001F_PCM_TX_CH_SRC_B */
+#define MAX98927_PCM_TX_CH_INTERLEAVE_MASK (0x1 << 5)
+
+/* MAX98927_R0020_PCM_MODE_CFG */
+#define MAX98927_PCM_MODE_CFG_PCM_BCLKEDGE (0x1 << 2)
+#define MAX98927_PCM_MODE_CFG_FORMAT_MASK (0x7 << 3)
+
+#define MAX98927_PCM_MODE_CFG_CHANSZ_MASK (0x3 << 6)
+#define MAX98927_PCM_MODE_CFG_CHANSZ_16 (0x1 << 6)
+#define MAX98927_PCM_MODE_CFG_CHANSZ_24 (0x2 << 6)
+#define MAX98927_PCM_MODE_CFG_CHANSZ_32 (0x3 << 6)
+
+/* MAX98927_R0021_PCM_MASTER_MODE */
+#define MAX98927_PCM_MASTER_MODE_MASK (0x3 << 0)
+#define MAX98927_PCM_MASTER_MODE_SLAVE (0x0 << 0)
+#define MAX98927_PCM_MASTER_MODE_MASTER (0x3 << 0)
+#define MAX98927_PCM_MASTER_MODE_HYBRID (0x1 << 0)
+
+#define MAX98927_PCM_MASTER_MODE_MCLK_MASK (0xF << 2)
+#define MAX98927_PCM_MASTER_MODE_MCLK_RATE_SHIFT (2)
+
+/* MAX98927_R0022_PCM_CLK_SETUP */
+#define MAX98927_PCM_CLK_SETUP_BSEL_MASK (0xF << 0)
+
+/* MAX98927_R0023_PCM_SR_SETUP1 */
+#define MAX98927_PCM_SR_SET1_SR_MASK (0xF << 0)
+
+#define MAX98927_PCM_SR_SET1_SR_8000 (0x0 << 0)
+#define MAX98927_PCM_SR_SET1_SR_11025 (0x1 << 0)
+#define MAX98927_PCM_SR_SET1_SR_12000 (0x2 << 0)
+#define MAX98927_PCM_SR_SET1_SR_16000 (0x3 << 0)
+#define MAX98927_PCM_SR_SET1_SR_22050 (0x4 << 0)
+#define MAX98927_PCM_SR_SET1_SR_24000 (0x5 << 0)
+#define MAX98927_PCM_SR_SET1_SR_32000 (0x6 << 0)
+#define MAX98927_PCM_SR_SET1_SR_44100 (0x7 << 0)
+#define MAX98927_PCM_SR_SET1_SR_48000 (0x8 << 0)
+
+/* MAX98927_R0024_PCM_SR_SETUP2 */
+#define MAX98927_PCM_SR_SET2_SR_MASK (0xF << 4)
+#define MAX98927_PCM_SR_SET2_SR_SHIFT (4)
+#define MAX98927_PCM_SR_SET2_IVADC_SR_MASK (0xf << 0)
+
+/* MAX98927_R0025_PCM_TO_SPK_MONOMIX_A */
+#define MAX98927_PCM_TO_SPK_MONOMIX_CFG_MASK (0x3 << 6)
+#define MAX98927_PCM_TO_SPK_MONOMIX_CFG_SHIFT (6)
+
+/* MAX98927_R0036_AMP_VOL_CTRL */
+#define MAX98927_AMP_VOL_SEL (0x1 << 7)
+#define MAX98927_AMP_VOL_SEL_WIDTH (1)
+#define MAX98927_AMP_VOL_SEL_SHIFT (7)
+#define MAX98927_AMP_VOL_MASK (0x7f << 0)
+#define MAX98927_AMP_VOL_WIDTH (7)
+#define MAX98927_AMP_VOL_SHIFT (0)
+
+/* MAX98927_R0037_AMP_DSP_CFG */
+#define MAX98927_AMP_DSP_CFG_DCBLK_EN (0x1 << 0)
+#define MAX98927_AMP_DSP_CFG_DITH_EN (0x1 << 1)
+#define MAX98927_AMP_DSP_CFG_RMP_BYPASS (0x1 << 4)
+#define MAX98927_AMP_DSP_CFG_DAC_INV (0x1 << 5)
+#define MAX98927_AMP_DSP_CFG_RMP_SHIFT (4)
+
+/* MAX98927_R0039_DRE_CTRL */
+#define MAX98927_DRE_CTRL_DRE_EN	(0x1 << 0)
+#define MAX98927_DRE_EN_SHIFT 0x1
+
+/* MAX98927_R003A_AMP_EN */
+#define MAX98927_AMP_EN_MASK (0x1 << 0)
+
+/* MAX98927_R003B_SPK_SRC_SEL */
+#define MAX98927_SPK_SRC_MASK (0x3 << 0)
+
+/* MAX98927_R003C_SPK_GAIN */
+#define MAX98927_SPK_PCM_GAIN_MASK (0x7 << 0)
+#define MAX98927_SPK_PDM_GAIN_MASK (0x7 << 4)
+#define MAX98927_SPK_GAIN_WIDTH (3)
+
+/* MAX98927_R003E_MEAS_EN */
+#define MAX98927_MEAS_V_EN (0x1 << 0)
+#define MAX98927_MEAS_I_EN (0x1 << 1)
+
+/* MAX98927_R0040_BOOST_CTRL0 */
+#define MAX98927_BOOST_CTRL0_VOUT_MASK (0x1f << 0)
+#define MAX98927_BOOST_CTRL0_PVDD_MASK (0x1 << 7)
+#define MAX98927_BOOST_CTRL0_PVDD_EN_SHIFT (7)
+
+/* MAX98927_R0052_BROWNOUT_EN */
+#define MAX98927_BROWNOUT_BDE_EN (0x1 << 0)
+#define MAX98927_BROWNOUT_AMP_EN (0x1 << 1)
+#define MAX98927_BROWNOUT_DSP_EN (0x1 << 2)
+#define MAX98927_BROWNOUT_DSP_SHIFT (2)
+
+/* MAX98927_R0100_SOFT_RESET */
+#define MAX98927_SOFT_RESET (0x1 << 0)
+
+/* MAX98927_R00FF_GLOBAL_SHDN */
+#define MAX98927_GLOBAL_EN_MASK (0x1 << 0)
+
+struct max98927_priv {
+	struct regmap *regmap;
+	struct snd_soc_codec *codec;
+	struct max98927_pdata *pdata;
+	unsigned int spk_gain;
+	unsigned int sysclk;
+	unsigned int v_l_slot;
+	unsigned int i_l_slot;
+	bool interleave_mode;
+	unsigned int ch_size;
+	unsigned int rate;
+	unsigned int iface;
+	unsigned int master;
+	unsigned int digital_gain;
+};
+#endif
-- 
2.7.4

^ permalink raw reply related

* Re: [PATCH 5/8] ARM64: dts: meson-gxbb: add i2s output pins
From: Jerome Brunet @ 2017-03-28 15:34 UTC (permalink / raw)
  To: Kevin Hilman
  Cc: Linus Walleij, Carlo Caione,
	linux-gpio-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r@public.gmane.org,
	open list:ARM/Amlogic Meson...,
	devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
In-Reply-To: <CACRpkdYYqX=qkLyvRMAPMyqvwg0=NHpq1GXRnTyZbd=L2cna9g-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>

On Tue, 2017-03-28 at 15:15 +0200, Linus Walleij wrote:
> On Thu, Mar 23, 2017 at 5:50 PM, Jerome Brunet <jbrunet-rdvid1DuHRBWk0Htik3J/w@public.gmane.org> wrote:
> 
> > Signed-off-by: Jerome Brunet <jbrunet-rdvid1DuHRBWk0Htik3J/w@public.gmane.org>
> 
> Acked-by: Linus Walleij <linus.walleij-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
> 
> For this and the other Meson DTS patches.
> 
> Please funnel these through the apropriate ARM SoC-feed tree.

Kevin,
You may want to take the ones from the v2 I sent later. There is less chance of
conflict with  Neil's patches.

Cheers
Jerome

> 
> Yours,
> Linus Walleij
--
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

* Re: [PATCH] ASoC: Add support for Maxim Integrated MAX98927 Amplifier
From: Ryans Lee @ 2017-03-28 15:36 UTC (permalink / raw)
  To: Rob Herring
  Cc: mark.rutland@arm.com, alsa-devel@alsa-project.org,
	kuninori.morimoto.gx@renesas.com, lgirdwood@gmail.com,
	tiwai@suse.com, srinivas.kandagatla@linaro.org,
	romain.perier@collabora.com, bardliao@realtek.com,
	lars@metafoo.de, axel.lin@ingics.com, Paul.Handrigan@cirrus.com,
	devicetree@vger.kernel.org, arnd@arndb.de, nh6z@nh6z.net,
	broonie@kernel.org, ckeepax@opensource.wolfsonmicro.com,
	dgreid@google.com, oder_chiou
In-Reply-To: <20170324152613.qzvsviznhiz5f3sj@rob-hp-laptop>


> -----Original Message-----
> From: Rob Herring [mailto:robh@kernel.org]
> Sent: Friday, March 24, 2017 8:26 AM
> To: Ryans Lee <RyanS.Lee@maximintegrated.com>
> Cc: lgirdwood@gmail.com; broonie@kernel.org; mark.rutland@arm.com; 
> perex@perex.cz; tiwai@suse.com; kuninori.morimoto.gx@renesas.com; 
> arnd@arndb.de; ckeepax@opensource.wolfsonmicro.com; lars@metafoo.de; 
> bardliao@realtek.com; nh6z@nh6z.net; KCHSU0@nuvoton.com; 
> axel.lin@ingics.com; romain.perier@collabora.com; petr@barix.com; 
> srinivas.kandagatla@linaro.org; oder_chiou@realtek.com; 
> Paul.Handrigan@cirrus.com; alsa-devel@alsa-project.org; 
> devicetree@vger.kernel.org; linux-kernel@vger.kernel.org; 
> dgreid@google.com
> Subject: Re: [PATCH] ASoC: Add support for Maxim Integrated MAX98927 
> Amplifier
>
> EXTERNAL EMAIL
>
>
>
> On Mon, Mar 20, 2017 at 09:40:23AM +0900, Ryan Lee wrote:
>> Signed-off-by: Ryan Lee <ryans.lee@maximintegrated.com>
>> ---
>> Resubmit the intial version of MAX98927 driver. Added all fixes into the initial patch.
>>
>>  .../devicetree/bindings/sound/max98927.txt         |   39 +
>>  sound/soc/codecs/Kconfig                           |    5 +
>>  sound/soc/codecs/Makefile                          |    2 +
>>  sound/soc/codecs/max98927.c                        | 1040 ++++++++++++++++++++
>>  sound/soc/codecs/max98927.h                        |  267 +++++
>>  5 files changed, 1353 insertions(+)
>>  create mode 100755
>> Documentation/devicetree/bindings/sound/max98927.txt
>>  mode change 100644 => 100755 sound/soc/codecs/Kconfig  mode change
>> 100644 => 100755 sound/soc/codecs/Makefile  create mode 100755 
>> sound/soc/codecs/max98927.c  create mode 100755 
>> sound/soc/codecs/max98927.h
>>
>> diff --git a/Documentation/devicetree/bindings/sound/max98927.txt
>> b/Documentation/devicetree/bindings/sound/max98927.txt
>> new file mode 100755
>> index 0000000..d610879
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/sound/max98927.txt
>> @@ -0,0 +1,39 @@
>> +max98927 audio CODEC
>> +
>> +This device supports I2C.
>
> Can we combine this and the other max9892x bindings. They all look similar.
They are not same but almost similar. I combined MAX98925, MAX98926 and MAX98927 bindings into MAX9892x.txt
Thank you for your feedback.
>
>> +
>> +Required properties:
>> +
>> +  - compatible : "maxim,max98927"
>> +
>> +  - maxim,vmon-slot-no : slot number used to send voltage 
>> + information
>
> While vendor prefix is preferred, so is using the same property names.
> Either use the existing names or deprecate the old ones in favor of these.
To use same property names, I kept existing names. Thanks.

>
>> +                   or in inteleave mode this will be used as
>> +                   interleave slot.
>> +                   This property can be set to values from 0 to 15 for slot 0 to 15.
>> +                   Default value is 0.
>> +
>> +  - maxim,imon-slot-no : slot number used to send current information
>> +                   This property can be set to values from 0 to 15 for slot 0 to 15.
>> +                   Default value is 0.
>> +
>> +  - maxim,interleave-mode : When using two MAX98927 in a system it is
>> +                   possible to create ADC data that that will
>> +                   overflow the frame size. Digital Audio Interleave
>> +                   mode provides a means to output VMON and IMON data
>> +                   from two devices on a single DOUT line when running
>> +                   smaller frames sizes such as 32 BCLKS per LRCLK or
>> +                   48 BCLKS per LRCLK.
>> +                   This property can be set to values from 0 to 1 for off and on.
>> +                   Default value is 0.
>> +
>> +  - reg : the I2C address of the device for I2C
>> +
>> +Example:
>> +
>> +codec: max98927@3a {
>> +   compatible = "maxim,max98927";
>> +   maxim,vmon-slot-no = <0>;
>> +   maxim,imon-slot-no = <1>;
>> +   maxim,interleave-mode = <0>;
>> +   reg = <0x3a>;
>> +};

^ permalink raw reply

* Re: [PATCH] arm64: tegra: Add CPU and PSCI nodes for NVIDIA Tegra210 platforms
From: Sudeep Holla @ 2017-03-28 15:37 UTC (permalink / raw)
  To: Jon Hunter, Thierry Reding, Stephen Warren
  Cc: Sudeep Holla, devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-tegra-u79uwXL29TY76Z2rM5mHXA, Martin Michlmayr,
	Vagrant Cascadian
In-Reply-To: <e3ab2e09-ee32-96e2-b0c8-f7def668bf31-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>



On 28/03/17 16:32, Jon Hunter wrote:
> 
> On 28/03/17 15:55, Sudeep Holla wrote:
>> On 28/03/17 12:48, Jon Hunter wrote:
>>> Add the CPU and PSCI nodes for the NVIDIA Tegra210 platforms so that
>>> all CPUs can be enabled on boot. This assumes that the PSCI firmware
>>> has been loaded during the initial bootstrap on the device before the
>>> kernel starts (which is typically the case for these platforms). The
>>> PSCI firmware version is set to v0.2 which aligns with the current
>>> shipping version for Tegra.
>>>
>>> Reported-by: Martin Michlmayr <tbm-R+vWnYXSFMfQT0dZR+AlfA@public.gmane.org>
>>> Signed-off-by: Jon Hunter <jonathanh-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>
>>> ---
>>>  arch/arm64/boot/dts/nvidia/tegra210-p2180.dtsi | 23 +++++++++++++++++++++++
>>>  arch/arm64/boot/dts/nvidia/tegra210-p2530.dtsi | 24 ++++++++++++++++++++++++
>>>  2 files changed, 47 insertions(+)
>>>
>>> diff --git a/arch/arm64/boot/dts/nvidia/tegra210-p2180.dtsi b/arch/arm64/boot/dts/nvidia/tegra210-p2180.dtsi
>>> index 906fb836d241..de1696c28140 100644
>>> --- a/arch/arm64/boot/dts/nvidia/tegra210-p2180.dtsi
>>> +++ b/arch/arm64/boot/dts/nvidia/tegra210-p2180.dtsi
>>> @@ -296,6 +296,29 @@
>>>  		};
>>>  	};
>>>  
>>> +	cpus {
>>> +		cpu@0 {
>>> +			enable-method = "psci";
>>
>> Is some firmware expected to populate other required properties ?
>>
>> If yes, why not populate everything in the firmware ?
>>
>> If no, how does it even boot secondaries without the "reg" property
>> that usually contains MPIDR.
> 
> Please see 'arch/arm64/boot/dts/nvidia/tegra210.dtsi' which has these
> fields. So far we have been populating the psci information at the board
> level and so I have kept with that here and so the above is just the
> delta. Maybe the $subject could be a little clearer!

Sorry for the noise, I traversed files/headers upwards and missed
the innermost tegra210.dtsi :(

-- 
Regards,
Sudeep

^ permalink raw reply

* [PATCH v8 1/4] PCI: imx6: Add code to support i.MX7D
From: Andrey Smirnov @ 2017-03-28 15:42 UTC (permalink / raw)
  To: Bjorn Helgaas
  Cc: Andrey Smirnov, yurovsky, Lucas Stach, Rob Herring, Mark Rutland,
	Lee Jones, Fabio Estevam, Dong Aisheng, linux-arm-kernel,
	devicetree, linux-pci, linux-kernel
In-Reply-To: <20170328154252.6902-1-andrew.smirnov@gmail.com>

Add various bits of code needed to support i.MX7D variant of the IP.

Cc: yurovsky@gmail.com
Cc: Lucas Stach <l.stach@pengutronix.de>
Cc: Rob Herring <robh+dt@kernel.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Lee Jones <lee.jones@linaro.org>
Cc: Fabio Estevam <fabio.estevam@nxp.com>
Cc: Dong Aisheng <dongas86@gmail.com>
Cc: linux-arm-kernel@lists.infradead.org
Cc: devicetree@vger.kernel.org
Cc: linux-pci@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Acked-by: Lee Jones <lee.jones@linaro.org>
Acked-by: Rob Herring <robh@kernel.org>
Reviewed-by: Lucas Stach <l.stach@pengutronix.de>
Signed-off-by: Andrey Smirnov <andrew.smirnov@gmail.com>
---
 .../devicetree/bindings/pci/fsl,imx6q-pcie.txt     |  13 ++-
 drivers/pci/dwc/pci-imx6.c                         | 120 ++++++++++++++++-----
 include/linux/mfd/syscon/imx7-iomuxc-gpr.h         |   4 +
 3 files changed, 111 insertions(+), 26 deletions(-)

diff --git a/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.txt b/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.txt
index 83aeb1f..11db2ab 100644
--- a/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.txt
+++ b/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.txt
@@ -4,7 +4,11 @@ This PCIe host controller is based on the Synopsis Designware PCIe IP
 and thus inherits all the common properties defined in designware-pcie.txt.
 
 Required properties:
-- compatible: "fsl,imx6q-pcie", "fsl,imx6sx-pcie", "fsl,imx6qp-pcie"
+- compatible:
+	- "fsl,imx6q-pcie"
+	- "fsl,imx6sx-pcie",
+	- "fsl,imx6qp-pcie"
+	- "fsl,imx7d-pcie"
 - reg: base address and length of the PCIe controller
 - interrupts: A list of interrupt outputs of the controller. Must contain an
   entry for each entry in the interrupt-names property.
@@ -34,6 +38,13 @@ Additional required properties for imx6sx-pcie:
 - clock names: Must include the following additional entries:
 	- "pcie_inbound_axi"
 
+Additional required properties for imx7d-pcie:
+- power-domains: Must be set to a phandle pointing to PCIE_PHY power domain
+- resets: Must contain phandles to PCIE related reset lines exposed by SRC IP block
+- reset-names: Must contain the following entires:
+  	       - "pciephy"
+	       - "apps"
+
 Example:
 
 	pcie@0x01000000 {
diff --git a/drivers/pci/dwc/pci-imx6.c b/drivers/pci/dwc/pci-imx6.c
index 801e46c..e0c36d6 100644
--- a/drivers/pci/dwc/pci-imx6.c
+++ b/drivers/pci/dwc/pci-imx6.c
@@ -17,6 +17,7 @@
 #include <linux/kernel.h>
 #include <linux/mfd/syscon.h>
 #include <linux/mfd/syscon/imx6q-iomuxc-gpr.h>
+#include <linux/mfd/syscon/imx7-iomuxc-gpr.h>
 #include <linux/module.h>
 #include <linux/of_gpio.h>
 #include <linux/of_device.h>
@@ -27,6 +28,7 @@
 #include <linux/signal.h>
 #include <linux/types.h>
 #include <linux/interrupt.h>
+#include <linux/reset.h>
 
 #include "pcie-designware.h"
 
@@ -36,6 +38,7 @@ enum imx6_pcie_variants {
 	IMX6Q,
 	IMX6SX,
 	IMX6QP,
+	IMX7D,
 };
 
 struct imx6_pcie {
@@ -47,6 +50,8 @@ struct imx6_pcie {
 	struct clk		*pcie_inbound_axi;
 	struct clk		*pcie;
 	struct regmap		*iomuxc_gpr;
+	struct reset_control	*pciephy_reset;
+	struct reset_control	*apps_reset;
 	enum imx6_pcie_variants variant;
 	u32			tx_deemph_gen1;
 	u32			tx_deemph_gen2_3p5db;
@@ -56,6 +61,11 @@ struct imx6_pcie {
 	int			link_gen;
 };
 
+/* Parameters for the waiting for PCIe PHY PLL to lock on i.MX7 */
+#define PHY_PLL_LOCK_WAIT_MAX_RETRIES	2000
+#define PHY_PLL_LOCK_WAIT_USLEEP_MIN	50
+#define PHY_PLL_LOCK_WAIT_USLEEP_MAX	200
+
 /* PCIe Root Complex registers (memory-mapped) */
 #define PCIE_RC_LCR				0x7c
 #define PCIE_RC_LCR_MAX_LINK_SPEEDS_GEN1	0x1
@@ -248,6 +258,10 @@ static int imx6q_pcie_abort_handler(unsigned long addr,
 static void imx6_pcie_assert_core_reset(struct imx6_pcie *imx6_pcie)
 {
 	switch (imx6_pcie->variant) {
+	case IMX7D:
+		reset_control_assert(imx6_pcie->pciephy_reset);
+		reset_control_assert(imx6_pcie->apps_reset);
+		break;
 	case IMX6SX:
 		regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12,
 				   IMX6SX_GPR12_PCIE_TEST_POWERDOWN,
@@ -303,11 +317,32 @@ static int imx6_pcie_enable_ref_clk(struct imx6_pcie *imx6_pcie)
 		regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR1,
 				   IMX6Q_GPR1_PCIE_REF_CLK_EN, 1 << 16);
 		break;
+	case IMX7D:
+		break;
 	}
 
 	return ret;
 }
 
+static void imx7d_pcie_wait_for_phy_pll_lock(struct imx6_pcie *imx6_pcie)
+{
+	u32 val;
+	unsigned int retries;
+	struct device *dev = imx6_pcie->pci->dev;
+
+	for (retries = 0; retries < PHY_PLL_LOCK_WAIT_MAX_RETRIES; retries++) {
+		regmap_read(imx6_pcie->iomuxc_gpr, IOMUXC_GPR22, &val);
+
+		if (val & IMX7D_GPR22_PCIE_PHY_PLL_LOCKED)
+			return;
+
+		usleep_range(PHY_PLL_LOCK_WAIT_USLEEP_MIN,
+			     PHY_PLL_LOCK_WAIT_USLEEP_MAX);
+	}
+
+	dev_err(dev, "PCIe PLL lock timeout\n");
+}
+
 static void imx6_pcie_deassert_core_reset(struct imx6_pcie *imx6_pcie)
 {
 	struct dw_pcie *pci = imx6_pcie->pci;
@@ -351,6 +386,10 @@ static void imx6_pcie_deassert_core_reset(struct imx6_pcie *imx6_pcie)
 	}
 
 	switch (imx6_pcie->variant) {
+	case IMX7D:
+		reset_control_deassert(imx6_pcie->pciephy_reset);
+		imx7d_pcie_wait_for_phy_pll_lock(imx6_pcie);
+		break;
 	case IMX6SX:
 		regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR5,
 				   IMX6SX_GPR5_PCIE_BTNRST_RESET, 0);
@@ -377,35 +416,44 @@ static void imx6_pcie_deassert_core_reset(struct imx6_pcie *imx6_pcie)
 
 static void imx6_pcie_init_phy(struct imx6_pcie *imx6_pcie)
 {
-	if (imx6_pcie->variant == IMX6SX)
+	switch (imx6_pcie->variant) {
+	case IMX7D:
+		regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12,
+				   IMX7D_GPR12_PCIE_PHY_REFCLK_SEL, 0);
+		break;
+	case IMX6SX:
 		regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12,
 				   IMX6SX_GPR12_PCIE_RX_EQ_MASK,
 				   IMX6SX_GPR12_PCIE_RX_EQ_2);
+		/* FALLTHROUGH */
+	default:
+		regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12,
+				   IMX6Q_GPR12_PCIE_CTL_2, 0 << 10);
 
-	regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12,
-			IMX6Q_GPR12_PCIE_CTL_2, 0 << 10);
+		/* configure constant input signal to the pcie ctrl and phy */
+		regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12,
+				   IMX6Q_GPR12_LOS_LEVEL, 9 << 4);
+
+		regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8,
+				   IMX6Q_GPR8_TX_DEEMPH_GEN1,
+				   imx6_pcie->tx_deemph_gen1 << 0);
+		regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8,
+				   IMX6Q_GPR8_TX_DEEMPH_GEN2_3P5DB,
+				   imx6_pcie->tx_deemph_gen2_3p5db << 6);
+		regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8,
+				   IMX6Q_GPR8_TX_DEEMPH_GEN2_6DB,
+				   imx6_pcie->tx_deemph_gen2_6db << 12);
+		regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8,
+				   IMX6Q_GPR8_TX_SWING_FULL,
+				   imx6_pcie->tx_swing_full << 18);
+		regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8,
+				   IMX6Q_GPR8_TX_SWING_LOW,
+				   imx6_pcie->tx_swing_low << 25);
+		break;
+	}
 
-	/* configure constant input signal to the pcie ctrl and phy */
 	regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12,
 			IMX6Q_GPR12_DEVICE_TYPE, PCI_EXP_TYPE_ROOT_PORT << 12);
-	regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12,
-			IMX6Q_GPR12_LOS_LEVEL, 9 << 4);
-
-	regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8,
-			   IMX6Q_GPR8_TX_DEEMPH_GEN1,
-			   imx6_pcie->tx_deemph_gen1 << 0);
-	regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8,
-			   IMX6Q_GPR8_TX_DEEMPH_GEN2_3P5DB,
-			   imx6_pcie->tx_deemph_gen2_3p5db << 6);
-	regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8,
-			   IMX6Q_GPR8_TX_DEEMPH_GEN2_6DB,
-			   imx6_pcie->tx_deemph_gen2_6db << 12);
-	regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8,
-			   IMX6Q_GPR8_TX_SWING_FULL,
-			   imx6_pcie->tx_swing_full << 18);
-	regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8,
-			   IMX6Q_GPR8_TX_SWING_LOW,
-			   imx6_pcie->tx_swing_low << 25);
 }
 
 static int imx6_pcie_wait_for_link(struct imx6_pcie *imx6_pcie)
@@ -469,8 +517,11 @@ static int imx6_pcie_establish_link(struct imx6_pcie *imx6_pcie)
 	dw_pcie_writel_dbi(pci, PCIE_RC_LCR, tmp);
 
 	/* Start LTSSM. */
-	regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12,
-			IMX6Q_GPR12_PCIE_CTL_2, 1 << 10);
+	if (imx6_pcie->variant == IMX7D)
+		reset_control_deassert(imx6_pcie->apps_reset);
+	else
+		regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12,
+				   IMX6Q_GPR12_PCIE_CTL_2, 1 << 10);
 
 	ret = imx6_pcie_wait_for_link(imx6_pcie);
 	if (ret)
@@ -653,13 +704,31 @@ static int __init imx6_pcie_probe(struct platform_device *pdev)
 		return PTR_ERR(imx6_pcie->pcie);
 	}
 
-	if (imx6_pcie->variant == IMX6SX) {
+	switch (imx6_pcie->variant) {
+	case IMX6SX:
 		imx6_pcie->pcie_inbound_axi = devm_clk_get(dev,
 							   "pcie_inbound_axi");
 		if (IS_ERR(imx6_pcie->pcie_inbound_axi)) {
 			dev_err(dev, "pcie_inbound_axi clock missing or invalid\n");
 			return PTR_ERR(imx6_pcie->pcie_inbound_axi);
 		}
+		break;
+	case IMX7D:
+		imx6_pcie->pciephy_reset = devm_reset_control_get(dev,
+								  "pciephy");
+		if (IS_ERR(imx6_pcie->pciephy_reset)) {
+			dev_err(dev, "Failed to get PCIEPHY reset contol\n");
+			return PTR_ERR(imx6_pcie->pciephy_reset);
+		}
+
+		imx6_pcie->apps_reset = devm_reset_control_get(dev, "apps");
+		if (IS_ERR(imx6_pcie->apps_reset)) {
+			dev_err(dev, "Failed to get PCIE APPS reset contol\n");
+			return PTR_ERR(imx6_pcie->apps_reset);
+		}
+		break;
+	default:
+		break;
 	}
 
 	/* Grab GPR config register range */
@@ -718,6 +787,7 @@ static const struct of_device_id imx6_pcie_of_match[] = {
 	{ .compatible = "fsl,imx6q-pcie",  .data = (void *)IMX6Q,  },
 	{ .compatible = "fsl,imx6sx-pcie", .data = (void *)IMX6SX, },
 	{ .compatible = "fsl,imx6qp-pcie", .data = (void *)IMX6QP, },
+	{ .compatible = "fsl,imx7d-pcie",  .data = (void *)IMX7D,  },
 	{},
 };
 
diff --git a/include/linux/mfd/syscon/imx7-iomuxc-gpr.h b/include/linux/mfd/syscon/imx7-iomuxc-gpr.h
index 4585d61..abbd524 100644
--- a/include/linux/mfd/syscon/imx7-iomuxc-gpr.h
+++ b/include/linux/mfd/syscon/imx7-iomuxc-gpr.h
@@ -44,4 +44,8 @@
 
 #define IMX7D_GPR5_CSI_MUX_CONTROL_MIPI		(0x1 << 4)
 
+#define IMX7D_GPR12_PCIE_PHY_REFCLK_SEL		BIT(5)
+
+#define IMX7D_GPR22_PCIE_PHY_PLL_LOCKED		BIT(31)
+
 #endif /* __LINUX_IMX7_IOMUXC_GPR_H */
-- 
2.9.3

^ permalink raw reply related

* Re: [PATCH v2,1/3] meson_uart: expose CLKID_UARTx
From: Jerome Brunet @ 2017-03-28 15:51 UTC (permalink / raw)
  To: Helmut Klein, mturquette-rdvid1DuHRBWk0Htik3J/w,
	sboyd-sgV2jX0FEOL9JmXXK+q4OQ
  Cc: devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-amlogic-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-clk-u79uwXL29TY76Z2rM5mHXA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
In-Reply-To: <20170328092545.4644-2-hgkr.klein-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>

On Tue, 2017-03-28 at 11:25 +0200, Helmut Klein wrote:
> Expose the clock ids for the three none AO uarts to the dt-bindings

Are they all used in the device tree ?
We try to only expose what we need in the DT
The recent discussion over CLKID_CPUCLK proved it was a sane thing to do.

> 
> Signed-off-by: Helmut Klein <hgkr.klein-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> ---
>  drivers/clk/meson/gxbb.h              | 6 +++---
>  include/dt-bindings/clock/gxbb-clkc.h | 3 +++
>  2 files changed, 6 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/clk/meson/gxbb.h b/drivers/clk/meson/gxbb.h
> index 8ee2022ce5d5..1edfaa5fe307 100644
> --- a/drivers/clk/meson/gxbb.h
> +++ b/drivers/clk/meson/gxbb.h
> @@ -194,7 +194,7 @@
>  /* #define CLKID_SAR_ADC */
>  #define CLKID_SMART_CARD	  24
>  #define CLKID_RNG0		  25
> -#define CLKID_UART0		  26
> +/* CLKID_UART0 */
>  #define CLKID_SDHC		  27
>  #define CLKID_STREAM		  28
>  #define CLKID_ASYNC_FIFO	  29
> @@ -216,7 +216,7 @@
>  #define CLKID_ADC		  45
>  #define CLKID_BLKMV		  46
>  #define CLKID_AIU		  47
> -#define CLKID_UART1		  48
> +/* CLKID_UART1 */
>  #define CLKID_G2D		  49
>  /* CLKID_USB0 */
>  /* CLKID_USB1 */
> @@ -236,7 +236,7 @@
>  /* CLKID_USB0_DDR_BRIDGE */
>  #define CLKID_MMC_PCLK		  66
>  #define CLKID_DVIN		  67
> -#define CLKID_UART2		  68
> +/* CLKID_UART2 */
>  /* #define CLKID_SANA */
>  #define CLKID_VPU_INTR		  70
>  #define CLKID_SEC_AHB_AHB3_BRIDGE 71
> diff --git a/include/dt-bindings/clock/gxbb-clkc.h b/include/dt-
> bindings/clock/gxbb-clkc.h
> index 692846c7941b..7b329df47752 100644
> --- a/include/dt-bindings/clock/gxbb-clkc.h
> +++ b/include/dt-bindings/clock/gxbb-clkc.h
> @@ -15,13 +15,16 @@
>  #define CLKID_SPI		34
>  #define CLKID_I2C		22
>  #define CLKID_SAR_ADC		23
> +#define CLKID_UART0		26
>  #define CLKID_ETH		36
> +#define CLKID_UART1		48
>  #define CLKID_USB0		50
>  #define CLKID_USB1		51
>  #define CLKID_USB		55
>  #define CLKID_HDMI_PCLK		63
>  #define CLKID_USB1_DDR_BRIDGE	64
>  #define CLKID_USB0_DDR_BRIDGE	65
> +#define CLKID_UART2		68
>  #define CLKID_SANA		69
>  #define CLKID_GCLK_VENCI_INT0	77
>  #define CLKID_AO_I2C		93
> --
> 2.11.0
> 
> 
> _______________________________________________
> linux-amlogic mailing list
> linux-amlogic-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r@public.gmane.org
> http://lists.infradead.org/mailman/listinfo/linux-amlogic
--
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

* [PATCHv3 00/10] Nokia H4+ support
From: Sebastian Reichel @ 2017-03-28 15:59 UTC (permalink / raw)
  To: Sebastian Reichel, Marcel Holtmann, Gustavo Padovan,
	Johan Hedberg, Rob Herring
  Cc: Samuel Thibault, Pavel Machek, Tony Lindgren, Greg Kroah-Hartman,
	Jiri Slaby, Mark Rutland, linux-bluetooth, linux-serial,
	devicetree, linux-kernel

Hi,

Here is PATCHv3 for the Nokia bluetooth patchset. I addressed all comments from
Rob and Pavel regarding the serdev patches and dropped the *.dts patches, since
they were queued by Tony. I also changed the patch order, so that the serdev
patches come first. All of them have Acked-by from Rob, so I think it makes
sense to merge them to serdev subsystem (now) and provide an immutable branch
for the bluetooth subsystem.

The patchset is based upon 4.11-rc1 and is also available from the following
branch:

https://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-n900.git/log/?h=nokia-bt-serdev-v3

-- Sebastian

Rob Herring (1):
  Bluetooth: hci_uart: add serdev driver support library

Sebastian Reichel (9):
  tty: serial: omap: add UPF_BOOT_AUTOCONF flag for DT init
  serdev: add serdev_device_wait_until_sent
  serdev: implement get/set tiocm
  serdev: add helpers for cts and rts handling
  Bluetooth: hci_uart: add support for word alignment
  Bluetooth: hci_serdev: do not open device in hci open
  Bluetooth: hci_serdev: allow modular drivers
  dt-bindings: net: bluetooth: Add nokia-bluetooth
  Bluetooth: add nokia driver

 .../devicetree/bindings/net/nokia-bluetooth.txt    |  51 ++
 drivers/bluetooth/Kconfig                          |  12 +
 drivers/bluetooth/Makefile                         |   3 +
 drivers/bluetooth/hci_h4.c                         |  17 +
 drivers/bluetooth/hci_ldisc.c                      |   5 +
 drivers/bluetooth/hci_nokia.c                      | 819 +++++++++++++++++++++
 drivers/bluetooth/hci_serdev.c                     | 356 +++++++++
 drivers/bluetooth/hci_uart.h                       |   7 +
 drivers/tty/serdev/core.c                          |  33 +
 drivers/tty/serdev/serdev-ttyport.c                |  42 +-
 drivers/tty/serial/omap-serial.c                   |   3 +
 include/linux/serdev.h                             |  47 ++
 12 files changed, 1391 insertions(+), 4 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/net/nokia-bluetooth.txt
 create mode 100644 drivers/bluetooth/hci_nokia.c
 create mode 100644 drivers/bluetooth/hci_serdev.c

-- 
2.11.0

^ permalink raw reply

* [PATCHv3 01/10] tty: serial: omap: add UPF_BOOT_AUTOCONF flag for DT init
From: Sebastian Reichel @ 2017-03-28 15:59 UTC (permalink / raw)
  To: Sebastian Reichel, Marcel Holtmann, Gustavo Padovan,
	Johan Hedberg, Rob Herring
  Cc: Samuel Thibault, Pavel Machek, Tony Lindgren, Greg Kroah-Hartman,
	Jiri Slaby, Mark Rutland, linux-bluetooth, linux-serial,
	devicetree, linux-kernel
In-Reply-To: <20170328155939.31566-1-sre@kernel.org>

The UPF_BOOT_AUTOCONF flag is needed for proper
flow control support.

Acked-by: Pavel Machek <pavel@ucw.cz>
Signed-off-by: Sebastian Reichel <sre@kernel.org>
---
Hi,

This patch can be queued independently - there is no compile time dependency
regarding nokia-bluetooth. Also 8250_omap.c can be used with nokia-bluetooth
(I use this driver nowadays), which does flow control right already.

-- Sebastian
---
 drivers/tty/serial/omap-serial.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/drivers/tty/serial/omap-serial.c b/drivers/tty/serial/omap-serial.c
index 6c6f82ad8d5c..a4734649a0f0 100644
--- a/drivers/tty/serial/omap-serial.c
+++ b/drivers/tty/serial/omap-serial.c
@@ -1597,6 +1597,9 @@ static struct omap_uart_port_info *of_get_uart_port_info(struct device *dev)
 
 	of_property_read_u32(dev->of_node, "clock-frequency",
 					 &omap_up_info->uartclk);
+
+	omap_up_info->flags = UPF_BOOT_AUTOCONF;
+
 	return omap_up_info;
 }
 
-- 
2.11.0

^ permalink raw reply related

* [PATCHv3 02/10] serdev: add serdev_device_wait_until_sent
From: Sebastian Reichel @ 2017-03-28 15:59 UTC (permalink / raw)
  To: Sebastian Reichel, Marcel Holtmann, Gustavo Padovan,
	Johan Hedberg, Rob Herring
  Cc: Samuel Thibault, Pavel Machek, Tony Lindgren, Greg Kroah-Hartman,
	Jiri Slaby, Mark Rutland, linux-bluetooth-u79uwXL29TY76Z2rM5mHXA,
	linux-serial-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <20170328155939.31566-1-sre-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>

Add method, which waits until the transmission buffer has been sent.
Note, that the change in ttyport_write_wakeup is related, since
tty_wait_until_sent will hang without that change.

Acked-by: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
Acked-by: Pavel Machek <pavel-+ZI9xUNit7I@public.gmane.org>
Signed-off-by: Sebastian Reichel <sre-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
---
Changes since PATCHv2:
 * Avoid goto in ttyport_write_wakeup
---
 drivers/tty/serdev/core.c           | 11 +++++++++++
 drivers/tty/serdev/serdev-ttyport.c | 18 ++++++++++++++----
 include/linux/serdev.h              |  3 +++
 3 files changed, 28 insertions(+), 4 deletions(-)

diff --git a/drivers/tty/serdev/core.c b/drivers/tty/serdev/core.c
index f4c6c90add78..a63b74031e22 100644
--- a/drivers/tty/serdev/core.c
+++ b/drivers/tty/serdev/core.c
@@ -173,6 +173,17 @@ void serdev_device_set_flow_control(struct serdev_device *serdev, bool enable)
 }
 EXPORT_SYMBOL_GPL(serdev_device_set_flow_control);
 
+void serdev_device_wait_until_sent(struct serdev_device *serdev, long timeout)
+{
+	struct serdev_controller *ctrl = serdev->ctrl;
+
+	if (!ctrl || !ctrl->ops->wait_until_sent)
+		return;
+
+	ctrl->ops->wait_until_sent(ctrl, timeout);
+}
+EXPORT_SYMBOL_GPL(serdev_device_wait_until_sent);
+
 static int serdev_drv_probe(struct device *dev)
 {
 	const struct serdev_device_driver *sdrv = to_serdev_device_driver(dev->driver);
diff --git a/drivers/tty/serdev/serdev-ttyport.c b/drivers/tty/serdev/serdev-ttyport.c
index d05393594f15..50dc75c4d204 100644
--- a/drivers/tty/serdev/serdev-ttyport.c
+++ b/drivers/tty/serdev/serdev-ttyport.c
@@ -14,6 +14,7 @@
 #include <linux/serdev.h>
 #include <linux/tty.h>
 #include <linux/tty_driver.h>
+#include <linux/poll.h>
 
 #define SERPORT_ACTIVE		1
 
@@ -46,11 +47,11 @@ static void ttyport_write_wakeup(struct tty_port *port)
 	struct serdev_controller *ctrl = port->client_data;
 	struct serport *serport = serdev_controller_get_drvdata(ctrl);
 
-	if (!test_and_clear_bit(TTY_DO_WRITE_WAKEUP, &port->tty->flags))
-		return;
-
-	if (test_bit(SERPORT_ACTIVE, &serport->flags))
+	if (test_and_clear_bit(TTY_DO_WRITE_WAKEUP, &port->tty->flags) &&
+	    test_bit(SERPORT_ACTIVE, &serport->flags))
 		serdev_controller_write_wakeup(ctrl);
+
+	wake_up_interruptible_poll(&port->tty->write_wait, POLLOUT);
 }
 
 static const struct tty_port_client_operations client_ops = {
@@ -167,6 +168,14 @@ static void ttyport_set_flow_control(struct serdev_controller *ctrl, bool enable
 	tty_set_termios(tty, &ktermios);
 }
 
+static void ttyport_wait_until_sent(struct serdev_controller *ctrl, long timeout)
+{
+	struct serport *serport = serdev_controller_get_drvdata(ctrl);
+	struct tty_struct *tty = serport->tty;
+
+	tty_wait_until_sent(tty, timeout);
+}
+
 static const struct serdev_controller_ops ctrl_ops = {
 	.write_buf = ttyport_write_buf,
 	.write_flush = ttyport_write_flush,
@@ -175,6 +184,7 @@ static const struct serdev_controller_ops ctrl_ops = {
 	.close = ttyport_close,
 	.set_flow_control = ttyport_set_flow_control,
 	.set_baudrate = ttyport_set_baudrate,
+	.wait_until_sent = ttyport_wait_until_sent,
 };
 
 struct device *serdev_tty_port_register(struct tty_port *port,
diff --git a/include/linux/serdev.h b/include/linux/serdev.h
index 9519da6253a8..a308b206d204 100644
--- a/include/linux/serdev.h
+++ b/include/linux/serdev.h
@@ -81,6 +81,7 @@ struct serdev_controller_ops {
 	void (*close)(struct serdev_controller *);
 	void (*set_flow_control)(struct serdev_controller *, bool);
 	unsigned int (*set_baudrate)(struct serdev_controller *, unsigned int);
+	void (*wait_until_sent)(struct serdev_controller *, long);
 };
 
 /**
@@ -186,6 +187,7 @@ int serdev_device_open(struct serdev_device *);
 void serdev_device_close(struct serdev_device *);
 unsigned int serdev_device_set_baudrate(struct serdev_device *, unsigned int);
 void serdev_device_set_flow_control(struct serdev_device *, bool);
+void serdev_device_wait_until_sent(struct serdev_device *, long);
 int serdev_device_write_buf(struct serdev_device *, const unsigned char *, size_t);
 void serdev_device_write_flush(struct serdev_device *);
 int serdev_device_write_room(struct serdev_device *);
@@ -223,6 +225,7 @@ static inline unsigned int serdev_device_set_baudrate(struct serdev_device *sdev
 	return 0;
 }
 static inline void serdev_device_set_flow_control(struct serdev_device *sdev, bool enable) {}
+static inline void serdev_device_wait_until_sent(struct serdev_device *sdev, long timeout) {}
 static inline int serdev_device_write_buf(struct serdev_device *sdev, const unsigned char *buf, size_t count)
 {
 	return -ENODEV;
-- 
2.11.0

^ permalink raw reply related

* [PATCHv3 03/10] serdev: implement get/set tiocm
From: Sebastian Reichel @ 2017-03-28 15:59 UTC (permalink / raw)
  To: Sebastian Reichel, Marcel Holtmann, Gustavo Padovan,
	Johan Hedberg, Rob Herring
  Cc: Samuel Thibault, Pavel Machek, Tony Lindgren, Greg Kroah-Hartman,
	Jiri Slaby, Mark Rutland, linux-bluetooth, linux-serial,
	devicetree, linux-kernel
In-Reply-To: <20170328155939.31566-1-sre@kernel.org>

Add method for getting and setting tiocm.

Acked-by: Pavel Machek <pavel@ucw.cz>
Acked-by: Rob Herring <robh@kernel.org>
Signed-off-by: Sebastian Reichel <sre@kernel.org>
---
 drivers/tty/serdev/core.c           | 22 ++++++++++++++++++++++
 drivers/tty/serdev/serdev-ttyport.c | 24 ++++++++++++++++++++++++
 include/linux/serdev.h              | 13 +++++++++++++
 3 files changed, 59 insertions(+)

diff --git a/drivers/tty/serdev/core.c b/drivers/tty/serdev/core.c
index a63b74031e22..1e1cbae3a0ea 100644
--- a/drivers/tty/serdev/core.c
+++ b/drivers/tty/serdev/core.c
@@ -184,6 +184,28 @@ void serdev_device_wait_until_sent(struct serdev_device *serdev, long timeout)
 }
 EXPORT_SYMBOL_GPL(serdev_device_wait_until_sent);
 
+int serdev_device_get_tiocm(struct serdev_device *serdev)
+{
+	struct serdev_controller *ctrl = serdev->ctrl;
+
+	if (!ctrl || !ctrl->ops->get_tiocm)
+		return -ENOTSUPP;
+
+	return ctrl->ops->get_tiocm(ctrl);
+}
+EXPORT_SYMBOL_GPL(serdev_device_get_tiocm);
+
+int serdev_device_set_tiocm(struct serdev_device *serdev, int set, int clear)
+{
+	struct serdev_controller *ctrl = serdev->ctrl;
+
+	if (!ctrl || !ctrl->ops->set_tiocm)
+		return -ENOTSUPP;
+
+	return ctrl->ops->set_tiocm(ctrl, set, clear);
+}
+EXPORT_SYMBOL_GPL(serdev_device_set_tiocm);
+
 static int serdev_drv_probe(struct device *dev)
 {
 	const struct serdev_device_driver *sdrv = to_serdev_device_driver(dev->driver);
diff --git a/drivers/tty/serdev/serdev-ttyport.c b/drivers/tty/serdev/serdev-ttyport.c
index 50dc75c4d204..487c88f6aa0e 100644
--- a/drivers/tty/serdev/serdev-ttyport.c
+++ b/drivers/tty/serdev/serdev-ttyport.c
@@ -176,6 +176,28 @@ static void ttyport_wait_until_sent(struct serdev_controller *ctrl, long timeout
 	tty_wait_until_sent(tty, timeout);
 }
 
+static int ttyport_get_tiocm(struct serdev_controller *ctrl)
+{
+	struct serport *serport = serdev_controller_get_drvdata(ctrl);
+	struct tty_struct *tty = serport->tty;
+
+	if (!tty->ops->tiocmget)
+		return -ENOTSUPP;
+
+	return tty->driver->ops->tiocmget(tty);
+}
+
+static int ttyport_set_tiocm(struct serdev_controller *ctrl, unsigned int set, unsigned int clear)
+{
+	struct serport *serport = serdev_controller_get_drvdata(ctrl);
+	struct tty_struct *tty = serport->tty;
+
+	if (!tty->ops->tiocmset)
+		return -ENOTSUPP;
+
+	return tty->driver->ops->tiocmset(tty, set, clear);
+}
+
 static const struct serdev_controller_ops ctrl_ops = {
 	.write_buf = ttyport_write_buf,
 	.write_flush = ttyport_write_flush,
@@ -185,6 +207,8 @@ static const struct serdev_controller_ops ctrl_ops = {
 	.set_flow_control = ttyport_set_flow_control,
 	.set_baudrate = ttyport_set_baudrate,
 	.wait_until_sent = ttyport_wait_until_sent,
+	.get_tiocm = ttyport_get_tiocm,
+	.set_tiocm = ttyport_set_tiocm,
 };
 
 struct device *serdev_tty_port_register(struct tty_port *port,
diff --git a/include/linux/serdev.h b/include/linux/serdev.h
index a308b206d204..e29a270f603c 100644
--- a/include/linux/serdev.h
+++ b/include/linux/serdev.h
@@ -15,6 +15,7 @@
 
 #include <linux/types.h>
 #include <linux/device.h>
+#include <linux/termios.h>
 
 struct serdev_controller;
 struct serdev_device;
@@ -82,6 +83,8 @@ struct serdev_controller_ops {
 	void (*set_flow_control)(struct serdev_controller *, bool);
 	unsigned int (*set_baudrate)(struct serdev_controller *, unsigned int);
 	void (*wait_until_sent)(struct serdev_controller *, long);
+	int (*get_tiocm)(struct serdev_controller *);
+	int (*set_tiocm)(struct serdev_controller *, unsigned int, unsigned int);
 };
 
 /**
@@ -188,6 +191,8 @@ void serdev_device_close(struct serdev_device *);
 unsigned int serdev_device_set_baudrate(struct serdev_device *, unsigned int);
 void serdev_device_set_flow_control(struct serdev_device *, bool);
 void serdev_device_wait_until_sent(struct serdev_device *, long);
+int serdev_device_get_tiocm(struct serdev_device *);
+int serdev_device_set_tiocm(struct serdev_device *, int, int);
 int serdev_device_write_buf(struct serdev_device *, const unsigned char *, size_t);
 void serdev_device_write_flush(struct serdev_device *);
 int serdev_device_write_room(struct serdev_device *);
@@ -226,6 +231,14 @@ static inline unsigned int serdev_device_set_baudrate(struct serdev_device *sdev
 }
 static inline void serdev_device_set_flow_control(struct serdev_device *sdev, bool enable) {}
 static inline void serdev_device_wait_until_sent(struct serdev_device *sdev, long timeout) {}
+static inline int serdev_device_get_tiocm(struct serdev_device *serdev)
+{
+	return -ENOTSUPP;
+}
+static inline int serdev_device_set_tiocm(struct serdev_device *serdev, int set, int clear)
+{
+	return -ENOTSUPP;
+}
 static inline int serdev_device_write_buf(struct serdev_device *sdev, const unsigned char *buf, size_t count)
 {
 	return -ENODEV;
-- 
2.11.0

^ permalink raw reply related

* [PATCHv3 04/10] serdev: add helpers for cts and rts handling
From: Sebastian Reichel @ 2017-03-28 15:59 UTC (permalink / raw)
  To: Sebastian Reichel, Marcel Holtmann, Gustavo Padovan,
	Johan Hedberg, Rob Herring
  Cc: Samuel Thibault, Pavel Machek, Tony Lindgren, Greg Kroah-Hartman,
	Jiri Slaby, Mark Rutland, linux-bluetooth, linux-serial,
	devicetree, linux-kernel
In-Reply-To: <20170328155939.31566-1-sre@kernel.org>

Add serdev helper functions for handling of cts and rts
lines using the serdev's tiocm functions.

Acked-by: Rob Herring <robh@kernel.org>
Signed-off-by: Sebastian Reichel <sre@kernel.org>
---
Changes since PATCHv2:
 * use time_is_after_jiffies(xyz) instead of !time_after(jiffies, xyz)
 * drop OUT2 handling in rts function
---
 include/linux/serdev.h | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)

diff --git a/include/linux/serdev.h b/include/linux/serdev.h
index e29a270f603c..37395b8eb8f1 100644
--- a/include/linux/serdev.h
+++ b/include/linux/serdev.h
@@ -16,6 +16,7 @@
 #include <linux/types.h>
 #include <linux/device.h>
 #include <linux/termios.h>
+#include <linux/delay.h>
 
 struct serdev_controller;
 struct serdev_device;
@@ -254,6 +255,36 @@ static inline int serdev_device_write_room(struct serdev_device *sdev)
 
 #endif /* CONFIG_SERIAL_DEV_BUS */
 
+static inline bool serdev_device_get_cts(struct serdev_device *serdev)
+{
+	int status = serdev_device_get_tiocm(serdev);
+	return !!(status & TIOCM_CTS);
+}
+
+static inline int serdev_device_wait_for_cts(struct serdev_device *serdev, bool state, int timeout_ms)
+{
+	unsigned long timeout;
+	bool signal;
+
+	timeout = jiffies + msecs_to_jiffies(timeout_ms);
+	while (time_is_after_jiffies(timeout)) {
+		signal = serdev_device_get_cts(serdev);
+		if (signal == state)
+			return 0;
+		usleep_range(1000, 2000);
+	}
+
+	return -ETIMEDOUT;
+}
+
+static inline int serdev_device_set_rts(struct serdev_device *serdev, bool enable)
+{
+	if (enable)
+		return serdev_device_set_tiocm(serdev, TIOCM_RTS, 0);
+	else
+		return serdev_device_set_tiocm(serdev, 0, TIOCM_RTS);
+}
+
 /*
  * serdev hooks into TTY core
  */
-- 
2.11.0

^ permalink raw reply related

* [PATCHv3 05/10] Bluetooth: hci_uart: add support for word alignment
From: Sebastian Reichel @ 2017-03-28 15:59 UTC (permalink / raw)
  To: Sebastian Reichel, Marcel Holtmann, Gustavo Padovan,
	Johan Hedberg, Rob Herring
  Cc: Samuel Thibault, Pavel Machek, Tony Lindgren, Greg Kroah-Hartman,
	Jiri Slaby, Mark Rutland, linux-bluetooth, linux-serial,
	devicetree, linux-kernel
In-Reply-To: <20170328155939.31566-1-sre@kernel.org>

This will be used by Nokia's H4+ protocol, which
uses 2-byte aligned packets.

Acked-by: Pavel Machek <pavel@ucw.cz>
Signed-off-by: Sebastian Reichel <sre@kernel.org>
---
Changes since PATCHv1:
 * use u8 instead of uint8_t
---
 drivers/bluetooth/hci_h4.c    | 17 +++++++++++++++++
 drivers/bluetooth/hci_ldisc.c |  4 ++++
 drivers/bluetooth/hci_uart.h  |  3 +++
 3 files changed, 24 insertions(+)

diff --git a/drivers/bluetooth/hci_h4.c b/drivers/bluetooth/hci_h4.c
index 635597b6e168..82e5a32b87a4 100644
--- a/drivers/bluetooth/hci_h4.c
+++ b/drivers/bluetooth/hci_h4.c
@@ -171,9 +171,20 @@ struct sk_buff *h4_recv_buf(struct hci_dev *hdev, struct sk_buff *skb,
 			    const unsigned char *buffer, int count,
 			    const struct h4_recv_pkt *pkts, int pkts_count)
 {
+	struct hci_uart *hu = hci_get_drvdata(hdev);
+	u8 alignment = hu->alignment;
+
 	while (count) {
 		int i, len;
 
+		/* remove padding bytes from buffer */
+		for (; hu->padding && count > 0; hu->padding--) {
+			count--;
+			buffer++;
+		}
+		if (!count)
+			break;
+
 		if (!skb) {
 			for (i = 0; i < pkts_count; i++) {
 				if (buffer[0] != (&pkts[i])->type)
@@ -253,11 +264,17 @@ struct sk_buff *h4_recv_buf(struct hci_dev *hdev, struct sk_buff *skb,
 			}
 
 			if (!dlen) {
+				hu->padding = (skb->len - 1) % alignment;
+				hu->padding = (alignment - hu->padding) % alignment;
+
 				/* No more data, complete frame */
 				(&pkts[i])->recv(hdev, skb);
 				skb = NULL;
 			}
 		} else {
+			hu->padding = (skb->len - 1) % alignment;
+			hu->padding = (alignment - hu->padding) % alignment;
+
 			/* Complete frame */
 			(&pkts[i])->recv(hdev, skb);
 			skb = NULL;
diff --git a/drivers/bluetooth/hci_ldisc.c b/drivers/bluetooth/hci_ldisc.c
index 9497c469efd2..0ec8a94bd712 100644
--- a/drivers/bluetooth/hci_ldisc.c
+++ b/drivers/bluetooth/hci_ldisc.c
@@ -459,6 +459,10 @@ static int hci_uart_tty_open(struct tty_struct *tty)
 	hu->tty = tty;
 	tty->receive_room = 65536;
 
+	/* disable alignment support by default */
+	hu->alignment = 1;
+	hu->padding = 0;
+
 	INIT_WORK(&hu->init_ready, hci_uart_init_work);
 	INIT_WORK(&hu->write_work, hci_uart_write_work);
 
diff --git a/drivers/bluetooth/hci_uart.h b/drivers/bluetooth/hci_uart.h
index 070139513e65..4aff50960cac 100644
--- a/drivers/bluetooth/hci_uart.h
+++ b/drivers/bluetooth/hci_uart.h
@@ -92,6 +92,9 @@ struct hci_uart {
 
 	unsigned int init_speed;
 	unsigned int oper_speed;
+
+	u8			alignment;
+	u8			padding;
 };
 
 /* HCI_UART proto flag bits */
-- 
2.11.0

^ permalink raw reply related

* [PATCHv3 06/10] Bluetooth: hci_uart: add serdev driver support library
From: Sebastian Reichel @ 2017-03-28 15:59 UTC (permalink / raw)
  To: Sebastian Reichel, Marcel Holtmann, Gustavo Padovan,
	Johan Hedberg, Rob Herring
  Cc: Samuel Thibault, Pavel Machek, Tony Lindgren, Greg Kroah-Hartman,
	Jiri Slaby, Mark Rutland, linux-bluetooth-u79uwXL29TY76Z2rM5mHXA,
	linux-serial-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA, Rob Herring
In-Reply-To: <20170328155939.31566-1-sre-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>

From: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>

This adds library functions for serdev based BT drivers. This is largely
copied from hci_ldisc.c and modified to use serdev calls. There's a little
bit of duplication, but I avoided intermixing this as the ldisc code should
eventually go away.

Signed-off-by: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
Cc: Marcel Holtmann <marcel-kz+m5ild9QBg9hUCZPvPmw@public.gmane.org>
Cc: Gustavo Padovan <gustavo-THi1TnShQwVAfugRpC6u6w@public.gmane.org>
Cc: Johan Hedberg <johan.hedberg-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
Cc: linux-bluetooth-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
Acked-by: Pavel Machek <pavel-+ZI9xUNit7I@public.gmane.org>
[Fix style issues reported by Pavel]
Signed-off-by: Sebastian Reichel <sre-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
---
Changes since PATCHv1:
 * Drop FSF address from license section
 * Add error message if hu->proto->set_baudrate fails
 * Remove useless label/goto in hci_uart_setup
 * Use proper kerneldoc for hci_uart_write_wakeup
 * Use proper kerneldoc for hci_uart_receive_buf
 * Wrap hci_uart_register_device define to get < 80 chars
 * Use do..while instead of goto restart in hci_uart_write_work
 * The file checkpatch clean now
---
 drivers/bluetooth/Makefile     |   1 +
 drivers/bluetooth/hci_serdev.c | 361 +++++++++++++++++++++++++++++++++++++++++
 drivers/bluetooth/hci_uart.h   |   4 +
 3 files changed, 366 insertions(+)
 create mode 100644 drivers/bluetooth/hci_serdev.c

diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile
index 80627187c8b6..fd571689eed6 100644
--- a/drivers/bluetooth/Makefile
+++ b/drivers/bluetooth/Makefile
@@ -29,6 +29,7 @@ btmrvl-y			:= btmrvl_main.o
 btmrvl-$(CONFIG_DEBUG_FS)	+= btmrvl_debugfs.o
 
 hci_uart-y				:= hci_ldisc.o
+hci_uart-$(CONFIG_SERIAL_DEV_BUS)	+= hci_serdev.o
 hci_uart-$(CONFIG_BT_HCIUART_H4)	+= hci_h4.o
 hci_uart-$(CONFIG_BT_HCIUART_BCSP)	+= hci_bcsp.o
 hci_uart-$(CONFIG_BT_HCIUART_LL)	+= hci_ll.o
diff --git a/drivers/bluetooth/hci_serdev.c b/drivers/bluetooth/hci_serdev.c
new file mode 100644
index 000000000000..f5ccb2c7ef92
--- /dev/null
+++ b/drivers/bluetooth/hci_serdev.c
@@ -0,0 +1,361 @@
+/*
+ *  Bluetooth HCI serdev driver lib
+ *
+ *  Copyright (C) 2017  Linaro, Ltd., Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
+ *
+ *  Based on hci_ldisc.c:
+ *
+ *  Copyright (C) 2000-2001  Qualcomm Incorporated
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk-zC7DfRvBq/JWk0Htik3J/w@public.gmane.org>
+ *  Copyright (C) 2004-2005  Marcel Holtmann <marcel-kz+m5ild9QBg9hUCZPvPmw@public.gmane.org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/serdev.h>
+#include <linux/skbuff.h>
+
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci_core.h>
+
+#include "hci_uart.h"
+
+struct serdev_device_ops hci_serdev_client_ops;
+
+static inline void hci_uart_tx_complete(struct hci_uart *hu, int pkt_type)
+{
+	struct hci_dev *hdev = hu->hdev;
+
+	/* Update HCI stat counters */
+	switch (pkt_type) {
+	case HCI_COMMAND_PKT:
+		hdev->stat.cmd_tx++;
+		break;
+
+	case HCI_ACLDATA_PKT:
+		hdev->stat.acl_tx++;
+		break;
+
+	case HCI_SCODATA_PKT:
+		hdev->stat.sco_tx++;
+		break;
+	}
+}
+
+static inline struct sk_buff *hci_uart_dequeue(struct hci_uart *hu)
+{
+	struct sk_buff *skb = hu->tx_skb;
+
+	if (!skb)
+		skb = hu->proto->dequeue(hu);
+	else
+		hu->tx_skb = NULL;
+
+	return skb;
+}
+
+static void hci_uart_write_work(struct work_struct *work)
+{
+	struct hci_uart *hu = container_of(work, struct hci_uart, write_work);
+	struct serdev_device *serdev = hu->serdev;
+	struct hci_dev *hdev = hu->hdev;
+	struct sk_buff *skb;
+
+	/* REVISIT:
+	 * should we cope with bad skbs or ->write() returning an error value?
+	 */
+	do {
+		clear_bit(HCI_UART_TX_WAKEUP, &hu->tx_state);
+
+		while ((skb = hci_uart_dequeue(hu))) {
+			int len;
+
+			len = serdev_device_write_buf(serdev,
+						      skb->data, skb->len);
+			hdev->stat.byte_tx += len;
+
+			skb_pull(skb, len);
+			if (skb->len) {
+				hu->tx_skb = skb;
+				break;
+			}
+
+			hci_uart_tx_complete(hu, hci_skb_pkt_type(skb));
+			kfree_skb(skb);
+		}
+	} while(test_bit(HCI_UART_TX_WAKEUP, &hu->tx_state));
+
+	clear_bit(HCI_UART_SENDING, &hu->tx_state);
+}
+
+/* ------- Interface to HCI layer ------ */
+
+/* Initialize device */
+static int hci_uart_open(struct hci_dev *hdev)
+{
+	struct hci_uart *hu  = hci_get_drvdata(hdev);
+
+	BT_DBG("%s %p", hdev->name, hdev);
+
+	serdev_device_set_client_ops(hu->serdev, &hci_serdev_client_ops);
+
+	return serdev_device_open(hu->serdev);
+}
+
+/* Reset device */
+static int hci_uart_flush(struct hci_dev *hdev)
+{
+	struct hci_uart *hu  = hci_get_drvdata(hdev);
+
+	BT_DBG("hdev %p serdev %p", hdev, hu->serdev);
+
+	if (hu->tx_skb) {
+		kfree_skb(hu->tx_skb); hu->tx_skb = NULL;
+	}
+
+	/* Flush any pending characters in the driver and discipline. */
+	serdev_device_write_flush(hu->serdev);
+
+	if (test_bit(HCI_UART_PROTO_READY, &hu->flags))
+		hu->proto->flush(hu);
+
+	return 0;
+}
+
+/* Close device */
+static int hci_uart_close(struct hci_dev *hdev)
+{
+	struct hci_uart *hu  = hci_get_drvdata(hdev);
+
+	BT_DBG("hdev %p", hdev);
+
+	hci_uart_flush(hdev);
+	hdev->flush = NULL;
+
+	serdev_device_close(hu->serdev);
+
+	return 0;
+}
+
+/* Send frames from HCI layer */
+static int hci_uart_send_frame(struct hci_dev *hdev, struct sk_buff *skb)
+{
+	struct hci_uart *hu = hci_get_drvdata(hdev);
+
+	BT_DBG("%s: type %d len %d", hdev->name, hci_skb_pkt_type(skb),
+	       skb->len);
+
+	hu->proto->enqueue(hu, skb);
+
+	hci_uart_tx_wakeup(hu);
+
+	return 0;
+}
+
+static int hci_uart_setup(struct hci_dev *hdev)
+{
+	struct hci_uart *hu = hci_get_drvdata(hdev);
+	struct hci_rp_read_local_version *ver;
+	struct sk_buff *skb;
+	unsigned int speed;
+	int err;
+
+	/* Init speed if any */
+	if (hu->init_speed)
+		speed = hu->init_speed;
+	else if (hu->proto->init_speed)
+		speed = hu->proto->init_speed;
+	else
+		speed = 0;
+
+	if (speed)
+		serdev_device_set_baudrate(hu->serdev, speed);
+
+	/* Operational speed if any */
+	if (hu->oper_speed)
+		speed = hu->oper_speed;
+	else if (hu->proto->oper_speed)
+		speed = hu->proto->oper_speed;
+	else
+		speed = 0;
+
+	if (hu->proto->set_baudrate && speed) {
+		err = hu->proto->set_baudrate(hu, speed);
+		if (err)
+			BT_ERR("%s: failed to set baudrate", hdev->name);
+		else
+			serdev_device_set_baudrate(hu->serdev, speed);
+	}
+
+	if (hu->proto->setup)
+		return hu->proto->setup(hu);
+
+	if (!test_bit(HCI_UART_VND_DETECT, &hu->hdev_flags))
+		return 0;
+
+	skb = __hci_cmd_sync(hdev, HCI_OP_READ_LOCAL_VERSION, 0, NULL,
+			     HCI_INIT_TIMEOUT);
+	if (IS_ERR(skb)) {
+		BT_ERR("%s: Reading local version information failed (%ld)",
+		       hdev->name, PTR_ERR(skb));
+		return 0;
+	}
+
+	if (skb->len != sizeof(*ver)) {
+		BT_ERR("%s: Event length mismatch for version information",
+		       hdev->name);
+	}
+
+	kfree_skb(skb);
+	return 0;
+}
+
+/** hci_uart_write_wakeup - transmit buffer wakeup
+ * @serdev: serial device
+ *
+ * This function is called by the serdev framework when it accepts
+ * more data being sent.
+ */
+static void hci_uart_write_wakeup(struct serdev_device *serdev)
+{
+	struct hci_uart *hu = serdev_device_get_drvdata(serdev);
+
+	BT_DBG("");
+
+	if (!hu || serdev != hu->serdev) {
+		WARN_ON(1);
+		return;
+	}
+
+	if (test_bit(HCI_UART_PROTO_READY, &hu->flags))
+		hci_uart_tx_wakeup(hu);
+}
+
+/** hci_uart_receive_buf - receive buffer wakeup
+ * @serdev: serial device
+ * @data:   pointer to received data
+ * @count:  count of received data in bytes
+ *
+ * This function is called by the serdev framework when it received data
+ * in the RX buffer.
+ *
+ * Return: number of processed bytes
+ */
+static int hci_uart_receive_buf(struct serdev_device *serdev, const u8 *data,
+				   size_t count)
+{
+	struct hci_uart *hu = serdev_device_get_drvdata(serdev);
+
+	if (!hu || serdev != hu->serdev) {
+		WARN_ON(1);
+		return 0;
+	}
+
+	if (!test_bit(HCI_UART_PROTO_READY, &hu->flags))
+		return 0;
+
+	/* It does not need a lock here as it is already protected by a mutex in
+	 * tty caller
+	 */
+	hu->proto->recv(hu, data, count);
+
+	if (hu->hdev)
+		hu->hdev->stat.byte_rx += count;
+
+	return count;
+}
+
+struct serdev_device_ops hci_serdev_client_ops = {
+	.receive_buf = hci_uart_receive_buf,
+	.write_wakeup = hci_uart_write_wakeup,
+};
+
+int hci_uart_register_device(struct hci_uart *hu,
+			     const struct hci_uart_proto *p)
+{
+	int err;
+	struct hci_dev *hdev;
+
+	BT_DBG("");
+
+	err = p->open(hu);
+	if (err)
+		return err;
+
+	hu->proto = p;
+	set_bit(HCI_UART_PROTO_READY, &hu->flags);
+
+	/* Initialize and register HCI device */
+	hdev = hci_alloc_dev();
+	if (!hdev) {
+		BT_ERR("Can't allocate HCI device");
+		err = -ENOMEM;
+		goto err_alloc;
+	}
+
+	hu->hdev = hdev;
+
+	hdev->bus = HCI_UART;
+	hci_set_drvdata(hdev, hu);
+
+	INIT_WORK(&hu->write_work, hci_uart_write_work);
+
+	/* Only when vendor specific setup callback is provided, consider
+	 * the manufacturer information valid. This avoids filling in the
+	 * value for Ericsson when nothing is specified.
+	 */
+	if (hu->proto->setup)
+		hdev->manufacturer = hu->proto->manufacturer;
+
+	hdev->open  = hci_uart_open;
+	hdev->close = hci_uart_close;
+	hdev->flush = hci_uart_flush;
+	hdev->send  = hci_uart_send_frame;
+	hdev->setup = hci_uart_setup;
+	SET_HCIDEV_DEV(hdev, &hu->serdev->dev);
+
+	if (test_bit(HCI_UART_RAW_DEVICE, &hu->hdev_flags))
+		set_bit(HCI_QUIRK_RAW_DEVICE, &hdev->quirks);
+
+	if (test_bit(HCI_UART_EXT_CONFIG, &hu->hdev_flags))
+		set_bit(HCI_QUIRK_EXTERNAL_CONFIG, &hdev->quirks);
+
+	if (!test_bit(HCI_UART_RESET_ON_INIT, &hu->hdev_flags))
+		set_bit(HCI_QUIRK_RESET_ON_CLOSE, &hdev->quirks);
+
+	if (test_bit(HCI_UART_CREATE_AMP, &hu->hdev_flags))
+		hdev->dev_type = HCI_AMP;
+	else
+		hdev->dev_type = HCI_PRIMARY;
+
+	if (test_bit(HCI_UART_INIT_PENDING, &hu->hdev_flags))
+		return 0;
+
+	if (hci_register_dev(hdev) < 0) {
+		BT_ERR("Can't register HCI device");
+		err = -ENODEV;
+		goto err_register;
+	}
+
+	set_bit(HCI_UART_REGISTERED, &hu->flags);
+
+	return 0;
+
+err_register:
+	hci_free_dev(hdev);
+err_alloc:
+	clear_bit(HCI_UART_PROTO_READY, &hu->flags);
+	p->close(hu);
+	return err;
+}
diff --git a/drivers/bluetooth/hci_uart.h b/drivers/bluetooth/hci_uart.h
index 4aff50960cac..1b41c661bbb8 100644
--- a/drivers/bluetooth/hci_uart.h
+++ b/drivers/bluetooth/hci_uart.h
@@ -58,6 +58,7 @@
 #define HCI_UART_VND_DETECT	5
 
 struct hci_uart;
+struct serdev_device;
 
 struct hci_uart_proto {
 	unsigned int id;
@@ -77,6 +78,7 @@ struct hci_uart_proto {
 
 struct hci_uart {
 	struct tty_struct	*tty;
+	struct serdev_device	*serdev;
 	struct hci_dev		*hdev;
 	unsigned long		flags;
 	unsigned long		hdev_flags;
@@ -108,6 +110,8 @@ struct hci_uart {
 
 int hci_uart_register_proto(const struct hci_uart_proto *p);
 int hci_uart_unregister_proto(const struct hci_uart_proto *p);
+int hci_uart_register_device(struct hci_uart *hu, const struct hci_uart_proto *p);
+
 int hci_uart_tx_wakeup(struct hci_uart *hu);
 int hci_uart_init_ready(struct hci_uart *hu);
 void hci_uart_init_tty(struct hci_uart *hu);
-- 
2.11.0

^ permalink raw reply related

* [PATCHv3 07/10] Bluetooth: hci_serdev: do not open device in hci open
From: Sebastian Reichel @ 2017-03-28 15:59 UTC (permalink / raw)
  To: Sebastian Reichel, Marcel Holtmann, Gustavo Padovan,
	Johan Hedberg, Rob Herring
  Cc: Samuel Thibault, Pavel Machek, Tony Lindgren, Greg Kroah-Hartman,
	Jiri Slaby, Mark Rutland, linux-bluetooth, linux-serial,
	devicetree, linux-kernel
In-Reply-To: <20170328155939.31566-1-sre@kernel.org>

The device driver may need to communicate with the UART
device while the Bluetooth device is closed (e.g. due
to interrupts).

Acked-by: Pavel Machek <pavel@ucw.cz>
Signed-off-by: Sebastian Reichel <sre@kernel.org>
---
 drivers/bluetooth/hci_serdev.c | 12 +++---------
 1 file changed, 3 insertions(+), 9 deletions(-)

diff --git a/drivers/bluetooth/hci_serdev.c b/drivers/bluetooth/hci_serdev.c
index f5ccb2c7ef92..3b8ac0ece3fb 100644
--- a/drivers/bluetooth/hci_serdev.c
+++ b/drivers/bluetooth/hci_serdev.c
@@ -104,13 +104,9 @@ static void hci_uart_write_work(struct work_struct *work)
 /* Initialize device */
 static int hci_uart_open(struct hci_dev *hdev)
 {
-	struct hci_uart *hu  = hci_get_drvdata(hdev);
-
 	BT_DBG("%s %p", hdev->name, hdev);
 
-	serdev_device_set_client_ops(hu->serdev, &hci_serdev_client_ops);
-
-	return serdev_device_open(hu->serdev);
+	return 0;
 }
 
 /* Reset device */
@@ -136,15 +132,11 @@ static int hci_uart_flush(struct hci_dev *hdev)
 /* Close device */
 static int hci_uart_close(struct hci_dev *hdev)
 {
-	struct hci_uart *hu  = hci_get_drvdata(hdev);
-
 	BT_DBG("hdev %p", hdev);
 
 	hci_uart_flush(hdev);
 	hdev->flush = NULL;
 
-	serdev_device_close(hu->serdev);
-
 	return 0;
 }
 
@@ -289,6 +281,8 @@ int hci_uart_register_device(struct hci_uart *hu,
 
 	BT_DBG("");
 
+	serdev_device_set_client_ops(hu->serdev, &hci_serdev_client_ops);
+
 	err = p->open(hu);
 	if (err)
 		return err;
-- 
2.11.0

^ permalink raw reply related

* [PATCHv3 08/10] Bluetooth: hci_serdev: allow modular drivers
From: Sebastian Reichel @ 2017-03-28 15:59 UTC (permalink / raw)
  To: Sebastian Reichel, Marcel Holtmann, Gustavo Padovan,
	Johan Hedberg, Rob Herring
  Cc: Samuel Thibault, Pavel Machek, Tony Lindgren, Greg Kroah-Hartman,
	Jiri Slaby, Mark Rutland, linux-bluetooth-u79uwXL29TY76Z2rM5mHXA,
	linux-serial-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <20170328155939.31566-1-sre-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>

For bluetooth protocol driver only supporting serdev it makes
sense to follow common practice and built them into their own
module.

Such modules need access to hci_uart_register_device and
hci_uart_tx_wakeup for using the common protocol helpers.

Signed-off-by: Sebastian Reichel <sre-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
---
New patch since patchset v2.
---
 drivers/bluetooth/hci_ldisc.c  | 1 +
 drivers/bluetooth/hci_serdev.c | 1 +
 2 files changed, 2 insertions(+)

diff --git a/drivers/bluetooth/hci_ldisc.c b/drivers/bluetooth/hci_ldisc.c
index 0ec8a94bd712..17bcbc13623f 100644
--- a/drivers/bluetooth/hci_ldisc.c
+++ b/drivers/bluetooth/hci_ldisc.c
@@ -134,6 +134,7 @@ int hci_uart_tx_wakeup(struct hci_uart *hu)
 
 	return 0;
 }
+EXPORT_SYMBOL_GPL(hci_uart_tx_wakeup);
 
 static void hci_uart_write_work(struct work_struct *work)
 {
diff --git a/drivers/bluetooth/hci_serdev.c b/drivers/bluetooth/hci_serdev.c
index 3b8ac0ece3fb..7de0edc0ff8c 100644
--- a/drivers/bluetooth/hci_serdev.c
+++ b/drivers/bluetooth/hci_serdev.c
@@ -353,3 +353,4 @@ int hci_uart_register_device(struct hci_uart *hu,
 	p->close(hu);
 	return err;
 }
+EXPORT_SYMBOL_GPL(hci_uart_register_device);
-- 
2.11.0

--
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

* [PATCHv3 09/10] dt-bindings: net: bluetooth: Add nokia-bluetooth
From: Sebastian Reichel @ 2017-03-28 15:59 UTC (permalink / raw)
  To: Sebastian Reichel, Marcel Holtmann, Gustavo Padovan,
	Johan Hedberg, Rob Herring
  Cc: Samuel Thibault, Pavel Machek, Tony Lindgren, Greg Kroah-Hartman,
	Jiri Slaby, Mark Rutland, linux-bluetooth-u79uwXL29TY76Z2rM5mHXA,
	linux-serial-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <20170328155939.31566-1-sre-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>

Add binding document for serial bluetooth chips using
Nokia H4+ protocol.

Acked-by: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
Signed-off-by: Sebastian Reichel <sre-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>

Changes since PATCHv1:
 * change compatible strings
 * mention active high/low state for GPIOs
---
 .../devicetree/bindings/net/nokia-bluetooth.txt    | 51 ++++++++++++++++++++++
 1 file changed, 51 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/net/nokia-bluetooth.txt

diff --git a/Documentation/devicetree/bindings/net/nokia-bluetooth.txt b/Documentation/devicetree/bindings/net/nokia-bluetooth.txt
new file mode 100644
index 000000000000..42be7dc9a70b
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/nokia-bluetooth.txt
@@ -0,0 +1,51 @@
+Nokia Bluetooth Chips
+---------------------
+
+Nokia phones often come with UART connected bluetooth chips from different
+vendors and modified device API. Those devices speak a protocol named H4+
+(also known as h4p) by Nokia, which is similar to the H4 protocol from the
+Bluetooth standard. In addition to the H4 protocol it specifies two more
+UART status lines for wakeup of UART transceivers to improve power management
+and a few new packet types used to negotiate uart speed.
+
+Required properties:
+
+ - compatible: should contain "nokia,h4p-bluetooth" as well as one of the following:
+   * "brcm,bcm2048-nokia"
+   * "ti,wl1271-bluetooth-nokia"
+ - reset-gpios: GPIO specifier, used to reset the BT module (active low)
+ - bluetooth-wakeup-gpios: GPIO specifier, used to wakeup the BT module (active high)
+ - host-wakeup-gpios: GPIO specifier, used to wakeup the host processor (active high)
+ - clock-names: should be "sysclk"
+ - clocks: should contain a clock specifier for every name in clock-names
+
+Optional properties:
+
+ - None
+
+Example:
+
+/ {
+       /* controlled (enabled/disabled) directly by BT module */
+       bluetooth_clk: vctcxo {
+               compatible = "fixed-clock";
+               #clock-cells = <0>;
+               clock-frequency = <38400000>;
+       };
+};
+
+&uart2 {
+       pinctrl-names = "default";
+       pinctrl-0 = <&uart2_pins>;
+
+       bluetooth {
+               compatible = "ti,wl1271-bluetooth-nokia", "nokia,h4p-bluetooth";
+
+               reset-gpios = <&gpio1 26 GPIO_ACTIVE_LOW>; /* gpio26 */
+               host-wakeup-gpios = <&gpio4 5 GPIO_ACTIVE_HIGH>; /* gpio101 */
+               bluetooth-wakeup-gpios = <&gpio2 5 GPIO_ACTIVE_HIGH>; /* gpio37 */
+
+               clocks = <&bluetooth_clk>;
+               clock-names = "sysclk";
+       };
+};
-- 
2.11.0

^ permalink raw reply related

* [PATCHv3 10/10] Bluetooth: add nokia driver
From: Sebastian Reichel @ 2017-03-28 15:59 UTC (permalink / raw)
  To: Sebastian Reichel, Marcel Holtmann, Gustavo Padovan,
	Johan Hedberg, Rob Herring
  Cc: Samuel Thibault, Pavel Machek, Tony Lindgren, Greg Kroah-Hartman,
	Jiri Slaby, Mark Rutland, linux-bluetooth-u79uwXL29TY76Z2rM5mHXA,
	linux-serial-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <20170328155939.31566-1-sre-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>

This adds a driver for the Nokia H4+ protocol, which is used
at least on the Nokia N9, N900 & N950.

Signed-off-by: Sebastian Reichel <sre-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
---
Changes since PATCHv1:
 * replace __u8 and uint8_t with u8
 * replace __u16 and uint16_t with u16
 * drop BT_BAUDRATE_DIVIDER and use btdev->sysclk_speed * 10 instead
 * fix wording of a sentence
 * fix error path of negotation & alive package receive functions
 * replaced nokia_wait_for_cts with newly introduced serdev function
 * use "nokia,h4p-bluetooth" as compatible string
---
 drivers/bluetooth/Kconfig     |  12 +
 drivers/bluetooth/Makefile    |   2 +
 drivers/bluetooth/hci_nokia.c | 819 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 833 insertions(+)
 create mode 100644 drivers/bluetooth/hci_nokia.c

diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig
index c2c14a12713b..2e3e4d3547ad 100644
--- a/drivers/bluetooth/Kconfig
+++ b/drivers/bluetooth/Kconfig
@@ -86,6 +86,18 @@ config BT_HCIUART_H4
 
 	  Say Y here to compile support for HCI UART (H4) protocol.
 
+config BT_HCIUART_NOKIA
+	tristate "UART Nokia H4+ protocol support"
+	depends on BT_HCIUART
+	depends on SERIAL_DEV_BUS
+	depends on PM
+	help
+	  Nokia H4+ is serial protocol for communication between Bluetooth
+	  device and host. This protocol is required for Bluetooth devices
+	  with UART interface in Nokia devices.
+
+	  Say Y here to compile support for Nokia's H4+ protocol.
+
 config BT_HCIUART_BCSP
 	bool "BCSP protocol support"
 	depends on BT_HCIUART
diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile
index fd571689eed6..a7f237320f4b 100644
--- a/drivers/bluetooth/Makefile
+++ b/drivers/bluetooth/Makefile
@@ -25,6 +25,8 @@ obj-$(CONFIG_BT_BCM)		+= btbcm.o
 obj-$(CONFIG_BT_RTL)		+= btrtl.o
 obj-$(CONFIG_BT_QCA)		+= btqca.o
 
+obj-$(CONFIG_BT_HCIUART_NOKIA)	+= hci_nokia.o
+
 btmrvl-y			:= btmrvl_main.o
 btmrvl-$(CONFIG_DEBUG_FS)	+= btmrvl_debugfs.o
 
diff --git a/drivers/bluetooth/hci_nokia.c b/drivers/bluetooth/hci_nokia.c
new file mode 100644
index 000000000000..c77f04af01bf
--- /dev/null
+++ b/drivers/bluetooth/hci_nokia.c
@@ -0,0 +1,819 @@
+/*
+ *  Bluetooth HCI UART H4 driver with Nokia Extensions AKA Nokia H4+
+ *
+ *  Copyright (C) 2015 Marcel Holtmann <marcel-kz+m5ild9QBg9hUCZPvPmw@public.gmane.org>
+ *  Copyright (C) 2015-2017 Sebastian Reichel <sre-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/pm_runtime.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/skbuff.h>
+#include <linux/gpio/consumer.h>
+#include <linux/unaligned/le_struct.h>
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci_core.h>
+#include <linux/serdev.h>
+
+#include "hci_uart.h"
+#include "btbcm.h"
+
+#define NOKIA_ID_BCM2048	0x04
+#define NOKIA_ID_TI1271		0x31
+
+#define FIRMWARE_BCM2048	"nokia/bcmfw.bin"
+#define FIRMWARE_TI1271		"nokia/ti1273.bin"
+
+#define HCI_NOKIA_NEG_PKT	0x06
+#define HCI_NOKIA_ALIVE_PKT	0x07
+#define HCI_NOKIA_RADIO_PKT	0x08
+
+#define HCI_NOKIA_NEG_HDR_SIZE		1
+#define HCI_NOKIA_MAX_NEG_SIZE		255
+#define HCI_NOKIA_ALIVE_HDR_SIZE	1
+#define HCI_NOKIA_MAX_ALIVE_SIZE	255
+#define HCI_NOKIA_RADIO_HDR_SIZE	2
+#define HCI_NOKIA_MAX_RADIO_SIZE	255
+
+#define NOKIA_PROTO_PKT		0x44
+#define NOKIA_PROTO_BYTE	0x4c
+
+#define NOKIA_NEG_REQ		0x00
+#define NOKIA_NEG_ACK		0x20
+#define NOKIA_NEG_NAK		0x40
+
+#define H4_TYPE_SIZE		1
+
+#define NOKIA_RECV_ALIVE \
+	.type = HCI_NOKIA_ALIVE_PKT, \
+	.hlen = HCI_NOKIA_ALIVE_HDR_SIZE, \
+	.loff = 0, \
+	.lsize = 1, \
+	.maxlen = HCI_NOKIA_MAX_ALIVE_SIZE \
+
+#define NOKIA_RECV_NEG \
+	.type = HCI_NOKIA_NEG_PKT, \
+	.hlen = HCI_NOKIA_NEG_HDR_SIZE, \
+	.loff = 0, \
+	.lsize = 1, \
+	.maxlen = HCI_NOKIA_MAX_NEG_SIZE \
+
+#define NOKIA_RECV_RADIO \
+	.type = HCI_NOKIA_RADIO_PKT, \
+	.hlen = HCI_NOKIA_RADIO_HDR_SIZE, \
+	.loff = 1, \
+	.lsize = 1, \
+	.maxlen = HCI_NOKIA_MAX_RADIO_SIZE \
+
+struct hci_nokia_neg_hdr {
+	u8	dlen;
+} __packed;
+
+struct hci_nokia_neg_cmd {
+	u8	ack;
+	u16	baud;
+	u16	unused1;
+	u8	proto;
+	u16	sys_clk;
+	u16	unused2;
+} __packed;
+
+#define NOKIA_ALIVE_REQ   0x55
+#define NOKIA_ALIVE_RESP  0xcc
+
+struct hci_nokia_alive_hdr {
+	u8	dlen;
+} __packed;
+
+struct hci_nokia_alive_pkt {
+	u8	mid;
+	u8	unused;
+} __packed;
+
+struct hci_nokia_neg_evt {
+	u8	ack;
+	u16	baud;
+	u16	unused1;
+	u8	proto;
+	u16	sys_clk;
+	u16	unused2;
+	u8	man_id;
+	u8	ver_id;
+} __packed;
+
+#define MAX_BAUD_RATE		3692300
+#define SETUP_BAUD_RATE		921600
+#define INIT_BAUD_RATE		120000
+
+struct hci_nokia_radio_hdr {
+	u8	evt;
+	u8	dlen;
+} __packed;
+
+struct nokia_bt_dev {
+	struct hci_uart hu;
+	struct serdev_device *serdev;
+
+	struct gpio_desc *reset;
+	struct gpio_desc *wakeup_host;
+	struct gpio_desc *wakeup_bt;
+	unsigned long sysclk_speed;
+
+	int wake_irq;
+	struct sk_buff *rx_skb;
+	struct sk_buff_head txq;
+	bdaddr_t bdaddr;
+
+	int init_error;
+	struct completion init_completion;
+
+	u8 man_id;
+	u8 ver_id;
+
+	bool initialized;
+	bool tx_enabled;
+	bool rx_enabled;
+};
+
+static int nokia_enqueue(struct hci_uart *hu, struct sk_buff *skb);
+
+static void nokia_flow_control(struct serdev_device *serdev, bool enable)
+{
+	if (enable) {
+		serdev_device_set_rts(serdev, true);
+		serdev_device_set_flow_control(serdev, true);
+	} else {
+		serdev_device_set_flow_control(serdev, false);
+		serdev_device_set_rts(serdev, false);
+	}
+}
+
+static irqreturn_t wakeup_handler(int irq, void *data)
+{
+	struct nokia_bt_dev *btdev = data;
+	struct device *dev = &btdev->serdev->dev;
+	int wake_state = gpiod_get_value(btdev->wakeup_host);
+
+	if (btdev->rx_enabled == wake_state)
+		return IRQ_HANDLED;
+
+	if (wake_state)
+		pm_runtime_get(dev);
+	else
+		pm_runtime_put(dev);
+
+	btdev->rx_enabled = wake_state;
+
+	return IRQ_HANDLED;
+}
+
+static int nokia_reset(struct hci_uart *hu)
+{
+	struct nokia_bt_dev *btdev = hu->priv;
+	struct device *dev = &btdev->serdev->dev;
+	int err;
+
+	/* reset routine */
+	gpiod_set_value_cansleep(btdev->reset, 1);
+	gpiod_set_value_cansleep(btdev->wakeup_bt, 1);
+
+	msleep(100);
+
+	/* safety check */
+	err = gpiod_get_value_cansleep(btdev->wakeup_host);
+	if (err == 1) {
+		dev_err(dev, "reset: host wakeup not low!");
+		return -EPROTO;
+	}
+
+	/* flush queue */
+	serdev_device_write_flush(btdev->serdev);
+
+	/* init uart */
+	nokia_flow_control(btdev->serdev, false);
+	serdev_device_set_baudrate(btdev->serdev, INIT_BAUD_RATE);
+
+	gpiod_set_value_cansleep(btdev->reset, 0);
+
+	/* wait for cts */
+	err = serdev_device_wait_for_cts(btdev->serdev, true, 200);
+	if (err < 0) {
+		dev_err(dev, "CTS not received: %d", err);
+		return err;
+	}
+
+	nokia_flow_control(btdev->serdev, true);
+
+	return 0;
+}
+
+static int nokia_send_alive_packet(struct hci_uart *hu)
+{
+	struct nokia_bt_dev *btdev = hu->priv;
+	struct device *dev = &btdev->serdev->dev;
+	struct hci_nokia_alive_hdr *hdr;
+	struct hci_nokia_alive_pkt *pkt;
+	struct sk_buff *skb;
+	int len;
+
+	init_completion(&btdev->init_completion);
+
+	len = H4_TYPE_SIZE + sizeof(*hdr) + sizeof(*pkt);
+	skb = bt_skb_alloc(len, GFP_KERNEL);
+	if (!skb)
+		return -ENOMEM;
+
+	hci_skb_pkt_type(skb) = HCI_NOKIA_ALIVE_PKT;
+	memset(skb->data, 0x00, len);
+
+	hdr = (struct hci_nokia_alive_hdr *)skb_put(skb, sizeof(*hdr));
+	hdr->dlen = sizeof(*pkt);
+	pkt = (struct hci_nokia_alive_pkt *)skb_put(skb, sizeof(*pkt));
+	pkt->mid = NOKIA_ALIVE_REQ;
+
+	nokia_enqueue(hu, skb);
+	hci_uart_tx_wakeup(hu);
+
+	dev_dbg(dev, "Alive sent");
+
+	if (!wait_for_completion_interruptible_timeout(&btdev->init_completion,
+		msecs_to_jiffies(1000))) {
+		return -ETIMEDOUT;
+	}
+
+	if (btdev->init_error < 0)
+		return btdev->init_error;
+
+	return 0;
+}
+
+static int nokia_send_negotiation(struct hci_uart *hu)
+{
+	struct nokia_bt_dev *btdev = hu->priv;
+	struct device *dev = &btdev->serdev->dev;
+	struct hci_nokia_neg_cmd *neg_cmd;
+	struct hci_nokia_neg_hdr *neg_hdr;
+	struct sk_buff *skb;
+	int len, err;
+	u16 baud = DIV_ROUND_CLOSEST(btdev->sysclk_speed * 10, SETUP_BAUD_RATE);
+	int sysclk = btdev->sysclk_speed / 1000;
+
+	len = H4_TYPE_SIZE + sizeof(*neg_hdr) + sizeof(*neg_cmd);
+	skb = bt_skb_alloc(len, GFP_KERNEL);
+	if (!skb)
+		return -ENOMEM;
+
+	hci_skb_pkt_type(skb) = HCI_NOKIA_NEG_PKT;
+
+	neg_hdr = (struct hci_nokia_neg_hdr *)skb_put(skb, sizeof(*neg_hdr));
+	neg_hdr->dlen = sizeof(*neg_cmd);
+
+	neg_cmd = (struct hci_nokia_neg_cmd *)skb_put(skb, sizeof(*neg_cmd));
+	neg_cmd->ack = NOKIA_NEG_REQ;
+	neg_cmd->baud = cpu_to_le16(baud);
+	neg_cmd->unused1 = 0x0000;
+	neg_cmd->proto = NOKIA_PROTO_BYTE;
+	neg_cmd->sys_clk = cpu_to_le16(sysclk);
+	neg_cmd->unused2 = 0x0000;
+
+	btdev->init_error = 0;
+	init_completion(&btdev->init_completion);
+
+	nokia_enqueue(hu, skb);
+	hci_uart_tx_wakeup(hu);
+
+	dev_dbg(dev, "Negotiation sent");
+
+	if (!wait_for_completion_interruptible_timeout(&btdev->init_completion,
+		msecs_to_jiffies(10000))) {
+		return -ETIMEDOUT;
+	}
+
+	if (btdev->init_error < 0)
+		return btdev->init_error;
+
+	/* Change to previously negotiated speed. Flow Control
+	 * is disabled until bluetooth adapter is ready to avoid
+	 * broken bytes being received.
+	 */
+	nokia_flow_control(btdev->serdev, false);
+	serdev_device_set_baudrate(btdev->serdev, SETUP_BAUD_RATE);
+	err = serdev_device_wait_for_cts(btdev->serdev, true, 200);
+	if (err < 0) {
+		dev_err(dev, "CTS not received: %d", err);
+		return err;
+	}
+	nokia_flow_control(btdev->serdev, true);
+
+	dev_dbg(dev, "Negotiation successful");
+
+	return 0;
+}
+
+static int nokia_setup_fw(struct hci_uart *hu)
+{
+	struct nokia_bt_dev *btdev = hu->priv;
+	struct device *dev = &btdev->serdev->dev;
+	const char *fwname;
+	const struct firmware *fw;
+	const u8 *fw_ptr;
+	size_t fw_size;
+	int err;
+
+	dev_dbg(dev, "setup firmware");
+
+	if (btdev->man_id == NOKIA_ID_BCM2048) {
+		fwname = FIRMWARE_BCM2048;
+	} else if (btdev->man_id == NOKIA_ID_TI1271) {
+		fwname = FIRMWARE_TI1271;
+	} else {
+		dev_err(dev, "Unsupported bluetooth device!");
+		return -ENODEV;
+	}
+
+	err = request_firmware(&fw, fwname, dev);
+	if (err < 0) {
+		dev_err(dev, "%s: Failed to load Nokia firmware file (%d)",
+			hu->hdev->name, err);
+		return err;
+	}
+
+	fw_ptr = fw->data;
+	fw_size = fw->size;
+
+	while (fw_size >= 4) {
+		u16 pkt_size = get_unaligned_le16(fw_ptr);
+		u8 pkt_type = fw_ptr[2];
+		const struct hci_command_hdr *cmd;
+		u16 opcode;
+		struct sk_buff *skb;
+
+		switch (pkt_type) {
+		case HCI_COMMAND_PKT:
+			cmd = (struct hci_command_hdr *)(fw_ptr + 3);
+			opcode = le16_to_cpu(cmd->opcode);
+
+			skb = __hci_cmd_sync(hu->hdev, opcode, cmd->plen,
+					     fw_ptr + 3 + HCI_COMMAND_HDR_SIZE,
+					     HCI_INIT_TIMEOUT);
+			if (IS_ERR(skb)) {
+				err = PTR_ERR(skb);
+				dev_err(dev, "%s: FW command %04x failed (%d)",
+				       hu->hdev->name, opcode, err);
+				goto done;
+			}
+			kfree_skb(skb);
+			break;
+		case HCI_NOKIA_RADIO_PKT:
+		case HCI_NOKIA_NEG_PKT:
+		case HCI_NOKIA_ALIVE_PKT:
+			break;
+		}
+
+		fw_ptr += pkt_size + 2;
+		fw_size -= pkt_size + 2;
+	}
+
+done:
+	release_firmware(fw);
+	return err;
+}
+
+static int nokia_setup(struct hci_uart *hu)
+{
+	struct nokia_bt_dev *btdev = hu->priv;
+	struct device *dev = &btdev->serdev->dev;
+	int err;
+
+	btdev->initialized = false;
+
+	nokia_flow_control(btdev->serdev, false);
+
+	pm_runtime_get_sync(dev);
+
+	if (btdev->tx_enabled) {
+		gpiod_set_value_cansleep(btdev->wakeup_bt, 0);
+		pm_runtime_put(&btdev->serdev->dev);
+		btdev->tx_enabled = false;
+	}
+
+	dev_dbg(dev, "protocol setup");
+
+	/* 0. reset connection */
+	err = nokia_reset(hu);
+	if (err < 0) {
+		dev_err(dev, "Reset failed: %d", err);
+		goto out;
+	}
+
+	/* 1. negotiate speed etc */
+	err = nokia_send_negotiation(hu);
+	if (err < 0) {
+		dev_err(dev, "Negotiation failed: %d", err);
+		goto out;
+	}
+
+	/* 2. verify correct setup using alive packet */
+	err = nokia_send_alive_packet(hu);
+	if (err < 0) {
+		dev_err(dev, "Alive check failed: %d", err);
+		goto out;
+	}
+
+	/* 3. send firmware */
+	err = nokia_setup_fw(hu);
+	if (err < 0) {
+		dev_err(dev, "Could not setup FW: %d", err);
+		goto out;
+	}
+
+	nokia_flow_control(btdev->serdev, false);
+	serdev_device_set_baudrate(btdev->serdev, MAX_BAUD_RATE);
+	nokia_flow_control(btdev->serdev, true);
+
+	if (btdev->man_id == NOKIA_ID_BCM2048) {
+		hu->hdev->set_bdaddr = btbcm_set_bdaddr;
+		set_bit(HCI_QUIRK_INVALID_BDADDR, &hu->hdev->quirks);
+		dev_dbg(dev, "bcm2048 has invalid bluetooth address!");
+	}
+
+	dev_dbg(dev, "protocol setup done!");
+
+	gpiod_set_value_cansleep(btdev->wakeup_bt, 0);
+	pm_runtime_put(dev);
+	btdev->tx_enabled = false;
+	btdev->initialized = true;
+
+	return 0;
+out:
+	pm_runtime_put(dev);
+
+	return err;
+}
+
+static int nokia_open(struct hci_uart *hu)
+{
+	struct device *dev = &hu->serdev->dev;
+
+	dev_dbg(dev, "protocol open");
+
+	serdev_device_open(hu->serdev);
+
+	pm_runtime_enable(dev);
+
+	return 0;
+}
+
+static int nokia_flush(struct hci_uart *hu)
+{
+	struct nokia_bt_dev *btdev = hu->priv;
+
+	dev_dbg(&btdev->serdev->dev, "flush device");
+
+	skb_queue_purge(&btdev->txq);
+
+	return 0;
+}
+
+static int nokia_close(struct hci_uart *hu)
+{
+	struct nokia_bt_dev *btdev = hu->priv;
+	struct device *dev = &btdev->serdev->dev;
+
+	dev_dbg(dev, "close device");
+
+	btdev->initialized = false;
+
+	skb_queue_purge(&btdev->txq);
+
+	kfree_skb(btdev->rx_skb);
+
+	/* disable module */
+	gpiod_set_value(btdev->reset, 1);
+	gpiod_set_value(btdev->wakeup_bt, 0);
+
+	pm_runtime_disable(&btdev->serdev->dev);
+	serdev_device_close(btdev->serdev);
+
+	return 0;
+}
+
+/* Enqueue frame for transmittion (padding, crc, etc) */
+static int nokia_enqueue(struct hci_uart *hu, struct sk_buff *skb)
+{
+	struct nokia_bt_dev *btdev = hu->priv;
+	int err;
+
+	/* Prepend skb with frame type */
+	memcpy(skb_push(skb, 1), &bt_cb(skb)->pkt_type, 1);
+
+	/* Packets must be word aligned */
+	if (skb->len % 2) {
+		err = skb_pad(skb, 1);
+		if (err)
+			return err;
+		*skb_put(skb, 1) = 0x00;
+	}
+
+	skb_queue_tail(&btdev->txq, skb);
+
+	return 0;
+}
+
+static int nokia_recv_negotiation_packet(struct hci_dev *hdev,
+					 struct sk_buff *skb)
+{
+	struct hci_uart *hu = hci_get_drvdata(hdev);
+	struct nokia_bt_dev *btdev = hu->priv;
+	struct device *dev = &btdev->serdev->dev;
+	struct hci_nokia_neg_hdr *hdr;
+	struct hci_nokia_neg_evt *evt;
+	int ret = 0;
+
+	hdr = (struct hci_nokia_neg_hdr *)skb->data;
+	if (hdr->dlen != sizeof(*evt)) {
+		btdev->init_error = -EIO;
+		ret = -EIO;
+		goto finish_neg;
+	}
+
+	evt = (struct hci_nokia_neg_evt *)skb_pull(skb, sizeof(*hdr));
+
+	if (evt->ack != NOKIA_NEG_ACK) {
+		dev_err(dev, "Negotiation received: wrong reply");
+		btdev->init_error = -EINVAL;
+		ret = -EINVAL;
+		goto finish_neg;
+	}
+
+	btdev->man_id = evt->man_id;
+	btdev->ver_id = evt->ver_id;
+
+	dev_dbg(dev, "Negotiation received: baud=%u:clk=%u:manu=%u:vers=%u",
+		evt->baud, evt->sys_clk, evt->man_id, evt->ver_id);
+
+finish_neg:
+	complete(&btdev->init_completion);
+	kfree_skb(skb);
+	return ret;
+}
+
+static int nokia_recv_alive_packet(struct hci_dev *hdev, struct sk_buff *skb)
+{
+	struct hci_uart *hu = hci_get_drvdata(hdev);
+	struct nokia_bt_dev *btdev = hu->priv;
+	struct device *dev = &btdev->serdev->dev;
+	struct hci_nokia_alive_hdr *hdr;
+	struct hci_nokia_alive_pkt *pkt;
+	int ret = 0;
+
+	hdr = (struct hci_nokia_alive_hdr *)skb->data;
+	if (hdr->dlen != sizeof(*pkt)) {
+		dev_err(dev, "Corrupted alive message");
+		btdev->init_error = -EIO;
+		ret = -EIO;
+		goto finish_alive;
+	}
+
+	pkt = (struct hci_nokia_alive_pkt *)skb_pull(skb, sizeof(*hdr));
+
+	if (pkt->mid != NOKIA_ALIVE_RESP) {
+		dev_err(dev, "Alive received: invalid response: 0x%02x!",
+			pkt->mid);
+		btdev->init_error = -EINVAL;
+		ret = -EINVAL;
+		goto finish_alive;
+	}
+
+	dev_dbg(dev, "Alive received");
+
+finish_alive:
+	complete(&btdev->init_completion);
+	kfree_skb(skb);
+	return ret;
+}
+
+static int nokia_recv_radio(struct hci_dev *hdev, struct sk_buff *skb)
+{
+	/* Packets received on the dedicated radio channel are
+	 * HCI events and so feed them back into the core.
+	 */
+	hci_skb_pkt_type(skb) = HCI_EVENT_PKT;
+	return hci_recv_frame(hdev, skb);
+}
+
+/* Recv data */
+static const struct h4_recv_pkt nokia_recv_pkts[] = {
+	{ H4_RECV_ACL,		.recv = hci_recv_frame },
+	{ H4_RECV_SCO,		.recv = hci_recv_frame },
+	{ H4_RECV_EVENT,	.recv = hci_recv_frame },
+	{ NOKIA_RECV_ALIVE,	.recv = nokia_recv_alive_packet },
+	{ NOKIA_RECV_NEG,	.recv = nokia_recv_negotiation_packet },
+	{ NOKIA_RECV_RADIO,	.recv = nokia_recv_radio },
+};
+
+static int nokia_recv(struct hci_uart *hu, const void *data, int count)
+{
+	struct nokia_bt_dev *btdev = hu->priv;
+	struct device *dev = &btdev->serdev->dev;
+	int err;
+
+	if (!test_bit(HCI_UART_REGISTERED, &hu->flags))
+		return -EUNATCH;
+
+	btdev->rx_skb = h4_recv_buf(hu->hdev, btdev->rx_skb, data, count,
+				  nokia_recv_pkts, ARRAY_SIZE(nokia_recv_pkts));
+	if (IS_ERR(btdev->rx_skb)) {
+		err = PTR_ERR(btdev->rx_skb);
+		dev_err(dev, "Frame reassembly failed (%d)", err);
+		btdev->rx_skb = NULL;
+		return err;
+	}
+
+	return count;
+}
+
+static struct sk_buff *nokia_dequeue(struct hci_uart *hu)
+{
+	struct nokia_bt_dev *btdev = hu->priv;
+	struct device *dev = &btdev->serdev->dev;
+	struct sk_buff *result = skb_dequeue(&btdev->txq);
+
+	if (!btdev->initialized)
+		return result;
+
+	if (btdev->tx_enabled == !!result)
+		return result;
+
+	if (result) {
+		pm_runtime_get_sync(dev);
+		gpiod_set_value_cansleep(btdev->wakeup_bt, 1);
+	} else {
+		serdev_device_wait_until_sent(btdev->serdev, 0);
+		gpiod_set_value_cansleep(btdev->wakeup_bt, 0);
+		pm_runtime_put(dev);
+	}
+
+	btdev->tx_enabled = !!result;
+
+	return result;
+}
+
+static const struct hci_uart_proto nokia_proto = {
+	.id		= HCI_UART_NOKIA,
+	.name		= "Nokia",
+	.open		= nokia_open,
+	.close		= nokia_close,
+	.recv		= nokia_recv,
+	.enqueue	= nokia_enqueue,
+	.dequeue	= nokia_dequeue,
+	.flush		= nokia_flush,
+	.setup		= nokia_setup,
+	.manufacturer	= 1,
+};
+
+static int nokia_bluetooth_serdev_probe(struct serdev_device *serdev)
+{
+	struct device *dev = &serdev->dev;
+	struct nokia_bt_dev *btdev;
+	struct clk *sysclk;
+	int err = 0;
+
+	btdev = devm_kzalloc(dev, sizeof(*btdev), GFP_KERNEL);
+	if (!btdev)
+		return -ENOMEM;
+
+	btdev->hu.serdev = btdev->serdev = serdev;
+	serdev_device_set_drvdata(serdev, btdev);
+
+	btdev->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
+	if (IS_ERR(btdev->reset)) {
+		err = PTR_ERR(btdev->reset);
+		dev_err(dev, "could not get reset gpio: %d", err);
+		return err;
+	}
+
+	btdev->wakeup_host = devm_gpiod_get(dev, "host-wakeup", GPIOD_IN);
+	if (IS_ERR(btdev->wakeup_host)) {
+		err = PTR_ERR(btdev->wakeup_host);
+		dev_err(dev, "could not get host wakeup gpio: %d", err);
+		return err;
+	}
+
+	btdev->wake_irq = gpiod_to_irq(btdev->wakeup_host);
+
+	err = devm_request_threaded_irq(dev, btdev->wake_irq, NULL,
+		wakeup_handler,
+		IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+		"wakeup", btdev);
+	if (err) {
+		dev_err(dev, "could request wakeup irq: %d", err);
+		return err;
+	}
+
+	btdev->wakeup_bt = devm_gpiod_get(dev, "bluetooth-wakeup",
+					   GPIOD_OUT_LOW);
+	if (IS_ERR(btdev->wakeup_bt)) {
+		err = PTR_ERR(btdev->wakeup_bt);
+		dev_err(dev, "could not get BT wakeup gpio: %d", err);
+		return err;
+	}
+
+	sysclk = devm_clk_get(dev, "sysclk");
+	if (IS_ERR(sysclk)) {
+		err = PTR_ERR(sysclk);
+		dev_err(dev, "could not get sysclk: %d", err);
+		return err;
+	}
+
+	clk_prepare_enable(sysclk);
+	btdev->sysclk_speed = clk_get_rate(sysclk);
+	clk_disable_unprepare(sysclk);
+
+	skb_queue_head_init(&btdev->txq);
+
+	btdev->hu.priv = btdev;
+	btdev->hu.alignment = 2; /* Nokia H4+ is word aligned */
+
+	err = hci_uart_register_device(&btdev->hu, &nokia_proto);
+	if (err) {
+		dev_err(dev, "could not register bluetooth uart: %d", err);
+		return err;
+	}
+
+	return 0;
+}
+
+static void nokia_bluetooth_serdev_remove(struct serdev_device *serdev)
+{
+	struct nokia_bt_dev *btdev = serdev_device_get_drvdata(serdev);
+	struct hci_uart *hu = &btdev->hu;
+	struct hci_dev *hdev = hu->hdev;
+
+	cancel_work_sync(&hu->write_work);
+
+	hci_unregister_dev(hdev);
+	hci_free_dev(hdev);
+	hu->proto->close(hu);
+
+	pm_runtime_disable(&btdev->serdev->dev);
+}
+
+static int nokia_bluetooth_runtime_suspend(struct device *dev)
+{
+	struct serdev_device *serdev = to_serdev_device(dev);
+
+	nokia_flow_control(serdev, false);
+	return 0;
+}
+
+static int nokia_bluetooth_runtime_resume(struct device *dev)
+{
+	struct serdev_device *serdev = to_serdev_device(dev);
+
+	nokia_flow_control(serdev, true);
+	return 0;
+}
+
+static const struct dev_pm_ops nokia_bluetooth_pm_ops = {
+	SET_RUNTIME_PM_OPS(nokia_bluetooth_runtime_suspend,
+			   nokia_bluetooth_runtime_resume,
+			   NULL)
+};
+
+#ifdef CONFIG_OF
+static const struct of_device_id nokia_bluetooth_of_match[] = {
+	{ .compatible = "nokia,h4p-bluetooth", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, nokia_bluetooth_of_match);
+#endif
+
+static struct serdev_device_driver nokia_bluetooth_serdev_driver = {
+	.probe = nokia_bluetooth_serdev_probe,
+	.remove = nokia_bluetooth_serdev_remove,
+	.driver = {
+		.name = "nokia-bluetooth",
+		.pm = &nokia_bluetooth_pm_ops,
+		.of_match_table = of_match_ptr(nokia_bluetooth_of_match),
+	},
+};
+
+module_serdev_device_driver(nokia_bluetooth_serdev_driver);
-- 
2.11.0

^ permalink raw reply related

* Re: [PATCH v2] ARM: dts: am335x-baltos: add LED support
From: Tony Lindgren @ 2017-03-28 16:07 UTC (permalink / raw)
  To: yegorslists-gM/Ye1E23mwN+BqQ9rBEUg
  Cc: linux-omap-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA, robh+dt-DgEjT+Ai2ygdnm+yROfE0A
In-Reply-To: <1490706556-30046-1-git-send-email-yegorslists-gM/Ye1E23mwN+BqQ9rBEUg@public.gmane.org>

* yegorslists-gM/Ye1E23mwN+BqQ9rBEUg@public.gmane.org <yegorslists-gM/Ye1E23mwN+BqQ9rBEUg@public.gmane.org> [170328 06:11]:
> From: Yegor Yefremov <yegorslists-gM/Ye1E23mwN+BqQ9rBEUg@public.gmane.org>
> 
> All three devices provide GPIO based LEDs named power,
> wlan and app.
> 
> Place LEDs definition into a separate dtsi file as not all
> devices including am335x-baltos.dtsi have the same LED layout.
> 
> Signed-off-by: Yegor Yefremov <yegorslists-gM/Ye1E23mwN+BqQ9rBEUg@public.gmane.org>
> ---
> Changes v1 -> v2:
>   - move LEDs definition to a separate dtsi file (Tony Lindgren)

Thanks for updating it, applying into omap-for-v4.12/dt-v2.

Tony
--
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 v2 0/5] firmware: google memconsole
From: Thierry Escande @ 2017-03-28 16:11 UTC (permalink / raw)
  To: Rob Herring, Greg Kroah-Hartman
  Cc: Olof Johansson, Stephen Warren, linux-kernel, devicetree,
	Julius Werner, Brian Norris

Hi,

This series extends the Google memory console driver to work on x86 and
ARM platforms running coreboot.

The Google memory console driver provides access to an in-memory log of
the firmware's output.

The legacy x86 memconsole driver is now named memconsole-x86-legacy and
a new driver named memconsole-coreboot is added.

On coreboot devices and depending on the device architecture, the driver
coreboot_table-acpi on x86 platforms or coreboot_table-of on ARM/ARM64
platforms is responsible to get the coreboot table header memory address
and then obtain the memory console address.

Thus, this series adds device tree binding for coreboot entries under
the '/firmware' node. The place where this should be/have been declared
has been discussed years ago when first submitted. Please see previous
discussions [1] and [2]. In the meantime a lot of chromebook devices
have been shipped with this binding in their firmware making it hard to
change/update.

[1] https://patchwork.ozlabs.org/patch/359672/
[2] https://patchwork.kernel.org/patch/4362661/

Regards,
 Thierry

This v2 adds a dependency on X86 for the GOOGLE_SMI symbol and few more
comments in this cover letter.

Julius Werner (1):
  firmware: Add coreboot device tree binding documentation

Thierry Escande (4):
  firmware: google memconsole: Remove useless submenu in Kconfig
  firmware: google memconsole: Move specific EBDA parts
  firmware: google memconsole: Add coreboot support
  firmware: google memconsole: Add ARM/ARM64 support

 .../devicetree/bindings/firmware/coreboot.txt      |  33 +++++
 drivers/firmware/google/Kconfig                    |  50 ++++++-
 drivers/firmware/google/Makefile                   |   7 +-
 drivers/firmware/google/coreboot_table-acpi.c      |  88 ++++++++++++
 drivers/firmware/google/coreboot_table-of.c        |  82 +++++++++++
 drivers/firmware/google/coreboot_table.c           |  94 +++++++++++++
 drivers/firmware/google/coreboot_table.h           |  50 +++++++
 drivers/firmware/google/memconsole-coreboot.c      | 109 +++++++++++++++
 drivers/firmware/google/memconsole-x86-legacy.c    | 154 ++++++++++++++++++++
 drivers/firmware/google/memconsole.c               | 155 +++------------------
 drivers/firmware/google/memconsole.h               |  43 ++++++
 11 files changed, 725 insertions(+), 140 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/firmware/coreboot.txt
 create mode 100644 drivers/firmware/google/coreboot_table-acpi.c
 create mode 100644 drivers/firmware/google/coreboot_table-of.c
 create mode 100644 drivers/firmware/google/coreboot_table.c
 create mode 100644 drivers/firmware/google/coreboot_table.h
 create mode 100644 drivers/firmware/google/memconsole-coreboot.c
 create mode 100644 drivers/firmware/google/memconsole-x86-legacy.c
 create mode 100644 drivers/firmware/google/memconsole.h

-- 
2.7.4

^ permalink raw reply

* [PATCH v2 1/5] firmware: google memconsole: Remove useless submenu in Kconfig
From: Thierry Escande @ 2017-03-28 16:11 UTC (permalink / raw)
  To: Rob Herring, Greg Kroah-Hartman
  Cc: Olof Johansson, Stephen Warren, linux-kernel, devicetree,
	Julius Werner, Brian Norris
In-Reply-To: <1490717489-19218-1-git-send-email-thierry.escande@collabora.com>

This patch removes the "Google Firmware Drivers" menu containing a
menuconfig entry with the exact same name. The menuconfig is now
directly under the "Firmware Drivers" entry.

Signed-off-by: Thierry Escande <thierry.escande@collabora.com>
---
 drivers/firmware/google/Kconfig | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/drivers/firmware/google/Kconfig b/drivers/firmware/google/Kconfig
index 29c8cdd..475bd01 100644
--- a/drivers/firmware/google/Kconfig
+++ b/drivers/firmware/google/Kconfig
@@ -1,4 +1,4 @@
-config GOOGLE_FIRMWARE
+menuconfig GOOGLE_FIRMWARE
 	bool "Google Firmware Drivers"
 	depends on X86
 	default n
@@ -7,8 +7,7 @@ config GOOGLE_FIRMWARE
 	  only useful if you are working directly on one of their
 	  proprietary servers.  If in doubt, say "N".
 
-menu "Google Firmware Drivers"
-	depends on GOOGLE_FIRMWARE
+if GOOGLE_FIRMWARE
 
 config GOOGLE_SMI
 	tristate "SMI interface for Google platforms"
@@ -28,4 +27,4 @@ config GOOGLE_MEMCONSOLE
 	  the EBDA on Google servers.  If found, this log is exported to
 	  userland in the file /sys/firmware/log.
 
-endmenu
+endif # GOOGLE_FIRMWARE
-- 
2.7.4

^ permalink raw reply related

* [PATCH v2 2/5] firmware: google memconsole: Move specific EBDA parts
From: Thierry Escande @ 2017-03-28 16:11 UTC (permalink / raw)
  To: Rob Herring, Greg Kroah-Hartman
  Cc: Olof Johansson, Stephen Warren, linux-kernel, devicetree,
	Julius Werner, Brian Norris
In-Reply-To: <1490717489-19218-1-git-send-email-thierry.escande@collabora.com>

This patch splits memconsole.c in 2 parts. One containing the
architecture-independent part and the other one containing the EBDA
specific part. This prepares the integration of coreboot support for the
memconsole.

The memconsole driver is now named as memconsole-x86-legacy.

Signed-off-by: Thierry Escande <thierry.escande@collabora.com>
---
 drivers/firmware/google/Kconfig                 |   9 +-
 drivers/firmware/google/Makefile                |   3 +-
 drivers/firmware/google/memconsole-x86-legacy.c | 154 +++++++++++++++++++++++
 drivers/firmware/google/memconsole.c            | 155 ++++--------------------
 drivers/firmware/google/memconsole.h            |  43 +++++++
 5 files changed, 229 insertions(+), 135 deletions(-)
 create mode 100644 drivers/firmware/google/memconsole-x86-legacy.c
 create mode 100644 drivers/firmware/google/memconsole.h

diff --git a/drivers/firmware/google/Kconfig b/drivers/firmware/google/Kconfig
index 475bd01..27a0658 100644
--- a/drivers/firmware/google/Kconfig
+++ b/drivers/firmware/google/Kconfig
@@ -20,8 +20,13 @@ config GOOGLE_SMI
 	  variables.
 
 config GOOGLE_MEMCONSOLE
-	tristate "Firmware Memory Console"
-	depends on DMI
+	tristate
+	depends on GOOGLE_MEMCONSOLE_X86_LEGACY
+
+config GOOGLE_MEMCONSOLE_X86_LEGACY
+	tristate "Firmware Memory Console - X86 Legacy support"
+	depends on X86 && ACPI && DMI
+	select GOOGLE_MEMCONSOLE
 	help
 	  This option enables the kernel to search for a firmware log in
 	  the EBDA on Google servers.  If found, this log is exported to
diff --git a/drivers/firmware/google/Makefile b/drivers/firmware/google/Makefile
index 54a294e..10683ef 100644
--- a/drivers/firmware/google/Makefile
+++ b/drivers/firmware/google/Makefile
@@ -1,3 +1,4 @@
 
 obj-$(CONFIG_GOOGLE_SMI)		+= gsmi.o
-obj-$(CONFIG_GOOGLE_MEMCONSOLE)		+= memconsole.o
+obj-$(CONFIG_GOOGLE_MEMCONSOLE)            += memconsole.o
+obj-$(CONFIG_GOOGLE_MEMCONSOLE_X86_LEGACY) += memconsole-x86-legacy.o
diff --git a/drivers/firmware/google/memconsole-x86-legacy.c b/drivers/firmware/google/memconsole-x86-legacy.c
new file mode 100644
index 0000000..529078c
--- /dev/null
+++ b/drivers/firmware/google/memconsole-x86-legacy.c
@@ -0,0 +1,154 @@
+/*
+ * memconsole-x86-legacy.c
+ *
+ * EBDA specific parts of the memory based BIOS console.
+ *
+ * Copyright 2017 Google Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2.0 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/dmi.h>
+#include <linux/mm.h>
+#include <asm/bios_ebda.h>
+#include <asm/e820.h>
+#include <linux/acpi.h>
+
+#include "memconsole.h"
+
+#define BIOS_MEMCONSOLE_V1_MAGIC	0xDEADBABE
+#define BIOS_MEMCONSOLE_V2_MAGIC	(('M')|('C'<<8)|('O'<<16)|('N'<<24))
+
+struct biosmemcon_ebda {
+	u32 signature;
+	union {
+		struct {
+			u8  enabled;
+			u32 buffer_addr;
+			u16 start;
+			u16 end;
+			u16 num_chars;
+			u8  wrapped;
+		} __packed v1;
+		struct {
+			u32 buffer_addr;
+			/* Misdocumented as number of pages! */
+			u16 num_bytes;
+			u16 start;
+			u16 end;
+		} __packed v2;
+	};
+} __packed;
+
+static void found_v1_header(struct biosmemcon_ebda *hdr)
+{
+	pr_info("memconsole: BIOS console v1 EBDA structure found at %p\n",
+		hdr);
+	pr_info("memconsole: BIOS console buffer at 0x%.8x, start = %d, end = %d, num = %d\n",
+		hdr->v1.buffer_addr, hdr->v1.start,
+		hdr->v1.end, hdr->v1.num_chars);
+
+	memconsole_setup(phys_to_virt(hdr->v1.buffer_addr), hdr->v1.num_chars);
+}
+
+static void found_v2_header(struct biosmemcon_ebda *hdr)
+{
+	pr_info("memconsole: BIOS console v2 EBDA structure found at %p\n",
+		hdr);
+	pr_info("memconsole: BIOS console buffer at 0x%.8x, start = %d, end = %d, num_bytes = %d\n",
+		hdr->v2.buffer_addr, hdr->v2.start,
+		hdr->v2.end, hdr->v2.num_bytes);
+
+	memconsole_setup(phys_to_virt(hdr->v2.buffer_addr + hdr->v2.start),
+			 hdr->v2.end - hdr->v2.start);
+}
+
+/*
+ * Search through the EBDA for the BIOS Memory Console, and
+ * set the global variables to point to it.  Return true if found.
+ */
+static bool memconsole_ebda_init(void)
+{
+	unsigned int address;
+	size_t length, cur;
+
+	address = get_bios_ebda();
+	if (!address) {
+		pr_info("memconsole: BIOS EBDA non-existent.\n");
+		return false;
+	}
+
+	/* EBDA length is byte 0 of EBDA (in KB) */
+	length = *(u8 *)phys_to_virt(address);
+	length <<= 10; /* convert to bytes */
+
+	/*
+	 * Search through EBDA for BIOS memory console structure
+	 * note: signature is not necessarily dword-aligned
+	 */
+	for (cur = 0; cur < length; cur++) {
+		struct biosmemcon_ebda *hdr = phys_to_virt(address + cur);
+
+		/* memconsole v1 */
+		if (hdr->signature == BIOS_MEMCONSOLE_V1_MAGIC) {
+			found_v1_header(hdr);
+			return true;
+		}
+
+		/* memconsole v2 */
+		if (hdr->signature == BIOS_MEMCONSOLE_V2_MAGIC) {
+			found_v2_header(hdr);
+			return true;
+		}
+	}
+
+	pr_info("memconsole: BIOS console EBDA structure not found!\n");
+	return false;
+}
+
+static struct dmi_system_id memconsole_dmi_table[] __initdata = {
+	{
+		.ident = "Google Board",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Google, Inc."),
+		},
+	},
+	{}
+};
+MODULE_DEVICE_TABLE(dmi, memconsole_dmi_table);
+
+static bool __init memconsole_find(void)
+{
+	if (!dmi_check_system(memconsole_dmi_table))
+		return false;
+
+	return memconsole_ebda_init();
+}
+
+static int __init memconsole_x86_init(void)
+{
+	if (!memconsole_find())
+		return -ENODEV;
+
+	return memconsole_sysfs_init();
+}
+
+static void __exit memconsole_x86_exit(void)
+{
+	memconsole_exit();
+}
+
+module_init(memconsole_x86_init);
+module_exit(memconsole_x86_exit);
+
+MODULE_AUTHOR("Google, Inc.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/firmware/google/memconsole.c b/drivers/firmware/google/memconsole.c
index 2f569aa..94e200d 100644
--- a/drivers/firmware/google/memconsole.c
+++ b/drivers/firmware/google/memconsole.c
@@ -1,66 +1,36 @@
 /*
  * memconsole.c
  *
- * Infrastructure for importing the BIOS memory based console
- * into the kernel log ringbuffer.
+ * Architecture-independent parts of the memory based BIOS console.
  *
- * Copyright 2010 Google Inc. All rights reserved.
+ * Copyright 2017 Google Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2.0 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
  */
 
-#include <linux/ctype.h>
 #include <linux/init.h>
-#include <linux/kernel.h>
-#include <linux/string.h>
 #include <linux/sysfs.h>
 #include <linux/kobject.h>
 #include <linux/module.h>
-#include <linux/dmi.h>
-#include <linux/io.h>
-#include <asm/bios_ebda.h>
 
-#define BIOS_MEMCONSOLE_V1_MAGIC	0xDEADBABE
-#define BIOS_MEMCONSOLE_V2_MAGIC	(('M')|('C'<<8)|('O'<<16)|('N'<<24))
+#include "memconsole.h"
 
-struct biosmemcon_ebda {
-	u32 signature;
-	union {
-		struct {
-			u8  enabled;
-			u32 buffer_addr;
-			u16 start;
-			u16 end;
-			u16 num_chars;
-			u8  wrapped;
-		} __packed v1;
-		struct {
-			u32 buffer_addr;
-			/* Misdocumented as number of pages! */
-			u16 num_bytes;
-			u16 start;
-			u16 end;
-		} __packed v2;
-	};
-} __packed;
-
-static u32 memconsole_baseaddr;
+static char *memconsole_baseaddr;
 static size_t memconsole_length;
 
 static ssize_t memconsole_read(struct file *filp, struct kobject *kobp,
 			       struct bin_attribute *bin_attr, char *buf,
 			       loff_t pos, size_t count)
 {
-	char *memconsole;
-	ssize_t ret;
-
-	memconsole = ioremap_cache(memconsole_baseaddr, memconsole_length);
-	if (!memconsole) {
-		pr_err("memconsole: ioremap_cache failed\n");
-		return -ENOMEM;
-	}
-	ret = memory_read_from_buffer(buf, count, &pos, memconsole,
-				      memconsole_length);
-	iounmap(memconsole);
-	return ret;
+	return memory_read_from_buffer(buf, count, &pos, memconsole_baseaddr,
+				       memconsole_length);
 }
 
 static struct bin_attribute memconsole_bin_attr = {
@@ -68,104 +38,25 @@ static struct bin_attribute memconsole_bin_attr = {
 	.read = memconsole_read,
 };
 
-
-static void __init found_v1_header(struct biosmemcon_ebda *hdr)
-{
-	pr_info("BIOS console v1 EBDA structure found at %p\n", hdr);
-	pr_info("BIOS console buffer at 0x%.8x, "
-	       "start = %d, end = %d, num = %d\n",
-	       hdr->v1.buffer_addr, hdr->v1.start,
-	       hdr->v1.end, hdr->v1.num_chars);
-
-	memconsole_length = hdr->v1.num_chars;
-	memconsole_baseaddr = hdr->v1.buffer_addr;
-}
-
-static void __init found_v2_header(struct biosmemcon_ebda *hdr)
-{
-	pr_info("BIOS console v2 EBDA structure found at %p\n", hdr);
-	pr_info("BIOS console buffer at 0x%.8x, "
-	       "start = %d, end = %d, num_bytes = %d\n",
-	       hdr->v2.buffer_addr, hdr->v2.start,
-	       hdr->v2.end, hdr->v2.num_bytes);
-
-	memconsole_length = hdr->v2.end - hdr->v2.start;
-	memconsole_baseaddr = hdr->v2.buffer_addr + hdr->v2.start;
-}
-
-/*
- * Search through the EBDA for the BIOS Memory Console, and
- * set the global variables to point to it.  Return true if found.
- */
-static bool __init found_memconsole(void)
+void memconsole_setup(void *baseaddr, size_t length)
 {
-	unsigned int address;
-	size_t length, cur;
-
-	address = get_bios_ebda();
-	if (!address) {
-		pr_info("BIOS EBDA non-existent.\n");
-		return false;
-	}
-
-	/* EBDA length is byte 0 of EBDA (in KB) */
-	length = *(u8 *)phys_to_virt(address);
-	length <<= 10; /* convert to bytes */
-
-	/*
-	 * Search through EBDA for BIOS memory console structure
-	 * note: signature is not necessarily dword-aligned
-	 */
-	for (cur = 0; cur < length; cur++) {
-		struct biosmemcon_ebda *hdr = phys_to_virt(address + cur);
-
-		/* memconsole v1 */
-		if (hdr->signature == BIOS_MEMCONSOLE_V1_MAGIC) {
-			found_v1_header(hdr);
-			return true;
-		}
-
-		/* memconsole v2 */
-		if (hdr->signature == BIOS_MEMCONSOLE_V2_MAGIC) {
-			found_v2_header(hdr);
-			return true;
-		}
-	}
-
-	pr_info("BIOS console EBDA structure not found!\n");
-	return false;
+	memconsole_baseaddr = baseaddr;
+	memconsole_length = length;
 }
+EXPORT_SYMBOL(memconsole_setup);
 
-static struct dmi_system_id memconsole_dmi_table[] __initdata = {
-	{
-		.ident = "Google Board",
-		.matches = {
-			DMI_MATCH(DMI_BOARD_VENDOR, "Google, Inc."),
-		},
-	},
-	{}
-};
-MODULE_DEVICE_TABLE(dmi, memconsole_dmi_table);
-
-static int __init memconsole_init(void)
+int memconsole_sysfs_init(void)
 {
-	if (!dmi_check_system(memconsole_dmi_table))
-		return -ENODEV;
-
-	if (!found_memconsole())
-		return -ENODEV;
-
 	memconsole_bin_attr.size = memconsole_length;
 	return sysfs_create_bin_file(firmware_kobj, &memconsole_bin_attr);
 }
+EXPORT_SYMBOL(memconsole_sysfs_init);
 
-static void __exit memconsole_exit(void)
+void memconsole_exit(void)
 {
 	sysfs_remove_bin_file(firmware_kobj, &memconsole_bin_attr);
 }
-
-module_init(memconsole_init);
-module_exit(memconsole_exit);
+EXPORT_SYMBOL(memconsole_exit);
 
 MODULE_AUTHOR("Google, Inc.");
 MODULE_LICENSE("GPL");
diff --git a/drivers/firmware/google/memconsole.h b/drivers/firmware/google/memconsole.h
new file mode 100644
index 0000000..190fc03
--- /dev/null
+++ b/drivers/firmware/google/memconsole.h
@@ -0,0 +1,43 @@
+/*
+ * memconsole.h
+ *
+ * Internal headers of the memory based BIOS console.
+ *
+ * Copyright 2017 Google Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2.0 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.
+ */
+
+#ifndef __FIRMWARE_GOOGLE_MEMCONSOLE_H
+#define __FIRMWARE_GOOGLE_MEMCONSOLE_H
+
+/*
+ * memconsole_setup
+ *
+ * Initialize the memory console from raw (virtual) base
+ * address and length.
+ */
+void memconsole_setup(void *baseaddr, size_t length);
+
+/*
+ * memconsole_sysfs_init
+ *
+ * Update memory console length and create binary file
+ * for firmware object.
+ */
+int memconsole_sysfs_init(void);
+
+/* memconsole_exit
+ *
+ * Unmap the console buffer.
+ */
+void memconsole_exit(void);
+
+#endif /* __FIRMWARE_GOOGLE_MEMCONSOLE_H */
-- 
2.7.4

^ 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