summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/ABI/testing/sysfs-class-led-driver-aw200xx108
-rw-r--r--Documentation/devicetree/bindings/clock/amlogic,a1-audio-clkc.yaml83
-rw-r--r--Documentation/devicetree/bindings/clock/amlogic,a1-cpu-clkc.yaml64
-rw-r--r--Documentation/devicetree/bindings/leds/awinic,aw200xx.yaml50
-rw-r--r--Documentation/devicetree/bindings/pwm/pwm-amlogic.yaml2
-rw-r--r--Documentation/devicetree/bindings/reset/amlogic,meson-axg-audio-arb.yaml10
-rw-r--r--Documentation/devicetree/bindings/sound/amlogic,axg-fifo.yaml8
-rw-r--r--Documentation/devicetree/bindings/sound/amlogic,axg-pdm.yaml5
-rw-r--r--Documentation/devicetree/bindings/sound/amlogic,axg-sound-card.yaml6
-rw-r--r--Documentation/devicetree/bindings/sound/amlogic,axg-tdm-formatters.yaml22
-rw-r--r--Documentation/devicetree/bindings/sound/amlogic,axg-tdm-iface.yaml6
-rw-r--r--Documentation/devicetree/bindings/sound/amlogic,g12a-toacodec.yaml1
-rw-r--r--Documentation/devicetree/bindings/sound/amlogic,sm1-eqdrc.yaml66
-rw-r--r--Documentation/devicetree/bindings/sound/amlogic,t9015.yaml58
-rw-r--r--Documentation/devicetree/bindings/sound/everest,es7243e.yaml57
-rw-r--r--Documentation/leds/leds-aw200xx.rst274
-rw-r--r--arch/arm/boot/dts/amlogic/meson8b-odroidc1.dts9
-rw-r--r--arch/arm/configs/meson8_odroid_c1_defconfig228
-rw-r--r--arch/arm64/boot/dts/amlogic/meson-a1-ad402.dts114
-rw-r--r--arch/arm64/boot/dts/amlogic/meson-a1.dtsi581
-rw-r--r--arch/arm64/boot/dts/amlogic/meson-sm1-ac200.dts26
-rw-r--r--arch/arm64/configs/meson_g12b_khadas_vim3_defconfig402
-rw-r--r--arch/arm64/configs/meson_gxl_khadas_vim1_defconfig397
-rw-r--r--arch/arm64/configs/meson_sm1_amlogic_ac200_defconfig338
-rw-r--r--drivers/clk/meson/Kconfig22
-rw-r--r--drivers/clk/meson/Makefile2
-rw-r--r--drivers/clk/meson/a1-audio.c560
-rw-r--r--drivers/clk/meson/a1-audio.h58
-rw-r--r--drivers/clk/meson/a1-cpu.c331
-rw-r--r--drivers/clk/meson/a1-peripherals.c18
-rw-r--r--drivers/clk/meson/a1-pll.c77
-rw-r--r--drivers/iio/light/Kconfig13
-rw-r--r--drivers/iio/light/Makefile1
-rw-r--r--drivers/iio/light/hx3203.c1462
-rw-r--r--drivers/leds/Kconfig3
-rw-r--r--drivers/leds/leds-aw200xx.c1392
-rw-r--r--drivers/mtd/nand/raw/Kconfig11
-rw-r--r--drivers/mtd/nand/raw/Makefile8
-rw-r--r--drivers/mtd/nand/raw/meson_nand.c9
-rw-r--r--drivers/mtd/nand/raw/nand_bbt_amlogic.c881
-rw-r--r--drivers/mtd/nand/spi/esmt.c68
-rw-r--r--drivers/mtd/nand/spi/micron.c28
-rw-r--r--drivers/tee/optee/supp.c107
-rw-r--r--include/dt-bindings/clock/amlogic,a1-audio-clkc.h122
-rw-r--r--include/dt-bindings/clock/amlogic,a1-cpu-clkc.h19
-rw-r--r--include/dt-bindings/sound/meson-g12a-toacodec.h5
-rw-r--r--sound/soc/codecs/Kconfig3
-rw-r--r--sound/soc/codecs/Makefile2
-rw-r--r--sound/soc/codecs/es7243e.c688
-rw-r--r--sound/soc/codecs/ntp8835.c72
-rw-r--r--sound/soc/codecs/ntp8918.c60
-rw-r--r--sound/soc/meson/Kconfig8
-rw-r--r--sound/soc/meson/Makefile2
-rw-r--r--sound/soc/meson/axg-frddr.c55
-rw-r--r--sound/soc/meson/axg-pdm.c10
-rw-r--r--sound/soc/meson/axg-tdmout.c13
-rw-r--r--sound/soc/meson/g12a-toacodec.c298
-rw-r--r--sound/soc/meson/meson-card-utils.c34
-rw-r--r--sound/soc/meson/meson-codec-glue.c174
-rw-r--r--sound/soc/meson/meson-codec-glue.h23
-rw-r--r--sound/soc/meson/sm1-eqdrc.c673
-rw-r--r--sound/soc/meson/t9015.c425
62 files changed, 10320 insertions, 332 deletions
diff --git a/Documentation/ABI/testing/sysfs-class-led-driver-aw200xx b/Documentation/ABI/testing/sysfs-class-led-driver-aw200xx
index 6d4449cf9d71f..1d3d74fa6f088 100644
--- a/Documentation/ABI/testing/sysfs-class-led-driver-aw200xx
+++ b/Documentation/ABI/testing/sysfs-class-led-driver-aw200xx
@@ -3,3 +3,111 @@ Date: May 2023
Description: 64-level DIM current. If you write a negative value or
"auto", the dim will be calculated according to the
brightness.
+
+What: /sys/class/leds/<led>/device/<pattern>/clear_leds
+What: /sys/class/leds/<led>/device/<pattern>/select_leds
+Date: December 2023
+Description: Bitmask for clear/select leds to pattern.
+
+What: /sys/class/leds/<led>/device/<pattern>/fall_time
+What: /sys/class/leds/<led>/device/<pattern>/off_time
+What: /sys/class/leds/<led>/device/<pattern>/on_time
+What: /sys/class/leds/<led>/device/<pattern>/rise_time
+Date: December 2023
+Description:
+ Set the pattern fall, off, on and rise times (0..15), where:
+
+ == ======= === ======
+ 0 0 ms 8 2.1 s
+ 1 130 ms 9 2.6 s
+ 2 260 ms 10 3.1 s
+ 3 380 ms 11 4.2 s
+ 4 510 ms 12 5.2 s
+ 5 770 ms 13 6.2 s
+ 6 1.04 s 14 7.3 s
+ 7 1.6 s 15 8.3 s
+ == ======= === ======
+
+What: /sys/class/leds/<led>/device/<pattern>/loop_begin
+Date: December 2023
+Description:
+ Choose where to start the loop (0..3), where:
+
+ == ==================
+ 0 from 'rise' state
+ 1 from 'on' state
+ 2 from 'fall' state
+ 3 from 'off' state
+ == ==================
+
+What: /sys/class/leds/<led>/device/<pattern>/loop_end_on
+Date: December 2023
+Description:
+ Choose where to end the loop (0..1), where:
+
+ == ========================
+ 0 loop end at 'off' state
+ 1 loop end at 'on' state
+ == ========================
+
+What: /sys/class/leds/<led>/device/<pattern>/max_breathing_level
+Date: December 2023
+Description: Maximum breathing Level.
+
+What: /sys/class/leds/<led>/device/<pattern>/min_breathing_level
+Date: December 2023
+Description: Minimum breathing Level
+
+What: /sys/class/leds/<led>/device/<pattern>/mode
+Date: December 2023
+Description: Pattern mode: manual or auto breathing (0, 1) where:
+
+ == ====================
+ 0 manual mode
+ 1 auto breathing mode
+ == ====================
+
+What: /sys/class/leds/<led>/device/<pattern>/ramp
+Date: December 2023
+Description:
+ The smooth ramp down/up function (0, 1), where (see ):
+
+ == ==================
+ 0 ramp mode disable
+ 1 ramp mode enable
+ == ==================
+
+ For more information, see Documentation/leds/leds-aw200xx.rst
+
+What: /sys/class/leds/<led>/device/<pattern>/repeat
+Date: December 2023
+Description: Loop times (0..4095). 0 - the loop is end-less.
+
+What: /sys/class/leds/<led>/device/<pattern>/running
+Date: December 2023
+Description: Reading this file will return the pattern state.
+ This file is read only and supports poll() to detect when the
+ pattern ended.
+ 1 means pattern is running and number 0 are finish or not run.
+
+What: /sys/class/leds/<led>/device/<pattern>/start
+Date: December 2023
+Description:
+ Start/stop pattern (0, 1), where:
+
+ == ==============
+ 0 stop pattern
+ 1 start pattern
+ == ==============
+
+What: /sys/class/leds/<led>/device/<pattern>/toggle
+Date: December 2023
+Description:
+ Manual on/off control (0, 1), where:
+
+ == =========
+ 0 LEDs off
+ 1 LEDs on
+ == =========
+
+ For more information, see Documentation/leds/leds-aw200xx.rst
diff --git a/Documentation/devicetree/bindings/clock/amlogic,a1-audio-clkc.yaml b/Documentation/devicetree/bindings/clock/amlogic,a1-audio-clkc.yaml
new file mode 100644
index 0000000000000..c76cad4da493c
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/amlogic,a1-audio-clkc.yaml
@@ -0,0 +1,83 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/clock/amlogic,a1-audio-clkc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Amlogic A1 Audio Clock Control Unit and Reset Controller
+
+maintainers:
+ - Neil Armstrong <neil.armstrong@linaro.org>
+ - Jerome Brunet <jbrunet@baylibre.com>
+ - Jan Dakinevich <jan.dakinevich@salutedevices.com>
+
+properties:
+ compatible:
+ const: amlogic,a1-audio-clkc
+
+ '#clock-cells':
+ const: 1
+
+ '#reset-cells':
+ const: 1
+
+ reg:
+ minItems: 2
+ maxItems: 2
+
+ clocks:
+ items:
+ - description: input main peripheral bus clock
+ - description: input dds_in
+ - description: input fixed pll div2
+ - description: input fixed pll div3
+ - description: input hifi_pll
+ - description: input oscillator (usually at 24MHz)
+
+ clock-names:
+ items:
+ - const: pclk
+ - const: dds_in
+ - const: fclk_div2
+ - const: fclk_div3
+ - const: hifi_pll
+ - const: xtal
+
+required:
+ - compatible
+ - '#clock-cells'
+ - '#reset-cells'
+ - reg
+ - clocks
+ - clock-names
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/amlogic,a1-pll-clkc.h>
+ #include <dt-bindings/clock/amlogic,a1-peripherals-clkc.h>
+ audio {
+ #address-cells = <2>;
+ #size-cells = <2>;
+
+ clkc_audio: audio-clock-controller@0 {
+ compatible = "amlogic,a1-audio-clkc";
+ reg = <0x0 0xfe050000 0x0 0xb0>,
+ <0x0 0xfe054800 0x0 0x20>;
+ #clock-cells = <1>;
+ #reset-cells = <1>;
+ clocks = <&clkc_periphs CLKID_AUDIO>,
+ <&clkc_periphs CLKID_DDS_IN>,
+ <&clkc_pll CLKID_FCLK_DIV2>,
+ <&clkc_pll CLKID_FCLK_DIV3>,
+ <&clkc_pll CLKID_HIFI_PLL>,
+ <&xtal>;
+ clock-names = "pclk",
+ "dds_in",
+ "fclk_div2",
+ "fclk_div3",
+ "hifi_pll",
+ "xtal";
+ };
+ };
diff --git a/Documentation/devicetree/bindings/clock/amlogic,a1-cpu-clkc.yaml b/Documentation/devicetree/bindings/clock/amlogic,a1-cpu-clkc.yaml
new file mode 100644
index 0000000000000..f4958b315ed4e
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/amlogic,a1-cpu-clkc.yaml
@@ -0,0 +1,64 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/clock/amlogic,a1-cpu-clkc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Amlogic A1 CPU Clock Control Unit
+
+maintainers:
+ - Neil Armstrong <neil.armstrong@linaro.org>
+ - Jerome Brunet <jbrunet@baylibre.com>
+ - Dmitry Rokosov <ddrokosov@salutedevices.com>
+
+properties:
+ compatible:
+ const: amlogic,a1-cpu-clkc
+
+ '#clock-cells':
+ const: 1
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ items:
+ - description: input fixed pll div2
+ - description: input fixed pll div3
+ - description: input sys pll
+ - description: input oscillator (usually at 24MHz)
+
+ clock-names:
+ items:
+ - const: fclk_div2
+ - const: fclk_div3
+ - const: sys_pll
+ - const: xtal
+
+required:
+ - compatible
+ - '#clock-cells'
+ - reg
+ - clocks
+ - clock-names
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/amlogic,a1-pll-clkc.h>
+ apb {
+ #address-cells = <2>;
+ #size-cells = <2>;
+
+ clock-controller@fd000000 {
+ compatible = "amlogic,a1-cpu-clkc";
+ reg = <0 0xfd000080 0 0x8>;
+ #clock-cells = <1>;
+ clocks = <&clkc_pll CLKID_FCLK_DIV2>,
+ <&clkc_pll CLKID_FCLK_DIV3>,
+ <&clkc_pll CLKID_SYS_PLL>,
+ <&xtal>;
+ clock-names = "fclk_div2", "fclk_div3", "sys_pll", "xtal";
+ };
+ };
diff --git a/Documentation/devicetree/bindings/leds/awinic,aw200xx.yaml b/Documentation/devicetree/bindings/leds/awinic,aw200xx.yaml
index 17e971903ee91..b731ef5953efe 100644
--- a/Documentation/devicetree/bindings/leds/awinic,aw200xx.yaml
+++ b/Documentation/devicetree/bindings/leds/awinic,aw200xx.yaml
@@ -44,6 +44,38 @@ properties:
enable-gpios:
maxItems: 1
+ interrupts:
+ maxItems: 1
+
+ fb-refresh-rate:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description:
+ framebuffer refresh rate, Hz
+
+ fb-backlight:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description:
+ framebuffer backlight brightness
+
+ fb-led-max-microamp:
+ description:
+ led-max-microamp property for every LED
+
+ fb-height:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description:
+ framebuffer logical height in pixels
+
+ fb-width:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description:
+ framebuffer logical width in pixels
+
+ fb-map:
+ $ref: /schemas/types.yaml#/definitions/uint8-array
+ description:
+ LED number for every pixel of framebuffer
+
patternProperties:
"^led@[0-9a-f]+$":
type: object
@@ -82,7 +114,7 @@ allOf:
reg:
items:
minimum: 0
- maximum: 36
+ maximum: 35
- if:
properties:
@@ -96,7 +128,7 @@ allOf:
reg:
items:
minimum: 0
- maximum: 54
+ maximum: 53
- if:
properties:
@@ -110,7 +142,7 @@ allOf:
reg:
items:
minimum: 0
- maximum: 72
+ maximum: 71
- if:
properties:
@@ -124,7 +156,7 @@ allOf:
reg:
items:
minimum: 0
- maximum: 108
+ maximum: 107
required:
- compatible
@@ -137,6 +169,7 @@ additionalProperties: false
examples:
- |
#include <dt-bindings/gpio/gpio.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
#include <dt-bindings/leds/common.h>
i2c {
@@ -149,6 +182,15 @@ examples:
#address-cells = <1>;
#size-cells = <0>;
enable-gpios = <&gpio 3 GPIO_ACTIVE_HIGH>;
+ interrupt-parent = <&gpio_intc>;
+ interrupts = <13 IRQ_TYPE_LEVEL_LOW>;
+ fb-height = <3>;
+ fb-width = <12>;
+ fb-refresh-rate = <60>;
+ fb-backlight = <255>;
+ fb-map = /bits/ 8 <0 1 2 3 4 5 6 7 8 9 10 11
+ 12 13 14 15 16 17 18 19 20 21 22 23
+ 24 25 26 27 28 29 30 31 32 33 34 35>;
led@0 {
reg = <0x0>;
diff --git a/Documentation/devicetree/bindings/pwm/pwm-amlogic.yaml b/Documentation/devicetree/bindings/pwm/pwm-amlogic.yaml
index cc3ebd4deeb69..7afe1e94de900 100644
--- a/Documentation/devicetree/bindings/pwm/pwm-amlogic.yaml
+++ b/Documentation/devicetree/bindings/pwm/pwm-amlogic.yaml
@@ -37,6 +37,7 @@ properties:
- enum:
- amlogic,meson8-pwm-v2
- amlogic,meson-s4-pwm
+ - amlogic,meson-a1-pwm
- items:
- enum:
- amlogic,c3-pwm
@@ -134,6 +135,7 @@ allOf:
contains:
enum:
- amlogic,meson-s4-pwm
+ - amlogic,meson-a1-pwm
then:
properties:
clocks:
diff --git a/Documentation/devicetree/bindings/reset/amlogic,meson-axg-audio-arb.yaml b/Documentation/devicetree/bindings/reset/amlogic,meson-axg-audio-arb.yaml
index bc1d284785e1a..57e977db029f4 100644
--- a/Documentation/devicetree/bindings/reset/amlogic,meson-axg-audio-arb.yaml
+++ b/Documentation/devicetree/bindings/reset/amlogic,meson-axg-audio-arb.yaml
@@ -15,9 +15,13 @@ description: The Amlogic Audio ARB is a simple device which enables or disables
properties:
compatible:
- enum:
- - amlogic,meson-axg-audio-arb
- - amlogic,meson-sm1-audio-arb
+ oneOf:
+ - enum:
+ - amlogic,meson-axg-audio-arb
+ - amlogic,meson-sm1-audio-arb
+ - items:
+ - const: amlogic,meson-a1-audio-arb
+ - const: amlogic,meson-sm1-audio-arb
reg:
maxItems: 1
diff --git a/Documentation/devicetree/bindings/sound/amlogic,axg-fifo.yaml b/Documentation/devicetree/bindings/sound/amlogic,axg-fifo.yaml
index b1b48d683101a..1e809c7c21799 100644
--- a/Documentation/devicetree/bindings/sound/amlogic,axg-fifo.yaml
+++ b/Documentation/devicetree/bindings/sound/amlogic,axg-fifo.yaml
@@ -25,6 +25,14 @@ properties:
- amlogic,g12a-frddr
- amlogic,sm1-frddr
- const: amlogic,axg-frddr
+ - items:
+ - const: amlogic,a1-toddr
+ - const: amlogic,sm1-toddr
+ - const: amlogic,axg-toddr
+ - items:
+ - const: amlogic,a1-frddr
+ - const: amlogic,sm1-frddr
+ - const: amlogic,axg-frddr
reg:
maxItems: 1
diff --git a/Documentation/devicetree/bindings/sound/amlogic,axg-pdm.yaml b/Documentation/devicetree/bindings/sound/amlogic,axg-pdm.yaml
index df21dd72fc65b..b129b80533528 100644
--- a/Documentation/devicetree/bindings/sound/amlogic,axg-pdm.yaml
+++ b/Documentation/devicetree/bindings/sound/amlogic,axg-pdm.yaml
@@ -16,6 +16,7 @@ properties:
- enum:
- amlogic,g12a-pdm
- amlogic,sm1-pdm
+ - amlogic,a1-pdm
- const: amlogic,axg-pdm
- const: amlogic,axg-pdm
@@ -40,6 +41,10 @@ properties:
resets:
maxItems: 1
+ sysrate:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description: redefine rate of DSP system clock
+
required:
- compatible
- reg
diff --git a/Documentation/devicetree/bindings/sound/amlogic,axg-sound-card.yaml b/Documentation/devicetree/bindings/sound/amlogic,axg-sound-card.yaml
index 4f13e8ab50b2a..c3d9df8e2eac3 100644
--- a/Documentation/devicetree/bindings/sound/amlogic,axg-sound-card.yaml
+++ b/Documentation/devicetree/bindings/sound/amlogic,axg-sound-card.yaml
@@ -14,7 +14,11 @@ allOf:
properties:
compatible:
- const: amlogic,axg-sound-card
+ oneOf:
+ - const: amlogic,axg-sound-card
+ - items:
+ - const: amlogic,a1-sound-card
+ - const: amlogic,axg-sound-card
audio-aux-devs:
$ref: /schemas/types.yaml#/definitions/phandle-array
diff --git a/Documentation/devicetree/bindings/sound/amlogic,axg-tdm-formatters.yaml b/Documentation/devicetree/bindings/sound/amlogic,axg-tdm-formatters.yaml
index 719ca8fc98c7d..1545224880200 100644
--- a/Documentation/devicetree/bindings/sound/amlogic,axg-tdm-formatters.yaml
+++ b/Documentation/devicetree/bindings/sound/amlogic,axg-tdm-formatters.yaml
@@ -11,14 +11,20 @@ maintainers:
properties:
compatible:
- enum:
- - amlogic,g12a-tdmout
- - amlogic,sm1-tdmout
- - amlogic,axg-tdmout
- - amlogic,g12a-tdmin
- - amlogic,sm1-tdmin
- - amlogic,axg-tdmin
-
+ oneOf:
+ - enum:
+ - amlogic,g12a-tdmout
+ - amlogic,sm1-tdmout
+ - amlogic,axg-tdmout
+ - amlogic,g12a-tdmin
+ - amlogic,sm1-tdmin
+ - amlogic,axg-tdmin
+ - items:
+ - const: amlogic,a1-tdmout
+ - const: amlogic,sm1-tdmout
+ - items:
+ - const: amlogic,a1-tdmin
+ - const: amlogic,sm1-tdmin
clocks:
items:
- description: Peripheral clock
diff --git a/Documentation/devicetree/bindings/sound/amlogic,axg-tdm-iface.yaml b/Documentation/devicetree/bindings/sound/amlogic,axg-tdm-iface.yaml
index 45955d8a26d1c..7c1af85b52b4e 100644
--- a/Documentation/devicetree/bindings/sound/amlogic,axg-tdm-iface.yaml
+++ b/Documentation/devicetree/bindings/sound/amlogic,axg-tdm-iface.yaml
@@ -14,7 +14,11 @@ allOf:
properties:
compatible:
- const: amlogic,axg-tdm-iface
+ oneOf:
+ - const: amlogic,axg-tdm-iface
+ - items:
+ - const: amlogic,a1-tdm-iface
+ - const: amlogic,axg-tdm-iface
"#sound-dai-cells":
const: 0
diff --git a/Documentation/devicetree/bindings/sound/amlogic,g12a-toacodec.yaml b/Documentation/devicetree/bindings/sound/amlogic,g12a-toacodec.yaml
index 23f82bb897508..c47604aa590f8 100644
--- a/Documentation/devicetree/bindings/sound/amlogic,g12a-toacodec.yaml
+++ b/Documentation/devicetree/bindings/sound/amlogic,g12a-toacodec.yaml
@@ -26,6 +26,7 @@ properties:
- items:
- enum:
- amlogic,sm1-toacodec
+ - amlogic,a1-toacodec
- const: amlogic,g12a-toacodec
reg:
diff --git a/Documentation/devicetree/bindings/sound/amlogic,sm1-eqdrc.yaml b/Documentation/devicetree/bindings/sound/amlogic,sm1-eqdrc.yaml
new file mode 100644
index 0000000000000..b8b8522b9a3e5
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/amlogic,sm1-eqdrc.yaml
@@ -0,0 +1,66 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/amlogic,sm1-eqdrc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Amlogic Audio EQDRC module
+
+maintainers:
+ - Neil Armstrong <neil.armstrong@linaro.org>
+ - Jerome Brunet <jbrunet@baylibre.com>
+ - Jan Dakinevich <jan.dakinevich@salutedevices.com>
+
+allOf:
+ - $ref: dai-common.yaml#
+
+properties:
+ compatible:
+ oneOf:
+ - const: amlogic,sm1-eqdrc
+ - items:
+ - const: amlogic,a1-eqdrc
+ - const: amlogic,sm1-eqdrc
+
+ reg:
+ maxItems: 1
+
+ "#sound-dai-cells":
+ const: 0
+
+ clocks:
+ items:
+ - description: Parent clock
+ - description: System clock
+
+ clock-names:
+ items:
+ - const: pclk
+ - const: sysclk
+
+ resets:
+ maxItems: 1
+
+required:
+ - compatible
+ - "#sound-dai-cells"
+ - clocks
+ - clock-names
+ - resets
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/amlogic,a1-audio-clkc.h>
+ #include <dt-bindings/reset/amlogic,meson-a1-audio-reset.h>
+
+ audio-controller {
+ compatible = "amlogic,sm1-eqdrc";
+ reg = <0x0 0xfe052000 0x0 0x214>;
+ #sound-dai-cells = <0>;
+ clocks = <&clkc_audio AUD_CLKID_EQDRC>,
+ <&clkc_audio AUD_CLKID_EQDRC_CLK>;
+ clock-names = "pclk", "sysclk";
+ resets = <&clkc_audio AUD_RESET_EQDRC>;
+ };
diff --git a/Documentation/devicetree/bindings/sound/amlogic,t9015.yaml b/Documentation/devicetree/bindings/sound/amlogic,t9015.yaml
index 5f5cccdbeb345..9fd6f98a52726 100644
--- a/Documentation/devicetree/bindings/sound/amlogic,t9015.yaml
+++ b/Documentation/devicetree/bindings/sound/amlogic,t9015.yaml
@@ -9,9 +9,6 @@ title: Amlogic T9015 Internal Audio DAC
maintainers:
- Jerome Brunet <jbrunet@baylibre.com>
-allOf:
- - $ref: dai-common.yaml#
-
properties:
$nodename:
pattern: "^audio-controller@.*"
@@ -21,7 +18,9 @@ properties:
compatible:
items:
- - const: amlogic,t9015
+ - enum:
+ - amlogic,t9015
+ - amlogic,t9015-a1
clocks:
items:
@@ -43,6 +42,15 @@ properties:
sound-name-prefix: true
+ lineout-left:
+ $ref: /schemas/types.yaml#/definitions/string
+ lineout-right:
+ $ref: /schemas/types.yaml#/definitions/string
+ linein-left:
+ $ref: /schemas/types.yaml#/definitions/string
+ linein-right:
+ $ref: /schemas/types.yaml#/definitions/string
+
required:
- "#sound-dai-cells"
- compatible
@@ -52,6 +60,48 @@ required:
- resets
- AVDD-supply
+
+allOf:
+ - $ref: dai-common.yaml#
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: amlogic,t9015-a1
+ then:
+ properties:
+ lineout-left:
+ items:
+ - enum:
+ - none
+ - right
+ - left-inverted
+ lineout-right:
+ items:
+ - enum:
+ - none
+ - left
+ - right-inverted
+ linein-left:
+ items:
+ - enum:
+ - none
+ - differential
+ - positive
+ - negative
+ linein-right:
+ items:
+ - enum:
+ - none
+ - differential
+ - positive
+ - negative
+ else:
+ lineout-left: false
+ lineout-right: false
+ linein-left: false
+ linein-right: false
+
additionalProperties: false
examples:
diff --git a/Documentation/devicetree/bindings/sound/everest,es7243e.yaml b/Documentation/devicetree/bindings/sound/everest,es7243e.yaml
new file mode 100644
index 0000000000000..4af8092971ccd
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/everest,es7243e.yaml
@@ -0,0 +1,57 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/everest,es7243e.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Everest ES7243E audio ADC
+
+maintainers:
+ - Igor Prusov <ivprusov@salutedevices.com>
+
+allOf:
+ - $ref: dai-common.yaml#
+
+properties:
+ compatible:
+ const: everest,es7243e
+
+ reg:
+ maxItems: 1
+
+ "#sound-dai-cells":
+ const: 0
+
+ clocks:
+ minItems: 2
+ maxItems: 2
+
+ clock-names:
+ items:
+ - const: sclk
+ - const: lrclk
+
+required:
+ - compatible
+ - reg
+ - "#sound-dai-cells"
+ - clocks
+ - clock-names
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ es7243e: es7243e@14 {
+ compatible = "everest,es7243e";
+ reg = <0x14>;
+ status = "okay";
+ #sound-dai-cells = <0>;
+ clocks = <&clk_audio_sclk>,
+ <&clk_audio_lrclk>;
+ clock-names = "sclk", "lrclk";
+ };
+ };
diff --git a/Documentation/leds/leds-aw200xx.rst b/Documentation/leds/leds-aw200xx.rst
new file mode 100644
index 0000000000000..52e777b048857
--- /dev/null
+++ b/Documentation/leds/leds-aw200xx.rst
@@ -0,0 +1,274 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+=================================================
+Kernel driver for AW20036/AW20054/AW20072/AW20108
+=================================================
+
+Description
+-----------
+
+The AW20036/AW20054/AW20072/AW20108 is a 3x12/6x9/6x12/9x12 matrix LED driver
+programmed via an I2C interface. The brightness of each LED is independently
+controlled by FADE and DIM parameter.
+
+Three integrated pattern controllers provide auto breathing or group dimming
+control. Each pattern controller can work in auto breathing or manual control
+mode. All breathing parameters including rising/falling slope, on/off time,
+repeat times, min/max brightness and so on are configurable.
+
+Device attribute
+-----------------------------------
+
+**/sys/class/leds/<led>/dim** - 64-level DIM current. If write negative value
+or "auto", the dim will be calculated according to the brightness.
+
+The configuration files for each pattern are located::
+
+ /sys/bus/i2c/devices/xxxx/pattern0/
+ /sys/bus/i2c/devices/xxxx/pattern1/
+ /sys/bus/i2c/devices/xxxx/pattern2/
+
+Directory layout example for pattern
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+::
+
+ $ ls -l /sys/bus/i2c/devices/xxxx/pattern0/
+ -rw-r--r-- 1 root root 4096 Jan 1 00:00 clear_leds
+ -rw-r--r-- 1 root root 4096 Jan 1 00:00 fall_time
+ -rw-r--r-- 1 root root 4096 Jan 1 00:00 loop_begin
+ -rw-r--r-- 1 root root 4096 Jan 1 00:00 loop_end_on
+ -rw-r--r-- 1 root root 4096 Jan 1 00:00 max_breathing_level
+ -rw-r--r-- 1 root root 4096 Jan 1 00:00 min_breathing_level
+ -rw-r--r-- 1 root root 4096 Jan 1 00:00 mode
+ -rw-r--r-- 1 root root 4096 Jan 1 00:00 off_time
+ -rw-r--r-- 1 root root 4096 Jan 1 00:00 on_time
+ -rw-r--r-- 1 root root 4096 Jan 1 00:00 ramp
+ -rw-r--r-- 1 root root 4096 Jan 1 00:00 repeat
+ -rw-r--r-- 1 root root 4096 Jan 1 00:00 rise_time
+ -r--r--r-- 1 root root 4096 Jan 1 00:00 running
+ -rw-r--r-- 1 root root 4096 Jan 1 00:00 select_leds
+ -rw-r--r-- 1 root root 4096 Jan 1 00:00 start
+ -rw-r--r-- 1 root root 4096 Jan 1 00:00 toggle
+
+Timing parameters
+~~~~~~~~~~~~~~~~~
+
+- **on_time**
+
+- **rise_time**
+
+- **fall_time**
+
+- **off_time**
+
+See :ref:`auto_breath_mode`.
+
+Select from predefined times:
+
+.. flat-table::
+
+ * - Value
+ - Time (in seconds)
+ - Value
+ - Time (in seconds)
+
+ * - 0
+ - 0.00
+ - 8
+ - 2.1
+
+ * - 1
+ - 0.13
+ - 9
+ - 2.6
+
+ * - 2
+ - 0.26
+ - 10
+ - 3.1
+
+ * - 3
+ - 0.38
+ - 11
+ - 4.2
+
+ * - 4
+ - 0.51
+ - 12
+ - 5.2
+
+ * - 5
+ - 0.77
+ - 13
+ - 6.2
+
+ * - 6
+ - 1.04
+ - 14
+ - 7.3
+
+ * - 7
+ - 1.6
+ - 15
+ - 8.3
+
+Example set for rise-time=0.13s, on-time=0.26s,
+fall_time=6.2s, off_time=0.51s:
+
+.. code-block:: bash
+
+ echo 1 > rise_time
+ echo 2 > on_time
+ echo 13 > fall_time
+ echo 4 > off_time
+
+Maximum and minimum breathing Level
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- **max_breathing_level**
+
+- **min_breathing_level**
+
+Loop
+~~~~~
+
+- **loop_begin** - choose where to start the loop:
+
+ .. flat-table::
+
+ * - Value
+ - Description
+
+ * - 0
+ - Begin from 'rise' state
+
+ * - 1
+ - Begin from 'on' state
+
+ * - 2
+ - Begin from 'fall' state
+
+ * - 3
+ - Begin from 'off' state
+
+- **loop_end_on** - write ``1`` loop end at 'on' state.
+
+- **repeat** - loop times. When write ``0``, the loop is end-less.
+
+Others
+~~~~~~
+
+- **clear_leds** - bitmask for clear leds to pattern.
+ For example clear leds 1, 2, 3, 4, 5, 7 (``10111110``):
+
+ .. code-block:: bash
+
+ echo be > clear_leds
+
+- **select_leds** - bitmask for set leds to pattern.
+ For example select leds 0, 3, 6, 7 (``11001001``):
+
+ .. code-block:: bash
+
+ echo c9 > select_leds
+
+- **mode** - pattern mode:
+ ``0`` - manual control, ``1`` - auto breathing
+
+- **start** - start/stop pattern:
+ ``0`` - to stop, ``1`` - to start
+
+- **toggle** - manual on/off control (see :ref:`manual_breath_mode`):
+ ``0`` - LEDs off, ``1`` - LEDs on
+
+- **ramp** - the smooth ramp up/down function (see :ref:`manual_breath_mode`):
+ ``0`` - disable, ``1`` - enable
+
+- **running** - Reading this file will return the pattern state:
+ ``1`` - is running, ``0`` - is finished (or not running)
+
+This file supports poll() to detect when the pattern finished.
+
+.. _auto_breath_mode:
+
+Auto breathing mode
+~~~~~~~~~~~~~~~~~~~
+
+::
+
+ breathing level
+ ^
+ max _ ________________
+ | /. .\
+ | / . . \
+ | / . . \
+ | / . . \
+ | / . . \
+ | / . . \
+ min _ ___/ . . \_______
+ | . . . .
+ | . . . .
+ | . rise . on . fall . off
+ |
+ -|------------------------------------------------> time
+
+Example:
+
+.. code-block:: bash
+
+ echo 10 > rise_time # 3.1 seconds
+ echo 4 > on_time # 0.51 seconds
+ echo 1 > off_time # 0.13 seconds
+ echo 10 > fall_time # 3.1 seconds
+ echo 0 > min_breathing_level
+ echo 255 > max_breathing_level
+ echo 0 > loop_begin # begin from 'rise'
+ echo 0 > loop_end_on # loop end at 'off' state
+ echo 1 > mode # auto breathing mode
+ echo 5 > repeat # 5 times repeat
+ echo 1249 > select_leds # select 0, 3, 6, 9 12 leds (1001001001001)
+ echo 1 > start # run
+
+
+.. _manual_breath_mode:
+
+Manual control mode
+~~~~~~~~~~~~~~~~~~~
+
+When 'ramp' enabled (echo 1 > ramp)::
+
+ breathing level
+ ^
+ max _ ____________________
+ | / .\
+ | / . \
+ | / . \
+ | / . \
+ | / . \
+ | / . \
+ min _ ______/ . \_______
+ | . .
+ | . .
+ | . .
+ | (echo 1 > toggle) (echo 0 > toggle)
+ -|---------------------------------------------------> time
+
+
+When 'ramp' disabled (echo 0 > ramp)::
+
+ breathing level
+ ^
+ max _ __________________________
+ | | |
+ | | |
+ | | |
+ | | |
+ | | |
+ | | |
+ min _ ______| |_______
+ | . .
+ | . .
+ | . .
+ | echo 1 > toggle echo 0 > toggle
+ -|---------------------------------------------------> time
diff --git a/arch/arm/boot/dts/amlogic/meson8b-odroidc1.dts b/arch/arm/boot/dts/amlogic/meson8b-odroidc1.dts
index 1cd2093202caa..4dc789de4e551 100644
--- a/arch/arm/boot/dts/amlogic/meson8b-odroidc1.dts
+++ b/arch/arm/boot/dts/amlogic/meson8b-odroidc1.dts
@@ -361,6 +361,15 @@
pinctrl-names = "default";
};
+&usb0_phy {
+ status = "okay";
+};
+
+&usb0 {
+ dr_mode = "otg";
+ status = "okay";
+};
+
&usb1_phy {
status = "okay";
};
diff --git a/arch/arm/configs/meson8_odroid_c1_defconfig b/arch/arm/configs/meson8_odroid_c1_defconfig
new file mode 100644
index 0000000000000..ab6b97c58da7a
--- /dev/null
+++ b/arch/arm/configs/meson8_odroid_c1_defconfig
@@ -0,0 +1,228 @@
+CONFIG_SYSVIPC=y
+CONFIG_POSIX_MQUEUE=y
+CONFIG_NO_HZ_IDLE=y
+CONFIG_HIGH_RES_TIMERS=y
+CONFIG_CGROUPS=y
+CONFIG_BLK_DEV_INITRD=y
+CONFIG_INITRAMFS_SOURCE="${BR_BINARIES_DIR}/rootfs.cpio"
+CONFIG_PERF_EVENTS=y
+CONFIG_ARCH_MESON=y
+# CONFIG_MACH_MESON6 is not set
+CONFIG_ARM_ERRATA_754322=y
+CONFIG_ARM_ERRATA_775420=y
+CONFIG_ARM_ERRATA_798181=y
+CONFIG_SMP=y
+CONFIG_HAVE_ARM_ARCH_TIMER=y
+CONFIG_HIGHMEM=y
+CONFIG_ARCH_FORCE_MAX_ORDER=12
+CONFIG_CPU_FREQ=y
+CONFIG_CPU_FREQ_GOV_POWERSAVE=y
+CONFIG_CPU_FREQ_GOV_ONDEMAND=y
+CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y
+CONFIG_CPUFREQ_DT=y
+CONFIG_ARM_SCMI_CPUFREQ=y
+CONFIG_CPU_IDLE=y
+CONFIG_ARM_CPUIDLE=y
+CONFIG_VFP=y
+CONFIG_NEON=y
+CONFIG_KERNEL_MODE_NEON=y
+CONFIG_PM_WAKELOCKS=y
+CONFIG_PM_DEBUG=y
+# CONFIG_GCC_PLUGINS is not set
+CONFIG_MODULES=y
+CONFIG_MODULE_UNLOAD=y
+CONFIG_PARTITION_ADVANCED=y
+CONFIG_CMDLINE_PARTITION=y
+CONFIG_NET=y
+CONFIG_PACKET=y
+CONFIG_PACKET_DIAG=y
+CONFIG_UNIX=y
+CONFIG_INET=y
+CONFIG_IP_PNP=y
+CONFIG_IP_PNP_DHCP=y
+CONFIG_IP_PNP_BOOTP=y
+CONFIG_IP_PNP_RARP=y
+CONFIG_IPV6_ROUTER_PREF=y
+CONFIG_IPV6_OPTIMISTIC_DAD=y
+CONFIG_IPV6_MULTIPLE_TABLES=y
+CONFIG_NETLINK_DIAG=y
+CONFIG_RFKILL=y
+CONFIG_RFKILL_GPIO=y
+CONFIG_UEVENT_HELPER=y
+CONFIG_DEVTMPFS=y
+CONFIG_DEVTMPFS_MOUNT=y
+CONFIG_ARM_SCMI_PROTOCOL=y
+CONFIG_ARM_SCPI_PROTOCOL=y
+CONFIG_TRUSTED_FOUNDATIONS=y
+CONFIG_BLK_DEV_LOOP=y
+CONFIG_VIRTIO_BLK=y
+CONFIG_EEPROM_AT24=y
+CONFIG_BLK_DEV_SD=y
+CONFIG_BLK_DEV_SR=y
+CONFIG_ATA=y
+CONFIG_SATA_AHCI_PLATFORM=y
+CONFIG_NETDEVICES=y
+CONFIG_VIRTIO_NET=y
+CONFIG_MACB=y
+CONFIG_HIX5HD2_GMAC=y
+CONFIG_KS8851=y
+CONFIG_SMSC911X=y
+CONFIG_STMMAC_ETH=y
+CONFIG_DWMAC_DWC_QOS_ETH=y
+CONFIG_XILINX_EMACLITE=y
+CONFIG_REALTEK_PHY=y
+CONFIG_MDIO_BITBANG=y
+CONFIG_INPUT_MATRIXKMAP=y
+CONFIG_INPUT_JOYDEV=y
+CONFIG_INPUT_EVDEV=y
+CONFIG_KEYBOARD_GPIO=y
+CONFIG_MOUSE_PS2_ELANTECH=y
+CONFIG_MOUSE_ELAN_I2C=y
+CONFIG_INPUT_TOUCHSCREEN=y
+CONFIG_INPUT_MISC=y
+CONFIG_SERIAL_MESON=y
+CONFIG_SERIAL_MESON_CONSOLE=y
+CONFIG_SERIAL_DEV_BUS=y
+CONFIG_VIRTIO_CONSOLE=y
+CONFIG_HW_RANDOM=y
+CONFIG_I2C=y
+CONFIG_I2C_CHARDEV=y
+CONFIG_I2C_MUX_GPIO=y
+CONFIG_I2C_MUX_PCA954x=y
+CONFIG_I2C_MUX_PINCTRL=y
+CONFIG_I2C_DEMUX_PINCTRL=y
+CONFIG_I2C_MESON=y
+CONFIG_SPI=y
+CONFIG_SPI_MEM=y
+CONFIG_SPI_CADENCE=y
+CONFIG_SPI_XILINX=y
+CONFIG_SPI_SPIDEV=y
+CONFIG_SPMI=y
+CONFIG_GPIO_SYSCON=y
+CONFIG_POWER_RESET=y
+CONFIG_POWER_RESET_SYSCON=y
+CONFIG_POWER_RESET_SYSCON_POWEROFF=y
+CONFIG_SYSCON_REBOOT_MODE=y
+CONFIG_NVMEM_REBOOT_MODE=y
+CONFIG_THERMAL=y
+CONFIG_WATCHDOG=y
+CONFIG_MESON_WATCHDOG=y
+CONFIG_MFD_STMFX=y
+CONFIG_REGULATOR=y
+CONFIG_REGULATOR_FIXED_VOLTAGE=y
+CONFIG_REGULATOR_GPIO=y
+CONFIG_MEDIA_CEC_SUPPORT=y
+CONFIG_CEC_MESON_AO=y
+CONFIG_FB=y
+CONFIG_FB_SIMPLE=y
+CONFIG_USB=y
+CONFIG_USB_OTG=y
+CONFIG_USB_XHCI_HCD=y
+CONFIG_USB_EHCI_HCD=y
+CONFIG_USB_EHCI_ROOT_HUB_TT=y
+CONFIG_USB_EHCI_HCD_PLATFORM=y
+CONFIG_USB_OHCI_HCD=y
+CONFIG_USB_OHCI_HCD_PLATFORM=y
+CONFIG_USB_STORAGE=y
+CONFIG_USB_DWC3=y
+CONFIG_USB_DWC2=y
+CONFIG_NOP_USB_XCEIV=y
+CONFIG_USB_ULPI=y
+CONFIG_USB_GADGET=y
+CONFIG_USB_SNP_UDC_PLAT=y
+CONFIG_USB_BDC_UDC=y
+CONFIG_USB_CONFIGFS=y
+CONFIG_USB_CONFIGFS_NCM=y
+CONFIG_USB_CONFIGFS_ECM=y
+CONFIG_USB_CONFIGFS_ECM_SUBSET=y
+CONFIG_USB_CONFIGFS_F_FS=y
+CONFIG_USB_GADGETFS=m
+CONFIG_USB_G_MULTI=m
+# CONFIG_USB_G_MULTI_RNDIS is not set
+CONFIG_USB_RAW_GADGET=m
+CONFIG_MMC=y
+CONFIG_MMC_BLOCK_MINORS=32
+CONFIG_MMC_SDHCI=y
+CONFIG_MMC_MESON_MX_SDHC=y
+CONFIG_MMC_MESON_MX_SDIO=y
+CONFIG_MMC_CQHCI=y
+CONFIG_MMC_HSQ=y
+CONFIG_NEW_LEDS=y
+CONFIG_LEDS_CLASS=y
+CONFIG_LEDS_GPIO=y
+CONFIG_LEDS_PWM=y
+CONFIG_LEDS_TRIGGERS=y
+CONFIG_LEDS_TRIGGER_HEARTBEAT=y
+CONFIG_LEDS_TRIGGER_PANIC=y
+CONFIG_EDAC=y
+CONFIG_RTC_CLASS=y
+CONFIG_RTC_DRV_MESON=y
+CONFIG_RTC_DRV_MESON_VRTC=y
+CONFIG_DMADEVICES=y
+CONFIG_FSL_EDMA=y
+CONFIG_DW_DMAC=y
+CONFIG_VIRTIO_MMIO=y
+CONFIG_STAGING=y
+CONFIG_CHROME_PLATFORMS=y
+CONFIG_COMMON_CLK_SCMI=y
+CONFIG_HWSPINLOCK=y
+CONFIG_MAILBOX=y
+CONFIG_REMOTEPROC=y
+CONFIG_EXTCON_USB_GPIO=y
+CONFIG_MEMORY=y
+CONFIG_IIO=y
+CONFIG_IIO_SW_TRIGGER=y
+CONFIG_XILINX_XADC=y
+CONFIG_MPU3050_I2C=y
+CONFIG_AK8975=y
+CONFIG_IIO_HRTIMER_TRIGGER=y
+CONFIG_PWM=y
+CONFIG_PWM_MESON=y
+CONFIG_RESET_MESON_AUDIO_ARB=y
+CONFIG_PHY_MESON8_HDMI_TX=y
+# CONFIG_PHY_MESON_GXL_USB2 is not set
+# CONFIG_PHY_MESON_G12A_MIPI_DPHY_ANALOG is not set
+# CONFIG_PHY_MESON_G12A_USB2 is not set
+# CONFIG_PHY_MESON_G12A_USB3_PCIE is not set
+# CONFIG_PHY_MESON_AXG_PCIE is not set
+# CONFIG_PHY_MESON_AXG_MIPI_PCIE_ANALOG is not set
+# CONFIG_PHY_MESON_AXG_MIPI_DPHY is not set
+CONFIG_MESON_DDR_PMU=y
+CONFIG_RAS=y
+CONFIG_NVMEM_MESON_MX_EFUSE=y
+CONFIG_NVMEM_RMEM=m
+CONFIG_TEE=y
+CONFIG_OPTEE=y
+CONFIG_TMPFS=y
+CONFIG_TMPFS_XATTR=y
+CONFIG_SQUASHFS=y
+CONFIG_SQUASHFS_LZO=y
+CONFIG_SQUASHFS_XZ=y
+CONFIG_NFS_FS=y
+CONFIG_NFS_V3_ACL=y
+CONFIG_NFS_V4=y
+CONFIG_NFS_V4_1=y
+CONFIG_NFS_V4_2=y
+CONFIG_ROOT_NFS=y
+CONFIG_NLS_CODEPAGE_437=y
+CONFIG_NLS_ISO8859_1=y
+CONFIG_NLS_UTF8=y
+CONFIG_CRYPTO_DEFLATE=y
+CONFIG_CRYPTO_LZO=y
+CONFIG_CRYPTO_ZSTD=y
+CONFIG_CRYPTO_GHASH_ARM_CE=m
+CONFIG_CRYPTO_SHA1_ARM_NEON=m
+CONFIG_CRYPTO_SHA1_ARM_CE=m
+CONFIG_CRYPTO_SHA2_ARM_CE=m
+CONFIG_CRYPTO_SHA512_ARM=m
+CONFIG_CRYPTO_AES_ARM=m
+CONFIG_CRYPTO_AES_ARM_BS=m
+CONFIG_CRYPTO_AES_ARM_CE=m
+CONFIG_CRYPTO_CHACHA20_NEON=m
+CONFIG_CRYPTO_CRC32_ARM_CE=m
+CONFIG_CRC16=y
+CONFIG_PRINTK_TIME=y
+CONFIG_CONSOLE_LOGLEVEL_DEFAULT=9
+CONFIG_DYNAMIC_DEBUG=y
+CONFIG_MAGIC_SYSRQ=y
+CONFIG_DEBUG_FS=y
diff --git a/arch/arm64/boot/dts/amlogic/meson-a1-ad402.dts b/arch/arm64/boot/dts/amlogic/meson-a1-ad402.dts
index 0d92f5253b640..6c89e2f8011d7 100644
--- a/arch/arm64/boot/dts/amlogic/meson-a1-ad402.dts
+++ b/arch/arm64/boot/dts/amlogic/meson-a1-ad402.dts
@@ -9,6 +9,8 @@
#include "meson-a1.dtsi"
#include <dt-bindings/thermal/thermal.h>
+#include <dt-bindings/gpio/gpio.h>
+
/ {
compatible = "amlogic,ad402", "amlogic,a1";
model = "Amlogic Meson A1 AD402 Development Board";
@@ -128,6 +130,102 @@
};
};
};
+
+ amplifier: amplifier {
+ compatible = "simple-audio-amplifier";
+ sound-name-prefix = "AMPLIFIER";
+ enable-gpios = <&gpio GPIOF_4 GPIO_ACTIVE_HIGH>;
+ VCC-supply = <&battery_4v2>;
+ };
+
+ dmics: dmics {
+ compatible = "dmic-codec";
+ #sound-dai-cells = <0>;
+ sound-name-prefix = "MIC";
+ num-channels = <4>;
+ wakeup-delay-ms = <50>;
+ };
+
+ sound {
+ compatible = "amlogic,a1-sound-card",
+ "amlogic,axg-sound-card";
+ model = "AD402";
+ audio-aux-devs = <&tdmout_a>,
+ <&amplifier>,
+ <&tdmin_lb>,
+ <&tdmin_a>;
+ audio-routing = "TDMOUT_A IN 0", "FRDDR_A OUT 0",
+ "TDM_A Playback", "TDMOUT_A OUT",
+ "AMPLIFIER INL", "ACODEC LOLP",
+ "AMPLIFIER INR", "ACODEC LORP",
+
+ "TODDR_A IN 4", "PDM Capture",
+
+ "TDMIN_LB IN 0", "TDM_A Loopback",
+ "TODDR_B IN 6", "TDMIN_LB OUT",
+
+ "TDMIN_A IN 3", "TDM_A Capture",
+ "TODDR_B IN 0", "TDMIN_A OUT";
+ assigned-clocks = <&clkc_pll CLKID_HIFI_PLL>;
+ assigned-clock-parents = <0>;
+ assigned-clock-rates = <1536000000>;
+
+
+ dai-link-0 {
+ link-name = "speaker";
+ sound-dai = <&frddr_a>;
+ };
+
+ dai-link-1 {
+ link-name = "dmics";
+ sound-dai = <&toddr_a>;
+ };
+
+ dai-link-2 {
+ link-name = "amics";
+ sound-dai = <&toddr_b>;
+ };
+
+ dai-link-3 {
+ sound-dai = <&tdmif_a>;
+ dai-format = "i2s";
+ dai-tdm-slot-tx-mask-0 = <1 1>;
+ dai-tdm-slot-rx-mask-0 = <1 1>;
+ mclk-fs = <256>;
+
+ codec-0 {
+ sound-dai = <&toacodec TOACODEC_IN_A>;
+ };
+
+ codec-1 {
+ sound-dai = <&toacodec TOACODEC_CAPTURE_OUT_A>;
+ };
+ };
+
+ dai-link-4 {
+ sound-dai = <&toacodec TOACODEC_OUT>;
+
+ codec {
+ sound-dai = <&acodec>;
+ };
+ };
+
+ dai-link-5 {
+ sound-dai = <&toacodec TOACODEC_CAPTURE_IN>;
+
+ codec {
+ sound-dai = <&acodec>;
+ };
+ };
+
+ dai-link-6 {
+ sound-dai = <&pdm>;
+
+ codec {
+ sound-dai = <&dmics>;
+ };
+ };
+ };
};
/* Bluetooth HCI H4 */
@@ -190,3 +288,19 @@
vmmc-supply = <&vddao_3v3>;
vqmmc-supply = <&vddio_1v8>;
};
+
+&acodec {
+ AVDD-supply = <&vddio_1v8>;
+ lineout-left = "right-inverted";
+ lineout-right = "right";
+ linein-left = "differential";
+ linein-right = "differential";
+};
+
+&pdm {
+ sysrate = <256000000>;
+ pinctrl-0 = <&pdm_din0_a_pins>,
+ <&pdm_din1_a_pins>,
+ <&pdm_dclk_a_pins>;
+ pinctrl-names = "default";
+};
diff --git a/arch/arm64/boot/dts/amlogic/meson-a1.dtsi b/arch/arm64/boot/dts/amlogic/meson-a1.dtsi
index 348411411f3d1..6bb5c3ee444ac 100644
--- a/arch/arm64/boot/dts/amlogic/meson-a1.dtsi
+++ b/arch/arm64/boot/dts/amlogic/meson-a1.dtsi
@@ -3,13 +3,18 @@
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
+#include <dt-bindings/clock/amlogic,a1-cpu-clkc.h>
#include <dt-bindings/clock/amlogic,a1-pll-clkc.h>
#include <dt-bindings/clock/amlogic,a1-peripherals-clkc.h>
+#include <dt-bindings/clock/amlogic,a1-audio-clkc.h>
#include <dt-bindings/gpio/meson-a1-gpio.h>
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <dt-bindings/interrupt-controller/irq.h>
#include <dt-bindings/power/meson-a1-power.h>
#include <dt-bindings/reset/amlogic,meson-a1-reset.h>
+#include <dt-bindings/reset/amlogic,meson-a1-audio-reset.h>
+#include <dt-bindings/reset/amlogic,meson-axg-audio-arb.h>
+#include <dt-bindings/sound/meson-g12a-toacodec.h>
/ {
compatible = "amlogic,a1";
@@ -34,6 +39,13 @@
i-cache-size = <0x8000>;
i-cache-sets = <32>;
next-level-cache = <&l2>;
+ clocks = <&clkc_cpu CLKID_CPU_CLK>;
+ clock-names = "core_clk";
+ operating-points-v2 = <&cpu_opp_table0>;
+ voltage-tolerance = <0>;
+ clock-latency = <50000>;
+ capacity-dmips-mhz = <400>;
+ dynamic-power-coefficient = <80>;
#cooling-cells = <2>;
};
@@ -49,6 +61,13 @@
i-cache-size = <0x8000>;
i-cache-sets = <32>;
next-level-cache = <&l2>;
+ clocks = <&clkc_cpu CLKID_CPU_CLK>;
+ clock-names = "core_clk";
+ operating-points-v2 = <&cpu_opp_table0>;
+ voltage-tolerance = <0>;
+ clock-latency = <50000>;
+ capacity-dmips-mhz = <400>;
+ dynamic-power-coefficient = <80>;
#cooling-cells = <2>;
};
@@ -62,6 +81,36 @@
};
};
+ cpu_opp_table0: cpu_opp_table0 {
+ compatible = "operating-points-v2";
+ opp-shared;
+
+ opp00 {
+ opp-hz = /bits/ 64 <128000000>;
+ opp-microvolt = <840000>;
+ };
+ opp01 {
+ opp-hz = /bits/ 64 <256000000>;
+ opp-microvolt = <840000>;
+ };
+ opp02 {
+ opp-hz = /bits/ 64 <512000000>;
+ opp-microvolt = <840000>;
+ };
+ opp03 {
+ opp-hz = /bits/ 64 <768000000>;
+ opp-microvolt = <840000>;
+ };
+ opp04 {
+ opp-hz = /bits/ 64 <1008000000>;
+ opp-microvolt = <840000>;
+ };
+ opp05 {
+ opp-hz = /bits/ 64 <1200000000>;
+ opp-microvolt = <840000>;
+ };
+ };
+
efuse: efuse {
compatible = "amlogic,meson-gxbb-efuse";
clocks = <&clkc_periphs CLKID_OTP>;
@@ -105,6 +154,19 @@
#size-cells = <2>;
ranges;
+ clkc_cpu: clock-controller@fd000000 {
+ compatible = "amlogic,a1-cpu-clkc";
+ reg = <0 0xfd000080 0 0x8>;
+ #clock-cells = <1>;
+
+ clocks = <&clkc_pll CLKID_FCLK_DIV2>,
+ <&clkc_pll CLKID_FCLK_DIV3>,
+ <&clkc_pll CLKID_SYS_PLL>,
+ <&xtal>;
+ clock-names = "fclk_div2", "fclk_div3",
+ "sys_pll", "xtal";
+ };
+
spifc: spi@fd000400 {
compatible = "amlogic,a1-spifc";
reg = <0x0 0xfd000400 0x0 0x290>;
@@ -261,6 +323,22 @@
};
};
+ uart_c_pins1: uart-c-pins1 {
+ mux {
+ groups = "uart_c_rx_x1",
+ "uart_c_tx_x0";
+ function = "uart_c";
+ };
+ };
+
+ uart_c_pins2: uart-c-pins2 {
+ mux {
+ groups = "uart_c_rx_x16",
+ "uart_c_tx_x15";
+ function = "uart_c";
+ };
+ };
+
pwm_a_pins1: pwm-a-pins1 {
mux {
groups = "pwm_a_x6";
@@ -480,6 +558,250 @@
function = "spif";
};
};
+
+ tdm_a_din0_pins: tdm-a-din0 {
+ mux {
+ groups = "tdm_a_din0";
+ function = "tdm_a";
+ bias-disable;
+ };
+ };
+
+ tdm_a_din1_pins: tdm-a-din1 {
+ mux {
+ groups = "tdm_a_din1";
+ function = "tdm_a";
+ bias-disable;
+ };
+ };
+
+ tdm_a_dout0_pins: tdm-a-dout0 {
+ mux {
+ groups = "tdm_a_dout0";
+ function = "tdm_a";
+ bias-disable;
+ drive-strength-microamp = <3000>;
+ };
+ };
+
+ tdm_a_dout1_pins: tdm-a-dout1 {
+ mux {
+ groups = "tdm_a_dout1";
+ function = "tdm_a";
+ bias-disable;
+ drive-strength-microamp = <3000>;
+ };
+ };
+
+ tdm_a_fs_pins: tdm-a-fs {
+ mux {
+ groups = "tdm_a_fs";
+ function = "tdm_a";
+ bias-disable;
+ drive-strength-microamp = <3000>;
+ };
+ };
+
+ tdm_a_sclk_pins: tdm-a-sclk {
+ mux {
+ groups = "tdm_a_sclk";
+ function = "tdm_a";
+ bias-disable;
+ drive-strength-microamp = <3000>;
+ };
+ };
+
+ tdm_a_slv_fs_pins: tdm-a-slv-fs {
+ mux {
+ groups = "tdm_a_slv_fs";
+ function = "tdm_a";
+ bias-disable;
+ };
+ };
+
+ tdm_a_slv_sclk_pins: tdm-a-slv-sclk {
+ mux {
+ groups = "tdm_a_slv_sclk";
+ function = "tdm_a";
+ bias-disable;
+ };
+ };
+
+ tdm_b_din0_pins: tdm-b-din0 {
+ mux {
+ groups = "tdm_b_din0";
+ function = "tdm_b";
+ bias-disable;
+ };
+ };
+
+ tdm_b_din1_pins: tdm-b-din1 {
+ mux {
+ groups = "tdm_b_din1";
+ function = "tdm_b";
+ bias-disable;
+ };
+ };
+
+ tdm_b_din2_pins: tdm-b-din2 {
+ mux {
+ groups = "tdm_b_din2";
+ function = "tdm_b";
+ bias-disable;
+ };
+ };
+
+ tdm_b_dout0_pins: tdm-b-dout0 {
+ mux {
+ groups = "tdm_b_dout0";
+ function = "tdm_b";
+ bias-disable;
+ drive-strength-microamp = <3000>;
+ };
+ };
+
+ tdm_b_dout1_pins: tdm-b-dout1 {
+ mux {
+ groups = "tdm_b_dout1";
+ function = "tdm_b";
+ bias-disable;
+ drive-strength-microamp = <3000>;
+ };
+ };
+
+ tdm_b_dout2_pins: tdm-b-dout2 {
+ mux {
+ groups = "tdm_b_dout2";
+ function = "tdm_b";
+ bias-disable;
+ drive-strength-microamp = <3000>;
+ };
+ };
+
+ tdm_b_dout3_pins: tdm-b-dout3 {
+ mux {
+ groups = "tdm_b_dout3";
+ function = "tdm_b";
+ bias-disable;
+ drive-strength-microamp = <3000>;
+ };
+ };
+
+ tdm_b_dout4_pins: tdm-b-dout4 {
+ mux {
+ groups = "tdm_b_dout4";
+ function = "tdm_b";
+ bias-disable;
+ drive-strength-microamp = <3000>;
+ };
+ };
+
+ tdm_b_dout5_pins: tdm-b-dout5 {
+ mux {
+ groups = "tdm_b_dout5";
+ function = "tdm_b";
+ bias-disable;
+ drive-strength-microamp = <3000>;
+ };
+ };
+
+ tdm_b_fs_pins: tdm-b-fs {
+ mux {
+ groups = "tdm_b_fs";
+ function = "tdm_b";
+ bias-disable;
+ drive-strength-microamp = <3000>;
+ };
+ };
+
+ tdm_b_sclk_pins: tdm-b-sclk {
+ mux {
+ groups = "tdm_b_sclk";
+ function = "tdm_b";
+ bias-disable;
+ drive-strength-microamp = <3000>;
+ };
+ };
+
+ tdm_b_slv_fs_pins: tdm-b-slv-fs {
+ mux {
+ groups = "tdm_b_slv_fs";
+ function = "tdm_b";
+ bias-disable;
+ };
+ };
+
+ tdm_b_slv_sclk_pins: tdm-b-slv-sclk {
+ mux {
+ groups = "tdm_b_slv_sclk";
+ function = "tdm_b";
+ bias-disable;
+ };
+ };
+
+ pdm_din0_a_pins: pdm-din0-a {
+ mux {
+ groups = "pdm_din0_a";
+ function = "pdm";
+ bias-disable;
+ };
+ };
+
+ pdm_din0_x_pins: pdm-din0-x {
+ mux {
+ groups = "pdm_din0_x";
+ function = "pdm";
+ bias-disable;
+ };
+ };
+
+ pdm_din1_a_pins: pdm-din1-a {
+ mux {
+ groups = "pdm_din1_a";
+ function = "pdm";
+ bias-disable;
+ };
+ };
+
+ pdm_din1_x_pins: pdm-din1-x {
+ mux {
+ groups = "pdm_din1_x";
+ function = "pdm";
+ bias-disable;
+ };
+ };
+
+ pdm_din2_a_pins: pdm-din2-a {
+ mux {
+ groups = "pdm_din2_a";
+ function = "pdm";
+ bias-disable;
+ };
+ };
+
+ pdm_din2_x_pins: pdm-din2-x {
+ mux {
+ groups = "pdm_din2_x";
+ function = "pdm";
+ bias-disable;
+ };
+ };
+
+ pdm_dclk_a_pins: pdm-dclk-a {
+ mux {
+ groups = "pdm_dclk_a";
+ function = "pdm";
+ bias-disable;
+ };
+ };
+
+ pdm_dclk_x_pins: pdm-dclk-x {
+ mux {
+ groups = "pdm_dclk_x";
+ function = "pdm";
+ bias-disable;
+ };
+ };
};
gpio_intc: interrupt-controller@440 {
@@ -501,10 +823,12 @@
<&clkc_pll CLKID_FCLK_DIV5>,
<&clkc_pll CLKID_FCLK_DIV7>,
<&clkc_pll CLKID_HIFI_PLL>,
- <&xtal>;
+ <&xtal>,
+ <&clkc_pll CLKID_SYS_PLL>;
clock-names = "fclk_div2", "fclk_div3",
"fclk_div5", "fclk_div7",
- "hifi_pll", "xtal";
+ "hifi_pll", "xtal",
+ "sys_pll";
};
i2c0: i2c@1400 {
@@ -538,6 +862,16 @@
status = "disabled";
};
+ uart_AO_C: serial@7000 {
+ compatible = "amlogic,meson-a1-uart",
+ "amlogic,meson-ao-uart";
+ reg = <0x0 0x7000 0x0 0x18>;
+ interrupts = <GIC_SPI 31 IRQ_TYPE_EDGE_RISING>;
+ clocks = <&xtal>, <&xtal>, <&xtal>;
+ clock-names = "xtal", "pclk", "baud";
+ status = "disabled";
+ };
+
pwm_ab: pwm@2400 {
compatible = "amlogic,meson-a1-pwm",
"amlogic,meson-s4-pwm";
@@ -660,8 +994,10 @@
reg = <0 0x7c80 0 0x18c>;
#clock-cells = <1>;
clocks = <&clkc_periphs CLKID_FIXPLL_IN>,
- <&clkc_periphs CLKID_HIFIPLL_IN>;
- clock-names = "fixpll_in", "hifipll_in";
+ <&clkc_periphs CLKID_HIFIPLL_IN>,
+ <&clkc_periphs CLKID_SYSPLL_IN>;
+ clock-names = "fixpll_in", "hifipll_in",
+ "syspll_in";
};
sd_emmc: mmc@10000 {
@@ -681,6 +1017,221 @@
assigned-clocks = <&clkc_periphs CLKID_SD_EMMC>;
assigned-clock-rates = <24000000>;
};
+
+ acodec: audio-controller@4800 {
+ compatible = "amlogic,t9015-a1";
+ reg = <0x0 0x4800 0x0 0x14>;
+ #sound-dai-cells = <0>;
+ sound-name-prefix = "ACODEC";
+ clocks = <&clkc_periphs CLKID_AUDIO>;
+ clock-names = "pclk";
+ resets = <&reset RESET_ACODEC>;
+ power-domains = <&pwrc PWRC_ACODEC_ID>;
+ };
+
+ audio: bus@50000 {
+ compatible = "simple-bus";
+ #address-cells = <2>;
+ #size-cells = <2>;
+ ranges = <0x0 0x0 0x0 0x50000 0 0x4980>;
+ power-domains = <&pwrc PWRC_AUDIO_ID>;
+
+ clkc_audio: audio-clock-controller@0 {
+ compatible = "amlogic,a1-audio-clkc";
+ reg = <0x0 0x0 0x0 0xb0>,
+ <0x0 0x4800 0x0 0x20>;
+ #clock-cells = <1>;
+ #reset-cells = <1>;
+ clocks = <&clkc_periphs CLKID_AUDIO>,
+ <&clkc_periphs CLKID_DDS_IN>,
+ <&clkc_pll CLKID_FCLK_DIV2>,
+ <&clkc_pll CLKID_FCLK_DIV3>,
+ <&clkc_pll CLKID_HIFI_PLL>,
+ <&xtal>;
+ clock-names = "pclk", "dds_in",
+ "fclk_div2", "fclk_div3",
+ "hifi_pll", "xtal";
+
+ resets = <&reset RESET_AUDIO>;
+ };
+
+ toddr_a: audio-controller@100 {
+ compatible = "amlogic,a1-toddr",
+ "amlogic,sm1-toddr",
+ "amlogic,axg-toddr";
+ reg = <0x0 0x100 0x0 0x2c>;
+ #sound-dai-cells = <0>;
+ sound-name-prefix = "TODDR_A";
+ interrupts = <GIC_SPI 37 IRQ_TYPE_EDGE_RISING>;
+ clocks = <&clkc_audio AUD_CLKID_TODDR_A>;
+ resets = <&arb AXG_ARB_TODDR_A>,
+ <&clkc_audio AUD_RESET_TODDR_A>;
+ reset-names = "arb", "rst";
+ amlogic,fifo-depth = <128>;
+ };
+
+ toddr_b: audio-controller@140 {
+ compatible = "amlogic,a1-toddr",
+ "amlogic,sm1-toddr",
+ "amlogic,axg-toddr";
+ reg = <0x0 0x140 0x0 0x2c>;
+ #sound-dai-cells = <0>;
+ sound-name-prefix = "TODDR_B";
+ interrupts = <GIC_SPI 38 IRQ_TYPE_EDGE_RISING>;
+ clocks = <&clkc_audio AUD_CLKID_TODDR_B>;
+ resets = <&arb AXG_ARB_TODDR_B>,
+ <&clkc_audio AUD_RESET_TODDR_B>;
+ reset-names = "arb", "rst";
+ amlogic,fifo-depth = <128>;
+ };
+
+ frddr_a: audio-controller@1c0 {
+ compatible = "amlogic,a1-frddr",
+ "amlogic,sm1-frddr",
+ "amlogic,axg-frddr";
+ reg = <0x0 0x1c0 0x0 0x2c>;
+ #sound-dai-cells = <0>;
+ sound-name-prefix = "FRDDR_A";
+ interrupts = <GIC_SPI 40 IRQ_TYPE_EDGE_RISING>;
+ clocks = <&clkc_audio AUD_CLKID_FRDDR_A>;
+ resets = <&arb AXG_ARB_FRDDR_A>,
+ <&clkc_audio AUD_RESET_FRDDR_A>;
+ reset-names = "arb", "rst";
+ amlogic,fifo-depth = <128>;
+ };
+
+ frddr_b: audio-controller@200 {
+ compatible = "amlogic,a1-frddr",
+ "amlogic,sm1-frddr",
+ "amlogic,axg-frddr";
+ reg = <0x0 0x200 0x0 0x2c>;
+ #sound-dai-cells = <0>;
+ sound-name-prefix = "FRDDR_B";
+ interrupts = <GIC_SPI 41 IRQ_TYPE_EDGE_RISING>;
+ clocks = <&clkc_audio AUD_CLKID_FRDDR_B>;
+ resets = <&arb AXG_ARB_FRDDR_B>,
+ <&clkc_audio AUD_RESET_FRDDR_B>;
+ reset-names = "arb", "rst";
+ amlogic,fifo-depth = <128>;
+ };
+
+ arb: reset-controller@280 {
+ compatible = "amlogic,meson-a1-audio-arb",
+ "amlogic,meson-sm1-audio-arb";
+ reg = <0x0 0x280 0x0 0x4>;
+ #reset-cells = <1>;
+ clocks = <&clkc_audio AUD_CLKID_DDR_ARB>;
+ };
+
+ tdmin_a: audio-controller@300 {
+ compatible = "amlogic,a1-tdmin",
+ "amlogic,sm1-tdmin";
+ reg = <0x0 0x300 0x0 0x40>;
+ sound-name-prefix = "TDMIN_A";
+ clocks = <&clkc_audio AUD_CLKID_TDMIN_A>,
+ <&clkc_audio AUD_CLKID_TDMIN_A_SCLK>,
+ <&clkc_audio AUD_CLKID_TDMIN_A_SCLK_SEL>,
+ <&clkc_audio AUD_CLKID_TDMIN_A_LRCLK>,
+ <&clkc_audio AUD_CLKID_TDMIN_A_LRCLK>;
+ clock-names = "pclk", "sclk", "sclk_sel",
+ "lrclk", "lrclk_sel";
+ resets = <&clkc_audio AUD_RESET_TDMIN_A>;
+ };
+
+ tdmin_b: audio-controller@340 {
+ compatible = "amlogic,a1-tdmin",
+ "amlogic,sm1-tdmin";
+ reg = <0x0 0x340 0x0 0x40>;
+ sound-name-prefix = "TDMIN_B";
+ clocks = <&clkc_audio AUD_CLKID_TDMIN_B>,
+ <&clkc_audio AUD_CLKID_TDMIN_B_SCLK>,
+ <&clkc_audio AUD_CLKID_TDMIN_B_SCLK_SEL>,
+ <&clkc_audio AUD_CLKID_TDMIN_B_LRCLK>,
+ <&clkc_audio AUD_CLKID_TDMIN_B_LRCLK>;
+ clock-names = "pclk", "sclk", "sclk_sel",
+ "lrclk", "lrclk_sel";
+ resets = <&clkc_audio AUD_RESET_TDMIN_B>;
+ };
+
+ tdmin_lb: audio-controller@3c0 {
+ compatible = "amlogic,a1-tdmin",
+ "amlogic,sm1-tdmin";
+ reg = <0x0 0x3c0 0x0 0x40>;
+ sound-name-prefix = "TDMIN_LB";
+ clocks = <&clkc_audio AUD_CLKID_TDMIN_LB>,
+ <&clkc_audio AUD_CLKID_TDMIN_LB_SCLK>,
+ <&clkc_audio AUD_CLKID_TDMIN_LB_SCLK_SEL>,
+ <&clkc_audio AUD_CLKID_TDMIN_LB_LRCLK>,
+ <&clkc_audio AUD_CLKID_TDMIN_LB_LRCLK>;
+ clock-names = "pclk", "sclk", "sclk_sel",
+ "lrclk", "lrclk_sel";
+ resets = <&clkc_audio AUD_RESET_TDMIN_LB>;
+ };
+
+ tdmout_a: audio-controller@500 {
+ compatible = "amlogic,a1-tdmout",
+ "amlogic,sm1-tdmout";
+ reg = <0x0 0x500 0x0 0x40>;
+ sound-name-prefix = "TDMOUT_A";
+ clocks = <&clkc_audio AUD_CLKID_TDMOUT_A>,
+ <&clkc_audio AUD_CLKID_TDMOUT_A_SCLK>,
+ <&clkc_audio AUD_CLKID_TDMOUT_A_SCLK_SEL>,
+ <&clkc_audio AUD_CLKID_TDMOUT_A_LRCLK>,
+ <&clkc_audio AUD_CLKID_TDMOUT_A_LRCLK>;
+ clock-names = "pclk", "sclk", "sclk_sel",
+ "lrclk", "lrclk_sel";
+ resets = <&clkc_audio AUD_RESET_TDMOUT_A>;
+ };
+
+ tdmout_b: audio-controller@540 {
+ compatible = "amlogic,a1-tdmout",
+ "amlogic,sm1-tdmout";
+ reg = <0x0 0x540 0x0 0x40>;
+ sound-name-prefix = "TDMOUT_B";
+ clocks = <&clkc_audio AUD_CLKID_TDMOUT_B>,
+ <&clkc_audio AUD_CLKID_TDMOUT_B_SCLK>,
+ <&clkc_audio AUD_CLKID_TDMOUT_B_SCLK_SEL>,
+ <&clkc_audio AUD_CLKID_TDMOUT_B_LRCLK>,
+ <&clkc_audio AUD_CLKID_TDMOUT_B_LRCLK>;
+ clock-names = "pclk", "sclk", "sclk_sel",
+ "lrclk", "lrclk_sel";
+ resets = <&clkc_audio AUD_RESET_TDMOUT_B>;
+ };
+
+ toacodec: audio-controller@740 {
+ compatible = "amlogic,a1-toacodec",
+ "amlogic,g12a-toacodec";
+ reg = <0x0 0x740 0x0 0x4>;
+ #sound-dai-cells = <1>;
+ sound-name-prefix = "TOACODEC";
+ resets = <&clkc_audio AUD_RESET_TOACODEC>;
+ };
+
+ pdm: audio-controller@1000 {
+ compatible = "amlogic,a1-pdm",
+ "amlogic,axg-pdm";
+ reg = <0x0 0x1000 0x0 0x34>;
+ #sound-dai-cells = <0>;
+ sound-name-prefix = "PDM";
+ clocks = <&clkc_audio AUD2_CLKID_PDM>,
+ <&clkc_audio AUD2_CLKID_PDM_DCLK>,
+ <&clkc_audio AUD2_CLKID_PDM_SYSCLK>;
+ clock-names = "pclk", "dclk", "sysclk";
+ power-domains = <&pwrc PWRC_PDMIN_ID>;
+ };
+
+ eqdrc: audio-controller@2000 {
+ compatible = "amlogic,a1-eqdrc",
+ "amlogic,sm1-eqdrc";
+ reg = <0x0 0x2000 0x0 0x214>;
+ #sound-dai-cells = <0>;
+ sound-name-prefix = "EQDRC";
+ clocks = <&clkc_audio AUD_CLKID_EQDRC>,
+ <&clkc_audio AUD_CLKID_EQDRC_CLK>;
+ clock-names = "pclk", "sysclk";
+ resets = <&clkc_audio AUD_RESET_EQDRC>;
+ };
+ };
};
usb: usb@fe004400 {
@@ -762,4 +1313,26 @@
clock-output-names = "xtal";
#clock-cells = <0>;
};
+
+ tdmif_a: audio-controller-0 {
+ compatible = "amlogic,a1-tdm-iface",
+ "amlogic,axg-tdm-iface";
+ #sound-dai-cells = <0>;
+ sound-name-prefix = "TDM_A";
+ clocks = <&clkc_audio AUD_CLKID_MST_A_SCLK>,
+ <&clkc_audio AUD_CLKID_MST_A_LRCLK>,
+ <&clkc_audio AUD_CLKID_MST_A_MCLK>;
+ clock-names = "sclk", "lrclk", "mclk";
+ };
+
+ tdmif_b: audio-controller-1 {
+ compatible = "amlogic,a1-tdm-iface",
+ "amlogic,axg-tdm-iface";
+ #sound-dai-cells = <0>;
+ sound-name-prefix = "TDM_B";
+ clocks = <&clkc_audio AUD_CLKID_MST_B_SCLK>,
+ <&clkc_audio AUD_CLKID_MST_B_LRCLK>,
+ <&clkc_audio AUD_CLKID_MST_B_MCLK>;
+ clock-names = "sclk", "lrclk", "mclk";
+ };
};
diff --git a/arch/arm64/boot/dts/amlogic/meson-sm1-ac200.dts b/arch/arm64/boot/dts/amlogic/meson-sm1-ac200.dts
new file mode 100644
index 0000000000000..502e37485c6a1
--- /dev/null
+++ b/arch/arm64/boot/dts/amlogic/meson-sm1-ac200.dts
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+/*
+ * Copyright (c) 2022 SberDevices
+ *
+ * Confidential and Proprietary.
+ * Unauthorized copying of this file, via any medium is strictly prohibited.
+ */
+
+/dts-v1/;
+
+#include "meson-sm1-ac2xx.dtsi"
+
+#include <dt-bindings/input/input.h>
+#include <dt-bindings/leds/common.h>
+#include <dt-bindings/gpio/meson-g12a-gpio.h>
+#include <dt-bindings/sound/meson-g12a-tohdmitx.h>
+
+/ {
+ compatible = "amlogic,ac200", "amlogic,sm1";
+ model = "Amlogic AC200";
+
+ memory@0 {
+ device_type = "memory";
+ reg = <0x0 0x0 0x0 0x80000000>; /* 2GB */
+ };
+};
diff --git a/arch/arm64/configs/meson_g12b_khadas_vim3_defconfig b/arch/arm64/configs/meson_g12b_khadas_vim3_defconfig
new file mode 100644
index 0000000000000..e49f941e99a2a
--- /dev/null
+++ b/arch/arm64/configs/meson_g12b_khadas_vim3_defconfig
@@ -0,0 +1,402 @@
+CONFIG_SYSVIPC=y
+CONFIG_GENERIC_IRQ_DEBUGFS=y
+CONFIG_NO_HZ_IDLE=y
+CONFIG_HIGH_RES_TIMERS=y
+CONFIG_PREEMPT=y
+CONFIG_IRQ_TIME_ACCOUNTING=y
+CONFIG_BSD_PROCESS_ACCT=y
+CONFIG_BSD_PROCESS_ACCT_V3=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_NUMA_BALANCING=y
+CONFIG_MEMCG=y
+CONFIG_BLK_CGROUP=y
+CONFIG_CGROUP_PIDS=y
+CONFIG_CGROUP_HUGETLB=y
+CONFIG_CPUSETS=y
+CONFIG_CGROUP_DEVICE=y
+CONFIG_CGROUP_CPUACCT=y
+CONFIG_CGROUP_PERF=y
+CONFIG_USER_NS=y
+CONFIG_SCHED_AUTOGROUP=y
+CONFIG_BLK_DEV_INITRD=y
+CONFIG_KALLSYMS_ALL=y
+CONFIG_PROFILING=y
+CONFIG_KEXEC=y
+CONFIG_KEXEC_FILE=y
+CONFIG_ARCH_MESON=y
+CONFIG_ARM64_VA_BITS_48=y
+CONFIG_SCHED_MC=y
+CONFIG_SCHED_SMT=y
+CONFIG_NUMA=y
+CONFIG_COMPAT=y
+CONFIG_RANDOMIZE_BASE=y
+# CONFIG_EFI is not set
+CONFIG_HIBERNATION=y
+CONFIG_PM_DEBUG=y
+CONFIG_PM_ADVANCED_DEBUG=y
+CONFIG_WQ_POWER_EFFICIENT_DEFAULT=y
+CONFIG_ENERGY_MODEL=y
+CONFIG_CPU_IDLE=y
+CONFIG_ARM_PSCI_CPUIDLE=y
+CONFIG_CPU_FREQ=y
+CONFIG_CPU_FREQ_STAT=y
+CONFIG_CPU_FREQ_GOV_USERSPACE=y
+CONFIG_CPU_FREQ_GOV_ONDEMAND=y
+CONFIG_CPUFREQ_DT=y
+CONFIG_ARM_SCPI_CPUFREQ=y
+CONFIG_ARM_SCMI_CPUFREQ=y
+CONFIG_JUMP_LABEL=y
+CONFIG_MODULES=y
+CONFIG_MODULE_UNLOAD=y
+CONFIG_BLK_DEV_BSGLIB=y
+CONFIG_BLK_DEV_INTEGRITY=y
+# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set
+# CONFIG_COMPAT_BRK is not set
+CONFIG_PAGE_REPORTING=y
+CONFIG_KSM=y
+CONFIG_MEMORY_FAILURE=y
+CONFIG_TRANSPARENT_HUGEPAGE=y
+CONFIG_NET=y
+CONFIG_PACKET=y
+CONFIG_UNIX=y
+CONFIG_INET=y
+# CONFIG_IPV6 is not set
+CONFIG_BT=y
+CONFIG_BT_HCIUART=y
+CONFIG_BT_HCIUART_BCM=y
+CONFIG_CFG80211=y
+CONFIG_MAC80211=y
+CONFIG_UEVENT_HELPER=y
+CONFIG_DEVTMPFS=y
+CONFIG_DEVTMPFS_MOUNT=y
+CONFIG_FW_LOADER_USER_HELPER=y
+CONFIG_ARM_SCMI_PROTOCOL=y
+CONFIG_ARM_SCPI_PROTOCOL=y
+CONFIG_MTD=y
+CONFIG_MTD_SPI_NOR=y
+CONFIG_BLK_DEV_LOOP=y
+CONFIG_SRAM=y
+CONFIG_SCSI=y
+# CONFIG_SCSI_PROC_FS is not set
+CONFIG_BLK_DEV_SD=y
+# CONFIG_BLK_DEV_BSG is not set
+# CONFIG_SCSI_LOWLEVEL is not set
+CONFIG_NETDEVICES=y
+# CONFIG_NET_VENDOR_ALACRITECH is not set
+# CONFIG_NET_VENDOR_AMAZON is not set
+# CONFIG_NET_VENDOR_AMD is not set
+# CONFIG_NET_VENDOR_AQUANTIA is not set
+# CONFIG_NET_VENDOR_ARC is not set
+# CONFIG_NET_VENDOR_ASIX is not set
+# CONFIG_NET_VENDOR_BROADCOM is not set
+# CONFIG_NET_VENDOR_CADENCE is not set
+# CONFIG_NET_VENDOR_CAVIUM is not set
+# CONFIG_NET_VENDOR_CORTINA is not set
+# CONFIG_NET_VENDOR_DAVICOM is not set
+# CONFIG_NET_VENDOR_ENGLEDER is not set
+# CONFIG_NET_VENDOR_EZCHIP is not set
+# CONFIG_NET_VENDOR_FUNGIBLE is not set
+# CONFIG_NET_VENDOR_GOOGLE is not set
+# CONFIG_NET_VENDOR_HISILICON is not set
+# CONFIG_NET_VENDOR_HUAWEI is not set
+# CONFIG_NET_VENDOR_INTEL is not set
+# CONFIG_NET_VENDOR_ADI is not set
+# CONFIG_NET_VENDOR_LITEX is not set
+# CONFIG_NET_VENDOR_MARVELL is not set
+# CONFIG_NET_VENDOR_MELLANOX is not set
+# CONFIG_NET_VENDOR_MICREL is not set
+# CONFIG_NET_VENDOR_MICROCHIP is not set
+# CONFIG_NET_VENDOR_MICROSEMI is not set
+# CONFIG_NET_VENDOR_MICROSOFT is not set
+# CONFIG_NET_VENDOR_NI is not set
+# CONFIG_NET_VENDOR_NATSEMI is not set
+# CONFIG_NET_VENDOR_NETRONOME is not set
+# CONFIG_NET_VENDOR_PENSANDO is not set
+# CONFIG_NET_VENDOR_QUALCOMM is not set
+# CONFIG_NET_VENDOR_RENESAS is not set
+# CONFIG_NET_VENDOR_ROCKER is not set
+# CONFIG_NET_VENDOR_SAMSUNG is not set
+# CONFIG_NET_VENDOR_SEEQ is not set
+# CONFIG_NET_VENDOR_SOLARFLARE is not set
+# CONFIG_NET_VENDOR_SMSC is not set
+# CONFIG_NET_VENDOR_SOCIONEXT is not set
+CONFIG_STMMAC_ETH=y
+# CONFIG_NET_VENDOR_SYNOPSYS is not set
+# CONFIG_NET_VENDOR_VERTEXCOM is not set
+# CONFIG_NET_VENDOR_VIA is not set
+# CONFIG_NET_VENDOR_WANGXUN is not set
+# CONFIG_NET_VENDOR_WIZNET is not set
+# CONFIG_NET_VENDOR_XILINX is not set
+CONFIG_REALTEK_PHY=y
+CONFIG_MDIO_BUS_MUX_MESON_G12A=y
+# CONFIG_WLAN_VENDOR_ADMTEK is not set
+# CONFIG_WLAN_VENDOR_ATH is not set
+# CONFIG_WLAN_VENDOR_ATMEL is not set
+CONFIG_BRCMFMAC=m
+CONFIG_BRCM_TRACING=y
+CONFIG_BRCMDBG=y
+# CONFIG_WLAN_VENDOR_INTEL is not set
+# CONFIG_WLAN_VENDOR_INTERSIL is not set
+# CONFIG_WLAN_VENDOR_MARVELL is not set
+# CONFIG_WLAN_VENDOR_MEDIATEK is not set
+# CONFIG_WLAN_VENDOR_MICROCHIP is not set
+# CONFIG_WLAN_VENDOR_PURELIFI is not set
+# CONFIG_WLAN_VENDOR_RALINK is not set
+# CONFIG_WLAN_VENDOR_REALTEK is not set
+# CONFIG_WLAN_VENDOR_RSI is not set
+# CONFIG_WLAN_VENDOR_SILABS is not set
+# CONFIG_WLAN_VENDOR_ST is not set
+# CONFIG_WLAN_VENDOR_TI is not set
+# CONFIG_WLAN_VENDOR_ZYDAS is not set
+# CONFIG_WLAN_VENDOR_QUANTENNA is not set
+CONFIG_INPUT_MATRIXKMAP=y
+CONFIG_INPUT_EVDEV=y
+CONFIG_KEYBOARD_ADC=y
+# CONFIG_KEYBOARD_ATKBD is not set
+CONFIG_KEYBOARD_GPIO=y
+CONFIG_KEYBOARD_GPIO_POLLED=y
+# CONFIG_SERIO_SERPORT is not set
+CONFIG_SERIO_AMBAKMI=y
+CONFIG_LEGACY_PTY_COUNT=16
+CONFIG_SERIAL_AMBA_PL011=y
+CONFIG_SERIAL_AMBA_PL011_CONSOLE=y
+CONFIG_SERIAL_MESON=y
+CONFIG_SERIAL_MESON_CONSOLE=y
+CONFIG_SERIAL_DEV_BUS=y
+CONFIG_HW_RANDOM=y
+# CONFIG_HW_RANDOM_ARM_SMCCC_TRNG is not set
+CONFIG_I2C_CHARDEV=y
+CONFIG_I2C_MESON=y
+CONFIG_I2C_SLAVE=y
+CONFIG_SPI=y
+CONFIG_SPI_MESON_SPICC=y
+CONFIG_SPI_MESON_SPIFC=y
+# CONFIG_PTP_1588_CLOCK is not set
+CONFIG_PINCTRL=y
+CONFIG_PINCTRL_SINGLE=y
+CONFIG_GPIO_DWAPB=y
+CONFIG_GPIO_GENERIC_PLATFORM=y
+CONFIG_GPIO_MAX732X=y
+CONFIG_GPIO_PCA953X=y
+CONFIG_GPIO_PCA953X_IRQ=y
+CONFIG_POWER_RESET_XGENE=y
+CONFIG_POWER_RESET_SYSCON=y
+CONFIG_SYSCON_REBOOT_MODE=y
+CONFIG_BATTERY_BQ27XXX=y
+CONFIG_SENSORS_ARM_SCMI=y
+CONFIG_SENSORS_ARM_SCPI=y
+CONFIG_THERMAL=y
+CONFIG_THERMAL_GOV_POWER_ALLOCATOR=y
+CONFIG_CPU_THERMAL=y
+CONFIG_THERMAL_EMULATION=y
+CONFIG_KHADAS_MCU_FAN_THERMAL=y
+CONFIG_WATCHDOG=y
+CONFIG_ARM_SP805_WATCHDOG=y
+CONFIG_ARM_SBSA_WATCHDOG=y
+CONFIG_DW_WATCHDOG=y
+CONFIG_MESON_WATCHDOG=y
+CONFIG_ARM_SMC_WATCHDOG=y
+CONFIG_MFD_KHADAS_MCU=y
+CONFIG_REGULATOR_FIXED_VOLTAGE=y
+CONFIG_REGULATOR_GPIO=y
+CONFIG_REGULATOR_MP8859=y
+CONFIG_REGULATOR_PWM=y
+CONFIG_RC_CORE=y
+CONFIG_LIRC=y
+CONFIG_RC_DECODERS=y
+CONFIG_IR_NEC_DECODER=y
+CONFIG_IR_RC5_DECODER=y
+CONFIG_IR_RC6_DECODER=y
+CONFIG_IR_SONY_DECODER=y
+CONFIG_RC_DEVICES=y
+CONFIG_IR_MESON=y
+CONFIG_IR_MESON_TX=y
+CONFIG_CEC_MESON_AO=y
+CONFIG_CEC_MESON_G12A_AO=y
+CONFIG_MEDIA_SUPPORT=y
+# CONFIG_MEDIA_SUPPORT_FILTER is not set
+# CONFIG_DVB_CORE is not set
+# CONFIG_RADIO_ADAPTERS is not set
+CONFIG_V4L_MEM2MEM_DRIVERS=y
+CONFIG_VIDEO_MESON_GE2D=y
+# CONFIG_MEDIA_TUNER_E4000 is not set
+# CONFIG_MEDIA_TUNER_FC0011 is not set
+# CONFIG_MEDIA_TUNER_FC0012 is not set
+# CONFIG_MEDIA_TUNER_FC0013 is not set
+# CONFIG_MEDIA_TUNER_FC2580 is not set
+# CONFIG_MEDIA_TUNER_IT913X is not set
+# CONFIG_MEDIA_TUNER_M88RS6000T is not set
+# CONFIG_MEDIA_TUNER_MAX2165 is not set
+# CONFIG_MEDIA_TUNER_MC44S803 is not set
+# CONFIG_MEDIA_TUNER_MSI001 is not set
+# CONFIG_MEDIA_TUNER_MT2060 is not set
+# CONFIG_MEDIA_TUNER_MT2063 is not set
+# CONFIG_MEDIA_TUNER_MT20XX is not set
+# CONFIG_MEDIA_TUNER_MT2131 is not set
+# CONFIG_MEDIA_TUNER_MT2266 is not set
+# CONFIG_MEDIA_TUNER_MXL301RF is not set
+# CONFIG_MEDIA_TUNER_MXL5005S is not set
+# CONFIG_MEDIA_TUNER_MXL5007T is not set
+# CONFIG_MEDIA_TUNER_QM1D1B0004 is not set
+# CONFIG_MEDIA_TUNER_QM1D1C0042 is not set
+# CONFIG_MEDIA_TUNER_QT1010 is not set
+# CONFIG_MEDIA_TUNER_R820T is not set
+# CONFIG_MEDIA_TUNER_SI2157 is not set
+# CONFIG_MEDIA_TUNER_SIMPLE is not set
+# CONFIG_MEDIA_TUNER_TDA18212 is not set
+# CONFIG_MEDIA_TUNER_TDA18218 is not set
+# CONFIG_MEDIA_TUNER_TDA18250 is not set
+# CONFIG_MEDIA_TUNER_TDA18271 is not set
+# CONFIG_MEDIA_TUNER_TDA827X is not set
+# CONFIG_MEDIA_TUNER_TDA8290 is not set
+# CONFIG_MEDIA_TUNER_TDA9887 is not set
+# CONFIG_MEDIA_TUNER_TEA5761 is not set
+# CONFIG_MEDIA_TUNER_TEA5767 is not set
+# CONFIG_MEDIA_TUNER_TUA9001 is not set
+# CONFIG_MEDIA_TUNER_XC2028 is not set
+# CONFIG_MEDIA_TUNER_XC4000 is not set
+# CONFIG_MEDIA_TUNER_XC5000 is not set
+CONFIG_DRM=y
+CONFIG_DRM_MESON=y
+CONFIG_FB=y
+CONFIG_FB_MODE_HELPERS=y
+CONFIG_BACKLIGHT_CLASS_DEVICE=y
+CONFIG_LOGO=y
+# CONFIG_LOGO_LINUX_MONO is not set
+# CONFIG_LOGO_LINUX_VGA16 is not set
+CONFIG_SOUND=y
+CONFIG_SND=y
+# CONFIG_SND_SPI is not set
+CONFIG_SND_SOC=y
+CONFIG_SND_MESON_AXG_SOUND_CARD=y
+# CONFIG_SND_MESON_AXG_PDM is not set
+# CONFIG_SND_SOC_MESON_T9015 is not set
+# CONFIG_SND_SOC_SPDIF is not set
+CONFIG_USB_CONN_GPIO=y
+CONFIG_USB=y
+CONFIG_USB_OTG=y
+CONFIG_USB_XHCI_HCD=y
+CONFIG_USB_EHCI_HCD=y
+CONFIG_USB_EHCI_HCD_PLATFORM=y
+CONFIG_USB_OHCI_HCD=y
+CONFIG_USB_OHCI_HCD_PLATFORM=y
+CONFIG_USB_STORAGE=y
+CONFIG_USB_MUSB_HDRC=y
+CONFIG_USB_DWC3=y
+CONFIG_USB_DWC2=y
+CONFIG_USB_CHIPIDEA=y
+CONFIG_USB_CHIPIDEA_UDC=y
+CONFIG_USB_CHIPIDEA_HOST=y
+CONFIG_USB_ISP1760=y
+CONFIG_USB_HSIC_USB3503=y
+CONFIG_NOP_USB_XCEIV=y
+CONFIG_USB_ULPI=y
+CONFIG_USB_GADGET=y
+CONFIG_USB_SNP_UDC_PLAT=y
+CONFIG_USB_BDC_UDC=y
+CONFIG_USB_CONFIGFS=y
+CONFIG_USB_CONFIGFS_NCM=y
+CONFIG_USB_CONFIGFS_ECM=y
+CONFIG_USB_CONFIGFS_ECM_SUBSET=y
+CONFIG_USB_CONFIGFS_F_FS=y
+CONFIG_MMC=y
+CONFIG_MMC_BLOCK_MINORS=32
+CONFIG_MMC_ARMMMCI=y
+CONFIG_MMC_SDHCI=y
+CONFIG_MMC_SDHCI_PLTFM=y
+CONFIG_MMC_SDHCI_OF_ARASAN=y
+CONFIG_MMC_SDHCI_CADENCE=y
+CONFIG_MMC_SDHCI_F_SDH30=y
+CONFIG_MMC_MESON_GX=y
+CONFIG_MMC_DW=y
+# CONFIG_MMC_DW_PLTFM is not set
+CONFIG_MMC_MTK=y
+CONFIG_MMC_SDHCI_XENON=y
+CONFIG_NEW_LEDS=y
+CONFIG_LEDS_CLASS=y
+CONFIG_LEDS_GPIO=y
+CONFIG_LEDS_PWM=y
+CONFIG_LEDS_SYSCON=y
+CONFIG_EDAC=y
+CONFIG_RTC_CLASS=y
+CONFIG_RTC_DRV_HYM8563=y
+CONFIG_RTC_DRV_DS3232=y
+CONFIG_RTC_DRV_MESON_VRTC=y
+CONFIG_RTC_DRV_PL031=y
+CONFIG_DMADEVICES=y
+CONFIG_FSL_EDMA=y
+CONFIG_MV_XOR_V2=y
+CONFIG_PL330_DMA=y
+CONFIG_VFIO=y
+# CONFIG_VIRTIO_MENU is not set
+# CONFIG_VHOST_MENU is not set
+CONFIG_STAGING=y
+CONFIG_STAGING_MEDIA=y
+# CONFIG_SURFACE_PLATFORMS is not set
+CONFIG_COMMON_CLK_SCPI=y
+CONFIG_COMMON_CLK_PWM=y
+CONFIG_HWSPINLOCK=y
+CONFIG_MAILBOX=y
+CONFIG_ARM_MHU=y
+CONFIG_PLATFORM_MHU=y
+CONFIG_IOMMU_IO_PGTABLE_ARMV7S=y
+CONFIG_ARM_SMMU=y
+CONFIG_ARM_SMMU_V3=y
+CONFIG_REMOTEPROC=y
+CONFIG_RPMSG_QCOM_GLINK_RPM=y
+CONFIG_FSL_RCPM=y
+CONFIG_SOC_TI=y
+CONFIG_PM_DEVFREQ=y
+CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND=y
+CONFIG_EXTCON_USB_GPIO=y
+CONFIG_MEMORY=y
+CONFIG_IIO=y
+CONFIG_IIO_BUFFER=y
+CONFIG_IIO_TRIGGERED_BUFFER=y
+CONFIG_IIO_SW_TRIGGER=y
+CONFIG_PWM=y
+CONFIG_PWM_MESON=y
+CONFIG_PHY_QCOM_USB_HS=y
+CONFIG_PHY_SAMSUNG_USB2=y
+CONFIG_MESON_DDR_PMU=y
+CONFIG_NVMEM_MESON_EFUSE=y
+CONFIG_INTERCONNECT=y
+CONFIG_EXT2_FS=y
+CONFIG_EXT3_FS=y
+CONFIG_FANOTIFY=y
+CONFIG_QUOTA=y
+CONFIG_QFMT_V2=y
+CONFIG_MSDOS_FS=y
+CONFIG_VFAT_FS=y
+CONFIG_EXFAT_FS=y
+CONFIG_TMPFS=y
+CONFIG_TMPFS_POSIX_ACL=y
+CONFIG_HUGETLBFS=y
+CONFIG_PSTORE=y
+CONFIG_NFS_FS=y
+CONFIG_NLS_CODEPAGE_437=y
+CONFIG_NLS_ISO8859_1=y
+CONFIG_CRYPTO_AUTHENC=m
+CONFIG_CRYPTO_ECHAINIV=y
+CONFIG_CRYPTO_ANSI_CPRNG=y
+CONFIG_CRYPTO_GHASH_ARM64_CE=y
+CONFIG_CRYPTO_SHA1_ARM64_CE=y
+CONFIG_CRYPTO_SHA2_ARM64_CE=y
+CONFIG_CRYPTO_AES_ARM64_CE_CCM=y
+CONFIG_CRYPTO_DEV_AMLOGIC_GXL=y
+CONFIG_INDIRECT_PIO=y
+CONFIG_CRC_ITU_T=y
+CONFIG_CRC7=y
+CONFIG_CRC8=y
+CONFIG_IRQ_POLL=y
+CONFIG_PRINTK_TIME=y
+CONFIG_DYNAMIC_DEBUG=y
+CONFIG_DEBUG_KERNEL=y
+CONFIG_MAGIC_SYSRQ=y
+CONFIG_DEBUG_FS=y
+# CONFIG_SCHED_DEBUG is not set
+CONFIG_FUNCTION_TRACER=y
+CONFIG_FUNCTION_PROFILER=y
+# CONFIG_STRICT_DEVMEM is not set
+CONFIG_MEMTEST=y
diff --git a/arch/arm64/configs/meson_gxl_khadas_vim1_defconfig b/arch/arm64/configs/meson_gxl_khadas_vim1_defconfig
new file mode 100644
index 0000000000000..2adeb586d2405
--- /dev/null
+++ b/arch/arm64/configs/meson_gxl_khadas_vim1_defconfig
@@ -0,0 +1,397 @@
+CONFIG_SYSVIPC=y
+CONFIG_GENERIC_IRQ_DEBUGFS=y
+CONFIG_NO_HZ_IDLE=y
+CONFIG_HIGH_RES_TIMERS=y
+CONFIG_PREEMPT=y
+CONFIG_IRQ_TIME_ACCOUNTING=y
+CONFIG_BSD_PROCESS_ACCT=y
+CONFIG_BSD_PROCESS_ACCT_V3=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_NUMA_BALANCING=y
+CONFIG_MEMCG=y
+CONFIG_BLK_CGROUP=y
+CONFIG_CGROUP_PIDS=y
+CONFIG_CGROUP_HUGETLB=y
+CONFIG_CPUSETS=y
+CONFIG_CGROUP_DEVICE=y
+CONFIG_CGROUP_CPUACCT=y
+CONFIG_CGROUP_PERF=y
+CONFIG_USER_NS=y
+CONFIG_SCHED_AUTOGROUP=y
+CONFIG_BLK_DEV_INITRD=y
+CONFIG_KALLSYMS_ALL=y
+CONFIG_PROFILING=y
+CONFIG_KEXEC=y
+CONFIG_KEXEC_FILE=y
+CONFIG_ARCH_MESON=y
+CONFIG_ARM64_VA_BITS_48=y
+CONFIG_SCHED_MC=y
+CONFIG_SCHED_SMT=y
+CONFIG_NUMA=y
+CONFIG_COMPAT=y
+CONFIG_RANDOMIZE_BASE=y
+# CONFIG_EFI is not set
+CONFIG_HIBERNATION=y
+CONFIG_PM_DEBUG=y
+CONFIG_PM_ADVANCED_DEBUG=y
+CONFIG_WQ_POWER_EFFICIENT_DEFAULT=y
+CONFIG_ENERGY_MODEL=y
+CONFIG_CPU_IDLE=y
+CONFIG_ARM_PSCI_CPUIDLE=y
+CONFIG_CPU_FREQ=y
+CONFIG_CPU_FREQ_STAT=y
+CONFIG_CPU_FREQ_GOV_USERSPACE=y
+CONFIG_CPU_FREQ_GOV_ONDEMAND=y
+CONFIG_CPUFREQ_DT=y
+CONFIG_ARM_SCPI_CPUFREQ=y
+CONFIG_ARM_SCMI_CPUFREQ=y
+CONFIG_JUMP_LABEL=y
+CONFIG_MODULES=y
+CONFIG_MODULE_UNLOAD=y
+CONFIG_BLK_DEV_BSGLIB=y
+CONFIG_BLK_DEV_INTEGRITY=y
+# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set
+# CONFIG_COMPAT_BRK is not set
+CONFIG_PAGE_REPORTING=y
+CONFIG_KSM=y
+CONFIG_MEMORY_FAILURE=y
+CONFIG_TRANSPARENT_HUGEPAGE=y
+CONFIG_NET=y
+CONFIG_PACKET=y
+CONFIG_UNIX=y
+CONFIG_INET=y
+# CONFIG_IPV6 is not set
+CONFIG_BT=y
+CONFIG_BT_HCIUART=y
+CONFIG_BT_HCIUART_BCM=y
+CONFIG_CFG80211=y
+CONFIG_MAC80211=y
+CONFIG_UEVENT_HELPER=y
+CONFIG_DEVTMPFS=y
+CONFIG_DEVTMPFS_MOUNT=y
+CONFIG_FW_LOADER_USER_HELPER=y
+CONFIG_ARM_SCMI_PROTOCOL=y
+CONFIG_ARM_SCPI_PROTOCOL=y
+CONFIG_MTD=y
+CONFIG_MTD_SPI_NOR=y
+CONFIG_BLK_DEV_LOOP=y
+CONFIG_SRAM=y
+CONFIG_SCSI=y
+# CONFIG_SCSI_PROC_FS is not set
+CONFIG_BLK_DEV_SD=y
+# CONFIG_BLK_DEV_BSG is not set
+# CONFIG_SCSI_LOWLEVEL is not set
+CONFIG_NETDEVICES=y
+# CONFIG_NET_VENDOR_ALACRITECH is not set
+# CONFIG_NET_VENDOR_AMAZON is not set
+# CONFIG_NET_VENDOR_AMD is not set
+# CONFIG_NET_VENDOR_AQUANTIA is not set
+# CONFIG_NET_VENDOR_ARC is not set
+# CONFIG_NET_VENDOR_ASIX is not set
+# CONFIG_NET_VENDOR_BROADCOM is not set
+# CONFIG_NET_VENDOR_CADENCE is not set
+# CONFIG_NET_VENDOR_CAVIUM is not set
+# CONFIG_NET_VENDOR_CORTINA is not set
+# CONFIG_NET_VENDOR_DAVICOM is not set
+# CONFIG_NET_VENDOR_ENGLEDER is not set
+# CONFIG_NET_VENDOR_EZCHIP is not set
+# CONFIG_NET_VENDOR_FUNGIBLE is not set
+# CONFIG_NET_VENDOR_GOOGLE is not set
+# CONFIG_NET_VENDOR_HISILICON is not set
+# CONFIG_NET_VENDOR_HUAWEI is not set
+# CONFIG_NET_VENDOR_INTEL is not set
+# CONFIG_NET_VENDOR_ADI is not set
+# CONFIG_NET_VENDOR_LITEX is not set
+# CONFIG_NET_VENDOR_MARVELL is not set
+# CONFIG_NET_VENDOR_MELLANOX is not set
+# CONFIG_NET_VENDOR_MICREL is not set
+# CONFIG_NET_VENDOR_MICROCHIP is not set
+# CONFIG_NET_VENDOR_MICROSEMI is not set
+# CONFIG_NET_VENDOR_MICROSOFT is not set
+# CONFIG_NET_VENDOR_NI is not set
+# CONFIG_NET_VENDOR_NATSEMI is not set
+# CONFIG_NET_VENDOR_NETRONOME is not set
+# CONFIG_NET_VENDOR_PENSANDO is not set
+# CONFIG_NET_VENDOR_QUALCOMM is not set
+# CONFIG_NET_VENDOR_RENESAS is not set
+# CONFIG_NET_VENDOR_ROCKER is not set
+# CONFIG_NET_VENDOR_SAMSUNG is not set
+# CONFIG_NET_VENDOR_SEEQ is not set
+# CONFIG_NET_VENDOR_SOLARFLARE is not set
+# CONFIG_NET_VENDOR_SMSC is not set
+# CONFIG_NET_VENDOR_SOCIONEXT is not set
+CONFIG_STMMAC_ETH=y
+# CONFIG_NET_VENDOR_SYNOPSYS is not set
+# CONFIG_NET_VENDOR_VERTEXCOM is not set
+# CONFIG_NET_VENDOR_VIA is not set
+# CONFIG_NET_VENDOR_WANGXUN is not set
+# CONFIG_NET_VENDOR_WIZNET is not set
+# CONFIG_NET_VENDOR_XILINX is not set
+CONFIG_REALTEK_PHY=y
+CONFIG_MDIO_BUS_MUX_MESON_G12A=y
+# CONFIG_WLAN_VENDOR_ADMTEK is not set
+# CONFIG_WLAN_VENDOR_ATH is not set
+# CONFIG_WLAN_VENDOR_ATMEL is not set
+# CONFIG_WLAN_VENDOR_BROADCOM is not set
+# CONFIG_WLAN_VENDOR_INTEL is not set
+# CONFIG_WLAN_VENDOR_INTERSIL is not set
+# CONFIG_WLAN_VENDOR_MARVELL is not set
+# CONFIG_WLAN_VENDOR_MEDIATEK is not set
+# CONFIG_WLAN_VENDOR_MICROCHIP is not set
+# CONFIG_WLAN_VENDOR_PURELIFI is not set
+# CONFIG_WLAN_VENDOR_RALINK is not set
+# CONFIG_WLAN_VENDOR_RSI is not set
+# CONFIG_WLAN_VENDOR_SILABS is not set
+# CONFIG_WLAN_VENDOR_ST is not set
+# CONFIG_WLAN_VENDOR_TI is not set
+# CONFIG_WLAN_VENDOR_ZYDAS is not set
+# CONFIG_WLAN_VENDOR_QUANTENNA is not set
+CONFIG_INPUT_MATRIXKMAP=y
+CONFIG_INPUT_EVDEV=y
+CONFIG_KEYBOARD_ADC=y
+# CONFIG_KEYBOARD_ATKBD is not set
+CONFIG_KEYBOARD_GPIO=y
+CONFIG_KEYBOARD_GPIO_POLLED=y
+# CONFIG_SERIO_SERPORT is not set
+CONFIG_SERIO_AMBAKMI=y
+CONFIG_LEGACY_PTY_COUNT=16
+CONFIG_SERIAL_AMBA_PL011=y
+CONFIG_SERIAL_AMBA_PL011_CONSOLE=y
+CONFIG_SERIAL_MESON=y
+CONFIG_SERIAL_MESON_CONSOLE=y
+CONFIG_SERIAL_DEV_BUS=y
+CONFIG_HW_RANDOM=y
+# CONFIG_HW_RANDOM_ARM_SMCCC_TRNG is not set
+CONFIG_I2C_CHARDEV=y
+CONFIG_I2C_MESON=y
+CONFIG_I2C_SLAVE=y
+CONFIG_SPI=y
+CONFIG_SPI_MESON_SPICC=y
+CONFIG_SPI_MESON_SPIFC=y
+# CONFIG_PTP_1588_CLOCK is not set
+CONFIG_PINCTRL=y
+CONFIG_PINCTRL_SINGLE=y
+CONFIG_GPIO_DWAPB=y
+CONFIG_GPIO_GENERIC_PLATFORM=y
+CONFIG_GPIO_MAX732X=y
+CONFIG_GPIO_PCA953X=y
+CONFIG_GPIO_PCA953X_IRQ=y
+CONFIG_POWER_RESET_XGENE=y
+CONFIG_POWER_RESET_SYSCON=y
+CONFIG_SYSCON_REBOOT_MODE=y
+CONFIG_BATTERY_BQ27XXX=y
+CONFIG_SENSORS_ARM_SCMI=y
+CONFIG_SENSORS_ARM_SCPI=y
+CONFIG_THERMAL=y
+CONFIG_THERMAL_GOV_POWER_ALLOCATOR=y
+CONFIG_CPU_THERMAL=y
+CONFIG_THERMAL_EMULATION=y
+CONFIG_KHADAS_MCU_FAN_THERMAL=y
+CONFIG_WATCHDOG=y
+CONFIG_ARM_SP805_WATCHDOG=y
+CONFIG_ARM_SBSA_WATCHDOG=y
+CONFIG_DW_WATCHDOG=y
+CONFIG_MESON_WATCHDOG=y
+CONFIG_ARM_SMC_WATCHDOG=y
+CONFIG_MFD_KHADAS_MCU=y
+CONFIG_REGULATOR_FIXED_VOLTAGE=y
+CONFIG_REGULATOR_GPIO=y
+CONFIG_REGULATOR_MP8859=y
+CONFIG_REGULATOR_PWM=y
+CONFIG_RC_CORE=y
+CONFIG_LIRC=y
+CONFIG_RC_DECODERS=y
+CONFIG_IR_NEC_DECODER=y
+CONFIG_IR_RC5_DECODER=y
+CONFIG_IR_RC6_DECODER=y
+CONFIG_IR_SONY_DECODER=y
+CONFIG_RC_DEVICES=y
+CONFIG_IR_MESON=y
+CONFIG_IR_MESON_TX=y
+CONFIG_CEC_MESON_AO=y
+CONFIG_CEC_MESON_G12A_AO=y
+CONFIG_MEDIA_SUPPORT=y
+# CONFIG_MEDIA_SUPPORT_FILTER is not set
+# CONFIG_DVB_CORE is not set
+# CONFIG_RADIO_ADAPTERS is not set
+CONFIG_V4L_MEM2MEM_DRIVERS=y
+CONFIG_VIDEO_MESON_GE2D=y
+# CONFIG_MEDIA_TUNER_E4000 is not set
+# CONFIG_MEDIA_TUNER_FC0011 is not set
+# CONFIG_MEDIA_TUNER_FC0012 is not set
+# CONFIG_MEDIA_TUNER_FC0013 is not set
+# CONFIG_MEDIA_TUNER_FC2580 is not set
+# CONFIG_MEDIA_TUNER_IT913X is not set
+# CONFIG_MEDIA_TUNER_M88RS6000T is not set
+# CONFIG_MEDIA_TUNER_MAX2165 is not set
+# CONFIG_MEDIA_TUNER_MC44S803 is not set
+# CONFIG_MEDIA_TUNER_MSI001 is not set
+# CONFIG_MEDIA_TUNER_MT2060 is not set
+# CONFIG_MEDIA_TUNER_MT2063 is not set
+# CONFIG_MEDIA_TUNER_MT20XX is not set
+# CONFIG_MEDIA_TUNER_MT2131 is not set
+# CONFIG_MEDIA_TUNER_MT2266 is not set
+# CONFIG_MEDIA_TUNER_MXL301RF is not set
+# CONFIG_MEDIA_TUNER_MXL5005S is not set
+# CONFIG_MEDIA_TUNER_MXL5007T is not set
+# CONFIG_MEDIA_TUNER_QM1D1B0004 is not set
+# CONFIG_MEDIA_TUNER_QM1D1C0042 is not set
+# CONFIG_MEDIA_TUNER_QT1010 is not set
+# CONFIG_MEDIA_TUNER_R820T is not set
+# CONFIG_MEDIA_TUNER_SI2157 is not set
+# CONFIG_MEDIA_TUNER_SIMPLE is not set
+# CONFIG_MEDIA_TUNER_TDA18212 is not set
+# CONFIG_MEDIA_TUNER_TDA18218 is not set
+# CONFIG_MEDIA_TUNER_TDA18250 is not set
+# CONFIG_MEDIA_TUNER_TDA18271 is not set
+# CONFIG_MEDIA_TUNER_TDA827X is not set
+# CONFIG_MEDIA_TUNER_TDA8290 is not set
+# CONFIG_MEDIA_TUNER_TDA9887 is not set
+# CONFIG_MEDIA_TUNER_TEA5761 is not set
+# CONFIG_MEDIA_TUNER_TEA5767 is not set
+# CONFIG_MEDIA_TUNER_TUA9001 is not set
+# CONFIG_MEDIA_TUNER_XC2028 is not set
+# CONFIG_MEDIA_TUNER_XC4000 is not set
+# CONFIG_MEDIA_TUNER_XC5000 is not set
+CONFIG_DRM=y
+CONFIG_DRM_MESON=y
+CONFIG_FB=y
+CONFIG_FB_MODE_HELPERS=y
+CONFIG_BACKLIGHT_CLASS_DEVICE=y
+CONFIG_LOGO=y
+# CONFIG_LOGO_LINUX_MONO is not set
+# CONFIG_LOGO_LINUX_VGA16 is not set
+CONFIG_SOUND=y
+CONFIG_SND=y
+# CONFIG_SND_SPI is not set
+CONFIG_SND_SOC=y
+CONFIG_SND_MESON_AXG_SOUND_CARD=y
+# CONFIG_SND_MESON_AXG_PDM is not set
+# CONFIG_SND_SOC_MESON_T9015 is not set
+# CONFIG_SND_SOC_SPDIF is not set
+CONFIG_USB_CONN_GPIO=y
+CONFIG_USB=y
+CONFIG_USB_OTG=y
+CONFIG_USB_XHCI_HCD=y
+CONFIG_USB_EHCI_HCD=y
+CONFIG_USB_EHCI_HCD_PLATFORM=y
+CONFIG_USB_OHCI_HCD=y
+CONFIG_USB_OHCI_HCD_PLATFORM=y
+CONFIG_USB_STORAGE=y
+CONFIG_USB_MUSB_HDRC=y
+CONFIG_USB_DWC3=y
+CONFIG_USB_DWC2=y
+CONFIG_USB_CHIPIDEA=y
+CONFIG_USB_CHIPIDEA_UDC=y
+CONFIG_USB_CHIPIDEA_HOST=y
+CONFIG_USB_ISP1760=y
+CONFIG_USB_HSIC_USB3503=y
+CONFIG_NOP_USB_XCEIV=y
+CONFIG_USB_ULPI=y
+CONFIG_USB_GADGET=y
+CONFIG_USB_SNP_UDC_PLAT=y
+CONFIG_USB_BDC_UDC=y
+CONFIG_USB_CONFIGFS=y
+CONFIG_USB_CONFIGFS_F_FS=y
+CONFIG_USB_GADGETFS=y
+CONFIG_MMC=y
+CONFIG_MMC_BLOCK_MINORS=32
+CONFIG_MMC_ARMMMCI=y
+CONFIG_MMC_SDHCI=y
+CONFIG_MMC_SDHCI_PLTFM=y
+CONFIG_MMC_SDHCI_OF_ARASAN=y
+CONFIG_MMC_SDHCI_CADENCE=y
+CONFIG_MMC_SDHCI_F_SDH30=y
+CONFIG_MMC_MESON_GX=y
+CONFIG_MMC_DW=y
+# CONFIG_MMC_DW_PLTFM is not set
+CONFIG_MMC_MTK=y
+CONFIG_MMC_SDHCI_XENON=y
+CONFIG_NEW_LEDS=y
+CONFIG_LEDS_CLASS=y
+CONFIG_LEDS_GPIO=y
+CONFIG_LEDS_PWM=y
+CONFIG_LEDS_SYSCON=y
+CONFIG_EDAC=y
+CONFIG_RTC_CLASS=y
+CONFIG_RTC_DRV_HYM8563=y
+CONFIG_RTC_DRV_DS3232=y
+CONFIG_RTC_DRV_MESON_VRTC=y
+CONFIG_RTC_DRV_PL031=y
+CONFIG_DMADEVICES=y
+CONFIG_FSL_EDMA=y
+CONFIG_MV_XOR_V2=y
+CONFIG_PL330_DMA=y
+CONFIG_VFIO=y
+# CONFIG_VIRTIO_MENU is not set
+# CONFIG_VHOST_MENU is not set
+CONFIG_STAGING=y
+CONFIG_STAGING_MEDIA=y
+# CONFIG_SURFACE_PLATFORMS is not set
+CONFIG_COMMON_CLK_SCPI=y
+CONFIG_COMMON_CLK_PWM=y
+CONFIG_HWSPINLOCK=y
+CONFIG_MAILBOX=y
+CONFIG_ARM_MHU=y
+CONFIG_PLATFORM_MHU=y
+CONFIG_IOMMU_IO_PGTABLE_ARMV7S=y
+CONFIG_ARM_SMMU=y
+CONFIG_ARM_SMMU_V3=y
+CONFIG_REMOTEPROC=y
+CONFIG_RPMSG_QCOM_GLINK_RPM=y
+CONFIG_FSL_RCPM=y
+CONFIG_SOC_TI=y
+CONFIG_PM_DEVFREQ=y
+CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND=y
+CONFIG_EXTCON_USB_GPIO=y
+CONFIG_MEMORY=y
+CONFIG_IIO=y
+CONFIG_IIO_BUFFER=y
+CONFIG_IIO_TRIGGERED_BUFFER=y
+CONFIG_IIO_SW_TRIGGER=y
+CONFIG_PWM=y
+CONFIG_PWM_MESON=y
+CONFIG_PHY_QCOM_USB_HS=y
+CONFIG_PHY_SAMSUNG_USB2=y
+CONFIG_MESON_DDR_PMU=y
+CONFIG_NVMEM_MESON_EFUSE=y
+CONFIG_INTERCONNECT=y
+CONFIG_EXT2_FS=y
+CONFIG_EXT3_FS=y
+CONFIG_FANOTIFY=y
+CONFIG_QUOTA=y
+CONFIG_QFMT_V2=y
+CONFIG_MSDOS_FS=y
+CONFIG_VFAT_FS=y
+CONFIG_EXFAT_FS=y
+CONFIG_TMPFS=y
+CONFIG_TMPFS_POSIX_ACL=y
+CONFIG_HUGETLBFS=y
+CONFIG_PSTORE=y
+CONFIG_NFS_FS=y
+CONFIG_NLS_CODEPAGE_437=y
+CONFIG_NLS_ISO8859_1=y
+CONFIG_CRYPTO_AUTHENC=m
+CONFIG_CRYPTO_ECHAINIV=y
+CONFIG_CRYPTO_ANSI_CPRNG=y
+CONFIG_CRYPTO_GHASH_ARM64_CE=y
+CONFIG_CRYPTO_SHA1_ARM64_CE=y
+CONFIG_CRYPTO_SHA2_ARM64_CE=y
+CONFIG_CRYPTO_AES_ARM64_CE_CCM=y
+CONFIG_CRYPTO_DEV_AMLOGIC_GXL=y
+CONFIG_INDIRECT_PIO=y
+CONFIG_CRC_ITU_T=y
+CONFIG_CRC7=y
+CONFIG_CRC8=y
+CONFIG_IRQ_POLL=y
+CONFIG_PRINTK_TIME=y
+CONFIG_DYNAMIC_DEBUG=y
+CONFIG_DEBUG_KERNEL=y
+CONFIG_MAGIC_SYSRQ=y
+CONFIG_DEBUG_FS=y
+# CONFIG_SCHED_DEBUG is not set
+CONFIG_FUNCTION_TRACER=y
+CONFIG_FUNCTION_PROFILER=y
+# CONFIG_STRICT_DEVMEM is not set
+CONFIG_MEMTEST=y
diff --git a/arch/arm64/configs/meson_sm1_amlogic_ac200_defconfig b/arch/arm64/configs/meson_sm1_amlogic_ac200_defconfig
new file mode 100644
index 0000000000000..97bd7b413506e
--- /dev/null
+++ b/arch/arm64/configs/meson_sm1_amlogic_ac200_defconfig
@@ -0,0 +1,338 @@
+CONFIG_SYSVIPC=y
+CONFIG_NO_HZ_IDLE=y
+CONFIG_HIGH_RES_TIMERS=y
+CONFIG_PREEMPT=y
+CONFIG_IRQ_TIME_ACCOUNTING=y
+CONFIG_BSD_PROCESS_ACCT=y
+CONFIG_BSD_PROCESS_ACCT_V3=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_NUMA_BALANCING=y
+CONFIG_MEMCG=y
+CONFIG_BLK_CGROUP=y
+CONFIG_CGROUP_PIDS=y
+CONFIG_CGROUP_HUGETLB=y
+CONFIG_CPUSETS=y
+CONFIG_CGROUP_DEVICE=y
+CONFIG_CGROUP_CPUACCT=y
+CONFIG_CGROUP_PERF=y
+CONFIG_USER_NS=y
+CONFIG_SCHED_AUTOGROUP=y
+CONFIG_BLK_DEV_INITRD=y
+CONFIG_INITRAMFS_SOURCE="${BR_BINARIES_DIR}/rootfs.cpio"
+CONFIG_KALLSYMS_ALL=y
+CONFIG_PROFILING=y
+CONFIG_KEXEC=y
+CONFIG_KEXEC_FILE=y
+CONFIG_ARCH_MESON=y
+CONFIG_ARM64_VA_BITS_48=y
+CONFIG_SCHED_MC=y
+CONFIG_SCHED_SMT=y
+CONFIG_NUMA=y
+CONFIG_PARAVIRT=y
+CONFIG_COMPAT=y
+CONFIG_RANDOMIZE_BASE=y
+CONFIG_HIBERNATION=y
+CONFIG_WQ_POWER_EFFICIENT_DEFAULT=y
+CONFIG_ENERGY_MODEL=y
+CONFIG_CPU_IDLE=y
+CONFIG_ARM_PSCI_CPUIDLE=y
+CONFIG_CPU_FREQ=y
+CONFIG_CPU_FREQ_STAT=y
+CONFIG_CPU_FREQ_GOV_USERSPACE=y
+CONFIG_CPU_FREQ_GOV_ONDEMAND=y
+CONFIG_CPUFREQ_DT=y
+CONFIG_ARM_SCPI_CPUFREQ=y
+CONFIG_ARM_SCMI_CPUFREQ=y
+CONFIG_JUMP_LABEL=y
+# CONFIG_GCC_PLUGINS is not set
+CONFIG_MODULES=y
+CONFIG_MODULE_UNLOAD=y
+# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set
+# CONFIG_COMPAT_BRK is not set
+CONFIG_PAGE_REPORTING=y
+CONFIG_KSM=y
+CONFIG_MEMORY_FAILURE=y
+CONFIG_TRANSPARENT_HUGEPAGE=y
+CONFIG_NET=y
+CONFIG_PACKET=y
+CONFIG_UNIX=y
+CONFIG_INET=y
+# CONFIG_IPV6 is not set
+CONFIG_PCI=y
+CONFIG_PCIEPORTBUS=y
+CONFIG_PCI_IOV=y
+CONFIG_PCI_PASID=y
+CONFIG_HOTPLUG_PCI=y
+CONFIG_PCIE_ALTERA=y
+CONFIG_PCIE_ALTERA_MSI=y
+CONFIG_PCI_HOST_THUNDER_PEM=y
+CONFIG_PCI_HOST_THUNDER_ECAM=y
+CONFIG_PCI_HOST_GENERIC=y
+CONFIG_PCI_XGENE=y
+CONFIG_PCI_HISI=y
+CONFIG_PCIE_KIRIN=y
+CONFIG_PCI_ENDPOINT=y
+CONFIG_PCI_ENDPOINT_CONFIGFS=y
+CONFIG_UEVENT_HELPER=y
+CONFIG_DEVTMPFS=y
+CONFIG_DEVTMPFS_MOUNT=y
+CONFIG_FW_LOADER_USER_HELPER=y
+CONFIG_VEXPRESS_CONFIG=y
+CONFIG_ARM_SCMI_PROTOCOL=y
+CONFIG_ARM_SCPI_PROTOCOL=y
+CONFIG_EFI_CAPSULE_LOADER=y
+CONFIG_BLK_DEV_LOOP=y
+CONFIG_VIRTIO_BLK=y
+CONFIG_SRAM=y
+# CONFIG_SCSI_PROC_FS is not set
+CONFIG_BLK_DEV_SD=y
+CONFIG_SCSI_SAS_ATA=y
+CONFIG_SCSI_HISI_SAS=y
+CONFIG_MEGARAID_SAS=y
+CONFIG_ATA=y
+CONFIG_SATA_AHCI=y
+CONFIG_SATA_AHCI_PLATFORM=y
+CONFIG_AHCI_CEVA=y
+CONFIG_SATA_SIL24=y
+CONFIG_PATA_OF_PLATFORM=y
+CONFIG_MD=y
+CONFIG_INPUT_EVDEV=y
+CONFIG_KEYBOARD_GPIO=y
+CONFIG_KEYBOARD_CROS_EC=y
+CONFIG_INPUT_TOUCHSCREEN=y
+CONFIG_INPUT_MISC=y
+# CONFIG_SERIO_SERPORT is not set
+CONFIG_SERIO_AMBAKMI=y
+CONFIG_LEGACY_PTY_COUNT=16
+CONFIG_SERIAL_8250=y
+CONFIG_SERIAL_8250_CONSOLE=y
+CONFIG_SERIAL_8250_EXTENDED=y
+CONFIG_SERIAL_8250_SHARE_IRQ=y
+CONFIG_SERIAL_8250_DW=y
+CONFIG_SERIAL_OF_PLATFORM=y
+CONFIG_SERIAL_AMBA_PL011=y
+CONFIG_SERIAL_AMBA_PL011_CONSOLE=y
+CONFIG_SERIAL_MESON=y
+CONFIG_SERIAL_MESON_CONSOLE=y
+CONFIG_SERIAL_XILINX_PS_UART=y
+CONFIG_SERIAL_XILINX_PS_UART_CONSOLE=y
+CONFIG_SERIAL_FSL_LPUART=y
+CONFIG_SERIAL_FSL_LPUART_CONSOLE=y
+CONFIG_SERIAL_FSL_LINFLEXUART=y
+CONFIG_SERIAL_FSL_LINFLEXUART_CONSOLE=y
+CONFIG_SERIAL_DEV_BUS=y
+CONFIG_TCG_TPM=y
+CONFIG_TCG_TIS_I2C_INFINEON=y
+CONFIG_I2C=y
+CONFIG_I2C_CHARDEV=y
+CONFIG_I2C_MUX=y
+CONFIG_I2C_MUX_PCA954x=y
+CONFIG_I2C_DESIGNWARE_PLATFORM=y
+CONFIG_I2C_MESON=y
+CONFIG_I2C_RK3X=y
+CONFIG_I2C_CROS_EC_TUNNEL=y
+CONFIG_I2C_SLAVE=y
+CONFIG_SPI=y
+CONFIG_SPI_MEM=y
+CONFIG_SPI_CADENCE_QUADSPI=y
+CONFIG_SPI_PL022=y
+CONFIG_SPMI=y
+CONFIG_PPS=y
+CONFIG_PINCTRL=y
+CONFIG_PINCTRL_MAX77620=y
+CONFIG_PINCTRL_SINGLE=y
+CONFIG_GPIO_DWAPB=y
+CONFIG_GPIO_MB86S7X=y
+CONFIG_GPIO_PL061=y
+CONFIG_GPIO_XGENE=y
+CONFIG_GPIO_MAX732X=y
+CONFIG_GPIO_PCA953X=y
+CONFIG_GPIO_PCA953X_IRQ=y
+CONFIG_GPIO_MAX77620=y
+CONFIG_POWER_RESET_XGENE=y
+CONFIG_POWER_RESET_SYSCON=y
+CONFIG_SYSCON_REBOOT_MODE=y
+CONFIG_BATTERY_BQ27XXX=y
+CONFIG_SENSORS_ARM_SCMI=y
+CONFIG_SENSORS_ARM_SCPI=y
+CONFIG_THERMAL=y
+CONFIG_THERMAL_GOV_POWER_ALLOCATOR=y
+CONFIG_CPU_THERMAL=y
+CONFIG_THERMAL_EMULATION=y
+CONFIG_WATCHDOG=y
+CONFIG_ARM_SP805_WATCHDOG=y
+CONFIG_ARM_SBSA_WATCHDOG=y
+CONFIG_DW_WATCHDOG=y
+CONFIG_ARM_SMC_WATCHDOG=y
+CONFIG_MFD_BD9571MWV=y
+CONFIG_MFD_AXP20X_I2C=y
+CONFIG_MFD_HI6421_PMIC=y
+CONFIG_MFD_MAX77620=y
+CONFIG_MFD_MT6397=y
+CONFIG_MFD_SEC_CORE=y
+CONFIG_MFD_ROHM_BD718XX=y
+CONFIG_REGULATOR_FIXED_VOLTAGE=y
+CONFIG_REGULATOR_AXP20X=y
+CONFIG_REGULATOR_BD718XX=y
+CONFIG_REGULATOR_BD9571MWV=y
+CONFIG_REGULATOR_FAN53555=y
+CONFIG_REGULATOR_GPIO=y
+CONFIG_REGULATOR_HI6421V530=y
+CONFIG_REGULATOR_MAX77620=y
+CONFIG_REGULATOR_MAX8973=y
+CONFIG_REGULATOR_MP8859=y
+CONFIG_REGULATOR_MT6358=y
+CONFIG_REGULATOR_MT6397=y
+CONFIG_REGULATOR_PCA9450=y
+CONFIG_REGULATOR_PF8X00=y
+CONFIG_REGULATOR_PFUZE100=y
+CONFIG_REGULATOR_PWM=y
+CONFIG_REGULATOR_QCOM_SPMI=y
+CONFIG_REGULATOR_S2MPS11=y
+CONFIG_FB=y
+CONFIG_FB_EFI=y
+CONFIG_FB_MODE_HELPERS=y
+CONFIG_BACKLIGHT_CLASS_DEVICE=y
+CONFIG_LOGO=y
+# CONFIG_LOGO_LINUX_MONO is not set
+# CONFIG_LOGO_LINUX_VGA16 is not set
+CONFIG_SOUND=y
+CONFIG_SND=y
+CONFIG_SND_SOC=y
+CONFIG_USB_CONN_GPIO=y
+CONFIG_USB=y
+CONFIG_USB_OTG=y
+CONFIG_USB_XHCI_HCD=y
+CONFIG_USB_EHCI_HCD=y
+CONFIG_USB_EHCI_HCD_PLATFORM=y
+CONFIG_USB_OHCI_HCD=y
+CONFIG_USB_OHCI_HCD_PLATFORM=y
+CONFIG_USB_STORAGE=y
+CONFIG_USB_MUSB_HDRC=y
+CONFIG_USB_DWC3=y
+CONFIG_USB_DWC2=y
+CONFIG_USB_CHIPIDEA=y
+CONFIG_USB_CHIPIDEA_UDC=y
+CONFIG_USB_CHIPIDEA_HOST=y
+CONFIG_USB_ISP1760=y
+CONFIG_USB_HSIC_USB3503=y
+CONFIG_NOP_USB_XCEIV=y
+CONFIG_USB_ULPI=y
+CONFIG_USB_GADGET=y
+CONFIG_USB_SNP_UDC_PLAT=y
+CONFIG_USB_BDC_UDC=y
+CONFIG_USB_CONFIGFS=y
+CONFIG_USB_CONFIGFS_NCM=y
+CONFIG_USB_CONFIGFS_ECM=y
+CONFIG_USB_CONFIGFS_ECM_SUBSET=y
+CONFIG_USB_CONFIGFS_F_FS=y
+CONFIG_MMC=y
+CONFIG_MMC_BLOCK_MINORS=32
+CONFIG_MMC_ARMMMCI=y
+CONFIG_MMC_SDHCI=y
+CONFIG_MMC_SDHCI_PLTFM=y
+CONFIG_MMC_SDHCI_OF_ARASAN=y
+CONFIG_MMC_SDHCI_CADENCE=y
+CONFIG_MMC_SDHCI_F_SDH30=y
+CONFIG_MMC_MESON_GX=y
+CONFIG_MMC_SPI=y
+CONFIG_MMC_DW=y
+CONFIG_MMC_DW_EXYNOS=y
+CONFIG_MMC_DW_HI3798CV200=y
+CONFIG_MMC_DW_K3=y
+CONFIG_MMC_MTK=y
+CONFIG_MMC_SDHCI_XENON=y
+CONFIG_SCSI_UFSHCD=y
+CONFIG_SCSI_UFSHCD_PLATFORM=y
+CONFIG_NEW_LEDS=y
+CONFIG_LEDS_CLASS=y
+CONFIG_LEDS_GPIO=y
+CONFIG_LEDS_PWM=y
+CONFIG_LEDS_SYSCON=y
+CONFIG_EDAC=y
+CONFIG_RTC_CLASS=y
+CONFIG_RTC_DRV_MAX77686=y
+CONFIG_RTC_DRV_S5M=y
+CONFIG_RTC_DRV_DS3232=y
+CONFIG_RTC_DRV_EFI=y
+CONFIG_RTC_DRV_CROS_EC=y
+CONFIG_RTC_DRV_PL031=y
+CONFIG_DMADEVICES=y
+CONFIG_FSL_EDMA=y
+CONFIG_MV_XOR_V2=y
+CONFIG_PL330_DMA=y
+CONFIG_QCOM_HIDMA_MGMT=y
+CONFIG_QCOM_HIDMA=y
+CONFIG_VFIO=y
+CONFIG_VFIO_PCI=y
+# CONFIG_VIRTIO_MENU is not set
+# CONFIG_VHOST_MENU is not set
+CONFIG_STAGING=y
+CONFIG_STAGING_MEDIA=y
+CONFIG_CHROME_PLATFORMS=y
+CONFIG_CROS_EC=y
+CONFIG_CROS_EC_I2C=y
+CONFIG_CROS_EC_SPI=y
+CONFIG_CLK_VEXPRESS_OSC=y
+CONFIG_COMMON_CLK_SCPI=y
+CONFIG_COMMON_CLK_CS2000_CP=y
+CONFIG_COMMON_CLK_S2MPS11=y
+CONFIG_COMMON_CLK_XGENE=y
+CONFIG_COMMON_CLK_PWM=y
+CONFIG_COMMON_CLK_VC5=y
+CONFIG_HWSPINLOCK=y
+CONFIG_MAILBOX=y
+CONFIG_ARM_MHU=y
+CONFIG_PLATFORM_MHU=y
+CONFIG_IOMMU_IO_PGTABLE_ARMV7S=y
+CONFIG_ARM_SMMU=y
+CONFIG_ARM_SMMU_V3=y
+CONFIG_REMOTEPROC=y
+CONFIG_RPMSG_QCOM_GLINK_RPM=y
+CONFIG_FSL_RCPM=y
+CONFIG_SOC_TI=y
+CONFIG_EXTCON_USB_GPIO=y
+CONFIG_EXTCON_USBC_CROS_EC=y
+CONFIG_MEMORY=y
+CONFIG_IIO=y
+CONFIG_PWM=y
+CONFIG_PWM_MESON=y
+CONFIG_PHY_QCOM_USB_HS=y
+CONFIG_PHY_SAMSUNG_USB2=y
+CONFIG_FPGA=y
+CONFIG_TEE=y
+CONFIG_OPTEE=y
+CONFIG_INTERCONNECT=y
+CONFIG_FANOTIFY=y
+CONFIG_FANOTIFY_ACCESS_PERMISSIONS=y
+CONFIG_QUOTA=y
+CONFIG_VFAT_FS=y
+CONFIG_TMPFS=y
+CONFIG_TMPFS_POSIX_ACL=y
+CONFIG_HUGETLBFS=y
+CONFIG_EFIVAR_FS=y
+CONFIG_PSTORE=y
+CONFIG_NLS_CODEPAGE_437=y
+CONFIG_NLS_ISO8859_1=y
+CONFIG_KEYS=y
+CONFIG_SECURITY=y
+CONFIG_CRYPTO_AUTHENC=m
+CONFIG_CRYPTO_ECHAINIV=y
+CONFIG_CRYPTO_MD5=m
+CONFIG_CRYPTO_CRC32C=y
+CONFIG_CRYPTO_ANSI_CPRNG=y
+CONFIG_CRYPTO_GHASH_ARM64_CE=y
+CONFIG_CRYPTO_SHA1_ARM64_CE=y
+CONFIG_CRYPTO_SHA2_ARM64_CE=y
+CONFIG_CRYPTO_AES_ARM64_CE_CCM=y
+CONFIG_INDIRECT_PIO=y
+CONFIG_CRC16=y
+CONFIG_CRC8=y
+CONFIG_PRINTK_TIME=y
+CONFIG_DEBUG_KERNEL=y
+CONFIG_MAGIC_SYSRQ=y
+CONFIG_DEBUG_FS=y
+# CONFIG_SCHED_DEBUG is not set
+# CONFIG_FTRACE is not set
+CONFIG_MEMTEST=y
diff --git a/drivers/clk/meson/Kconfig b/drivers/clk/meson/Kconfig
index cf8cf3f9e4ee8..8e5e92bb88c08 100644
--- a/drivers/clk/meson/Kconfig
+++ b/drivers/clk/meson/Kconfig
@@ -110,6 +110,16 @@ config COMMON_CLK_AXG_AUDIO
Support for the audio clock controller on AmLogic A113D devices,
aka axg, Say Y if you want audio subsystem to work.
+config COMMON_CLK_A1_CPU
+ tristate "Amlogic A1 SoC CPU controller support"
+ depends on ARM64
+ select COMMON_CLK_MESON_REGMAP
+ select COMMON_CLK_MESON_CLKC_UTILS
+ help
+ Support for the CPU clock controller on Amlogic A113L based
+ device, A1 SoC Family. Say Y if you want A1 CPU clock controller
+ to work.
+
config COMMON_CLK_A1_PLL
tristate "Amlogic A1 SoC PLL controller support"
depends on ARM64
@@ -132,6 +142,18 @@ config COMMON_CLK_A1_PERIPHERALS
device, A1 SoC Family. Say Y if you want A1 Peripherals clock
controller to work.
+config COMMON_CLK_A1_AUDIO
+ tristate "Amlogic A1 SoC Audio clock controller support"
+ depends on ARM64
+ select COMMON_CLK_MESON_REGMAP
+ select COMMON_CLK_MESON_CLKC_UTILS
+ select COMMON_CLK_MESON_PHASE
+ select COMMON_CLK_MESON_SCLK_DIV
+ help
+ Support for the Audio clock controller on Amlogic A113L based
+ device, A1 SoC Family. Say Y if you want A1 Audio clock controller
+ to work.
+
config COMMON_CLK_C3_PLL
tristate "Amlogic C3 PLL clock controller"
depends on ARM64
diff --git a/drivers/clk/meson/Makefile b/drivers/clk/meson/Makefile
index c6719694a242e..5cd55cb633b5f 100644
--- a/drivers/clk/meson/Makefile
+++ b/drivers/clk/meson/Makefile
@@ -17,8 +17,10 @@ obj-$(CONFIG_COMMON_CLK_MESON_VCLK) += vclk.o
obj-$(CONFIG_COMMON_CLK_AXG) += axg.o axg-aoclk.o
obj-$(CONFIG_COMMON_CLK_AXG_AUDIO) += axg-audio.o
+obj-$(CONFIG_COMMON_CLK_A1_CPU) += a1-cpu.o
obj-$(CONFIG_COMMON_CLK_A1_PLL) += a1-pll.o
obj-$(CONFIG_COMMON_CLK_A1_PERIPHERALS) += a1-peripherals.o
+obj-$(CONFIG_COMMON_CLK_A1_AUDIO) += a1-audio.o
obj-$(CONFIG_COMMON_CLK_C3_PLL) += c3-pll.o
obj-$(CONFIG_COMMON_CLK_C3_PERIPHERALS) += c3-peripherals.o
obj-$(CONFIG_COMMON_CLK_GXBB) += gxbb.o gxbb-aoclk.o
diff --git a/drivers/clk/meson/a1-audio.c b/drivers/clk/meson/a1-audio.c
new file mode 100644
index 0000000000000..f45737f0458e4
--- /dev/null
+++ b/drivers/clk/meson/a1-audio.c
@@ -0,0 +1,560 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+/*
+ * Copyright (c) 2024, SaluteDevices. All Rights Reserved.
+ *
+ * Author: Jan Dakinevich <jan.dakinevich@salutedevices.com>
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/init.h>
+#include <linux/of_device.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/reset-controller.h>
+#include <linux/slab.h>
+
+#include "meson-clkc-utils.h"
+#include "clk-regmap.h"
+#include "clk-phase.h"
+#include "sclk-div.h"
+#include "a1-audio.h"
+
+#define AUDIO_PDATA(_name) \
+ ((const struct clk_parent_data[]) { { .hw = &(_name).hw } })
+
+#define AUDIO_MUX(_name, _reg, _mask, _shift, _pdata) \
+static struct clk_regmap _name = { \
+ .map = AUDIO_REG_MAP(_reg), \
+ .data = &(struct clk_regmap_mux_data){ \
+ .offset = AUDIO_REG_OFFSET(_reg), \
+ .mask = (_mask), \
+ .shift = (_shift), \
+ }, \
+ .hw.init = &(struct clk_init_data) { \
+ .name = #_name, \
+ .ops = &clk_regmap_mux_ops, \
+ .parent_data = (_pdata), \
+ .num_parents = ARRAY_SIZE(_pdata), \
+ }, \
+}
+
+#define AUDIO_DIV(_name, _reg, _shift, _width, _pdata) \
+static struct clk_regmap _name = { \
+ .map = AUDIO_REG_MAP(_reg), \
+ .data = &(struct clk_regmap_div_data){ \
+ .offset = AUDIO_REG_OFFSET(_reg), \
+ .shift = (_shift), \
+ .width = (_width), \
+ }, \
+ .hw.init = &(struct clk_init_data) { \
+ .name = #_name, \
+ .ops = &clk_regmap_divider_ops, \
+ .parent_data = (_pdata), \
+ .num_parents = 1, \
+ .flags = CLK_SET_RATE_PARENT, \
+ }, \
+}
+
+#define AUDIO_GATE(_name, _reg, _bit, _pdata) \
+static struct clk_regmap _name = { \
+ .map = AUDIO_REG_MAP(_reg), \
+ .data = &(struct clk_regmap_gate_data){ \
+ .offset = AUDIO_REG_OFFSET(_reg), \
+ .bit_idx = (_bit), \
+ }, \
+ .hw.init = &(struct clk_init_data) { \
+ .name = #_name, \
+ .ops = &clk_regmap_gate_ops, \
+ .parent_data = (_pdata), \
+ .num_parents = 1, \
+ .flags = CLK_SET_RATE_PARENT, \
+ }, \
+}
+
+#define AUDIO_SCLK_DIV(_name, _reg, _div_shift, _div_width, \
+ _hi_shift, _hi_width, _pdata, _set_rate_parent) \
+static struct clk_regmap _name = { \
+ .map = AUDIO_REG_MAP(_reg), \
+ .data = &(struct meson_sclk_div_data) { \
+ .div = { \
+ .reg_off = AUDIO_REG_OFFSET(_reg), \
+ .shift = (_div_shift), \
+ .width = (_div_width), \
+ }, \
+ .hi = { \
+ .reg_off = AUDIO_REG_OFFSET(_reg), \
+ .shift = (_hi_shift), \
+ .width = (_hi_width), \
+ }, \
+ }, \
+ .hw.init = &(struct clk_init_data) { \
+ .name = #_name, \
+ .ops = &meson_sclk_div_ops, \
+ .parent_data = (_pdata), \
+ .num_parents = 1, \
+ .flags = (_set_rate_parent) ? CLK_SET_RATE_PARENT : 0, \
+ }, \
+}
+
+#define AUDIO_TRIPHASE(_name, _reg, _width, _shift0, _shift1, _shift2, \
+ _pdata) \
+static struct clk_regmap _name = { \
+ .map = AUDIO_REG_MAP(_reg), \
+ .data = &(struct meson_clk_triphase_data) { \
+ .ph0 = { \
+ .reg_off = AUDIO_REG_OFFSET(_reg), \
+ .shift = (_shift0), \
+ .width = (_width), \
+ }, \
+ .ph1 = { \
+ .reg_off = AUDIO_REG_OFFSET(_reg), \
+ .shift = (_shift1), \
+ .width = (_width), \
+ }, \
+ .ph2 = { \
+ .reg_off = AUDIO_REG_OFFSET(_reg), \
+ .shift = (_shift2), \
+ .width = (_width), \
+ }, \
+ }, \
+ .hw.init = &(struct clk_init_data) { \
+ .name = #_name, \
+ .ops = &meson_clk_triphase_ops, \
+ .parent_data = (_pdata), \
+ .num_parents = 1, \
+ .flags = CLK_SET_RATE_PARENT | CLK_DUTY_CYCLE_PARENT, \
+ }, \
+}
+
+#define AUDIO_SCLK_WS(_name, _reg, _width, _shift_ph, _shift_ws, \
+ _pdata) \
+static struct clk_regmap _name = { \
+ .map = AUDIO_REG_MAP(_reg), \
+ .data = &(struct meson_sclk_ws_inv_data) { \
+ .ph = { \
+ .reg_off = AUDIO_REG_OFFSET(_reg), \
+ .shift = (_shift_ph), \
+ .width = (_width), \
+ }, \
+ .ws = { \
+ .reg_off = AUDIO_REG_OFFSET(_reg), \
+ .shift = (_shift_ws), \
+ .width = (_width), \
+ }, \
+ }, \
+ .hw.init = &(struct clk_init_data) { \
+ .name = #_name, \
+ .ops = &meson_sclk_ws_inv_ops, \
+ .parent_data = (_pdata), \
+ .num_parents = 1, \
+ .flags = CLK_SET_RATE_PARENT | CLK_DUTY_CYCLE_PARENT, \
+ }, \
+}
+
+static const struct clk_parent_data a1_pclk_pdata[] = {
+ { .fw_name = "pclk", },
+};
+
+AUDIO_GATE(audio_ddr_arb, AUDIO_CLK_GATE_EN0, 0, a1_pclk_pdata);
+AUDIO_GATE(audio_tdmin_a, AUDIO_CLK_GATE_EN0, 1, a1_pclk_pdata);
+AUDIO_GATE(audio_tdmin_b, AUDIO_CLK_GATE_EN0, 2, a1_pclk_pdata);
+AUDIO_GATE(audio_tdmin_lb, AUDIO_CLK_GATE_EN0, 3, a1_pclk_pdata);
+AUDIO_GATE(audio_loopback, AUDIO_CLK_GATE_EN0, 4, a1_pclk_pdata);
+AUDIO_GATE(audio_tdmout_a, AUDIO_CLK_GATE_EN0, 5, a1_pclk_pdata);
+AUDIO_GATE(audio_tdmout_b, AUDIO_CLK_GATE_EN0, 6, a1_pclk_pdata);
+AUDIO_GATE(audio_frddr_a, AUDIO_CLK_GATE_EN0, 7, a1_pclk_pdata);
+AUDIO_GATE(audio_frddr_b, AUDIO_CLK_GATE_EN0, 8, a1_pclk_pdata);
+AUDIO_GATE(audio_toddr_a, AUDIO_CLK_GATE_EN0, 9, a1_pclk_pdata);
+AUDIO_GATE(audio_toddr_b, AUDIO_CLK_GATE_EN0, 10, a1_pclk_pdata);
+AUDIO_GATE(audio_spdifin, AUDIO_CLK_GATE_EN0, 11, a1_pclk_pdata);
+AUDIO_GATE(audio_resample, AUDIO_CLK_GATE_EN0, 12, a1_pclk_pdata);
+AUDIO_GATE(audio_eqdrc, AUDIO_CLK_GATE_EN0, 13, a1_pclk_pdata);
+AUDIO_GATE(audio_audiolocker, AUDIO_CLK_GATE_EN0, 14, a1_pclk_pdata);
+
+AUDIO_GATE(audio2_ddr_arb, AUDIO2_CLK_GATE_EN0, 0, a1_pclk_pdata);
+AUDIO_GATE(audio2_pdm, AUDIO2_CLK_GATE_EN0, 1, a1_pclk_pdata);
+AUDIO_GATE(audio2_tdmin_vad, AUDIO2_CLK_GATE_EN0, 2, a1_pclk_pdata);
+AUDIO_GATE(audio2_toddr_vad, AUDIO2_CLK_GATE_EN0, 3, a1_pclk_pdata);
+AUDIO_GATE(audio2_vad, AUDIO2_CLK_GATE_EN0, 4, a1_pclk_pdata);
+AUDIO_GATE(audio2_audiotop, AUDIO2_CLK_GATE_EN0, 7, a1_pclk_pdata);
+
+static const struct clk_parent_data a1_mst_pdata[] = {
+ { .fw_name = "dds_in" },
+ { .fw_name = "fclk_div2" },
+ { .fw_name = "fclk_div3" },
+ { .fw_name = "hifi_pll" },
+ { .fw_name = "xtal" },
+};
+
+#define AUDIO_MST_MCLK(_name, _reg) \
+ AUDIO_MUX(_name##_mux, (_reg), 0x7, 24, a1_mst_pdata); \
+ AUDIO_DIV(_name##_div, (_reg), 0, 16, \
+ AUDIO_PDATA(_name##_mux)); \
+ AUDIO_GATE(_name, (_reg), 31, AUDIO_PDATA(_name##_div))
+
+AUDIO_MST_MCLK(audio_mst_a_mclk, AUDIO_MCLK_A_CTRL);
+AUDIO_MST_MCLK(audio_mst_b_mclk, AUDIO_MCLK_B_CTRL);
+AUDIO_MST_MCLK(audio_mst_c_mclk, AUDIO_MCLK_C_CTRL);
+AUDIO_MST_MCLK(audio_mst_d_mclk, AUDIO_MCLK_D_CTRL);
+AUDIO_MST_MCLK(audio_spdifin_clk, AUDIO_CLK_SPDIFIN_CTRL);
+AUDIO_MST_MCLK(audio_eqdrc_clk, AUDIO_CLK_EQDRC_CTRL);
+
+AUDIO_MUX(audio_resample_clk_mux, AUDIO_CLK_RESAMPLE_CTRL, 0xf, 24,
+ a1_mst_pdata);
+AUDIO_DIV(audio_resample_clk_div, AUDIO_CLK_RESAMPLE_CTRL, 0, 8,
+ AUDIO_PDATA(audio_resample_clk_mux));
+AUDIO_GATE(audio_resample_clk, AUDIO_CLK_RESAMPLE_CTRL, 31,
+ AUDIO_PDATA(audio_resample_clk_div));
+
+AUDIO_MUX(audio_locker_in_clk_mux, AUDIO_CLK_LOCKER_CTRL, 0xf, 8,
+ a1_mst_pdata);
+AUDIO_DIV(audio_locker_in_clk_div, AUDIO_CLK_LOCKER_CTRL, 0, 8,
+ AUDIO_PDATA(audio_locker_in_clk_mux));
+AUDIO_GATE(audio_locker_in_clk, AUDIO_CLK_LOCKER_CTRL, 15,
+ AUDIO_PDATA(audio_locker_in_clk_div));
+
+AUDIO_MUX(audio_locker_out_clk_mux, AUDIO_CLK_LOCKER_CTRL, 0xf, 24,
+ a1_mst_pdata);
+AUDIO_DIV(audio_locker_out_clk_div, AUDIO_CLK_LOCKER_CTRL, 16, 8,
+ AUDIO_PDATA(audio_locker_out_clk_mux));
+AUDIO_GATE(audio_locker_out_clk, AUDIO_CLK_LOCKER_CTRL, 31,
+ AUDIO_PDATA(audio_locker_out_clk_div));
+
+AUDIO_MST_MCLK(audio2_vad_mclk, AUDIO2_MCLK_VAD_CTRL);
+AUDIO_MST_MCLK(audio2_vad_clk, AUDIO2_CLK_VAD_CTRL);
+AUDIO_MST_MCLK(audio2_pdm_dclk, AUDIO2_CLK_PDMIN_CTRL0);
+AUDIO_MST_MCLK(audio2_pdm_sysclk, AUDIO2_CLK_PDMIN_CTRL1);
+
+#define AUDIO_MST_SCLK(_name, _reg0, _reg1, _pdata) \
+ AUDIO_GATE(_name##_pre_en, (_reg0), 31, (_pdata)); \
+ AUDIO_SCLK_DIV(_name##_div, (_reg0), 20, 10, 0, 0, \
+ AUDIO_PDATA(_name##_pre_en), true); \
+ AUDIO_GATE(_name##_post_en, (_reg0), 30, \
+ AUDIO_PDATA(_name##_div)); \
+ AUDIO_TRIPHASE(_name, (_reg1), 1, 0, 2, 4, \
+ AUDIO_PDATA(_name##_post_en))
+
+#define AUDIO_MST_LRCLK(_name, _reg0, _reg1, _pdata) \
+ AUDIO_SCLK_DIV(_name##_div, (_reg0), 0, 10, 10, 10, \
+ (_pdata), false); \
+ AUDIO_TRIPHASE(_name, (_reg1), 1, 1, 3, 5, \
+ AUDIO_PDATA(_name##_div))
+
+AUDIO_MST_SCLK(audio_mst_a_sclk, AUDIO_MST_A_SCLK_CTRL0, AUDIO_MST_A_SCLK_CTRL1,
+ AUDIO_PDATA(audio_mst_a_mclk));
+AUDIO_MST_SCLK(audio_mst_b_sclk, AUDIO_MST_B_SCLK_CTRL0, AUDIO_MST_B_SCLK_CTRL1,
+ AUDIO_PDATA(audio_mst_b_mclk));
+AUDIO_MST_SCLK(audio_mst_c_sclk, AUDIO_MST_C_SCLK_CTRL0, AUDIO_MST_C_SCLK_CTRL1,
+ AUDIO_PDATA(audio_mst_c_mclk));
+AUDIO_MST_SCLK(audio_mst_d_sclk, AUDIO_MST_D_SCLK_CTRL0, AUDIO_MST_D_SCLK_CTRL1,
+ AUDIO_PDATA(audio_mst_d_mclk));
+
+AUDIO_MST_LRCLK(audio_mst_a_lrclk, AUDIO_MST_A_SCLK_CTRL0, AUDIO_MST_A_SCLK_CTRL1,
+ AUDIO_PDATA(audio_mst_a_sclk_post_en));
+AUDIO_MST_LRCLK(audio_mst_b_lrclk, AUDIO_MST_B_SCLK_CTRL0, AUDIO_MST_B_SCLK_CTRL1,
+ AUDIO_PDATA(audio_mst_b_sclk_post_en));
+AUDIO_MST_LRCLK(audio_mst_c_lrclk, AUDIO_MST_C_SCLK_CTRL0, AUDIO_MST_C_SCLK_CTRL1,
+ AUDIO_PDATA(audio_mst_c_sclk_post_en));
+AUDIO_MST_LRCLK(audio_mst_d_lrclk, AUDIO_MST_D_SCLK_CTRL0, AUDIO_MST_D_SCLK_CTRL1,
+ AUDIO_PDATA(audio_mst_d_sclk_post_en));
+
+static const struct clk_parent_data a1_mst_sclk_pdata[] = {
+ { .hw = &audio_mst_a_sclk.hw },
+ { .hw = &audio_mst_b_sclk.hw },
+ { .hw = &audio_mst_c_sclk.hw },
+ { .hw = &audio_mst_d_sclk.hw },
+ { .fw_name = "slv_sclk0" },
+ { .fw_name = "slv_sclk1" },
+ { .fw_name = "slv_sclk2" },
+ { .fw_name = "slv_sclk3" },
+ { .fw_name = "slv_sclk4" },
+ { .fw_name = "slv_sclk5" },
+ { .fw_name = "slv_sclk6" },
+ { .fw_name = "slv_sclk7" },
+ { .fw_name = "slv_sclk8" },
+ { .fw_name = "slv_sclk9" },
+};
+
+static const struct clk_parent_data a1_mst_lrclk_pdata[] = {
+ { .hw = &audio_mst_a_lrclk.hw },
+ { .hw = &audio_mst_b_lrclk.hw },
+ { .hw = &audio_mst_c_lrclk.hw },
+ { .hw = &audio_mst_d_lrclk.hw },
+ { .fw_name = "slv_lrclk0" },
+ { .fw_name = "slv_lrclk1" },
+ { .fw_name = "slv_lrclk2" },
+ { .fw_name = "slv_lrclk3" },
+ { .fw_name = "slv_lrclk4" },
+ { .fw_name = "slv_lrclk5" },
+ { .fw_name = "slv_lrclk6" },
+ { .fw_name = "slv_lrclk7" },
+ { .fw_name = "slv_lrclk8" },
+ { .fw_name = "slv_lrclk9" },
+};
+
+#define AUDIO_TDM_SCLK(_name, _reg) \
+ AUDIO_MUX(_name##_mux, (_reg), 0xf, 24, a1_mst_sclk_pdata); \
+ AUDIO_GATE(_name##_pre_en, (_reg), 31, \
+ AUDIO_PDATA(_name##_mux)); \
+ AUDIO_GATE(_name##_post_en, (_reg), 30, \
+ AUDIO_PDATA(_name##_pre_en)); \
+ AUDIO_SCLK_WS(_name, (_reg), 1, 29, 28, \
+ AUDIO_PDATA(_name##_post_en))
+
+#define AUDIO_TDM_LRCLK(_name, _reg) \
+ AUDIO_MUX(_name, (_reg), 0xf, 20, a1_mst_lrclk_pdata)
+
+AUDIO_TDM_SCLK(audio_tdmin_a_sclk, AUDIO_CLK_TDMIN_A_CTRL);
+AUDIO_TDM_SCLK(audio_tdmin_b_sclk, AUDIO_CLK_TDMIN_B_CTRL);
+AUDIO_TDM_SCLK(audio_tdmin_lb_sclk, AUDIO_CLK_TDMIN_LB_CTRL);
+AUDIO_TDM_SCLK(audio_tdmout_a_sclk, AUDIO_CLK_TDMOUT_A_CTRL);
+AUDIO_TDM_SCLK(audio_tdmout_b_sclk, AUDIO_CLK_TDMOUT_B_CTRL);
+
+AUDIO_TDM_LRCLK(audio_tdmin_a_lrclk, AUDIO_CLK_TDMIN_A_CTRL);
+AUDIO_TDM_LRCLK(audio_tdmin_b_lrclk, AUDIO_CLK_TDMIN_B_CTRL);
+AUDIO_TDM_LRCLK(audio_tdmin_lb_lrclk, AUDIO_CLK_TDMIN_LB_CTRL);
+AUDIO_TDM_LRCLK(audio_tdmout_a_lrclk, AUDIO_CLK_TDMOUT_A_CTRL);
+AUDIO_TDM_LRCLK(audio_tdmout_b_lrclk, AUDIO_CLK_TDMOUT_B_CTRL);
+
+static struct clk_hw *a1_audio_hw_clks[] = {
+ [AUD_CLKID_DDR_ARB] = &audio_ddr_arb.hw,
+ [AUD_CLKID_TDMIN_A] = &audio_tdmin_a.hw,
+ [AUD_CLKID_TDMIN_B] = &audio_tdmin_b.hw,
+ [AUD_CLKID_TDMIN_LB] = &audio_tdmin_lb.hw,
+ [AUD_CLKID_LOOPBACK] = &audio_loopback.hw,
+ [AUD_CLKID_TDMOUT_A] = &audio_tdmout_a.hw,
+ [AUD_CLKID_TDMOUT_B] = &audio_tdmout_b.hw,
+ [AUD_CLKID_FRDDR_A] = &audio_frddr_a.hw,
+ [AUD_CLKID_FRDDR_B] = &audio_frddr_b.hw,
+ [AUD_CLKID_TODDR_A] = &audio_toddr_a.hw,
+ [AUD_CLKID_TODDR_B] = &audio_toddr_b.hw,
+ [AUD_CLKID_SPDIFIN] = &audio_spdifin.hw,
+ [AUD_CLKID_RESAMPLE] = &audio_resample.hw,
+ [AUD_CLKID_EQDRC] = &audio_eqdrc.hw,
+ [AUD_CLKID_LOCKER] = &audio_audiolocker.hw,
+ [AUD_CLKID_MST_A_MCLK_SEL] = &audio_mst_a_mclk_mux.hw,
+ [AUD_CLKID_MST_A_MCLK_DIV] = &audio_mst_a_mclk_div.hw,
+ [AUD_CLKID_MST_A_MCLK] = &audio_mst_a_mclk.hw,
+ [AUD_CLKID_MST_B_MCLK_SEL] = &audio_mst_b_mclk_mux.hw,
+ [AUD_CLKID_MST_B_MCLK_DIV] = &audio_mst_b_mclk_div.hw,
+ [AUD_CLKID_MST_B_MCLK] = &audio_mst_b_mclk.hw,
+ [AUD_CLKID_MST_C_MCLK_SEL] = &audio_mst_c_mclk_mux.hw,
+ [AUD_CLKID_MST_C_MCLK_DIV] = &audio_mst_c_mclk_div.hw,
+ [AUD_CLKID_MST_C_MCLK] = &audio_mst_c_mclk.hw,
+ [AUD_CLKID_MST_D_MCLK_SEL] = &audio_mst_d_mclk_mux.hw,
+ [AUD_CLKID_MST_D_MCLK_DIV] = &audio_mst_d_mclk_div.hw,
+ [AUD_CLKID_MST_D_MCLK] = &audio_mst_d_mclk.hw,
+ [AUD_CLKID_RESAMPLE_CLK_SEL] = &audio_resample_clk_mux.hw,
+ [AUD_CLKID_RESAMPLE_CLK_DIV] = &audio_resample_clk_div.hw,
+ [AUD_CLKID_RESAMPLE_CLK] = &audio_resample_clk.hw,
+ [AUD_CLKID_LOCKER_IN_CLK_SEL] = &audio_locker_in_clk_mux.hw,
+ [AUD_CLKID_LOCKER_IN_CLK_DIV] = &audio_locker_in_clk_div.hw,
+ [AUD_CLKID_LOCKER_IN_CLK] = &audio_locker_in_clk.hw,
+ [AUD_CLKID_LOCKER_OUT_CLK_SEL] = &audio_locker_out_clk_mux.hw,
+ [AUD_CLKID_LOCKER_OUT_CLK_DIV] = &audio_locker_out_clk_div.hw,
+ [AUD_CLKID_LOCKER_OUT_CLK] = &audio_locker_out_clk.hw,
+ [AUD_CLKID_SPDIFIN_CLK_SEL] = &audio_spdifin_clk_mux.hw,
+ [AUD_CLKID_SPDIFIN_CLK_DIV] = &audio_spdifin_clk_div.hw,
+ [AUD_CLKID_SPDIFIN_CLK] = &audio_spdifin_clk.hw,
+ [AUD_CLKID_EQDRC_CLK_SEL] = &audio_eqdrc_clk_mux.hw,
+ [AUD_CLKID_EQDRC_CLK_DIV] = &audio_eqdrc_clk_div.hw,
+ [AUD_CLKID_EQDRC_CLK] = &audio_eqdrc_clk.hw,
+ [AUD_CLKID_MST_A_SCLK_PRE_EN] = &audio_mst_a_sclk_pre_en.hw,
+ [AUD_CLKID_MST_A_SCLK_DIV] = &audio_mst_a_sclk_div.hw,
+ [AUD_CLKID_MST_A_SCLK_POST_EN] = &audio_mst_a_sclk_post_en.hw,
+ [AUD_CLKID_MST_A_SCLK] = &audio_mst_a_sclk.hw,
+ [AUD_CLKID_MST_B_SCLK_PRE_EN] = &audio_mst_b_sclk_pre_en.hw,
+ [AUD_CLKID_MST_B_SCLK_DIV] = &audio_mst_b_sclk_div.hw,
+ [AUD_CLKID_MST_B_SCLK_POST_EN] = &audio_mst_b_sclk_post_en.hw,
+ [AUD_CLKID_MST_B_SCLK] = &audio_mst_b_sclk.hw,
+ [AUD_CLKID_MST_C_SCLK_PRE_EN] = &audio_mst_c_sclk_pre_en.hw,
+ [AUD_CLKID_MST_C_SCLK_DIV] = &audio_mst_c_sclk_div.hw,
+ [AUD_CLKID_MST_C_SCLK_POST_EN] = &audio_mst_c_sclk_post_en.hw,
+ [AUD_CLKID_MST_C_SCLK] = &audio_mst_c_sclk.hw,
+ [AUD_CLKID_MST_D_SCLK_PRE_EN] = &audio_mst_d_sclk_pre_en.hw,
+ [AUD_CLKID_MST_D_SCLK_DIV] = &audio_mst_d_sclk_div.hw,
+ [AUD_CLKID_MST_D_SCLK_POST_EN] = &audio_mst_d_sclk_post_en.hw,
+ [AUD_CLKID_MST_D_SCLK] = &audio_mst_d_sclk.hw,
+ [AUD_CLKID_MST_A_LRCLK_DIV] = &audio_mst_a_lrclk_div.hw,
+ [AUD_CLKID_MST_A_LRCLK] = &audio_mst_a_lrclk.hw,
+ [AUD_CLKID_MST_B_LRCLK_DIV] = &audio_mst_b_lrclk_div.hw,
+ [AUD_CLKID_MST_B_LRCLK] = &audio_mst_b_lrclk.hw,
+ [AUD_CLKID_MST_C_LRCLK_DIV] = &audio_mst_c_lrclk_div.hw,
+ [AUD_CLKID_MST_C_LRCLK] = &audio_mst_c_lrclk.hw,
+ [AUD_CLKID_MST_D_LRCLK_DIV] = &audio_mst_d_lrclk_div.hw,
+ [AUD_CLKID_MST_D_LRCLK] = &audio_mst_d_lrclk.hw,
+ [AUD_CLKID_TDMIN_A_SCLK_SEL] = &audio_tdmin_a_sclk_mux.hw,
+ [AUD_CLKID_TDMIN_A_SCLK_PRE_EN] = &audio_tdmin_a_sclk_pre_en.hw,
+ [AUD_CLKID_TDMIN_A_SCLK_POST_EN] = &audio_tdmin_a_sclk_post_en.hw,
+ [AUD_CLKID_TDMIN_A_SCLK] = &audio_tdmin_a_sclk.hw,
+ [AUD_CLKID_TDMIN_A_LRCLK] = &audio_tdmin_a_lrclk.hw,
+ [AUD_CLKID_TDMIN_B_SCLK_SEL] = &audio_tdmin_b_sclk_mux.hw,
+ [AUD_CLKID_TDMIN_B_SCLK_PRE_EN] = &audio_tdmin_b_sclk_pre_en.hw,
+ [AUD_CLKID_TDMIN_B_SCLK_POST_EN] = &audio_tdmin_b_sclk_post_en.hw,
+ [AUD_CLKID_TDMIN_B_SCLK] = &audio_tdmin_b_sclk.hw,
+ [AUD_CLKID_TDMIN_B_LRCLK] = &audio_tdmin_b_lrclk.hw,
+ [AUD_CLKID_TDMIN_LB_SCLK_SEL] = &audio_tdmin_lb_sclk_mux.hw,
+ [AUD_CLKID_TDMIN_LB_SCLK_PRE_EN] = &audio_tdmin_lb_sclk_pre_en.hw,
+ [AUD_CLKID_TDMIN_LB_SCLK_POST_EN] = &audio_tdmin_lb_sclk_post_en.hw,
+ [AUD_CLKID_TDMIN_LB_SCLK] = &audio_tdmin_lb_sclk.hw,
+ [AUD_CLKID_TDMIN_LB_LRCLK] = &audio_tdmin_lb_lrclk.hw,
+ [AUD_CLKID_TDMOUT_A_SCLK_SEL] = &audio_tdmout_a_sclk_mux.hw,
+ [AUD_CLKID_TDMOUT_A_SCLK_PRE_EN] = &audio_tdmout_a_sclk_pre_en.hw,
+ [AUD_CLKID_TDMOUT_A_SCLK_POST_EN] = &audio_tdmout_a_sclk_post_en.hw,
+ [AUD_CLKID_TDMOUT_A_SCLK] = &audio_tdmout_a_sclk.hw,
+ [AUD_CLKID_TDMOUT_A_LRCLK] = &audio_tdmout_a_lrclk.hw,
+ [AUD_CLKID_TDMOUT_B_SCLK_SEL] = &audio_tdmout_b_sclk_mux.hw,
+ [AUD_CLKID_TDMOUT_B_SCLK_PRE_EN] = &audio_tdmout_b_sclk_pre_en.hw,
+ [AUD_CLKID_TDMOUT_B_SCLK_POST_EN] = &audio_tdmout_b_sclk_post_en.hw,
+ [AUD_CLKID_TDMOUT_B_SCLK] = &audio_tdmout_b_sclk.hw,
+ [AUD_CLKID_TDMOUT_B_LRCLK] = &audio_tdmout_b_lrclk.hw,
+
+ [AUD2_CLKID_DDR_ARB] = &audio2_ddr_arb.hw,
+ [AUD2_CLKID_PDM] = &audio2_pdm.hw,
+ [AUD2_CLKID_TDMIN_VAD] = &audio2_tdmin_vad.hw,
+ [AUD2_CLKID_TODDR_VAD] = &audio2_toddr_vad.hw,
+ [AUD2_CLKID_VAD] = &audio2_vad.hw,
+ [AUD2_CLKID_AUDIOTOP] = &audio2_audiotop.hw,
+ [AUD2_CLKID_VAD_MCLK_SEL] = &audio2_vad_mclk_mux.hw,
+ [AUD2_CLKID_VAD_MCLK_DIV] = &audio2_vad_mclk_div.hw,
+ [AUD2_CLKID_VAD_MCLK] = &audio2_vad_mclk.hw,
+ [AUD2_CLKID_VAD_CLK_SEL] = &audio2_vad_clk_mux.hw,
+ [AUD2_CLKID_VAD_CLK_DIV] = &audio2_vad_clk_div.hw,
+ [AUD2_CLKID_VAD_CLK] = &audio2_vad_clk.hw,
+ [AUD2_CLKID_PDM_DCLK_SEL] = &audio2_pdm_dclk_mux.hw,
+ [AUD2_CLKID_PDM_DCLK_DIV] = &audio2_pdm_dclk_div.hw,
+ [AUD2_CLKID_PDM_DCLK] = &audio2_pdm_dclk.hw,
+ [AUD2_CLKID_PDM_SYSCLK_SEL] = &audio2_pdm_sysclk_mux.hw,
+ [AUD2_CLKID_PDM_SYSCLK_DIV] = &audio2_pdm_sysclk_div.hw,
+ [AUD2_CLKID_PDM_SYSCLK] = &audio2_pdm_sysclk.hw,
+};
+
+static struct meson_clk_hw_data a1_audio_clks = {
+ .hws = a1_audio_hw_clks,
+ .num = ARRAY_SIZE(a1_audio_hw_clks),
+};
+
+static struct regmap *a1_audio_map(struct platform_device *pdev,
+ unsigned int index)
+{
+ char name[32];
+ const struct regmap_config cfg = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .name = name,
+ };
+ void __iomem *base;
+
+ base = devm_platform_ioremap_resource(pdev, index);
+ if (IS_ERR(base))
+ return base;
+
+ scnprintf(name, sizeof(name), "%d", index);
+ return devm_regmap_init_mmio(&pdev->dev, base, &cfg);
+}
+
+static int a1_register_clk(struct platform_device *pdev,
+ struct regmap *map0, struct regmap *map1,
+ struct clk_hw *hw)
+{
+ struct clk_regmap *clk = container_of(hw, struct clk_regmap, hw);
+
+ if (!hw)
+ return 0;
+
+ switch ((unsigned long)clk->map) {
+ case AUDIO_RANGE_0:
+ clk->map = map0;
+ break;
+ case AUDIO_RANGE_1:
+ clk->map = map1;
+ break;
+ default:
+ WARN_ON(1);
+ return -EINVAL;
+ }
+
+ return devm_clk_hw_register(&pdev->dev, hw);
+}
+
+static int a1_audio_clkc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct auxiliary_device *auxdev;
+ struct regmap *map0, *map1;
+ struct clk *clk;
+ unsigned int i;
+ int ret;
+
+ clk = devm_clk_get_enabled(dev, "pclk");
+ if (WARN_ON(IS_ERR(clk)))
+ return PTR_ERR(clk);
+
+ map0 = a1_audio_map(pdev, 0);
+ if (IS_ERR(map0))
+ return PTR_ERR(map0);
+
+ map1 = a1_audio_map(pdev, 1);
+ if (IS_ERR(map1))
+ return PTR_ERR(map1);
+
+ /*
+ * Register and enable AUD2_CLKID_AUDIOTOP clock first. Unless
+ * it is enabled any read/write to 'map0' hangs the CPU.
+ */
+
+ ret = a1_register_clk(pdev, map0, map1,
+ a1_audio_clks.hws[AUD2_CLKID_AUDIOTOP]);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(a1_audio_clks.hws[AUD2_CLKID_AUDIOTOP]->clk);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < a1_audio_clks.num; i++) {
+ if (i == AUD2_CLKID_AUDIOTOP)
+ continue;
+
+ ret = a1_register_clk(pdev, map0, map1, a1_audio_clks.hws[i]);
+ if (ret)
+ return ret;
+ }
+
+ ret = devm_of_clk_add_hw_provider(dev, meson_clk_hw_get,
+ &a1_audio_clks);
+ if (ret)
+ return ret;
+
+ /* Register auxiliary reset driver */
+ auxdev = __devm_auxiliary_device_create(dev, dev->driver->name,
+ "rst-a1", NULL, 0);
+ if (!auxdev)
+ return -ENODEV;
+
+ return 0;
+}
+
+static const struct of_device_id a1_audio_clkc_match_table[] = {
+ { .compatible = "amlogic,a1-audio-clkc", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, a1_audio_clkc_match_table);
+
+static struct platform_driver a1_audio_clkc_driver = {
+ .probe = a1_audio_clkc_probe,
+ .driver = {
+ .name = "a1-audio-clkc",
+ .of_match_table = a1_audio_clkc_match_table,
+ },
+};
+module_platform_driver(a1_audio_clkc_driver);
+
+MODULE_DESCRIPTION("Amlogic A1 Audio Clock driver");
+MODULE_AUTHOR("Jan Dakinevich <jan.dakinevich@salutedevices.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/meson/a1-audio.h b/drivers/clk/meson/a1-audio.h
new file mode 100644
index 0000000000000..f994e87276cdf
--- /dev/null
+++ b/drivers/clk/meson/a1-audio.h
@@ -0,0 +1,58 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */
+/*
+ * Copyright (c) 2024, SaluteDevices. All Rights Reserved.
+ *
+ * Author: Jan Dakinevich <jan.dakinevich@salutedevices.com>
+ */
+
+#ifndef __A1_AUDIO_H
+#define __A1_AUDIO_H
+
+#define AUDIO_RANGE_0 0xa
+#define AUDIO_RANGE_1 0xb
+#define AUDIO_RANGE_SHIFT 16
+
+#define AUDIO_REG(_range, _offset) \
+ (((_range) << AUDIO_RANGE_SHIFT) + (_offset))
+
+#define AUDIO_REG_OFFSET(_reg) \
+ ((_reg) & ((1 << AUDIO_RANGE_SHIFT) - 1))
+
+#define AUDIO_REG_MAP(_reg) \
+ ((void *)((_reg) >> AUDIO_RANGE_SHIFT))
+
+#define AUDIO_CLK_GATE_EN0 AUDIO_REG(AUDIO_RANGE_0, 0x000)
+#define AUDIO_MCLK_A_CTRL AUDIO_REG(AUDIO_RANGE_0, 0x008)
+#define AUDIO_MCLK_B_CTRL AUDIO_REG(AUDIO_RANGE_0, 0x00c)
+#define AUDIO_MCLK_C_CTRL AUDIO_REG(AUDIO_RANGE_0, 0x010)
+#define AUDIO_MCLK_D_CTRL AUDIO_REG(AUDIO_RANGE_0, 0x014)
+#define AUDIO_MCLK_E_CTRL AUDIO_REG(AUDIO_RANGE_0, 0x018)
+#define AUDIO_MCLK_F_CTRL AUDIO_REG(AUDIO_RANGE_0, 0x01c)
+#define AUDIO_SW_RESET0 AUDIO_REG(AUDIO_RANGE_0, 0x028)
+#define AUDIO_MST_A_SCLK_CTRL0 AUDIO_REG(AUDIO_RANGE_0, 0x040)
+#define AUDIO_MST_A_SCLK_CTRL1 AUDIO_REG(AUDIO_RANGE_0, 0x044)
+#define AUDIO_MST_B_SCLK_CTRL0 AUDIO_REG(AUDIO_RANGE_0, 0x048)
+#define AUDIO_MST_B_SCLK_CTRL1 AUDIO_REG(AUDIO_RANGE_0, 0x04c)
+#define AUDIO_MST_C_SCLK_CTRL0 AUDIO_REG(AUDIO_RANGE_0, 0x050)
+#define AUDIO_MST_C_SCLK_CTRL1 AUDIO_REG(AUDIO_RANGE_0, 0x054)
+#define AUDIO_MST_D_SCLK_CTRL0 AUDIO_REG(AUDIO_RANGE_0, 0x058)
+#define AUDIO_MST_D_SCLK_CTRL1 AUDIO_REG(AUDIO_RANGE_0, 0x05c)
+#define AUDIO_CLK_TDMIN_A_CTRL AUDIO_REG(AUDIO_RANGE_0, 0x080)
+#define AUDIO_CLK_TDMIN_B_CTRL AUDIO_REG(AUDIO_RANGE_0, 0x084)
+#define AUDIO_CLK_TDMIN_LB_CTRL AUDIO_REG(AUDIO_RANGE_0, 0x08c)
+#define AUDIO_CLK_TDMOUT_A_CTRL AUDIO_REG(AUDIO_RANGE_0, 0x090)
+#define AUDIO_CLK_TDMOUT_B_CTRL AUDIO_REG(AUDIO_RANGE_0, 0x094)
+#define AUDIO_CLK_SPDIFIN_CTRL AUDIO_REG(AUDIO_RANGE_0, 0x09c)
+#define AUDIO_CLK_RESAMPLE_CTRL AUDIO_REG(AUDIO_RANGE_0, 0x0a4)
+#define AUDIO_CLK_LOCKER_CTRL AUDIO_REG(AUDIO_RANGE_0, 0x0a8)
+#define AUDIO_CLK_EQDRC_CTRL AUDIO_REG(AUDIO_RANGE_0, 0x0c0)
+
+#define AUDIO2_CLK_GATE_EN0 AUDIO_REG(AUDIO_RANGE_1, 0x00c)
+#define AUDIO2_MCLK_VAD_CTRL AUDIO_REG(AUDIO_RANGE_1, 0x040)
+#define AUDIO2_CLK_VAD_CTRL AUDIO_REG(AUDIO_RANGE_1, 0x044)
+#define AUDIO2_CLK_PDMIN_CTRL0 AUDIO_REG(AUDIO_RANGE_1, 0x058)
+#define AUDIO2_CLK_PDMIN_CTRL1 AUDIO_REG(AUDIO_RANGE_1, 0x05c)
+
+#include <dt-bindings/clock/amlogic,a1-audio-clkc.h>
+
+#endif /* __A1_AUDIO_H */
diff --git a/drivers/clk/meson/a1-cpu.c b/drivers/clk/meson/a1-cpu.c
new file mode 100644
index 0000000000000..a9edabeafea9b
--- /dev/null
+++ b/drivers/clk/meson/a1-cpu.c
@@ -0,0 +1,331 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Amlogic A1 SoC family CPU Clock Controller driver.
+ *
+ * Copyright (c) 2024, SaluteDevices. All Rights Reserved.
+ * Author: Dmitry Rokosov <ddrokosov@salutedevices.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include "clk-regmap.h"
+#include "meson-clkc-utils.h"
+
+#include <dt-bindings/clock/amlogic,a1-cpu-clkc.h>
+
+/* CPU Clock Controller register offset */
+#define CPUCTRL_CLK_CTRL0 0x0
+#define CPUCTRL_CLK_CTRL1 0x4
+
+static u32 cpu_fsource_sel_table[] = { 0, 1, 2 };
+static const struct clk_parent_data cpu_fsource_sel_parents[] = {
+ { .fw_name = "xtal" },
+ { .fw_name = "fclk_div2" },
+ { .fw_name = "fclk_div3" },
+};
+
+static struct clk_regmap cpu_fsource_sel0 = {
+ .data = &(struct clk_regmap_mux_data) {
+ .offset = CPUCTRL_CLK_CTRL0,
+ .mask = 0x3,
+ .shift = 0,
+ .table = cpu_fsource_sel_table,
+ },
+ .hw.init = &(struct clk_init_data) {
+ .name = "cpu_fsource_sel0",
+ .ops = &clk_regmap_mux_ops,
+ .parent_data = cpu_fsource_sel_parents,
+ .num_parents = ARRAY_SIZE(cpu_fsource_sel_parents),
+ .flags = CLK_SET_RATE_PARENT,
+ },
+};
+
+static struct clk_regmap cpu_fsource_div0 = {
+ .data = &(struct clk_regmap_div_data) {
+ .offset = CPUCTRL_CLK_CTRL0,
+ .shift = 4,
+ .width = 6,
+ },
+ .hw.init = &(struct clk_init_data) {
+ .name = "cpu_fsource_div0",
+ .ops = &clk_regmap_divider_ops,
+ .parent_hws = (const struct clk_hw *[]) {
+ &cpu_fsource_sel0.hw
+ },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ },
+};
+
+static struct clk_regmap cpu_fsel0 = {
+ .data = &(struct clk_regmap_mux_data) {
+ .offset = CPUCTRL_CLK_CTRL0,
+ .mask = 0x1,
+ .shift = 2,
+ },
+ .hw.init = &(struct clk_init_data) {
+ .name = "cpu_fsel0",
+ .ops = &clk_regmap_mux_ops,
+ .parent_hws = (const struct clk_hw *[]) {
+ &cpu_fsource_sel0.hw,
+ &cpu_fsource_div0.hw,
+ },
+ .num_parents = 2,
+ .flags = CLK_SET_RATE_GATE | CLK_SET_RATE_PARENT,
+ },
+};
+
+static struct clk_regmap cpu_fsource_sel1 = {
+ .data = &(struct clk_regmap_mux_data) {
+ .offset = CPUCTRL_CLK_CTRL0,
+ .mask = 0x3,
+ .shift = 16,
+ .table = cpu_fsource_sel_table,
+ },
+ .hw.init = &(struct clk_init_data) {
+ .name = "cpu_fsource_sel1",
+ .ops = &clk_regmap_mux_ops,
+ .parent_data = cpu_fsource_sel_parents,
+ .num_parents = ARRAY_SIZE(cpu_fsource_sel_parents),
+ .flags = CLK_SET_RATE_PARENT,
+ },
+};
+
+static struct clk_regmap cpu_fsource_div1 = {
+ .data = &(struct clk_regmap_div_data) {
+ .offset = CPUCTRL_CLK_CTRL0,
+ .shift = 20,
+ .width = 6,
+ },
+ .hw.init = &(struct clk_init_data) {
+ .name = "cpu_fsource_div1",
+ .ops = &clk_regmap_divider_ops,
+ .parent_hws = (const struct clk_hw *[]) {
+ &cpu_fsource_sel1.hw
+ },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ },
+};
+
+static struct clk_regmap cpu_fsel1 = {
+ .data = &(struct clk_regmap_mux_data) {
+ .offset = CPUCTRL_CLK_CTRL0,
+ .mask = 0x1,
+ .shift = 18,
+ },
+ .hw.init = &(struct clk_init_data) {
+ .name = "cpu_fsel1",
+ .ops = &clk_regmap_mux_ops,
+ .parent_hws = (const struct clk_hw *[]) {
+ &cpu_fsource_sel1.hw,
+ &cpu_fsource_div1.hw,
+ },
+ .num_parents = 2,
+ .flags = CLK_SET_RATE_GATE | CLK_SET_RATE_PARENT,
+ },
+};
+
+static struct clk_regmap cpu_fclk = {
+ .data = &(struct clk_regmap_mux_data) {
+ .offset = CPUCTRL_CLK_CTRL0,
+ .mask = 0x1,
+ .shift = 10,
+ },
+ .hw.init = &(struct clk_init_data) {
+ .name = "cpu_fclk",
+ .ops = &clk_regmap_mux_ops,
+ .parent_hws = (const struct clk_hw *[]) {
+ &cpu_fsel0.hw,
+ &cpu_fsel1.hw,
+ },
+ .num_parents = 2,
+ .flags = CLK_SET_RATE_PARENT,
+ },
+};
+
+static struct clk_regmap cpu_clk = {
+ .data = &(struct clk_regmap_mux_data) {
+ .offset = CPUCTRL_CLK_CTRL0,
+ .mask = 0x1,
+ .shift = 11,
+ },
+ .hw.init = &(struct clk_init_data) {
+ .name = "cpu_clk",
+ .ops = &clk_regmap_mux_ops,
+ .parent_data = (const struct clk_parent_data []) {
+ { .hw = &cpu_fclk.hw },
+ { .fw_name = "sys_pll", },
+ },
+ .num_parents = 2,
+ .flags = CLK_SET_RATE_PARENT | CLK_IS_CRITICAL,
+ },
+};
+
+/* Array of all clocks registered by this provider */
+static struct clk_hw *a1_cpu_hw_clks[] = {
+ [CLKID_CPU_FSOURCE_SEL0] = &cpu_fsource_sel0.hw,
+ [CLKID_CPU_FSOURCE_DIV0] = &cpu_fsource_div0.hw,
+ [CLKID_CPU_FSEL0] = &cpu_fsel0.hw,
+ [CLKID_CPU_FSOURCE_SEL1] = &cpu_fsource_sel1.hw,
+ [CLKID_CPU_FSOURCE_DIV1] = &cpu_fsource_div1.hw,
+ [CLKID_CPU_FSEL1] = &cpu_fsel1.hw,
+ [CLKID_CPU_FCLK] = &cpu_fclk.hw,
+ [CLKID_CPU_CLK] = &cpu_clk.hw,
+};
+
+static struct clk_regmap *const a1_cpu_regmaps[] = {
+ &cpu_fsource_sel0,
+ &cpu_fsource_div0,
+ &cpu_fsel0,
+ &cpu_fsource_sel1,
+ &cpu_fsource_div1,
+ &cpu_fsel1,
+ &cpu_fclk,
+ &cpu_clk,
+};
+
+static struct regmap_config a1_cpu_regmap_cfg = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = CPUCTRL_CLK_CTRL1,
+};
+
+static struct meson_clk_hw_data a1_cpu_clks = {
+ .hws = a1_cpu_hw_clks,
+ .num = ARRAY_SIZE(a1_cpu_hw_clks),
+};
+
+struct a1_sys_pll_nb_data {
+ struct notifier_block nb;
+ struct clk_hw *cpu_clk;
+ struct clk_hw *cpu_fclk;
+ struct clk *sys_pll;
+};
+
+static int meson_a1_sys_pll_notifier_cb(struct notifier_block *nb,
+ unsigned long event, void *data)
+{
+ struct a1_sys_pll_nb_data *nbd;
+ int ret = 0;
+
+ nbd = container_of(nb, struct a1_sys_pll_nb_data, nb);
+
+ switch (event) {
+ case PRE_RATE_CHANGE:
+ /*
+ * Clock sys_pll will be changed to feed cpu_clk,
+ * configure cpu_clk to use cpu_fclk fixed clock.
+ */
+ ret = clk_hw_set_parent(nbd->cpu_clk, nbd->cpu_fclk);
+
+ /* Wait for clock propagation */
+ if (!ret)
+ udelay(100);
+
+ break;
+
+ case POST_RATE_CHANGE:
+ /*
+ * Clock sys_pll rate has ben calculated,
+ * switch back cpu_clk to sys_pll
+ */
+ ret = clk_set_parent(nbd->cpu_clk->clk, nbd->sys_pll);
+
+ /* Wait for clock propagation */
+ if (!ret)
+ udelay(100);
+ break;
+
+ default:
+ pr_warn("Unknown event %lu for sys_pll notifier\n", event);
+ break;
+ }
+
+ return notifier_from_errno(ret);
+}
+
+static struct a1_sys_pll_nb_data a1_sys_pll_nb_data = {
+ .nb.notifier_call = meson_a1_sys_pll_notifier_cb,
+ .cpu_clk = &cpu_clk.hw,
+ .cpu_fclk = &cpu_fclk.hw,
+};
+
+static int meson_a1_dvfs_setup(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct clk *sys_pll;
+ int ret;
+
+ /* Setup clock notifier for sys_pll clk */
+ sys_pll = devm_clk_get(dev, "sys_pll");
+ if (IS_ERR(sys_pll))
+ return dev_err_probe(dev, PTR_ERR(sys_pll),
+ "can't get sys_pll as notifier clock\n");
+
+ a1_sys_pll_nb_data.sys_pll = sys_pll;
+ ret = devm_clk_notifier_register(dev, sys_pll,
+ &a1_sys_pll_nb_data.nb);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "can't register sys_pll notifier\n");
+
+ return ret;
+}
+
+static int meson_a1_cpu_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ void __iomem *base;
+ struct regmap *map;
+ int clkid, i, err;
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return dev_err_probe(dev, PTR_ERR(base),
+ "can't ioremap resource\n");
+
+ map = devm_regmap_init_mmio(dev, base, &a1_cpu_regmap_cfg);
+ if (IS_ERR(map))
+ return dev_err_probe(dev, PTR_ERR(map),
+ "can't init regmap mmio region\n");
+
+ /* Populate regmap for the regmap backed clocks */
+ for (i = 0; i < ARRAY_SIZE(a1_cpu_regmaps); i++)
+ a1_cpu_regmaps[i]->map = map;
+
+ for (clkid = 0; clkid < a1_cpu_clks.num; clkid++) {
+ err = devm_clk_hw_register(dev, a1_cpu_clks.hws[clkid]);
+ if (err)
+ return dev_err_probe(dev, err,
+ "clock[%d] registration failed\n",
+ clkid);
+ }
+
+ err = devm_of_clk_add_hw_provider(dev, meson_clk_hw_get, &a1_cpu_clks);
+ if (err)
+ return dev_err_probe(dev, err, "can't add clk hw provider\n");
+
+ return meson_a1_dvfs_setup(pdev);
+}
+
+static const struct of_device_id a1_cpu_clkc_match_table[] = {
+ { .compatible = "amlogic,a1-cpu-clkc", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, a1_cpu_clkc_match_table);
+
+static struct platform_driver a1_cpu_clkc_driver = {
+ .probe = meson_a1_cpu_probe,
+ .driver = {
+ .name = "a1-cpu-clkc",
+ .of_match_table = a1_cpu_clkc_match_table,
+ },
+};
+
+module_platform_driver(a1_cpu_clkc_driver);
+MODULE_AUTHOR("Dmitry Rokosov <ddrokosov@salutedevices.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/meson/a1-peripherals.c b/drivers/clk/meson/a1-peripherals.c
index 5e0d58c01405c..a814c62637118 100644
--- a/drivers/clk/meson/a1-peripherals.c
+++ b/drivers/clk/meson/a1-peripherals.c
@@ -775,14 +775,27 @@ static struct clk_regmap a1_fclk_div2_divn = {
},
};
+static struct clk_fixed_factor a1_sys_pll_div16 = {
+ .mult = 1,
+ .div = 16,
+ .hw.init = &(struct clk_init_data){
+ .name = "sys_pll_div16",
+ .ops = &clk_fixed_factor_ops,
+ .parent_data = &(const struct clk_parent_data) {
+ .fw_name = "sys_pll",
+ },
+ .num_parents = 1,
+ },
+};
+
/*
- * the index 2 is sys_pll_div16, it will be implemented in the CPU clock driver,
* the index 4 is the clock measurement source, it's not supported yet
*/
-static u32 a1_gen_parents_val_table[] = { 0, 1, 3, 5, 6, 7, 8 };
+static u32 a1_gen_parents_val_table[] = { 0, 1, 2, 3, 5, 6, 7, 8 };
static const struct clk_parent_data a1_gen_parents[] = {
{ .fw_name = "xtal", },
{ .hw = &a1_rtc.hw },
+ { .hw = &a1_sys_pll_div16.hw, },
{ .fw_name = "hifi_pll", },
{ .fw_name = "fclk_div2", },
{ .fw_name = "fclk_div3", },
@@ -2066,6 +2079,7 @@ static struct clk_hw *a1_peripherals_hw_clks[] = {
[CLKID_DMC_SEL] = &a1_dmc_sel.hw,
[CLKID_DMC_DIV] = &a1_dmc_div.hw,
[CLKID_DMC_SEL2] = &a1_dmc_sel2.hw,
+ [CLKID_SYS_PLL_DIV16] = &a1_sys_pll_div16.hw,
};
static const struct meson_clkc_data a1_peripherals_clkc_data = {
diff --git a/drivers/clk/meson/a1-pll.c b/drivers/clk/meson/a1-pll.c
index 1f82e9c7c14eb..ea6bddad2c97f 100644
--- a/drivers/clk/meson/a1-pll.c
+++ b/drivers/clk/meson/a1-pll.c
@@ -17,6 +17,12 @@
#define ANACTRL_FIXPLL_CTRL0 0x0
#define ANACTRL_FIXPLL_CTRL1 0x4
#define ANACTRL_FIXPLL_STS 0x14
+#define ANACTRL_SYSPLL_CTRL0 0x80
+#define ANACTRL_SYSPLL_CTRL1 0x84
+#define ANACTRL_SYSPLL_CTRL2 0x88
+#define ANACTRL_SYSPLL_CTRL3 0x8c
+#define ANACTRL_SYSPLL_CTRL4 0x90
+#define ANACTRL_SYSPLL_STS 0x94
#define ANACTRL_HIFIPLL_CTRL0 0xc0
#define ANACTRL_HIFIPLL_CTRL1 0xc4
#define ANACTRL_HIFIPLL_CTRL2 0xc8
@@ -148,6 +154,76 @@ static struct clk_regmap a1_hifi_pll = {
},
};
+static const struct pll_mult_range a1_sys_pll_mult_range = {
+ .min = 32,
+ .max = 64,
+};
+
+static const struct reg_sequence a1_sys_pll_init_regs[] = {
+ { .reg = ANACTRL_SYSPLL_CTRL1, .def = 0x01800000 },
+ { .reg = ANACTRL_SYSPLL_CTRL2, .def = 0x00001100 },
+ { .reg = ANACTRL_SYSPLL_CTRL3, .def = 0x10022300 },
+ { .reg = ANACTRL_SYSPLL_CTRL4, .def = 0x00300000 },
+ { .reg = ANACTRL_SYSPLL_CTRL0, .def = 0x01f18432 },
+};
+
+static struct clk_regmap a1_sys_pll = {
+ .data = &(struct meson_clk_pll_data){
+ .en = {
+ .reg_off = ANACTRL_SYSPLL_CTRL0,
+ .shift = 28,
+ .width = 1,
+ },
+ .m = {
+ .reg_off = ANACTRL_SYSPLL_CTRL0,
+ .shift = 0,
+ .width = 8,
+ },
+ .n = {
+ .reg_off = ANACTRL_SYSPLL_CTRL0,
+ .shift = 10,
+ .width = 5,
+ },
+ .frac = {
+ .reg_off = ANACTRL_SYSPLL_CTRL1,
+ .shift = 0,
+ .width = 19,
+ },
+ .l = {
+ .reg_off = ANACTRL_SYSPLL_STS,
+ .shift = 31,
+ .width = 1,
+ },
+ .current_en = {
+ .reg_off = ANACTRL_SYSPLL_CTRL0,
+ .shift = 26,
+ .width = 1,
+ },
+ .l_detect = {
+ .reg_off = ANACTRL_SYSPLL_CTRL2,
+ .shift = 6,
+ .width = 1,
+ },
+ .range = &a1_sys_pll_mult_range,
+ .init_regs = a1_sys_pll_init_regs,
+ .init_count = ARRAY_SIZE(a1_sys_pll_init_regs),
+ /*
+ * The sys_pll clock is usually enabled and initialized in the
+ * bootloader stage. Additionally, the cpu_clk is connected to
+ * sys_pll. As a result, it is not allowed to initialize the
+ * cpu_clk again, as doing so would prevent the CPU from
+ * executing any instructions.
+ */
+ .flags = CLK_MESON_PLL_NOINIT_ENABLED,
+ },
+ .hw.init = &(struct clk_init_data){
+ .name = "sys_pll",
+ .ops = &meson_clk_pll_ops,
+ .parent_names = (const char *[]){ "syspll_in" },
+ .num_parents = 1,
+ },
+};
+
static struct clk_fixed_factor a1_fclk_div2_div = {
.mult = 1,
.div = 2,
@@ -293,6 +369,7 @@ static struct clk_hw *a1_pll_hw_clks[] = {
[CLKID_FCLK_DIV5] = &a1_fclk_div5.hw,
[CLKID_FCLK_DIV7] = &a1_fclk_div7.hw,
[CLKID_HIFI_PLL] = &a1_hifi_pll.hw,
+ [CLKID_SYS_PLL] = &a1_sys_pll.hw,
};
static const struct meson_clkc_data a1_pll_clkc_data = {
diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
index ac1408d374c92..0737a886de8d2 100644
--- a/drivers/iio/light/Kconfig
+++ b/drivers/iio/light/Kconfig
@@ -339,6 +339,19 @@ config HID_SENSOR_PROX
To compile this driver as a module, choose M here: the
module will be called hid-sensor-prox.
+config HX3203
+ tristate "Nanjing TianYiHeXin Electronics HX3203 Ambient Light Sensor"
+ depends on I2C
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+ select REGMAP_I2C
+ help
+ Say yes here to build support for the Nanjing TianYiHeXin Electronics
+ HX3203 Ambient Light Sensor driver.
+
+ To compile this driver as a module, choose M here: the module will be
+ called hx3203.
+
config JSA1212
tristate "JSA1212 ALS and proximity sensor driver"
depends on I2C
diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile
index c0048e0d5ca8f..935e1ac5a73f4 100644
--- a/drivers/iio/light/Makefile
+++ b/drivers/iio/light/Makefile
@@ -28,6 +28,7 @@ obj-$(CONFIG_GP2AP002) += gp2ap002.o
obj-$(CONFIG_GP2AP020A00F) += gp2ap020a00f.o
obj-$(CONFIG_HID_SENSOR_ALS) += hid-sensor-als.o
obj-$(CONFIG_HID_SENSOR_PROX) += hid-sensor-prox.o
+obj-$(CONFIG_HX3203) += hx3203.o
obj-$(CONFIG_IQS621_ALS) += iqs621-als.o
obj-$(CONFIG_SENSORS_ISL29018) += isl29018.o
obj-$(CONFIG_SENSORS_ISL29028) += isl29028.o
diff --git a/drivers/iio/light/hx3203.c b/drivers/iio/light/hx3203.c
new file mode 100644
index 0000000000000..5e33b61c04fc3
--- /dev/null
+++ b/drivers/iio/light/hx3203.c
@@ -0,0 +1,1462 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nanjing TianYiHeXin Electronics HX3203 Ambient Light Sensor
+ *
+ * HX3203 is an optical sensor which measures the ambient light intensity
+ * (ALS).
+ * The sensor incorporates with a fast, two-wire i2c bus (up to 800kHz) to
+ * communicate with a microcontroller or embedded controller.
+ *
+ * Ambient light sensor (ALS) utilizes photodiodes with spectral close to
+ * the human eye response. The amplified photodiode current is converted to
+ * an integrated ADC with adjustable resolution. Then the converted result
+ * is transferred to the data register which can be read by
+ * a microprocessor.
+ *
+ * The device also has a dedicated pin for level-style interrupts. When
+ * interrupts are enabled and a preset value is exceeded, the interrupt pin
+ * is asserted and remains asserted until cleared by the controller.
+ * An interrupt is generated when the value of ALS exceeds either an upper
+ * or lower threshold. In addition, it has the feature of programmable
+ * interrupt persistence, which allows the user to prevent false trigger.
+ *
+ * Copyright (c) 2023, SberDevices. All Rights Reserved.
+ *
+ * Author: Dmitry Rokosov <ddrokosov@sberdevices.ru>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/i2c.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+
+#include <linux/iio/buffer.h>
+#include <linux/iio/events.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+
+#define HX3203_ID_REG 0x00
+#define HX3203_ENABLE_REG 0x01
+#define HX3203_INTERRUPT_REG 0x02
+#define HX3203_AILTM_REG 0x05
+#define HX3203_AIXTX_REG 0x06
+#define HX3203_AIHTM_REG 0x07
+#define HX3203_C1DATAM_REG 0x08
+#define HX3203_C0DATAM_REG 0x09
+#define HX3203_C0DATAH_REG 0x0A
+#define HX3203_AGAIN_REG 0x0B
+#define HX3203_PPULSE_REG 0x0C
+#define HX3203_C1DATAH_REG 0x0D
+#define HX3203_C1DATAL_REG 0x0E
+#define HX3203_C0DATAL_REG 0x0F
+#define HX3203_AIHTH_REG 0x10
+#define HX3203_AILTL_REG 0x11
+#define HX3203_RES_REG 0x16
+#define HX3203_EN2RST_DELAY_REG 0x1F
+#define HX3203_ALSMAXL_REG 0x80
+#define HX3203_ALSMAXH_REG 0x81
+
+enum hx3203_fields {
+ /* Enable */
+ F_AWT, F_AEN,
+ /* Interrupt */
+ F_AINT, F_APERS, F_INTC,
+ /* ALS Interrupt Threshold */
+ F_AILT0, F_AILT1, F_AILT2, F_AILT3,
+ F_AIHT0, F_AIHT1, F_AIHT2, F_AIHT3,
+ /* CH1 Data */
+ F_C1DATA0, F_C1DATA1, F_C1DATA2,
+ /* CH0 Data */
+ F_C0DATA0, F_C0DATA1, F_C0DATA2, F_C0DATA3,
+ /* ALS Gain Control */
+ F_AGAIN,
+ /* PON and PULSE set */
+ F_PON, F_WLONG, F_PPULSE,
+ /* Resolution */
+ F_ALS_RES,
+ /* Delay clock cycles in the ADC bus */
+ F_EN2RST_DELAY,
+ /* ALS max */
+ F_ALS_MAXL, F_ALS_MAXH,
+ /* End of register map */
+ F_MAX_FIELDS,
+};
+
+static const struct reg_field hx3203_reg_fields[] = {
+ /* Enable */
+ [F_AWT] = REG_FIELD(HX3203_ENABLE_REG, 4, 6),
+ [F_AEN] = REG_FIELD(HX3203_ENABLE_REG, 2, 2),
+ /* Interrupt */
+ [F_AINT] = REG_FIELD(HX3203_INTERRUPT_REG, 3, 3),
+ [F_APERS] = REG_FIELD(HX3203_INTERRUPT_REG, 1, 2),
+ [F_INTC] = REG_FIELD(HX3203_INTERRUPT_REG, 0, 0),
+ /* ALS Interrupt Threshold */
+ [F_AILT0] = REG_FIELD(HX3203_AILTL_REG, 0, 3),
+ [F_AILT1] = REG_FIELD(HX3203_AILTM_REG, 0, 7),
+ [F_AILT2] = REG_FIELD(HX3203_AIXTX_REG, 0, 3),
+ [F_AILT3] = REG_FIELD(HX3203_AILTL_REG, 4, 5),
+ [F_AIHT0] = REG_FIELD(HX3203_AIHTH_REG, 0, 3),
+ [F_AIHT1] = REG_FIELD(HX3203_AIXTX_REG, 4, 7),
+ [F_AIHT2] = REG_FIELD(HX3203_AIHTM_REG, 0, 7),
+ [F_AIHT3] = REG_FIELD(HX3203_AIHTH_REG, 4, 5),
+ /* CH0 Data */
+ [F_C0DATA0] = REG_FIELD(HX3203_C0DATAL_REG, 0, 3),
+ [F_C0DATA1] = REG_FIELD(HX3203_C0DATAH_REG, 0, 3),
+ [F_C0DATA2] = REG_FIELD(HX3203_C0DATAM_REG, 0, 7),
+ [F_C0DATA3] = REG_FIELD(HX3203_C0DATAL_REG, 4, 5),
+ /* CH1 Data */
+ [F_C1DATA0] = REG_FIELD(HX3203_C1DATAL_REG, 0, 2),
+ [F_C1DATA1] = REG_FIELD(HX3203_C1DATAM_REG, 0, 7),
+ [F_C1DATA2] = REG_FIELD(HX3203_C1DATAH_REG, 0, 6),
+ /* ALS Gain Control */
+ [F_AGAIN] = REG_FIELD(HX3203_AGAIN_REG, 0, 2),
+ /* PON and PULSE set */
+ [F_PON] = REG_FIELD(HX3203_PPULSE_REG, 5, 5),
+ [F_WLONG] = REG_FIELD(HX3203_PPULSE_REG, 4, 4),
+ [F_PPULSE] = REG_FIELD(HX3203_PPULSE_REG, 0, 3),
+ /* Resolution */
+ [F_ALS_RES] = REG_FIELD(HX3203_RES_REG, 0, 3),
+ /* Delay clock cycles in the ADC bus */
+ [F_EN2RST_DELAY] = REG_FIELD(HX3203_EN2RST_DELAY_REG, 0, 6),
+ /* ALS max */
+ [F_ALS_MAXL] = REG_FIELD(HX3203_ALSMAXL_REG, 4, 7),
+ [F_ALS_MAXH] = REG_FIELD(HX3203_ALSMAXH_REG, 0, 1),
+};
+
+/* Proximity/ALS wait time between each conversion cycle */
+enum {
+ HX3203_AWT_800_MS,
+ HX3203_AWT_400_MS,
+ HX3203_AWT_200_MS,
+ HX3203_AWT_100_MS,
+ HX3203_AWT_75_MS,
+ HX3203_AWT_50_MS,
+ HX3203_AWT_12_5_MS,
+ HX3203_AWT_0_MS,
+};
+
+/* ALS ADC resolution. Generate TSEL to analog in ALS mode */
+enum {
+ HX3203_ALS_RES_8_BITS,
+ HX3203_ALS_RES_9_BITS,
+ HX3203_ALS_RES_10_BITS,
+ HX3203_ALS_RES_11_BITS,
+ HX3203_ALS_RES_12_BITS,
+ HX3203_ALS_RES_13_BITS,
+ HX3203_ALS_RES_14_BITS,
+ HX3203_ALS_RES_15_BITS,
+ HX3203_ALS_RES_16_BITS,
+ HX3203_ALS_RES_17_BITS,
+ HX3203_ALS_RES_18_BITS,
+};
+
+/* ALS Gain control */
+enum {
+ HX3203_AGAIN_1_X,
+ HX3203_AGAIN_2_X,
+ HX3203_AGAIN_4_X,
+ HX3203_AGAIN_8_X,
+ HX3203_AGAIN_64_X,
+};
+
+struct iio_decimal_fract {
+ int integral;
+ int microfract;
+};
+
+static const int hx3203_again_table[] = { 1, 2, 4, 8, 64 };
+
+static const struct iio_decimal_fract hx3203_wait_time_table[] = {
+ {800, 0}, {400, 0}, {200, 0}, {100, 0},
+ {75, 0}, {50, 0}, {12, 500000}, {0, 0},
+};
+
+/* CH0 Data */
+#define HX3203_C0DATA0 GENMASK(3, 0)
+#define HX3203_C0DATA1 GENMASK(7, 4)
+#define HX3203_C0DATA2 GENMASK(15, 8)
+#define HX3203_C0DATA3 GENMASK(17, 16)
+
+/* CH1 Data */
+#define HX3203_C1DATA0 GENMASK(2, 0)
+#define HX3203_C1DATA1 GENMASK(10, 3)
+#define HX3203_C1DATA2 GENMASK(17, 11)
+
+/* ALS Low Threshold */
+#define HX3203_AILT0 GENMASK(3, 0)
+#define HX3203_AILT1 GENMASK(11, 4)
+#define HX3203_AILT2 GENMASK(15, 12)
+#define HX3203_AILT3 GENMASK(17, 16)
+
+/* ALS High Threshold */
+#define HX3203_AIHT0 GENMASK(3, 0)
+#define HX3203_AIHT1 GENMASK(7, 4)
+#define HX3203_AIHT2 GENMASK(15, 8)
+#define HX3203_AIHT3 GENMASK(17, 16)
+
+/* ALS Max */
+#define HX3203_ALS_MAXL GENMASK(3, 0)
+#define HX3203_ALS_MAXH BIT(4)
+
+#define HX3203_WHO_AM_I 0x21
+
+/* Possible ALS Interrupt persistence values */
+enum {
+ HX3203_APERS_ONE_ALS_VAL_OOR,
+ HX3203_APERS_FOUR_ALS_VAL_OOR,
+ HX3203_APERS_EIGHT_ALS_VAL_OOR,
+ HX3203_APERS_SIXTEEN_ALS_VAL_OOR,
+};
+
+static const int hx3203_apers_table[] = { 1, 4, 8, 16 };
+
+static const struct regmap_range hx3203_readonly_registers[] = {
+ regmap_reg_range(HX3203_ID_REG, HX3203_ID_REG),
+ regmap_reg_range(HX3203_C1DATAM_REG, HX3203_C0DATAH_REG),
+ regmap_reg_range(HX3203_C1DATAH_REG, HX3203_C0DATAL_REG),
+};
+
+static const struct regmap_access_table hx3203_writable_table = {
+ .no_ranges = hx3203_readonly_registers,
+ .n_no_ranges = ARRAY_SIZE(hx3203_readonly_registers),
+};
+
+static const struct regmap_range hx3203_volatile_registers[] = {
+ regmap_reg_range(HX3203_INTERRUPT_REG, HX3203_INTERRUPT_REG),
+ regmap_reg_range(HX3203_C1DATAM_REG, HX3203_C0DATAH_REG),
+ regmap_reg_range(HX3203_C1DATAH_REG, HX3203_C0DATAL_REG),
+};
+
+static const struct regmap_access_table hx3203_volatile_table = {
+ .yes_ranges = hx3203_volatile_registers,
+ .n_yes_ranges = ARRAY_SIZE(hx3203_volatile_registers),
+};
+
+static const struct regmap_config hx3203_regmap_config = {
+ .name = "hx3203",
+ .reg_bits = 8,
+ .val_bits = 8,
+ .disable_locking = true,
+ .max_register = HX3203_ALSMAXH_REG,
+ .wr_table = &hx3203_writable_table,
+ .volatile_table = &hx3203_volatile_table,
+ .cache_type = REGCACHE_RBTREE,
+};
+
+/**
+ * struct hx3203_priv - HX3203 internal private state
+ * @regs: Underlying I2C bus adapter used to abstract slave
+ * register accesses
+ * @fields: Abstract objects for each registers fields access
+ * @dev: Device handler associated with appropriate bus client
+ * @vdd: Input voltage regulator
+ * @aint_enabled: ALS interrupt enabled or disabled
+ * @lock: Protects hx3203 device state between setup and data access
+ */
+struct hx3203_priv {
+ struct regmap *regs;
+ struct regmap_field *fields[F_MAX_FIELDS];
+
+ struct device *dev;
+ struct regulator *vdd;
+
+ bool aint_enabled;
+
+ struct mutex lock;
+};
+
+enum hx3203_si {
+ HX3203_SI_LIGHT_BOTH,
+ HX3203_SI_LIGHT_IR,
+ HX3203_SI_LIGHT_LUX,
+ HX3203_SI_TIMESTAMP,
+};
+
+#define HX3203_THRESHOLD_EVENT(direction, mask) { \
+ .type = IIO_EV_TYPE_THRESH, \
+ .dir = IIO_EV_DIR_##direction, \
+ .mask_separate = mask, \
+}
+
+static const struct iio_event_spec hx3203_events[] = {
+ HX3203_THRESHOLD_EVENT(RISING, BIT(IIO_EV_INFO_VALUE)),
+ HX3203_THRESHOLD_EVENT(FALLING, BIT(IIO_EV_INFO_VALUE)),
+ HX3203_THRESHOLD_EVENT(EITHER, BIT(IIO_EV_INFO_ENABLE) |
+ BIT(IIO_EV_INFO_PERIOD)),
+};
+
+#define HX3203_INTENSITY_CHANNEL(_light, _events, _num_event_specs) { \
+ .type = IIO_INTENSITY, \
+ .channel2 = IIO_MOD_##_light, \
+ .scan_index = HX3203_SI_##_light, \
+ .scan_type = { \
+ .sign = 'u', \
+ .realbits = 18, \
+ .storagebits = 32, \
+ .endianness = IIO_CPU, \
+ }, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_INT_TIME), \
+ .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_INT_TIME), \
+ .event_spec = _events, \
+ .num_event_specs = _num_event_specs, \
+ .modified = 1, \
+}
+
+#define HX3203_LIGHT_CHANNEL() { \
+ .type = IIO_LIGHT, \
+ .scan_index = HX3203_SI_LIGHT_LUX, \
+ .scan_type = { \
+ .sign = 'u', \
+ .realbits = 18, \
+ .storagebits = 32, \
+ .endianness = IIO_CPU, \
+ }, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_INT_TIME), \
+ .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_INT_TIME), \
+ .indexed = 1, \
+}
+
+static const struct iio_chan_spec hx3203_channels[] = {
+ HX3203_INTENSITY_CHANNEL(LIGHT_BOTH, hx3203_events,
+ ARRAY_SIZE(hx3203_events)),
+ HX3203_INTENSITY_CHANNEL(LIGHT_IR, NULL, 0),
+ HX3203_LIGHT_CHANNEL(),
+ IIO_CHAN_SOFT_TIMESTAMP(HX3203_SI_TIMESTAMP),
+};
+
+#define HX3203_GENMASK(field) ({ \
+ typeof(&(hx3203_reg_fields)[0]) _field; \
+ _field = &hx3203_reg_fields[(field)]; \
+ GENMASK(_field->msb, _field->lsb); \
+})
+
+#define HX3203_REG(field) ({ \
+ typeof(&(hx3203_reg_fields)[0]) _field; \
+ _field = &hx3203_reg_fields[(field)]; \
+ _field->reg; \
+})
+
+#define HX3203_CDATA_BULK(reg) ((reg) - HX3203_C1DATAM_REG)
+
+#define HX3203_FIELD_GET(_mask, _reg) ({ \
+ (typeof(_mask))(((_reg) & (_mask)) >> __bf_shf(_mask)); \
+})
+
+#define HX3203_CH_PREP(als, ch) ({ \
+ typeof((als)[0]) _fval; \
+ _fval = HX3203_FIELD_GET(HX3203_GENMASK(F_##ch), \
+ (als)[HX3203_CDATA_BULK(HX3203_REG(F_##ch))]); \
+ FIELD_PREP(HX3203_##ch, _fval); \
+})
+
+#define HX3203_WRITE_AIXTX(hx3203, thd, aixt) ({ \
+ regmap_field_write((hx3203)->fields[F_##aixt], \
+ HX3203_FIELD_GET(HX3203_##aixt, thd)); \
+})
+
+#define HX3203_WRITE_AIXT(hx3203, thd, lvl) ({ \
+ int _err; \
+ bool _all = !(_err = HX3203_WRITE_AIXTX(hx3203, thd, AI##lvl##T0)) && \
+ !(_err = HX3203_WRITE_AIXTX(hx3203, thd, AI##lvl##T1)) && \
+ !(_err = HX3203_WRITE_AIXTX(hx3203, thd, AI##lvl##T2)) && \
+ !(_err = HX3203_WRITE_AIXTX(hx3203, thd, AI##lvl##T3)); \
+ _all ? 0 : _err; \
+})
+
+#define HX3203_WRITE_AILT(hx3203, thd) HX3203_WRITE_AIXT(hx3203, thd, L)
+#define HX3203_WRITE_AIHT(hx3203, thd) HX3203_WRITE_AIXT(hx3203, thd, H)
+
+#define HX3203_READ_AIXTX(hx3203, thd, aixt) ({ \
+ unsigned int _thd; \
+ int _err; \
+ _err = regmap_field_read((hx3203)->fields[F_##aixt], &_thd); \
+ if (!_err) \
+ *(thd) |= FIELD_PREP(HX3203_##aixt, _thd); \
+ _err; \
+})
+
+#define HX3203_READ_AIXT(hx3203, thd, lvl) ({ \
+ int _err; \
+ bool _all = !(_err = HX3203_READ_AIXTX(hx3203, thd, AI##lvl##T0)) && \
+ !(_err = HX3203_READ_AIXTX(hx3203, thd, AI##lvl##T1)) && \
+ !(_err = HX3203_READ_AIXTX(hx3203, thd, AI##lvl##T2)) && \
+ !(_err = HX3203_READ_AIXTX(hx3203, thd, AI##lvl##T3)); \
+ _all ? 0 : _err; \
+})
+
+#define HX3203_READ_AILT(hx3203, thd) HX3203_READ_AIXT(hx3203, thd, L)
+#define HX3203_READ_AIHT(hx3203, thd) HX3203_READ_AIXT(hx3203, thd, H)
+
+static int hx3203_powerup(struct hx3203_priv *hx3203)
+{
+ return regmap_field_write(hx3203->fields[F_PON], 1);
+}
+
+static int hx3203_powerdown(struct hx3203_priv *hx3203)
+{
+ return regmap_field_write(hx3203->fields[F_PON], 0);
+}
+
+static int hx3203_enable_als(struct hx3203_priv *hx3203)
+{
+ return regmap_field_write(hx3203->fields[F_AEN], 1);
+}
+
+static int hx3203_disable_als(struct hx3203_priv *hx3203)
+{
+ return regmap_field_write(hx3203->fields[F_AEN], 0);
+}
+
+static void hx3203_enable_aint(struct hx3203_priv *hx3203)
+{
+ struct i2c_client *i2c = to_i2c_client(hx3203->dev);
+
+ if (!hx3203->aint_enabled) {
+ hx3203->aint_enabled = true;
+ enable_irq(i2c->irq);
+ }
+}
+
+static void hx3203_disable_aint(struct hx3203_priv *hx3203)
+{
+ struct i2c_client *i2c = to_i2c_client(hx3203->dev);
+
+ if (hx3203->aint_enabled) {
+ disable_irq(i2c->irq);
+ hx3203->aint_enabled = false;
+ }
+}
+
+static int hx3203_get_chs(struct hx3203_priv *hx3203, uint32_t chs[2])
+{
+ struct device *dev = hx3203->dev;
+ uint8_t als[HX3203_CDATA_BULK(HX3203_C0DATAL_REG) + 1];
+ int err;
+
+ err = regmap_bulk_read(hx3203->regs, HX3203_C1DATAM_REG,
+ als, sizeof(als));
+ if (err) {
+ dev_err(dev, "can't get channels data (%d)\n", err);
+ return err;
+ }
+
+ /* CH0 Data */
+ chs[0] = HX3203_CH_PREP(als, C0DATA0) |
+ HX3203_CH_PREP(als, C0DATA1) |
+ HX3203_CH_PREP(als, C0DATA2) |
+ HX3203_CH_PREP(als, C0DATA3);
+
+ /* CH1 Data */
+ chs[1] = HX3203_CH_PREP(als, C1DATA0) |
+ HX3203_CH_PREP(als, C1DATA1) |
+ HX3203_CH_PREP(als, C1DATA2);
+
+ return 0;
+}
+
+static int hx3203_get_als_max(struct hx3203_priv *hx3203, uint8_t *als_max)
+{
+ struct device *dev = hx3203->dev;
+ unsigned int als_maxl, als_maxh;
+ int err;
+
+ err = regmap_field_read(hx3203->fields[F_ALS_MAXL], &als_maxl);
+ if (!err)
+ err = regmap_field_read(hx3203->fields[F_ALS_MAXH], &als_maxh);
+
+ if (err) {
+ dev_err(dev, "can't read ALS_MAX fields (%d)\n", err);
+ return err;
+ }
+
+ *als_max = FIELD_PREP(HX3203_ALS_MAXL, als_maxl) |
+ FIELD_PREP(HX3203_ALS_MAXH, als_maxh);
+
+ return 0;
+}
+
+static int hx3203_get_lux(struct hx3203_priv *hx3203, uint32_t *lux)
+{
+ struct device *dev = hx3203->dev;
+ uint32_t chs[2];
+ uint8_t als_max;
+ int err;
+
+ err = hx3203_get_chs(hx3203, chs);
+ if (err) {
+ dev_err(dev, "can't get channels data (%d)\n", err);
+ return err;
+ }
+
+ err = hx3203_get_als_max(hx3203, &als_max);
+ if (err) {
+ dev_err(dev, "can't read ALS_MAX value (%d)\n", err);
+ return err;
+ }
+
+ /*
+ * Subtract the two channels values to make spectroscopic
+ * performance better.
+ * 1.45 is the scale factor of the infrared
+ */
+ chs[1] = chs[1] * 145 / 100 + als_max;
+ *lux = chs[0] > chs[1] ? chs[0] - chs[1] : 0;
+
+ return 0;
+}
+
+static int hx3203_get_als(struct hx3203_priv *hx3203,
+ struct iio_chan_spec const *chan, uint32_t *als)
+{
+ struct device *dev = hx3203->dev;
+ int err;
+
+ switch (chan->scan_index) {
+ case HX3203_SI_LIGHT_BOTH:
+ case HX3203_SI_LIGHT_IR: {
+ uint32_t chs[2];
+
+ err = hx3203_get_chs(hx3203, chs);
+ if (!err)
+ *als = chs[chan->scan_index];
+ else
+ dev_err(dev, "can't get chs data (%d)\n", err);
+
+ return err;
+ }
+
+ case HX3203_SI_LIGHT_LUX:
+ err = hx3203_get_lux(hx3203, als);
+ if (err)
+ dev_err(dev, "can't get lux data (%d)\n", err);
+
+ return err;
+
+ default:
+ dev_err(dev, "invalid channel index [%d]\n", chan->scan_index);
+
+ return -EINVAL;
+ }
+}
+
+static irqreturn_t hx3203_irq_thread(int irq, void *p)
+{
+ struct iio_dev *indio_dev = p;
+ struct hx3203_priv *hx3203 = iio_priv(indio_dev);
+ struct device *dev = hx3203->dev;
+ unsigned int aint;
+ int err;
+
+ mutex_lock(&hx3203->lock);
+ err = regmap_field_read(hx3203->fields[F_AINT], &aint);
+ /* Reset AINT status */
+ if (!err)
+ err = regmap_field_write(hx3203->fields[F_AINT], 0);
+ mutex_unlock(&hx3203->lock);
+
+ if (err) {
+ dev_err(dev, "can't read AINT interrupt state (%d)\n", err);
+ return IRQ_NONE;
+ }
+
+ if (aint)
+ iio_push_event(indio_dev,
+ IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, 0,
+ IIO_EV_TYPE_THRESH,
+ IIO_EV_DIR_EITHER),
+ iio_get_time_ns(indio_dev));
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t hx3203_buffer_thread(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct hx3203_priv *hx3203 = iio_priv(pf->indio_dev);
+ struct iio_dev *indio_dev = pf->indio_dev;
+ const struct iio_chan_spec *chan;
+ struct device *dev = hx3203->dev;
+ struct {
+ uint32_t channels[HX3203_SI_LIGHT_LUX + 1];
+ aligned_s64 ts;
+ } buf;
+ uint32_t als;
+ int bit, err, i = 0;
+
+ memset(&buf, 0, sizeof(buf));
+
+ mutex_lock(&hx3203->lock);
+
+ iio_for_each_active_channel(indio_dev, bit) {
+ chan = &hx3203_channels[bit];
+
+ err = hx3203_get_als(hx3203, chan, &als);
+ if (err) {
+ mutex_unlock(&hx3203->lock);
+ dev_err(dev, "can't get als (%d)\n", err);
+ goto notify_done;
+ }
+
+ buf.channels[i++] = als;
+ }
+
+ mutex_unlock(&hx3203->lock);
+
+ iio_push_to_buffers_with_timestamp(indio_dev, &buf,
+ iio_get_time_ns(indio_dev));
+
+notify_done:
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+static int hx3203_debugfs_reg_access(struct iio_dev *indio_dev,
+ unsigned int reg, unsigned int writeval,
+ unsigned int *readval)
+{
+ struct hx3203_priv *hx3203 = iio_priv(indio_dev);
+ struct device *dev = hx3203->dev;
+ int err;
+
+ if (reg > regmap_get_max_register(hx3203->regs))
+ return -EINVAL;
+
+ mutex_lock(&hx3203->lock);
+
+ if (readval)
+ err = regmap_read(hx3203->regs, reg, readval);
+ else
+ err = regmap_write(hx3203->regs, reg, writeval);
+
+ mutex_unlock(&hx3203->lock);
+
+ if (err)
+ dev_err(dev, "can't %s register %u from debugfs (%d)\n",
+ readval ? "read" : "write", reg, err);
+
+ return err;
+}
+
+static int hx3203_read_raw_data(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val)
+{
+ struct hx3203_priv *hx3203 = iio_priv(indio_dev);
+ struct device *dev = hx3203->dev;
+ uint32_t chs[2];
+ int err;
+
+ if (chan->type != IIO_INTENSITY) {
+ dev_err(dev, "invalid channel type %u\n", chan->type);
+ return -EINVAL;
+ }
+
+ if (!iio_device_claim_direct(indio_dev)) {
+ dev_err(dev, "can't claim direct mode\n");
+ return -EBUSY;
+ }
+
+ mutex_lock(&hx3203->lock);
+ err = hx3203_get_chs(hx3203, chs);
+ mutex_unlock(&hx3203->lock);
+
+ iio_device_release_direct(indio_dev);
+
+ if (err) {
+ dev_err(dev, "can't get channels data (%d)\n", err);
+ return err;
+ }
+
+ switch (chan->channel2) {
+ case IIO_MOD_LIGHT_BOTH:
+ *val = chs[0];
+ return IIO_VAL_INT;
+
+ case IIO_MOD_LIGHT_IR:
+ *val = chs[1];
+ return IIO_VAL_INT;
+
+ default:
+ dev_err(dev, "invalid channel modifier %d\n",
+ chan->channel2);
+ return -EINVAL;
+ }
+}
+
+static int hx3203_read_processed(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val)
+{
+ struct hx3203_priv *hx3203 = iio_priv(indio_dev);
+ struct device *dev = hx3203->dev;
+ int err;
+
+ if (chan->type != IIO_LIGHT) {
+ dev_err(dev, "invalid channel type %u\n", chan->type);
+ return -EINVAL;
+ }
+
+ if (!iio_device_claim_direct(indio_dev)) {
+ dev_err(dev, "can't claim direct mode\n");
+ return -EBUSY;
+ }
+
+ mutex_lock(&hx3203->lock);
+ err = hx3203_get_lux(hx3203, val);
+ mutex_unlock(&hx3203->lock);
+
+ iio_device_release_direct(indio_dev);
+
+ if (err) {
+ dev_err(dev, "can't get lux measurement (%d)\n", err);
+ return err;
+ }
+
+ return IIO_VAL_INT;
+}
+
+static int hx3203_read_scale(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val)
+{
+ struct hx3203_priv *hx3203 = iio_priv(indio_dev);
+ struct device *dev = hx3203->dev;
+ unsigned int again;
+ int err;
+
+ if (chan->type != IIO_INTENSITY && chan->type != IIO_LIGHT) {
+ dev_err(dev, "invalid channel type %u\n", chan->type);
+ return -EINVAL;
+ }
+
+ mutex_lock(&hx3203->lock);
+ err = regmap_field_read(hx3203->fields[F_AGAIN], &again);
+ mutex_unlock(&hx3203->lock);
+
+ if (err) {
+ dev_err(dev, "can't get again scale (%d)\n", err);
+ return err;
+ }
+
+ /*
+ * All the AGAIN values higher than HX3203_AGAIN_64_X have the same
+ * scale value - 'x64'
+ */
+ again = min_t(unsigned int, again, HX3203_AGAIN_64_X);
+
+ *val = hx3203_again_table[again];
+
+ return IIO_VAL_INT;
+}
+
+static int hx3203_read_int_time(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2)
+{
+ struct hx3203_priv *hx3203 = iio_priv(indio_dev);
+ struct device *dev = hx3203->dev;
+ unsigned int awt;
+ int err;
+
+ if (chan->type != IIO_INTENSITY && chan->type != IIO_LIGHT) {
+ dev_err(dev, "invalid channel type %u\n", chan->type);
+ return -EINVAL;
+ }
+
+ mutex_lock(&hx3203->lock);
+ err = regmap_field_read(hx3203->fields[F_AWT], &awt);
+ mutex_unlock(&hx3203->lock);
+
+ if (err) {
+ dev_err(dev, "can't get wait time (%d)\n", err);
+ return err;
+ }
+
+ *val = hx3203_wait_time_table[awt].integral;
+ *val2 = hx3203_wait_time_table[awt].microfract;
+
+ return IIO_VAL_INT_PLUS_MICRO;
+}
+
+static int hx3203_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ return hx3203_read_raw_data(indio_dev, chan, val);
+
+ case IIO_CHAN_INFO_PROCESSED:
+ return hx3203_read_processed(indio_dev, chan, val);
+
+ case IIO_CHAN_INFO_SCALE:
+ return hx3203_read_scale(indio_dev, chan, val);
+
+ case IIO_CHAN_INFO_INT_TIME:
+ return hx3203_read_int_time(indio_dev, chan, val, val2);
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int hx3203_write_scale(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int val)
+{
+ struct hx3203_priv *hx3203 = iio_priv(indio_dev);
+ struct device *dev = hx3203->dev;
+ unsigned int again;
+ int err;
+
+ if (chan->type != IIO_INTENSITY)
+ return -EINVAL;
+
+ if (!iio_device_claim_direct(indio_dev)) {
+ dev_err(dev, "can't claim direct mode\n");
+ return -EBUSY;
+ }
+
+ /*
+ * All the AGAIN values higher than HX3203_AGAIN_64_X have the same
+ * scale value - 'x64'
+ */
+ again = min_t(unsigned int, val, HX3203_AGAIN_64_X);
+
+ mutex_lock(&hx3203->lock);
+ err = regmap_field_write(hx3203->fields[F_AGAIN], again);
+ mutex_unlock(&hx3203->lock);
+
+ iio_device_release_direct(indio_dev);
+
+ if (err) {
+ dev_err(dev, "can't set again scale to %u (%d)\n", again, err);
+ return err;
+ }
+
+ return 0;
+}
+
+static int hx3203_write_int_time(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2)
+{
+ struct hx3203_priv *hx3203 = iio_priv(indio_dev);
+ struct device *dev = hx3203->dev;
+ unsigned int awt;
+ int err;
+
+ if (chan->type != IIO_INTENSITY && chan->type != IIO_LIGHT) {
+ dev_err(dev, "invalid channel type %u\n", chan->type);
+ return -EINVAL;
+ }
+
+ if (!iio_device_claim_direct(indio_dev)) {
+ dev_err(dev, "can't claim direct mode\n");
+ return -EBUSY;
+ }
+
+ err = -EINVAL;
+ for (awt = 0; awt < ARRAY_SIZE(hx3203_wait_time_table); awt++)
+ if (val == hx3203_wait_time_table[awt].integral &&
+ val2 == hx3203_wait_time_table[awt].microfract) {
+ mutex_lock(&hx3203->lock);
+ err = regmap_field_write(hx3203->fields[F_AWT], awt);
+ mutex_unlock(&hx3203->lock);
+ break;
+ }
+
+ iio_device_release_direct(indio_dev);
+
+ if (err)
+ dev_err(dev,
+ "can't set wait time to %u.%06ums (%d)\n",
+ val, val2, err);
+
+ return err;
+}
+
+static int hx3203_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ return hx3203_write_scale(indio_dev, chan, val);
+
+ case IIO_CHAN_INFO_INT_TIME:
+ return hx3203_write_int_time(indio_dev, chan, val, val2);
+ }
+
+ return -EINVAL;
+}
+
+static int hx3203_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type,
+ int *length, long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ *vals = hx3203_again_table;
+ *type = IIO_VAL_INT;
+ *length = ARRAY_SIZE(hx3203_again_table);
+ return IIO_AVAIL_LIST;
+
+ case IIO_CHAN_INFO_INT_TIME:
+ *vals = (int *)hx3203_wait_time_table;
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ /* Wait time value has 2 ints (integer and fractional parts) */
+ *length = ARRAY_SIZE(hx3203_wait_time_table) * 2;
+ return IIO_AVAIL_LIST;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int hx3203_read_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir)
+{
+ struct hx3203_priv *hx3203 = iio_priv(indio_dev);
+ struct device *dev = hx3203->dev;
+
+ if (type != IIO_EV_TYPE_THRESH) {
+ dev_err(dev, "invalid event type %u\n", type);
+ return -EINVAL;
+ }
+
+ if (chan->type != IIO_INTENSITY) {
+ dev_err(dev, "invalid channel type %u\n", chan->type);
+ return -EINVAL;
+ }
+
+ if (chan->channel2 != IIO_MOD_LIGHT_BOTH) {
+ dev_err(dev, "invalid channel modifier %u\n", chan->channel2);
+ return -EINVAL;
+ }
+
+ return hx3203->aint_enabled;
+}
+
+static int hx3203_write_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ bool state)
+{
+ struct hx3203_priv *hx3203 = iio_priv(indio_dev);
+ struct device *dev = hx3203->dev;
+
+ if (type != IIO_EV_TYPE_THRESH) {
+ dev_err(dev, "invalid event type %u\n", chan->type);
+ return -EINVAL;
+ }
+
+ if (chan->type != IIO_INTENSITY) {
+ dev_err(dev, "invalid channel type %u\n", chan->type);
+ return -EINVAL;
+ }
+
+ if (chan->channel2 != IIO_MOD_LIGHT_BOTH) {
+ dev_err(dev, "invalid channel modifier %u\n", chan->channel2);
+ return -EINVAL;
+ }
+
+ mutex_lock(&hx3203->lock);
+ if (state)
+ hx3203_enable_aint(hx3203);
+ else
+ hx3203_disable_aint(hx3203);
+ mutex_unlock(&hx3203->lock);
+
+ return 0;
+}
+
+static int hx3203_read_threshold(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_direction dir,
+ int *val, int *val2)
+{
+ struct hx3203_priv *hx3203 = iio_priv(indio_dev);
+ struct device *dev = hx3203->dev;
+ int err;
+
+ if (chan->type != IIO_INTENSITY) {
+ dev_err(dev, "invalid channel type %u\n", chan->type);
+ return -EINVAL;
+ }
+
+ if (chan->channel2 != IIO_MOD_LIGHT_BOTH) {
+ dev_err(dev, "invalid channel modifier %u\n", chan->channel2);
+ return -EINVAL;
+ }
+
+ *val = 0;
+ *val2 = 0;
+
+ mutex_lock(&hx3203->lock);
+
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ err = HX3203_READ_AIHT(hx3203, val);
+ break;
+
+ case IIO_EV_DIR_FALLING:
+ err = HX3203_READ_AILT(hx3203, val);
+ break;
+
+ default:
+ err = -EINVAL;
+ }
+
+ mutex_unlock(&hx3203->lock);
+
+ if (err)
+ dev_err(dev, "can't read AIT value (%d)\n", err);
+ else
+ err = IIO_VAL_INT;
+
+ return err;
+}
+
+static int hx3203_read_persistence(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_direction dir,
+ int *val, int *val2)
+{
+ struct hx3203_priv *hx3203 = iio_priv(indio_dev);
+ struct device *dev = hx3203->dev;
+ unsigned int apers;
+ int err;
+
+ if (chan->type != IIO_INTENSITY) {
+ dev_err(dev, "invalid channel type %u\n", chan->type);
+ return -EINVAL;
+ }
+
+ if (chan->channel2 != IIO_MOD_LIGHT_BOTH) {
+ dev_err(dev, "invalid channel modifier %u\n", chan->channel2);
+ return -EINVAL;
+ }
+
+ *val = 0;
+ *val2 = 0;
+
+ mutex_lock(&hx3203->lock);
+ err = regmap_field_read(hx3203->fields[F_APERS], &apers);
+ mutex_unlock(&hx3203->lock);
+
+ if (err) {
+ dev_err(dev, "can't get apers value (%d)\n", err);
+ return err;
+ }
+
+ *val = hx3203_apers_table[apers];
+
+ return IIO_VAL_INT;
+}
+
+static int hx3203_read_event(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int *val, int *val2)
+{
+ struct hx3203_priv *hx3203 = iio_priv(indio_dev);
+ struct device *dev = hx3203->dev;
+
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ return hx3203_read_threshold(indio_dev, chan, dir, val, val2);
+
+ case IIO_EV_INFO_PERIOD:
+ return hx3203_read_persistence(indio_dev, chan, dir, val, val2);
+
+ default:
+ dev_err(dev, "wrong info value %u\n", info);
+ return -EINVAL;
+ }
+}
+
+static int hx3203_write_threshold(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_direction dir,
+ int val, int val2)
+{
+ struct hx3203_priv *hx3203 = iio_priv(indio_dev);
+ struct device *dev = hx3203->dev;
+ int err;
+
+ if (chan->type != IIO_INTENSITY) {
+ dev_err(dev, "invalid channel type %u\n", chan->type);
+ return -EINVAL;
+ }
+
+ if (chan->channel2 != IIO_MOD_LIGHT_BOTH) {
+ dev_err(dev, "invalid channel modifier %u\n", chan->channel2);
+ return -EINVAL;
+ }
+
+ mutex_lock(&hx3203->lock);
+
+ hx3203_disable_aint(hx3203);
+
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ err = HX3203_WRITE_AIHT(hx3203, val);
+ break;
+
+ case IIO_EV_DIR_FALLING:
+ err = HX3203_WRITE_AILT(hx3203, val);
+ break;
+
+ default:
+ err = -EINVAL;
+ }
+
+ /* Enable AINT back anyway */
+ hx3203_enable_aint(hx3203);
+
+ mutex_unlock(&hx3203->lock);
+
+ if (err)
+ dev_err(dev, "can't write AIT value %d (%d)\n", val, err);
+
+ return err;
+}
+
+static int hx3203_write_persistence(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_direction dir,
+ int val, int val2)
+{
+ struct hx3203_priv *hx3203 = iio_priv(indio_dev);
+ struct device *dev = hx3203->dev;
+ unsigned int apers;
+ int err;
+
+ if (chan->type != IIO_INTENSITY) {
+ dev_err(dev, "invalid channel type %u\n", chan->type);
+ return -EINVAL;
+ }
+
+ if (chan->channel2 != IIO_MOD_LIGHT_BOTH) {
+ dev_err(dev, "invalid channel modifier %u\n", chan->channel2);
+ return -EINVAL;
+ }
+
+ if (val2) {
+ dev_err(dev, "wrong val2 input value %d\n", val2);
+ return -EINVAL;
+ }
+
+ err = -EINVAL;
+ for (apers = 0; apers < ARRAY_SIZE(hx3203_apers_table); apers++)
+ if (val == hx3203_apers_table[apers]) {
+ mutex_lock(&hx3203->lock);
+ err = regmap_field_write(hx3203->fields[F_APERS],
+ apers);
+ mutex_unlock(&hx3203->lock);
+ break;
+ }
+
+ if (err)
+ dev_err(dev, "can't set APERS to %d (%d)\n", val, err);
+
+ return IIO_VAL_INT;
+}
+
+static int hx3203_write_event(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int val, int val2)
+{
+ struct hx3203_priv *hx3203 = iio_priv(indio_dev);
+ struct device *dev = hx3203->dev;
+
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ return hx3203_write_threshold(indio_dev, chan, dir,
+ val, val2);
+
+ case IIO_EV_INFO_PERIOD:
+ return hx3203_write_persistence(indio_dev, chan, dir,
+ val, val2);
+
+ default:
+ dev_err(dev, "wrong info value %u\n", info);
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info hx3203_info = {
+ .read_raw = hx3203_read_raw,
+ .read_avail = hx3203_read_avail,
+ .write_raw = hx3203_write_raw,
+ .read_event_config = hx3203_read_event_config,
+ .write_event_config = hx3203_write_event_config,
+ .read_event_value = hx3203_read_event,
+ .write_event_value = hx3203_write_event,
+ .debugfs_reg_access = hx3203_debugfs_reg_access,
+};
+
+static int hx3203_setup_interrupts(struct hx3203_priv *hx3203)
+{
+ struct device *dev = hx3203->dev;
+ struct i2c_client *i2c = to_i2c_client(dev);
+ struct iio_dev *indio_dev = i2c_get_clientdata(i2c);
+ int err;
+
+ /* Keep going without interrupts if no initialized I2C IRQ */
+ if (i2c->irq <= 0)
+ return 0;
+
+ /* Don't enable interrupts automatically via request_irq() */
+ irq_set_status_flags(i2c->irq, IRQ_NOAUTOEN);
+ hx3203->aint_enabled = false;
+
+ err = devm_request_threaded_irq(&i2c->dev, i2c->irq, NULL,
+ hx3203_irq_thread,
+ IRQF_ONESHOT,
+ indio_dev->name, indio_dev);
+ if (err)
+ return dev_err_probe(dev, err, "failed to request IRQ\n");
+
+ err = regmap_field_write(hx3203->fields[F_APERS],
+ HX3203_APERS_ONE_ALS_VAL_OOR);
+ if (err)
+ return dev_err_probe(dev, err,
+ "can't setup ALS intr persistence\n");
+
+ err = regmap_field_write(hx3203->fields[F_AINT], 1);
+ if (err)
+ return dev_err_probe(dev, err, "can't enable ALS interrupt\n");
+
+ return 0;
+}
+
+static int hx3203_regmap_init(struct hx3203_priv *hx3203)
+{
+ struct regmap_field **fields = hx3203->fields;
+ struct device *dev = hx3203->dev;
+ struct i2c_client *i2c = to_i2c_client(dev);
+ struct regmap *regmap;
+ int i;
+
+ regmap = devm_regmap_init_i2c(i2c, &hx3203_regmap_config);
+ if (IS_ERR(regmap))
+ return dev_err_probe(dev, PTR_ERR(regmap),
+ "failed to register i2c regmap\n");
+
+ hx3203->regs = regmap;
+
+ for (i = 0; i < F_MAX_FIELDS; i++) {
+ fields[i] = devm_regmap_field_alloc(dev,
+ hx3203->regs,
+ hx3203_reg_fields[i]);
+ if (IS_ERR(hx3203->fields[i]))
+ return dev_err_probe(dev, PTR_ERR(hx3203->fields[i]),
+ "can't alloc field[%d]\n", i);
+ }
+
+ return 0;
+}
+
+static int hx3203_check_id(struct hx3203_priv *hx3203, unsigned int *id)
+{
+ struct device *dev = hx3203->dev;
+ int err;
+
+ err = regmap_read(hx3203->regs, HX3203_ID_REG, id);
+ if (err)
+ return dev_err_probe(dev, err, "failed to read id\n");
+
+ if (*id != HX3203_WHO_AM_I)
+ dev_warn(dev, "invalid id (%#x), expected (%#x)\n",
+ *id, HX3203_WHO_AM_I);
+
+ return 0;
+}
+
+static int hx3203_chip_init(struct hx3203_priv *hx3203)
+{
+ struct device *dev = hx3203->dev;
+ int err;
+
+ /* Setup wait time */
+ err = regmap_field_write(hx3203->fields[F_AWT], HX3203_AWT_0_MS);
+ if (err)
+ return dev_err_probe(dev, err, "can't setup wait time\n");
+
+ /*
+ * 14-bits resolution is default value, vendor doesn't suggest to use
+ * other values
+ */
+ err = regmap_field_write(hx3203->fields[F_ALS_RES],
+ HX3203_ALS_RES_14_BITS);
+ if (err)
+ return dev_err_probe(dev, err, "can't setup ALS resolution\n");
+
+ /* No gain multiplier by default */
+ err = regmap_field_write(hx3203->fields[F_AGAIN], HX3203_AGAIN_1_X);
+ if (err)
+ return dev_err_probe(dev, err, "can't init AGAIN\n");
+
+ /* Enable ALS logic */
+ err = hx3203_enable_als(hx3203);
+ if (err)
+ return dev_err_probe(dev, err, "can't enable ALS logic\n");
+
+ return 0;
+}
+
+static int hx3203_probe(struct i2c_client *i2c)
+{
+ struct device *dev = &i2c->dev;
+ struct hx3203_priv *hx3203;
+ struct iio_dev *indio_dev;
+ unsigned int id;
+ int err;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*hx3203));
+ if (!indio_dev)
+ return dev_err_probe(dev, -ENOMEM,
+ "IIO device allocation failed\n");
+
+ hx3203 = iio_priv(indio_dev);
+ hx3203->dev = dev;
+ i2c_set_clientdata(i2c, indio_dev);
+
+ err = hx3203_regmap_init(hx3203);
+ if (err)
+ return err;
+
+ mutex_init(&hx3203->lock);
+
+ hx3203->vdd = devm_regulator_get(dev, "vdd");
+ if (IS_ERR(hx3203->vdd))
+ return dev_err_probe(dev, PTR_ERR(hx3203->vdd),
+ "can't get vdd supply\n");
+
+ /*
+ * Power up the chip firstly. Sometimes I2C backend doesn't work
+ * without PON=1
+ */
+ err = hx3203_powerup(hx3203);
+ if (err) {
+ dev_err(dev, "can't powerup the chip (%d)\n", err);
+ return -EPROBE_DEFER;
+ }
+
+ err = hx3203_check_id(hx3203, &id);
+ if (err)
+ goto powerdown;
+
+ err = hx3203_chip_init(hx3203);
+ if (err)
+ goto powerdown;
+
+ indio_dev->name = devm_kasprintf(dev, GFP_KERNEL, "hx3203-%02x", id);
+ if (!indio_dev->name) {
+ dev_err(dev, "can't alloc chip name\n");
+ err = -ENOMEM;
+ goto disable_als;
+ }
+
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = hx3203_channels;
+ indio_dev->num_channels = ARRAY_SIZE(hx3203_channels);
+ indio_dev->info = &hx3203_info;
+
+ err = devm_iio_triggered_buffer_setup(dev, indio_dev,
+ iio_pollfunc_store_time,
+ hx3203_buffer_thread,
+ NULL);
+ if (err) {
+ dev_err(dev, "can't setup IIO trigger buffer (%d)\n", err);
+ goto disable_als;
+ }
+
+ err = hx3203_setup_interrupts(hx3203);
+ if (err)
+ goto disable_als;
+
+ err = devm_iio_device_register(dev, indio_dev);
+ if (err) {
+ dev_err(dev, "IIO device register failed (%d)\n", err);
+ goto disable_als;
+ }
+
+ return 0;
+
+disable_als:
+ hx3203_disable_als(hx3203);
+
+powerdown:
+ hx3203_powerdown(hx3203);
+
+ return err;
+}
+
+static void hx3203_remove(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+ struct hx3203_priv *hx3203 = iio_priv(indio_dev);
+ struct device *dev = hx3203->dev;
+ int err;
+
+ mutex_lock(&hx3203->lock);
+
+ err = hx3203_disable_als(hx3203);
+ if (err)
+ dev_err(dev, "failed to disable ALS logic (%d)\n", err);
+
+ err = hx3203_powerdown(hx3203);
+ if (err)
+ dev_err(dev, "can't powerdown the chip (%d)\n", err);
+
+ mutex_unlock(&hx3203->lock);
+
+ regulator_disable(hx3203->vdd);
+}
+
+static const struct i2c_device_id hx3203_i2c_id[] = {
+ { .name = "hx3203" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, hx3203_i2c_id);
+
+static const struct of_device_id hx3203_of_match[] = {
+ { .compatible = "tianyihexin,hx3203" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, hx3203_of_match);
+
+static struct i2c_driver hx3203_driver = {
+ .driver = {
+ .name = "hx3203",
+ .of_match_table = hx3203_of_match,
+ },
+ .probe = hx3203_probe,
+ .remove = hx3203_remove,
+ .id_table = hx3203_i2c_id,
+};
+module_i2c_driver(hx3203_driver);
+
+MODULE_AUTHOR("Dmitry Rokosov <ddrokosov@sberdevices.ru>");
+MODULE_DESCRIPTION("Tianyihexin HX3203 Ambient Light Sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 597d7a79c988b..9e802c77ff770 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -124,6 +124,9 @@ config LEDS_AW200XX
tristate "LED support for Awinic AW20036/AW20054/AW20072/AW20108"
depends on LEDS_CLASS
depends on I2C
+ depends on FB
+ depends on BACKLIGHT_CLASS_DEVICE
+ select FB_SYSMEM_HELPERS
help
This option enables support for the Awinic AW200XX LED controllers.
It is a matrix LED driver programmed via an I2C interface. Devices have
diff --git a/drivers/leds/leds-aw200xx.c b/drivers/leds/leds-aw200xx.c
index fe223d363a5d4..6cf983f233c71 100644
--- a/drivers/leds/leds-aw200xx.c
+++ b/drivers/leds/leds-aw200xx.c
@@ -20,6 +20,11 @@
#include <linux/time.h>
#include <linux/units.h>
+#include <linux/backlight.h>
+#include <linux/fb.h>
+
+#define AW200XX_LEDS_MAX 108
+#define AW200XX_PATTERN_MAX 3
#define AW200XX_DIM_MAX (BIT(6) - 1)
#define AW200XX_FADE_MAX (BIT(8) - 1)
#define AW200XX_IMAX_DEFAULT_uA 60000
@@ -68,6 +73,88 @@
#define AW200XX_REG_FCD AW200XX_REG(AW200XX_PAGE0, 0x04)
#define AW200XX_FCD_CLEAR 0x01
+/* Interrupt status register */
+#define AW200XX_REG_ISRFLT AW200XX_REG(AW200XX_PAGE0, 0x0B)
+#define AW200XX_ISRFLT_PATIS_MASK GENMASK(6, 4)
+
+/* Pattern enable control register */
+#define AW200XX_REG_PATCR AW200XX_REG(AW200XX_PAGE0, 0x43)
+#define AW200XX_PATCR_PAT_IE_MASK GENMASK(6, 4)
+#define AW200XX_PATCR_PAT_IE_ALL AW200XX_PATCR_PAT_IE_MASK
+#define AW200XX_PATCR_PAT_ENABLE(x) BIT(x)
+
+/*
+ * Maximum breathing level registers
+ * For patterns 0 - 0x44, 1 - 0x45, 2 - 0x46 (step 1)
+ */
+#define AW200XX_REG_PAT0_MAX_BREATH AW200XX_REG(AW200XX_PAGE0, 0x44)
+
+/*
+ * Minimum breathing level registers
+ * For patterns 0 - 0x47, 1 - 0x48, 2 - 0x49 (step 1)
+ */
+#define AW200XX_REG_PAT0_MIN_BREATH AW200XX_REG(AW200XX_PAGE0, 0x47)
+
+/*
+ * Template 1 (rise-time) & template 2 (on-time) configuration register
+ * For patterns 0 - 0x4A, 1 - 0x4E, 2 - 0x52 (step 4)
+ */
+#define AW200XX_REG_PAT0_T0 AW200XX_REG(AW200XX_PAGE0, 0x4A)
+
+/*
+ * Template 3 (fall-time) & template 4 (off-time) configuration register
+ * For patterns 0 - 0x4B, 1 - 0x4F, 2 - 0x53 (step 4)
+ */
+#define AW200XX_REG_PAT0_T1 AW200XX_REG(AW200XX_PAGE0, 0x4B)
+
+/*
+ * Loop configuration registers:
+ * loop end point setting (LE)
+ * loop beginning point setting (LB)
+ * MSB of loop times (LT)
+ * For patterns 0 - 0x4C, 1 - 0x50, 2 - 0x54 (step 4)
+ */
+#define AW200XX_REG_PAT0_T2 AW200XX_REG(AW200XX_PAGE0, 0x4C)
+#define AW200XX_REG_PATX_T2(x) (AW200XX_REG_PAT0_T2 + (x))
+
+/*
+ * Loop configuration registers:
+ * LSB of loop times (LT)
+ * For patterns 0 - 0x4D, 1 - 0x51, 2 - 0x55 (step 4)
+ */
+#define AW200XX_REG_PAT0_T3 AW200XX_REG(AW200XX_PAGE0, 0x4D)
+#define AW200XX_REG_PATX_T3(x) (AW200XX_REG_PAT0_T3 + (x))
+
+#define AW200XX_PAT_T2_LE_MASK GENMASK(7, 6)
+#define AW200XX_PAT_T2_LB_MASK GENMASK(5, 4)
+#define AW200XX_PAT_T2_LT_MASK GENMASK(3, 0)
+#define AW200XX_PAT_T3_LT_MASK GENMASK(7, 0)
+#define AW200XX_PAT0_T2_LT_MSB(x) ((x) >> 8)
+#define AW200XX_PAT0_T3_LT_LSB(x) ((x) & 0xFF)
+#define AW200XX_PAT0_T_LT(msb, lsb) ((msb) << 8 | (lsb))
+#define AW200XX_PAT0_T_LT_MAX (BIT(12) - 1)
+
+#define AW200XX_PAT_T_STEP 4
+
+#define AW200XX_PAT_T1_T3_MASK GENMASK(7, 4)
+#define AW200XX_PAT_T2_T4_MASK GENMASK(3, 0)
+#define AW200XX_TEMPLATE_TIME_MAX (BIT(4) - 1)
+
+/*
+ * Pattern mode configuration register
+ * For patterns 0 - 0x56, 1 - 0x57, 2 - 0x58 (step 1)
+ */
+#define AW200XX_REG_PAT0_CFG AW200XX_REG(AW200XX_PAGE0, 0x56)
+#define AW200XX_PAT_CFG_MODE_MASK BIT(0)
+#define AW200XX_PAT_CFG_RAMP_MASK BIT(1)
+#define AW200XX_PAT_CFG_SWITCH_MASK BIT(2)
+
+/* Start pattern register */
+#define AW200XX_REG_PATGO AW200XX_REG(AW200XX_PAGE0, 0x59)
+#define AW200XX_PATGO(x) BIT(x)
+#define AW200XX_PATGO_RUN(x, run) ((run) << (x))
+#define AW200XX_PATGO_STATE(x) BIT((x) + 4)
+
/* Display size configuration */
#define AW200XX_REG_DSIZE AW200XX_REG(AW200XX_PAGE0, 0x80)
#define AW200XX_DSIZE_COLUMNS_MAX 12
@@ -79,6 +166,11 @@
#define AW200XX_REG_DIM_PAGE1(x, columns) \
AW200XX_REG(AW200XX_PAGE1, AW200XX_LED2REG(x, columns))
+/* Pattern selection register*/
+#define AW200XX_REG_PAT_SELECT(x, columns) \
+ AW200XX_REG(AW200XX_PAGE3, AW200XX_LED2REG(x, columns))
+#define AW200XX_PATX_SELECT(x) ((x) + 1)
+
/*
* DIM current configuration register (page 4).
* The even address for current DIM configuration.
@@ -89,6 +181,8 @@
#define AW200XX_REG_DIM2FADE(x) ((x) + 1)
#define AW200XX_REG_FADE2DIM(fade) \
DIV_ROUND_UP((fade) * AW200XX_DIM_MAX, AW200XX_FADE_MAX)
+#define AW200XX_SCALE_FADE(fade, scale) \
+ DIV_ROUND_UP((fade) * (scale), AW200XX_FADE_MAX)
/*
* Duty ratio of display scan (see p.15 of datasheet for formula):
@@ -105,6 +199,31 @@ struct aw200xx_chipdef {
u32 display_size_columns;
};
+struct aw200xx_page {
+ u8 dirty_start;
+ u8 dirty_end;
+ u8 *buf;
+ u32 page;
+};
+
+#define FB_BUFFER_COUNT 2
+
+struct aw200xx_fb {
+ int width;
+ int height;
+ int num_leds;
+ u8 *map;
+ atomic_t open_count;
+ struct fb_info *info;
+ u32 refresh_rate;
+ u8 *buffer;
+ u8 min_reg;
+ u8 max_reg;
+ int offset_y;
+ struct backlight_device *bl;
+ u8 fade_scale;
+};
+
struct aw200xx_led {
struct led_classdev cdev;
struct aw200xx *chip;
@@ -117,12 +236,539 @@ struct aw200xx {
struct i2c_client *client;
struct regmap *regmap;
struct mutex mutex;
+ DECLARE_BITMAP(pattern_leds[AW200XX_PATTERN_MAX], AW200XX_LEDS_MAX);
u32 num_leds;
+ u32 num_pattern_leds;
u32 display_rows;
+ struct aw200xx_page dim_page;
+ struct aw200xx_page fade_page;
+ struct delayed_work drawer;
+ bool queue_mode;
+ struct aw200xx_fb fb;
+ u32 imax;
struct gpio_desc *hwen;
struct aw200xx_led leds[] __counted_by(num_leds);
};
+struct aw200xx_led_data {
+ int num;
+ u32 imax;
+ enum led_default_state default_state;
+};
+
+struct aw200xx_attribute {
+ struct device_attribute dev_attr;
+ u32 reg;
+ u32 mask;
+ u32 max;
+};
+
+#define to_aw200xx_attr(attr) \
+ container_of(attr, struct aw200xx_attribute, dev_attr)
+
+#define AW200XX_ATTR(_n, _m, _sh, _st, _r, _msk, _max) { \
+ .dev_attr = __ATTR(_n, _m, _sh, _st), \
+ .reg = _r, \
+ .mask = _msk, \
+ .max = _max, \
+}
+
+#define AW200XX_DEVICE_ATTR_RW(_v, _n, _sh, _st, _r, _msk, _max) \
+struct aw200xx_attribute _v##_attr = \
+ AW200XX_ATTR(_n, 0644, _sh, _st, \
+ _r, _msk, _max)
+
+#define AW200XX_DEVICE_ATTR_RO(_v, _n, _sh, _r, _msk, _max) \
+struct aw200xx_attribute _v##_attr = \
+ AW200XX_ATTR(_n, 0444, _sh, NULL, \
+ _r, _msk, _max)
+
+static ssize_t aw200xx_store_internal(struct device *dev,
+ struct device_attribute *devattr,
+ const char *buf, size_t count)
+{
+ const struct aw200xx_attribute *attr = to_aw200xx_attr(devattr);
+ struct aw200xx *chip = i2c_get_clientdata(to_i2c_client(dev));
+ u32 val;
+ int ret;
+
+ ret = kstrtouint(buf, 0, &val);
+ if (ret < 0 || val > attr->max)
+ return -EINVAL;
+
+ val <<= __ffs(attr->mask);
+
+ mutex_lock(&chip->mutex);
+ ret = regmap_update_bits(chip->regmap, attr->reg, attr->mask, val);
+ mutex_unlock(&chip->mutex);
+
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static ssize_t aw200xx_show_internal(struct device *dev,
+ struct device_attribute *devattr,
+ char *buf)
+{
+ const struct aw200xx_attribute *attr = to_aw200xx_attr(devattr);
+ struct aw200xx *chip = i2c_get_clientdata(to_i2c_client(dev));
+ u32 val;
+ int ret;
+
+ mutex_lock(&chip->mutex);
+ ret = regmap_read(chip->regmap, attr->reg, &val);
+ mutex_unlock(&chip->mutex);
+
+ if (ret)
+ return ret;
+
+ val = (val & attr->mask) >> __ffs(attr->mask);
+
+ return sysfs_emit(buf, "%u\n", val);
+}
+
+static ssize_t aw200xx_template_time_show(struct device *dev,
+ struct device_attribute *devattr,
+ char *buf)
+{
+ static const u32 ttimes_ms[] = {
+ 0, 130, 260, 380, 510, 770, 1040, 1600,
+ 2100, 2600, 3100, 4200, 5200, 6200, 7300, 8300,
+ };
+ const struct aw200xx_attribute *attr = to_aw200xx_attr(devattr);
+ struct aw200xx *chip = i2c_get_clientdata(to_i2c_client(dev));
+ u32 ttime;
+ int ret;
+
+ mutex_lock(&chip->mutex);
+ ret = regmap_read(chip->regmap, attr->reg, &ttime);
+ mutex_unlock(&chip->mutex);
+
+ if (ret)
+ return ret;
+
+ ttime = (ttime & attr->mask) >> __ffs(attr->mask);
+ if (ttime >= ARRAY_SIZE(ttimes_ms))
+ return -EIO;
+
+ ttime = ttimes_ms[ttime];
+
+ /* For On & Off time minimum is 40ms */
+ if (ttime == 0 && attr->mask == AW200XX_PAT_T2_T4_MASK)
+ ttime = 40;
+
+ return sysfs_emit(buf, "%ums\n", ttime);
+}
+
+static ssize_t aw200xx_pattern_leds_store(struct device *dev,
+ struct device_attribute *devattr,
+ const char *buf, size_t count,
+ bool clear)
+{
+ const struct aw200xx_attribute *attr = to_aw200xx_attr(devattr);
+ struct aw200xx *chip = i2c_get_clientdata(to_i2c_client(dev));
+ unsigned long *pattern_leds = chip->pattern_leds[attr->reg];
+ u32 columns = chip->cdef->display_size_columns;
+ DECLARE_BITMAP(leds, AW200XX_LEDS_MAX);
+ u32 val = clear ? 0 : AW200XX_PATX_SELECT(attr->reg);
+ u32 i;
+ ssize_t ret;
+
+ if (sysfs_streq(buf, "all")) {
+ bitmap_fill(leds, chip->num_pattern_leds);
+ } else {
+ ret = bitmap_parse(buf, count, leds, chip->num_pattern_leds);
+ if (ret)
+ return -EINVAL;
+ }
+
+ mutex_lock(&chip->mutex);
+
+ if (!clear && atomic_read(&chip->fb.open_count) &&
+ find_next_bit(leds, chip->num_pattern_leds, chip->num_leds) !=
+ chip->num_pattern_leds) {
+ ret = -EBUSY;
+ goto out_unlock;
+ }
+
+ for_each_set_bit(i, leds, chip->num_pattern_leds) {
+ const u32 num = i < chip->num_leds ?
+ chip->leds[i].num :
+ chip->fb.map[i - chip->num_leds];
+
+ ret = regmap_write(chip->regmap,
+ AW200XX_REG_PAT_SELECT(num, columns), val);
+ if (ret)
+ goto out_unlock;
+
+ if (clear)
+ __clear_bit(i, pattern_leds);
+ else
+ __set_bit(i, pattern_leds);
+ }
+
+ ret = count;
+
+out_unlock:
+ mutex_unlock(&chip->mutex);
+ return ret;
+}
+
+static ssize_t aw200xx_pattern_select_leds_show(struct device *dev,
+ struct device_attribute *devattr,
+ char *buf)
+{
+ const struct aw200xx_attribute *attr = to_aw200xx_attr(devattr);
+ struct aw200xx *chip = i2c_get_clientdata(to_i2c_client(dev));
+ int ret;
+
+ mutex_lock(&chip->mutex);
+ ret = sysfs_emit(buf, "%*pb\n",
+ chip->num_pattern_leds, chip->pattern_leds[attr->reg]);
+ mutex_unlock(&chip->mutex);
+
+ return ret;
+}
+
+static ssize_t aw200xx_pattern_select_leds_store(struct device *dev,
+ struct device_attribute *devattr,
+ const char *buf, size_t count)
+{
+ return aw200xx_pattern_leds_store(dev, devattr, buf, count, false);
+}
+
+static ssize_t aw200xx_pattern_clear_leds_show(struct device *dev,
+ struct device_attribute *devattr,
+ char *buf)
+{
+ const struct aw200xx_attribute *attr = to_aw200xx_attr(devattr);
+ struct aw200xx *chip = i2c_get_clientdata(to_i2c_client(dev));
+ DECLARE_BITMAP(leds, AW200XX_LEDS_MAX);
+ int ret;
+
+ mutex_lock(&chip->mutex);
+ bitmap_fill(leds, chip->num_pattern_leds);
+ bitmap_xor(leds, leds, chip->pattern_leds[attr->reg], chip->num_pattern_leds);
+ ret = scnprintf(buf, PAGE_SIZE, "%*pb", chip->num_pattern_leds, leds);
+ mutex_unlock(&chip->mutex);
+
+ return ret;
+}
+
+static ssize_t aw200xx_pattern_clear_leds_store(struct device *dev,
+ struct device_attribute *devattr,
+ const char *buf, size_t count)
+{
+ return aw200xx_pattern_leds_store(dev, devattr, buf, count, true);
+}
+
+static ssize_t aw200xx_pattern_start_show(struct device *dev,
+ struct device_attribute *devattr,
+ char *buf)
+{
+ const struct aw200xx_attribute *attr = to_aw200xx_attr(devattr);
+ struct aw200xx *chip = i2c_get_clientdata(to_i2c_client(dev));
+ u32 start = 0;
+ u32 val;
+ int ret;
+
+ mutex_lock(&chip->mutex);
+
+ ret = regmap_read(chip->regmap, AW200XX_REG_PATCR, &val);
+ if (ret)
+ goto out_unlock;
+
+ if (val & AW200XX_PATCR_PAT_ENABLE(attr->reg)) {
+ ret = regmap_read(chip->regmap, AW200XX_REG_PATGO, &val);
+ if (ret)
+ goto out_unlock;
+
+ start = !!(val & AW200XX_PATGO(attr->reg));
+ }
+
+ ret = sysfs_emit(buf, "%u\n", start);
+
+out_unlock:
+ mutex_unlock(&chip->mutex);
+ return ret;
+}
+
+static ssize_t aw200xx_pattern_start_store(struct device *dev,
+ struct device_attribute *devattr,
+ const char *buf, size_t count)
+{
+ const struct aw200xx_attribute *attr = to_aw200xx_attr(devattr);
+ struct aw200xx *chip = i2c_get_clientdata(to_i2c_client(dev));
+ u32 start;
+ ssize_t ret;
+
+ ret = kstrtouint(buf, 0, &start);
+ if (ret < 0 || start > attr->max)
+ return -EINVAL;
+
+ start = AW200XX_PATGO_RUN(attr->reg, start);
+
+ mutex_lock(&chip->mutex);
+
+ ret = regmap_update_bits(chip->regmap, AW200XX_REG_PATCR,
+ AW200XX_PATCR_PAT_ENABLE(attr->reg), start);
+ if (ret)
+ goto out_unlock;
+
+ ret = regmap_update_bits(chip->regmap, AW200XX_REG_PATGO,
+ AW200XX_PATGO(attr->reg), start);
+ if (ret)
+ goto out_unlock;
+
+ ret = count;
+
+out_unlock:
+ mutex_unlock(&chip->mutex);
+ return ret;
+}
+
+static ssize_t aw200xx_pattern_running_show(struct device *dev,
+ struct device_attribute *devattr,
+ char *buf)
+{
+ const struct aw200xx_attribute *attr = to_aw200xx_attr(devattr);
+ struct aw200xx *chip = i2c_get_clientdata(to_i2c_client(dev));
+ u32 running;
+ int ret;
+
+ mutex_lock(&chip->mutex);
+
+ ret = regmap_read(chip->regmap, AW200XX_REG_PATGO, &running);
+ if (ret)
+ goto out_unlock;
+
+ running &= AW200XX_PATGO_STATE(attr->reg);
+ ret = sysfs_emit(buf, "%u\n", !!running);
+
+out_unlock:
+ mutex_unlock(&chip->mutex);
+ return ret;
+}
+
+static ssize_t aw200xx_pattern_repeat_show(struct device *dev,
+ struct device_attribute *devattr,
+ char *buf)
+{
+ const struct aw200xx_attribute *attr = to_aw200xx_attr(devattr);
+ struct aw200xx *chip = i2c_get_clientdata(to_i2c_client(dev));
+ u32 repeat_msb;
+ u32 repeat_lsb;
+ int ret;
+
+ mutex_lock(&chip->mutex);
+
+ ret = regmap_read(chip->regmap,
+ AW200XX_REG_PATX_T2(attr->reg), &repeat_msb);
+ if (ret)
+ goto out_unlock;
+
+ ret = regmap_read(chip->regmap,
+ AW200XX_REG_PATX_T3(attr->reg), &repeat_lsb);
+ if (ret)
+ goto out_unlock;
+
+ repeat_msb &= AW200XX_PAT_T2_LT_MASK;
+ repeat_lsb &= AW200XX_PAT_T3_LT_MASK;
+
+ ret = sysfs_emit(buf, "%u\n",
+ AW200XX_PAT0_T_LT(repeat_msb, repeat_lsb));
+
+out_unlock:
+ mutex_unlock(&chip->mutex);
+ return ret;
+}
+
+static ssize_t aw200xx_pattern_repeat_store(struct device *dev,
+ struct device_attribute *devattr,
+ const char *buf, size_t count)
+{
+ const struct aw200xx_attribute *attr = to_aw200xx_attr(devattr);
+ struct aw200xx *chip = i2c_get_clientdata(to_i2c_client(dev));
+ u32 repeat;
+ ssize_t ret;
+
+ ret = kstrtouint(buf, 0, &repeat);
+ if (ret < 0 || repeat > attr->max)
+ return -EINVAL;
+
+ mutex_lock(&chip->mutex);
+
+ ret = regmap_update_bits(chip->regmap,
+ AW200XX_REG_PATX_T2(attr->reg),
+ AW200XX_PAT_T2_LT_MASK,
+ AW200XX_PAT0_T2_LT_MSB(repeat));
+ if (ret)
+ goto out_unlock;
+
+ ret = regmap_update_bits(chip->regmap,
+ AW200XX_REG_PATX_T3(attr->reg),
+ AW200XX_PAT_T3_LT_MASK,
+ AW200XX_PAT0_T3_LT_LSB(repeat));
+ if (ret)
+ goto out_unlock;
+
+ ret = count;
+
+out_unlock:
+ mutex_unlock(&chip->mutex);
+ return ret;
+}
+
+#define AW200XX_DEVICE_ATTR_PAT_RW(_n, _sh, _st, _r, _step, _msk, _max) \
+static AW200XX_DEVICE_ATTR_RW(_n##0, _n, _sh, _st, \
+ _r, _msk, _max); \
+static AW200XX_DEVICE_ATTR_RW(_n##1, _n, _sh, _st, \
+ _r + (1 * (_step)), _msk, _max); \
+static AW200XX_DEVICE_ATTR_RW(_n##2, _n, _sh, _st, \
+ _r + (2 * (_step)), _msk, _max)
+
+#define AW200XX_DEVICE_ATTR_PAT_RO(_n, _sh, _r, _step, _msk, _max) \
+static AW200XX_DEVICE_ATTR_RO(_n##0, _n, _sh, \
+ _r, _msk, _max); \
+static AW200XX_DEVICE_ATTR_RO(_n##1, _n, _sh, \
+ _r + (1 * (_step)), _msk, _max); \
+static AW200XX_DEVICE_ATTR_RO(_n##2, _n, _sh, \
+ _r + (2 * (_step)), _msk, _max)
+
+#define AW200XX_DEFINE_ATTR_GROUP(_idx, _a0, _a1, _a2, _a3, _a4, _a5, \
+ _a6, _a7, _a8, _a9, _a10, _a11, _a12, _a13, _a14, _a15) \
+static struct attribute *aw200xx_pattern##_idx##_attributes[] = { \
+ &_a0##_idx##_attr.dev_attr.attr, \
+ &_a1##_idx##_attr.dev_attr.attr, \
+ &_a2##_idx##_attr.dev_attr.attr, \
+ &_a3##_idx##_attr.dev_attr.attr, \
+ &_a4##_idx##_attr.dev_attr.attr, \
+ &_a5##_idx##_attr.dev_attr.attr, \
+ &_a6##_idx##_attr.dev_attr.attr, \
+ &_a7##_idx##_attr.dev_attr.attr, \
+ &_a8##_idx##_attr.dev_attr.attr, \
+ &_a9##_idx##_attr.dev_attr.attr, \
+ &_a10##_idx##_attr.dev_attr.attr, \
+ &_a11##_idx##_attr.dev_attr.attr, \
+ &_a12##_idx##_attr.dev_attr.attr, \
+ &_a13##_idx##_attr.dev_attr.attr, \
+ &_a14##_idx##_attr.dev_attr.attr, \
+ &_a15##_idx##_attr.dev_attr.attr, \
+ NULL}; \
+static const struct attribute_group aw200xx_pattern##_idx##_group = { \
+ .attrs = aw200xx_pattern##_idx##_attributes, \
+ .name = "pattern"#_idx, \
+}
+
+#define AW200XX_DEFINE_ATTR_GROUPS(...) \
+AW200XX_DEFINE_ATTR_GROUP(0, __VA_ARGS__); \
+AW200XX_DEFINE_ATTR_GROUP(1, __VA_ARGS__); \
+AW200XX_DEFINE_ATTR_GROUP(2, __VA_ARGS__); \
+static const struct attribute_group *aw200xx_pattern_groups[] = { \
+ &aw200xx_pattern0_group, \
+ &aw200xx_pattern1_group, \
+ &aw200xx_pattern2_group, \
+ NULL}
+
+AW200XX_DEVICE_ATTR_PAT_RW(rise_time,
+ aw200xx_template_time_show, aw200xx_store_internal,
+ AW200XX_REG_PAT0_T0, AW200XX_PAT_T_STEP,
+ AW200XX_PAT_T1_T3_MASK, AW200XX_TEMPLATE_TIME_MAX);
+
+AW200XX_DEVICE_ATTR_PAT_RW(on_time,
+ aw200xx_template_time_show, aw200xx_store_internal,
+ AW200XX_REG_PAT0_T0, AW200XX_PAT_T_STEP,
+ AW200XX_PAT_T2_T4_MASK, AW200XX_TEMPLATE_TIME_MAX);
+
+AW200XX_DEVICE_ATTR_PAT_RW(fall_time,
+ aw200xx_template_time_show, aw200xx_store_internal,
+ AW200XX_REG_PAT0_T1, AW200XX_PAT_T_STEP,
+ AW200XX_PAT_T1_T3_MASK, AW200XX_TEMPLATE_TIME_MAX);
+
+AW200XX_DEVICE_ATTR_PAT_RW(off_time,
+ aw200xx_template_time_show, aw200xx_store_internal,
+ AW200XX_REG_PAT0_T1, AW200XX_PAT_T_STEP,
+ AW200XX_PAT_T2_T4_MASK, AW200XX_TEMPLATE_TIME_MAX);
+
+AW200XX_DEVICE_ATTR_PAT_RW(mode,
+ aw200xx_show_internal, aw200xx_store_internal,
+ AW200XX_REG_PAT0_CFG, 1,
+ AW200XX_PAT_CFG_MODE_MASK, 1);
+
+AW200XX_DEVICE_ATTR_PAT_RW(ramp,
+ aw200xx_show_internal, aw200xx_store_internal,
+ AW200XX_REG_PAT0_CFG, 1,
+ AW200XX_PAT_CFG_RAMP_MASK, 1);
+
+AW200XX_DEVICE_ATTR_PAT_RW(toggle,
+ aw200xx_show_internal, aw200xx_store_internal,
+ AW200XX_REG_PAT0_CFG, 1,
+ AW200XX_PAT_CFG_SWITCH_MASK, 1);
+
+AW200XX_DEVICE_ATTR_PAT_RW(loop_end_on,
+ aw200xx_show_internal, aw200xx_store_internal,
+ AW200XX_REG_PAT0_T2, AW200XX_PAT_T_STEP,
+ AW200XX_PAT_T2_LE_MASK, 1);
+
+AW200XX_DEVICE_ATTR_PAT_RW(loop_begin,
+ aw200xx_show_internal, aw200xx_store_internal,
+ AW200XX_REG_PAT0_T2, AW200XX_PAT_T_STEP,
+ AW200XX_PAT_T2_LB_MASK, 3);
+
+AW200XX_DEVICE_ATTR_PAT_RW(max_breathing_level,
+ aw200xx_show_internal, aw200xx_store_internal,
+ AW200XX_REG_PAT0_MAX_BREATH, 1,
+ 0xFF, AW200XX_FADE_MAX);
+
+AW200XX_DEVICE_ATTR_PAT_RW(min_breathing_level,
+ aw200xx_show_internal, aw200xx_store_internal,
+ AW200XX_REG_PAT0_MIN_BREATH, 1,
+ 0xFF, AW200XX_FADE_MAX);
+
+AW200XX_DEVICE_ATTR_PAT_RW(start,
+ aw200xx_pattern_start_show,
+ aw200xx_pattern_start_store,
+ 0, 1, 1, 1);
+
+AW200XX_DEVICE_ATTR_PAT_RO(running,
+ aw200xx_pattern_running_show, 0, 1, 0, 0);
+
+AW200XX_DEVICE_ATTR_PAT_RW(repeat,
+ aw200xx_pattern_repeat_show,
+ aw200xx_pattern_repeat_store,
+ 0, AW200XX_PAT_T_STEP,
+ 0, AW200XX_PAT0_T_LT_MAX);
+
+AW200XX_DEVICE_ATTR_PAT_RW(select_leds,
+ aw200xx_pattern_select_leds_show,
+ aw200xx_pattern_select_leds_store,
+ 0, 1, 0, 0);
+
+AW200XX_DEVICE_ATTR_PAT_RW(clear_leds,
+ aw200xx_pattern_clear_leds_show,
+ aw200xx_pattern_clear_leds_store,
+ 0, 1, 0, 0);
+
+AW200XX_DEFINE_ATTR_GROUPS(start, running, mode, ramp, toggle, repeat,
+ loop_end_on, loop_begin, select_leds, clear_leds,
+ max_breathing_level, min_breathing_level,
+ rise_time, on_time, fall_time, off_time);
+
+#define IS_PAGE_DIRTY(page) ((page)->dirty_end)
+
+static void add_dirty_range(struct aw200xx_page *page, u8 start, u8 end)
+{
+ if (IS_PAGE_DIRTY(page)) {
+ page->dirty_start = min(page->dirty_start, start);
+ page->dirty_end = max(page->dirty_end, end);
+ } else {
+ page->dirty_start = start;
+ page->dirty_end = end;
+ }
+}
+
static ssize_t dim_show(struct device *dev, struct device_attribute *devattr,
char *buf)
{
@@ -136,7 +782,23 @@ static ssize_t dim_show(struct device *dev, struct device_attribute *devattr,
return sysfs_emit(buf, "%d\n", dim);
}
-static ssize_t dim_store(struct device *dev, struct device_attribute *devattr,
+static int aw200xx_dim_fill(const char *buf, int *dim)
+{
+ if (sysfs_streq(buf, "auto")) {
+ *dim = -1;
+ } else {
+ int ret = kstrtoint(buf, 0, dim);
+ if (ret)
+ return ret;
+
+ if (*dim > AW200XX_DIM_MAX)
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static ssize_t dim_store_queue(struct device *dev, struct device_attribute *devattr,
const char *buf, size_t count)
{
struct led_classdev *cdev = dev_get_drvdata(dev);
@@ -146,17 +808,43 @@ static ssize_t dim_store(struct device *dev, struct device_attribute *devattr,
int dim;
ssize_t ret;
- if (sysfs_streq(buf, "auto")) {
- dim = -1;
- } else {
- ret = kstrtoint(buf, 0, &dim);
- if (ret)
- return ret;
+ ret = aw200xx_dim_fill(buf, &dim);
+ if (ret < 0)
+ return ret;
- if (dim > AW200XX_DIM_MAX)
- return -EINVAL;
+ mutex_lock(&chip->mutex);
+
+ if (dim >= 0) {
+ const u8 reg = AW200XX_LED2REG(led->num, columns);
+
+ chip->dim_page.buf[reg] = dim;
+ add_dirty_range(&chip->dim_page, reg, reg + 1);
}
+ led->dim = dim;
+
+ mutex_unlock(&chip->mutex);
+
+ if (dim >= 0)
+ schedule_delayed_work(&chip->drawer, 0);
+
+ return count;
+}
+
+static ssize_t dim_store(struct device *dev, struct device_attribute *devattr,
+ const char *buf, size_t count)
+{
+ struct led_classdev *cdev = dev_get_drvdata(dev);
+ struct aw200xx_led *led = container_of(cdev, struct aw200xx_led, cdev);
+ struct aw200xx *chip = led->chip;
+ u32 columns = chip->cdef->display_size_columns;
+ int dim;
+ ssize_t ret;
+
+ ret = aw200xx_dim_fill(buf, &dim);
+ if (ret < 0)
+ return ret;
+
mutex_lock(&chip->mutex);
if (dim >= 0) {
@@ -182,6 +870,71 @@ static struct attribute *dim_attrs[] = {
};
ATTRIBUTE_GROUPS(dim);
+static inline void aw200xx_fb_queue(struct aw200xx *chip)
+{
+ const struct aw200xx_fb *fbdev = &chip->fb;
+
+ schedule_delayed_work(&chip->drawer,
+ msecs_to_jiffies(HZ / fbdev->refresh_rate));
+}
+
+static inline void aw200xx_fb_add_screen_dirty_range(struct aw200xx *chip)
+{
+ const struct aw200xx_fb *fbdev = &chip->fb;
+ const uint8_t *fb_ptr = fbdev->buffer + fbdev->offset_y * fbdev->width;
+ int i;
+
+ for (i = 0; i < fbdev->num_leds; i++) {
+ const u8 reg = fbdev->map[i];
+ const u8 fade = AW200XX_SCALE_FADE(fb_ptr[i],
+ fbdev->fade_scale);
+
+ chip->fade_page.buf[reg] = fade;
+ chip->dim_page.buf[reg] = AW200XX_REG_FADE2DIM(fade);
+ }
+
+ add_dirty_range(&chip->fade_page, fbdev->min_reg, fbdev->max_reg + 1);
+ add_dirty_range(&chip->dim_page, fbdev->min_reg, fbdev->max_reg + 1);
+}
+
+static int aw200xx_update_dirty_page(struct aw200xx *chip,
+ struct aw200xx_page *page)
+{
+ int ret;
+
+ if (!IS_PAGE_DIRTY(page))
+ return 0;
+
+ ret = regmap_raw_write(chip->regmap, page->page + page->dirty_start,
+ page->buf + page->dirty_start,
+ page->dirty_end - page->dirty_start);
+ if (ret)
+ return ret;
+
+ page->dirty_start = 0;
+ page->dirty_end = 0;
+
+ return 0;
+}
+
+static void aw200xx_update(struct work_struct *work)
+{
+ struct aw200xx *chip = container_of(work, struct aw200xx, drawer.work);
+
+ mutex_lock(&chip->mutex);
+
+ if (atomic_read(&chip->fb.open_count))
+ aw200xx_fb_add_screen_dirty_range(chip);
+
+ aw200xx_update_dirty_page(chip, &chip->dim_page);
+ aw200xx_update_dirty_page(chip, &chip->fade_page);
+
+ if (atomic_read(&chip->fb.open_count))
+ aw200xx_fb_queue(chip);
+
+ mutex_unlock(&chip->mutex);
+}
+
static int aw200xx_brightness_set(struct led_classdev *cdev,
enum led_brightness brightness)
{
@@ -212,6 +965,87 @@ out_unlock:
return ret;
}
+static int aw200xx_brightness_set_queue(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct aw200xx_led *led = container_of(cdev, struct aw200xx_led, cdev);
+ struct aw200xx *chip = led->chip;
+ u32 reg = AW200XX_LED2REG(led->num, chip->cdef->display_size_columns);
+
+ mutex_lock(&chip->mutex);
+
+ if (led->dim < 0) {
+ int dim = AW200XX_REG_FADE2DIM(brightness);
+ chip->dim_page.buf[reg] = dim;
+ add_dirty_range(&chip->dim_page, reg, reg + 1);
+ }
+
+ chip->fade_page.buf[reg] = brightness;
+ add_dirty_range(&chip->fade_page, reg, reg + 1);
+
+ mutex_unlock(&chip->mutex);
+
+ schedule_delayed_work(&chip->drawer, 0);
+
+ return 0;
+}
+
+static irqreturn_t aw200xx_irq_thread(int irq, void *dev_id)
+{
+ struct aw200xx *chip = dev_id;
+ unsigned long pattern_state;
+ u32 interrupt_state;
+ int i;
+ int ret;
+
+ mutex_lock(&chip->mutex);
+ ret = regmap_read(chip->regmap, AW200XX_REG_ISRFLT, &interrupt_state);
+ mutex_unlock(&chip->mutex);
+
+ if (ret) {
+ dev_err(&chip->client->dev,
+ "Failed to get interrupt status: %d\n", ret);
+ return IRQ_HANDLED;
+ }
+
+ pattern_state = FIELD_GET(AW200XX_ISRFLT_PATIS_MASK, interrupt_state);
+
+ for_each_set_bit(i, &pattern_state, AW200XX_PATTERN_MAX) {
+ char dir[sizeof("patternx")];
+
+ snprintf(dir, sizeof(dir), "pattern%d", i);
+ sysfs_notify(&chip->client->dev.kobj, dir, "running");
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int aw200xx_setup_interrupts(struct aw200xx *chip)
+{
+ struct i2c_client *i2c = chip->client;
+ int ret;
+
+ if (i2c->irq <= 0)
+ return 0;
+
+ ret = devm_request_threaded_irq(&i2c->dev, i2c->irq,
+ NULL,
+ aw200xx_irq_thread,
+ IRQF_ONESHOT,
+ i2c->name,
+ chip);
+ if (ret)
+ return dev_err_probe(&i2c->dev, ret, "Failed to request irq\n");
+
+ ret = regmap_update_bits(chip->regmap, AW200XX_REG_PATCR,
+ AW200XX_PATCR_PAT_IE_MASK,
+ AW200XX_PATCR_PAT_IE_ALL);
+ if (ret)
+ dev_err_probe(&i2c->dev, ret, "Failed to enable interrupts\n");
+
+ return ret;
+}
+
static u32 aw200xx_imax_from_global(const struct aw200xx *const chip,
u32 global_imax_uA)
{
@@ -332,6 +1166,10 @@ static int aw200xx_chip_init(const struct aw200xx *const chip)
{
int ret;
+ ret = aw200xx_set_imax(chip, chip->imax);
+ if (ret)
+ return ret;
+
ret = regmap_write(chip->regmap, AW200XX_REG_DSIZE,
chip->display_rows - 1);
if (ret)
@@ -382,51 +1220,27 @@ static void aw200xx_disable(const struct aw200xx *const chip)
gpiod_set_value_cansleep(chip->hwen, 0);
}
-static int aw200xx_probe_get_display_rows(struct device *dev,
- struct aw200xx *chip)
+static int aw200xx_get_display_rows(struct aw200xx *chip, int max_reg)
{
- struct fwnode_handle *child;
- u32 max_source = 0;
-
- device_for_each_child_node(dev, child) {
- u32 source;
- int ret;
-
- ret = fwnode_property_read_u32(child, "reg", &source);
- if (ret || source >= chip->cdef->channels)
- continue;
-
- max_source = max(max_source, source);
- }
-
- if (max_source == 0)
- return -EINVAL;
-
- chip->display_rows = max_source / chip->cdef->display_size_columns + 1;
-
- return 0;
+ return max_reg / chip->cdef->display_size_columns + 1;
}
-static int aw200xx_probe_fw(struct device *dev, struct aw200xx *chip)
+static void aw200xx_probe_dt_leds(struct device *dev, struct aw200xx *chip,
+ struct aw200xx_led_data *leds, bool *keep_state)
{
u32 current_min, current_max, min_uA;
+ int count = 0;
+ int max_source = -1;
int ret;
int i;
- ret = aw200xx_probe_get_display_rows(dev, chip);
- if (ret)
- return dev_err_probe(dev, ret,
- "No valid led definitions found\n");
-
- current_max = aw200xx_imax_from_global(chip, AW200XX_IMAX_MAX_uA);
- current_min = aw200xx_imax_from_global(chip, AW200XX_IMAX_MIN_uA);
min_uA = UINT_MAX;
- i = 0;
device_for_each_child_node_scoped(dev, child) {
- struct led_init_data init_data = {};
- struct aw200xx_led *led;
- u32 source, imax;
+ struct aw200xx_led_data *led = &leds[count++];
+ u32 source;
+
+ led->num = -1;
ret = fwnode_property_read_u32(child, "reg", &source);
if (ret) {
@@ -442,29 +1256,94 @@ static int aw200xx_probe_fw(struct device *dev, struct aw200xx *chip)
continue;
}
+ max_source = max_t(int, max_source, source);
+
ret = fwnode_property_read_u32(child, "led-max-microamp",
- &imax);
+ &led->imax);
if (ret) {
dev_info(&chip->client->dev,
"DT property led-max-microamp is missing\n");
- } else if (imax < current_min || imax > current_max) {
+ } else {
+ min_uA = min(min_uA, led->imax);
+ }
+
+ led->default_state = led_init_default_state_get(child);
+ led->num = source;
+ }
+
+ if (max_source < 0)
+ return;
+
+ chip->display_rows = aw200xx_get_display_rows(chip, max_source);
+ current_max = aw200xx_imax_from_global(chip, AW200XX_IMAX_MAX_uA);
+ current_min = aw200xx_imax_from_global(chip, AW200XX_IMAX_MIN_uA);
+
+ for (i = 0; i < count; i++) {
+ struct aw200xx_led_data *led = &leds[i];
+
+ if (led->num < 0)
+ continue;
+
+ if (led->imax &&
+ (led->imax < current_min || led->imax > current_max)) {
dev_err(dev, "Invalid value %u for led-max-microamp\n",
- imax);
+ led->imax);
chip->num_leds--;
+ led->num = -1;
continue;
- } else {
- min_uA = min(min_uA, imax);
}
+ if (led->default_state == LEDS_DEFSTATE_KEEP)
+ *keep_state = true;
+ }
+
+ if (min_uA == U32_MAX)
+ min_uA = aw200xx_imax_from_global(chip, AW200XX_IMAX_DEFAULT_uA);
+
+ chip->imax = min_uA;
+}
+
+static void aw200xx_probe_register_leds(struct device *dev, struct aw200xx *chip,
+ const struct aw200xx_led_data *led_data,
+ bool keep_state)
+{
+ int i = 0, data_index = 0;
+
+ if (chip->queue_mode)
+ dev_attr_dim.store = dim_store_queue;
+
+ device_for_each_child_node_scoped(dev, child) {
+ struct led_init_data init_data = {};
+ struct aw200xx_led *led;
+ const struct aw200xx_led_data *data = &led_data[data_index++];
+ int ret;
+
+ if (data->num < 0)
+ continue;
+
led = &chip->leds[i];
led->dim = -1;
- led->num = source;
+ led->num = data->num;
led->chip = chip;
- led->cdev.brightness_set_blocking = aw200xx_brightness_set;
+ led->cdev.brightness_set_blocking = chip->queue_mode ?
+ aw200xx_brightness_set_queue : aw200xx_brightness_set;
led->cdev.max_brightness = AW200XX_FADE_MAX;
led->cdev.groups = dim_groups;
init_data.fwnode = child;
+ switch (led_data->default_state) {
+ case LEDS_DEFSTATE_ON:
+ led->cdev.brightness = AW200XX_FADE_MAX;
+ aw200xx_brightness_set(&led->cdev, AW200XX_FADE_MAX);
+ break;
+ case LEDS_DEFSTATE_OFF:
+ if (keep_state)
+ aw200xx_brightness_set(&led->cdev, 0);
+ break;
+ default:
+ break;
+ }
+
ret = devm_led_classdev_register_ext(dev, &led->cdev,
&init_data);
if (ret)
@@ -472,16 +1351,317 @@ static int aw200xx_probe_fw(struct device *dev, struct aw200xx *chip)
i++;
}
+ chip->num_pattern_leds += chip->num_leds;
+
+ return;
+}
+
+static int aw200xx_bl_update_status(struct backlight_device *bl)
+{
+ struct aw200xx *chip = bl_get_data(bl);
+ int brightness = bl->props.brightness;
+
+ if (bl->props.power != FB_BLANK_UNBLANK ||
+ bl->props.state & BL_CORE_FBBLANK)
+ brightness = 0;
+
+ mutex_lock(&chip->mutex);
+
+ chip->fb.fade_scale = brightness;
+ aw200xx_fb_add_screen_dirty_range(chip);
- if (!chip->num_leds)
+ mutex_unlock(&chip->mutex);
+
+ schedule_delayed_work(&chip->drawer, 0);
+
+ return 0;
+}
+
+static bool aw200xx_bl_controls_device(struct backlight_device *bl,
+ struct device *display_dev)
+{
+ const struct aw200xx *chip = bl_get_data(bl);
+
+ return display_dev == &chip->client->dev;
+}
+
+static const struct backlight_ops aw200xx_bl_ops = {
+ .update_status = aw200xx_bl_update_status,
+ .controls_device = aw200xx_bl_controls_device,
+};
+
+static int aw200xx_fb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info)
+{
+ struct aw200xx *chip = info->par;
+
+ mutex_lock(&chip->mutex);
+ chip->fb.offset_y = var->yoffset;
+ mutex_unlock(&chip->mutex);
+
+ return 0;
+}
+
+static int aw200xx_fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
+{
+ struct aw200xx *chip = info->par;
+
+ return vm_insert_page(vma, vma->vm_start,
+ virt_to_page(chip->fb.buffer));
+}
+
+static int aw200xx_fb_open(struct fb_info *info, int user)
+{
+ struct aw200xx *chip = info->par;
+ int i;
+
+ mutex_lock(&chip->mutex);
+
+ for (i = 0; i < AW200XX_PATTERN_MAX; i++)
+ if (find_next_bit(chip->pattern_leds[i], chip->num_pattern_leds,
+ chip->num_leds) != chip->num_pattern_leds) {
+ mutex_unlock(&chip->mutex);
+ return -EBUSY;
+ }
+
+ atomic_inc(&chip->fb.open_count);
+ mutex_unlock(&chip->mutex);
+
+ schedule_delayed_work(&chip->drawer, 0);
+
+ return 0;
+}
+
+static int aw200xx_fb_release(struct fb_info *info, int user)
+{
+ struct aw200xx *chip = info->par;
+
+ if (atomic_dec_and_test(&chip->fb.open_count)) {
+ mutex_lock(&chip->mutex);
+ aw200xx_fb_add_screen_dirty_range(chip);
+ aw200xx_fb_queue(chip);
+ mutex_unlock(&chip->mutex);
+ }
+ return 0;
+}
+
+static const struct fb_fix_screeninfo aw200xx_fb_fix = {
+ .id = "aw200xx-fb",
+ .type = FB_TYPE_PACKED_PIXELS,
+ .visual = FB_VISUAL_PSEUDOCOLOR,
+ .accel = FB_ACCEL_NONE,
+};
+
+static const struct fb_var_screeninfo aw200xx_fb_var = {
+ .bits_per_pixel = 8,
+ .grayscale = 1,
+ .red = { 0, 255, 0 },
+ .green = { 0, 255, 0 },
+ .blue = { 0, 255, 0 },
+ .vmode = FB_VMODE_NONINTERLACED,
+};
+
+static const struct fb_ops aw200xx_fb_ops = {
+ .owner = THIS_MODULE,
+ .fb_open = aw200xx_fb_open,
+ .fb_release = aw200xx_fb_release,
+ .fb_read = fb_sys_read,
+ .fb_write = fb_sys_write,
+ .fb_fillrect = sys_fillrect,
+ .fb_copyarea = sys_copyarea,
+ .fb_imageblit = sys_imageblit,
+ .fb_mmap = aw200xx_fb_mmap,
+ .fb_pan_display = aw200xx_fb_pan_display,
+};
+
+static void aw200xx_probe_find_fb_reg_range(struct aw200xx *chip)
+{
+ struct aw200xx_fb *fbdev = &chip->fb;
+ int i;
+
+ fbdev->max_reg = 0;
+ fbdev->min_reg = chip->cdef->channels - 1;
+
+ for (i = 0; i < fbdev->height * fbdev->width; i++) {
+ const uint8_t reg = fbdev->map[i];
+
+ fbdev->max_reg = max(fbdev->max_reg, reg);
+ fbdev->min_reg = min(fbdev->min_reg, reg);
+ }
+}
+
+static int aw200xx_probe_add_backlight(struct aw200xx *chip, struct device *dev)
+{
+ struct aw200xx_fb *fbdev = &chip->fb;
+ struct device_node *np = dev_of_node(dev);
+ struct backlight_properties bl_props;
+ u32 brightness = AW200XX_FADE_MAX;
+
+ of_property_read_u32(np, "fb-backlight", &brightness);
+ if (brightness > AW200XX_FADE_MAX) {
+ dev_err(dev, "property 'fb-backlight' is out of range [0-%d]\n",
+ (int)AW200XX_FADE_MAX);
return -EINVAL;
+ }
+
+ fbdev->fade_scale = brightness;
- if (min_uA == UINT_MAX) {
- min_uA = aw200xx_imax_from_global(chip,
- AW200XX_IMAX_DEFAULT_uA);
+ memset(&bl_props, 0, sizeof(struct backlight_properties));
+ bl_props.type = BACKLIGHT_RAW;
+ bl_props.max_brightness = AW200XX_FADE_MAX;
+ bl_props.brightness = brightness;
+
+ fbdev->bl = devm_backlight_device_register(dev, "aw200xx-bl",
+ dev, chip,
+ &aw200xx_bl_ops,
+ &bl_props);
+ if (IS_ERR(fbdev->bl)) {
+ dev_err(dev, "failed to register backlight\n");
+ return PTR_ERR(fbdev->bl);
}
- return aw200xx_set_imax(chip, min_uA);
+ return 0;
+}
+
+static int dev_err_prop(struct device *dev, const char *prop_name, int err)
+{
+ dev_err(dev, "Failed to read '%s' property: %d\n",
+ prop_name, err);
+ return err;
+}
+
+static int aw200xx_probe_dt_fb(struct aw200xx *chip, struct device *dev)
+{
+ struct aw200xx_fb *fbdev = &chip->fb;
+ struct device_node *np = dev_of_node(dev);
+ u32 imax, size;
+ int ret, display_rows;
+
+ if (!of_property_present(np, "fb-map"))
+ return 0;
+
+ ret = of_property_read_u32(np, "fb-width", &fbdev->width);
+ if (ret)
+ return dev_err_prop(dev, "fb-width", -EINVAL);
+
+ ret = of_property_read_u32(np, "fb-height", &fbdev->height);
+ if (ret)
+ return dev_err_prop(dev, "fb-height", -EINVAL);
+
+ ret = of_property_read_u32(np, "fb-refresh-rate", &fbdev->refresh_rate);
+ if (ret)
+ return dev_err_prop(dev, "fb-refresh-rate", -EINVAL);
+
+ size = of_property_count_u8_elems(np, "fb-map");
+ if (size < 0)
+ return dev_err_prop(dev, "fb-map", -EINVAL);
+
+ if (size > chip->cdef->channels) {
+ dev_err(dev, "Display size %d is greater than led count\n", size);
+ return -EINVAL;
+ }
+
+ if (size != fbdev->height * fbdev->width) {
+ dev_err(dev, "Map size (%d) is not equal to display size\n", size);
+ return -EINVAL;
+ }
+
+ fbdev->map = devm_kzalloc(dev, size, GFP_KERNEL);
+ if (!fbdev->map)
+ return -ENOMEM;
+
+ ret = of_property_read_u8_array(np, "fb-map", fbdev->map, size);
+ if (ret)
+ return ret;
+
+ aw200xx_probe_find_fb_reg_range(chip);
+ display_rows = aw200xx_get_display_rows(chip, fbdev->max_reg);
+ chip->display_rows = max(display_rows, chip->display_rows);
+
+ ret = of_property_read_u32(np, "fb-led-max-microamp", &imax);
+ if (!ret) {
+ const u32 current_min =
+ aw200xx_imax_from_global(chip, AW200XX_IMAX_MIN_uA);
+ const u32 current_max =
+ aw200xx_imax_from_global(chip, AW200XX_IMAX_MAX_uA);
+ if (imax < current_min || imax > current_max) {
+ dev_err(dev, "Framebuffer led-max-microamp is out of range\n");
+ return -EINVAL;
+ }
+ chip->imax = min(chip->imax, imax);
+ } else {
+ dev_info(dev, "DT property led-max-microamp is missing\n");
+ }
+
+ chip->queue_mode = true;
+ chip->fb.num_leds = size;
+
+ return 0;
+}
+
+static int aw200xx_probe_register_fb(struct device *dev, struct aw200xx *chip)
+{
+ struct aw200xx_fb *fbdev = &chip->fb;
+ const int size = fbdev->width * fbdev->width;
+ int ret;
+
+ if (!fbdev->num_leds)
+ return 0;
+
+ if (size * FB_BUFFER_COUNT > PAGE_SIZE)
+ return -EINVAL;
+
+ fbdev->buffer = (uint8_t *)get_zeroed_page(GFP_KERNEL);
+ if (!fbdev->buffer)
+ return -ENOMEM;
+
+ fbdev->info = framebuffer_alloc(0, dev);
+ if (!fbdev->info) {
+ ret = -ENOMEM;
+ goto err_fb_alloc;
+ }
+
+ fbdev->info->fbops = &aw200xx_fb_ops;
+ fbdev->info->screen_buffer = (char __iomem *)fbdev->buffer;
+ fbdev->info->screen_size = size;
+ fbdev->info->fix = aw200xx_fb_fix;
+ fbdev->info->var = aw200xx_fb_var;
+ fbdev->info->pseudo_palette = NULL;
+ fbdev->info->flags = FBINFO_VIRTFB;
+ fbdev->info->par = chip;
+
+ fbdev->info->fix.smem_len = size * FB_BUFFER_COUNT;
+ fbdev->info->fix.line_length = fbdev->width;
+ fbdev->info->fix.ypanstep = fbdev->height;
+
+ fbdev->info->var.xres = fbdev->width;
+ fbdev->info->var.yres = fbdev->height;
+ fbdev->info->var.xres_virtual = fbdev->width;
+ fbdev->info->var.yres_virtual = fbdev->height * FB_BUFFER_COUNT;
+
+ ret = register_framebuffer(fbdev->info);
+ if (ret) {
+ dev_err(dev, "Register framebuffer failed\n");
+ goto err_register_fb;
+ }
+
+ ret = aw200xx_probe_add_backlight(chip, dev);
+ if (ret)
+ goto err_probe_bl;
+
+ chip->num_pattern_leds += fbdev->num_leds;
+
+ return 0;
+
+err_probe_bl:
+ unregister_framebuffer(fbdev->info);
+
+err_register_fb:
+ framebuffer_release(fbdev->info);
+
+err_fb_alloc:
+ free_page((unsigned long) fbdev->buffer);
+
+ return ret;
}
static const struct regmap_range_cfg aw200xx_ranges[] = {
@@ -508,6 +1688,7 @@ static const struct regmap_access_table aw200xx_readable_table = {
static const struct regmap_range aw200xx_readonly_ranges[] = {
regmap_reg_range(AW200XX_REG_IDR, AW200XX_REG_IDR),
+ regmap_reg_range(AW200XX_REG_ISRFLT, AW200XX_REG_ISRFLT),
};
static const struct regmap_access_table aw200xx_writeable_table = {
@@ -515,6 +1696,16 @@ static const struct regmap_access_table aw200xx_writeable_table = {
.n_no_ranges = ARRAY_SIZE(aw200xx_readonly_ranges),
};
+static const struct regmap_range aw200xx_volatile_registers[] = {
+ regmap_reg_range(AW200XX_REG_ISRFLT, AW200XX_REG_ISRFLT),
+ regmap_reg_range(AW200XX_REG_PATGO, AW200XX_REG_PATGO),
+};
+
+static const struct regmap_access_table aw200xx_volatile_table = {
+ .yes_ranges = aw200xx_volatile_registers,
+ .n_yes_ranges = ARRAY_SIZE(aw200xx_volatile_registers),
+};
+
static const struct regmap_config aw200xx_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
@@ -523,6 +1714,7 @@ static const struct regmap_config aw200xx_regmap_config = {
.num_ranges = ARRAY_SIZE(aw200xx_ranges),
.rd_table = &aw200xx_readable_table,
.wr_table = &aw200xx_writeable_table,
+ .volatile_table = &aw200xx_volatile_table,
.cache_type = REGCACHE_MAPLE,
.disable_locking = true,
};
@@ -541,6 +1733,8 @@ static int aw200xx_probe(struct i2c_client *client)
{
const struct aw200xx_chipdef *cdef;
struct aw200xx *chip;
+ struct aw200xx_led_data *leds;
+ bool keep_state = false;
int count;
int ret;
@@ -563,6 +1757,40 @@ static int aw200xx_probe(struct i2c_client *client)
chip->client = client;
i2c_set_clientdata(client, chip);
+ leds = devm_kcalloc(&client->dev, count, sizeof(struct aw200xx_led_data),
+ GFP_KERNEL);
+ if (!leds)
+ return -ENOMEM;
+
+ aw200xx_probe_dt_leds(&client->dev, chip, leds, &keep_state);
+ ret = aw200xx_probe_dt_fb(chip, &client->dev);
+ if (ret) {
+ dev_err_probe(&client->dev, ret, "Framebuffer probe failed\n");
+ return ret;
+ }
+
+ if (!chip->num_leds && !chip->fb.num_leds)
+ return dev_err_probe(&client->dev, -EINVAL,
+ "No valid led definitions found\n");
+
+ if (chip->queue_mode) {
+ chip->dim_page.buf = devm_kzalloc(&client->dev,
+ chip->cdef->channels, GFP_KERNEL);
+ if (!chip->dim_page.buf)
+ return -ENOMEM;
+
+ chip->dim_page.page = AW200XX_REG(AW200XX_PAGE1, 0);
+
+ chip->fade_page.buf = devm_kzalloc(&client->dev,
+ chip->cdef->channels, GFP_KERNEL);
+ if (!chip->fade_page.buf)
+ return -ENOMEM;
+
+ chip->fade_page.page = AW200XX_REG(AW200XX_PAGE2, 0);
+
+ INIT_DELAYED_WORK(&chip->drawer, aw200xx_update);
+ }
+
chip->regmap = devm_regmap_init_i2c(client, &aw200xx_regmap_config);
if (IS_ERR(chip->regmap))
return PTR_ERR(chip->regmap);
@@ -587,29 +1815,53 @@ static int aw200xx_probe(struct i2c_client *client)
if (ret)
return ret;
- /* Need a lock now since after call aw200xx_probe_fw, sysfs nodes created */
- mutex_lock(&chip->mutex);
-
- ret = aw200xx_chip_reset(chip);
- if (ret)
- goto out_unlock;
+ if (!keep_state) {
+ ret = aw200xx_chip_reset(chip);
+ if (ret)
+ return ret;
+ }
ret = devm_add_action(&client->dev, aw200xx_chip_reset_action, chip);
if (ret)
- goto out_unlock;
+ return ret;
+
+ if (!keep_state) {
+ ret = aw200xx_chip_init(chip);
+ if (ret)
+ return ret;
+ }
- ret = aw200xx_probe_fw(&client->dev, chip);
+ ret = aw200xx_setup_interrupts(chip);
if (ret)
- goto out_unlock;
+ return ret;
- ret = aw200xx_chip_init(chip);
+ aw200xx_probe_register_leds(&client->dev, chip, leds, keep_state);
-out_unlock:
+ ret = aw200xx_probe_register_fb(&client->dev, chip);
if (ret)
- aw200xx_disable(chip);
+ return ret;
- mutex_unlock(&chip->mutex);
- return ret;
+ devm_kfree(&client->dev, leds);
+
+ return 0;
+}
+
+static void aw200xx_remove(struct i2c_client *client)
+{
+ struct aw200xx *chip = i2c_get_clientdata(client);
+ struct aw200xx_fb *fbdev = &chip->fb;
+
+ if (fbdev) {
+ mutex_lock(&chip->mutex);
+ devm_backlight_device_unregister(&client->dev, fbdev->bl);
+ unregister_framebuffer(fbdev->info);
+ framebuffer_release(fbdev->info);
+ free_page((unsigned long) fbdev->buffer);
+ mutex_unlock(&chip->mutex);
+ }
+
+ if (chip->queue_mode)
+ disable_delayed_work_sync(&chip->drawer);
}
static const struct aw200xx_chipdef aw20036_cdef = {
@@ -658,8 +1910,10 @@ static struct i2c_driver aw200xx_driver = {
.driver = {
.name = "aw200xx",
.of_match_table = aw200xx_match_table,
+ .dev_groups = aw200xx_pattern_groups,
},
.probe = aw200xx_probe,
+ .remove = aw200xx_remove,
.id_table = aw200xx_id,
};
module_i2c_driver(aw200xx_driver);
diff --git a/drivers/mtd/nand/raw/Kconfig b/drivers/mtd/nand/raw/Kconfig
index 7408f34f0c68a..17f4a13f51063 100644
--- a/drivers/mtd/nand/raw/Kconfig
+++ b/drivers/mtd/nand/raw/Kconfig
@@ -355,6 +355,17 @@ config MTD_NAND_MESON
Enables support for NAND controller on Amlogic's Meson SoCs.
This controller is found on Meson SoCs.
+config MTD_NAND_AMLOGIC_BBT
+ bool "Use Amlogic format for bad block table"
+ depends on MTD_NAND_MESON
+ default n
+ help
+ Forces to use Amlogic format for bad block table. Amlogic's
+ bad block table has another values to mark good blocks and
+ another magic. Also it is located in predefined area in the
+ NAND chip. This option will overload default kernel API for
+ NAND bad block table management.
+
config MTD_NAND_GPIO
tristate "GPIO assisted NAND controller"
depends on GPIOLIB || COMPILE_TEST
diff --git a/drivers/mtd/nand/raw/Makefile b/drivers/mtd/nand/raw/Makefile
index 619760138d322..97e6ec23c60ac 100644
--- a/drivers/mtd/nand/raw/Makefile
+++ b/drivers/mtd/nand/raw/Makefile
@@ -60,7 +60,7 @@ obj-$(CONFIG_MTD_NAND_RENESAS) += renesas-nand-controller.o
obj-$(CONFIG_MTD_NAND_NUVOTON_MA35) += nuvoton-ma35d1-nand-controller.o
obj-$(CONFIG_MTD_NAND_LOONGSON) += loongson-nand-controller.o
-nand-objs := nand_base.o nand_legacy.o nand_bbt.o nand_timings.o nand_ids.o
+nand-objs := nand_base.o nand_legacy.o nand_timings.o nand_ids.o
nand-objs += nand_onfi.o
nand-objs += nand_jedec.o
nand-objs += nand_amd.o
@@ -71,3 +71,9 @@ nand-objs += nand_micron.o
nand-objs += nand_sandisk.o
nand-objs += nand_samsung.o
nand-objs += nand_toshiba.o
+
+ifeq ($(CONFIG_MTD_NAND_AMLOGIC_BBT),y)
+nand-objs += nand_bbt_amlogic.o
+else
+nand-objs += nand_bbt.o
+endif
diff --git a/drivers/mtd/nand/raw/meson_nand.c b/drivers/mtd/nand/raw/meson_nand.c
index b8834aa96e81c..a265b7a7d7330 100644
--- a/drivers/mtd/nand/raw/meson_nand.c
+++ b/drivers/mtd/nand/raw/meson_nand.c
@@ -1310,6 +1310,11 @@ static void meson_nand_detach_chip(struct nand_chip *nand)
meson_nfc_free_buffer(nand);
}
+static int __nand_write_oob_std(struct nand_chip *chip, int page)
+{
+ return 0;
+}
+
static int meson_nand_attach_chip(struct nand_chip *nand)
{
struct meson_nfc *nfc = nand_get_controller_data(nand);
@@ -1355,8 +1360,8 @@ static int meson_nand_attach_chip(struct nand_chip *nand)
nand->ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST;
nand->ecc.write_page_raw = meson_nfc_write_page_raw;
nand->ecc.write_page = meson_nfc_write_page_hwecc;
- nand->ecc.write_oob_raw = nand_write_oob_std;
- nand->ecc.write_oob = nand_write_oob_std;
+ nand->ecc.write_oob_raw = __nand_write_oob_std;
+ nand->ecc.write_oob = __nand_write_oob_std;
nand->ecc.read_page_raw = meson_nfc_read_page_raw;
nand->ecc.read_page = meson_nfc_read_page_hwecc;
diff --git a/drivers/mtd/nand/raw/nand_bbt_amlogic.c b/drivers/mtd/nand/raw/nand_bbt_amlogic.c
new file mode 100644
index 0000000000000..e06cc15146e16
--- /dev/null
+++ b/drivers/mtd/nand/raw/nand_bbt_amlogic.c
@@ -0,0 +1,881 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Amlogic format bad block table parser
+ *
+ * Copyright (c) 2023, SberDevices. All Rights Reserved.
+ *
+ * Author: Arseniy Krasnov <avkrasnov@sberdevices.ru>
+ */
+#include <linux/kernel.h>
+#include <linux/bitmap.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/rawnand.h>
+
+#include "internals.h"
+
+/* "In-memory" state of each block. */
+#define BBT_BLOCK_GOOD 0x00
+#define BBT_BLOCK_WORN 0x01
+#define BBT_BLOCK_RESERVED 0x02
+#define BBT_BLOCK_FACTORY_BAD 0x03
+
+#define BBT_ENTRY_MASK 0x03
+#define BBT_BITS_PER_BLOCK 2
+
+/* "On NAND" state of each block. */
+#define AMLOGIC_BLOCK_WORN_BAD 0x01
+#define AMLOGIC_BLOCK_FACTORY_BAD 0x02
+
+/* Amlogic reserved region is usually located right after BL2 partition:
+ * +-----------+-----------------+-------------+--------+
+ * | | | | |
+ * | BL2 | RESERVED | TPL | ... |
+ * | | | | |
+ * +-----------+-----------------+-------------+--------+
+ * 1024 pages 48 erase blocks 8MB
+ */
+ #define BL2_TOTAL_PAGES 1024
+ #define AML_RSV_BLOCKS 48
+/*
+ * +-----+-----+-----------+-----------+-----+-----------+
+ * | | | | | | |
+ * | GAP | BBT | ENV | KEY | DTB | ... |
+ * | | | | | | |
+ * +-----+-----+-----------+-----------+-----+-----------+
+ * eraseblocks: 4 4 8 8 4 20
+ */
+#define AML_RSV_GAP_BLOCKS 4
+#define AML_RSV_BBT_BLOCKS 4
+
+#define AML_RSV_BBT_BLOCK_MAGIC "nbbt"
+
+#define AML_RSV_BBT_START_BLOCK(_start) (AML_RSV_GAP_BLOCKS + (_start))
+
+#define POWER_ABNORMAL_FLAG BIT(0)
+#define ECC_ABNORMAL_FLAG BIT(1)
+#define ALL_ABNORMAL_FLAG (POWER_ABNORMAL_FLAG | ECC_ABNORMAL_FLAG)
+
+#define is_abnormal(_info) ((_info)->valid_record.status & ALL_ABNORMAL_FLAG)
+
+struct aml_oobinfo {
+ char name[4];
+ s16 ec; /* erase counter */
+ u32 timestamp:15;
+ u32 status_page:1;
+} __packed;
+
+/**
+ * struct aml_eb - eraseblock descriptor
+ * @num: eraseblock number
+ * @ec: erase counter
+ * @timestamp: record Number. The newer the record, the higher this value is.
+ */
+struct aml_eb {
+ int num;
+ s16 ec;
+ int timestamp;
+};
+
+/**
+ * struct aml_record - record descriptor
+ * @page: a page within the eraseblock
+ * @status: record status (e.g. ecc fail)
+ * @eb: descriptor of current eraseblock
+ */
+struct aml_record {
+ int page;
+ int status;
+ struct aml_eb eb;
+};
+
+/**
+ * struct aml_free_eb - free eraseblock descriptor
+ * @link: list link
+ * @eb: descriptor of current eraseblock
+ * @useful: indicates whether the current eraseblock stores an old record(s)
+ *
+ * A eraseblock with an inactive (old) record is also considered a free.
+ * The @useful field is set to true for such a block.
+ */
+struct aml_free_eb {
+ struct list_head link;
+ struct aml_eb eb;
+ bool useful;
+};
+
+/**
+ * struct aml_rsv_info - reserved information area descriptor (e.g. env, key)
+ * @dev: struct device pointer
+ * @mtd: the master MTD device descriptor
+ * @valid_record: the current record descriptor (the newest). This field may be
+ * invalid if the current reserved area is empty. The validity of this
+ * field is indicated by the @valid field.
+ * @free_ebs: list of free physical eraseblocks
+ * @start_eb: first eraseblock of the current reserved area
+ * @end_eb: last eraseblock of the current reserved area
+ * @size: size of one record
+ * @name: name of the current reserved area
+ * @valid: indicates the validity of the @valid_record field
+ */
+struct aml_rsv_info {
+ struct mtd_info *mtd;
+ struct aml_record valid_record;
+ struct list_head free_ebs;
+ u32 start_eb;
+ u32 end_eb;
+ size_t size;
+ char name[8];
+ bool valid;
+};
+
+static struct aml_rsv_ctx {
+ struct aml_rsv_info bbt;
+} aml_rsv_areas = {
+ .bbt = {
+ .valid_record.eb.num = -1,
+ .free_ebs = LIST_HEAD_INIT(aml_rsv_areas.bbt.free_ebs),
+ .name = AML_RSV_BBT_BLOCK_MAGIC,
+ },
+};
+
+#define AML_RSV_MAX_FREE_EB_ENTRIES AML_RSV_BBT_BLOCKS
+static struct aml_free_eb free_eb_entries[AML_RSV_MAX_FREE_EB_ENTRIES];
+static DECLARE_BITMAP(free_eb_entries_in_use, AML_RSV_MAX_FREE_EB_ENTRIES);
+
+static struct aml_free_eb *alloc_free_eb(void)
+{
+ unsigned long nr;
+
+ nr = find_first_zero_bit(free_eb_entries_in_use,
+ AML_RSV_MAX_FREE_EB_ENTRIES);
+ if (nr >= AML_RSV_MAX_FREE_EB_ENTRIES)
+ return NULL;
+
+ set_bit(nr, free_eb_entries_in_use);
+
+ return &free_eb_entries[nr];
+}
+
+static void release_free_eb(const struct aml_free_eb *free_eb)
+{
+ unsigned long nr = (unsigned long)(free_eb - free_eb_entries);
+
+ if (!WARN_ON(nr >= AML_RSV_MAX_FREE_EB_ENTRIES))
+ clear_bit(nr, free_eb_entries_in_use);
+}
+
+static void dump_rsv_info(const struct aml_rsv_info *rsv_info)
+{
+ struct aml_free_eb *free_eb;
+
+ pr_info("%s: eraseblock=%d ec=%d page=%d timestamp=%d\n",
+ rsv_info->name, rsv_info->valid_record.eb.num,
+ rsv_info->valid_record.eb.ec, rsv_info->valid_record.page,
+ rsv_info->valid_record.eb.timestamp);
+
+ pr_info("%s free list:\n", rsv_info->name);
+ list_for_each_entry(free_eb, &rsv_info->free_ebs, link) {
+ pr_info("\teraseblock=%d ec=%d\n", free_eb->eb.num,
+ free_eb->eb.ec);
+ }
+}
+
+static bool is_valid_page(const struct aml_rsv_info *rsv_info,
+ const struct aml_oobinfo *oobinfo)
+{
+ return !memcmp(oobinfo->name, rsv_info->name, sizeof(oobinfo->name));
+}
+
+static void del_free_eb(struct aml_free_eb *free_eb)
+{
+ list_del(&free_eb->link);
+ release_free_eb(free_eb);
+}
+
+static int add_free_eb(struct aml_rsv_info *rsv_info, const struct aml_eb *eb,
+ bool useful)
+{
+ struct aml_free_eb *free_eb;
+
+ free_eb = alloc_free_eb();
+ if (!free_eb)
+ return -ENOMEM;
+
+ free_eb->eb = *eb;
+ free_eb->useful = useful;
+ list_add_tail(&free_eb->link, &rsv_info->free_ebs);
+
+ return 0;
+}
+
+static int add_eb(struct aml_rsv_info *rsv_info,
+ const struct aml_oobinfo *oobinfo, int eb_num)
+{
+ bool valid = is_valid_page(rsv_info, oobinfo);
+ struct aml_eb eb = {
+ .num = eb_num,
+ };
+
+ if (valid) {
+ rsv_info->valid = true;
+
+ eb.ec = oobinfo->ec;
+ eb.timestamp = oobinfo->timestamp;
+
+ if (rsv_info->valid_record.eb.num < 0) {
+ rsv_info->valid_record.eb = eb;
+ rsv_info->valid_record.page = 0;
+
+ return 0;
+ }
+
+ if (oobinfo->timestamp > rsv_info->valid_record.eb.timestamp) {
+ swap(rsv_info->valid_record.eb, eb);
+ rsv_info->valid_record.page = 0;
+ }
+ } else {
+ eb.ec = 0;
+ eb.timestamp = 0;
+ }
+
+ return add_free_eb(rsv_info, &eb, valid);
+}
+
+static int erase_valid_record(struct aml_rsv_info *rsv_info)
+{
+ struct mtd_info *mtd = rsv_info->mtd;
+ struct nand_chip *this = mtd_to_nand(mtd);
+ struct erase_info instr = {};
+ int ret;
+
+ instr.addr = rsv_info->valid_record.eb.num * mtd->erasesize;
+ instr.len = mtd->erasesize;
+
+ ret = nand_erase_nand(this, &instr, 1);
+ if (ret) {
+ pr_err("%s: Error %d while erasing offset %llu, size %llu\n",
+ rsv_info->name, ret, instr.addr, instr.len);
+ return ret;
+ }
+
+ rsv_info->valid_record.eb.ec++;
+ return 0;
+}
+
+static int next_valid_record(struct aml_rsv_info *rsv_info)
+{
+ struct aml_free_eb *free_eb;
+
+ if (list_empty(&rsv_info->free_ebs))
+ return -ENOENT;
+
+ free_eb = list_first_entry(&rsv_info->free_ebs, struct aml_free_eb,
+ link);
+
+ rsv_info->valid_record.eb.num = free_eb->eb.num;
+ rsv_info->valid_record.eb.ec = free_eb->eb.ec;
+ rsv_info->valid_record.eb.timestamp++;
+ rsv_info->valid_record.page = 0;
+
+ del_free_eb(free_eb);
+
+ return 0;
+}
+
+static int prev_valid_record(struct aml_rsv_info *rsv_info)
+{
+ struct aml_free_eb *free_eb, *cur = NULL;
+
+ list_for_each_entry(free_eb, &rsv_info->free_ebs, link) {
+ if (!free_eb->useful)
+ continue;
+
+ if (!cur || cur->eb.timestamp < free_eb->eb.timestamp)
+ cur = free_eb;
+ }
+
+ if (!cur)
+ return -ENOENT;
+
+ rsv_info->valid_record.eb = cur->eb;
+ rsv_info->valid_record.page = 0;
+ rsv_info->valid_record.status = 0;
+
+ del_free_eb(cur);
+
+ return 0;
+}
+
+static int find_latest_record(struct aml_rsv_info *rsv_info)
+{
+ struct mtd_info *mtd = rsv_info->mtd;
+ struct mtd_oob_ops ops = {};
+ struct aml_oobinfo oobinfo;
+ u32 npages, i = 0;
+ int pages_per_eb, last_page;
+ loff_t eb_off = rsv_info->valid_record.eb.num * mtd->erasesize;
+
+ ops.mode = MTD_OPS_AUTO_OOB;
+ ops.oobbuf = (u8 *)&oobinfo;
+ ops.ooblen = sizeof(oobinfo);
+
+ pages_per_eb = mtd_wunit_per_eb(mtd);
+ npages = mtd_div_by_ws(rsv_info->size + mtd->writesize - 1, mtd);
+ if (npages == 1)
+ last_page = 0;
+ else
+ last_page = -EIO;
+
+ while (i < pages_per_eb) {
+ loff_t off;
+ int err;
+
+ off = mtd_wunit_to_offset(mtd, eb_off, i++);
+
+ err = mtd_read_oob(mtd, off, &ops);
+ if ((err && !mtd_is_bitflip(err)) ||
+ ops.oobretlen != ops.ooblen) {
+ pr_warn("OOB read failure at %#llx (%zu of %zu read), error %d\n",
+ off, ops.oobretlen, ops.ooblen, err);
+ rsv_info->valid_record.status |= ECC_ABNORMAL_FLAG;
+ i = roundup(i, npages);
+ continue;
+ }
+
+ if (is_valid_page(rsv_info, &oobinfo)) {
+ if ((i % npages) == 0)
+ last_page = i - npages;
+ } else {
+ break;
+ }
+ }
+
+ if (((i - 1) % npages) != 0)
+ rsv_info->valid_record.status |= POWER_ABNORMAL_FLAG;
+
+ return last_page;
+}
+
+static void aml_rsv_free(struct aml_rsv_info *rsv_info)
+{
+ struct aml_free_eb *free_eb, *next;
+
+ list_for_each_entry_safe(free_eb, next, &rsv_info->free_ebs, link) {
+ del_free_eb(free_eb);
+ }
+}
+
+static int aml_rsv_write(struct aml_rsv_info *rsv_info, const u8 *buf)
+{
+ struct mtd_info *mtd = rsv_info->mtd;
+ struct mtd_oob_ops ops = {};
+ struct aml_oobinfo oobinfo;
+ size_t nwrite = 0;
+ loff_t off;
+
+ off = rsv_info->valid_record.eb.num * mtd->erasesize;
+ off = mtd_wunit_to_offset(mtd, off, rsv_info->valid_record.page);
+
+ memcpy(oobinfo.name, rsv_info->name, sizeof(oobinfo.name));
+ oobinfo.ec = rsv_info->valid_record.eb.ec;
+ oobinfo.timestamp = rsv_info->valid_record.eb.timestamp;
+ oobinfo.status_page = 0;
+
+ ops.mode = MTD_OPS_AUTO_OOB;
+ ops.oobbuf = (u8 *)&oobinfo;
+ ops.ooblen = sizeof(oobinfo);
+
+ while (nwrite < rsv_info->size) {
+ int ret;
+
+ ops.len = min_t(size_t, mtd->writesize,
+ rsv_info->size - nwrite);
+ ops.datbuf = (u8 *)buf + nwrite;
+
+ ret = mtd_write_oob(mtd, off, &ops);
+ if (ret) {
+ pr_err("Write failure at %#llx (%zu of %zu write), error %d\n",
+ off, ops.oobretlen, ops.ooblen, ret);
+ return ret;
+ }
+
+ off += mtd->writesize;
+ nwrite += ops.len;
+ }
+
+ return 0;
+}
+
+static int aml_rsv_save(struct aml_rsv_info *rsv_info, const u8 *buf)
+{
+ struct mtd_info *mtd = rsv_info->mtd;
+ struct nand_chip *this = mtd_to_nand(mtd);
+ u32 npages;
+ int pages_per_eb;
+ int ret;
+
+ pages_per_eb = mtd_wunit_per_eb(mtd);
+ npages = mtd_div_by_ws(rsv_info->size + mtd->writesize - 1, mtd);
+
+ while (true) {
+ loff_t off;
+
+ if (rsv_info->valid) {
+ int next_record_page = rsv_info->valid_record.page +
+ npages;
+
+ if (next_record_page + npages > pages_per_eb) {
+ if (is_abnormal(rsv_info))
+ erase_valid_record(rsv_info);
+
+ ret = add_free_eb(rsv_info,
+ &rsv_info->valid_record.eb,
+ true);
+ if (ret)
+ return ret;
+
+ ret = next_valid_record(rsv_info);
+ } else {
+ rsv_info->valid_record.page = next_record_page;
+ ret = 0;
+ }
+ } else {
+ ret = next_valid_record(rsv_info);
+ }
+
+ if (ret) {
+ pr_err("%s: No free eraseblock\n", rsv_info->name);
+ return -ENOSPC;
+ }
+
+ if (rsv_info->valid_record.page)
+ break;
+
+ off = rsv_info->valid_record.eb.num * mtd->erasesize;
+
+ if (nand_isbad_bbt(this, off, 1)) {
+ pr_warn("%s: Block %d is bad\n",rsv_info->name,
+ rsv_info->valid_record.eb.num);
+ rsv_info->valid = false;
+ continue;
+ }
+
+ ret = erase_valid_record(rsv_info);
+ if (ret)
+ return ret;
+
+ break;
+ }
+
+ ret = aml_rsv_write(rsv_info, buf);
+ if (ret)
+ return ret;
+
+ rsv_info->valid = true;
+ rsv_info->valid_record.status = 0;
+
+ return 0;
+}
+
+static int aml_rsv_read(struct aml_rsv_info *rsv_info, u8 *buf, size_t size,
+ size_t *ret_len)
+{
+ struct mtd_info *mtd = rsv_info->mtd;
+ struct mtd_oob_ops ops = {};
+ struct aml_oobinfo oobinfo;
+ size_t nread = 0;
+ loff_t off;
+
+ if (!rsv_info->valid) {
+ pr_info("Info %s is empty\n", rsv_info->name);
+ return -ENODATA;
+ }
+
+ ops.mode = MTD_OPS_AUTO_OOB;
+ ops.oobbuf = (u8 *)&oobinfo;
+ ops.ooblen = sizeof(oobinfo);
+
+ off = rsv_info->valid_record.eb.num * mtd->erasesize;
+ off = mtd_wunit_to_offset(mtd, off, rsv_info->valid_record.page);
+
+ size = min(size, rsv_info->size);
+
+ while (nread < size) {
+ int ret;
+
+ ops.len = min_t(size_t, mtd->writesize, size - nread);
+ ops.datbuf = buf + nread;
+
+ ret = mtd_read_oob(mtd, off, &ops);
+ if (ret && !mtd_is_bitflip(ret)) {
+ pr_err("Read failure at %#llx (%zu of %zu read), error %d\n",
+ off, ops.oobretlen, ops.ooblen, ret);
+ return ret;
+ }
+
+ if (!is_valid_page(rsv_info, &oobinfo)) {
+ pr_err("Invalid info %s magic at %#llx\n",
+ rsv_info->name, off);
+ return -EIO;
+ }
+
+ off += mtd->writesize;
+ nread += ops.len;
+ }
+
+ if (ret_len)
+ *ret_len = nread;
+
+ return 0;
+}
+
+/*
+ * The maximum number of attempts to read the first page of a record
+ * within a block in order to detect a valid block. If reading the page
+ * fails (e.g. due to ECC errors), the reader will move on to the first
+ * page of the next record in the same block, up to this limit.
+ */
+#define AML_RSV_READ_ATTEMPTS 6
+
+static bool is_valid_eb(struct aml_rsv_info *rsv_info, u32 eb,
+ struct aml_oobinfo *oobinfo)
+{
+ struct mtd_oob_ops ops = {};
+ struct mtd_info *mtd = rsv_info->mtd;
+ loff_t off = eb * mtd->erasesize;
+ bool readsuccess;
+ int attempts = 0;
+
+ ops.mode = MTD_OPS_AUTO_OOB;
+ ops.oobbuf = (u8 *)oobinfo;
+ ops.ooblen = sizeof(*oobinfo);
+
+ do {
+ int ret = mtd_read_oob(mtd, off, &ops);
+ readsuccess = (!ret || mtd_is_bitflip(ret)) &&
+ (ops.oobretlen == ops.ooblen);
+ if (readsuccess)
+ break;
+
+ pr_warn("OOB read failure at %#llx (%zu of %zu read), error %d\n",
+ off, ops.oobretlen, ops.ooblen, ret);
+
+ off += rsv_info->size;
+ } while (attempts++ < AML_RSV_READ_ATTEMPTS && mtd_mod_by_eb(off, mtd));
+
+ return readsuccess;
+}
+
+static int aml_rsv_scan(struct aml_rsv_info *rsv_info)
+{
+ struct aml_oobinfo oobinfo;
+ u32 eb;
+ int ret;
+
+ pr_info("%s: Scanning info from:%u to:%u size:%zu\n", rsv_info->name,
+ rsv_info->start_eb, rsv_info->end_eb, rsv_info->size);
+
+ for (eb = rsv_info->start_eb; eb < rsv_info->end_eb; eb++) {
+ if (!is_valid_eb(rsv_info, eb, &oobinfo)) {
+ pr_warn("ECC error. Skip eraseblock %u\n", eb);
+ continue;
+ }
+
+ ret = add_eb(rsv_info, &oobinfo, eb);
+ if (ret)
+ return ret;
+ }
+
+ if (!rsv_info->valid) {
+ pr_info("%s: is empty\n", rsv_info->name);
+ return -ENODATA;
+ }
+
+ do {
+ ret = find_latest_record(rsv_info);
+ if (ret >= 0) {
+ rsv_info->valid_record.page = ret;
+ ret = 0;
+ break;
+ }
+
+ pr_warn("%s: Can't find the latest info pages. Trying to find previous info pages\n",
+ rsv_info->name);
+
+ if (!erase_valid_record(rsv_info)) {
+ ret = add_free_eb(rsv_info, &rsv_info->valid_record.eb,
+ false);
+ if (ret)
+ break;
+ }
+
+ ret = prev_valid_record(rsv_info);
+ } while (!ret);
+
+ return ret;
+}
+
+static size_t aml_rsv_bbt_size(void)
+{
+ return aml_rsv_areas.bbt.size;
+}
+
+static int aml_rsv_bbt_read(u8 *buf, size_t size, size_t *ret_len)
+{
+ return aml_rsv_read(&aml_rsv_areas.bbt, buf, size, ret_len);
+}
+
+static int aml_rsv_bbt_write(const u8 *buf, size_t size)
+{
+ return aml_rsv_save(&aml_rsv_areas.bbt, buf);
+}
+
+static inline uint8_t bbt_get_entry(struct nand_chip *chip, int block)
+{
+ u8 entry = chip->bbt[block >> BBT_BITS_PER_BLOCK];
+
+ entry >>= (block & BBT_ENTRY_MASK) * BBT_BITS_PER_BLOCK;
+ return entry & BBT_ENTRY_MASK;
+}
+
+static inline void bbt_mark_entry(struct nand_chip *chip, int block,
+ uint8_t mark)
+{
+ u8 msk = (mark & BBT_ENTRY_MASK) <<
+ ((block & BBT_ENTRY_MASK) * BBT_BITS_PER_BLOCK);
+
+ chip->bbt[block >> BBT_BITS_PER_BLOCK] |= msk;
+}
+
+static int aml_rsv_init(struct nand_chip *this)
+{
+ struct mtd_info *mtd = nand_to_mtd(this);
+ u32 rsv_start_eb, phys_erase_shift;
+ int i;
+ int ret;
+
+ rsv_start_eb = BL2_TOTAL_PAGES / mtd_wunit_per_eb(mtd);
+ phys_erase_shift = ffs(mtd->erasesize) - 1;
+
+ aml_rsv_areas.bbt.mtd = mtd;
+ aml_rsv_areas.bbt.start_eb = AML_RSV_BBT_START_BLOCK(rsv_start_eb);
+ aml_rsv_areas.bbt.end_eb = aml_rsv_areas.bbt.start_eb + AML_RSV_BBT_BLOCKS;
+ aml_rsv_areas.bbt.size = mtd->size >> phys_erase_shift;
+
+ ret = aml_rsv_scan(&aml_rsv_areas.bbt);
+ if (ret && ret != -ENODATA) {
+ pr_err("Can't find bbt info. Error %d\n", ret);
+ aml_rsv_free(&aml_rsv_areas.bbt);
+ return ret;
+ }
+
+ dump_rsv_info(&aml_rsv_areas.bbt);
+
+ for (i = 0; i < AML_RSV_BLOCKS; i++)
+ bbt_mark_entry(this, rsv_start_eb + i, BBT_BLOCK_RESERVED);
+
+ return 0;
+}
+
+static int nand_parse_bbt_amlogic(struct nand_chip *this)
+{
+ size_t bbt_len_bytes, retlen;
+ size_t i;
+ int ret;
+ u8 *buf;
+
+ bbt_len_bytes = aml_rsv_bbt_size();
+ buf = kmalloc(bbt_len_bytes, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ ret = aml_rsv_bbt_read(buf, bbt_len_bytes, &retlen);
+ if (ret) {
+ if (ret == -ENODATA)
+ ret = 0;
+ else
+ pr_err("Failed to read BBT. Error %d\n", ret);
+
+ goto out;
+ }
+
+ if (retlen != bbt_len_bytes) {
+ pr_err("Failed to read whole BBT\n");
+ ret = -EIO;
+ goto out;
+ }
+
+ for (i = 0; i < bbt_len_bytes; i++) {
+ bool is_bad = false;
+
+ switch (buf[i]) {
+ case AMLOGIC_BLOCK_FACTORY_BAD:
+ is_bad = true;
+ bbt_mark_entry(this, i, BBT_BLOCK_FACTORY_BAD);
+ break;
+ case AMLOGIC_BLOCK_WORN_BAD:
+ is_bad = true;
+ bbt_mark_entry(this, i, BBT_BLOCK_WORN);
+ break;
+ }
+
+ if (is_bad)
+ pr_warn("Bad eraseblock %zu at 0x%lx\n",
+ i, (unsigned long)(i << this->phys_erase_shift));
+ }
+
+out:
+ kfree(buf);
+
+ return ret;
+}
+
+/**
+ * nand_create_bbt - [NAND Interface] Select a default bad block table for the device
+ * @this: NAND chip object
+ *
+ * This function selects the default bad block table support for the device and
+ * calls the nand_scan_bbt function.
+ *
+ * This is overloaded function from nand_bbt.c.
+ */
+int nand_create_bbt(struct nand_chip *this)
+{
+ struct mtd_info *mtd = nand_to_mtd(this);
+ size_t len;
+ int ret;
+
+ pr_info("Using Amlogic BBT format\n");
+
+ /* 2 bits per block. */
+ len = mtd->size >> (this->bbt_erase_shift + BBT_BITS_PER_BLOCK);
+
+ this->bbt = kzalloc(len, GFP_KERNEL);
+ if (!this->bbt)
+ return -ENOMEM;
+
+ ret = aml_rsv_init(this);
+ if (ret) {
+ pr_err("failed to rsv-info init: %d\n", ret);
+ goto out;
+ }
+
+ ret = nand_parse_bbt_amlogic(this);
+
+out:
+ if (ret) {
+ kfree(this->bbt);
+ this->bbt = NULL;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL(nand_create_bbt);
+
+/**
+ * nand_isreserved_bbt - [NAND Interface] Check if a block is reserved
+ * @this: NAND chip object
+ * @offs: offset in the device
+ *
+ * This is overloaded function from nand_bbt.c.
+ */
+int nand_isreserved_bbt(struct nand_chip *this, loff_t offs)
+{
+ int block = (int)(offs >> this->bbt_erase_shift);
+
+ return bbt_get_entry(this, block) == BBT_BLOCK_RESERVED;
+}
+
+/**
+ * nand_isbad_bbt - [NAND Interface] Check if a block is bad
+ * @this: NAND chip object
+ * @offs: offset in the device
+ * @allowbbt: allow access to bad block table region
+ *
+ * This is overloaded function from nand_bbt.c.
+ */
+int nand_isbad_bbt(struct nand_chip *this, loff_t offs, int allowbbt)
+{
+ int block, res;
+
+ block = (int)(offs >> this->bbt_erase_shift);
+ res = bbt_get_entry(this, block);
+
+ pr_debug("nand_isbad_bbt(): bbt info for offs 0x%08x: (block %d) 0x%02x\n",
+ (unsigned int)offs, block, res);
+
+ if (mtd_check_expert_analysis_mode())
+ return 0;
+
+ switch (res) {
+ case BBT_BLOCK_GOOD:
+ return 0;
+
+ case BBT_BLOCK_WORN:
+ return 1;
+
+ case BBT_BLOCK_RESERVED:
+ return allowbbt ? 0 : 1;
+ }
+ return 1;
+}
+
+static int nand_update_bbt(struct nand_chip *this)
+{
+ size_t bbt_len_bytes;
+ size_t i;
+ int res;
+ u8 *buf;
+
+ if (!this->bbt)
+ return -EFAULT;
+
+ pr_info("Updating Amlogic BBT\n");
+
+ bbt_len_bytes = aml_rsv_bbt_size();
+ buf = kzalloc(bbt_len_bytes, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ for (i = 0; i < bbt_len_bytes; i++) {
+ res = bbt_get_entry(this, i);
+
+ /* Skip reserved blocks. */
+ switch (res) {
+ case BBT_BLOCK_FACTORY_BAD:
+ buf[i] = AMLOGIC_BLOCK_FACTORY_BAD;
+ break;
+ case BBT_BLOCK_WORN:
+ buf[i] = AMLOGIC_BLOCK_WORN_BAD;
+ break;
+ default:
+ break;
+ }
+ }
+
+ res = aml_rsv_bbt_write(buf, bbt_len_bytes);
+ if (res) {
+ pr_err("Failed to write BBT. Error %d\n", res);
+ goto out_free_buf;
+ }
+
+out_free_buf:
+ kfree(buf);
+ return res;
+}
+
+/**
+ * nand_markbad_bbt - [NAND Interface] Mark a block bad in the BBT
+ * @this: NAND chip object
+ * @offs: offset of the bad block
+ *
+ * This is overloaded function from nand_bbt.c.
+ */
+int nand_markbad_bbt(struct nand_chip *this, loff_t offs)
+{
+ int block = (int)(offs >> this->bbt_erase_shift);
+
+ bbt_mark_entry(this, block, BBT_BLOCK_WORN);
+
+ return nand_update_bbt(this);
+}
diff --git a/drivers/mtd/nand/spi/esmt.c b/drivers/mtd/nand/spi/esmt.c
index 8607c2fdedcf3..3444279508b7f 100644
--- a/drivers/mtd/nand/spi/esmt.c
+++ b/drivers/mtd/nand/spi/esmt.c
@@ -18,6 +18,8 @@
#define ESMT_F50L1G41LB_CFG_OTP_LOCK \
(CFG_OTP_ENABLE | ESMT_F50L1G41LB_CFG_OTP_PROTECT)
+#define ESMT_F50L1G41LB_USER_OTP_START_PAGE 2
+
static SPINAND_OP_VARIANTS(read_cache_variants,
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0),
@@ -173,11 +175,73 @@ out:
return ret;
}
+static int f50l1g41lb_otp_rw(struct spinand_device *spinand, loff_t ofs,
+ size_t len, size_t *retlen, u8 *buf, bool is_write)
+{
+ struct nand_page_io_req req = {};
+ unsigned long long page;
+ size_t copied = 0;
+ size_t otp_pagesize = spinand_otp_page_size(spinand);
+ int ret;
+
+ ret = spinand_upd_cfg(spinand, CFG_OTP_ENABLE, CFG_OTP_ENABLE);
+ if (ret)
+ return ret;
+
+ page = ofs;
+ req.dataoffs = do_div(page, otp_pagesize);
+ req.pos.page = page + ESMT_F50L1G41LB_USER_OTP_START_PAGE;
+ req.type = is_write ? NAND_PAGE_WRITE : NAND_PAGE_READ;
+ req.mode = MTD_OPS_PLACE_OOB;
+ req.databuf.in = buf;
+
+ while (copied < len) {
+ req.datalen = min_t(unsigned int,
+ otp_pagesize - req.dataoffs,
+ len - copied);
+
+ if (is_write)
+ ret = spinand_write_page(spinand, &req);
+ else
+ ret = spinand_read_page(spinand, &req);
+
+ if (ret < 0)
+ break;
+
+ req.databuf.in += req.datalen;
+ req.pos.page++;
+ req.dataoffs = 0;
+ copied += req.datalen;
+ }
+
+ *retlen = copied;
+
+ if (spinand_upd_cfg(spinand, CFG_OTP_ENABLE, 0)) {
+ dev_warn(&spinand_to_mtd(spinand)->dev,
+ "Can not disable OTP mode\n");
+ ret = -EIO;
+ }
+
+ return ret > 0 ? 0 : ret;
+}
+
+static int f50l1g41lb_user_otp_read(struct spinand_device *spinand, loff_t ofs,
+ size_t len, size_t *retlen, u8 *buf)
+{
+ return f50l1g41lb_otp_rw(spinand, ofs, len, retlen, buf, false);
+}
+
+static int f50l1g41lb_user_otp_write(struct spinand_device *spinand, loff_t ofs,
+ size_t len, size_t *retlen, const u8 *buf)
+{
+ return f50l1g41lb_otp_rw(spinand, ofs, len, retlen, (u8 *)buf, true);
+}
+
static const struct spinand_user_otp_ops f50l1g41lb_user_otp_ops = {
.info = f50l1g41lb_user_otp_info,
.lock = f50l1g41lb_otp_lock,
- .read = spinand_user_otp_read,
- .write = spinand_user_otp_write,
+ .read = f50l1g41lb_user_otp_read,
+ .write = f50l1g41lb_user_otp_write,
};
static const struct spinand_fact_otp_ops f50l1g41lb_fact_otp_ops = {
diff --git a/drivers/mtd/nand/spi/micron.c b/drivers/mtd/nand/spi/micron.c
index 36f6cbbd7462c..1b60d8ac72794 100644
--- a/drivers/mtd/nand/spi/micron.c
+++ b/drivers/mtd/nand/spi/micron.c
@@ -35,9 +35,9 @@
(CFG_OTP_ENABLE | MICRON_MT29F2G01ABAGD_CFG_OTP_STATE)
static SPINAND_OP_VARIANTS(quadio_read_cache_variants,
- SPINAND_PAGE_READ_FROM_CACHE_1S_4S_4S_OP(0, 2, NULL, 0, 0),
+ //SPINAND_PAGE_READ_FROM_CACHE_1S_4S_4S_OP(0, 2, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
- SPINAND_PAGE_READ_FROM_CACHE_1S_2S_2S_OP(0, 1, NULL, 0, 0),
+ //SPINAND_PAGE_READ_FROM_CACHE_1S_2S_2S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
@@ -63,14 +63,28 @@ static SPINAND_OP_VARIANTS(x1_write_cache_variants,
static SPINAND_OP_VARIANTS(x1_update_cache_variants,
SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0));
+/*
+ * OOB spare area map (128 and 256 bytes)
+ *
+ * +-----+-----------------+-------------------+---------------------+
+ * | BBM | Non ECC | ECC protected | ECC Area |
+ * | | protected Area | Area | |
+ * ----------+-----+-----------------+-------------------+---------------------+
+ * oobsize | 0:3 | 4:31 (28 bytes) | 32:63 (32 bytes) | 64:127 (64 bytes) |
+ * 128 bytes | | | | |
+ * ----------+-----+-----------------+-------------------+---------------------+
+ * oobsize | 0:3 | 4:63 (60 bytes) | 64:127 (64 bytes) | 127:255 (128 bytes) |
+ * 256 bytes | | | | |
+ * ----------+-----+-----------------+-------------------+---------------------+
+ */
static int micron_8_ooblayout_ecc(struct mtd_info *mtd, int section,
struct mtd_oob_region *region)
{
if (section)
return -ERANGE;
- region->offset = mtd->oobsize / 2;
- region->length = mtd->oobsize / 2;
+ region->offset = 64;
+ region->length = 64;
return 0;
}
@@ -81,9 +95,9 @@ static int micron_8_ooblayout_free(struct mtd_info *mtd, int section,
if (section)
return -ERANGE;
- /* Reserve 2 bytes for the BBM. */
- region->offset = 2;
- region->length = (mtd->oobsize / 2) - 2;
+ /* Reserve 4 bytes for the BBM. */
+ region->offset = 4;
+ region->length = 28;
return 0;
}
diff --git a/drivers/tee/optee/supp.c b/drivers/tee/optee/supp.c
index a3d11b1f90fa5..06747e90c2309 100644
--- a/drivers/tee/optee/supp.c
+++ b/drivers/tee/optee/supp.c
@@ -10,7 +10,11 @@
struct optee_supp_req {
struct list_head link;
+ int id;
+
bool in_queue;
+ bool processed;
+
u32 func;
u32 ret;
size_t num_params;
@@ -19,6 +23,9 @@ struct optee_supp_req {
struct completion c;
};
+/* It is temporary request used for revoked pending request in supp->idr. */
+#define INVALID_REQ_PTR ((struct optee_supp_req *)ERR_PTR(-EBADF))
+
void optee_supp_init(struct optee_supp *supp)
{
memset(supp, 0, sizeof(*supp));
@@ -39,21 +46,23 @@ void optee_supp_release(struct optee_supp *supp)
{
int id;
struct optee_supp_req *req;
- struct optee_supp_req *req_tmp;
mutex_lock(&supp->mutex);
- /* Abort all request retrieved by supplicant */
+ /* Abort all request */
idr_for_each_entry(&supp->idr, req, id) {
idr_remove(&supp->idr, id);
- req->ret = TEEC_ERROR_COMMUNICATION;
- complete(&req->c);
- }
+ /* Skip if request was already marked invalid */
+ if (IS_ERR(req))
+ continue;
- /* Abort all queued requests */
- list_for_each_entry_safe(req, req_tmp, &supp->reqs, link) {
- list_del(&req->link);
- req->in_queue = false;
+ /* For queued requests where supplicant has not seen it */
+ if (req->in_queue) {
+ list_del(&req->link);
+ req->in_queue = false;
+ }
+
+ req->processed = true;
req->ret = TEEC_ERROR_COMMUNICATION;
complete(&req->c);
}
@@ -100,8 +109,16 @@ u32 optee_supp_thrd_req(struct tee_context *ctx, u32 func, size_t num_params,
/* Insert the request in the request list */
mutex_lock(&supp->mutex);
+ req->id = idr_alloc(&supp->idr, req, 1, 0, GFP_KERNEL);
+ if (req->id < 0) {
+ mutex_unlock(&supp->mutex);
+ kfree(req);
+ return TEEC_ERROR_OUT_OF_MEMORY;
+ }
+
list_add_tail(&req->link, &supp->reqs);
req->in_queue = true;
+ req->processed = false;
mutex_unlock(&supp->mutex);
/* Tell an eventual waiter there's a new request */
@@ -117,21 +134,43 @@ u32 optee_supp_thrd_req(struct tee_context *ctx, u32 func, size_t num_params,
if (wait_for_completion_killable(&req->c)) {
mutex_lock(&supp->mutex);
if (req->in_queue) {
+ /* Supplicant has not seen this request yet. */
+ idr_remove(&supp->idr, req->id);
list_del(&req->link);
req->in_queue = false;
+
+ ret = TEEC_ERROR_COMMUNICATION;
+ } else if (req->processed) {
+ /*
+ * Supplicant has processed this request. Ignore the
+ * kill signal for now and submit the result. req is not
+ * in supp->reqs (removed by supp_pop_entry()) nor in
+ * supp->idr (removed by supp_pop_req()).
+ */
+ ret = req->ret;
+ } else {
+ /*
+ * Supplicant is in the middle of processing this
+ * request. Replace req with INVALID_REQ_PTR so that
+ * the ID remains busy, causing optee_supp_send() to
+ * fail on the next call to supp_pop_req() with this ID.
+ */
+ idr_replace(&supp->idr, INVALID_REQ_PTR, req->id);
+ ret = TEEC_ERROR_COMMUNICATION;
}
+
mutex_unlock(&supp->mutex);
- req->ret = TEEC_ERROR_COMMUNICATION;
+ } else {
+ ret = req->ret;
}
- ret = req->ret;
kfree(req);
return ret;
}
static struct optee_supp_req *supp_pop_entry(struct optee_supp *supp,
- int num_params, int *id)
+ int num_params)
{
struct optee_supp_req *req;
@@ -153,10 +192,6 @@ static struct optee_supp_req *supp_pop_entry(struct optee_supp *supp,
return ERR_PTR(-EINVAL);
}
- *id = idr_alloc(&supp->idr, req, 1, 0, GFP_KERNEL);
- if (*id < 0)
- return ERR_PTR(-ENOMEM);
-
list_del(&req->link);
req->in_queue = false;
@@ -214,7 +249,6 @@ int optee_supp_recv(struct tee_context *ctx, u32 *func, u32 *num_params,
struct optee *optee = tee_get_drvdata(teedev);
struct optee_supp *supp = &optee->supp;
struct optee_supp_req *req = NULL;
- int id;
size_t num_meta;
int rc;
@@ -224,15 +258,11 @@ int optee_supp_recv(struct tee_context *ctx, u32 *func, u32 *num_params,
while (true) {
mutex_lock(&supp->mutex);
- req = supp_pop_entry(supp, *num_params - num_meta, &id);
+ req = supp_pop_entry(supp, *num_params - num_meta);
+ if (req)
+ break; /* Keep mutex held. */
mutex_unlock(&supp->mutex);
- if (req) {
- if (IS_ERR(req))
- return PTR_ERR(req);
- break;
- }
-
/*
* If we didn't get a request we'll block in
* wait_for_completion() to avoid needless spinning.
@@ -245,6 +275,13 @@ int optee_supp_recv(struct tee_context *ctx, u32 *func, u32 *num_params,
return -ERESTARTSYS;
}
+ /* supp->mutex held and req != NULL. */
+
+ if (IS_ERR(req)) {
+ mutex_unlock(&supp->mutex);
+ return PTR_ERR(req);
+ }
+
if (num_meta) {
/*
* tee-supplicant support meta parameters -> requsts can be
@@ -252,13 +289,11 @@ int optee_supp_recv(struct tee_context *ctx, u32 *func, u32 *num_params,
*/
param->attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INOUT |
TEE_IOCTL_PARAM_ATTR_META;
- param->u.value.a = id;
+ param->u.value.a = req->id;
param->u.value.b = 0;
param->u.value.c = 0;
} else {
- mutex_lock(&supp->mutex);
- supp->req_id = id;
- mutex_unlock(&supp->mutex);
+ supp->req_id = req->id;
}
*func = req->func;
@@ -266,6 +301,7 @@ int optee_supp_recv(struct tee_context *ctx, u32 *func, u32 *num_params,
memcpy(param + num_meta, req->param,
sizeof(struct tee_param) * req->num_params);
+ mutex_unlock(&supp->mutex);
return 0;
}
@@ -297,12 +333,17 @@ static struct optee_supp_req *supp_pop_req(struct optee_supp *supp,
if (!req)
return ERR_PTR(-ENOENT);
+ /* optee_supp_thrd_req() already returned to optee. */
+ if (IS_ERR(req))
+ goto failed_req;
+
if ((num_params - nm) != req->num_params)
return ERR_PTR(-EINVAL);
+ *num_meta = nm;
+failed_req:
idr_remove(&supp->idr, id);
supp->req_id = -1;
- *num_meta = nm;
return req;
}
@@ -328,10 +369,9 @@ int optee_supp_send(struct tee_context *ctx, u32 ret, u32 num_params,
mutex_lock(&supp->mutex);
req = supp_pop_req(supp, num_params, param, &num_meta);
- mutex_unlock(&supp->mutex);
-
if (IS_ERR(req)) {
- /* Something is wrong, let supplicant restart. */
+ mutex_unlock(&supp->mutex);
+ /* Something is wrong, let supplicant handel it. */
return PTR_ERR(req);
}
@@ -355,9 +395,10 @@ int optee_supp_send(struct tee_context *ctx, u32 ret, u32 num_params,
}
}
req->ret = ret;
-
+ req->processed = true;
/* Let the requesting thread continue */
complete(&req->c);
+ mutex_unlock(&supp->mutex);
return 0;
}
diff --git a/include/dt-bindings/clock/amlogic,a1-audio-clkc.h b/include/dt-bindings/clock/amlogic,a1-audio-clkc.h
new file mode 100644
index 0000000000000..3392974784e79
--- /dev/null
+++ b/include/dt-bindings/clock/amlogic,a1-audio-clkc.h
@@ -0,0 +1,122 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */
+/*
+ * Copyright (c) 2024, SaluteDevices. All Rights Reserved.
+ *
+ * Author: Jan Dakinevich <jan.dakinevich@salutedevices.com>
+ */
+
+#ifndef __A1_AUDIO_CLKC_BINDINGS_H
+#define __A1_AUDIO_CLKC_BINDINGS_H
+
+#define AUD_CLKID_DDR_ARB 1
+#define AUD_CLKID_TDMIN_A 2
+#define AUD_CLKID_TDMIN_B 3
+#define AUD_CLKID_TDMIN_LB 4
+#define AUD_CLKID_LOOPBACK 5
+#define AUD_CLKID_TDMOUT_A 6
+#define AUD_CLKID_TDMOUT_B 7
+#define AUD_CLKID_FRDDR_A 8
+#define AUD_CLKID_FRDDR_B 9
+#define AUD_CLKID_TODDR_A 10
+#define AUD_CLKID_TODDR_B 11
+#define AUD_CLKID_SPDIFIN 12
+#define AUD_CLKID_RESAMPLE 13
+#define AUD_CLKID_EQDRC 14
+#define AUD_CLKID_LOCKER 15
+#define AUD_CLKID_MST_A_MCLK_SEL 16
+#define AUD_CLKID_MST_A_MCLK_DIV 17
+#define AUD_CLKID_MST_A_MCLK 18
+#define AUD_CLKID_MST_B_MCLK_SEL 19
+#define AUD_CLKID_MST_B_MCLK_DIV 20
+#define AUD_CLKID_MST_B_MCLK 21
+#define AUD_CLKID_MST_C_MCLK_SEL 22
+#define AUD_CLKID_MST_C_MCLK_DIV 23
+#define AUD_CLKID_MST_C_MCLK 24
+#define AUD_CLKID_MST_D_MCLK_SEL 25
+#define AUD_CLKID_MST_D_MCLK_DIV 26
+#define AUD_CLKID_MST_D_MCLK 27
+#define AUD_CLKID_SPDIFIN_CLK_SEL 28
+#define AUD_CLKID_SPDIFIN_CLK_DIV 29
+#define AUD_CLKID_SPDIFIN_CLK 30
+#define AUD_CLKID_RESAMPLE_CLK_SEL 31
+#define AUD_CLKID_RESAMPLE_CLK_DIV 32
+#define AUD_CLKID_RESAMPLE_CLK 33
+#define AUD_CLKID_LOCKER_IN_CLK_SEL 34
+#define AUD_CLKID_LOCKER_IN_CLK_DIV 35
+#define AUD_CLKID_LOCKER_IN_CLK 36
+#define AUD_CLKID_LOCKER_OUT_CLK_SEL 37
+#define AUD_CLKID_LOCKER_OUT_CLK_DIV 38
+#define AUD_CLKID_LOCKER_OUT_CLK 39
+#define AUD_CLKID_EQDRC_CLK_SEL 40
+#define AUD_CLKID_EQDRC_CLK_DIV 41
+#define AUD_CLKID_EQDRC_CLK 42
+#define AUD_CLKID_MST_A_SCLK_PRE_EN 43
+#define AUD_CLKID_MST_A_SCLK_DIV 44
+#define AUD_CLKID_MST_A_SCLK_POST_EN 45
+#define AUD_CLKID_MST_A_SCLK 46
+#define AUD_CLKID_MST_B_SCLK_PRE_EN 47
+#define AUD_CLKID_MST_B_SCLK_DIV 48
+#define AUD_CLKID_MST_B_SCLK_POST_EN 49
+#define AUD_CLKID_MST_B_SCLK 50
+#define AUD_CLKID_MST_C_SCLK_PRE_EN 51
+#define AUD_CLKID_MST_C_SCLK_DIV 52
+#define AUD_CLKID_MST_C_SCLK_POST_EN 53
+#define AUD_CLKID_MST_C_SCLK 54
+#define AUD_CLKID_MST_D_SCLK_PRE_EN 55
+#define AUD_CLKID_MST_D_SCLK_DIV 56
+#define AUD_CLKID_MST_D_SCLK_POST_EN 57
+#define AUD_CLKID_MST_D_SCLK 58
+#define AUD_CLKID_MST_A_LRCLK_DIV 59
+#define AUD_CLKID_MST_A_LRCLK 60
+#define AUD_CLKID_MST_B_LRCLK_DIV 61
+#define AUD_CLKID_MST_B_LRCLK 62
+#define AUD_CLKID_MST_C_LRCLK_DIV 63
+#define AUD_CLKID_MST_C_LRCLK 64
+#define AUD_CLKID_MST_D_LRCLK_DIV 65
+#define AUD_CLKID_MST_D_LRCLK 66
+#define AUD_CLKID_TDMIN_A_SCLK_SEL 67
+#define AUD_CLKID_TDMIN_A_SCLK_PRE_EN 68
+#define AUD_CLKID_TDMIN_A_SCLK_POST_EN 69
+#define AUD_CLKID_TDMIN_A_SCLK 70
+#define AUD_CLKID_TDMIN_A_LRCLK 71
+#define AUD_CLKID_TDMIN_B_SCLK_SEL 72
+#define AUD_CLKID_TDMIN_B_SCLK_PRE_EN 73
+#define AUD_CLKID_TDMIN_B_SCLK_POST_EN 74
+#define AUD_CLKID_TDMIN_B_SCLK 75
+#define AUD_CLKID_TDMIN_B_LRCLK 76
+#define AUD_CLKID_TDMIN_LB_SCLK_SEL 77
+#define AUD_CLKID_TDMIN_LB_SCLK_PRE_EN 78
+#define AUD_CLKID_TDMIN_LB_SCLK_POST_EN 79
+#define AUD_CLKID_TDMIN_LB_SCLK 80
+#define AUD_CLKID_TDMIN_LB_LRCLK 81
+#define AUD_CLKID_TDMOUT_A_SCLK_SEL 82
+#define AUD_CLKID_TDMOUT_A_SCLK_PRE_EN 83
+#define AUD_CLKID_TDMOUT_A_SCLK_POST_EN 84
+#define AUD_CLKID_TDMOUT_A_SCLK 85
+#define AUD_CLKID_TDMOUT_A_LRCLK 86
+#define AUD_CLKID_TDMOUT_B_SCLK_SEL 87
+#define AUD_CLKID_TDMOUT_B_SCLK_PRE_EN 88
+#define AUD_CLKID_TDMOUT_B_SCLK_POST_EN 89
+#define AUD_CLKID_TDMOUT_B_SCLK 90
+#define AUD_CLKID_TDMOUT_B_LRCLK 91
+
+#define AUD2_CLKID_DDR_ARB 100
+#define AUD2_CLKID_PDM 101
+#define AUD2_CLKID_TDMIN_VAD 102
+#define AUD2_CLKID_TODDR_VAD 103
+#define AUD2_CLKID_VAD 104
+#define AUD2_CLKID_AUDIOTOP 105
+#define AUD2_CLKID_VAD_MCLK_SEL 106
+#define AUD2_CLKID_VAD_MCLK_DIV 107
+#define AUD2_CLKID_VAD_MCLK 108
+#define AUD2_CLKID_VAD_CLK_SEL 109
+#define AUD2_CLKID_VAD_CLK_DIV 110
+#define AUD2_CLKID_VAD_CLK 111
+#define AUD2_CLKID_PDM_DCLK_SEL 112
+#define AUD2_CLKID_PDM_DCLK_DIV 113
+#define AUD2_CLKID_PDM_DCLK 114
+#define AUD2_CLKID_PDM_SYSCLK_SEL 115
+#define AUD2_CLKID_PDM_SYSCLK_DIV 116
+#define AUD2_CLKID_PDM_SYSCLK 117
+
+#endif /* __A1_AUDIO_CLKC_BINDINGS_H */
diff --git a/include/dt-bindings/clock/amlogic,a1-cpu-clkc.h b/include/dt-bindings/clock/amlogic,a1-cpu-clkc.h
new file mode 100644
index 0000000000000..1d321c6eddb7e
--- /dev/null
+++ b/include/dt-bindings/clock/amlogic,a1-cpu-clkc.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause */
+/*
+ * Copyright (c) 2024, SaluteDevices. All Rights Reserved.
+ * Author: Dmitry Rokosov <ddrokosov@salutedevices.com>
+ */
+
+#ifndef __A1_CPU_CLKC_H
+#define __A1_CPU_CLKC_H
+
+#define CLKID_CPU_FSOURCE_SEL0 0
+#define CLKID_CPU_FSOURCE_DIV0 1
+#define CLKID_CPU_FSEL0 2
+#define CLKID_CPU_FSOURCE_SEL1 3
+#define CLKID_CPU_FSOURCE_DIV1 4
+#define CLKID_CPU_FSEL1 5
+#define CLKID_CPU_FCLK 6
+#define CLKID_CPU_CLK 7
+
+#endif /* __A1_CPU_CLKC_H */
diff --git a/include/dt-bindings/sound/meson-g12a-toacodec.h b/include/dt-bindings/sound/meson-g12a-toacodec.h
index 69d7a75592a2a..f726e2c6064da 100644
--- a/include/dt-bindings/sound/meson-g12a-toacodec.h
+++ b/include/dt-bindings/sound/meson-g12a-toacodec.h
@@ -7,4 +7,9 @@
#define TOACODEC_IN_C 2
#define TOACODEC_OUT 3
+#define TOACODEC_CAPTURE_OUT_A 4
+#define TOACODEC_CAPTURE_OUT_B 5
+#define TOACODEC_CAPTURE_OUT_C 6
+#define TOACODEC_CAPTURE_IN 7
+
#endif /* __DT_MESON_G12A_TOACODEC_H */
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index adb3fb923be39..2c820fd825d64 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -1251,6 +1251,9 @@ config SND_SOC_ES7134
config SND_SOC_ES7241
tristate "Everest Semi ES7241 CODEC"
+config SND_SOC_ES7243E
+ tristate "Everest Semi ES7243E CODEC"
+
config SND_SOC_ES83XX_DSM_COMMON
depends on ACPI
tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 3ddee52987219..35fd01f0fce09 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -128,6 +128,7 @@ snd-soc-da9055-y := da9055.o
snd-soc-dmic-y := dmic.o
snd-soc-es7134-y := es7134.o
snd-soc-es7241-y := es7241.o
+snd-soc-es7243e-objs := es7243e.o
snd-soc-es83xx-dsm-common-y := es83xx-dsm-common.o
snd-soc-es8311-y := es8311.o
snd-soc-es8316-y := es8316.o
@@ -564,6 +565,7 @@ obj-$(CONFIG_SND_SOC_DA9055) += snd-soc-da9055.o
obj-$(CONFIG_SND_SOC_DMIC) += snd-soc-dmic.o
obj-$(CONFIG_SND_SOC_ES7134) += snd-soc-es7134.o
obj-$(CONFIG_SND_SOC_ES7241) += snd-soc-es7241.o
+obj-$(CONFIG_SND_SOC_ES7243E) += snd-soc-es7243e.o
obj-$(CONFIG_SND_SOC_ES83XX_DSM_COMMON) += snd-soc-es83xx-dsm-common.o
obj-$(CONFIG_SND_SOC_ES8311) += snd-soc-es8311.o
obj-$(CONFIG_SND_SOC_ES8316) += snd-soc-es8316.o
diff --git a/sound/soc/codecs/es7243e.c b/sound/soc/codecs/es7243e.c
new file mode 100644
index 0000000000000..5b359cdb81089
--- /dev/null
+++ b/sound/soc/codecs/es7243e.c
@@ -0,0 +1,688 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/**
+ * es7243e.c - ASoC Everest Semiconductor ES7243E audio ADC driver
+ *
+ * Copyright (c) 2024, SberDevices. All Rights Reserved.
+ *
+ * Authors: Viktor Prutyanov <vvprutyanov@sberdevices.ru>
+ * Igor Prusov <ivprusov@salutedevices.com>
+ *
+ * Based on ES7243E driver by David Yang <yangxiaohua@everest-semi.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/tlv.h>
+#include <sound/initval.h>
+#include <linux/regmap.h>
+
+#define ES7243E_RESET 0x00
+#define ES7243E_CLK1 0x01
+#define ES7243E_CLK1_ANA_ON BIT(1)
+#define ES7243E_CLK1_ADC_ON BIT(3)
+#define ES7243E_CLK2 0x02
+#define ES7243E_CLK2_BCLK_INV BIT(0)
+#define ES7243E_ADC_OSR 0x03
+#define ES7243E_MCLK_PRE 0x04
+#define ES7243E_CF_DSP_DIV 0x05
+#define ES7243E_BCLK_DIV 0x06
+#define ES7243E_CLK7 0x07
+#define ES7243E_CLK7_LRCK_DIV_HI GENMASK(3, 0)
+#define ES7243E_LRCK_DIV 0x08
+#define ES7243E_S1_SEL 0x09
+#define ES7243E_S3_SEL 0x0A
+#define ES7243E_SDP 0x0B
+#define ES7243E_SDP_MUTE GENMASK(7, 6)
+#define ES7243E_SDP_LRP BIT(5)
+#define ES7243E_SDP_WL GENMASK(4, 2)
+#define ES7243E_SDP_FMT GENMASK(1, 0)
+#define ES7243E_TDM 0x0B
+#define ES7243E_ADCCTL1 0x0D
+#define ES7243E_ADCCTL1_SCALE GENMASK(2, 0)
+#define ES7243E_ADC_VOL 0x0E
+#define ES7243E_ADCCTL2 0x0F
+#define ES7243E_AUTOMUTE1 0x10
+#define ES7243E_AUTOMUTE2 0x11
+#define ES7243E_AUTOMUTE3 0x12
+#define ES7243E_AUTOMUTE4 0x13
+#define ES7243E_ADC_HPF1 0x14
+#define ES7243E_ADC_HPF2 0x15
+#define ES7243E_PDN 0x16
+#define ES7243E_PDN_ANA BIT(7)
+#define ES7243E_PDN_BIAS BIT(6)
+#define ES7243E_PDN_MOD1_RST BIT(5)
+#define ES7243E_PDN_MOD2_RST BIT(4)
+#define ES7243E_PDN_MOD1 BIT(3)
+#define ES7243E_PDN_MOD2 BIT(2)
+#define ES7243E_PDN_PGA1 BIT(1)
+#define ES7243E_PDN_PGA2 BIT(0)
+#define ES7243E_VMIDSEL 0x17
+#define ES7243E_ADC_BIAS_0x18 0x18
+#define ES7243E_PGA_BIAS 0x19
+#define ES7243E_ADC_BIAS_0x1A 0x1A
+#define ES7243E_ADC_MICBIAS 0x1B
+#define ES7243E_ADC_VRPBIAS 0x1C
+#define ES7243E_ADC_LP 0x1D
+#define ES7243E_ADC_PGA_LP 0x1E
+#define ES7243E_ADC_VMID 0x1F
+#define ES7243E_PGA1 0x20
+#define ES7243E_PGA1_EN BIT(4)
+#define ES7243E_PGA2 0x21
+#define ES7243E_PGA2_EN BIT(4)
+#define ES7243E_DLL_PWN 0xF9
+#define ES7243E_I2C_CONFIG 0xFC
+#define ES7243E_FLAG 0xFA
+#define ES7243E_CHIP_ID1 0xFD
+#define ES7243E_CHIP_ID2 0xFE
+#define ES7243E_CHIP_VER 0xFF
+
+struct es7243e_priv {
+ struct i2c_client *i2c;
+ struct clk *sclk;
+ struct clk *lrclk;
+ unsigned int sysclk;
+ struct snd_pcm_hw_constraint_list rates;
+};
+
+static const struct regmap_config es7243e_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = ES7243E_CHIP_VER,
+ .cache_type = REGCACHE_MAPLE,
+};
+
+struct coeff_div {
+ u32 mclk; /* mclk frequency */
+ u32 sr_rate; /* sample rate */
+ u8 osr; /* ADC over sample rate */
+ u8 mclk_pre; /* mclk prediv/premult */
+ u8 cf_dsp_div; /* adclrck divider and daclrck divider */
+ u8 scale; /* ADC gain scale up */
+ u8 lrckdiv; /* lrck divider */
+ u8 bclkdiv; /* sclk divider */
+};
+
+static const struct coeff_div coeff_div[] = {
+ /* mclk lrck osr pre div scale lrdiv bclkdiv */
+ { 24576000, 8000, 0x20, 0x50, 0x00, 0x00, 0x0b, 0x2f },
+ { 24576000, 12000, 0x20, 0x30, 0x00, 0x00, 0x07, 0x1f },
+ { 24576000, 16000, 0x20, 0x20, 0x00, 0x00, 0x05, 0x17 },
+ { 24576000, 24000, 0x20, 0x10, 0x00, 0x00, 0x03, 0x0f },
+ { 24576000, 32000, 0x20, 0x21, 0x00, 0x00, 0x02, 0x0b },
+ { 24576000, 48000, 0x20, 0x00, 0x00, 0x00, 0x01, 0x07 },
+ { 12288000, 8000, 0x20, 0x20, 0x00, 0x00, 0x05, 0x17 },
+ { 12288000, 12000, 0x20, 0x10, 0x00, 0x00, 0x03, 0x0f },
+ { 12288000, 16000, 0x20, 0x21, 0x00, 0x00, 0x02, 0x0b },
+ { 12288000, 24000, 0x20, 0x00, 0x00, 0x00, 0x01, 0x07 },
+ { 12288000, 32000, 0x20, 0x22, 0x00, 0x00, 0x01, 0x05 },
+ { 12288000, 48000, 0x20, 0x01, 0x00, 0x00, 0x00, 0x03 },
+ { 6144000, 8000, 0x20, 0x21, 0x00, 0x00, 0x02, 0x0b },
+ { 6144000, 12000, 0x20, 0x00, 0x00, 0x00, 0x01, 0x07 },
+ { 6144000, 16000, 0x20, 0x22, 0x00, 0x00, 0x01, 0x05 },
+ { 6144000, 24000, 0x20, 0x01, 0x00, 0x00, 0x00, 0x03 },
+ { 6144000, 32000, 0x20, 0x23, 0x00, 0x00, 0x00, 0x02 },
+ { 6144000, 48000, 0x20, 0x02, 0x00, 0x00, 0x00, 0x01 },
+ { 3072000, 8000, 0x20, 0x22, 0x00, 0x00, 0x01, 0x05 },
+ { 3072000, 12000, 0x20, 0x01, 0x00, 0x00, 0x00, 0x03 },
+ { 3072000, 16000, 0x20, 0x23, 0x00, 0x00, 0x00, 0x02 },
+ { 3072000, 24000, 0x20, 0x02, 0x00, 0x00, 0x00, 0x01 },
+ { 3072000, 32000, 0x10, 0x03, 0x20, 0x04, 0x00, 0x02 },
+ { 3072000, 48000, 0x20, 0x03, 0x00, 0x00, 0x00, 0x00 },
+ { 1536000, 8000, 0x20, 0x23, 0x00, 0x00, 0x00, 0x02 },
+ { 1536000, 12000, 0x20, 0x02, 0x00, 0x00, 0x00, 0x01 },
+ { 1536000, 16000, 0x10, 0x03, 0x20, 0x04, 0x00, 0x00 },
+ { 1536000, 24000, 0x20, 0x03, 0x00, 0x00, 0x00, 0x00 },
+ { 32768000, 8000, 0x20, 0x70, 0x00, 0x00, 0x0f, 0x3f },
+ { 32768000, 16000, 0x20, 0x30, 0x00, 0x00, 0x07, 0x1f },
+ { 32768000, 32000, 0x20, 0x10, 0x00, 0x00, 0x03, 0x0f },
+ { 16384000, 8000, 0x20, 0x30, 0x00, 0x00, 0x07, 0x1f },
+ { 16384000, 16000, 0x20, 0x10, 0x00, 0x00, 0x03, 0x0f },
+ { 16384000, 32000, 0x20, 0x00, 0x00, 0x00, 0x01, 0x07 },
+ { 8192000, 8000, 0x20, 0x10, 0x00, 0x00, 0x03, 0x0f },
+ { 8192000, 16000, 0x20, 0x00, 0x00, 0x00, 0x01, 0x07 },
+ { 8192000, 32000, 0x20, 0x01, 0x00, 0x00, 0x00, 0x03 },
+ { 4096000, 8000, 0x20, 0x00, 0x00, 0x00, 0x01, 0x07 },
+ { 4096000, 16000, 0x20, 0x01, 0x00, 0x00, 0x00, 0x03 },
+ { 4096000, 32000, 0x20, 0x02, 0x00, 0x00, 0x00, 0x01 },
+ { 2048000, 8000, 0x20, 0x01, 0x00, 0x00, 0x00, 0x03 },
+ { 2048000, 16000, 0x20, 0x02, 0x00, 0x00, 0x00, 0x01 },
+ { 2048000, 32000, 0x20, 0x03, 0x00, 0x00, 0x00, 0x00 },
+ { 1024000, 8000, 0x20, 0x02, 0x00, 0x00, 0x00, 0x01 },
+ { 1024000, 16000, 0x20, 0x03, 0x00, 0x00, 0x00, 0x00 },
+ { 22579200, 11025, 0x20, 0x30, 0x00, 0x00, 0x07, 0x1f },
+ { 22579200, 22050, 0x20, 0x10, 0x00, 0x00, 0x03, 0x0f },
+ { 22579200, 44100, 0x20, 0x00, 0x00, 0x00, 0x01, 0x07 },
+ { 11289600, 11025, 0x20, 0x10, 0x00, 0x00, 0x03, 0x0f },
+ { 11289600, 22050, 0x20, 0x00, 0x00, 0x00, 0x01, 0x07 },
+ { 11289600, 44100, 0x20, 0x01, 0x00, 0x00, 0x00, 0x03 },
+ { 56448000, 11025, 0x20, 0x00, 0x00, 0x00, 0x01, 0x07 },
+ { 56448000, 22050, 0x20, 0x01, 0x00, 0x00, 0x00, 0x03 },
+ { 56448000, 44100, 0x20, 0x02, 0x00, 0x00, 0x00, 0x01 },
+ { 28224000, 11025, 0x20, 0x01, 0x00, 0x00, 0x00, 0x03 },
+ { 28224000, 22050, 0x20, 0x02, 0x00, 0x00, 0x00, 0x01 },
+ { 28224000, 44100, 0x20, 0x03, 0x00, 0x00, 0x00, 0x00 },
+ { 14112000, 11025, 0x20, 0x02, 0x00, 0x00, 0x00, 0x01 },
+ { 14112000, 22050, 0x20, 0x03, 0x00, 0x00, 0x00, 0x00 },
+};
+
+static const struct coeff_div *get_coeff(int mclk, int rate)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(coeff_div); i++)
+ if (coeff_div[i].sr_rate == rate && coeff_div[i].mclk == mclk)
+ return &coeff_div[i];
+
+ return NULL;
+}
+
+static int es7243e_set_dai_sysclk(struct snd_soc_dai *dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_component *component = dai->component;
+ struct es7243e_priv *es7243e = snd_soc_component_get_drvdata(component);
+
+ es7243e->sysclk = freq;
+
+ return 0;
+}
+
+static int es7243e_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct snd_soc_component *component = dai->component;
+ unsigned int sdpfmt = 0;
+ unsigned int clk2 = 0;
+ bool is_dsp = false;
+ bool invert_lrck = false;
+ int ret;
+
+ /* Set protocol of the serial data port */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ sdpfmt |= FIELD_PREP(ES7243E_SDP_FMT, 0);
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ sdpfmt |= FIELD_PREP(ES7243E_SDP_FMT, 1);
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ sdpfmt |= FIELD_PREP(ES7243E_SDP_FMT, 3);
+ is_dsp = true;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ sdpfmt |= FIELD_PREP(ES7243E_SDP_FMT, 3);
+ is_dsp = true;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Set LR and BCLK polarity */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ clk2 |= FIELD_PREP(ES7243E_CLK2_BCLK_INV, 0);
+ sdpfmt |= FIELD_PREP(ES7243E_SDP_LRP, 0);
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ clk2 |= FIELD_PREP(ES7243E_CLK2_BCLK_INV, 1);
+ sdpfmt |= FIELD_PREP(ES7243E_SDP_LRP, 1);
+ invert_lrck = true;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ clk2 |= FIELD_PREP(ES7243E_CLK2_BCLK_INV, 1);
+ sdpfmt |= FIELD_PREP(ES7243E_SDP_LRP, 0);
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ clk2 |= FIELD_PREP(ES7243E_CLK2_BCLK_INV, 0);
+ sdpfmt |= FIELD_PREP(ES7243E_SDP_LRP, 1);
+ invert_lrck = true;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Inverted LRCK is not available in DSP mode. */
+ if (is_dsp && invert_lrck)
+ return -EINVAL;
+
+ ret = snd_soc_component_update_bits(component, ES7243E_CLK2,
+ ES7243E_CLK2_BCLK_INV, clk2);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_component_update_bits(component, ES7243E_SDP,
+ ES7243E_SDP_LRP, sdpfmt);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int es7243e_pcm_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct es7243e_priv *es7243e = snd_soc_component_get_drvdata(dai->component);
+
+ return snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &es7243e->rates);
+}
+
+static int es7243e_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_component *component = dai->component;
+ struct es7243e_priv *es7243e = dev_get_drvdata(dai->component->dev);
+ const struct coeff_div *cd;
+ unsigned int val;
+ int width;
+ int ret;
+
+ cd = get_coeff(es7243e->sysclk, params_rate(params));
+ if (!cd) {
+ dev_err(dai->component->dev,
+ "Can't set sample rate = %uHz, F_mclk = %uHz\n",
+ params_rate(params), es7243e->sysclk);
+
+ return -EINVAL;
+ }
+
+ ret = snd_soc_component_write(component, ES7243E_ADC_OSR, cd->osr);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_component_write(component, ES7243E_MCLK_PRE, cd->mclk_pre);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_component_write(component, ES7243E_CF_DSP_DIV, cd->cf_dsp_div);
+ if (ret < 0)
+ return ret;
+
+ val = FIELD_PREP(ES7243E_ADCCTL1_SCALE, cd->scale);
+ ret = snd_soc_component_update_bits(component, ES7243E_ADCCTL1,
+ ES7243E_ADCCTL1_SCALE, val);
+ if (ret < 0)
+ return ret;
+
+ val = FIELD_PREP(ES7243E_CLK7_LRCK_DIV_HI, cd->lrckdiv);
+ ret = snd_soc_component_update_bits(component, ES7243E_CLK7,
+ ES7243E_CLK7_LRCK_DIV_HI, val);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_component_write(component, ES7243E_BCLK_DIV, cd->bclkdiv);
+ if (ret < 0)
+ return ret;
+
+ switch ((params_width(params))) {
+ case 24:
+ width = 0;
+ break;
+ case 20:
+ width = 1;
+ break;
+ case 18:
+ width = 2;
+ break;
+ case 16:
+ width = 3;
+ break;
+ case 32:
+ width = 4;
+ break;
+ default:
+ return -ENOENT;
+ }
+
+ val = FIELD_PREP(ES7243E_SDP_WL, width);
+ ret = snd_soc_component_update_bits(component, ES7243E_SDP,
+ ES7243E_SDP_WL, val);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Delay timings are not documented in datasheet and come from vendor's
+ * driver implementation.
+ */
+ fsleep(50 * USEC_PER_MSEC);
+ val = FIELD_PREP(ES7243E_SDP_MUTE, 0);
+ ret = snd_soc_component_update_bits(component, ES7243E_SDP,
+ ES7243E_SDP_MUTE, val);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int es7243e_mute(struct snd_soc_dai *dai, int mute, int direction)
+{
+ const int val = FIELD_PREP(ES7243E_SDP_MUTE, 3 * !!mute);
+ int ret;
+
+ ret = snd_soc_component_update_bits(dai->component, ES7243E_SDP,
+ ES7243E_SDP_MUTE, val);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static const DECLARE_TLV_DB_RANGE(pga_scale,
+ 0, 10, TLV_DB_SCALE_ITEM(0, 300, 0),
+ 11, 14, TLV_DB_SCALE_ITEM(3300, 150, 0),
+);
+
+static const DECLARE_TLV_DB_SCALE(vol_scale, -9550, 50, 0);
+
+static const struct snd_kcontrol_new es7243e_snd_controls[] = {
+ SOC_SINGLE_RANGE_TLV("PGA1 Volume", ES7243E_PGA1, 0, 0, 14, 0, pga_scale),
+ SOC_SINGLE_RANGE_TLV("PGA2 Volume", ES7243E_PGA2, 0, 0, 14, 0, pga_scale),
+ SOC_SINGLE_RANGE_TLV("ADC Volume", ES7243E_ADC_VOL, 0, 0, 255, 0, vol_scale),
+};
+
+static const struct snd_soc_dapm_widget es7243e_dapm_widgets[] = {
+ SND_SOC_DAPM_INPUT("INPUT"),
+ SND_SOC_DAPM_ADC("ADC", NULL, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("SDOUT", "I2S Capture", 0, SND_SOC_NOPM, 0, 0),
+};
+
+static const struct snd_soc_dapm_route es7243e_dapm_routes[] = {
+ { "ADC", NULL, "INPUT" },
+ { "SDOUT", NULL, "ADC" },
+};
+
+static const struct snd_soc_dai_ops es7243e_ops = {
+ .startup = es7243e_pcm_startup,
+ .hw_params = es7243e_pcm_hw_params,
+ .set_fmt = es7243e_set_dai_fmt,
+ .set_sysclk = es7243e_set_dai_sysclk,
+ .mute_stream = es7243e_mute,
+};
+
+static struct snd_soc_dai_driver es7243e_dai = {
+ .name = "es7243e-hifi",
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = (SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S20_3LE |
+ SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_S32_LE),
+ },
+ .ops = &es7243e_ops,
+};
+
+static const struct reg_sequence init_sequence[] = {
+ { ES7243E_CLK2, 0x00 },
+ { ES7243E_SDP, 0x00 },
+ { ES7243E_TDM, 0x00 },
+
+ /* Set MCLK/LRCK ratio to 256 */
+ { ES7243E_ADC_OSR, 0x20 },
+ { ES7243E_ADCCTL1, 0x00 },
+ { ES7243E_MCLK_PRE, 0x01 },
+ { ES7243E_CF_DSP_DIV, 0x00 },
+
+ { ES7243E_S1_SEL, 0xe0 },
+ { ES7243E_S3_SEL, 0xa0 },
+
+ /* Set ADC volume to 0dB */
+ { ES7243E_ADC_VOL, 0xbf },
+ /* Enable dynamic HPF (high-pass filter), disable auto level control */
+ { ES7243E_ADCCTL2, 0x80 },
+ { ES7243E_ADC_HPF1, 0x0c },
+ { ES7243E_ADC_HPF2, 0x0c },
+ /* VDDA = 3.3V */
+ { ES7243E_ADC_BIAS_0x18, 0x26 },
+ { ES7243E_VMIDSEL, 0x02 },
+ { ES7243E_PGA_BIAS, 0x77 },
+ { ES7243E_ADC_BIAS_0x1A, 0xf4 },
+ { ES7243E_ADC_MICBIAS, 0x66 },
+ { ES7243E_ADC_VRPBIAS, 0x44 },
+ { ES7243E_ADC_LP, 0x3c },
+ { ES7243E_ADC_PGA_LP, 0x00 },
+ { ES7243E_ADC_VMID, 0x0c },
+ /* Set slave mode, enable clocks, power up */
+ { ES7243E_RESET, 0x80 },
+ { ES7243E_CLK1, 0x3a },
+ { ES7243E_PDN, 0x00 },
+
+ /* Set PGAs gain to 0dB */
+ { ES7243E_PGA1, 0x10 },
+ { ES7243E_PGA2, 0x10, 100 * USEC_PER_MSEC },
+
+ /* Reset PGAs */
+ { ES7243E_PDN, 0x03, 100 * USEC_PER_MSEC },
+ { ES7243E_PDN, 0x00 },
+};
+
+static int es7243e_probe(struct snd_soc_component *component)
+{
+ return regmap_multi_reg_write(component->regmap, init_sequence,
+ ARRAY_SIZE(init_sequence));
+}
+
+static int es7243e_suspend(struct snd_soc_component *component)
+{
+ struct es7243e_priv *es7243e = snd_soc_component_get_drvdata(component);
+ int ret;
+ unsigned int val, mask;
+
+ val = FIELD_PREP(ES7243E_SDP_MUTE, 0);
+ ret = snd_soc_component_update_bits(component, ES7243E_SDP,
+ ES7243E_SDP_MUTE, val);
+ if (ret < 0)
+ return ret;
+
+ val = FIELD_PREP(ES7243E_PGA1_EN, 0);
+ snd_soc_component_update_bits(component, ES7243E_PGA1,
+ ES7243E_PGA1_EN, val);
+ if (ret < 0)
+ return ret;
+
+ val = FIELD_PREP(ES7243E_PGA2_EN, 0);
+ snd_soc_component_update_bits(component, ES7243E_PGA2,
+ ES7243E_PGA2_EN, val);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_component_write(component, ES7243E_PDN, 0xff);
+ if (ret < 0)
+ return ret;
+
+ mask = ES7243E_CLK1_ANA_ON | ES7243E_CLK1_ADC_ON;
+ ret = snd_soc_component_update_bits(component, ES7243E_CLK1, mask, 0);
+ if (ret < 0)
+ return ret;
+
+ clk_disable_unprepare(es7243e->lrclk);
+ clk_disable_unprepare(es7243e->sclk);
+
+ return 0;
+}
+
+static int es7243e_resume(struct snd_soc_component *component)
+{
+ struct es7243e_priv *es7243e = snd_soc_component_get_drvdata(component);
+ int ret;
+ unsigned int val;
+
+ ret = clk_prepare_enable(es7243e->sclk);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(es7243e->lrclk);
+ if (ret)
+ return ret;
+
+ val = ES7243E_CLK1_ANA_ON | ES7243E_CLK1_ADC_ON;
+ ret = snd_soc_component_update_bits(component, ES7243E_CLK1, val, val);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_component_write(component, ES7243E_PDN, 0);
+ if (ret < 0)
+ return ret;
+
+ val = FIELD_PREP(ES7243E_PGA1_EN, 1);
+ ret = snd_soc_component_update_bits(component, ES7243E_PGA1,
+ ES7243E_PGA1_EN, val);
+ if (ret < 0)
+ return ret;
+
+ val = FIELD_PREP(ES7243E_PGA2_EN, 1);
+ ret = snd_soc_component_update_bits(component, ES7243E_PGA2,
+ ES7243E_PGA2_EN, val);
+ if (ret < 0)
+ return ret;
+
+ val = FIELD_PREP(ES7243E_SDP_MUTE, 3);
+ ret = snd_soc_component_update_bits(component, ES7243E_SDP,
+ ES7243E_SDP_MUTE, val);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static const struct snd_soc_component_driver soc_component_dev_es7243e = {
+ .probe = es7243e_probe,
+ .controls = es7243e_snd_controls,
+ .suspend = es7243e_suspend,
+ .resume = es7243e_resume,
+ .num_controls = ARRAY_SIZE(es7243e_snd_controls),
+ .dapm_widgets = es7243e_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(es7243e_dapm_widgets),
+ .dapm_routes = es7243e_dapm_routes,
+ .num_dapm_routes = ARRAY_SIZE(es7243e_dapm_routes),
+};
+
+static int es7243e_rates_init(struct device *dev)
+{
+ struct es7243e_priv *es7243e = dev_get_drvdata(dev);
+ unsigned int *rates;
+ unsigned int i;
+
+ rates = devm_kcalloc(dev, ARRAY_SIZE(coeff_div), sizeof(*rates),
+ GFP_KERNEL);
+ if (!rates)
+ return -ENOMEM;
+
+ for (i = 0; i < ARRAY_SIZE(coeff_div); i++)
+ rates[i] = coeff_div[i].sr_rate;
+
+ es7243e->rates.list = rates;
+ es7243e->rates.count = ARRAY_SIZE(coeff_div);
+
+ return 0;
+}
+
+static int es7243e_hw_init(struct device *dev)
+{
+ struct regmap *regmap = dev_get_regmap(dev, NULL);
+ u8 ids[2];
+ u16 chip_id;
+ int ret;
+
+ ret = regmap_bulk_read(regmap, ES7243E_CHIP_ID1, ids, ARRAY_SIZE(ids));
+ if (ret < 0)
+ goto fail;
+
+ chip_id = (ids[0] << 8) | ids[1];
+
+ if (chip_id != 0x7A43) {
+ return dev_err_probe(dev, -ENODEV,
+ "wrong CHIP ID: 0x%04x, expected 0x7A43\n",
+ chip_id);
+ }
+
+ return 0;
+
+fail:
+ return dev_err_probe(dev, ret, "failed to read chip ID\n");
+}
+
+static int es7243e_i2c_probe(struct i2c_client *i2c)
+{
+ struct es7243e_priv *es7243e;
+ struct regmap *regmap;
+ int ret;
+
+ es7243e = devm_kzalloc(&i2c->dev, sizeof(*es7243e), GFP_KERNEL);
+ if (!es7243e)
+ return -ENOMEM;
+
+ es7243e->i2c = i2c;
+ dev_set_drvdata(&i2c->dev, es7243e);
+
+ regmap = devm_regmap_init_i2c(i2c, &es7243e_regmap_config);
+ if (IS_ERR(regmap)) {
+ dev_err(&i2c->dev, "failed to init regmap\n");
+ return PTR_ERR(regmap);
+ }
+
+ es7243e->sclk = devm_clk_get_enabled(&i2c->dev, "sclk");
+ if (IS_ERR(es7243e->sclk))
+ return dev_err_probe(&i2c->dev, PTR_ERR(es7243e->sclk),
+ "failed to get sclk clock\n");
+
+ es7243e->lrclk = devm_clk_get_enabled(&i2c->dev, "lrclk");
+ if (IS_ERR(es7243e->lrclk))
+ return dev_err_probe(&i2c->dev, PTR_ERR(es7243e->lrclk),
+ "failed to get lrclk clock\n");
+
+ ret = es7243e_rates_init(&i2c->dev);
+ if (ret)
+ return ret;
+
+ if (es7243e_hw_init(&i2c->dev))
+ return -ENODEV;
+
+ ret = devm_snd_soc_register_component(&i2c->dev,
+ &soc_component_dev_es7243e,
+ &es7243e_dai, 1);
+ if (ret)
+ return dev_err_probe(&i2c->dev, ret, "failed to register component\n");
+
+ return ret;
+}
+
+static const struct i2c_device_id es7243e_i2c_id[] = {
+ { "es7243e", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, es7243e_i2c_id);
+
+static const struct of_device_id es7243e_dt_ids[] = {
+ { .compatible = "everest,es7243e", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, es7243e_dt_ids);
+
+static struct i2c_driver es7243e_i2c_driver = {
+ .probe = es7243e_i2c_probe,
+ .id_table = es7243e_i2c_id,
+ .driver = {
+ .name = "es7243e-codec",
+ .of_match_table = es7243e_dt_ids,
+ },
+};
+
+module_i2c_driver(es7243e_i2c_driver);
+MODULE_DESCRIPTION("ASoC ES7243E audio ADC driver");
+MODULE_AUTHOR("Igor Prusov <ivprusov@salutedevices.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/ntp8835.c b/sound/soc/codecs/ntp8835.c
index 2b93bea11752c..75426262e9bae 100644
--- a/sound/soc/codecs/ntp8835.c
+++ b/sound/soc/codecs/ntp8835.c
@@ -74,57 +74,10 @@ static const DECLARE_TLV_DB_RANGE(ntp8835_vol_scale,
7, 0xff, TLV_DB_SCALE_ITEM(-10000, 50, 0),
);
-static int ntp8835_mute_info(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_info *uinfo)
-{
- uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
- uinfo->access =
- (SNDRV_CTL_ELEM_ACCESS_TLV_READ | SNDRV_CTL_ELEM_ACCESS_READWRITE);
- uinfo->count = 1;
-
- uinfo->value.integer.min = 0;
- uinfo->value.integer.max = 1;
- uinfo->value.integer.step = 1;
-
- return 0;
-}
-
-static int ntp8835_mute_get(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
-{
- struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
- unsigned int val;
-
- val = snd_soc_component_read(component, NTP8835_SOFT_MUTE);
-
- ucontrol->value.integer.value[0] = val ? 0 : 1;
- return 0;
-}
-
-static int ntp8835_mute_put(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
-{
- struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
- unsigned int val;
-
- val = ucontrol->value.integer.value[0] ? 0 : 7;
-
- snd_soc_component_write(component, NTP8835_SOFT_MUTE, val);
-
- return 0;
-}
-
static const struct snd_kcontrol_new ntp8835_vol_control[] = {
SOC_SINGLE_TLV("Playback Volume", NTP8835_MASTER_VOL, 0,
0xff, 0, ntp8835_vol_scale),
- {
- .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
- .name = "Playback Switch",
- .info = ntp8835_mute_info,
- .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | SNDRV_CTL_ELEM_ACCESS_READWRITE,
- .get = ntp8835_mute_get,
- .put = ntp8835_mute_put,
- },
+ SOC_SINGLE("Playback Switch", NTP8835_PWM_MASK_CTRL0, 1, 1, 1),
};
static void ntp8835_reset_gpio(struct ntp8835_priv *ntp8835)
@@ -321,7 +274,7 @@ static int ntp8835_hw_params(struct snd_pcm_substream *substream,
ret = snd_soc_component_update_bits(component, NTP8835_MCLK_FREQ_CTRL,
NTP8835_MCLK_FREQ_MCF, mcf);
- if (ret)
+ if (ret < 0)
return ret;
switch (ntp8835->format) {
@@ -364,8 +317,10 @@ static int ntp8835_hw_params(struct snd_pcm_substream *substream,
gsa_fmt_mask = NTP8835_GSA_BS_MASK |
NTP8835_GSA_RIGHT_J |
NTP8835_GSA_LSB;
- return snd_soc_component_update_bits(component, NTP8835_GSA_FMT,
- gsa_fmt_mask, gsa_fmt);
+ ret = snd_soc_component_update_bits(component, NTP8835_GSA_FMT,
+ gsa_fmt_mask, gsa_fmt);
+
+ return ret < 0 ? ret : 0;
}
static int ntp8835_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
@@ -385,9 +340,24 @@ static int ntp8835_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
return 0;
};
+static int ntp8835_mute(struct snd_soc_dai *dai, int mute, int stream)
+{
+ unsigned int mute_mask = NTP8835_SOFT_MUTE_SM1 |
+ NTP8835_SOFT_MUTE_SM2 |
+ NTP8835_SOFT_MUTE_SM3;
+
+ int ret = snd_soc_component_update_bits(dai->component,
+ NTP8835_SOFT_MUTE,
+ mute_mask,
+ mute ? mute_mask : 0);
+
+ return ret < 0 ? ret : 0;
+}
+
static const struct snd_soc_dai_ops ntp8835_dai_ops = {
.hw_params = ntp8835_hw_params,
.set_fmt = ntp8835_set_fmt,
+ .mute_stream = ntp8835_mute,
};
static struct snd_soc_dai_driver ntp8835_dai = {
diff --git a/sound/soc/codecs/ntp8918.c b/sound/soc/codecs/ntp8918.c
index 5593d48ef696f..c37bd5b53ed8a 100644
--- a/sound/soc/codecs/ntp8918.c
+++ b/sound/soc/codecs/ntp8918.c
@@ -32,6 +32,15 @@
SNDRV_PCM_FMTBIT_S24_LE | \
SNDRV_PCM_FMTBIT_S32_LE)
+/*
+ * The NTP8918 word clock (WCK) period is fixed at 64 bits,
+ * and the bit clock (BCK) period is equal to the word clock period.
+ * The interface has only 2 channels, meaning the number of ticks
+ * for both word and bit clocks per channel is 64 / 2 = 32.
+ */
+#define NTP8918_BCK_CHANNEL_PERIOD 32
+#define NTP8918_NR_CHANNELS 2
+
#define NTP8918_INPUT_FMT 0x0
#define NTP8918_INPUT_FMT_MASTER_MODE BIT(0)
#define NTP8918_INPUT_FMT_GSA_MODE BIT(1)
@@ -215,7 +224,17 @@ static int ntp8918_hw_params(struct snd_pcm_substream *substream,
int bclk;
int ret;
- bclk = snd_soc_params_to_bclk(params);
+ /*
+ * When calculating bit clock frequency the only non-constant
+ * input that's needed is sample rate which then needs to be
+ * multiplied by constant values of BCK channel period and
+ * number of channels.
+ */
+ bclk = snd_soc_tdm_params_to_bclk(params,
+ NTP8918_BCK_CHANNEL_PERIOD,
+ NTP8918_NR_CHANNELS,
+ 0);
+
switch (bclk) {
case 3072000:
case 2822400:
@@ -233,7 +252,7 @@ static int ntp8918_hw_params(struct snd_pcm_substream *substream,
ret = snd_soc_component_update_bits(component, NTP8918_MCLK_FREQ_CTRL,
NTP8918_MCLK_FREQ_MCF, mcf);
- if (ret)
+ if (ret < 0)
return ret;
switch (ntp8918->format) {
@@ -276,8 +295,11 @@ static int ntp8918_hw_params(struct snd_pcm_substream *substream,
gsa_fmt_mask = NTP8918_GSA_BS_MASK |
NTP8918_GSA_RIGHT_J |
NTP8918_GSA_LSB;
- return snd_soc_component_update_bits(component, NTP8918_GSA_FMT,
- gsa_fmt_mask, gsa_fmt);
+
+ ret = snd_soc_component_update_bits(component, NTP8918_GSA_FMT,
+ gsa_fmt_mask, gsa_fmt);
+
+ return ret < 0 ? ret : 0;
}
static int ntp8918_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
@@ -302,14 +324,40 @@ static int ntp8918_digital_mute(struct snd_soc_dai *dai, int mute, int stream)
unsigned int mute_mask = NTP8918_SOFT_MUTE_SM1 |
NTP8918_SOFT_MUTE_SM2;
- return snd_soc_component_update_bits(dai->component, NTP8918_SOFT_MUTE,
- mute_mask, mute ? mute_mask : 0);
+ int ret = snd_soc_component_update_bits(dai->component,
+ NTP8918_SOFT_MUTE,
+ mute_mask,
+ mute ? mute_mask : 0);
+
+ return ret < 0 ? ret : 0;
+}
+
+static int ntp8918_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
+ unsigned int rx_mask, int slots,
+ int slot_width)
+{
+ if (slots != NTP8918_NR_CHANNELS) {
+ dev_err(dai->component->dev,
+ "Unsupported number of TDM slots %d, should be %d\n",
+ slots, NTP8918_NR_CHANNELS);
+ return -EINVAL;
+ }
+
+ if (slot_width != NTP8918_BCK_CHANNEL_PERIOD) {
+ dev_err(dai->component->dev,
+ "Unsupported TDM slot width %d, should be %d\n",
+ slot_width, NTP8918_BCK_CHANNEL_PERIOD);
+ return -EINVAL;
+ }
+
+ return 0;
}
static const struct snd_soc_dai_ops ntp8918_dai_ops = {
.hw_params = ntp8918_hw_params,
.set_fmt = ntp8918_set_fmt,
.mute_stream = ntp8918_digital_mute,
+ .set_tdm_slot = ntp8918_set_tdm_slot,
};
static struct snd_soc_dai_driver ntp8918_dai = {
diff --git a/sound/soc/meson/Kconfig b/sound/soc/meson/Kconfig
index d9a730994a2a2..86bcd75aaecbb 100644
--- a/sound/soc/meson/Kconfig
+++ b/sound/soc/meson/Kconfig
@@ -135,4 +135,12 @@ config SND_SOC_MESON_T9015
help
Say Y or M if you want to add support for the internal DAC found
on GXL, G12 and SM1 SoC family.
+
+config SND_SOC_MESON_SM1_EQDRC
+ tristate "Amlogic SM1 EQDRC Support"
+ select REGMAP_MMIO
+ help
+ Select Y or M to add support for EQDRC audio effects embedded
+ in the Amlogic SM1 SoC family
+
endmenu
diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile
index 24078e4396b02..ddad06a148fb3 100644
--- a/sound/soc/meson/Makefile
+++ b/sound/soc/meson/Makefile
@@ -25,6 +25,7 @@ snd-soc-meson-gx-sound-card-y := gx-card.o
snd-soc-meson-g12a-toacodec-y := g12a-toacodec.o
snd-soc-meson-g12a-tohdmitx-y := g12a-tohdmitx.o
snd-soc-meson-t9015-y := t9015.o
+snd-soc-meson-sm1-eqdrc-objs := sm1-eqdrc.o
obj-$(CONFIG_SND_MESON_AIU) += snd-soc-meson-aiu.o
obj-$(CONFIG_SND_MESON_AXG_FIFO) += snd-soc-meson-axg-fifo.o
@@ -44,3 +45,4 @@ obj-$(CONFIG_SND_MESON_GX_SOUND_CARD) += snd-soc-meson-gx-sound-card.o
obj-$(CONFIG_SND_MESON_G12A_TOACODEC) += snd-soc-meson-g12a-toacodec.o
obj-$(CONFIG_SND_MESON_G12A_TOHDMITX) += snd-soc-meson-g12a-tohdmitx.o
obj-$(CONFIG_SND_SOC_MESON_T9015) += snd-soc-meson-t9015.o
+obj-$(CONFIG_SND_SOC_MESON_SM1_EQDRC) += snd-soc-meson-sm1-eqdrc.o
diff --git a/sound/soc/meson/axg-frddr.c b/sound/soc/meson/axg-frddr.c
index e70c8c34c7db8..80c418d9216b9 100644
--- a/sound/soc/meson/axg-frddr.c
+++ b/sound/soc/meson/axg-frddr.c
@@ -122,6 +122,11 @@ static const char * const axg_frddr_sel_texts[] = {
"OUT 0", "OUT 1", "OUT 2", "OUT 3", "OUT 4", "OUT 5", "OUT 6", "OUT 7",
};
+static const char * const sm1_frddr_sel_texts[] = {
+ "OUT 0", "OUT 1", "OUT 2", "OUT 3", "OUT 4", "OUT 5", "OUT 6", "OUT 7",
+ "OUT 8",
+};
+
static SOC_ENUM_SINGLE_DECL(axg_frddr_sel_enum, FIFO_CTRL0, CTRL0_SEL_SHIFT,
axg_frddr_sel_texts);
@@ -313,11 +318,11 @@ static const struct snd_kcontrol_new sm1_frddr_out3_enable =
CTRL2_SEL3_EN_SHIFT, 1, 0);
static SOC_ENUM_SINGLE_DECL(sm1_frddr_sel1_enum, FIFO_CTRL2, CTRL2_SEL1_SHIFT,
- axg_frddr_sel_texts);
+ sm1_frddr_sel_texts);
static SOC_ENUM_SINGLE_DECL(sm1_frddr_sel2_enum, FIFO_CTRL2, CTRL2_SEL2_SHIFT,
- axg_frddr_sel_texts);
+ sm1_frddr_sel_texts);
static SOC_ENUM_SINGLE_DECL(sm1_frddr_sel3_enum, FIFO_CTRL2, CTRL2_SEL3_SHIFT,
- axg_frddr_sel_texts);
+ sm1_frddr_sel_texts);
static const struct snd_kcontrol_new sm1_frddr_out1_demux =
SOC_DAPM_ENUM("Output Src 1", sm1_frddr_sel1_enum);
@@ -350,13 +355,53 @@ static const struct snd_soc_dapm_widget sm1_frddr_dapm_widgets[] = {
SND_SOC_DAPM_AIF_OUT("OUT 5", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("OUT 6", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("OUT 7", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("OUT 8", NULL, 0, SND_SOC_NOPM, 0, 0),
+};
+
+static const struct snd_soc_dapm_route sm1_frddr_dapm_routes[] = {
+ { "SRC 1", NULL, "Playback" },
+ { "SRC 2", NULL, "Playback" },
+ { "SRC 3", NULL, "Playback" },
+ { "SRC 1 EN", "Switch", "SRC 1" },
+ { "SRC 2 EN", "Switch", "SRC 2" },
+ { "SRC 3 EN", "Switch", "SRC 3" },
+ { "SINK 1 SEL", NULL, "SRC 1 EN" },
+ { "SINK 2 SEL", NULL, "SRC 2 EN" },
+ { "SINK 3 SEL", NULL, "SRC 3 EN" },
+ { "OUT 0", "OUT 0", "SINK 1 SEL" },
+ { "OUT 1", "OUT 1", "SINK 1 SEL" },
+ { "OUT 2", "OUT 2", "SINK 1 SEL" },
+ { "OUT 3", "OUT 3", "SINK 1 SEL" },
+ { "OUT 4", "OUT 4", "SINK 1 SEL" },
+ { "OUT 5", "OUT 5", "SINK 1 SEL" },
+ { "OUT 6", "OUT 6", "SINK 1 SEL" },
+ { "OUT 7", "OUT 7", "SINK 1 SEL" },
+ { "OUT 8", "OUT 8", "SINK 1 SEL" },
+ { "OUT 0", "OUT 0", "SINK 2 SEL" },
+ { "OUT 1", "OUT 1", "SINK 2 SEL" },
+ { "OUT 2", "OUT 2", "SINK 2 SEL" },
+ { "OUT 3", "OUT 3", "SINK 2 SEL" },
+ { "OUT 4", "OUT 4", "SINK 2 SEL" },
+ { "OUT 5", "OUT 5", "SINK 2 SEL" },
+ { "OUT 6", "OUT 6", "SINK 2 SEL" },
+ { "OUT 7", "OUT 7", "SINK 2 SEL" },
+ { "OUT 8", "OUT 8", "SINK 2 SEL" },
+ { "OUT 0", "OUT 0", "SINK 3 SEL" },
+ { "OUT 1", "OUT 1", "SINK 3 SEL" },
+ { "OUT 2", "OUT 2", "SINK 3 SEL" },
+ { "OUT 3", "OUT 3", "SINK 3 SEL" },
+ { "OUT 4", "OUT 4", "SINK 3 SEL" },
+ { "OUT 5", "OUT 5", "SINK 3 SEL" },
+ { "OUT 6", "OUT 6", "SINK 3 SEL" },
+ { "OUT 7", "OUT 7", "SINK 3 SEL" },
+ { "OUT 8", "OUT 8", "SINK 3 SEL" },
};
static const struct snd_soc_component_driver sm1_frddr_component_drv = {
.dapm_widgets = sm1_frddr_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(sm1_frddr_dapm_widgets),
- .dapm_routes = g12a_frddr_dapm_routes,
- .num_dapm_routes = ARRAY_SIZE(g12a_frddr_dapm_routes),
+ .dapm_routes = sm1_frddr_dapm_routes,
+ .num_dapm_routes = ARRAY_SIZE(sm1_frddr_dapm_routes),
.open = axg_fifo_pcm_open,
.close = axg_fifo_pcm_close,
.hw_params = g12a_fifo_pcm_hw_params,
diff --git a/sound/soc/meson/axg-pdm.c b/sound/soc/meson/axg-pdm.c
index d59050914d3cc..a132444a51fb4 100644
--- a/sound/soc/meson/axg-pdm.c
+++ b/sound/soc/meson/axg-pdm.c
@@ -94,6 +94,7 @@ struct axg_pdm {
struct clk *dclk;
struct clk *sysclk;
struct clk *pclk;
+ u32 sys_rate;
};
static void axg_pdm_enable(struct regmap *map)
@@ -172,10 +173,10 @@ static int axg_pdm_set_sysclk(struct axg_pdm *priv, unsigned int os,
* the requested sample rate. In this case, the sample pointer
* counter could overflow so set a lower system clock rate
*/
- if (sys_rate < priv->cfg->sys_rate)
+ if (sys_rate < priv->sys_rate)
return clk_set_rate(priv->sysclk, sys_rate);
- return clk_set_rate(priv->sysclk, priv->cfg->sys_rate);
+ return clk_set_rate(priv->sysclk, priv->sys_rate);
}
static int axg_pdm_set_sample_pointer(struct axg_pdm *priv)
@@ -386,7 +387,7 @@ static int axg_pdm_dai_probe(struct snd_soc_dai *dai)
* sysclk must be set and enabled as well to access the pdm registers
* Accessing the register w/o it will give a bus error.
*/
- ret = clk_set_rate(priv->sysclk, priv->cfg->sys_rate);
+ ret = clk_set_rate(priv->sysclk, priv->sys_rate);
if (ret) {
dev_err(dai->dev, "setting sysclk failed\n");
goto err_pclk;
@@ -623,6 +624,9 @@ static int axg_pdm_probe(struct platform_device *pdev)
if (IS_ERR(priv->sysclk))
return dev_err_probe(dev, PTR_ERR(priv->sysclk), "failed to get dclk\n");
+ if (device_property_read_u32(dev, "sysrate", &priv->sys_rate))
+ priv->sys_rate = priv->cfg->sys_rate;
+
return devm_snd_soc_register_component(dev, &axg_pdm_component_drv,
&axg_pdm_dai_drv, 1);
}
diff --git a/sound/soc/meson/axg-tdmout.c b/sound/soc/meson/axg-tdmout.c
index c4039e4f08473..2a965cdae4329 100644
--- a/sound/soc/meson/axg-tdmout.c
+++ b/sound/soc/meson/axg-tdmout.c
@@ -261,11 +261,16 @@ static const struct snd_kcontrol_new sm1_tdmout_controls[] = {
};
static const char * const sm1_tdmout_sel_texts[] = {
- "IN 0", "IN 1", "IN 2", "IN 3", "IN 4",
+ "IN 0", "IN 1", "IN 2", "IN 3", "IN 4", "IN EQDRC",
};
-static SOC_ENUM_SINGLE_DECL(sm1_tdmout_sel_enum, TDMOUT_CTRL1,
- TDMOUT_CTRL1_SEL_SHIFT, sm1_tdmout_sel_texts);
+static const unsigned int sm1_tdmout_sel_values[] = {
+ 0, 1, 2, 3, 4, 128,
+};
+
+static SOC_VALUE_ENUM_SINGLE_DECL(sm1_tdmout_sel_enum, TDMOUT_CTRL1,
+ TDMOUT_CTRL1_SEL_SHIFT, 0x87,
+ sm1_tdmout_sel_texts, sm1_tdmout_sel_values);
static const struct snd_kcontrol_new sm1_tdmout_in_mux =
SOC_DAPM_ENUM("Input Source", sm1_tdmout_sel_enum);
@@ -276,6 +281,7 @@ static const struct snd_soc_dapm_widget sm1_tdmout_dapm_widgets[] = {
SND_SOC_DAPM_AIF_IN("IN 2", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 3", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 4", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("IN EQDRC", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_MUX("SRC SEL", SND_SOC_NOPM, 0, 0, &sm1_tdmout_in_mux),
SND_SOC_DAPM_PGA_E("ENC", SND_SOC_NOPM, 0, 0, NULL, 0,
axg_tdm_formatter_event,
@@ -289,6 +295,7 @@ static const struct snd_soc_dapm_route sm1_tdmout_dapm_routes[] = {
{ "SRC SEL", "IN 2", "IN 2" },
{ "SRC SEL", "IN 3", "IN 3" },
{ "SRC SEL", "IN 4", "IN 4" },
+ { "SRC SEL", "IN EQDRC", "IN EQDRC" },
{ "ENC", NULL, "SRC SEL" },
{ "OUT", NULL, "ENC" },
};
diff --git a/sound/soc/meson/g12a-toacodec.c b/sound/soc/meson/g12a-toacodec.c
index a95375b53f0a3..a7c1b69f5d79f 100644
--- a/sound/soc/meson/g12a-toacodec.c
+++ b/sound/soc/meson/g12a-toacodec.c
@@ -20,26 +20,52 @@
#define G12A_TOACODEC_DRV_NAME "g12a-toacodec"
#define TOACODEC_CTRL0 0x0
-#define CTRL0_ENABLE_SHIFT 31
-#define CTRL0_DAT_SEL_SM1_MSB 19
-#define CTRL0_DAT_SEL_SM1_LSB 18
-#define CTRL0_DAT_SEL_MSB 15
-#define CTRL0_DAT_SEL_LSB 14
-#define CTRL0_LANE_SEL_SM1 16
-#define CTRL0_LANE_SEL 12
-#define CTRL0_LRCLK_SEL_SM1_MSB 14
-#define CTRL0_LRCLK_SEL_SM1_LSB 12
-#define CTRL0_LRCLK_SEL_MSB 9
-#define CTRL0_LRCLK_SEL_LSB 8
-#define CTRL0_LRCLK_INV_SM1 BIT(10)
-#define CTRL0_BLK_CAP_INV_SM1 BIT(9)
-#define CTRL0_BLK_CAP_INV BIT(7)
-#define CTRL0_BCLK_O_INV_SM1 BIT(8)
-#define CTRL0_BCLK_O_INV BIT(6)
-#define CTRL0_BCLK_SEL_SM1_MSB 6
-#define CTRL0_BCLK_SEL_MSB 5
-#define CTRL0_BCLK_SEL_LSB 4
-#define CTRL0_MCLK_SEL GENMASK(2, 0)
+
+/* Common bits */
+#define CTRL0_ENABLE_SHIFT 31
+#define CTRL0_MCLK_SEL GENMASK(2, 0)
+
+/* G12A bits */
+#define CTRL0_DAT_SEL_G12A_MSB 15
+#define CTRL0_DAT_SEL_G12A_LSB 14
+#define CTRL0_LANE_SEL_G12A_MSB 13
+#define CTRL0_LANE_SEL_G12A_LSB 12
+#define CTRL0_LRCLK_SEL_G12A_MSB 9
+#define CTRL0_LRCLK_SEL_G12A_LSB 8
+#define CTRL0_BLK_CAP_INV_G12A BIT(7)
+#define CTRL0_BCLK_O_INV_G12A BIT(6)
+#define CTRL0_BCLK_SEL_G12A_MSB 5
+#define CTRL0_BCLK_SEL_G12A_LSB 4
+
+/* SM1 bits */
+#define CTRL0_DAT_SEL_SM1_MSB 19
+#define CTRL0_DAT_SEL_SM1_LSB 18
+#define CTRL0_LANE_SEL_SM1_MSB 17
+#define CTRL0_LANE_SEL_SM1_LSB 16
+#define CTRL0_LRCLK_SEL_SM1_MSB 14
+#define CTRL0_LRCLK_SEL_SM1_LSB 12
+#define CTRL0_LRCLK_INV_SM1 BIT(10)
+#define CTRL0_BLK_CAP_INV_SM1 BIT(9)
+#define CTRL0_BCLK_O_INV_SM1 BIT(8)
+#define CTRL0_BCLK_SEL_SM1_MSB 6
+#define CTRL0_BCLK_SEL_SM1_LSB 4
+
+/* A1 bits */
+#define CTRL0_DAT_CAPTURE_SEL_A1_MSB 23
+#define CTRL0_DAT_CAPTURE_SEL_A1_LSB 23
+#define CTRL0_LANE_CAPTURE_SEL_A1_MSB 22
+#define CTRL0_LANE_CAPTURE_SEL_A1_LSB 20
+#define CTRL0_DAT_SEL_A1_MSB 19
+#define CTRL0_DAT_SEL_A1_LSB 19
+#define CTRL0_LANE_SEL_A1_MSB 18
+#define CTRL0_LANE_SEL_A1_LSB 16
+#define CTRL0_LRCLK_SEL_A1_MSB 14
+#define CTRL0_LRCLK_SEL_A1_LSB 12
+#define CTRL0_LRCLK_INV_A1 BIT(10)
+#define CTRL0_BLK_CAP_INV_A1 BIT(9)
+#define CTRL0_BCLK_O_INV_A1 BIT(8)
+#define CTRL0_BCLK_SEL_A1_MSB 6
+#define CTRL0_BCLK_SEL_A1_LSB 4
#define TOACODEC_OUT_CHMAX 2
@@ -51,6 +77,8 @@ struct g12a_toacodec {
struct g12a_toacodec_match_data {
const struct snd_soc_component_driver *component_drv;
+ struct snd_soc_dai_driver *dai_drv;
+ int num_dai_drv;
struct reg_field field_dat_sel;
struct reg_field field_lrclk_sel;
struct reg_field field_bclk_sel;
@@ -60,6 +88,10 @@ static const char * const g12a_toacodec_mux_texts[] = {
"I2S A", "I2S B", "I2S C",
};
+static const char * const a1_toacodec_mux_texts[] = {
+ "I2S A", "I2S B",
+};
+
static int g12a_toacodec_mux_put_enum(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
@@ -106,15 +138,19 @@ static int g12a_toacodec_mux_put_enum(struct snd_kcontrol *kcontrol,
}
static SOC_ENUM_SINGLE_DECL(g12a_toacodec_mux_enum, TOACODEC_CTRL0,
- CTRL0_DAT_SEL_LSB,
+ CTRL0_DAT_SEL_G12A_LSB,
g12a_toacodec_mux_texts);
static SOC_ENUM_SINGLE_DECL(sm1_toacodec_mux_enum, TOACODEC_CTRL0,
CTRL0_DAT_SEL_SM1_LSB,
g12a_toacodec_mux_texts);
-static const struct snd_kcontrol_new g12a_toacodec_mux =
- SOC_DAPM_ENUM_EXT("Source", g12a_toacodec_mux_enum,
+static SOC_ENUM_SINGLE_DECL(a1_toacodec_mux_enum, TOACODEC_CTRL0,
+ CTRL0_DAT_SEL_A1_LSB,
+ a1_toacodec_mux_texts);
+
+static const struct snd_kcontrol_new a1_toacodec_mux =
+ SOC_DAPM_ENUM_EXT("Source", a1_toacodec_mux_enum,
snd_soc_dapm_get_enum_double,
g12a_toacodec_mux_put_enum);
@@ -123,10 +159,21 @@ static const struct snd_kcontrol_new sm1_toacodec_mux =
snd_soc_dapm_get_enum_double,
g12a_toacodec_mux_put_enum);
+static const struct snd_kcontrol_new g12a_toacodec_mux =
+ SOC_DAPM_ENUM_EXT("Source", g12a_toacodec_mux_enum,
+ snd_soc_dapm_get_enum_double,
+ g12a_toacodec_mux_put_enum);
+
static const struct snd_kcontrol_new g12a_toacodec_out_enable =
SOC_DAPM_SINGLE_AUTODISABLE("Switch", TOACODEC_CTRL0,
CTRL0_ENABLE_SHIFT, 1, 0);
+/* Don't use AUTODISABLE unlike G12A. On A1 it causes noise after playback
+ * is stopped.
+ */
+static const struct snd_kcontrol_new a1_toacodec_enable =
+ SOC_DAPM_SINGLE("Switch", TOACODEC_CTRL0, CTRL0_ENABLE_SHIFT, 1, 0);
+
static const struct snd_soc_dapm_widget g12a_toacodec_widgets[] = {
SND_SOC_DAPM_MUX("SRC", SND_SOC_NOPM, 0, 0,
&g12a_toacodec_mux),
@@ -141,19 +188,34 @@ static const struct snd_soc_dapm_widget sm1_toacodec_widgets[] = {
&g12a_toacodec_out_enable),
};
-static int g12a_toacodec_input_hw_params(struct snd_pcm_substream *substream,
- struct snd_pcm_hw_params *params,
- struct snd_soc_dai *dai)
+static const struct snd_soc_dapm_widget a1_toacodec_widgets[] = {
+ SND_SOC_DAPM_SWITCH("EN", SND_SOC_NOPM, 0, 0,
+ &a1_toacodec_enable),
+ SND_SOC_DAPM_MUX("Playback SRC", SND_SOC_NOPM, 0, 0,
+ &a1_toacodec_mux),
+ SND_SOC_DAPM_MUX("Capture SRC A", SND_SOC_NOPM, 0, 0,
+ &a1_toacodec_mux),
+ SND_SOC_DAPM_MUX("Capture SRC B", SND_SOC_NOPM, 0, 0,
+ &a1_toacodec_mux),
+};
+
+static int g12a_toacodec_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai,
+ bool playback)
{
struct meson_codec_glue_input *data;
int ret;
- ret = meson_codec_glue_input_hw_params(substream, params, dai);
+ ret = playback ? meson_codec_glue_input_hw_params(substream, params, dai)
+ : meson_codec_glue_capture_output_hw_params(substream,
+ params, dai);
if (ret)
return ret;
/* The glue will provide 1 lane out of the 4 to the output */
- data = meson_codec_glue_input_get_data(dai);
+ data = playback ? meson_codec_glue_input_get_data(dai)
+ : meson_codec_glue_capture_output_get_data(dai);
data->params.channels_min = min_t(unsigned int, TOACODEC_OUT_CHMAX,
data->params.channels_min);
data->params.channels_max = min_t(unsigned int, TOACODEC_OUT_CHMAX,
@@ -162,6 +224,21 @@ static int g12a_toacodec_input_hw_params(struct snd_pcm_substream *substream,
return 0;
}
+static int g12a_toacodec_input_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ return g12a_toacodec_hw_params(substream, params, dai, true);
+}
+
+static int
+g12a_toacodec_capture_output_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ return g12a_toacodec_hw_params(substream, params, dai, false);
+}
+
static const struct snd_soc_dai_ops g12a_toacodec_input_ops = {
.probe = meson_codec_glue_input_dai_probe,
.remove = meson_codec_glue_input_dai_remove,
@@ -173,6 +250,17 @@ static const struct snd_soc_dai_ops g12a_toacodec_output_ops = {
.startup = meson_codec_glue_output_startup,
};
+static const struct snd_soc_dai_ops g12a_toacodec_capture_output_ops = {
+ .probe = meson_codec_glue_capture_output_dai_probe,
+ .remove = meson_codec_glue_capture_output_dai_remove,
+ .hw_params = g12a_toacodec_capture_output_hw_params,
+ .set_fmt = meson_codec_glue_capture_output_set_fmt,
+};
+
+static const struct snd_soc_dai_ops g12a_toacodec_capture_input_ops = {
+ .startup = meson_codec_glue_capture_input_startup,
+};
+
#define TOACODEC_STREAM(xname, xsuffix, xchmax) \
{ \
.stream_name = xname " " xsuffix, \
@@ -197,6 +285,20 @@ static const struct snd_soc_dai_ops g12a_toacodec_output_ops = {
.ops = &g12a_toacodec_output_ops, \
}
+#define TOACODEC_CAPTURE_INPUT(xname, xid) { \
+ .name = xname, \
+ .id = (xid), \
+ .playback = TOACODEC_STREAM(xname, "Playback", 2), \
+ .ops = &g12a_toacodec_capture_input_ops, \
+}
+
+#define TOACODEC_CAPTURE_OUTPUT(xname, xid) { \
+ .name = xname, \
+ .id = (xid), \
+ .capture = TOACODEC_STREAM(xname, "Capture", 2), \
+ .ops = &g12a_toacodec_capture_output_ops, \
+}
+
static struct snd_soc_dai_driver g12a_toacodec_dai_drv[] = {
TOACODEC_INPUT("IN A", TOACODEC_IN_A),
TOACODEC_INPUT("IN B", TOACODEC_IN_B),
@@ -204,11 +306,21 @@ static struct snd_soc_dai_driver g12a_toacodec_dai_drv[] = {
TOACODEC_OUTPUT("OUT", TOACODEC_OUT),
};
+static struct snd_soc_dai_driver a1_toacodec_dai_drv[] = {
+ TOACODEC_INPUT("IN A", TOACODEC_IN_A),
+ TOACODEC_INPUT("IN B", TOACODEC_IN_B),
+ TOACODEC_OUTPUT("OUT", TOACODEC_OUT),
+
+ TOACODEC_CAPTURE_OUTPUT("OUT A", TOACODEC_CAPTURE_OUT_A),
+ TOACODEC_CAPTURE_OUTPUT("OUT B", TOACODEC_CAPTURE_OUT_B),
+ TOACODEC_CAPTURE_INPUT("IN", TOACODEC_CAPTURE_IN),
+};
+
static int g12a_toacodec_component_probe(struct snd_soc_component *c)
{
/* Initialize the static clock parameters */
return snd_soc_component_write(c, TOACODEC_CTRL0,
- CTRL0_BLK_CAP_INV);
+ CTRL0_BLK_CAP_INV_G12A);
}
static int sm1_toacodec_component_probe(struct snd_soc_component *c)
@@ -218,6 +330,32 @@ static int sm1_toacodec_component_probe(struct snd_soc_component *c)
CTRL0_BLK_CAP_INV_SM1);
}
+static int a1_toacodec_component_probe(struct snd_soc_component *c)
+{
+ /* Initialize the static clock parameters */
+ return snd_soc_component_write(c, TOACODEC_CTRL0,
+ CTRL0_BLK_CAP_INV_A1);
+}
+
+static int a1_toacodec_of_xlate_dai_name(struct snd_soc_component *component,
+ const struct of_phandle_args *args,
+ const char **dai_name)
+{
+ struct snd_soc_dai *dai;
+
+ if (args->args_count != 1)
+ return -EINVAL;
+
+ for_each_component_dais(component, dai) {
+ if (dai->id == args->args[0]) {
+ *dai_name = dai->driver->name;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
static const struct snd_soc_dapm_route g12a_toacodec_routes[] = {
{ "SRC", "I2S A", "IN A Playback" },
{ "SRC", "I2S B", "IN B Playback" },
@@ -226,12 +364,32 @@ static const struct snd_soc_dapm_route g12a_toacodec_routes[] = {
{ "OUT Capture", NULL, "OUT EN" },
};
+static const struct snd_soc_dapm_route a1_toacodec_routes[] = {
+ { "Playback SRC", "I2S A", "IN A Playback" },
+ { "Playback SRC", "I2S B", "IN B Playback" },
+ { "EN", "Switch", "Playback SRC" },
+ { "OUT Capture", NULL, "Playback SRC" },
+
+ { "EN", "Switch", "IN Playback" },
+ { "Capture SRC A", "I2S A", "EN" },
+ { "Capture SRC B", "I2S B", "EN" },
+ { "OUT A Capture", NULL, "Capture SRC A" },
+ { "OUT B Capture", NULL, "Capture SRC B" },
+};
+
static const struct snd_kcontrol_new g12a_toacodec_controls[] = {
- SOC_SINGLE("Lane Select", TOACODEC_CTRL0, CTRL0_LANE_SEL, 3, 0),
+ SOC_SINGLE("Lane Select", TOACODEC_CTRL0, CTRL0_LANE_SEL_G12A_LSB,
+ CTRL0_LANE_SEL_G12A_MSB - CTRL0_LANE_SEL_G12A_LSB + 1, 0),
};
static const struct snd_kcontrol_new sm1_toacodec_controls[] = {
- SOC_SINGLE("Lane Select", TOACODEC_CTRL0, CTRL0_LANE_SEL_SM1, 3, 0),
+ SOC_SINGLE("Lane Select", TOACODEC_CTRL0, CTRL0_LANE_SEL_SM1_LSB,
+ CTRL0_LANE_SEL_SM1_MSB - CTRL0_LANE_SEL_SM1_LSB + 1, 0),
+};
+
+static const struct snd_kcontrol_new a1_toacodec_controls[] = {
+ SOC_SINGLE("Lane Select", TOACODEC_CTRL0, CTRL0_LANE_SEL_A1_LSB,
+ CTRL0_LANE_SEL_A1_MSB - CTRL0_LANE_SEL_A1_LSB + 1, 0),
};
static const struct snd_soc_component_driver g12a_toacodec_component_drv = {
@@ -256,24 +414,74 @@ static const struct snd_soc_component_driver sm1_toacodec_component_drv = {
.endianness = 1,
};
+static const struct snd_soc_component_driver a1_toacodec_component_drv = {
+ .probe = a1_toacodec_component_probe,
+ .of_xlate_dai_name = a1_toacodec_of_xlate_dai_name,
+ .controls = a1_toacodec_controls,
+ .num_controls = ARRAY_SIZE(a1_toacodec_controls),
+ .dapm_widgets = a1_toacodec_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(a1_toacodec_widgets),
+ .dapm_routes = a1_toacodec_routes,
+ .num_dapm_routes = ARRAY_SIZE(a1_toacodec_routes),
+ .endianness = 1,
+};
+
+static const struct reg_default g12a_toacodec_reg_default[] = {
+ { TOACODEC_CTRL0, 0x0 },
+};
+
+static bool g12_toacodec_readable_reg(struct device *dev, unsigned int reg)
+{
+ /* There are no readable registers */
+ return false;
+}
+
static const struct regmap_config g12a_toacodec_regmap_cfg = {
- .reg_bits = 32,
- .val_bits = 32,
- .reg_stride = 4,
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = TOACODEC_CTRL0,
+ .max_register_is_0 = true,
+ .cache_type = REGCACHE_FLAT,
+ .reg_defaults = g12a_toacodec_reg_default,
+ .num_reg_defaults = ARRAY_SIZE(g12a_toacodec_reg_default),
+ .readable_reg = g12_toacodec_readable_reg,
};
static const struct g12a_toacodec_match_data g12a_toacodec_match_data = {
.component_drv = &g12a_toacodec_component_drv,
- .field_dat_sel = REG_FIELD(TOACODEC_CTRL0, 14, 15),
- .field_lrclk_sel = REG_FIELD(TOACODEC_CTRL0, 8, 9),
- .field_bclk_sel = REG_FIELD(TOACODEC_CTRL0, 4, 5),
+ .dai_drv = g12a_toacodec_dai_drv,
+ .num_dai_drv = ARRAY_SIZE(g12a_toacodec_dai_drv),
+ .field_dat_sel = REG_FIELD(TOACODEC_CTRL0, CTRL0_DAT_SEL_G12A_LSB,
+ CTRL0_DAT_SEL_G12A_MSB),
+ .field_lrclk_sel = REG_FIELD(TOACODEC_CTRL0, CTRL0_LRCLK_SEL_G12A_LSB,
+ CTRL0_LRCLK_SEL_G12A_MSB),
+ .field_bclk_sel = REG_FIELD(TOACODEC_CTRL0, CTRL0_BCLK_SEL_G12A_LSB,
+ CTRL0_BCLK_SEL_G12A_MSB),
};
static const struct g12a_toacodec_match_data sm1_toacodec_match_data = {
.component_drv = &sm1_toacodec_component_drv,
- .field_dat_sel = REG_FIELD(TOACODEC_CTRL0, 18, 19),
- .field_lrclk_sel = REG_FIELD(TOACODEC_CTRL0, 12, 14),
- .field_bclk_sel = REG_FIELD(TOACODEC_CTRL0, 4, 6),
+ .dai_drv = g12a_toacodec_dai_drv,
+ .num_dai_drv = ARRAY_SIZE(g12a_toacodec_dai_drv),
+ .field_dat_sel = REG_FIELD(TOACODEC_CTRL0, CTRL0_DAT_SEL_SM1_LSB,
+ CTRL0_DAT_SEL_SM1_MSB),
+ .field_lrclk_sel = REG_FIELD(TOACODEC_CTRL0, CTRL0_LRCLK_SEL_SM1_LSB,
+ CTRL0_LRCLK_SEL_SM1_MSB),
+ .field_bclk_sel = REG_FIELD(TOACODEC_CTRL0, CTRL0_BCLK_SEL_SM1_LSB,
+ CTRL0_BCLK_SEL_SM1_MSB),
+};
+
+static const struct g12a_toacodec_match_data a1_toacodec_match_data = {
+ .component_drv = &a1_toacodec_component_drv,
+ .dai_drv = a1_toacodec_dai_drv,
+ .num_dai_drv = ARRAY_SIZE(a1_toacodec_dai_drv),
+ .field_dat_sel = REG_FIELD(TOACODEC_CTRL0, CTRL0_DAT_SEL_A1_LSB,
+ CTRL0_DAT_SEL_A1_MSB),
+ .field_lrclk_sel = REG_FIELD(TOACODEC_CTRL0, CTRL0_LRCLK_SEL_A1_LSB,
+ CTRL0_LRCLK_SEL_A1_MSB),
+ .field_bclk_sel = REG_FIELD(TOACODEC_CTRL0, CTRL0_BCLK_SEL_A1_LSB,
+ CTRL0_BCLK_SEL_A1_MSB),
};
static const struct of_device_id g12a_toacodec_of_match[] = {
@@ -285,6 +493,10 @@ static const struct of_device_id g12a_toacodec_of_match[] = {
.compatible = "amlogic,sm1-toacodec",
.data = &sm1_toacodec_match_data,
},
+ {
+ .compatible = "amlogic,a1-toacodec",
+ .data = &a1_toacodec_match_data,
+ },
{}
};
MODULE_DEVICE_TABLE(of, g12a_toacodec_of_match);
@@ -337,9 +549,9 @@ static int g12a_toacodec_probe(struct platform_device *pdev)
if (IS_ERR(priv->field_bclk_sel))
return PTR_ERR(priv->field_bclk_sel);
- return devm_snd_soc_register_component(dev,
- data->component_drv, g12a_toacodec_dai_drv,
- ARRAY_SIZE(g12a_toacodec_dai_drv));
+ return devm_snd_soc_register_component(dev, data->component_drv,
+ data->dai_drv,
+ data->num_dai_drv);
}
static struct platform_driver g12a_toacodec_pdrv = {
diff --git a/sound/soc/meson/meson-card-utils.c b/sound/soc/meson/meson-card-utils.c
index cdb759b466ad4..601f20b0ef99d 100644
--- a/sound/soc/meson/meson-card-utils.c
+++ b/sound/soc/meson/meson-card-utils.c
@@ -136,29 +136,30 @@ int meson_card_set_be_link(struct snd_soc_card *card,
struct snd_soc_dai_link *link,
struct device_node *node)
{
- struct snd_soc_dai_link_component *codec;
int ret, num_codecs;
num_codecs = of_get_child_count(node);
- if (!num_codecs) {
- dev_err(card->dev, "be link %s has no codec\n",
- node->full_name);
- return -EINVAL;
- }
+ if (num_codecs) {
+ struct snd_soc_dai_link_component *codec;
- codec = devm_kcalloc(card->dev, num_codecs, sizeof(*codec), GFP_KERNEL);
- if (!codec)
- return -ENOMEM;
+ codec = devm_kcalloc(card->dev, num_codecs, sizeof(*codec),
+ GFP_KERNEL);
+ if (!codec)
+ return -ENOMEM;
- link->codecs = codec;
- link->num_codecs = num_codecs;
+ link->codecs = codec;
+ link->num_codecs = num_codecs;
- for_each_child_of_node_scoped(node, np) {
- ret = meson_card_parse_dai(card, np, codec);
- if (ret)
- return ret;
+ for_each_child_of_node_scoped(node, np) {
+ ret = meson_card_parse_dai(card, np, codec);
+ if (ret)
+ return ret;
- codec++;
+ codec++;
+ }
+ } else {
+ link->codecs = &snd_soc_dummy_dlc;
+ link->num_codecs = 1;
}
ret = meson_card_set_link_name(card, link, node, "be");
@@ -281,6 +282,7 @@ int meson_card_probe(struct platform_device *pdev)
priv->card.owner = THIS_MODULE;
priv->card.dev = dev;
priv->card.driver_name = dev->driver->name;
+ priv->card.component_chaining = 1;
priv->match_data = data;
ret = snd_soc_of_parse_card_name(&priv->card, "model");
diff --git a/sound/soc/meson/meson-codec-glue.c b/sound/soc/meson/meson-codec-glue.c
index 2ff6066e1b6cc..fb29bd0cd77b3 100644
--- a/sound/soc/meson/meson-codec-glue.c
+++ b/sound/soc/meson/meson-codec-glue.c
@@ -11,65 +11,94 @@
#include "meson-codec-glue.h"
static struct snd_soc_dapm_widget *
-meson_codec_glue_get_input(struct snd_soc_dapm_widget *w)
+meson_codec_glue_get_data_widget(struct snd_soc_dapm_widget *w, bool playback)
{
struct snd_soc_dapm_path *p;
- struct snd_soc_dapm_widget *in;
-
- snd_soc_dapm_widget_for_each_source_path(w, p) {
+ struct snd_soc_dapm_widget *node;
+ enum snd_soc_dapm_type id = playback ? snd_soc_dapm_dai_in
+ : snd_soc_dapm_dai_out;
+ enum snd_soc_dapm_direction dir = playback ? SND_SOC_DAPM_DIR_IN
+ : SND_SOC_DAPM_DIR_OUT;
+ enum snd_soc_dapm_direction rdir = playback ? SND_SOC_DAPM_DIR_OUT
+ : SND_SOC_DAPM_DIR_IN;
+
+ snd_soc_dapm_widget_for_each_path(w, rdir, p) {
if (!p->connect)
continue;
/* Check that we still are in the same component */
if (snd_soc_dapm_to_component(w->dapm) !=
- snd_soc_dapm_to_component(p->source->dapm))
+ snd_soc_dapm_to_component(p->node[dir]->dapm))
continue;
- if (p->source->id == snd_soc_dapm_dai_in)
- return p->source;
+ if (p->node[dir]->id == id)
+ return p->node[dir];
- in = meson_codec_glue_get_input(p->source);
- if (in)
- return in;
+ node = meson_codec_glue_get_data_widget(p->node[dir], playback);
+ if (node)
+ return node;
}
return NULL;
}
-static void meson_codec_glue_input_set_data(struct snd_soc_dai *dai,
- struct meson_codec_glue_input *data)
+static void meson_codec_glue_set_data(struct snd_soc_dai *dai,
+ struct meson_codec_glue_input *data,
+ bool playback)
+{
+ int stream = playback ? SNDRV_PCM_STREAM_PLAYBACK
+ : SNDRV_PCM_STREAM_CAPTURE;
+
+ snd_soc_dai_dma_data_set(dai, stream, data);
+}
+
+static struct meson_codec_glue_input *
+meson_codec_glue_get_data(struct snd_soc_dai *dai, bool playback)
{
- snd_soc_dai_dma_data_set_playback(dai, data);
+ int stream = playback ? SNDRV_PCM_STREAM_PLAYBACK
+ : SNDRV_PCM_STREAM_CAPTURE;
+
+ return snd_soc_dai_dma_data_get(dai, stream);
}
struct meson_codec_glue_input *
meson_codec_glue_input_get_data(struct snd_soc_dai *dai)
{
- return snd_soc_dai_dma_data_get_playback(dai);
+ return meson_codec_glue_get_data(dai, true);
}
EXPORT_SYMBOL_GPL(meson_codec_glue_input_get_data);
+struct meson_codec_glue_input *
+meson_codec_glue_capture_output_get_data(struct snd_soc_dai *dai)
+{
+ return meson_codec_glue_get_data(dai, false);
+}
+EXPORT_SYMBOL_GPL(meson_codec_glue_capture_output_get_data);
+
static struct meson_codec_glue_input *
-meson_codec_glue_output_get_input_data(struct snd_soc_dapm_widget *w)
+meson_codec_glue_data(struct snd_soc_dapm_widget *w, bool playback)
{
- struct snd_soc_dapm_widget *in =
- meson_codec_glue_get_input(w);
+ struct snd_soc_dapm_widget *node =
+ meson_codec_glue_get_data_widget(w, playback);
struct snd_soc_dai *dai;
- if (WARN_ON(!in))
+ if (WARN_ON(!node))
return NULL;
- dai = in->priv;
+ dai = node->priv;
- return meson_codec_glue_input_get_data(dai);
+ return meson_codec_glue_get_data(dai, playback);
}
-int meson_codec_glue_input_hw_params(struct snd_pcm_substream *substream,
- struct snd_pcm_hw_params *params,
- struct snd_soc_dai *dai)
+static int meson_codec_glue_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai,
+ bool playback)
{
struct meson_codec_glue_input *data =
- meson_codec_glue_input_get_data(dai);
+ meson_codec_glue_get_data(dai, playback);
+ struct snd_soc_pcm_stream *stream = playback ? &dai->driver->playback
+ : &dai->driver->capture;
data->params.rates = snd_pcm_rate_to_rate_bit(params_rate(params));
data->params.rate_min = params_rate(params);
@@ -77,32 +106,64 @@ int meson_codec_glue_input_hw_params(struct snd_pcm_substream *substream,
data->params.formats = 1ULL << (__force int) params_format(params);
data->params.channels_min = params_channels(params);
data->params.channels_max = params_channels(params);
- data->params.sig_bits = dai->driver->playback.sig_bits;
+ data->params.sig_bits = stream->sig_bits;
return 0;
}
+
+int meson_codec_glue_input_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ return meson_codec_glue_hw_params(substream, params, dai, true);
+}
EXPORT_SYMBOL_GPL(meson_codec_glue_input_hw_params);
-int meson_codec_glue_input_set_fmt(struct snd_soc_dai *dai,
- unsigned int fmt)
+int meson_codec_glue_capture_output_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ return meson_codec_glue_hw_params(substream, params, dai, false);
+}
+EXPORT_SYMBOL_GPL(meson_codec_glue_capture_output_hw_params);
+
+static int meson_codec_glue_set_fmt(struct snd_soc_dai *dai,
+ unsigned int fmt,
+ bool playback)
{
struct meson_codec_glue_input *data =
- meson_codec_glue_input_get_data(dai);
+ meson_codec_glue_get_data(dai, playback);
/* Save the source stream format for the downstream link */
data->fmt = fmt;
return 0;
}
+
+int meson_codec_glue_input_set_fmt(struct snd_soc_dai *dai,
+ unsigned int fmt)
+{
+ return meson_codec_glue_set_fmt(dai, fmt, true);
+}
EXPORT_SYMBOL_GPL(meson_codec_glue_input_set_fmt);
-int meson_codec_glue_output_startup(struct snd_pcm_substream *substream,
- struct snd_soc_dai *dai)
+int meson_codec_glue_capture_output_set_fmt(struct snd_soc_dai *dai,
+ unsigned int fmt)
+{
+ return meson_codec_glue_set_fmt(dai, fmt, false);
+}
+EXPORT_SYMBOL_GPL(meson_codec_glue_capture_output_set_fmt);
+
+static int meson_codec_glue_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai,
+ bool playback)
{
+ int stream = playback ? SNDRV_PCM_STREAM_CAPTURE
+ : SNDRV_PCM_STREAM_PLAYBACK;
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
- struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget_capture(dai);
- struct meson_codec_glue_input *in_data = meson_codec_glue_output_get_input_data(w);
+ struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(dai, stream);
+ struct meson_codec_glue_input *data = meson_codec_glue_data(w, playback);
- if (!in_data)
+ if (!data)
return -ENODEV;
if (WARN_ON(!rtd->dai_link->c2c_params)) {
@@ -111,14 +172,27 @@ int meson_codec_glue_output_startup(struct snd_pcm_substream *substream,
}
/* Replace link params with the input params */
- rtd->dai_link->c2c_params = &in_data->params;
+ rtd->dai_link->c2c_params = &data->params;
rtd->dai_link->num_c2c_params = 1;
- return snd_soc_runtime_set_dai_fmt(rtd, in_data->fmt);
+ return snd_soc_runtime_set_dai_fmt(rtd, data->fmt);
+}
+
+int meson_codec_glue_output_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ return meson_codec_glue_startup(substream, dai, true);
}
EXPORT_SYMBOL_GPL(meson_codec_glue_output_startup);
-int meson_codec_glue_input_dai_probe(struct snd_soc_dai *dai)
+int meson_codec_glue_capture_input_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ return meson_codec_glue_startup(substream, dai, false);
+}
+EXPORT_SYMBOL_GPL(meson_codec_glue_capture_input_startup);
+
+static int meson_codec_glue_dai_probe(struct snd_soc_dai *dai, bool playback)
{
struct meson_codec_glue_input *data;
@@ -126,21 +200,43 @@ int meson_codec_glue_input_dai_probe(struct snd_soc_dai *dai)
if (!data)
return -ENOMEM;
- meson_codec_glue_input_set_data(dai, data);
+ meson_codec_glue_set_data(dai, data, playback);
return 0;
}
+
+int meson_codec_glue_input_dai_probe(struct snd_soc_dai *dai)
+{
+ return meson_codec_glue_dai_probe(dai, true);
+}
EXPORT_SYMBOL_GPL(meson_codec_glue_input_dai_probe);
-int meson_codec_glue_input_dai_remove(struct snd_soc_dai *dai)
+int meson_codec_glue_capture_output_dai_probe(struct snd_soc_dai *dai)
+{
+ return meson_codec_glue_dai_probe(dai, false);
+}
+EXPORT_SYMBOL_GPL(meson_codec_glue_capture_output_dai_probe);
+
+static int meson_codec_glue_dai_remove(struct snd_soc_dai *dai, bool playback)
{
struct meson_codec_glue_input *data =
- meson_codec_glue_input_get_data(dai);
+ meson_codec_glue_get_data(dai, playback);
kfree(data);
return 0;
}
+
+int meson_codec_glue_input_dai_remove(struct snd_soc_dai *dai)
+{
+ return meson_codec_glue_dai_remove(dai, true);
+}
EXPORT_SYMBOL_GPL(meson_codec_glue_input_dai_remove);
+int meson_codec_glue_capture_output_dai_remove(struct snd_soc_dai *dai)
+{
+ return meson_codec_glue_dai_remove(dai, false);
+}
+EXPORT_SYMBOL_GPL(meson_codec_glue_capture_output_dai_remove);
+
MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
MODULE_DESCRIPTION("Amlogic Codec Glue Helpers");
MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/meson/meson-codec-glue.h b/sound/soc/meson/meson-codec-glue.h
index 07f99446c0c6b..75d20aa756383 100644
--- a/sound/soc/meson/meson-codec-glue.h
+++ b/sound/soc/meson/meson-codec-glue.h
@@ -14,6 +14,10 @@ struct meson_codec_glue_input {
unsigned int fmt;
};
+/*
+ * Playback stream
+ */
+
/* Input helpers */
struct meson_codec_glue_input *
meson_codec_glue_input_get_data(struct snd_soc_dai *dai);
@@ -29,4 +33,23 @@ int meson_codec_glue_input_dai_remove(struct snd_soc_dai *dai);
int meson_codec_glue_output_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai);
+/*
+ * Capture stream
+ */
+
+/* Output helpers */
+struct meson_codec_glue_input *
+meson_codec_glue_capture_output_get_data(struct snd_soc_dai *dai);
+int meson_codec_glue_capture_output_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai);
+int meson_codec_glue_capture_output_set_fmt(struct snd_soc_dai *dai,
+ unsigned int fmt);
+int meson_codec_glue_capture_output_dai_probe(struct snd_soc_dai *dai);
+int meson_codec_glue_capture_output_dai_remove(struct snd_soc_dai *dai);
+
+/* Input helpers */
+int meson_codec_glue_capture_input_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai);
+
#endif /* _MESON_CODEC_GLUE_H */
diff --git a/sound/soc/meson/sm1-eqdrc.c b/sound/soc/meson/sm1-eqdrc.c
new file mode 100644
index 0000000000000..47d236ae8c463
--- /dev/null
+++ b/sound/soc/meson/sm1-eqdrc.c
@@ -0,0 +1,673 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+/*
+ * Copyright (c) 2024, SaluteDevices. All Rights Reserved.
+ *
+ * Author: Jan Dakinevich <jan.dakinevich@salutedevices.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+#include <sound/pcm_params.h>
+
+#include "axg-tdm.h"
+
+#define COEF_RAM_CNTL 0x000
+#define COEF_RAM_CNTL_VALID 0
+#define COEF_RAM_CNTL_WRITE 1
+#define COEF_RAM_CNTL_ADDR_LSB 2
+#define COEF_RAM_CNTL_ADDR_MSB 9
+#define COEF_RAM_DATA 0x004
+
+#define EQ_EN 0x008
+#define EQ_1_EN 0
+#define EQ_2_EN 1
+
+#define TAP_CNTL 0x00c
+#define TAP_EQ_1_VAL(_val) ((_val) << 0)
+#define TAP_EQ_2_VAL(_val) ((_val) << 5)
+
+#define EQ_VOLUME 0x010
+#define EQ_VOLUME_SLEW_CNT 0x014
+#define MUTE 0x018
+#define MUTE_ALL 31
+#define MUTE_R 0
+#define MUTE_L 1
+
+#define DRC_CNTL 0x01c
+#define DRC_CNTL_EN 0
+#define DRC_CNTL_RMS 1
+#define DRC_CNTL_TAP(_val) ((_val) << 3)
+#define DRC_BASE 0x020
+#define DRC_RMS_COEF0 0x00
+#define DRC_RMS_COEF1 0x04
+#define DRC_THD0 0x08
+#define DRC_THD1 0x0c
+#define DRC_THD2 0x10
+#define DRC_THD3 0x14
+#define DRC_THD4 0x18
+#define DRC_K0 0x1c
+#define DRC_K1 0x20
+#define DRC_K2 0x24
+#define DRC_K3 0x28
+#define DRC_K4 0x2c
+#define DRC_K5 0x30
+#define DRC_THD_OUT0 0x34
+#define DRC_THD_OUT1 0x38
+#define DRC_THD_OUT2 0x3c
+#define DRC_THD_OUT3 0x40
+#define DRC_OFFSET 0x44
+#define DRC_RELEASE_COEF00 0x48
+#define DRC_RELEASE_COEF01 0x4c
+#define DRC_RELEASE_COEF10 0x50
+#define DRC_RELEASE_COEF11 0x54
+#define DRC_RELEASE_COEF20 0x58
+#define DRC_RELEASE_COEF21 0x5c
+#define DRC_RELEASE_COEF30 0x60
+#define DRC_RELEASE_COEF31 0x64
+#define DRC_RELEASE_COEF40 0x68
+#define DRC_RELEASE_COEF41 0x6c
+#define DRC_RELEASE_COEF50 0x70
+#define DRC_RELEASE_COEF51 0x74
+#define DRC_ATTACK_COEF00 0x78
+#define DRC_ATTACK_COEF01 0x7c
+#define DRC_ATTACK_COEF10 0x80
+#define DRC_ATTACK_COEF11 0x84
+#define DRC_ATTACK_COEF20 0x88
+#define DRC_ATTACK_COEF21 0x8c
+#define DRC_ATTACK_COEF30 0x90
+#define DRC_ATTACK_COEF31 0x94
+#define DRC_ATTACK_COEF40 0x98
+#define DRC_ATTACK_COEF41 0x9c
+#define DRC_ATTACK_COEF50 0xa0
+#define DRC_ATTACK_COEF51 0xa4
+#define DRC_LOOPBACK_CNTL 0xa8
+
+#define MDRC_CNTL 0x0cc
+#define MDRC_CNTL_LOW_EN 0
+#define MDRC_CNTL_MID_EN 1
+#define MDRC_CNTL_HIGH_EN 2
+#define MDRC_CNTL_LOW_RMS 3
+#define MDRC_CNTL_MID_RMS 4
+#define MDRC_CNTL_HIGH_RMS 5
+#define MDRC_CNTL_ALL_EN 8
+#define MDRC_LOW_BASE 0x0d0
+#define MDRC_LOW_RMS_COEF0 0x00
+#define MDRC_LOW_RMS_COEF1 0x04
+#define MDRC_LOW_RELEASE_COEF0 0x08
+#define MDRC_LOW_RELEASE_COEF1 0x0c
+#define MDRC_LOW_ATTACK_COEF0 0x10
+#define MDRC_LOW_ATTACK_COEF1 0x14
+#define MDRC_LOW_THD 0x18
+#define MDRC_LOW_K 0x1c
+#define MDRC_LOW_GAIN 0x20
+#define MDRC_LOW_OFFSET 0x24
+#define MDRC_MID_BASE 0x0f8
+#define MDRC_MID_RMS_COEF0 0x00
+#define MDRC_MID_RMS_COEF1 0x04
+#define MDRC_MID_RELEASE_COEF0 0x08
+#define MDRC_MID_RELEASE_COEF1 0x0c
+#define MDRC_MID_ATTACK_COEF0 0x10
+#define MDRC_MID_ATTACK_COEF1 0x14
+#define MDRC_MID_THD 0x18
+#define MDRC_MID_K 0x1c
+#define MDRC_MID_OFFSET 0x20
+#define MDRC_MID_GAIN 0x24
+#define MDRC_HIGH_BASE 0x120
+#define MDRC_HIGH_RMS_COEF0 0x00
+#define MDRC_HIGH_RMS_COEF1 0x04
+#define MDRC_HIGH_RELEASE_COEF0 0x08
+#define MDRC_HIGH_RELEASE_COEF1 0x0c
+#define MDRC_HIGH_ATTACK_COEF0 0x10
+#define MDRC_HIGH_ATTACK_COEF1 0x14
+#define MDRC_HIGH_THD 0x18
+#define MDRC_HIGH_K 0x1c
+#define MDRC_HIGH_OFFSET 0x20
+#define MDRC_HIGH_GAIN 0x24
+
+#define ED_CNTL 0x148
+#define DC_EN 0x14c
+
+#define TOP_CTL0 0x20c
+#define TOP_CTL0_EN 0
+#define TOP_CTL0_RST_OUT 1
+#define TOP_CTL0_RST_IN 2
+#define TOP_CTL0_MSB_POS_MASK GENMASK(10, 6)
+#define TOP_CTL0_MSB_POS_VAL(x) ((x) << 6)
+#define TOP_CTL0_MSB_TYPE_MASK GENMASK(13, 11)
+#define TOP_CTL0_MSB_TYPE_VAL(x) ((x) << 11)
+#define TOP_CTL0_START 31
+#define TOP_CTL1 0x210
+#define TOP_CTL2 0x214
+#define TOP_CTL2_REQ_EN 3
+#define TOP_CTL2_REQ_SEL 0
+
+
+#define RAM_DC_CUT_ADDR 0
+#define RAM_DC_CUT_COUNT 5
+#define RAM_EQ_1_ADDR 5
+#define RAM_EQ_1_COUNT 100
+#define RAM_EQ_2_ADDR 105
+#define RAM_EQ_2_COUNT 100
+#define RAM_CROSSOVER_ADDR 205
+#define RAM_CROSSOVER_COUNT 20
+
+#define REG_MDRC_COUNT 10
+#define REG_DRC_COUNT 43
+
+struct sm1_eqdrc {
+ unsigned int physical_width;
+ unsigned int width;
+};
+
+typedef int (*sm1_eqdrc_rw_t)(struct snd_soc_component *, bool, unsigned int,
+ unsigned int *, unsigned int);
+
+static int sm1_eqdrc_ram_rw(struct snd_soc_component *c, bool is_write,
+ unsigned int offset, unsigned int *data,
+ unsigned int count)
+{
+ while (count--) {
+ unsigned int value, cntl;
+
+ cntl = offset << COEF_RAM_CNTL_ADDR_LSB;
+ if (cntl & ~GENMASK(COEF_RAM_CNTL_ADDR_MSB,
+ COEF_RAM_CNTL_ADDR_LSB))
+ return -EINVAL;
+
+ cntl |= BIT(COEF_RAM_CNTL_VALID);
+
+ if (is_write) {
+ value = cpu_to_le32(*data);
+
+ snd_soc_component_write(c, COEF_RAM_DATA, value);
+ cntl |= BIT(COEF_RAM_CNTL_WRITE);
+ snd_soc_component_write(c, COEF_RAM_CNTL, cntl);
+ } else {
+ snd_soc_component_write(c, COEF_RAM_CNTL, cntl);
+ value = snd_soc_component_read(c, COEF_RAM_DATA);
+
+ *data = le32_to_cpu(value);
+ }
+
+ snd_soc_component_write(c, COEF_RAM_CNTL, cntl);
+
+ offset++;
+ data++;
+ }
+
+ return 0;
+}
+
+static int sm1_eqdrc_reg_rw(struct snd_soc_component *c, bool is_write,
+ unsigned int offset, unsigned int *data,
+ unsigned int count)
+{
+ while (count--) {
+ unsigned int addr = offset;
+
+ /*
+ * Registers's layout is mostly the same for all three bands of
+ * MDRC, except these two register which are swapped in the
+ * hardware. This w/a make the layout of firmwares consistent
+ * from point of view of userspace.
+ */
+ switch (offset) {
+ case MDRC_LOW_BASE + MDRC_LOW_GAIN:
+ addr = MDRC_LOW_BASE + MDRC_LOW_OFFSET;
+ break;
+ case MDRC_LOW_BASE + MDRC_LOW_OFFSET:
+ addr = MDRC_LOW_BASE + MDRC_LOW_GAIN;
+ break;
+ }
+
+ if (is_write)
+ snd_soc_component_write(c, addr, cpu_to_le32(*data));
+ else
+ *data = le32_to_cpu(snd_soc_component_read(c, addr));
+
+ offset += sizeof(unsigned int);
+ data++;
+ }
+
+ return 0;
+}
+
+static int sm1_eqdrc_component_probe(struct snd_soc_component *component)
+{
+ /*
+ * In the documentation TOP_CTL1's description is actually the
+ * description of TOP_CTL2 and there is absolutely no information about
+ * real TOP_CTL1 register. Just write the value, that Amlogic writes in
+ * their own driver.
+ */
+ snd_soc_component_write(component, TOP_CTL1, 0x00100031);
+
+ /* Apparently, TAP_CNTL configures how many of EQ bands is used */
+ snd_soc_component_write(component, TAP_CNTL,
+ TAP_EQ_1_VAL(20) | TAP_EQ_2_VAL(0));
+
+ /*
+ * Initial value of this register is 0x83ff3030, not 0 as described in
+ * the doc. Bits 30-31 are described as "volume_step: It is used to set
+ * volume adjust step." and they are not handled in this driver.
+ * Non-zero value of these bits causes harmonic distortion.
+ */
+ snd_soc_component_write(component, EQ_VOLUME, 0);
+
+ /* No clue, what is it */
+ snd_soc_component_write(component, DRC_CNTL, DRC_CNTL_TAP(2));
+
+ return 0;
+}
+
+static int sm1_eqdrc_pm_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *control,
+ int event)
+{
+ struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm);
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ /*
+ * In the documentation bit0 is described as "ed_int: soft reset"
+ * Amlogic in their driver writes 1 to this bit two times.
+ * May be, it is sufficient.
+ */
+ snd_soc_component_write(c, ED_CNTL, BIT(0));
+ snd_soc_component_write(c, ED_CNTL, BIT(0));
+
+ /* Enable sink */
+ snd_soc_component_update_bits(c, TOP_CTL2,
+ BIT(TOP_CTL2_REQ_EN), BIT(TOP_CTL2_REQ_EN));
+
+ /* Reset fifo */
+ snd_soc_component_update_bits(c, TOP_CTL0,
+ BIT(TOP_CTL0_RST_IN) | BIT(TOP_CTL0_RST_OUT),
+ BIT(TOP_CTL0_RST_IN) | BIT(TOP_CTL0_RST_OUT));
+
+ /* EQDRC module enable */
+ snd_soc_component_update_bits(c, TOP_CTL0,
+ BIT(TOP_CTL0_EN), BIT(TOP_CTL0_EN));
+
+ /* Start EQDRC function */
+ snd_soc_component_update_bits(c, TOP_CTL0,
+ BIT(TOP_CTL0_START), BIT(TOP_CTL0_START));
+
+ break;
+
+ case SND_SOC_DAPM_PRE_PMD:
+ snd_soc_component_write(c, TOP_CTL0, 0);
+ break;
+
+ default:
+ dev_err(c->dev, "Unexpected event %d\n", event);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const DECLARE_TLV_DB_MINMAX(sm1_eqdrc_master_volume_tlv, -10388, 2388);
+static const DECLARE_TLV_DB_MINMAX(sm1_eqdrc_volume_tlv, -10350, 2400);
+
+static int sm1_eqdrc_ram_kctl_read(struct snd_kcontrol *kcontrol,
+ sm1_eqdrc_rw_t rw,
+ unsigned int offset,
+ unsigned int __user *bytes,
+ unsigned int size)
+{
+ struct snd_soc_component *c = snd_kcontrol_chip(kcontrol);
+ struct soc_bytes_ext *params = (void *)kcontrol->private_value;
+ unsigned int data_size = size - 2 * sizeof(unsigned int);
+ unsigned int *data;
+ unsigned int len = params->max - 2 * sizeof(unsigned int);
+ int ret;
+
+ if (size < 2 * sizeof(unsigned int))
+ return -EINVAL;
+
+ if (len > data_size)
+ return -EINVAL;
+
+ data = kmalloc(len, GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ ret = rw(c, false, offset, data, len / sizeof(unsigned int));
+ if (ret)
+ goto out;
+
+ ret = put_user(0, &bytes[0]);
+ if (ret)
+ goto out;
+
+ ret = put_user(len, &bytes[1]);
+ if (ret)
+ goto out;
+
+ ret = copy_to_user(&bytes[2], data, len);
+
+out:
+ kfree(data);
+ return ret;
+}
+
+static int sm1_eqdrc_ram_kctl_write(struct snd_kcontrol *kcontrol,
+ sm1_eqdrc_rw_t rw,
+ unsigned int offset,
+ const unsigned int __user *bytes,
+ unsigned int size)
+{
+ struct snd_soc_component *c = snd_kcontrol_chip(kcontrol);
+ struct soc_bytes_ext *params = (void *)kcontrol->private_value;
+ unsigned int data_size = size - 2 * sizeof(unsigned int);
+ unsigned int *data;
+ unsigned int len;
+ int ret;
+
+ if (size < 2 * sizeof(unsigned int))
+ return -EINVAL;
+
+ if (get_user(len, &bytes[1]))
+ return -EFAULT;
+ if (len > data_size)
+ return -EINVAL;
+ if (len != params->max - 2 * sizeof(unsigned int))
+ return -EINVAL;
+
+ data = memdup_user(&bytes[2], len);
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ ret = rw(c, true, offset, data, len / sizeof(unsigned int));
+ kfree(data);
+
+ return ret;
+}
+
+#define DECLARE_MEM_KCTL(__name, __rw, __addr) \
+static int sm1_eqdrc_##__name##_get(struct snd_kcontrol *kcontrol, \
+ unsigned int __user *bytes, \
+ unsigned int size) \
+{ \
+ return sm1_eqdrc_ram_kctl_read(kcontrol, (__rw), (__addr), \
+ bytes, size); \
+} \
+static int sm1_eqdrc_##__name##_put(struct snd_kcontrol *kcontrol, \
+ const unsigned int __user *bytes, \
+ unsigned int size) \
+{ \
+ return sm1_eqdrc_ram_kctl_write(kcontrol, (__rw), (__addr), \
+ bytes, size); \
+}
+
+DECLARE_MEM_KCTL(dc_cut, sm1_eqdrc_ram_rw, RAM_DC_CUT_ADDR)
+
+DECLARE_MEM_KCTL(eq_1, sm1_eqdrc_ram_rw, RAM_EQ_1_ADDR)
+DECLARE_MEM_KCTL(eq_2, sm1_eqdrc_ram_rw, RAM_EQ_2_ADDR)
+
+DECLARE_MEM_KCTL(crossover, sm1_eqdrc_ram_rw, RAM_CROSSOVER_ADDR)
+
+DECLARE_MEM_KCTL(mdrc_low, sm1_eqdrc_reg_rw, MDRC_LOW_BASE)
+DECLARE_MEM_KCTL(mdrc_mid, sm1_eqdrc_reg_rw, MDRC_MID_BASE)
+DECLARE_MEM_KCTL(mdrc_high, sm1_eqdrc_reg_rw, MDRC_HIGH_BASE)
+
+DECLARE_MEM_KCTL(drc, sm1_eqdrc_reg_rw, DRC_BASE)
+
+static const char * const sm1_eqdrc_peaking_rms_txt[] = { "Peaking", "RMS" };
+
+static SOC_ENUM_SINGLE_DECL(sm1_eqdrc_mdrc_low_mode, MDRC_CNTL,
+ MDRC_CNTL_LOW_RMS, sm1_eqdrc_peaking_rms_txt);
+static SOC_ENUM_SINGLE_DECL(sm1_eqdrc_mdrc_mid_mode, MDRC_CNTL,
+ MDRC_CNTL_MID_RMS, sm1_eqdrc_peaking_rms_txt);
+static SOC_ENUM_SINGLE_DECL(sm1_eqdrc_mdrc_high_mode, MDRC_CNTL,
+ MDRC_CNTL_HIGH_RMS, sm1_eqdrc_peaking_rms_txt);
+
+static SOC_ENUM_SINGLE_DECL(sm1_eqdrc_drc_mode, DRC_CNTL,
+ DRC_CNTL_RMS, sm1_eqdrc_peaking_rms_txt);
+
+static const struct snd_kcontrol_new sm1_eqdrc_controls[] = {
+ SOC_SINGLE("Master Playback Switch", MUTE, MUTE_ALL, 1, 1),
+ SOC_SINGLE_TLV("Master Playback Volume", EQ_VOLUME, 16, 0x3ff, 1,
+ sm1_eqdrc_master_volume_tlv),
+ SOC_DOUBLE("Playback Switch", MUTE, MUTE_L, MUTE_R, 1, 1),
+ SOC_DOUBLE_TLV("Playback Volume", EQ_VOLUME, 0, 8, 0xff, 1,
+ sm1_eqdrc_volume_tlv),
+ SOC_SINGLE("Volume Slew", EQ_VOLUME_SLEW_CNT, 0, 0xfff, 0),
+
+ SOC_SINGLE("DC Cut Switch", DC_EN, 0, 1, 0),
+ SND_SOC_BYTES_TLV("DC Cut",
+ (RAM_DC_CUT_COUNT + 2) * sizeof(unsigned int),
+ sm1_eqdrc_dc_cut_get, sm1_eqdrc_dc_cut_put),
+
+ SOC_SINGLE("EQ 1 Switch", EQ_EN, EQ_1_EN, 1, 0),
+ SND_SOC_BYTES_TLV("EQ 1",
+ (RAM_EQ_1_COUNT + 2) * sizeof(unsigned int),
+ sm1_eqdrc_eq_1_get, sm1_eqdrc_eq_1_put),
+ SOC_SINGLE("EQ 2 Switch", EQ_EN, EQ_2_EN, 1, 0),
+ SND_SOC_BYTES_TLV("EQ 2",
+ (RAM_EQ_2_COUNT + 2) * sizeof(unsigned int),
+ sm1_eqdrc_eq_2_get, sm1_eqdrc_eq_2_put),
+
+ SND_SOC_BYTES_TLV("Crossover",
+ (RAM_CROSSOVER_COUNT + 2) * sizeof(unsigned int),
+ sm1_eqdrc_crossover_get, sm1_eqdrc_crossover_put),
+
+ SOC_SINGLE("MDRC Switch", MDRC_CNTL, MDRC_CNTL_ALL_EN, 1, 0),
+ SOC_SINGLE("MDRC Low Switch", MDRC_CNTL, MDRC_CNTL_LOW_EN, 1, 0),
+ SOC_ENUM("MDRC Low Mode", sm1_eqdrc_mdrc_low_mode),
+ SND_SOC_BYTES_TLV("MDRC Low",
+ (REG_MDRC_COUNT + 2) * sizeof(unsigned int),
+ sm1_eqdrc_mdrc_low_get, sm1_eqdrc_mdrc_low_put),
+ SOC_SINGLE("MDRC Mid Switch", MDRC_CNTL, MDRC_CNTL_MID_EN, 1, 0),
+ SOC_ENUM("MDRC Mid Mode", sm1_eqdrc_mdrc_mid_mode),
+ SND_SOC_BYTES_TLV("MDRC Mid",
+ (REG_MDRC_COUNT + 2) * sizeof(unsigned int),
+ sm1_eqdrc_mdrc_mid_get, sm1_eqdrc_mdrc_mid_put),
+ SOC_SINGLE("MDRC High Switch", MDRC_CNTL, MDRC_CNTL_HIGH_EN, 1, 0),
+ SOC_ENUM("MDRC High Mode", sm1_eqdrc_mdrc_high_mode),
+ SND_SOC_BYTES_TLV("MDRC High",
+ (REG_MDRC_COUNT + 2) * sizeof(unsigned int),
+ sm1_eqdrc_mdrc_high_get, sm1_eqdrc_mdrc_high_put),
+
+ SOC_SINGLE("DRC Switch", DRC_CNTL, DRC_CNTL_EN, 1, 0),
+ SOC_ENUM("DRC Mode", sm1_eqdrc_drc_mode),
+ SND_SOC_BYTES_TLV("DRC",
+ (REG_DRC_COUNT + 2) * sizeof(unsigned int),
+ sm1_eqdrc_drc_get, sm1_eqdrc_drc_put),
+};
+
+static const char * const sm1_eqdrc_src_sel_txt[] = {
+ "FRDDR_A",
+ "FRDDR_B",
+ "FRDDR_C",
+};
+static SOC_ENUM_SINGLE_DECL(sm1_eqdrc_src_sel, TOP_CTL0, 4,
+ sm1_eqdrc_src_sel_txt);
+static const struct snd_kcontrol_new sm1_eqdrc_src_sel_mux =
+ SOC_DAPM_ENUM("SRC SEL", sm1_eqdrc_src_sel);
+
+static const char * const sm1_eqdrc_sink_sel_txt[] = {
+ "TDMOUT_A",
+ "TDMOUT_B",
+ "TDMOUT_C",
+ "SPDIFOUT_A",
+ "SPDIFOUT_B",
+};
+static SOC_ENUM_SINGLE_DECL(sm1_eqdrc_sink_sel, TOP_CTL2, TOP_CTL2_REQ_SEL,
+ sm1_eqdrc_sink_sel_txt);
+static const struct snd_kcontrol_new sm1_eqdrc_sink_sel_mux =
+ SOC_DAPM_ENUM("SINK SEL", sm1_eqdrc_sink_sel);
+
+static const struct snd_soc_dapm_widget sm1_eqdrc_dapm_widgets[] = {
+ SND_SOC_DAPM_MUX("SRC SEL", SND_SOC_NOPM, 0, 0, &sm1_eqdrc_src_sel_mux),
+ SND_SOC_DAPM_PGA_E("ENC", SND_SOC_NOPM, 0, 0, NULL, 0,
+ sm1_eqdrc_pm_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_MUX("SINK SEL", SND_SOC_NOPM, 0, 0,
+ &sm1_eqdrc_sink_sel_mux),
+ SND_SOC_DAPM_AIF_OUT("OUT", NULL, 0, SND_SOC_NOPM, 0, 0),
+};
+
+static const struct snd_soc_dapm_route sm1_eqdrc_dapm_routes[] = {
+ { "SRC SEL", "FRDDR_A", "Playback" },
+ { "SRC SEL", "FRDDR_B", "Playback" },
+ { "SRC SEL", "FRDDR_C", "Playback" },
+ { "ENC", NULL, "SRC SEL" },
+ { "SINK SEL", "TDMOUT_A", "ENC" },
+ { "SINK SEL", "TDMOUT_B", "ENC" },
+ { "SINK SEL", "TDMOUT_C", "ENC" },
+ { "SINK SEL", "SPDIFOUT_A", "ENC" },
+ { "SINK SEL", "SPDIFOUT_B", "ENC" },
+ { "OUT", NULL, "SINK SEL" },
+};
+
+static int sm1_eqdrc_component_hw_params(struct snd_soc_component *c,
+ struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct sm1_eqdrc *priv = snd_soc_component_get_drvdata(c);
+
+ priv->physical_width = params_physical_width(params);
+ priv->width = params_width(params);
+
+ return 0;
+}
+
+static int sm1_eqdrc_component_prepare(struct snd_soc_component *c,
+ struct snd_pcm_substream *substream)
+{
+ struct sm1_eqdrc *priv = snd_soc_component_get_drvdata(c);
+ unsigned int val;
+ unsigned int mask;
+
+ mask = TOP_CTL0_MSB_POS_MASK;
+ val = TOP_CTL0_MSB_POS_VAL(priv->width - 1);
+
+ mask |= TOP_CTL0_MSB_TYPE_MASK;
+ switch (priv->physical_width) {
+ case 8:
+ val |= TOP_CTL0_MSB_TYPE_VAL(0);
+ break;
+ case 16:
+ val |= TOP_CTL0_MSB_TYPE_VAL(2);
+ break;
+ case 32:
+ val |= TOP_CTL0_MSB_TYPE_VAL(4);
+ break;
+ default:
+ dev_err(c->dev, "Unsupported physical width: %u\n",
+ priv->physical_width);
+ return -EINVAL;
+ }
+
+ snd_soc_component_update_bits(c, TOP_CTL0, mask, val);
+
+ return 0;
+}
+
+static const struct snd_soc_component_driver sm1_eqdrc_component_driver = {
+ .probe = sm1_eqdrc_component_probe,
+ .hw_params = sm1_eqdrc_component_hw_params,
+ .prepare = sm1_eqdrc_component_prepare,
+ .controls = sm1_eqdrc_controls,
+ .num_controls = ARRAY_SIZE(sm1_eqdrc_controls),
+ .dapm_widgets = sm1_eqdrc_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(sm1_eqdrc_dapm_widgets),
+ .dapm_routes = sm1_eqdrc_dapm_routes,
+ .num_dapm_routes = ARRAY_SIZE(sm1_eqdrc_dapm_routes),
+ .endianness = 1,
+};
+
+static struct snd_soc_dai_driver sm1_eqdrc_dai_driver = {
+ .name = "sm1-eqdrc",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS,
+ .rate_min = 5512,
+ .rate_max = 768000,
+ .formats = AXG_TDM_FORMATS,
+ },
+};
+
+static const struct regmap_config sm1_eqdrc_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+};
+
+static int sm1_eqdrc_probe(struct platform_device *pdev)
+{
+ struct sm1_eqdrc *priv;
+ void __iomem *regs;
+ struct regmap *map;
+ struct clk *clk;
+ struct reset_control *reset;
+ int ret;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(struct sm1_eqdrc), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+ platform_set_drvdata(pdev, priv);
+
+ regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ map = devm_regmap_init_mmio(&pdev->dev, regs, &sm1_eqdrc_config);
+ if (IS_ERR(map))
+ return dev_err_probe(&pdev->dev, PTR_ERR(map),
+ "failed to init regmap");
+
+ clk = devm_clk_get_enabled(&pdev->dev, "pclk");
+ if (IS_ERR(clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(clk),
+ "failed to init parent clock");
+
+ clk = devm_clk_get_enabled(&pdev->dev, "sysclk");
+ if (IS_ERR(clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(clk),
+ "failed to init system clock");
+
+ reset = devm_reset_control_get_exclusive(&pdev->dev, NULL);
+ if (IS_ERR(reset))
+ return dev_err_probe(&pdev->dev, PTR_ERR(reset),
+ "failed to init reset");
+
+ ret = reset_control_reset(reset);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "failed to perform reset");
+
+ return devm_snd_soc_register_component(&pdev->dev,
+ &sm1_eqdrc_component_driver,
+ &sm1_eqdrc_dai_driver, 1);
+}
+
+static const struct of_device_id sm1_eqdrc_of_match[] = {
+ {
+ .compatible = "amlogic,sm1-eqdrc",
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, sm1_eqdrc_of_match);
+
+static struct platform_driver sm1_eqdrc_driver = {
+ .driver = {
+ .name = "sm1-eqdrc",
+ .of_match_table = of_match_ptr(sm1_eqdrc_of_match),
+ },
+ .probe = sm1_eqdrc_probe,
+};
+
+module_platform_driver(sm1_eqdrc_driver);
+
+MODULE_DESCRIPTION("Amlogic SM1 EQDRC driver");
+MODULE_AUTHOR("Jan Dakinevich <jan.dakinevich@salutedevices.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/meson/t9015.c b/sound/soc/meson/t9015.c
index da1a93946d679..ef5452576fa92 100644
--- a/sound/soc/meson/t9015.c
+++ b/sound/soc/meson/t9015.c
@@ -19,16 +19,33 @@
#define LOLP_EN 3
#define DACR_EN 4
#define DACL_EN 5
+#define ADCR_EN 6
+#define ADCL_EN 7
+#define PGAR_ZCD_EN 8
+#define PGAL_ZCD_EN 9
+#define PGAR_EN 10
+#define PGAL_EN 11
+#define ADCR_INV 16
+#define ADCL_INV 17
+#define ADCR_SRC 18
+#define ADCL_SRC 19
#define DACR_INV 20
#define DACL_INV 21
#define DACR_SRC 22
#define DACL_SRC 23
+#define ADC_DEM_EN 26
+#define ADC_FILTER_MODE 28
+#define ADC_FILTER_EN 29
#define REFP_BUF_EN BIT(12)
#define BIAS_CURRENT_EN BIT(13)
#define VMID_GEN_FAST BIT(14)
#define VMID_GEN_EN BIT(15)
#define I2S_MODE BIT(30)
#define VOL_CTRL0 0x04
+#define PGAR_VC 0
+#define PGAL_VC 8
+#define ADCR_VC 16
+#define ADCL_VC 24
#define GAIN_H 31
#define GAIN_L 23
#define VOL_CTRL1 0x08
@@ -46,9 +63,38 @@
#define LOLN_POL 8
#define LOLP_POL 12
#define POWER_CFG 0x10
+#define LINEIN_CFG 0x14
+#define MICBIAS_LEVEL 0
+#define MICBIAS_EN 3
+#define PGAR_CTVMN 8
+#define PGAR_CTVMP 9
+#define PGAL_CTVMN 10
+#define PGAL_CTVMP 11
+#define PGAR_CTVIN 12
+#define PGAR_CTVIP 13
+#define PGAL_CTVIN 14
+#define PGAL_CTVIP 15
+
+#define PGAR_MASK (BIT(PGAR_CTVMP) | BIT(PGAR_CTVMN) | \
+ BIT(PGAR_CTVIP) | BIT(PGAR_CTVIN))
+#define PGAR_DIFF (BIT(PGAR_CTVIP) | BIT(PGAR_CTVIN))
+#define PGAR_POSITIVE (BIT(PGAR_CTVIP) | BIT(PGAR_CTVMN))
+#define PGAR_NEGATIVE (BIT(PGAR_CTVIN) | BIT(PGAR_CTVMP))
+#define PGAL_MASK (BIT(PGAL_CTVMP) | BIT(PGAL_CTVMN) | \
+ BIT(PGAL_CTVIP) | BIT(PGAL_CTVIN))
+#define PGAL_DIFF (BIT(PGAL_CTVIP) | BIT(PGAL_CTVIN))
+#define PGAL_POSITIVE (BIT(PGAL_CTVIP) | BIT(PGAL_CTVMN))
+#define PGAL_NEGATIVE (BIT(PGAL_CTVIN) | BIT(PGAL_CTVMP))
struct t9015 {
struct regulator *avdd;
+ bool out_drv_enable;
+};
+
+struct t9015_match_data {
+ const struct snd_soc_component_driver *component_drv;
+ struct snd_soc_dai_driver *dai_drv;
+ unsigned int max_register;
};
static int t9015_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
@@ -97,6 +143,31 @@ static struct snd_soc_dai_driver t9015_dai = {
.ops = &t9015_dai_ops,
};
+static struct snd_soc_dai_driver a1_t9015_dai = {
+ .name = "t9015-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = (SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S20_LE |
+ SNDRV_PCM_FMTBIT_S24_LE),
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = (SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S20_LE |
+ SNDRV_PCM_FMTBIT_S24_LE),
+ },
+ .ops = &t9015_dai_ops,
+};
+
static const DECLARE_TLV_DB_MINMAX_MUTE(dac_vol_tlv, -9525, 0);
static const char * const ramp_rate_txt[] = { "Fast", "Slow" };
@@ -112,6 +183,11 @@ static SOC_ENUM_SINGLE_DECL(dacl_in_enum, BLOCK_EN, DACL_SRC, dacl_in_txt);
static const char * const mono_txt[] = { "Stereo", "Mono"};
static SOC_ENUM_SINGLE_DECL(mono_enum, VOL_CTRL1, DAC_MONO, mono_txt);
+static const struct snd_kcontrol_new t9015_right_dac_mux =
+ SOC_DAPM_ENUM("Right DAC Source", dacr_in_enum);
+static const struct snd_kcontrol_new t9015_left_dac_mux =
+ SOC_DAPM_ENUM("Left DAC Source", dacl_in_enum);
+
static const struct snd_kcontrol_new t9015_snd_controls[] = {
/* Volume Controls */
SOC_ENUM("Playback Channel Mode", mono_enum),
@@ -126,11 +202,6 @@ static const struct snd_kcontrol_new t9015_snd_controls[] = {
SOC_SINGLE("Unmute Ramp Switch", VOL_CTRL1, UNMUTE_MODE, 1, 0),
};
-static const struct snd_kcontrol_new t9015_right_dac_mux =
- SOC_DAPM_ENUM("Right DAC Source", dacr_in_enum);
-static const struct snd_kcontrol_new t9015_left_dac_mux =
- SOC_DAPM_ENUM("Left DAC Source", dacl_in_enum);
-
static const struct snd_soc_dapm_widget t9015_dapm_widgets[] = {
SND_SOC_DAPM_AIF_IN("Right IN", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("Left IN", NULL, 0, SND_SOC_NOPM, 0, 0),
@@ -173,6 +244,157 @@ static const struct snd_soc_dapm_route t9015_dapm_routes[] = {
{ "LOLP", NULL, "Left+ Driver", },
};
+static const DECLARE_TLV_DB_MINMAX_MUTE(a1_adc_vol_tlv, -29625, 0);
+static const DECLARE_TLV_DB_MINMAX_MUTE(a1_adc_pga_vol_tlv, -1200, 0);
+
+static const char * const a1_adc_right_txt[] = { "Right", "Left" };
+static SOC_ENUM_SINGLE_DECL(a1_adc_right, BLOCK_EN, ADCR_SRC, a1_adc_right_txt);
+
+static const char * const a1_adc_left_txt[] = { "Left", "Right" };
+static SOC_ENUM_SINGLE_DECL(a1_adc_left, BLOCK_EN, ADCL_SRC, a1_adc_left_txt);
+
+static const struct snd_kcontrol_new a1_adc_right_mux =
+ SOC_DAPM_ENUM("ADC Right Source", a1_adc_right);
+static const struct snd_kcontrol_new a1_adc_left_mux =
+ SOC_DAPM_ENUM("ADC Left Source", a1_adc_left);
+
+static const char * const a1_adc_filter_mode_txt[] = { "Voice", "HiFi"};
+static SOC_ENUM_SINGLE_DECL(a1_adc_filter_mode, BLOCK_EN, ADC_FILTER_MODE,
+ a1_adc_filter_mode_txt);
+
+static const char * const a1_adc_mic_bias_level_txt[] = { "2.0V", "2.1V",
+ "2.3V", "2.5V", "2.8V" };
+static const unsigned int a1_adc_mic_bias_level_values[] = { 0, 1, 2, 3, 7 };
+static SOC_VALUE_ENUM_SINGLE_DECL(a1_adc_mic_bias_level,
+ LINEIN_CFG, MICBIAS_LEVEL, 0x7,
+ a1_adc_mic_bias_level_txt,
+ a1_adc_mic_bias_level_values);
+
+static const struct snd_kcontrol_new a1_t9015_snd_controls[] = {
+ /* Volume Controls */
+ SOC_ENUM("Playback Channel Mode", mono_enum),
+ SOC_SINGLE("Playback Switch", VOL_CTRL1, DAC_SOFT_MUTE, 1, 1),
+ SOC_DOUBLE_TLV("Playback Volume", VOL_CTRL1, DACL_VC, DACR_VC,
+ 0xff, 0, dac_vol_tlv),
+
+ /* Ramp Controls */
+ SOC_ENUM("Ramp Rate", ramp_rate_enum),
+ SOC_SINGLE("Volume Ramp Switch", VOL_CTRL1, VC_RAMP_MODE, 1, 0),
+ SOC_SINGLE("Mute Ramp Switch", VOL_CTRL1, MUTE_MODE, 1, 0),
+ SOC_SINGLE("Unmute Ramp Switch", VOL_CTRL1, UNMUTE_MODE, 1, 0),
+
+ /* ADC Controls */
+ SOC_DOUBLE_TLV("ADC Volume", VOL_CTRL0, ADCL_VC, ADCR_VC,
+ 0x7f, 0, a1_adc_vol_tlv),
+ SOC_SINGLE("ADC Filter Switch", BLOCK_EN, ADC_FILTER_EN, 1, 0),
+ SOC_ENUM("ADC Filter Mode", a1_adc_filter_mode),
+ SOC_SINGLE("ADC Mic Bias Switch", LINEIN_CFG, MICBIAS_EN, 1, 0),
+ SOC_ENUM("ADC Mic Bias Level", a1_adc_mic_bias_level),
+ SOC_SINGLE("ADC DEM Switch", BLOCK_EN, ADC_DEM_EN, 1, 0),
+ SOC_DOUBLE_TLV("ADC PGA Volume", VOL_CTRL0, PGAR_VC, PGAL_VC,
+ 0x1f, 0, a1_adc_pga_vol_tlv),
+ SOC_DOUBLE("ADC PGA Zero Cross-detection Switch", BLOCK_EN,
+ PGAL_ZCD_EN, PGAR_ZCD_EN, 1, 0),
+};
+
+static int a1_t9015_dapm_out_drv_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol,
+ int event, bool is_left)
+{
+ struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm);
+ struct t9015 *priv = snd_soc_component_get_drvdata(c);
+ unsigned mask = is_left ? BIT(LOLP_EN) : BIT(LORP_EN);
+
+ if (priv->out_drv_enable)
+ return 0;
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ snd_soc_component_update_bits(c, BLOCK_EN, mask, mask);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ snd_soc_component_update_bits(c, BLOCK_EN, mask, 0);
+ break;
+ default:
+ dev_err(c->dev, "'%s': unexpected event %#x", w->name, event);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int a1_t9015_dapm_right_out_drv_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol,
+ int event)
+{
+ return a1_t9015_dapm_out_drv_event(w, kcontrol, event, false);
+}
+
+static int a1_t9015_dapm_left_out_drv_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol,
+ int event)
+{
+ return a1_t9015_dapm_out_drv_event(w, kcontrol, event, true);
+}
+
+static const struct snd_soc_dapm_widget a1_t9015_dapm_widgets[] = {
+ SND_SOC_DAPM_AIF_IN("Right IN", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("Left IN", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_MUX("Right DAC Sel", SND_SOC_NOPM, 0, 0,
+ &t9015_right_dac_mux),
+ SND_SOC_DAPM_MUX("Left DAC Sel", SND_SOC_NOPM, 0, 0,
+ &t9015_left_dac_mux),
+ SND_SOC_DAPM_DAC("Right DAC", NULL, BLOCK_EN, DACR_EN, 0),
+ SND_SOC_DAPM_DAC("Left DAC", NULL, BLOCK_EN, DACL_EN, 0),
+ SND_SOC_DAPM_OUT_DRV_E("Right+ Driver", SND_SOC_NOPM, 0, 0, NULL, 0,
+ a1_t9015_dapm_right_out_drv_event,
+ (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD)),
+ SND_SOC_DAPM_OUT_DRV_E("Left+ Driver", SND_SOC_NOPM, 0, 0, NULL, 0,
+ a1_t9015_dapm_left_out_drv_event,
+ (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD)),
+ SND_SOC_DAPM_OUTPUT("LORP"),
+ SND_SOC_DAPM_OUTPUT("LOLP"),
+
+ SND_SOC_DAPM_INPUT("ADC IN Right"),
+ SND_SOC_DAPM_INPUT("ADC IN Left"),
+ SND_SOC_DAPM_PGA("ADC PGA Right", BLOCK_EN, PGAR_EN, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("ADC PGA Left", BLOCK_EN, PGAL_EN, 0, NULL, 0),
+ SND_SOC_DAPM_ADC("ADC Right", NULL, BLOCK_EN, ADCR_EN, 0),
+ SND_SOC_DAPM_ADC("ADC Left", NULL, BLOCK_EN, ADCL_EN, 0),
+ SND_SOC_DAPM_MUX("ADC Right Sel", SND_SOC_NOPM, 0, 0, &a1_adc_right_mux),
+ SND_SOC_DAPM_MUX("ADC Left Sel", SND_SOC_NOPM, 0, 0, &a1_adc_left_mux),
+ SND_SOC_DAPM_AIF_OUT("ADC OUT Right", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("ADC OUT Left", NULL, 0, SND_SOC_NOPM, 0, 0),
+};
+
+static const struct snd_soc_dapm_route a1_t9015_dapm_routes[] = {
+ { "Right IN", NULL, "Playback" },
+ { "Left IN", NULL, "Playback" },
+ { "Right DAC Sel", "Right", "Right IN" },
+ { "Right DAC Sel", "Left", "Left IN" },
+ { "Left DAC Sel", "Right", "Right IN" },
+ { "Left DAC Sel", "Left", "Left IN" },
+ { "Right DAC", NULL, "Right DAC Sel" },
+ { "Left DAC", NULL, "Left DAC Sel" },
+ { "Right+ Driver", NULL, "Right DAC" },
+ { "Left+ Driver", NULL, "Left DAC" },
+ { "LORP", NULL, "Right+ Driver", },
+ { "LOLP", NULL, "Left+ Driver", },
+
+ { "ADC PGA Right", NULL, "ADC IN Right" },
+ { "ADC PGA Left", NULL, "ADC IN Left" },
+ { "ADC Right", NULL, "ADC PGA Right" },
+ { "ADC Left", NULL, "ADC PGA Left" },
+ { "ADC Right Sel", "Right", "ADC Right" },
+ { "ADC Right Sel", "Left", "ADC Left" },
+ { "ADC Left Sel", "Right", "ADC Right" },
+ { "ADC Left Sel", "Left", "ADC Left" },
+ { "ADC OUT Right", NULL, "ADC Right Sel" },
+ { "ADC OUT Left", NULL, "ADC Left Sel" },
+ { "Capture", NULL, "ADC OUT Right" },
+ { "Capture", NULL, "ADC OUT Left" },
+};
+
static int t9015_set_bias_level(struct snd_soc_component *component,
enum snd_soc_bias_level level)
{
@@ -183,14 +405,16 @@ static int t9015_set_bias_level(struct snd_soc_component *component,
switch (level) {
case SND_SOC_BIAS_ON:
- snd_soc_component_update_bits(component, BLOCK_EN,
- BIAS_CURRENT_EN,
- BIAS_CURRENT_EN);
+ if (!snd_soc_dapm_get_idle_bias(dapm))
+ snd_soc_component_update_bits(component, BLOCK_EN,
+ BIAS_CURRENT_EN,
+ BIAS_CURRENT_EN);
break;
case SND_SOC_BIAS_PREPARE:
snd_soc_component_update_bits(component, BLOCK_EN,
BIAS_CURRENT_EN,
- 0);
+ snd_soc_dapm_get_idle_bias(dapm) ?
+ BIAS_CURRENT_EN : 0);
break;
case SND_SOC_BIAS_STANDBY:
ret = regulator_enable(priv->avdd);
@@ -212,6 +436,11 @@ static int t9015_set_bias_level(struct snd_soc_component *component,
break;
case SND_SOC_BIAS_OFF:
+ if (snd_soc_dapm_get_idle_bias(dapm))
+ snd_soc_component_update_bits(component, BLOCK_EN,
+ BIAS_CURRENT_EN,
+ 0);
+
snd_soc_component_update_bits(component, BLOCK_EN,
VMID_GEN_EN | VMID_GEN_FAST | REFP_BUF_EN,
0);
@@ -223,7 +452,123 @@ static int t9015_set_bias_level(struct snd_soc_component *component,
return 0;
}
+static int t9015_component_probe(struct snd_soc_component *component)
+{
+ /*
+ * Initialize output polarity:
+ * ATM the output polarity is fixed but in the future it might useful
+ * to add DT property to set this depending on the platform needs
+ */
+ snd_soc_component_write(component, LINEOUT_CFG, 0x1111);
+
+ return 0;
+}
+
+static int a1_t9015_dt_cfg(struct snd_soc_component *component,
+ const char *propname, unsigned int reg,
+ unsigned int mask, const char **names,
+ const unsigned int *values, unsigned int count)
+{
+ const char *propval;
+ unsigned int i;
+ int ret;
+
+ ret = device_property_read_string(component->dev, propname, &propval);
+ if (ret)
+ propval = names[0];
+
+ for (i = 0; i < count; i++) {
+ if (strcmp(propval, names[i]))
+ continue;
+
+ snd_soc_component_update_bits(component, reg, mask, values[i]);
+ return 0;
+ }
+
+ dev_err(component->dev, "wrong '%s' property value: '%s'",
+ propname, propval);
+ return -EINVAL;
+}
+
+static int a1_t9015_lineout_cfg(struct snd_soc_component *component)
+{
+ const char *right_names[] = { "none", "right", "left-inverted" };
+ unsigned int right_values[] = { 0, 0x2000, 0x4000, };
+ const char *left_names[] = { "none", "left", "right-inverted" };
+ unsigned int left_values[] = { 0, 0x20, 0x40, };
+ int ret;
+
+ ret = a1_t9015_dt_cfg(component, "lineout-right", LINEOUT_CFG, 0x7000,
+ right_names, right_values, ARRAY_SIZE(right_names));
+ if (ret)
+ return ret;
+
+ ret = a1_t9015_dt_cfg(component, "lineout-left", LINEOUT_CFG, 0x70,
+ left_names, left_values, ARRAY_SIZE(left_names));
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int a1_t9015_linein_cfg(struct snd_soc_component *component)
+{
+ const char *names[] = { "none", "differential", "positive",
+ "negative" };
+ unsigned int right_values[] = { 0, PGAR_DIFF, PGAR_POSITIVE,
+ PGAR_NEGATIVE };
+ unsigned int left_values[] = { 0, PGAL_DIFF, PGAL_POSITIVE,
+ PGAL_NEGATIVE };
+ int ret;
+
+ ret = a1_t9015_dt_cfg(component, "linein-right", LINEIN_CFG, PGAR_MASK,
+ names, right_values, ARRAY_SIZE(names));
+ if (ret)
+ return ret;
+
+ ret = a1_t9015_dt_cfg(component, "linein-left", LINEIN_CFG, PGAL_MASK,
+ names, left_values, ARRAY_SIZE(names));
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int a1_t9015_component_probe(struct snd_soc_component *component)
+{
+ struct t9015 *priv = snd_soc_component_get_drvdata(component);
+ int ret;
+
+ ret = a1_t9015_lineout_cfg(component);
+ if (ret)
+ return ret;
+
+ ret = a1_t9015_linein_cfg(component);
+ if (ret)
+ return ret;
+
+ /*
+ * This configuration was stealed from original Amlogic's driver to
+ * reproduce the behavior of the driver more accurately. However, it is
+ * not known for certain what it actually affects.
+ */
+ snd_soc_component_write(component, POWER_CFG, 0x00010000);
+
+ /*
+ * Keep output drivers always enabled
+ */
+ priv->out_drv_enable = device_property_present(component->dev,
+ "out_drv_enable");
+ if (priv->out_drv_enable)
+ snd_soc_component_update_bits(component, BLOCK_EN,
+ BIT(LOLP_EN) | BIT(LORP_EN),
+ BIT(LOLP_EN) | BIT(LORP_EN));
+
+ return 0;
+}
+
static const struct snd_soc_component_driver t9015_codec_driver = {
+ .probe = t9015_component_probe,
.set_bias_level = t9015_set_bias_level,
.controls = t9015_snd_controls,
.num_controls = ARRAY_SIZE(t9015_snd_controls),
@@ -235,22 +580,39 @@ static const struct snd_soc_component_driver t9015_codec_driver = {
.endianness = 1,
};
-static const struct regmap_config t9015_regmap_config = {
- .reg_bits = 32,
- .reg_stride = 4,
- .val_bits = 32,
- .max_register = POWER_CFG,
+static const struct snd_soc_component_driver a1_t9015_codec_driver = {
+ .probe = a1_t9015_component_probe,
+ .set_bias_level = t9015_set_bias_level,
+ .controls = a1_t9015_snd_controls,
+ .num_controls = ARRAY_SIZE(a1_t9015_snd_controls),
+ .dapm_widgets = a1_t9015_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(a1_t9015_dapm_widgets),
+ .dapm_routes = a1_t9015_dapm_routes,
+ .num_dapm_routes = ARRAY_SIZE(a1_t9015_dapm_routes),
+ .suspend_bias_off = 1,
+ .idle_bias_on = 1,
+ .endianness = 1,
};
static int t9015_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
+ const struct t9015_match_data *data;
struct t9015 *priv;
void __iomem *regs;
+ struct regmap_config config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ };
struct regmap *regmap;
struct clk *pclk;
int ret;
+ data = device_get_match_data(dev);
+ if (!data)
+ dev_err_probe(dev, -ENODEV, "failed to match device\n");
+
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
@@ -276,25 +638,38 @@ static int t9015_probe(struct platform_device *pdev)
return PTR_ERR(regs);
}
- regmap = devm_regmap_init_mmio(dev, regs, &t9015_regmap_config);
+ config.max_register = data->max_register;
+ regmap = devm_regmap_init_mmio(dev, regs, &config);
if (IS_ERR(regmap)) {
dev_err(dev, "regmap init failed\n");
return PTR_ERR(regmap);
}
- /*
- * Initialize output polarity:
- * ATM the output polarity is fixed but in the future it might useful
- * to add DT property to set this depending on the platform needs
- */
- regmap_write(regmap, LINEOUT_CFG, 0x1111);
-
- return devm_snd_soc_register_component(dev, &t9015_codec_driver,
- &t9015_dai, 1);
+ return devm_snd_soc_register_component(dev, data->component_drv,
+ data->dai_drv, 1);
}
+static const struct t9015_match_data t9015_match_data = {
+ .component_drv = &t9015_codec_driver,
+ .dai_drv = &t9015_dai,
+ .max_register = POWER_CFG,
+};
+
+static const struct t9015_match_data a1_t9015_match_data = {
+ .component_drv = &a1_t9015_codec_driver,
+ .dai_drv = &a1_t9015_dai,
+ .max_register = LINEIN_CFG,
+};
+
static const struct of_device_id t9015_ids[] __maybe_unused = {
- { .compatible = "amlogic,t9015", },
+ {
+ .compatible = "amlogic,t9015",
+ .data = &t9015_match_data,
+ },
+ {
+ .compatible = "amlogic,t9015-a1",
+ .data = &a1_t9015_match_data,
+ },
{ }
};
MODULE_DEVICE_TABLE(of, t9015_ids);