diff options
| author | Dmitry Rokosov <rockosov@rulkc.org> | 2026-08-14 01:40:02 +0300 |
|---|---|---|
| committer | Dmitry Rokosov <rockosov@rulkc.org> | 2026-08-14 01:40:02 +0300 |
| commit | b1a9f82c8ec64a6c7a648b6a4ab481f1d3b9f1e4 (patch) | |
| tree | 8f0964c1f46b9cf430b5fdaa2d12bcbcbe16ada5 | |
| parent | 028ef9c96e96197026887c0f092424679298aae8 (diff) | |
| parent | 0e0488eb915954f96e9ce4b40c0a9d8da6eebc19 (diff) | |
| download | linux-b1a9f82c8ec64a6c7a648b6a4ab481f1d3b9f1e4.tar.gz linux-b1a9f82c8ec64a6c7a648b6a4ab481f1d3b9f1e4.zip | |
Merge tag 'linux-amlogic/landau-v7.0' into landau-next
Amlogic SoC updates for LANDAU Linux v7.0
This merge brings support for new A1 CPU/audio clocks, Amlogic BBT parser,
AW200xx LED enhancements, and new board defconfigs.
Signed-off-by: Dmitry Rokosov <rockosov@rulkc.org>
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>, + <&lifier>, + <&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); |
