From 73db495db2ec8d7dafa4ee94e89139c89f139925 Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Mon, 21 Sep 2020 00:36:35 +0200 Subject: [PATCH 001/187] arm64: dts: qcom: sdm630: Fix intc reg indentation Signed-off-by: Konrad Dybcio --- arch/arm64/boot/dts/qcom/sdm630.dtsi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/sdm630.dtsi b/arch/arm64/boot/dts/qcom/sdm630.dtsi index 3e0165bb61c5..51c59eb39813 100644 --- a/arch/arm64/boot/dts/qcom/sdm630.dtsi +++ b/arch/arm64/boot/dts/qcom/sdm630.dtsi @@ -2280,8 +2280,8 @@ intc: interrupt-controller@17a00000 { compatible = "arm,gic-v3"; - reg = <0x17a00000 0x10000>, /* GICD */ - <0x17b00000 0x100000>; /* GICR * 8 */ + reg = <0x17a00000 0x10000>, /* GICD */ + <0x17b00000 0x100000>; /* GICR * 8 */ #interrupt-cells = <3>; #address-cells = <1>; #size-cells = <1>; From a1583a51153f34fd9ac62f1e9714afafe514483e Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Fri, 15 Jan 2021 06:27:25 +0100 Subject: [PATCH 002/187] arm64: dts: qcom: sdm630-xperia-nile: Add all RPM and fixed regulators Add all of the RPM PM660/PM660L regulators and the fixed ones, defining the common electrical part of this platform. Signed-off-by: AngeloGioacchino Del Regno Signed-off-by: Konrad Dybcio --- .../dts/qcom/sdm630-sony-xperia-nile.dtsi | 342 ++++++++++++++++++ 1 file changed, 342 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile.dtsi b/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile.dtsi index 11d0a8c1cf35..503e527f8321 100644 --- a/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile.dtsi +++ b/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile.dtsi @@ -615,3 +615,345 @@ dr_mode = "peripheral"; extcon = <&extcon_usb>; }; + +&rpm_requests { + pm660l-regulators { + compatible = "qcom,rpm-pm660l-regulators"; + + vdd_s1-supply = <&vph_pwr>; + vdd_s2-supply = <&vph_pwr>; + vdd_s3_s4-supply = <&vph_pwr>; + vdd_s5-supply = <&vph_pwr>; + vdd_s6-supply = <&vph_pwr>; + + vdd_l1_l9_l10-supply = <&vreg_s2b_1p05>; + vdd_l2-supply = <&vreg_bob>; + vdd_l3_l5_l7_l8-supply = <&vreg_bob>; + vdd_l4_l6-supply = <&vreg_bob>; + vdd_bob-supply = <&vph_pwr>; + + vreg_s1b_1p125: s1 { + regulator-min-microvolt = <1125000>; + regulator-max-microvolt = <1125000>; + regulator-enable-ramp-delay = <200>; + regulator-ramp-delay = <0>; + }; + + vreg_s2b_1p05: s2 { + regulator-min-microvolt = <1050000>; + regulator-max-microvolt = <1050000>; + regulator-enable-ramp-delay = <200>; + regulator-ramp-delay = <0>; + }; + + /* + * At least on Nile's configuration, S3B/S4B (VDD_CX) and + * S5B (VDD_MX) are managed only through RPM Power Domains. + * Trying to set a voltage on the main supply will create + * havoc and freeze the SoC. + * In any case, reference voltages for these regulators are: + * S3B/S4B: 0.870V + * S5B: 0.915V + */ + + /* LDOs */ + vreg_l1b_0p925: l1 { + regulator-min-microvolt = <920000>; + regulator-max-microvolt = <928000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + regulator-allow-set-load; + }; + + vreg_l2b_2p95: l2 { + /* + * This regulator supports 1.648 - 3.104V on this board + * but we set a max voltage of anything less than 2.7V + * to satisfy a condition in sdhci.c that will disable + * 3.3V SDHCI signaling, which happens to be not really + * supported on the Xperia Nile/Ganges platform. + */ + regulator-min-microvolt = <1648000>; + regulator-max-microvolt = <2696000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + regulator-allow-set-load; + }; + + vreg_l3b_3p0: l3 { + regulator-min-microvolt = <2800000>; + regulator-max-microvolt = <2800000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + regulator-min-microamp = <200>; + regulator-max-microamp = <600000>; + regulator-system-load = <100000>; + regulator-allow-set-load; + }; + + vreg_l4b_29p5: l4 { + regulator-min-microvolt = <2944000>; + regulator-max-microvolt = <2952000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + + regulator-min-microamp = <200>; + regulator-max-microamp = <600000>; + regulator-system-load = <570000>; + regulator-allow-set-load; + }; + + /* + * Downstream specifies a range of 1721-3600mV, + * but the only assigned consumers are SDHCI2 VMMC + * and Coresight QPDI that both request pinned 2.95V. + * Tighten the range to 1.8-3.328 (closest to 3.3) to + * make the mmc driver happy. + */ + vreg_l5b_29p5: l5 { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <3328000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + regulator-allow-set-load; + }; + + vreg_l6b_3p3: l6 { + regulator-min-microvolt = <1704000>; + regulator-max-microvolt = <3312000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + }; + + vreg_l7b_3p125: l7 { + regulator-min-microvolt = <2704000>; + regulator-max-microvolt = <3128000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + }; + + vreg_l8b_3p3: l8 { + regulator-min-microvolt = <2800000>; + regulator-max-microvolt = <3400000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + }; + + /* L9B (870mV) is currently unused */ + /* L10B (915mV) is currently unused */ + + vreg_bob: bob { + regulator-min-microvolt = <3304000>; + regulator-max-microvolt = <3624000>; + regulator-enable-ramp-delay = <500>; + regulator-ramp-delay = <0>; + }; + }; + + pm660-regulators { + compatible = "qcom,rpm-pm660-regulators"; + + vdd_s1-supply = <&vph_pwr>; + vdd_s2-supply = <&vph_pwr>; + vdd_s3-supply = <&vph_pwr>; + vdd_s4-supply = <&vph_pwr>; + vdd_s5-supply = <&vph_pwr>; + vdd_s6-supply = <&vph_pwr>; + + vdd_l1_l6_l7-supply = <&vreg_s5a_1p35>; + vdd_l2_l3-supply = <&vreg_s2b_1p05>; + vdd_l5-supply = <&vreg_s2b_1p05>; + vdd_l8_l9_l10_l11_l12_l13_l14-supply = <&vreg_s4a_2p04>; + vdd_l15_l16_l17_l18_l19-supply = <&vreg_bob>; + + /* + * S1A (FTAPC0), S2A (FTAPC1), S3A (HFAPC1) are managed + * by the Core Power Reduction hardened (CPRh) and the + * Operating State Manager (OSM) HW automatically. + */ + + vreg_s4a_2p04: s4 { + regulator-min-microvolt = <2040000>; + regulator-max-microvolt = <2040000>; + regulator-enable-ramp-delay = <200>; + regulator-ramp-delay = <0>; + regulator-always-on; + }; + + vreg_s5a_1p35: s5 { + regulator-min-microvolt = <1224000>; + regulator-max-microvolt = <1350000>; + regulator-enable-ramp-delay = <200>; + regulator-ramp-delay = <0>; + }; + + vreg_s6a_0p87: s6 { + regulator-min-microvolt = <504000>; + regulator-max-microvolt = <992000>; + regulator-enable-ramp-delay = <150>; + regulator-ramp-delay = <0>; + }; + + /* LDOs */ + vreg_l1a_1p225: l1 { + regulator-min-microvolt = <1226000>; + regulator-max-microvolt = <1250000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + regulator-allow-set-load; + }; + + vreg_l2a_1p0: l2 { + regulator-min-microvolt = <944000>; + regulator-max-microvolt = <1008000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + }; + + vreg_l3a_1p0: l3 { + regulator-min-microvolt = <944000>; + regulator-max-microvolt = <1008000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + }; + + vreg_l5a_0p848: l5 { + regulator-min-microvolt = <800000>; + regulator-max-microvolt = <952000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + }; + + vreg_l6a_1p3: l6 { + regulator-min-microvolt = <1304000>; + regulator-max-microvolt = <1368000>; + regulator-allow-set-load; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + }; + + vreg_l7a_1p2: l7 { + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + }; + + vreg_l8a_1p8: l8 { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + regulator-system-load = <325000>; + regulator-allow-set-load; + }; + + + vreg_l9a_1p8: l9 { + regulator-min-microvolt = <1804000>; + regulator-max-microvolt = <1896000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + regulator-allow-set-load; + }; + + vreg_l10a_1p8: l10 { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1944000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + regulator-allow-set-load; + }; + + vreg_l11a_1p8: l11 { + regulator-min-microvolt = <1784000>; + regulator-max-microvolt = <1944000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + }; + + vreg_l12a_1p8: l12 { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1944000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + }; + + /* This gives power to the LPDDR4: never turn it off! */ + vreg_l13a_1p8: l13 { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1944000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + regulator-boot-on; + regulator-always-on; + }; + + vreg_l14a_1p8: l14 { + regulator-min-microvolt = <1710000>; + regulator-max-microvolt = <1904000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + }; + + vreg_l15a_1p8: l15 { + regulator-min-microvolt = <1648000>; + regulator-max-microvolt = <2952000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + }; + + /* L16A (2.70V) is unused */ + + vreg_l17a_1p8: l17 { + regulator-min-microvolt = <1648000>; + regulator-max-microvolt = <2952000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + }; + + vreg_l18a_1v8: l18 { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <10>; + regulator-min-microamp = <200>; + regulator-max-microamp = <50000>; + regulator-system-load = <10000>; + regulator-allow-set-load; + }; + + vreg_l19a_3p3: l19 { + regulator-min-microvolt = <3312000>; + regulator-max-microvolt = <3400000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + regulator-allow-set-load; + }; + }; +}; + +&tlmm { + gpio-reserved-ranges = <8 4>; + + imx300_vana_default: imx300-vana-default { + pins = "gpio50"; + function = "gpio"; + bias-disable; + drive-strength = <2>; + }; + + imx219_vana_default: imx219-vana-default { + pins = "gpio51"; + function = "gpio"; + bias-disable; + drive-strength = <2>; + }; + + cam_vdig_default: cam-vdig-default { + pins = "gpio52"; + function = "gpio"; + bias-disable; + drive-strength = <2>; + }; +}; From d582b78bb40fb9fcc60bba96647dacfb40aeb2c3 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Sun, 31 Jan 2021 19:37:45 +0100 Subject: [PATCH 003/187] arm64: dts: qcom: sdm630-nile: Configure WCN3990 Bluetooth Add a node for WCN3990 HCIUART Bluetooth. Signed-off-by: AngeloGioacchino Del Regno Signed-off-by: Konrad Dybcio --- .../boot/dts/qcom/sdm630-sony-xperia-nile.dtsi | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile.dtsi b/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile.dtsi index 503e527f8321..0c631a3cdaf2 100644 --- a/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile.dtsi +++ b/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile.dtsi @@ -212,7 +212,18 @@ &blsp2_uart1 { status = "okay"; - /* HCI Bluetooth */ + bluetooth: wcn3990-bt { + compatible = "qcom,wcn3998-bt"; + + vddio-supply = <&vreg_l13a_1p8>; + vddxo-suppky = <&vreg_l9a_1p8>; + vddrf-supply = <&vreg_l6a_1p3>; + vddch0-supply = <&vreg_l19a_3p3>; + + max-speed = <3200000>; + + clocks = <&rpmcc RPM_SMD_RF_CLK1_PIN>; + }; }; &pon { From c5f2e76cb4c59b78676912feb20c2a0942d08104 Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Fri, 23 Oct 2020 13:27:40 +0200 Subject: [PATCH 004/187] drivers: interconnect: sdm660: MASTER_CRYPTO_C0 isn't AP-Owned Signed-off-by: Konrad Dybcio (JAMI: fixed up for v5.16-rc1) --- drivers/interconnect/qcom/sdm660.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/interconnect/qcom/sdm660.c b/drivers/interconnect/qcom/sdm660.c index 471bb88f8828..84885ed2b506 100644 --- a/drivers/interconnect/qcom/sdm660.c +++ b/drivers/interconnect/qcom/sdm660.c @@ -301,7 +301,7 @@ static struct qcom_icc_node mas_crypto = { .buswidth = 8, .mas_rpm_id = 23, .slv_rpm_id = -1, - .qos.ap_owned = true, + .qos.ap_owned = false, .qos.qos_mode = NOC_QOS_MODE_FIXED, .qos.areq_prio = 1, .qos.prio_level = 1, From 2f5fb1d716c8c68f8a5a86a5638ade0072ebf314 Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Mon, 9 Nov 2020 22:05:58 +0100 Subject: [PATCH 005/187] drivers: interconnect: qcom: sdm660: Adjust to v5.10 changes Signed-off-by: Konrad Dybcio (JAMI: fixed up for v5.16-rc1) --- drivers/interconnect/qcom/sdm660.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/drivers/interconnect/qcom/sdm660.c b/drivers/interconnect/qcom/sdm660.c index 84885ed2b506..79e8b5afef69 100644 --- a/drivers/interconnect/qcom/sdm660.c +++ b/drivers/interconnect/qcom/sdm660.c @@ -245,10 +245,10 @@ static struct qcom_icc_node mas_ufs = { .mas_rpm_id = 68, .slv_rpm_id = -1, .qos.ap_owned = true, - .qos.qos_mode = NOC_QOS_MODE_FIXED, - .qos.areq_prio = 1, - .qos.prio_level = 1, - .qos.qos_port = 4, + .qos.qos_mode = -1, + .qos.areq_prio = 0, + .qos.prio_level = 0, + .qos.qos_port = -1, .num_links = ARRAY_SIZE(mas_ufs_links), .links = mas_ufs_links, }; @@ -264,10 +264,10 @@ static struct qcom_icc_node mas_usb_hs = { .mas_rpm_id = 42, .slv_rpm_id = -1, .qos.ap_owned = true, - .qos.qos_mode = NOC_QOS_MODE_FIXED, - .qos.areq_prio = 1, - .qos.prio_level = 1, - .qos.qos_port = 1, + .qos.qos_mode = -1, + .qos.areq_prio = 0, + .qos.prio_level = 0, + .qos.qos_port = -1, .num_links = ARRAY_SIZE(mas_usb_hs_links), .links = mas_usb_hs_links, }; @@ -1711,6 +1711,7 @@ static struct platform_driver sdm660_noc_driver = { .driver = { .name = "qnoc-sdm660", .of_match_table = sdm660_noc_of_match, + .sync_state = icc_sync_state, }, }; module_platform_driver(sdm660_noc_driver); From 2a63c83b720a46e9045fc0f19e6213d3855e48c4 Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Wed, 30 Dec 2020 22:07:16 +0100 Subject: [PATCH 006/187] drm/panel: Add support for Sony TD4322 Truly FHD panel This commit adds support for Sony TD4322 Truly Full HD command mode DSI panel, as found in Sony Xperia XA2 smartphones. Due to the nature of phone manufacturing, it is impossible to retrieve the actual panel name, hence the replacement one, detailing the device it's used on. Signed-off-by: Konrad Dybcio --- drivers/gpu/drm/panel/Kconfig | 10 + drivers/gpu/drm/panel/Makefile | 1 + .../gpu/drm/panel/panel-td4322-truly-fhd.c | 277 ++++++++++++++++++ 3 files changed, 288 insertions(+) create mode 100644 drivers/gpu/drm/panel/panel-td4322-truly-fhd.c diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig index cfc8d644cedf..034f05759117 100644 --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig @@ -580,6 +580,16 @@ config DRM_PANEL_SONY_ACX565AKM Say Y here if you want to enable support for the Sony ACX565AKM 800x600 3.5" panel (found on the Nokia N900). +config DRM_PANEL_SONY_PIONEER_TD4322_TRULY + tristate "Sony Pioneer TD4322 Truly panel" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y here if you want to enable the Truly TD4322 1080x + 1920 command mode display panel (as found on Sony Xperia XA2 + devices). + config DRM_PANEL_TDO_TL070WSH30 tristate "TDO TL070WSH30 DSI panel" depends on OF diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile index bca4cc1f2715..e93dea3a6a6a 100644 --- a/drivers/gpu/drm/panel/Makefile +++ b/drivers/gpu/drm/panel/Makefile @@ -59,6 +59,7 @@ obj-$(CONFIG_DRM_PANEL_SITRONIX_ST7703) += panel-sitronix-st7703.o obj-$(CONFIG_DRM_PANEL_SITRONIX_ST7789V) += panel-sitronix-st7789v.o obj-$(CONFIG_DRM_PANEL_SONY_ACX424AKP) += panel-sony-acx424akp.o obj-$(CONFIG_DRM_PANEL_SONY_ACX565AKM) += panel-sony-acx565akm.o +obj-$(CONFIG_DRM_PANEL_SONY_PIONEER_TD4322_TRULY) += panel-td4322-truly-fhd.o obj-$(CONFIG_DRM_PANEL_TDO_TL070WSH30) += panel-tdo-tl070wsh30.o obj-$(CONFIG_DRM_PANEL_TPO_TD028TTEC1) += panel-tpo-td028ttec1.o obj-$(CONFIG_DRM_PANEL_TPO_TD043MTEA1) += panel-tpo-td043mtea1.o diff --git a/drivers/gpu/drm/panel/panel-td4322-truly-fhd.c b/drivers/gpu/drm/panel/panel-td4322-truly-fhd.c new file mode 100644 index 000000000000..7dfc02e68812 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-td4322-truly-fhd.c @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include + +#include +#include +#include + +struct td4322_truly_fhd { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + struct regulator_bulk_data supplies[4]; + struct gpio_desc *reset_gpio; + bool prepared; +}; + +static inline +struct td4322_truly_fhd *to_td4322_truly_fhd(struct drm_panel *panel) +{ + return container_of(panel, struct td4322_truly_fhd, panel); +} + +#define dsi_generic_write_seq(dsi, seq...) do { \ + static const u8 d[] = { seq }; \ + int ret; \ + ret = mipi_dsi_generic_write(dsi, d, ARRAY_SIZE(d)); \ + if (ret < 0) \ + return ret; \ + } while (0) + +static void td4322_truly_fhd_reset(struct td4322_truly_fhd *ctx) +{ + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + msleep(30); + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + msleep(150); +} + +static int td4322_truly_fhd_on(struct td4322_truly_fhd *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct device *dev = &dsi->dev; + int ret; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + dsi_generic_write_seq(dsi, 0xb0, 0x00); + dsi_generic_write_seq(dsi, 0xd5, + 0x03, 0x00, 0x00, 0x02, 0x23, 0x02, 0x23); + + ret = mipi_dsi_dcs_set_display_on(dsi); + if (ret < 0) { + dev_err(dev, "Failed to set display on: %d\n", ret); + return ret; + } + msleep(30); + + ret = mipi_dsi_dcs_exit_sleep_mode(dsi); + if (ret < 0) { + dev_err(dev, "Failed to exit sleep mode: %d\n", ret); + return ret; + } + msleep(120); + + return 0; +} + +static int td4322_truly_fhd_off(struct td4322_truly_fhd *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct device *dev = &dsi->dev; + int ret; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + ret = mipi_dsi_dcs_set_display_off(dsi); + if (ret < 0) { + dev_err(dev, "Failed to set display off: %d\n", ret); + return ret; + } + msleep(20); + + ret = mipi_dsi_dcs_enter_sleep_mode(dsi); + if (ret < 0) { + dev_err(dev, "Failed to enter sleep mode: %d\n", ret); + return ret; + } + msleep(120); + + return 0; +} + +static int td4322_truly_fhd_prepare(struct drm_panel *panel) +{ + struct td4322_truly_fhd *ctx = to_td4322_truly_fhd(panel); + struct device *dev = &ctx->dsi->dev; + int ret; + + if (ctx->prepared) + return 0; + + ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + if (ret < 0) { + dev_err(dev, "Failed to enable regulators: %d\n", ret); + return ret; + } + + td4322_truly_fhd_reset(ctx); + + ret = td4322_truly_fhd_on(ctx); + if (ret < 0) { + dev_err(dev, "Failed to initialize panel: %d\n", ret); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + return ret; + } + + ctx->prepared = true; + return 0; +} + +static int td4322_truly_fhd_unprepare(struct drm_panel *panel) +{ + struct td4322_truly_fhd *ctx = to_td4322_truly_fhd(panel); + struct device *dev = &ctx->dsi->dev; + int ret; + + if (!ctx->prepared) + return 0; + + ret = td4322_truly_fhd_off(ctx); + if (ret < 0) + dev_err(dev, "Failed to un-initialize panel: %d\n", ret); + + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + + ctx->prepared = false; + return 0; +} + +static const struct drm_display_mode td4322_truly_fhd_mode = { + .clock = (1080 + 104 + 20 + 56) * (1920 + 10 + 2 + 8) * 60 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 104, + .hsync_end = 1080 + 104 + 20, + .htotal = 1080 + 104 + 20 + 56, + .vdisplay = 1920, + .vsync_start = 1920 + 10, + .vsync_end = 1920 + 10 + 2, + .vtotal = 1920 + 10 + 2 + 8, + .width_mm = 64, + .height_mm = 115, +}; + +static int td4322_truly_fhd_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &td4322_truly_fhd_mode); + if (!mode) + return -ENOMEM; + + drm_mode_set_name(mode); + + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + connector->display_info.width_mm = mode->width_mm; + connector->display_info.height_mm = mode->height_mm; + drm_mode_probed_add(connector, mode); + + return 1; +} + +static const struct drm_panel_funcs td4322_truly_fhd_panel_funcs = { + .prepare = td4322_truly_fhd_prepare, + .unprepare = td4322_truly_fhd_unprepare, + .get_modes = td4322_truly_fhd_get_modes, +}; + +static int td4322_truly_fhd_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct td4322_truly_fhd *ctx; + int ret; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->supplies[0].supply = "vdd"; + ctx->supplies[1].supply = "vddio"; + ctx->supplies[2].supply = "vsn"; + ctx->supplies[3].supply = "vsp"; + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies), + ctx->supplies); + if (ret < 0) { + dev_err(dev, "Failed to get regulators: %d\n", ret); + return ret; + } + + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ctx->reset_gpio)) { + ret = PTR_ERR(ctx->reset_gpio); + dev_err(dev, "Failed to get reset-gpios: %d\n", ret); + return ret; + } + + ctx->dsi = dsi; + mipi_dsi_set_drvdata(dsi, ctx); + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_CLOCK_NON_CONTINUOUS; + + drm_panel_init(&ctx->panel, dev, &td4322_truly_fhd_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) { + dev_err(dev, "Failed to get backlight: %d\n", ret); + return ret; + } + + drm_panel_add(&ctx->panel); + if (ret < 0) { + dev_err(dev, "Failed to add panel: %d\n", ret); + return ret; + } + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(dev, "Failed to attach to DSI host: %d\n", ret); + return ret; + } + + return 0; +} + +static int td4322_truly_fhd_remove(struct mipi_dsi_device *dsi) +{ + struct td4322_truly_fhd *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); + + drm_panel_remove(&ctx->panel); + + return 0; +} + +static const struct of_device_id td4322_truly_fhd_of_match[] = { + { .compatible = "sony,pioneer-td4322-truly" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, td4322_truly_fhd_of_match); + +static struct mipi_dsi_driver td4322_truly_fhd_driver = { + .probe = td4322_truly_fhd_probe, + .remove = td4322_truly_fhd_remove, + .driver = { + .name = "panel-td4322-truly-fhd", + .of_match_table = td4322_truly_fhd_of_match, + }, +}; +module_mipi_dsi_driver(td4322_truly_fhd_driver); + +MODULE_AUTHOR("Konrad Dybcio "); +MODULE_DESCRIPTION("DRM driver for TD4322 Truly FHD CMD mode panel"); +MODULE_LICENSE("GPL v2"); From b10673b76c2663774c75cec1b24bfd7faf99cf7f Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Wed, 10 Feb 2021 22:58:16 +0100 Subject: [PATCH 007/187] dt-bindings: display: panel: Document Sony Pioneer TD4322 Truly display panel Add bindings for the newly added panel. Signed-off-by: Konrad Dybcio --- .../panel/sony,pioneer-td4322-truly.yaml | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/panel/sony,pioneer-td4322-truly.yaml diff --git a/Documentation/devicetree/bindings/display/panel/sony,pioneer-td4322-truly.yaml b/Documentation/devicetree/bindings/display/panel/sony,pioneer-td4322-truly.yaml new file mode 100644 index 000000000000..4d45f87e513b --- /dev/null +++ b/Documentation/devicetree/bindings/display/panel/sony,pioneer-td4322-truly.yaml @@ -0,0 +1,63 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/display/panel/sony,pioneer-td4322-truly.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: SONY Pioneer TD4322 Truly 5.2" 1080×1920 LCD Panel + +maintainers: + - Konrad Dybcio + +description: |+ + This panel seems to only be found in the Sony Xperia XA2 + smartphone and we have no straightforward way of + actually getting the correct model number, + as no schematics are released publicly. + +allOf: + - $ref: panel-common.yaml# + +properties: + compatible: + const: sony,pioneer-td4322-truly + reg: true + reset-gpios: true + vdd-supply: + description: core voltage supply + vddio-supply: + description: vddio supply + vsp-supply: + description: positive voltage supply + vsn-supply: + description: negative voltage supply + +required: + - compatible + - reg + - vdd-supply + - vddio-supply + - vsp-supply + - vsn-supply + - reset-gpios + +additionalProperties: false + +examples: + - | + #include + + dsi { + #address-cells = <1>; + #size-cells = <0>; + panel@0 { + reg = <0>; + compatible = "sony,pioneer-td4322-truly"; + + vdd-supply = <&pm8916_l8>; + vddio-supply = <&pm8916_l6>; + vsp-supply = <&lab>; + vsn-supply = <&ibb>; + reset-gpios = <&tlmm 53 GPIO_ACTIVE_HIGH>; + }; + }; From dd1f6900f8e658ec7da436a71d2996f15fc285d7 Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Thu, 16 Jul 2020 21:33:21 +0200 Subject: [PATCH 008/187] drm/panel: Add support for Sony TD4322 Innolux FHD panel This commit adds support for Sony TD4322 Innolux Full HD command mode DSI panel, as found in Sony Xperia XA2 Ultra smartphones. Due to the nature of phone manufacturing, it is impossible to retrieve the actual panel name, hence the replacement one, detailing the device it's used on. Signed-off-by: Konrad Dybcio --- drivers/gpu/drm/panel/Kconfig | 10 + drivers/gpu/drm/panel/Makefile | 1 + .../gpu/drm/panel/panel-td4322-innolux-fhd.c | 274 ++++++++++++++++++ 3 files changed, 285 insertions(+) create mode 100644 drivers/gpu/drm/panel/panel-td4322-innolux-fhd.c diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig index 034f05759117..cf97126165a4 100644 --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig @@ -580,6 +580,16 @@ config DRM_PANEL_SONY_ACX565AKM Say Y here if you want to enable support for the Sony ACX565AKM 800x600 3.5" panel (found on the Nokia N900). +config DRM_PANEL_SONY_DISCOVERY_TD4322_INNOLUX + tristate "Sony Discovery TD4322 Innolux panel" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y here if you want to enable the Innolux TD4322 + 1080-1920 command mode panel (as found on Sony Xperia + XA2 Ultra devices). + config DRM_PANEL_SONY_PIONEER_TD4322_TRULY tristate "Sony Pioneer TD4322 Truly panel" depends on OF diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile index e93dea3a6a6a..cbfe1668d806 100644 --- a/drivers/gpu/drm/panel/Makefile +++ b/drivers/gpu/drm/panel/Makefile @@ -59,6 +59,7 @@ obj-$(CONFIG_DRM_PANEL_SITRONIX_ST7703) += panel-sitronix-st7703.o obj-$(CONFIG_DRM_PANEL_SITRONIX_ST7789V) += panel-sitronix-st7789v.o obj-$(CONFIG_DRM_PANEL_SONY_ACX424AKP) += panel-sony-acx424akp.o obj-$(CONFIG_DRM_PANEL_SONY_ACX565AKM) += panel-sony-acx565akm.o +obj-$(CONFIG_DRM_PANEL_SONY_DISCOVERY_TD4322_INNOLUX) += panel-td4322-innolux-fhd.o obj-$(CONFIG_DRM_PANEL_SONY_PIONEER_TD4322_TRULY) += panel-td4322-truly-fhd.o obj-$(CONFIG_DRM_PANEL_TDO_TL070WSH30) += panel-tdo-tl070wsh30.o obj-$(CONFIG_DRM_PANEL_TPO_TD028TTEC1) += panel-tpo-td028ttec1.o diff --git a/drivers/gpu/drm/panel/panel-td4322-innolux-fhd.c b/drivers/gpu/drm/panel/panel-td4322-innolux-fhd.c new file mode 100644 index 000000000000..69a962cc106e --- /dev/null +++ b/drivers/gpu/drm/panel/panel-td4322-innolux-fhd.c @@ -0,0 +1,274 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include + +#include +#include +#include + +struct td4322_innolux_fhd { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + struct regulator_bulk_data supplies[4]; + struct gpio_desc *reset_gpio; + bool prepared; +}; + +static inline +struct td4322_innolux_fhd *to_td4322_innolux_fhd(struct drm_panel *panel) +{ + return container_of(panel, struct td4322_innolux_fhd, panel); +} + +#define dsi_dcs_write_seq(dsi, seq...) do { \ + static const u8 d[] = { seq }; \ + int ret; \ + ret = mipi_dsi_dcs_write_buffer(dsi, d, ARRAY_SIZE(d)); \ + if (ret < 0) \ + return ret; \ + } while (0) + +static void td4322_innolux_fhd_reset(struct td4322_innolux_fhd *ctx) +{ + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + msleep(30); + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + msleep(150); +} + +static int td4322_innolux_fhd_on(struct td4322_innolux_fhd *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct device *dev = &dsi->dev; + int ret; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + dsi_dcs_write_seq(dsi, 0x35); + + ret = mipi_dsi_dcs_set_display_on(dsi); + if (ret < 0) { + dev_err(dev, "Failed to set display on: %d\n", ret); + return ret; + } + + ret = mipi_dsi_dcs_exit_sleep_mode(dsi); + if (ret < 0) { + dev_err(dev, "Failed to exit sleep mode: %d\n", ret); + return ret; + } + msleep(70); + + return 0; +} + +static int td4322_innolux_fhd_off(struct td4322_innolux_fhd *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct device *dev = &dsi->dev; + int ret; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + ret = mipi_dsi_dcs_set_display_off(dsi); + if (ret < 0) { + dev_err(dev, "Failed to set display off: %d\n", ret); + return ret; + } + msleep(20); + + ret = mipi_dsi_dcs_enter_sleep_mode(dsi); + if (ret < 0) { + dev_err(dev, "Failed to enter sleep mode: %d\n", ret); + return ret; + } + msleep(120); + + return 0; +} + +static int td4322_innolux_fhd_prepare(struct drm_panel *panel) +{ + struct td4322_innolux_fhd *ctx = to_td4322_innolux_fhd(panel); + struct device *dev = &ctx->dsi->dev; + int ret; + + if (ctx->prepared) + return 0; + + ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + if (ret < 0) { + dev_err(dev, "Failed to enable regulators: %d\n", ret); + return ret; + } + + td4322_innolux_fhd_reset(ctx); + + ret = td4322_innolux_fhd_on(ctx); + if (ret < 0) { + dev_err(dev, "Failed to initialize panel: %d\n", ret); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + return ret; + } + + ctx->prepared = true; + return 0; +} + +static int td4322_innolux_fhd_unprepare(struct drm_panel *panel) +{ + struct td4322_innolux_fhd *ctx = to_td4322_innolux_fhd(panel); + struct device *dev = &ctx->dsi->dev; + int ret; + + if (!ctx->prepared) + return 0; + + ret = td4322_innolux_fhd_off(ctx); + if (ret < 0) + dev_err(dev, "Failed to un-initialize panel: %d\n", ret); + + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + + ctx->prepared = false; + return 0; +} + +static const struct drm_display_mode td4322_innolux_fhd_mode = { + .clock = (1080 + 96 + 20 + 48) * (1920 + 5 + 2 + 3) * 60 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 96, + .hsync_end = 1080 + 96 + 20, + .htotal = 1080 + 96 + 20 + 48, + .vdisplay = 1920, + .vsync_start = 1920 + 5, + .vsync_end = 1920 + 5 + 2, + .vtotal = 1920 + 5 + 2 + 3, + .width_mm = 74, + .height_mm = 132, +}; + +static int td4322_innolux_fhd_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &td4322_innolux_fhd_mode); + if (!mode) + return -ENOMEM; + + drm_mode_set_name(mode); + + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + connector->display_info.width_mm = mode->width_mm; + connector->display_info.height_mm = mode->height_mm; + drm_mode_probed_add(connector, mode); + + return 1; +} + +static const struct drm_panel_funcs td4322_innolux_fhd_panel_funcs = { + .prepare = td4322_innolux_fhd_prepare, + .unprepare = td4322_innolux_fhd_unprepare, + .get_modes = td4322_innolux_fhd_get_modes, +}; + +static int td4322_innolux_fhd_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct td4322_innolux_fhd *ctx; + int ret; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->supplies[0].supply = "vdd"; + ctx->supplies[1].supply = "vddio"; + ctx->supplies[2].supply = "vsn"; + ctx->supplies[3].supply = "vsp"; + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies), + ctx->supplies); + if (ret < 0) { + dev_err(dev, "Failed to get regulators: %d\n", ret); + return ret; + } + + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ctx->reset_gpio)) { + ret = PTR_ERR(ctx->reset_gpio); + dev_err(dev, "Failed to get reset-gpios: %d\n", ret); + return ret; + } + + ctx->dsi = dsi; + mipi_dsi_set_drvdata(dsi, ctx); + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_CLOCK_NON_CONTINUOUS; + + drm_panel_init(&ctx->panel, dev, &td4322_innolux_fhd_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) { + dev_err(dev, "Failed to get backlight: %d\n", ret); + return ret; + } + + drm_panel_add(&ctx->panel); + if (ret < 0) { + dev_err(dev, "Failed to add panel: %d\n", ret); + return ret; + } + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(dev, "Failed to attach to DSI host: %d\n", ret); + return ret; + } + + return 0; +} + +static int td4322_innolux_fhd_remove(struct mipi_dsi_device *dsi) +{ + struct td4322_innolux_fhd *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); + + drm_panel_remove(&ctx->panel); + + return 0; +} + +static const struct of_device_id td4322_innolux_fhd_of_match[] = { + { .compatible = "sony,discovery-td4322-innolux" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, td4322_innolux_fhd_of_match); + +static struct mipi_dsi_driver td4322_innolux_fhd_driver = { + .probe = td4322_innolux_fhd_probe, + .remove = td4322_innolux_fhd_remove, + .driver = { + .name = "panel-td4322-innolux-fhd", + .of_match_table = td4322_innolux_fhd_of_match, + }, +}; +module_mipi_dsi_driver(td4322_innolux_fhd_driver); + +MODULE_AUTHOR("Konrad Dybcio "); +MODULE_DESCRIPTION("DRM driver for Sony TD4322 Innolux FHD panel"); +MODULE_LICENSE("GPL v2"); From a592fe2d9515c01438c6fe73a9c82d9fb2977ae3 Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Wed, 10 Feb 2021 22:58:03 +0100 Subject: [PATCH 009/187] dt-bindings: display: panel: Document Sony Discovery TD4322 Innolux display panel Add bindings for the newly added panel. Signed-off-by: Konrad Dybcio --- .../panel/sony,discovery-td4322-innolux.yaml | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/panel/sony,discovery-td4322-innolux.yaml diff --git a/Documentation/devicetree/bindings/display/panel/sony,discovery-td4322-innolux.yaml b/Documentation/devicetree/bindings/display/panel/sony,discovery-td4322-innolux.yaml new file mode 100644 index 000000000000..b7e7c717c844 --- /dev/null +++ b/Documentation/devicetree/bindings/display/panel/sony,discovery-td4322-innolux.yaml @@ -0,0 +1,63 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/display/panel/sony,discovery-td4322-innolux.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: SONY Discovery TD4322 Innolux 6.0" 1080×1920 LCD Panel + +maintainers: + - Konrad Dybcio + +description: |+ + This panel seems to only be found in the Sony Xperia XA2 Ultra + smartphone and we have no straightforward way of + actually getting the correct model number, + as no schematics are released publicly. + +allOf: + - $ref: panel-common.yaml# + +properties: + compatible: + const: sony,discovery-td4322-innolux + reg: true + reset-gpios: true + vdd-supply: + description: core voltage supply + vddio-supply: + description: vddio supply + vsp-supply: + description: positive voltage supply + vsn-supply: + description: negative voltage supply + +required: + - compatible + - reg + - vdd-supply + - vddio-supply + - vsp-supply + - vsn-supply + - reset-gpios + +additionalProperties: false + +examples: + - | + #include + + dsi { + #address-cells = <1>; + #size-cells = <0>; + panel@0 { + reg = <0>; + compatible = "sony,discovery-td4322-innolux"; + + vdd-supply = <&pm8916_l8>; + vddio-supply = <&pm8916_l6>; + vsp-supply = <&lab>; + vsn-supply = <&ibb>; + reset-gpios = <&tlmm 53 GPIO_ACTIVE_HIGH>; + }; + }; From 38b61c0a4c7074c3796088f69d29a2ba191680e7 Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Wed, 30 Dec 2020 22:08:09 +0100 Subject: [PATCH 010/187] drm/panel: Add support for Sony TD4328 Tianma FHD+ panel This commit adds support for Sony TD4328 Truly Full HD Plus command mode DSI panel, as found in Sony Xperia XA2 Plus smartphones. Due to the nature of phone manufacturing, it is impossible to retrieve the actual panel name, hence the replacement one, detailing the device it's used on. Signed-off-by: Konrad Dybcio --- drivers/gpu/drm/panel/Kconfig | 10 + drivers/gpu/drm/panel/Makefile | 1 + .../drm/panel/panel-td4328-tianma-fhdplus.c | 277 ++++++++++++++++++ 3 files changed, 288 insertions(+) create mode 100644 drivers/gpu/drm/panel/panel-td4328-tianma-fhdplus.c diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig index cf97126165a4..be072b396443 100644 --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig @@ -600,6 +600,16 @@ config DRM_PANEL_SONY_PIONEER_TD4322_TRULY 1920 command mode display panel (as found on Sony Xperia XA2 devices). +config DRM_PANEL_SONY_VOYAGER_TD4328_TIANMA + tristate "Sony Voyager TD4328 Tianma panel" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y here if you want to enable the Tianma TD4328 1080x + 2160 command mode panel (as found on Sony Xperia XA2 Plus + devices). + config DRM_PANEL_TDO_TL070WSH30 tristate "TDO TL070WSH30 DSI panel" depends on OF diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile index cbfe1668d806..0d6e2be47647 100644 --- a/drivers/gpu/drm/panel/Makefile +++ b/drivers/gpu/drm/panel/Makefile @@ -61,6 +61,7 @@ obj-$(CONFIG_DRM_PANEL_SONY_ACX424AKP) += panel-sony-acx424akp.o obj-$(CONFIG_DRM_PANEL_SONY_ACX565AKM) += panel-sony-acx565akm.o obj-$(CONFIG_DRM_PANEL_SONY_DISCOVERY_TD4322_INNOLUX) += panel-td4322-innolux-fhd.o obj-$(CONFIG_DRM_PANEL_SONY_PIONEER_TD4322_TRULY) += panel-td4322-truly-fhd.o +obj-$(CONFIG_DRM_PANEL_SONY_VOYAGER_TD4328_TIANMA) += panel-td4328-tianma-fhdplus.o obj-$(CONFIG_DRM_PANEL_TDO_TL070WSH30) += panel-tdo-tl070wsh30.o obj-$(CONFIG_DRM_PANEL_TPO_TD028TTEC1) += panel-tpo-td028ttec1.o obj-$(CONFIG_DRM_PANEL_TPO_TD043MTEA1) += panel-tpo-td043mtea1.o diff --git a/drivers/gpu/drm/panel/panel-td4328-tianma-fhdplus.c b/drivers/gpu/drm/panel/panel-td4328-tianma-fhdplus.c new file mode 100644 index 000000000000..d1de8b549bd8 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-td4328-tianma-fhdplus.c @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include + +#include +#include +#include + +struct td4328_tianma_fhdplus { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + struct regulator_bulk_data supplies[4]; + struct gpio_desc *reset_gpio; + bool prepared; +}; + +static inline +struct td4328_tianma_fhdplus *to_td4328_tianma_fhdplus(struct drm_panel *panel) +{ + return container_of(panel, struct td4328_tianma_fhdplus, panel); +} + +#define dsi_generic_write_seq(dsi, seq...) do { \ + static const u8 d[] = { seq }; \ + int ret; \ + ret = mipi_dsi_generic_write(dsi, d, ARRAY_SIZE(d)); \ + if (ret < 0) \ + return ret; \ + } while (0) + +static void td4328_tianma_fhdplus_reset(struct td4328_tianma_fhdplus *ctx) +{ + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + msleep(30); + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + msleep(150); +} + +static int td4328_tianma_fhdplus_on(struct td4328_tianma_fhdplus *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct device *dev = &dsi->dev; + int ret; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + dsi_generic_write_seq(dsi, 0xb0, 0x04); + dsi_generic_write_seq(dsi, 0xb3, 0x00, 0x00, 0x06); + dsi_generic_write_seq(dsi, 0xd6, 0x01); + + ret = mipi_dsi_dcs_exit_sleep_mode(dsi); + if (ret < 0) { + dev_err(dev, "Failed to exit sleep mode: %d\n", ret); + return ret; + } + msleep(120); + + ret = mipi_dsi_dcs_set_display_on(dsi); + if (ret < 0) { + dev_err(dev, "Failed to set display on: %d\n", ret); + return ret; + } + msleep(70); + + return 0; +} + +static int td4328_tianma_fhdplus_off(struct td4328_tianma_fhdplus *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct device *dev = &dsi->dev; + int ret; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + ret = mipi_dsi_dcs_set_display_off(dsi); + if (ret < 0) { + dev_err(dev, "Failed to set display off: %d\n", ret); + return ret; + } + msleep(50); + + ret = mipi_dsi_dcs_enter_sleep_mode(dsi); + if (ret < 0) { + dev_err(dev, "Failed to enter sleep mode: %d\n", ret); + return ret; + } + msleep(100); + + return 0; +} + +static int td4328_tianma_fhdplus_prepare(struct drm_panel *panel) +{ + struct td4328_tianma_fhdplus *ctx = to_td4328_tianma_fhdplus(panel); + struct device *dev = &ctx->dsi->dev; + int ret; + + if (ctx->prepared) + return 0; + + ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + if (ret < 0) { + dev_err(dev, "Failed to enable regulators: %d\n", ret); + return ret; + } + + td4328_tianma_fhdplus_reset(ctx); + + ret = td4328_tianma_fhdplus_on(ctx); + if (ret < 0) { + dev_err(dev, "Failed to initialize panel: %d\n", ret); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + return ret; + } + + ctx->prepared = true; + return 0; +} + +static int td4328_tianma_fhdplus_unprepare(struct drm_panel *panel) +{ + struct td4328_tianma_fhdplus *ctx = to_td4328_tianma_fhdplus(panel); + struct device *dev = &ctx->dsi->dev; + int ret; + + if (!ctx->prepared) + return 0; + + ret = td4328_tianma_fhdplus_off(ctx); + if (ret < 0) + dev_err(dev, "Failed to un-initialize panel: %d\n", ret); + + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + + ctx->prepared = false; + return 0; +} + +static const struct drm_display_mode td4328_tianma_fhdplus_mode = { + .clock = (1080 + 76 + 4 + 20) * (2160 + 5 + 1 + 30) * 60 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 76, + .hsync_end = 1080 + 76 + 4, + .htotal = 1080 + 76 + 4 + 20, + .vdisplay = 2160, + .vsync_start = 2160 + 5, + .vsync_end = 2160 + 5 + 1, + .vtotal = 2160 + 5 + 1 + 30, + .width_mm = 68, + .height_mm = 136, +}; + +static int td4328_tianma_fhdplus_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &td4328_tianma_fhdplus_mode); + if (!mode) + return -ENOMEM; + + drm_mode_set_name(mode); + + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + connector->display_info.width_mm = mode->width_mm; + connector->display_info.height_mm = mode->height_mm; + drm_mode_probed_add(connector, mode); + + return 1; +} + +static const struct drm_panel_funcs td4328_tianma_fhdplus_panel_funcs = { + .prepare = td4328_tianma_fhdplus_prepare, + .unprepare = td4328_tianma_fhdplus_unprepare, + .get_modes = td4328_tianma_fhdplus_get_modes, +}; + +static int td4328_tianma_fhdplus_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct td4328_tianma_fhdplus *ctx; + int ret; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->supplies[0].supply = "vdd"; + ctx->supplies[1].supply = "vddio"; + ctx->supplies[2].supply = "vsn"; + ctx->supplies[3].supply = "vsp"; + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies), + ctx->supplies); + if (ret < 0) { + dev_err(dev, "Failed to get regulators: %d\n", ret); + return ret; + } + + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ctx->reset_gpio)) { + ret = PTR_ERR(ctx->reset_gpio); + dev_err(dev, "Failed to get reset-gpios: %d\n", ret); + return ret; + } + + ctx->dsi = dsi; + mipi_dsi_set_drvdata(dsi, ctx); + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_CLOCK_NON_CONTINUOUS; + + drm_panel_init(&ctx->panel, dev, &td4328_tianma_fhdplus_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) { + dev_err(dev, "Failed to get backlight: %d\n", ret); + return ret; + } + + drm_panel_add(&ctx->panel); + if (ret < 0) { + dev_err(dev, "Failed to add panel: %d\n", ret); + return ret; + } + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(dev, "Failed to attach to DSI host: %d\n", ret); + return ret; + } + + return 0; +} + +static int td4328_tianma_fhdplus_remove(struct mipi_dsi_device *dsi) +{ + struct td4328_tianma_fhdplus *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); + + drm_panel_remove(&ctx->panel); + + return 0; +} + +static const struct of_device_id td4328_tianma_fhdplus_of_match[] = { + { .compatible = "sony,voyager-td4328-tianma" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, td4328_tianma_fhdplus_of_match); + +static struct mipi_dsi_driver td4328_tianma_fhdplus_driver = { + .probe = td4328_tianma_fhdplus_probe, + .remove = td4328_tianma_fhdplus_remove, + .driver = { + .name = "panel-td4328-tianma-fhdplus", + .of_match_table = td4328_tianma_fhdplus_of_match, + }, +}; +module_mipi_dsi_driver(td4328_tianma_fhdplus_driver); + +MODULE_AUTHOR("Konrad Dybcio "); +MODULE_DESCRIPTION("DRM driver for 7"); +MODULE_LICENSE("GPL v2"); From 521b4dcb0c5e966f381a1b096a8ceecb00bf9cc5 Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Wed, 10 Feb 2021 22:58:36 +0100 Subject: [PATCH 011/187] dt-bindings: display: panel: Document Sony Voyager TD4328 Tianma display panel Add bindings for the newly added panel. Signed-off-by: Konrad Dybcio --- .../panel/sony,voyager-td4328-tianma.yaml | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/panel/sony,voyager-td4328-tianma.yaml diff --git a/Documentation/devicetree/bindings/display/panel/sony,voyager-td4328-tianma.yaml b/Documentation/devicetree/bindings/display/panel/sony,voyager-td4328-tianma.yaml new file mode 100644 index 000000000000..0d48b623ce59 --- /dev/null +++ b/Documentation/devicetree/bindings/display/panel/sony,voyager-td4328-tianma.yaml @@ -0,0 +1,63 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/display/panel/sony,voyager-td4328-tianma.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: SONY Voyager TD4328 Tianma 6.0" 1080×2160 LCD Panel + +maintainers: + - Konrad Dybcio + +description: |+ + This panel seems to only be found in the Sony Xperia XA2 Plus + smartphone and we have no straightforward way of + actually getting the correct model number, + as no schematics are released publicly. + +allOf: + - $ref: panel-common.yaml# + +properties: + compatible: + const: sony,voyager-td4328-tianma + reg: true + reset-gpios: true + vdd-supply: + description: core voltage supply + vddio-supply: + description: vddio supply + vsp-supply: + description: positive voltage supply + vsn-supply: + description: negative voltage supply + +required: + - compatible + - reg + - vdd-supply + - vddio-supply + - vsp-supply + - vsn-supply + - reset-gpios + +additionalProperties: false + +examples: + - | + #include + + dsi { + #address-cells = <1>; + #size-cells = <0>; + panel@0 { + reg = <0>; + compatible = "sony,voyager-td4328-tianma"; + + vdd-supply = <&pm8916_l8>; + vddio-supply = <&pm8916_l6>; + vsp-supply = <&lab>; + vsn-supply = <&ibb>; + reset-gpios = <&tlmm 53 GPIO_ACTIVE_HIGH>; + }; + }; From bf0f4ab2ce7e8524223cd8a9d4d799fcc09b1a4a Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Wed, 30 Dec 2020 22:08:42 +0100 Subject: [PATCH 012/187] drm/panel-simple: Add nt36672a Truly FHD+ panel This adds support for nt36672a Truly FHD+ (2520x1080) video mode panel as found on Sony Xperia 10 (kirin) devices. Due to the nature of phone manufacturing, it is impossible to retrieve the actual panel name, hence the replacement one, detailing the device it's used on. Signed-off-by: Konrad Dybcio --- .../display/panel/panel-simple-dsi.yaml | 2 ++ drivers/gpu/drm/panel/panel-simple.c | 33 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/Documentation/devicetree/bindings/display/panel/panel-simple-dsi.yaml b/Documentation/devicetree/bindings/display/panel/panel-simple-dsi.yaml index fbd71669248f..ed6f96ab6aa5 100644 --- a/Documentation/devicetree/bindings/display/panel/panel-simple-dsi.yaml +++ b/Documentation/devicetree/bindings/display/panel/panel-simple-dsi.yaml @@ -55,6 +55,8 @@ properties: - samsung,sofef00 # Shangai Top Display Optoelectronics 7" TL070WSH30 1024x600 TFT LCD panel - tdo,tl070wsh30 + # Sony Kirin nt36672a Truly FHD+ IPS LCD panel + - sony,kirin-nt36672a-truly reg: maxItems: 1 diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c index eb475a3a774b..a1524d91667e 100644 --- a/drivers/gpu/drm/panel/panel-simple.c +++ b/drivers/gpu/drm/panel/panel-simple.c @@ -4187,6 +4187,36 @@ static const struct panel_desc_dsi osd101t2045_53ts = { .lanes = 4, }; +static const struct drm_display_mode sony_kirin_nt36672a_truly_mode = { + .clock = (1080 + 25 + 12 + 120) * (2520 + 12 + 4 + 10) * 60 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 25, + .hsync_end = 1080 + 25 + 12, + .htotal = 1080 + 25 + 12 + 120, + .vdisplay = 2520, + .vsync_start = 2520 + 12, + .vsync_end = 2520 + 12 + 4, + .vtotal = 2520 + 12 + 4 + 10, + .width_mm = 60, + .height_mm = 139, +}; + +static const struct panel_desc_dsi sony_kirin_nt36672a_truly = { + .desc = { + .modes = &sony_kirin_nt36672a_truly_mode, + .num_modes = 1, + .bpc = 8, + .size = { + .width = 60, + .height = 139, + }, + }, + .flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_CLOCK_NON_CONTINUOUS, + .format = MIPI_DSI_FMT_RGB888, + .lanes = 4, +}; + static const struct of_device_id dsi_of_match[] = { { .compatible = "auo,b080uan01", @@ -4209,6 +4239,9 @@ static const struct of_device_id dsi_of_match[] = { }, { .compatible = "osddisplays,osd101t2045-53ts", .data = &osd101t2045_53ts + }, { + .compatible = "sony,kirin-nt36672a-truly", + .data = &sony_kirin_nt36672a_truly }, { /* sentinel */ } From 3e5b523e39a431b6916e587ea007111ac34b3b9f Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Sat, 18 Jul 2020 18:41:17 +0200 Subject: [PATCH 013/187] drm/panel-simple: Add nt36672a Tianma FHD+ panel This adds support for nt36672a Tianma FHD+ (2520x1080) video mode panel as found on Sony Xperia 10 Plus (mermaid) devices. Due to the nature of phone manufacturing, it is impossible to retrieve the actual panel name, hence the replacement one, detailing the device it's used on. Signed-off-by: Konrad Dybcio --- .../display/panel/panel-simple-dsi.yaml | 2 ++ drivers/gpu/drm/panel/panel-simple.c | 33 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/Documentation/devicetree/bindings/display/panel/panel-simple-dsi.yaml b/Documentation/devicetree/bindings/display/panel/panel-simple-dsi.yaml index ed6f96ab6aa5..358969aab60a 100644 --- a/Documentation/devicetree/bindings/display/panel/panel-simple-dsi.yaml +++ b/Documentation/devicetree/bindings/display/panel/panel-simple-dsi.yaml @@ -57,6 +57,8 @@ properties: - tdo,tl070wsh30 # Sony Kirin nt36672a Truly FHD+ IPS LCD panel - sony,kirin-nt36672a-truly + # Sony mermaid nt36672a Tianma FHD+ IPS LCD panel + - sony,mermaid-nt36672a-tianma reg: maxItems: 1 diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c index a1524d91667e..1e34437e52d2 100644 --- a/drivers/gpu/drm/panel/panel-simple.c +++ b/drivers/gpu/drm/panel/panel-simple.c @@ -4217,6 +4217,36 @@ static const struct panel_desc_dsi sony_kirin_nt36672a_truly = { .lanes = 4, }; +static const struct drm_display_mode sony_mermaid_nt36672a_tianma_mode = { + .clock = (1080 + 102 + 20 + 40) * (2520 + 10 + 2 + 8) * 60 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 102, + .hsync_end = 1080 + 102 + 20, + .htotal = 1080 + 102 + 20 + 40, + .vdisplay = 2520, + .vsync_start = 2520 + 10, + .vsync_end = 2520 + 10 + 2, + .vtotal = 2520 + 10 + 2 + 8, + .width_mm = 65, + .height_mm = 151, +}; + +static const struct panel_desc_dsi sony_mermaid_nt36672a_tianma = { + .desc = { + .modes = &sony_mermaid_nt36672a_tianma_mode, + .num_modes = 1, + .bpc = 8, + .size = { + .width = 65, + .height = 151, + }, + }, + .flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_CLOCK_NON_CONTINUOUS, + .format = MIPI_DSI_FMT_RGB888, + .lanes = 4, +}; + static const struct of_device_id dsi_of_match[] = { { .compatible = "auo,b080uan01", @@ -4242,6 +4272,9 @@ static const struct of_device_id dsi_of_match[] = { }, { .compatible = "sony,kirin-nt36672a-truly", .data = &sony_kirin_nt36672a_truly + }, { + .compatible = "sony,mermaid-nt36672a-tianma", + .data = &sony_mermaid_nt36672a_tianma }, { /* sentinel */ } From deeb3aa715fdaf40ab97c9097fcf3eaf99a3cba0 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Sun, 20 Sep 2020 14:11:50 +0200 Subject: [PATCH 014/187] drm/msm/mdp5: Fix SDM630/660 MDP5 configuration SDM630 has two usable layer mixers (LM0, LM2) and pingpongs (PP0, PP2), with writeback located on LM2, has no RGB pipe and does not support source split. SDM660 has four usable layer mixers (LM0, LM1, LM2, LM5) and three pingpongs (PP0, PP1, PP4), with writeback located on LM2, has no RGB pipes and does support source split; also, the HDMI is on INTF0. As cherry on top, let's also write the correct flush_hw_mask for both MDP configurations to stop flushing useless and unused pipes for performance purposes. Signed-off-by: AngeloGioacchino Del Regno --- drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.c | 56 +++++++----------------- 1 file changed, 17 insertions(+), 39 deletions(-) diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.c index 1bf9ff5dbabc..d3320caf042c 100644 --- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.c +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.c @@ -1038,13 +1038,12 @@ static const struct mdp5_cfg_hw sdm630_config = { .mdp = { .count = 1, .caps = MDP_CAP_CDM | - MDP_CAP_SRC_SPLIT | 0, }, .ctl = { .count = 5, .base = { 0x01000, 0x01200, 0x01400, 0x01600, 0x01800 }, - .flush_hw_mask = 0xf4ffffff, + .flush_hw_mask = 0xf54338c1, }, .pipe_vig = { .count = 1, @@ -1057,16 +1056,6 @@ static const struct mdp5_cfg_hw sdm630_config = { MDP_PIPE_CAP_SW_PIX_EXT | 0, }, - .pipe_rgb = { - .count = 4, - .base = { 0x14000, 0x16000, 0x18000, 0x1a000 }, - .caps = MDP_PIPE_CAP_HFLIP | - MDP_PIPE_CAP_VFLIP | - MDP_PIPE_CAP_SCALE | - MDP_PIPE_CAP_DECIMATION | - MDP_PIPE_CAP_SW_PIX_EXT | - 0, - }, .pipe_dma = { .count = 2, /* driver supports max of 2 currently */ .base = { 0x24000, 0x26000, 0x28000 }, @@ -1086,13 +1075,12 @@ static const struct mdp5_cfg_hw sdm630_config = { }, .lm = { - .count = 2, - .base = { 0x44000, 0x46000 }, + .count = 3, /* LM_1 seems broken */ + .base = { 0x44000, 0x45000, 0x46000 }, .instances = { { .id = 0, .pp = 0, .dspp = 0, - .caps = MDP_LM_CAP_DISPLAY | - MDP_LM_CAP_PAIR, }, - { .id = 1, .pp = 1, .dspp = -1, + .caps = MDP_LM_CAP_DISPLAY, }, + { .id = 2, .pp = 2, .dspp = -1, .caps = MDP_LM_CAP_WB, }, }, .nb_stages = 8, @@ -1108,8 +1096,8 @@ static const struct mdp5_cfg_hw sdm630_config = { .base = { 0x78000, 0x78800 }, }, .pp = { - .count = 3, - .base = { 0x70000, 0x71000, 0x72000 }, + .count = 3, /* Do not use PP1 */ + .base = { 0x70000, 0x70800, 0x71000 }, }, .cdm = { .count = 1, @@ -1137,11 +1125,11 @@ static const struct mdp5_cfg_hw sdm660_config = { .ctl = { .count = 5, .base = { 0x01000, 0x01200, 0x01400, 0x01600, 0x01800 }, - .flush_hw_mask = 0xf4ffffff, + .flush_hw_mask = 0xf5437bc3, }, .pipe_vig = { .count = 2, - .base = { 0x04000, 0x6000 }, + .base = { 0x04000, 0x06000 }, .caps = MDP_PIPE_CAP_HFLIP | MDP_PIPE_CAP_VFLIP | MDP_PIPE_CAP_SCALE | @@ -1150,16 +1138,6 @@ static const struct mdp5_cfg_hw sdm660_config = { MDP_PIPE_CAP_SW_PIX_EXT | 0, }, - .pipe_rgb = { - .count = 4, - .base = { 0x14000, 0x16000, 0x18000, 0x1a000 }, - .caps = MDP_PIPE_CAP_HFLIP | - MDP_PIPE_CAP_VFLIP | - MDP_PIPE_CAP_SCALE | - MDP_PIPE_CAP_DECIMATION | - MDP_PIPE_CAP_SW_PIX_EXT | - 0, - }, .pipe_dma = { .count = 2, /* driver supports max of 2 currently */ .base = { 0x24000, 0x26000, 0x28000 }, @@ -1179,19 +1157,19 @@ static const struct mdp5_cfg_hw sdm660_config = { }, .lm = { - .count = 4, - .base = { 0x44000, 0x45000, 0x46000, 0x49000 }, + .count = 6, /* Do not use LM_3, LM_4 */ + .base = { 0x44000, 0x45000, 0x46000, 0x47000, 0x48000, 0x49000 }, .instances = { { .id = 0, .pp = 0, .dspp = 0, .caps = MDP_LM_CAP_DISPLAY | MDP_LM_CAP_PAIR, }, { .id = 1, .pp = 1, .dspp = 1, .caps = MDP_LM_CAP_DISPLAY, }, - { .id = 2, .pp = 2, .dspp = -1, + { .id = 2, .pp = -1, .dspp = -1, + .caps = MDP_LM_CAP_WB, }, + { .id = 5, .pp = 4, .dspp = -1, .caps = MDP_LM_CAP_DISPLAY | MDP_LM_CAP_PAIR, }, - { .id = 3, .pp = 3, .dspp = -1, - .caps = MDP_LM_CAP_WB, }, }, .nb_stages = 8, .max_width = 2560, @@ -1206,7 +1184,7 @@ static const struct mdp5_cfg_hw sdm660_config = { .base = { 0x78000, 0x78800 }, }, .pp = { - .count = 5, + .count = 5, /* Do not use PP_2 and PP_3 */ .base = { 0x70000, 0x70800, 0x71000, 0x71800, 0x72000 }, }, .cdm = { @@ -1220,10 +1198,10 @@ static const struct mdp5_cfg_hw sdm660_config = { .intf = { .base = { 0x6a000, 0x6a800, 0x6b000, 0x6b800 }, .connect = { - [0] = INTF_DISABLED, + [0] = INTF_HDMI, [1] = INTF_DSI, [2] = INTF_DSI, - [3] = INTF_HDMI, + [3] = INTF_DISABLED, }, }, .max_clk = 412500000, From 64f6436594535fe6653f3a007d41e86e4990c2e5 Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Sat, 18 Jul 2020 22:01:12 +0200 Subject: [PATCH 015/187] remoteproc: Add support for SDM630/636/660 MSS remoteproc This adds support for sdm630/636/660 modem subsystem remote processor. Currently an issue persists, where the remote processor will restart itself every 40 seconds, but it seems to work otherwise. Signed-off-by: Konrad Dybcio (JAMI: fixed up for v5.16-rc1) --- drivers/remoteproc/qcom_q6v5_mss.c | 108 +++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/drivers/remoteproc/qcom_q6v5_mss.c b/drivers/remoteproc/qcom_q6v5_mss.c index 43ea8455546c..0939c8e6893c 100644 --- a/drivers/remoteproc/qcom_q6v5_mss.c +++ b/drivers/remoteproc/qcom_q6v5_mss.c @@ -68,6 +68,7 @@ #define QDSP6SS_MEM_PWR_CTL 0x0B0 #define QDSP6V6SS_MEM_PWR_CTL 0x034 #define QDSP6SS_STRAP_ACC 0x110 +#define QDSP6V62SS_BHS_STATUS 0x0C4 /* AXI Halt Register Offsets */ #define AXI_HALTREQ_REG 0x0 @@ -117,6 +118,9 @@ #define QDSP6v56_CLAMP_QMC_MEM BIT(22) #define QDSP6SS_XO_CBCR 0x0038 #define QDSP6SS_ACC_OVERRIDE_VAL 0x20 +#define QDSP6v55_BHS_EN_REST_ACK BIT(0) + +#define BHS_CHECK_MAX_LOOPS (200) /* QDSP6v65 parameters */ #define QDSP6SS_CORE_CBCR 0x20 @@ -238,6 +242,7 @@ enum { MSS_MSM8998, MSS_SC7180, MSS_SC7280, + MSS_SDM660, MSS_SDM845, }; @@ -753,6 +758,80 @@ static int q6v5proc_reset(struct q6v5 *qproc) val |= readl(qproc->reg_base + mem_pwr_ctl); udelay(1); } + /* Remove word line clamp */ + val = readl(qproc->reg_base + QDSP6SS_PWR_CTL_REG); + val &= ~QDSP6v56_CLAMP_WL; + writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG); + } else if (qproc->version == MSS_SDM660) { + int mem_pwr_ctl; + + /* Override the ACC value if required */ + writel(QDSP6SS_ACC_OVERRIDE_VAL, + qproc->reg_base + QDSP6SS_STRAP_ACC); + + /* Assert resets, stop core */ + val = readl(qproc->reg_base + QDSP6SS_RESET_REG); + val |= Q6SS_CORE_ARES | Q6SS_BUS_ARES_ENABLE | Q6SS_STOP_CORE; + writel(val, qproc->reg_base + QDSP6SS_RESET_REG); + + /* BHS require xo cbcr to be enabled */ + val = readl(qproc->reg_base + QDSP6SS_XO_CBCR); + val |= 1; + writel(val, qproc->reg_base + QDSP6SS_XO_CBCR); + + /* Enable power block headswitch and wait for it to stabilize */ + val = readl(qproc->reg_base + QDSP6SS_PWR_CTL_REG); + val |= QDSP6v56_BHS_ON; + writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG); + //val |= readl(qproc->reg_base + QDSP6SS_PWR_CTL_REG); + mb(); + udelay(1); + + for (i = BHS_CHECK_MAX_LOOPS; i > 0; i--) { + if (readl_relaxed(qproc->reg_base + QDSP6V62SS_BHS_STATUS) + & QDSP6v55_BHS_EN_REST_ACK) + break; + udelay(1); + } + if (!i) { + pr_err("%s: BHS_EN_REST_ACK not set!\n", __func__); + return -ETIMEDOUT; + } + + /* Put LDO in bypass mode */ + val |= QDSP6v56_LDO_BYP; + writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG); + + /* Remove QMC_MEM clamp */ + val &= ~QDSP6v56_CLAMP_QMC_MEM; + writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG); + + /* Deassert QDSP6 compiler memory clamp */ + val = readl(qproc->reg_base + QDSP6SS_PWR_CTL_REG); + val &= ~QDSP6v56_CLAMP_QMC_MEM; + writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG); + + /* Deassert memory peripheral sleep and L2 memory standby */ + val |= Q6SS_L2DATA_STBY_N | Q6SS_SLP_RET_N; + writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG); + + /* Turn on L1, L2, ETB and JU memories 1 at a time */ + mem_pwr_ctl = QDSP6V6SS_MEM_PWR_CTL; + i = 29; + + val = readl(qproc->reg_base + mem_pwr_ctl); //? + for (; i >= 0; i--) { + val |= BIT(i); + writel(val, qproc->reg_base + mem_pwr_ctl); + /* + * Read back value to ensure the write is done then + * wait for 1us for both memory peripheral and data + * array to turn on. + */ + val |= readl(qproc->reg_base + mem_pwr_ctl); + udelay(1); + } + /* Remove word line clamp */ val = readl(qproc->reg_base + QDSP6SS_PWR_CTL_REG); val &= ~QDSP6v56_CLAMP_WL; @@ -784,6 +863,7 @@ static int q6v5proc_reset(struct q6v5 *qproc) val |= Q6SS_L2DATA_SLP_NRET_N_0; writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG); } + /* Remove IO clamp */ val &= ~Q6SS_CLAMP_IO; writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG); @@ -2086,6 +2166,33 @@ static const struct rproc_hexagon_res sc7280_mss = { .version = MSS_SC7280, }; +static const struct rproc_hexagon_res sdm660_mss = { + .hexagon_mba_image = "mba.mbn", + .proxy_clk_names = (char*[]){ + "xo", + "qdss", + NULL + }, + .active_clk_names = (char*[]){ + "iface", + "bus", + "gpll0_mss", + "mnoc_axi", + "snoc_axi", + "mem", + NULL + }, + .proxy_pd_names = (char*[]){ + "cx", + "mx", + NULL + }, + .need_mem_protection = true, + .has_alt_reset = false, + .has_spare_reg = false, + .version = MSS_SDM660, +}; + static const struct rproc_hexagon_res sdm845_mss = { .hexagon_mba_image = "mba.mbn", .proxy_clk_names = (char*[]){ @@ -2292,6 +2399,7 @@ static const struct of_device_id q6v5_of_match[] = { { .compatible = "qcom,msm8998-mss-pil", .data = &msm8998_mss}, { .compatible = "qcom,sc7180-mss-pil", .data = &sc7180_mss}, { .compatible = "qcom,sc7280-mss-pil", .data = &sc7280_mss}, + { .compatible = "qcom,sdm660-mss-pil", .data = &sdm660_mss}, { .compatible = "qcom,sdm845-mss-pil", .data = &sdm845_mss}, { }, }; From 16bb0ee336f65cf9bcfd4ae29404f96502df04ad Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Sun, 31 Jan 2021 21:01:36 +0100 Subject: [PATCH 016/187] arm64: dts: qcom: sdm630: Enable SMMUs Now that the quirks are in, SMMUs are fine to enable. Signed-off-by: Konrad Dybcio --- arch/arm64/boot/dts/qcom/sdm630.dtsi | 8 -------- 1 file changed, 8 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/sdm630.dtsi b/arch/arm64/boot/dts/qcom/sdm630.dtsi index 51c59eb39813..0666b9b0365c 100644 --- a/arch/arm64/boot/dts/qcom/sdm630.dtsi +++ b/arch/arm64/boot/dts/qcom/sdm630.dtsi @@ -646,8 +646,6 @@ , , ; - - status = "disabled"; }; a2noc: interconnect@1704000 { @@ -1125,8 +1123,6 @@ , , ; - - status = "disabled"; }; gpucc: clock-controller@5065000 { @@ -1172,8 +1168,6 @@ , , ; - - status = "disabled"; }; sram@290000 { @@ -2117,8 +2111,6 @@ , , ; - - status = "disabled"; }; adsp_pil: remoteproc@15700000 { From 3a590afb1ac5bc73d344ea43c5343a0dead29030 Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Sun, 21 Mar 2021 20:30:19 +0100 Subject: [PATCH 017/187] nile: Enable gpucc --- arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile.dtsi | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile.dtsi b/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile.dtsi index 0c631a3cdaf2..a2626ba633fd 100644 --- a/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile.dtsi +++ b/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile.dtsi @@ -226,6 +226,10 @@ }; }; +&gpucc { + status = "okay"; +}; + &pon { volup { compatible = "qcom,pm8941-resin"; From 5d7ecb459b0c38e3f3c888ea78415cba7e6541fb Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Mon, 22 Mar 2021 00:18:38 +0100 Subject: [PATCH 018/187] bye framebuffer and sdhc_2 --- .../dts/qcom/sdm630-sony-xperia-nile.dtsi | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile.dtsi b/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile.dtsi index a2626ba633fd..8790c16de785 100644 --- a/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile.dtsi +++ b/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile.dtsi @@ -25,17 +25,17 @@ #size-cells = <2>; ranges; - stdout-path = "framebuffer0"; + // stdout-path = "framebuffer0"; - framebuffer0: framebuffer@9d400000 { - compatible = "simple-framebuffer"; - reg = <0 0x9d400000 0 (1920 * 1080 * 4)>; - width = <1080>; - height = <1920>; - stride = <(1080 * 4)>; - format = "a8r8g8b8"; - status= "okay"; - }; + // framebuffer0: framebuffer@9d400000 { + // compatible = "simple-framebuffer"; + // reg = <0 0x9d400000 0 (1920 * 1080 * 4)>; + // width = <1080>; + // height = <1920>; + // stride = <(1080 * 4)>; + // format = "a8r8g8b8"; + // status= "okay"; + // }; }; board_vbat: vbat-regulator { @@ -579,7 +579,7 @@ }; &sdhc_2 { - status = "okay"; + status = "disabled"; vmmc-supply = <&vreg_l5b_2p95>; vqmmc-supply = <&vreg_l2b_2p95>; From 98751f8c786e1d2489fc3e16286c6199ca8ec8a2 Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Thu, 13 Aug 2020 17:49:29 +0200 Subject: [PATCH 019/187] Adreno firmware in .c file; vendor DT --- arch/arm64/boot/dts/qcom/sdm630.dtsi | 14 ++++++++++++++ drivers/gpu/drm/msm/adreno/adreno_device.c | 4 ++++ 2 files changed, 18 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/sdm630.dtsi b/arch/arm64/boot/dts/qcom/sdm630.dtsi index 0666b9b0365c..37c17d0cd666 100644 --- a/arch/arm64/boot/dts/qcom/sdm630.dtsi +++ b/arch/arm64/boot/dts/qcom/sdm630.dtsi @@ -314,6 +314,20 @@ scm { compatible = "qcom,scm-msm8998", "qcom,scm"; }; + android { + compatible = "android,firmware"; + fstab { + compatible = "android,fstab"; + vendor { + compatible = "android,vendor"; + dev = "/dev/block/platform/soc/c0c4000.sdhci/by-name/vendor"; + type = "ext4"; + mnt_flags = "ro,barrier=1,discard"; + fsmgr_flags = "wait,slotselect"; + status = "ok"; + }; + }; + }; }; memory@80000000 { diff --git a/drivers/gpu/drm/msm/adreno/adreno_device.c b/drivers/gpu/drm/msm/adreno/adreno_device.c index 2a6ce76656aa..850c69e1bfbf 100644 --- a/drivers/gpu/drm/msm/adreno/adreno_device.c +++ b/drivers/gpu/drm/msm/adreno/adreno_device.c @@ -338,6 +338,10 @@ MODULE_FIRMWARE("qcom/a530_zap.mdt"); MODULE_FIRMWARE("qcom/a530_zap.b00"); MODULE_FIRMWARE("qcom/a530_zap.b01"); MODULE_FIRMWARE("qcom/a530_zap.b02"); +MODULE_FIRMWARE("qcom/a508_zap.mdt"); +MODULE_FIRMWARE("qcom/a508_zap.b00"); +MODULE_FIRMWARE("qcom/a508_zap.b01"); +MODULE_FIRMWARE("qcom/a508_zap.b02"); MODULE_FIRMWARE("qcom/a630_sqe.fw"); MODULE_FIRMWARE("qcom/a630_gmu.bin"); MODULE_FIRMWARE("qcom/a630_zap.mbn"); From d2f323864d8dc2b54b66f037e7c71cbe1ac7a17e Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Fri, 14 Aug 2020 10:08:14 +0200 Subject: [PATCH 020/187] bye slotselect --- arch/arm64/boot/dts/qcom/sdm630.dtsi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/sdm630.dtsi b/arch/arm64/boot/dts/qcom/sdm630.dtsi index 37c17d0cd666..6662c6ed3b1c 100644 --- a/arch/arm64/boot/dts/qcom/sdm630.dtsi +++ b/arch/arm64/boot/dts/qcom/sdm630.dtsi @@ -320,10 +320,10 @@ compatible = "android,fstab"; vendor { compatible = "android,vendor"; - dev = "/dev/block/platform/soc/c0c4000.sdhci/by-name/vendor"; + dev = "/dev/block/platform/soc/c0c4000.sdhci/by-name/vendor_a"; type = "ext4"; mnt_flags = "ro,barrier=1,discard"; - fsmgr_flags = "wait,slotselect"; + fsmgr_flags = "wait"; status = "ok"; }; }; From 4ccb81c57b0b3ed69aca557cb5201f33c03e6a45 Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Mon, 22 Mar 2021 10:06:57 +0100 Subject: [PATCH 021/187] 630: Also system part, root fstab not found --- arch/arm64/boot/dts/qcom/sdm630.dtsi | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/sdm630.dtsi b/arch/arm64/boot/dts/qcom/sdm630.dtsi index 6662c6ed3b1c..bc876f1c2a06 100644 --- a/arch/arm64/boot/dts/qcom/sdm630.dtsi +++ b/arch/arm64/boot/dts/qcom/sdm630.dtsi @@ -326,6 +326,14 @@ fsmgr_flags = "wait"; status = "ok"; }; + system { + compatible = "android,system"; + dev = "/dev/block/platform/soc/c0c4000.sdhci/by-name/system_a"; + type = "ext4"; + mnt_flags = "ro,barrier=1,discard"; + fsmgr_flags = "wait"; + status = "ok"; + }; }; }; }; From f10637ebf121420dc9e9e2d3d5c8b7676f4f9206 Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Mon, 21 Sep 2020 01:30:01 +0200 Subject: [PATCH 022/187] arm64: dts: qcom: sdm63*-xperia: Add display panels Signed-off-by: Konrad Dybcio --- arch/arm64/boot/dts/qcom/sdm630-sony-xperia-ganges-kirin.dts | 5 +++++ .../boot/dts/qcom/sdm630-sony-xperia-nile-discovery.dts | 4 ++++ arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile-pioneer.dts | 4 ++++ arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile-voyager.dts | 4 ++++ .../boot/dts/qcom/sdm636-sony-xperia-ganges-mermaid.dts | 4 ++++ 5 files changed, 21 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-ganges-kirin.dts b/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-ganges-kirin.dts index 71b448978e88..dd4b64696d2a 100644 --- a/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-ganges-kirin.dts +++ b/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-ganges-kirin.dts @@ -23,4 +23,9 @@ /* Ganges devices feature a Novatek touchscreen instead. */ /delete-node/ &touchscreen; + +&panel { + compatible = "sony,kirin-nt36672a-truly"; +}; + /delete-node/ &vreg_l18a_1v8; diff --git a/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile-discovery.dts b/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile-discovery.dts index dd484a9bc7cd..803e0a2e74a6 100644 --- a/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile-discovery.dts +++ b/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile-discovery.dts @@ -13,3 +13,7 @@ compatible = "sony,discovery-row", "qcom,sdm630"; chassis-type = "handset"; }; + +&panel { + compatible = "sony,discovery-td4322-innolux"; +}; diff --git a/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile-pioneer.dts b/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile-pioneer.dts index 2da83cd41187..043aaf2f1af7 100644 --- a/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile-pioneer.dts +++ b/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile-pioneer.dts @@ -13,3 +13,7 @@ compatible = "sony,pioneer-row", "qcom,sdm630"; chassis-type = "handset"; }; + +&panel { + compatible = "sony,pioneer-td4322-truly"; +}; diff --git a/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile-voyager.dts b/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile-voyager.dts index a679d4acee29..87adba0213d5 100644 --- a/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile-voyager.dts +++ b/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile-voyager.dts @@ -20,3 +20,7 @@ }; }; }; + +&panel { + compatible = "sony,voyager-td4328-tianma"; +}; diff --git a/arch/arm64/boot/dts/qcom/sdm636-sony-xperia-ganges-mermaid.dts b/arch/arm64/boot/dts/qcom/sdm636-sony-xperia-ganges-mermaid.dts index bba1c2bce213..4166e50a9f55 100644 --- a/arch/arm64/boot/dts/qcom/sdm636-sony-xperia-ganges-mermaid.dts +++ b/arch/arm64/boot/dts/qcom/sdm636-sony-xperia-ganges-mermaid.dts @@ -17,6 +17,10 @@ qcom,pmic-id = <0x1001b 0x101011a 0x00 0x00 0x1001b 0x201011a 0x00 0x00 0x1001b 0x102001a 0x00 0x00>; }; +&panel { + compatible = "sony,mermaid-nt36672a-tianma"; +}; + &sdc2_state_on { pinconf-clk { drive-strength = <14>; From 32e78e5c4152233fd7651844e2aa12e9e900c494 Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Mon, 22 Mar 2021 11:06:21 +0100 Subject: [PATCH 023/187] PANELS TODO FINISHME: use dev_err_probe in _probe! --- .../gpu/drm/panel/panel-td4322-innolux-fhd.c | 29 +++++-------------- .../drm/panel/panel-td4328-tianma-fhdplus.c | 29 +++++-------------- 2 files changed, 16 insertions(+), 42 deletions(-) diff --git a/drivers/gpu/drm/panel/panel-td4322-innolux-fhd.c b/drivers/gpu/drm/panel/panel-td4322-innolux-fhd.c index 69a962cc106e..8959dfc00c40 100644 --- a/drivers/gpu/drm/panel/panel-td4322-innolux-fhd.c +++ b/drivers/gpu/drm/panel/panel-td4322-innolux-fhd.c @@ -195,17 +195,12 @@ static int td4322_innolux_fhd_probe(struct mipi_dsi_device *dsi) ctx->supplies[3].supply = "vsp"; ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies), ctx->supplies); - if (ret < 0) { - dev_err(dev, "Failed to get regulators: %d\n", ret); - return ret; - } + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to get regulators\n"); ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); - if (IS_ERR(ctx->reset_gpio)) { - ret = PTR_ERR(ctx->reset_gpio); - dev_err(dev, "Failed to get reset-gpios: %d\n", ret); - return ret; - } + if (IS_ERR(ctx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), "Failed to get reset-gpios\n"); ctx->dsi = dsi; mipi_dsi_set_drvdata(dsi, ctx); @@ -219,22 +214,14 @@ static int td4322_innolux_fhd_probe(struct mipi_dsi_device *dsi) DRM_MODE_CONNECTOR_DSI); ret = drm_panel_of_backlight(&ctx->panel); - if (ret) { - dev_err(dev, "Failed to get backlight: %d\n", ret); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "Failed to get backlight\n"); drm_panel_add(&ctx->panel); - if (ret < 0) { - dev_err(dev, "Failed to add panel: %d\n", ret); - return ret; - } ret = mipi_dsi_attach(dsi); - if (ret < 0) { - dev_err(dev, "Failed to attach to DSI host: %d\n", ret); - return ret; - } + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to attach to DSI host\n"); return 0; } diff --git a/drivers/gpu/drm/panel/panel-td4328-tianma-fhdplus.c b/drivers/gpu/drm/panel/panel-td4328-tianma-fhdplus.c index d1de8b549bd8..6a1ff6fdfa55 100644 --- a/drivers/gpu/drm/panel/panel-td4328-tianma-fhdplus.c +++ b/drivers/gpu/drm/panel/panel-td4328-tianma-fhdplus.c @@ -198,17 +198,12 @@ static int td4328_tianma_fhdplus_probe(struct mipi_dsi_device *dsi) ctx->supplies[3].supply = "vsp"; ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies), ctx->supplies); - if (ret < 0) { - dev_err(dev, "Failed to get regulators: %d\n", ret); - return ret; - } + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to get regulators\n"); ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); - if (IS_ERR(ctx->reset_gpio)) { - ret = PTR_ERR(ctx->reset_gpio); - dev_err(dev, "Failed to get reset-gpios: %d\n", ret); - return ret; - } + if (IS_ERR(ctx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), "Failed to get reset-gpios\n"); ctx->dsi = dsi; mipi_dsi_set_drvdata(dsi, ctx); @@ -222,22 +217,14 @@ static int td4328_tianma_fhdplus_probe(struct mipi_dsi_device *dsi) DRM_MODE_CONNECTOR_DSI); ret = drm_panel_of_backlight(&ctx->panel); - if (ret) { - dev_err(dev, "Failed to get backlight: %d\n", ret); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "Failed to get backlight\n"); drm_panel_add(&ctx->panel); - if (ret < 0) { - dev_err(dev, "Failed to add panel: %d\n", ret); - return ret; - } ret = mipi_dsi_attach(dsi); - if (ret < 0) { - dev_err(dev, "Failed to attach to DSI host: %d\n", ret); - return ret; - } + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to attach to DSI host\n"); return 0; } From 4d86d0c5b4b3a1d653e089b96b62a63972ecd9ab Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Mon, 22 Mar 2021 11:12:13 +0100 Subject: [PATCH 024/187] DISCO PANELS?!?!? --- .../dts/qcom/sdm630-sony-xperia-nile.dtsi | 49 ++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile.dtsi b/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile.dtsi index 8790c16de785..c0c706f22253 100644 --- a/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile.dtsi +++ b/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile.dtsi @@ -19,7 +19,7 @@ /* This part enables graphical output via bootloader-enabled display */ chosen { - bootargs = "earlycon=tty0 console=tty0"; + // bootargs = "earlycon=tty0 console=tty0"; #address-cells = <2>; #size-cells = <2>; @@ -226,6 +226,39 @@ }; }; +&dsi0 { + #address-cells = <1>; + #size-cells = <0>; + + vdda-supply = <&vreg_l1a_1p225>; + + panel: panel@0 { + reg = <0>; + + reset-gpios = <&tlmm 53 GPIO_ACTIVE_HIGH>; + disp-te-gpios = <&tlmm 59 GPIO_ACTIVE_HIGH>; + + backlight = <&pm660l_wled>; + + pinctrl-names = "default"; + pinctrl-0 = <&panel_reset_n &mdp_vsync_n>; + port { + panel_in: endpoint { + remote-endpoint = <&dsi0_out>; + }; + }; + }; +}; + +&dsi0_out { + remote-endpoint = <&panel_in>; + data-lanes = <0 1 2 3>; +}; + +&mdss { + status = "okay"; +}; + &gpucc { status = "okay"; }; @@ -620,6 +653,20 @@ bias-disable; drive-strength = <2>; }; + + panel_reset_n: panel-rst-n { + pins = "gpio53"; + function = "gpio"; + drive-strength = <8>; + bias-disable; + }; + + mdp_vsync_n: mdp-vsync-n { + pins = "gpio59"; + function = "mdp_vsync"; + drive-strength = <2>; + bias-pull-down; + }; }; &usb3 { From 67da594956333e19b5fc1f7ac8923284ddf90e0b Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Tue, 30 Mar 2021 23:30:27 +0200 Subject: [PATCH 025/187] nile: Turn on pm660l_wled to not -EPROBE_DEFER the panel --- arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile.dtsi | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile.dtsi b/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile.dtsi index c0c706f22253..8fab280ad9e8 100644 --- a/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile.dtsi +++ b/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile.dtsi @@ -259,6 +259,10 @@ status = "okay"; }; +&pm660l_wled { + status = "okay"; +}; + &gpucc { status = "okay"; }; From b2d482ea0d479cefd5b9df6bea766597fb7781b7 Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Tue, 30 Mar 2021 23:40:47 +0200 Subject: [PATCH 026/187] sdm630: Add missing zap-shader! --- arch/arm64/boot/dts/qcom/sdm630.dtsi | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/sdm630.dtsi b/arch/arm64/boot/dts/qcom/sdm630.dtsi index bc876f1c2a06..79fca5ae9592 100644 --- a/arch/arm64/boot/dts/qcom/sdm630.dtsi +++ b/arch/arm64/boot/dts/qcom/sdm630.dtsi @@ -1111,6 +1111,10 @@ opp-supported-hw = <0xFF>; }; }; + + zap-shader { + memory-region = <&zap_shader_region>; + }; }; kgsl_smmu: iommu@5040000 { From d865ddef074ed58e5a6c04df0468139f9d5c8404 Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Sun, 18 Oct 2020 16:10:41 +0200 Subject: [PATCH 027/187] gpu go 700mhz --- arch/arm64/boot/dts/qcom/sdm630.dtsi | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/sdm630.dtsi b/arch/arm64/boot/dts/qcom/sdm630.dtsi index 79fca5ae9592..523c638063af 100644 --- a/arch/arm64/boot/dts/qcom/sdm630.dtsi +++ b/arch/arm64/boot/dts/qcom/sdm630.dtsi @@ -1068,6 +1068,20 @@ gpu_sdm630_opp_table: opp-table { compatible = "operating-points-v2"; + + opp-700000000 { + opp-hz = /bits/ 64 <700000000>; + opp-level = ; + opp-peak-kBps = <5184000>; + opp-supported-hw = <0xFF>; + }; + + /* + * 775MHz is only available on default speed bin + * or 0xA2 (speed bin 1). Though it cannot be used + * for now due to interconnect framework not supporting + * multiple frequencies at the same opp-level + opp-775000000 { opp-hz = /bits/ 64 <775000000>; opp-level = ; @@ -1110,6 +1124,7 @@ opp-peak-kBps = <1200000>; opp-supported-hw = <0xFF>; }; + */ }; zap-shader { From 7bd23d981fb07e67a33ac3df0e62eb2a4ae0cb69 Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Sat, 31 Jul 2021 13:25:42 +0200 Subject: [PATCH 028/187] PANEL INNOLUX FIXUP!: Use set_tear_on for VBLANK instead of magic const --- drivers/gpu/drm/panel/panel-td4322-innolux-fhd.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/panel/panel-td4322-innolux-fhd.c b/drivers/gpu/drm/panel/panel-td4322-innolux-fhd.c index 8959dfc00c40..77cd13d2d353 100644 --- a/drivers/gpu/drm/panel/panel-td4322-innolux-fhd.c +++ b/drivers/gpu/drm/panel/panel-td4322-innolux-fhd.c @@ -48,7 +48,11 @@ static int td4322_innolux_fhd_on(struct td4322_innolux_fhd *ctx) dsi->mode_flags |= MIPI_DSI_MODE_LPM; - dsi_dcs_write_seq(dsi, 0x35); + ret = mipi_dsi_dcs_set_tear_on(dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK); + if (ret < 0) { + dev_err(dev, "Failed to set tear on: %d\n", ret); + return ret; + } ret = mipi_dsi_dcs_set_display_on(dsi); if (ret < 0) { From 845ae94d37259e5a6b3ff4c2cf0fd1db465d20aa Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Tue, 15 Jun 2021 01:00:20 +0200 Subject: [PATCH 029/187] FIXUP: simple-panel: add missing connector_type! --- drivers/gpu/drm/panel/panel-simple.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c index 1e34437e52d2..b00792f52029 100644 --- a/drivers/gpu/drm/panel/panel-simple.c +++ b/drivers/gpu/drm/panel/panel-simple.c @@ -4210,6 +4210,7 @@ static const struct panel_desc_dsi sony_kirin_nt36672a_truly = { .width = 60, .height = 139, }, + .connector_type = DRM_MODE_CONNECTOR_DSI, }, .flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | MIPI_DSI_CLOCK_NON_CONTINUOUS, @@ -4240,6 +4241,7 @@ static const struct panel_desc_dsi sony_mermaid_nt36672a_tianma = { .width = 65, .height = 151, }, + .connector_type = DRM_MODE_CONNECTOR_DSI, }, .flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | MIPI_DSI_CLOCK_NON_CONTINUOUS, From fdafc0019a775abb9e5e5ce610ad434db2aceecd Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Sun, 11 Apr 2021 23:04:31 +0200 Subject: [PATCH 030/187] leds: ledtrig-pattern: Use last_repeat when applying hw pattern `last_repeat` holds the actual value requested by the user whereas `repeat` is a software iteration variable that is unused in hardware patterns. Furthermore `last_repeat` is the field returned to the user when reading the `repeat` sysfs property. This field is initialized to `-1` which is - together with `1` - the only valid value in the upcoming Qualcomm LPG driver. It is thus unexpected when `repeat` with an initialization value of `0` is passed into the the driver, when the sysfs property clearly presents a value of `-1`. Signed-off-by: Marijn Suijten --- drivers/leds/trigger/ledtrig-pattern.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/leds/trigger/ledtrig-pattern.c b/drivers/leds/trigger/ledtrig-pattern.c index 43a265dc4696..a11d68143757 100644 --- a/drivers/leds/trigger/ledtrig-pattern.c +++ b/drivers/leds/trigger/ledtrig-pattern.c @@ -126,7 +126,8 @@ static int pattern_trig_start_pattern(struct led_classdev *led_cdev) if (data->is_hw_pattern) { return led_cdev->pattern_set(led_cdev, data->patterns, - data->npatterns, data->repeat); + data->npatterns, + data->last_repeat); } /* At least 2 tuples for software pattern. */ From e07ec640880e151af24b20afeb56bad747a0c683 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Thu, 29 Apr 2021 14:15:12 -0700 Subject: [PATCH 031/187] dt-bindings: leds: Add Qualcomm Light Pulse Generator binding This adds the binding document describing the three hardware blocks related to the Light Pulse Generator found in a wide range of Qualcomm PMICs. Signed-off-by: Bjorn Andersson --- .../bindings/leds/leds-qcom-lpg.yaml | 158 ++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 Documentation/devicetree/bindings/leds/leds-qcom-lpg.yaml diff --git a/Documentation/devicetree/bindings/leds/leds-qcom-lpg.yaml b/Documentation/devicetree/bindings/leds/leds-qcom-lpg.yaml new file mode 100644 index 000000000000..2998598e8785 --- /dev/null +++ b/Documentation/devicetree/bindings/leds/leds-qcom-lpg.yaml @@ -0,0 +1,158 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/leds/leds-qcom-lpg.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm Light Pulse Generator + +maintainers: + - Bjorn Andersson + +description: > + The Qualcomm Light Pulse Generator consists of three different hardware blocks; + a ramp generator with lookup table, the light pulse generator and a three + channel current sink. These blocks are found in a wide range of Qualcomm PMICs. + +properties: + compatible: + enum: + - qcom,pm8150b-lpg + - qcom,pm8150l-lpg + - qcom,pm8916-pwm + - qcom,pm8941-lpg + - qcom,pm8994-lpg + - qcom,pmi8994-lpg + - qcom,pmi8998-lpg + + "#pwm-cells": + const: 2 + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + + qcom,power-source: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + power-source used to drive the output, as defined in the datasheet. + Should be specified if the TRILED block is present + + qcom,dtest: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: > + A list of integer pairs, where each pair represent the dtest line the + particular channel should be connected to and the flags denoting how the + value should be outputed, as defined in the datasheet. The number of + pairs should be the same as the number of channels. + + multi-led: + type: object + $ref: leds-class-multicolor.yaml# + properties: + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + + "^led@[0-9a-f]$": + type: object + $ref: common.yaml# + +patternProperties: + "^led@[0-9a-f]$": + type: object + $ref: common.yaml# + + properties: + reg: true + + required: + - reg + +required: + - compatible + +additionalProperties: false + +examples: + - | + #include + + lpg { + compatible = "qcom,pmi8994-lpg"; + + #address-cells = <1>; + #size-cells = <0>; + + qcom,power-source = <1>; + + qcom,dtest = <0 0 + 0 0 + 0 0 + 4 1>; + + led@1 { + reg = <1>; + label = "green:user1"; + }; + + led@2 { + reg = <2>; + label = "green:user0"; + default-state = "on"; + }; + + led@3 { + reg = <3>; + label = "green:user2"; + }; + + led@4 { + reg = <4>; + label = "green:user3"; + }; + }; + - | + #include + + lpg { + compatible = "qcom,pmi8994-lpg"; + + #address-cells = <1>; + #size-cells = <0>; + + qcom,power-source = <1>; + + multi-led { + color = ; + label = "rgb:notification"; + + #address-cells = <1>; + #size-cells = <0>; + + led@1 { + reg = <1>; + color = ; + }; + + led@2 { + reg = <2>; + color = ; + }; + + led@3 { + reg = <3>; + color = ; + }; + }; + }; + - | + lpg { + compatible = "qcom,pm8916-pwm"; + #pwm-cells = <2>; + }; +... From fed6e276831399878a64909a396cbe719fcc993d Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Thu, 29 Apr 2021 14:15:13 -0700 Subject: [PATCH 032/187] leds: Add driver for Qualcomm LPG The Light Pulse Generator (LPG) is a PWM-block found in a wide range of PMICs from Qualcomm. It can operate on fixed parameters or based on a lookup-table, altering the duty cycle over time - which provides the means for e.g. hardware assisted transitions of LED brightness. Signed-off-by: Bjorn Andersson --- drivers/leds/Kconfig | 3 + drivers/leds/Makefile | 3 + drivers/leds/rgb/leds-qcom-lpg.c | 1286 ++++++++++++++++++++++++++++++ 3 files changed, 1292 insertions(+) create mode 100644 drivers/leds/rgb/leds-qcom-lpg.c diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index ed800f5da7d8..ffb8777842ff 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -876,6 +876,9 @@ source "drivers/leds/blink/Kconfig" comment "Flash and Torch LED drivers" source "drivers/leds/flash/Kconfig" +comment "RGB LED drivers" +source "drivers/leds/rgb/Kconfig" + comment "LED Triggers" source "drivers/leds/trigger/Kconfig" diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index c636ec069612..351153f84070 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -100,6 +100,9 @@ obj-$(CONFIG_LEDS_USER) += uleds.o # Flash and Torch LED Drivers obj-$(CONFIG_LEDS_CLASS_FLASH) += flash/ +# RGB LED Drivers +obj-$(CONFIG_LEDS_CLASS_MULTICOLOR) += rgb/ + # LED Triggers obj-$(CONFIG_LEDS_TRIGGERS) += trigger/ diff --git a/drivers/leds/rgb/leds-qcom-lpg.c b/drivers/leds/rgb/leds-qcom-lpg.c new file mode 100644 index 000000000000..c68233b43c2c --- /dev/null +++ b/drivers/leds/rgb/leds-qcom-lpg.c @@ -0,0 +1,1286 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2021 Linaro Ltd + * Copyright (c) 2010-2012, The Linux Foundation. All rights reserved. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LPG_PATTERN_CONFIG_REG 0x40 +#define LPG_SIZE_CLK_REG 0x41 +#define LPG_PREDIV_CLK_REG 0x42 +#define PWM_TYPE_CONFIG_REG 0x43 +#define PWM_VALUE_REG 0x44 +#define PWM_ENABLE_CONTROL_REG 0x46 +#define PWM_SYNC_REG 0x47 +#define LPG_RAMP_DURATION_REG 0x50 +#define LPG_HI_PAUSE_REG 0x52 +#define LPG_LO_PAUSE_REG 0x54 +#define LPG_HI_IDX_REG 0x56 +#define LPG_LO_IDX_REG 0x57 +#define PWM_SEC_ACCESS_REG 0xd0 +#define PWM_DTEST_REG(x) (0xe2 + (x) - 1) + +#define TRI_LED_SRC_SEL 0x45 +#define TRI_LED_EN_CTL 0x46 +#define TRI_LED_ATC_CTL 0x47 + +#define LPG_LUT_REG(x) (0x40 + (x) * 2) +#define RAMP_CONTROL_REG 0xc8 + +struct lpg_channel; +struct lpg_data; + +/** + * struct lpg - LPG device context + * @dev: struct device for LPG device + * @map: regmap for register access + * @pwm: PWM-chip object, if operating in PWM mode + * @lut_base: base address of the LUT block (optional) + * @lut_size: number of entries in the LUT block + * @lut_bitmap: allocation bitmap for LUT entries + * @triled_base: base address of the TRILED block (optional) + * @triled_src: power-source for the TRILED + * @triled_has_atc_ctl: true if there is TRI_LED_ATC_CTL register + * @triled_has_src_sel: true if there is TRI_LED_SRC_SEL register + * @channels: list of PWM channels + * @num_channels: number of @channels + */ +struct lpg { + struct device *dev; + struct regmap *map; + + struct pwm_chip pwm; + + const struct lpg_data *data; + + u32 lut_base; + u32 lut_size; + unsigned long *lut_bitmap; + + u32 triled_base; + u32 triled_src; + bool triled_has_atc_ctl; + bool triled_has_src_sel; + + struct lpg_channel *channels; + unsigned int num_channels; +}; + +/** + * struct lpg_channel - per channel data + * @lpg: reference to parent lpg + * @base: base address of the PWM channel + * @triled_mask: mask in TRILED to enable this channel + * @lut_mask: mask in LUT to start pattern generator for this channel + * @in_use: channel is exposed to LED framework + * @color: color of the LED attached to this channel + * @dtest_line: DTEST line for output, or 0 if disabled + * @dtest_value: DTEST line configuration + * @pwm_value: duty (in microseconds) of the generated pulses, overridden by LUT + * @enabled: output enabled? + * @period_us: period (in microseconds) of the generated pulses + * @pwm_size: resolution of the @pwm_value, 6 or 9 bits + * @clk: base frequency of the clock generator + * @pre_div: divider of @clk + * @pre_div_exp: exponential divider of @clk + * @ramp_enabled: duty cycle is driven by iterating over lookup table + * @ramp_ping_pong: reverse through pattern, rather than wrapping to start + * @ramp_oneshot: perform only a single pass over the pattern + * @ramp_reverse: iterate over pattern backwards + * @ramp_tick_ms: length (in milliseconds) of one step in the pattern + * @ramp_lo_pause_ms: pause (in milliseconds) before iterating over pattern + * @ramp_hi_pause_ms: pause (in milliseconds) after iterating over pattern + * @pattern_lo_idx: start index of associated pattern + * @pattern_hi_idx: last index of associated pattern + */ +struct lpg_channel { + struct lpg *lpg; + + u32 base; + unsigned int triled_mask; + unsigned int lut_mask; + + bool in_use; + + int color; + + u32 dtest_line; + u32 dtest_value; + + u16 pwm_value; + bool enabled; + + unsigned int period_us; + unsigned int pwm_size; + unsigned int clk; + unsigned int pre_div; + unsigned int pre_div_exp; + + bool ramp_enabled; + bool ramp_ping_pong; + bool ramp_oneshot; + bool ramp_reverse; + unsigned long ramp_tick_ms; + unsigned long ramp_lo_pause_ms; + unsigned long ramp_hi_pause_ms; + + unsigned int pattern_lo_idx; + unsigned int pattern_hi_idx; +}; + +/** + * struct lpg_led - logical LED object + * @lpg: lpg context reference + * @cdev: LED class device + * @mcdev: Multicolor LED class device + * @num_channels: number of @channels + * @channels: list of channels associated with the LED + */ +struct lpg_led { + struct lpg *lpg; + + struct led_classdev cdev; + struct led_classdev_mc mcdev; + + unsigned int num_channels; + struct lpg_channel *channels[]; +}; + +/** + * struct lpg_channel_data - per channel initialization data + * @base: base address for PWM channel registers + * @triled_mask: bitmask for controlling this channel in TRILED + */ +struct lpg_channel_data { + unsigned int base; + u8 triled_mask; +}; + +/** + * struct lpg_data - initialization data + * @lut_base: base address of LUT block + * @lut_size: number of entries in LUT + * @triled_base: base address of TRILED + * @triled_has_atc_ctl: true if there is TRI_LED_ATC_CTL register + * @triled_has_src_sel: true if there is TRI_LED_SRC_SEL register + * @pwm_9bit_mask: bitmask for switching from 6bit to 9bit pwm + * @num_channels: number of channels in LPG + * @channels: list of channel initialization data + */ +struct lpg_data { + unsigned int lut_base; + unsigned int lut_size; + unsigned int triled_base; + bool triled_has_atc_ctl; + bool triled_has_src_sel; + unsigned int pwm_9bit_mask; + int num_channels; + struct lpg_channel_data *channels; +}; + +static int triled_set(struct lpg *lpg, unsigned int mask, unsigned int enable) +{ + /* Skip if we don't have a triled block */ + if (!lpg->triled_base) + return 0; + + return regmap_update_bits(lpg->map, lpg->triled_base + TRI_LED_EN_CTL, + mask, enable); +} + +static int lpg_lut_store(struct lpg *lpg, struct led_pattern *pattern, + size_t len, unsigned int *lo_idx, unsigned int *hi_idx) +{ + unsigned int idx; + u16 val; + int i; + + /* Hardware does not behave when LO_IDX == HI_IDX */ + if (len == 1) + return -EINVAL; + + idx = bitmap_find_next_zero_area(lpg->lut_bitmap, lpg->lut_size, + 0, len, 0); + if (idx >= lpg->lut_size) + return -ENOMEM; + + for (i = 0; i < len; i++) { + val = pattern[i].brightness; + + regmap_bulk_write(lpg->map, lpg->lut_base + LPG_LUT_REG(idx + i), &val, 1); + } + + bitmap_set(lpg->lut_bitmap, idx, len); + + *lo_idx = idx; + *hi_idx = idx + len - 1; + + return 0; +} + +static void lpg_lut_free(struct lpg *lpg, unsigned int lo_idx, unsigned int hi_idx) +{ + int len; + + if (lo_idx == hi_idx) + return; + + len = hi_idx - lo_idx + 1; + bitmap_clear(lpg->lut_bitmap, lo_idx, len); +} + +static int lpg_lut_sync(struct lpg *lpg, unsigned int mask) +{ + return regmap_write(lpg->map, lpg->lut_base + RAMP_CONTROL_REG, mask); +} + +#define NUM_PWM_PREDIV 4 +#define NUM_PWM_CLK 3 +#define NUM_EXP 7 + +static const unsigned int lpg_clk_table[NUM_PWM_PREDIV][NUM_PWM_CLK] = { + { + 1 * (NSEC_PER_SEC / 1024), + 1 * (NSEC_PER_SEC / 32768), + 1 * (NSEC_PER_SEC / 19200000), + }, + { + 3 * (NSEC_PER_SEC / 1024), + 3 * (NSEC_PER_SEC / 32768), + 3 * (NSEC_PER_SEC / 19200000), + }, + { + 5 * (NSEC_PER_SEC / 1024), + 5 * (NSEC_PER_SEC / 32768), + 5 * (NSEC_PER_SEC / 19200000), + }, + { + 6 * (NSEC_PER_SEC / 1024), + 6 * (NSEC_PER_SEC / 32768), + 6 * (NSEC_PER_SEC / 19200000), + }, +}; + +/* + * PWM Frequency = Clock Frequency / (N * T) + * or + * PWM Period = Clock Period * (N * T) + * where + * N = 2^9 or 2^6 for 9-bit or 6-bit PWM size + * T = Pre-divide * 2^m, where m = 0..7 (exponent) + * + * This is the formula to figure out m for the best pre-divide and clock: + * (PWM Period / N) = (Pre-divide * Clock Period) * 2^m + */ +static void lpg_calc_freq(struct lpg_channel *chan, unsigned int period_us) +{ + int n, m, clk, div; + int best_m, best_div, best_clk; + unsigned int last_err, cur_err, min_err; + unsigned int tmp_p, period_n; + + if (period_us == chan->period_us) + return; + + /* PWM Period / N */ + if (period_us < UINT_MAX / NSEC_PER_USEC) + n = 6; + else + n = 9; + + period_n = ((u64)period_us * NSEC_PER_USEC) >> n; + + min_err = UINT_MAX; + last_err = UINT_MAX; + best_m = 0; + best_clk = 0; + best_div = 0; + for (clk = 0; clk < NUM_PWM_CLK; clk++) { + for (div = 0; div < NUM_PWM_PREDIV; div++) { + /* period_n = (PWM Period / N) */ + /* tmp_p = (Pre-divide * Clock Period) * 2^m */ + tmp_p = lpg_clk_table[div][clk]; + for (m = 0; m <= NUM_EXP; m++) { + cur_err = abs(period_n - tmp_p); + if (cur_err < min_err) { + min_err = cur_err; + best_m = m; + best_clk = clk; + best_div = div; + } + + if (m && cur_err > last_err) + /* Break for bigger cur_err */ + break; + + last_err = cur_err; + tmp_p <<= 1; + } + } + } + + /* Use higher resolution */ + if (best_m >= 3 && n == 6) { + n += 3; + best_m -= 3; + } + + chan->clk = best_clk; + chan->pre_div = best_div; + chan->pre_div_exp = best_m; + chan->pwm_size = n; + + chan->period_us = period_us; +} + +static void lpg_calc_duty(struct lpg_channel *chan, unsigned int duty_us) +{ + unsigned int max = (1 << chan->pwm_size) - 1; + unsigned int val = div_u64((u64)duty_us << chan->pwm_size, chan->period_us); + + chan->pwm_value = min(val, max); +} + +static void lpg_apply_freq(struct lpg_channel *chan) +{ + unsigned long val; + struct lpg *lpg = chan->lpg; + + if (!chan->enabled) + return; + + /* Clock register values are off-by-one from lpg_clk_table */ + val = chan->clk + 1; + + if (chan->pwm_size == 9) + val |= lpg->data->pwm_9bit_mask; + + regmap_write(lpg->map, chan->base + LPG_SIZE_CLK_REG, val); + + val = chan->pre_div << 5 | chan->pre_div_exp; + regmap_write(lpg->map, chan->base + LPG_PREDIV_CLK_REG, val); +} + +#define LPG_ENABLE_GLITCH_REMOVAL BIT(5) + +static void lpg_enable_glitch(struct lpg_channel *chan) +{ + struct lpg *lpg = chan->lpg; + + regmap_update_bits(lpg->map, chan->base + PWM_TYPE_CONFIG_REG, + LPG_ENABLE_GLITCH_REMOVAL, 0); +} + +static void lpg_disable_glitch(struct lpg_channel *chan) +{ + struct lpg *lpg = chan->lpg; + + regmap_update_bits(lpg->map, chan->base + PWM_TYPE_CONFIG_REG, + LPG_ENABLE_GLITCH_REMOVAL, + LPG_ENABLE_GLITCH_REMOVAL); +} + +static void lpg_apply_pwm_value(struct lpg_channel *chan) +{ + struct lpg *lpg = chan->lpg; + u16 val = chan->pwm_value; + + if (!chan->enabled) + return; + + regmap_bulk_write(lpg->map, chan->base + PWM_VALUE_REG, &val, 1); +} + +#define LPG_PATTERN_CONFIG_LO_TO_HI BIT(4) +#define LPG_PATTERN_CONFIG_REPEAT BIT(3) +#define LPG_PATTERN_CONFIG_TOGGLE BIT(2) +#define LPG_PATTERN_CONFIG_PAUSE_HI BIT(1) +#define LPG_PATTERN_CONFIG_PAUSE_LO BIT(0) + +static void lpg_apply_lut_control(struct lpg_channel *chan) +{ + struct lpg *lpg = chan->lpg; + unsigned int hi_pause; + unsigned int lo_pause; + unsigned int step; + unsigned int conf = 0; + unsigned int lo_idx = chan->pattern_lo_idx; + unsigned int hi_idx = chan->pattern_hi_idx; + int pattern_len; + + if (!chan->ramp_enabled || chan->pattern_lo_idx == chan->pattern_hi_idx) + return; + + pattern_len = hi_idx - lo_idx + 1; + + step = chan->ramp_tick_ms; + hi_pause = DIV_ROUND_UP(chan->ramp_hi_pause_ms, step); + lo_pause = DIV_ROUND_UP(chan->ramp_lo_pause_ms, step); + + if (!chan->ramp_reverse) + conf |= LPG_PATTERN_CONFIG_LO_TO_HI; + if (!chan->ramp_oneshot) + conf |= LPG_PATTERN_CONFIG_REPEAT; + if (chan->ramp_ping_pong) + conf |= LPG_PATTERN_CONFIG_TOGGLE; + if (chan->ramp_hi_pause_ms) + conf |= LPG_PATTERN_CONFIG_PAUSE_HI; + if (chan->ramp_lo_pause_ms) + conf |= LPG_PATTERN_CONFIG_PAUSE_LO; + + regmap_write(lpg->map, chan->base + LPG_PATTERN_CONFIG_REG, conf); + regmap_write(lpg->map, chan->base + LPG_HI_IDX_REG, hi_idx); + regmap_write(lpg->map, chan->base + LPG_LO_IDX_REG, lo_idx); + + regmap_write(lpg->map, chan->base + LPG_RAMP_DURATION_REG, step); + regmap_write(lpg->map, chan->base + LPG_HI_PAUSE_REG, hi_pause); + regmap_write(lpg->map, chan->base + LPG_LO_PAUSE_REG, lo_pause); +} + +#define LPG_ENABLE_CONTROL_OUTPUT BIT(7) +#define LPG_ENABLE_CONTROL_BUFFER_TRISTATE BIT(5) +#define LPG_ENABLE_CONTROL_SRC_PWM BIT(2) +#define LPG_ENABLE_CONTROL_RAMP_GEN BIT(1) + +static void lpg_apply_control(struct lpg_channel *chan) +{ + unsigned int ctrl; + struct lpg *lpg = chan->lpg; + + ctrl = LPG_ENABLE_CONTROL_BUFFER_TRISTATE; + + if (chan->enabled) + ctrl |= LPG_ENABLE_CONTROL_OUTPUT; + + if (chan->pattern_lo_idx != chan->pattern_hi_idx) + ctrl |= LPG_ENABLE_CONTROL_RAMP_GEN; + else + ctrl |= LPG_ENABLE_CONTROL_SRC_PWM; + + regmap_write(lpg->map, chan->base + PWM_ENABLE_CONTROL_REG, ctrl); + + /* + * Due to LPG hardware bug, in the PWM mode, having enabled PWM, + * We have to write PWM values one more time. + */ + if (chan->enabled) + lpg_apply_pwm_value(chan); +} + +#define LPG_SYNC_PWM BIT(0) + +static void lpg_apply_sync(struct lpg_channel *chan) +{ + struct lpg *lpg = chan->lpg; + + regmap_write(lpg->map, chan->base + PWM_SYNC_REG, LPG_SYNC_PWM); +} + +static int lpg_parse_dtest(struct lpg *lpg) +{ + struct lpg_channel *chan; + struct device_node *np = lpg->dev->of_node; + int count; + int ret; + int i; + + count = of_property_count_u32_elems(np, "qcom,dtest"); + if (count == -EINVAL) { + return 0; + } else if (count < 0) { + ret = count; + goto err_malformed; + } else if (count != lpg->data->num_channels * 2) { + dev_err(lpg->dev, "qcom,dtest needs to be %d items\n", + lpg->data->num_channels * 2); + return -EINVAL; + } + + for (i = 0; i < lpg->data->num_channels; i++) { + chan = &lpg->channels[i]; + + ret = of_property_read_u32_index(np, "qcom,dtest", i * 2, + &chan->dtest_line); + if (ret) + goto err_malformed; + + ret = of_property_read_u32_index(np, "qcom,dtest", i * 2 + 1, + &chan->dtest_value); + if (ret) + goto err_malformed; + } + + return 0; + +err_malformed: + dev_err(lpg->dev, "malformed qcom,dtest\n"); + return ret; +} + +static void lpg_apply_dtest(struct lpg_channel *chan) +{ + struct lpg *lpg = chan->lpg; + + if (!chan->dtest_line) + return; + + regmap_write(lpg->map, chan->base + PWM_SEC_ACCESS_REG, 0xa5); + regmap_write(lpg->map, chan->base + PWM_DTEST_REG(chan->dtest_line), + chan->dtest_value); +} + +static void lpg_apply(struct lpg_channel *chan) +{ + lpg_disable_glitch(chan); + lpg_apply_freq(chan); + lpg_apply_pwm_value(chan); + lpg_apply_control(chan); + lpg_apply_sync(chan); + lpg_apply_lut_control(chan); + lpg_enable_glitch(chan); +} + +static void lpg_brightness_set(struct lpg_led *led, struct led_classdev *cdev, + struct mc_subled *subleds) +{ + enum led_brightness brightness; + struct lpg_channel *chan; + unsigned int triled_enabled = 0; + unsigned int triled_mask = 0; + unsigned int lut_mask = 0; + unsigned int duty_us; + struct lpg *lpg = led->lpg; + int i; + + for (i = 0; i < led->num_channels; i++) { + chan = led->channels[i]; + brightness = subleds[i].brightness; + + if (brightness == LED_OFF) { + chan->enabled = false; + chan->ramp_enabled = false; + } else if (chan->pattern_lo_idx != chan->pattern_hi_idx) { + lpg_calc_freq(chan, NSEC_PER_USEC); + + chan->enabled = true; + chan->ramp_enabled = true; + + lut_mask |= chan->lut_mask; + triled_enabled |= chan->triled_mask; + } else { + lpg_calc_freq(chan, NSEC_PER_USEC); + + duty_us = brightness * chan->period_us / cdev->max_brightness; + lpg_calc_duty(chan, duty_us); + chan->enabled = true; + chan->ramp_enabled = false; + + triled_enabled |= chan->triled_mask; + } + + triled_mask |= chan->triled_mask; + + lpg_apply(chan); + } + + /* Toggle triled lines */ + if (triled_mask) + triled_set(lpg, triled_mask, triled_enabled); + + /* Trigger start of ramp generator(s) */ + if (lut_mask) + lpg_lut_sync(lpg, lut_mask); +} + +static void lpg_brightness_single_set(struct led_classdev *cdev, + enum led_brightness value) +{ + struct lpg_led *led = container_of(cdev, struct lpg_led, cdev); + struct mc_subled info; + + info.brightness = value; + lpg_brightness_set(led, cdev, &info); +} + +static void lpg_brightness_mc_set(struct led_classdev *cdev, + enum led_brightness value) +{ + struct led_classdev_mc *mc = lcdev_to_mccdev(cdev); + struct lpg_led *led = container_of(mc, struct lpg_led, mcdev); + + led_mc_calc_color_components(mc, value); + lpg_brightness_set(led, cdev, mc->subled_info); +} + +static int lpg_blink_set(struct lpg_led *led, + unsigned long *delay_on, unsigned long *delay_off) +{ + struct lpg_channel *chan; + unsigned int period_us; + unsigned int duty_us; + int i; + + if (!*delay_on && !*delay_off) { + *delay_on = 500; + *delay_off = 500; + } + + duty_us = *delay_on * USEC_PER_MSEC; + period_us = (*delay_on + *delay_off) * USEC_PER_MSEC; + + for (i = 0; i < led->num_channels; i++) { + chan = led->channels[i]; + + lpg_calc_freq(chan, period_us); + lpg_calc_duty(chan, duty_us); + + chan->enabled = true; + chan->ramp_enabled = false; + + lpg_apply(chan); + } + + return 0; +} + +static int lpg_blink_single_set(struct led_classdev *cdev, + unsigned long *delay_on, unsigned long *delay_off) +{ + struct lpg_led *led = container_of(cdev, struct lpg_led, cdev); + + return lpg_blink_set(led, delay_on, delay_off); +} + +static int lpg_blink_mc_set(struct led_classdev *cdev, + unsigned long *delay_on, unsigned long *delay_off) +{ + struct led_classdev_mc *mc = lcdev_to_mccdev(cdev); + struct lpg_led *led = container_of(mc, struct lpg_led, mcdev); + + return lpg_blink_set(led, delay_on, delay_off); +} + +static int lpg_pattern_set(struct lpg_led *led, struct led_pattern *pattern, + u32 len, int repeat) +{ + struct lpg_channel *chan; + struct lpg *lpg = led->lpg; + unsigned int hi_pause; + unsigned int lo_pause; + unsigned int lo_idx; + unsigned int hi_idx; + bool ping_pong = true; + int brightness_a; + int brightness_b; + int ret; + int i; + + /* Only support oneshot or indefinite loops, due to limited pattern space */ + if (repeat != -1 && repeat != 1) + return -EINVAL; + + /* + * The LPG plays patterns with at a fixed pace, a "low pause" can be + * performed before the pattern and a "high pause" after. In order to + * save space the pattern can be played in "ping pong" mode, in which + * the pattern is first played forward, then "high pause" is applied, + * then the pattern is played backwards and finally the "low pause" is + * applied. + * + * The delta_t of the first entry is used to determine the pace of the + * pattern. + * + * If the specified pattern is a palindrome the ping pong mode is + * enabled. In this scenario the delta_t of the last entry determines + * the "low pause" time and the delta_t of the middle entry (i.e. the + * last in the programmed pattern) determines the "high pause". If the + * pattern consists of an odd number of values, no "high pause" is + * used. + * + * When ping pong mode is not selected, the delta_t of the last entry + * is used as "high pause". No "low pause" is used. + * + * delta_t of any other members of the pattern is ignored. + */ + + /* Detect palindromes and use "ping pong" to reduce LUT usage */ + for (i = 0; i < len / 2; i++) { + brightness_a = pattern[i].brightness; + brightness_b = pattern[len - i - 1].brightness; + + if (brightness_a != brightness_b) { + ping_pong = false; + break; + } + } + + if (ping_pong) { + if (len % 2) + hi_pause = 0; + else + hi_pause = pattern[(len + 1) / 2].delta_t; + lo_pause = pattern[len - 1].delta_t; + + len = (len + 1) / 2; + } else { + hi_pause = pattern[len - 1].delta_t; + lo_pause = 0; + } + + ret = lpg_lut_store(lpg, pattern, len, &lo_idx, &hi_idx); + if (ret < 0) + return ret; + + for (i = 0; i < led->num_channels; i++) { + chan = led->channels[i]; + + chan->ramp_tick_ms = pattern[0].delta_t; + chan->ramp_ping_pong = ping_pong; + chan->ramp_oneshot = repeat != -1; + + chan->ramp_lo_pause_ms = lo_pause; + chan->ramp_hi_pause_ms = hi_pause; + + chan->pattern_lo_idx = lo_idx; + chan->pattern_hi_idx = hi_idx; + } + + return 0; +} + +static int lpg_pattern_single_set(struct led_classdev *cdev, + struct led_pattern *pattern, u32 len, + int repeat) +{ + struct lpg_led *led = container_of(cdev, struct lpg_led, cdev); + int ret; + + ret = lpg_pattern_set(led, pattern, len, repeat); + if (ret < 0) + return ret; + + lpg_brightness_single_set(cdev, LED_FULL); + + return 0; +} + +static int lpg_pattern_mc_set(struct led_classdev *cdev, + struct led_pattern *pattern, u32 len, + int repeat) +{ + struct led_classdev_mc *mc = lcdev_to_mccdev(cdev); + struct lpg_led *led = container_of(mc, struct lpg_led, mcdev); + int ret; + + ret = lpg_pattern_set(led, pattern, len, repeat); + if (ret < 0) + return ret; + + led_mc_calc_color_components(mc, LED_FULL); + lpg_brightness_set(led, cdev, mc->subled_info); + + return 0; +} + +static int lpg_pattern_clear(struct lpg_led *led) +{ + struct lpg_channel *chan; + struct lpg *lpg = led->lpg; + int i; + + chan = led->channels[0]; + lpg_lut_free(lpg, chan->pattern_lo_idx, chan->pattern_hi_idx); + + for (i = 0; i < led->num_channels; i++) { + chan = led->channels[i]; + chan->pattern_lo_idx = 0; + chan->pattern_hi_idx = 0; + } + + return 0; +} + +static int lpg_pattern_single_clear(struct led_classdev *cdev) +{ + struct lpg_led *led = container_of(cdev, struct lpg_led, cdev); + + return lpg_pattern_clear(led); +} + +static int lpg_pattern_mc_clear(struct led_classdev *cdev) +{ + struct led_classdev_mc *mc = lcdev_to_mccdev(cdev); + struct lpg_led *led = container_of(mc, struct lpg_led, mcdev); + + return lpg_pattern_clear(led); +} + +static int lpg_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct lpg *lpg = container_of(chip, struct lpg, pwm); + struct lpg_channel *chan = &lpg->channels[pwm->hwpwm]; + + return chan->in_use ? -EBUSY : 0; +} + +static int lpg_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct lpg *lpg = container_of(chip, struct lpg, pwm); + struct lpg_channel *chan = &lpg->channels[pwm->hwpwm]; + + lpg_calc_freq(chan, div_u64(state->period, NSEC_PER_USEC)); + lpg_calc_duty(chan, div_u64(state->duty_cycle, NSEC_PER_USEC)); + chan->enabled = state->enabled; + + lpg_apply(chan); + + triled_set(lpg, chan->triled_mask, chan->enabled ? chan->triled_mask : 0); + + return 0; +} + +static const struct pwm_ops lpg_pwm_ops = { + .request = lpg_pwm_request, + .apply = lpg_pwm_apply, + .owner = THIS_MODULE, +}; + +static int lpg_add_pwm(struct lpg *lpg) +{ + int ret; + + lpg->pwm.base = -1; + lpg->pwm.dev = lpg->dev; + lpg->pwm.npwm = lpg->num_channels; + lpg->pwm.ops = &lpg_pwm_ops; + + ret = pwmchip_add(&lpg->pwm); + if (ret) + dev_err(lpg->dev, "failed to add PWM chip: ret %d\n", ret); + + return ret; +} + +static int lpg_parse_channel(struct lpg *lpg, struct device_node *np, + struct lpg_channel **channel) +{ + struct lpg_channel *chan; + u32 color = LED_COLOR_ID_GREEN; + u32 reg; + int ret; + + ret = of_property_read_u32(np, "reg", ®); + if (ret || !reg || reg > lpg->num_channels) { + dev_err(lpg->dev, "invalid reg of %pOFn\n", np); + return -EINVAL; + } + + chan = &lpg->channels[reg - 1]; + chan->in_use = true; + + ret = of_property_read_u32(np, "color", &color); + if (ret < 0 && ret != -EINVAL) { + dev_err(lpg->dev, "failed to parse \"color\" of %pOF\n", np); + return ret; + } + + chan->color = color; + + *channel = chan; + + return 0; +} + +static int lpg_add_led(struct lpg *lpg, struct device_node *np) +{ + struct led_classdev *cdev; + struct device_node *child; + struct mc_subled *info; + struct lpg_led *led; + const char *state; + int num_channels; + u32 color = 0; + int ret; + int i; + + ret = of_property_read_u32(np, "color", &color); + if (ret < 0 && ret != -EINVAL) { + dev_err(lpg->dev, "failed to parse \"color\" of %pOF\n", np); + return ret; + } + + if (color == LED_COLOR_ID_MULTI) + num_channels = of_get_available_child_count(np); + else + num_channels = 1; + + led = devm_kzalloc(lpg->dev, struct_size(led, channels, num_channels), GFP_KERNEL); + if (!led) + return -ENOMEM; + + led->lpg = lpg; + led->num_channels = num_channels; + + if (color == LED_COLOR_ID_MULTI) { + info = devm_kcalloc(lpg->dev, num_channels, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + i = 0; + for_each_available_child_of_node(np, child) { + ret = lpg_parse_channel(lpg, child, &led->channels[i]); + if (ret < 0) + return ret; + + info[i].color_index = led->channels[i]->color; + info[i].intensity = LED_FULL; + i++; + } + + led->mcdev.subled_info = info; + led->mcdev.num_colors = num_channels; + + cdev = &led->mcdev.led_cdev; + cdev->brightness_set = lpg_brightness_mc_set; + cdev->blink_set = lpg_blink_mc_set; + + /* Register pattern accessors only if we have a LUT block */ + if (lpg->lut_base) { + cdev->pattern_set = lpg_pattern_mc_set; + cdev->pattern_clear = lpg_pattern_mc_clear; + } + } else { + ret = lpg_parse_channel(lpg, np, &led->channels[0]); + if (ret < 0) + return ret; + + cdev = &led->cdev; + cdev->brightness_set = lpg_brightness_single_set; + cdev->blink_set = lpg_blink_single_set; + + /* Register pattern accessors only if we have a LUT block */ + if (lpg->lut_base) { + cdev->pattern_set = lpg_pattern_single_set; + cdev->pattern_clear = lpg_pattern_single_clear; + } + } + + /* Use label else node name */ + cdev->name = of_get_property(np, "label", NULL) ? : np->name; + cdev->default_trigger = of_get_property(np, "linux,default-trigger", NULL); + cdev->max_brightness = 255; + + if (!of_property_read_string(np, "default-state", &state) && + !strcmp(state, "on")) + cdev->brightness = LED_FULL; + else + cdev->brightness = LED_OFF; + + cdev->brightness_set(cdev, cdev->brightness); + + if (color == LED_COLOR_ID_MULTI) + ret = devm_led_classdev_multicolor_register(lpg->dev, &led->mcdev); + else + ret = devm_led_classdev_register(lpg->dev, &led->cdev); + if (ret) + dev_err(lpg->dev, "unable to register %s\n", cdev->name); + + return ret; +} + +static int lpg_init_channels(struct lpg *lpg) +{ + const struct lpg_data *data = lpg->data; + int i; + + lpg->num_channels = data->num_channels; + lpg->channels = devm_kcalloc(lpg->dev, data->num_channels, + sizeof(struct lpg_channel), GFP_KERNEL); + if (!lpg->channels) + return -ENOMEM; + + for (i = 0; i < data->num_channels; i++) { + lpg->channels[i].lpg = lpg; + lpg->channels[i].base = data->channels[i].base; + lpg->channels[i].triled_mask = data->channels[i].triled_mask; + lpg->channels[i].lut_mask = BIT(i); + } + + return 0; +} + +static int lpg_init_triled(struct lpg *lpg) +{ + struct device_node *np = lpg->dev->of_node; + int ret; + + /* Skip initialization if we don't have a triled block */ + if (!lpg->data->triled_base) + return 0; + + lpg->triled_base = lpg->data->triled_base; + lpg->triled_has_atc_ctl = lpg->data->triled_has_atc_ctl; + lpg->triled_has_src_sel = lpg->data->triled_has_src_sel; + + if (lpg->triled_has_src_sel) { + ret = of_property_read_u32(np, "qcom,power-source", &lpg->triled_src); + if (ret || lpg->triled_src == 2 || lpg->triled_src > 3) { + dev_err(lpg->dev, "invalid power source\n"); + return -EINVAL; + } + } + + /* Disable automatic trickle charge LED */ + if (lpg->triled_has_atc_ctl) + regmap_write(lpg->map, lpg->triled_base + TRI_LED_ATC_CTL, 0); + + /* Configure power source */ + if (lpg->triled_has_src_sel) + regmap_write(lpg->map, lpg->triled_base + TRI_LED_SRC_SEL, lpg->triled_src); + + /* Default all outputs to off */ + regmap_write(lpg->map, lpg->triled_base + TRI_LED_EN_CTL, 0); + + return 0; +} + +static int lpg_init_lut(struct lpg *lpg) +{ + const struct lpg_data *data = lpg->data; + size_t bitmap_size; + + if (!data->lut_base) + return 0; + + lpg->lut_base = data->lut_base; + lpg->lut_size = data->lut_size; + + bitmap_size = BITS_TO_BYTES(lpg->lut_size); + lpg->lut_bitmap = devm_kzalloc(lpg->dev, bitmap_size, GFP_KERNEL); + if (!lpg->lut_bitmap) + return -ENOMEM; + + bitmap_clear(lpg->lut_bitmap, 0, lpg->lut_size); + + return 0; +} + +static int lpg_probe(struct platform_device *pdev) +{ + struct device_node *np; + struct lpg *lpg; + int ret; + int i; + + lpg = devm_kzalloc(&pdev->dev, sizeof(*lpg), GFP_KERNEL); + if (!lpg) + return -ENOMEM; + + lpg->data = of_device_get_match_data(&pdev->dev); + if (!lpg->data) + return -EINVAL; + + lpg->dev = &pdev->dev; + + lpg->map = dev_get_regmap(pdev->dev.parent, NULL); + if (!lpg->map) { + dev_err(&pdev->dev, "parent regmap unavailable\n"); + return -ENXIO; + } + + ret = lpg_init_channels(lpg); + if (ret < 0) + return ret; + + ret = lpg_parse_dtest(lpg); + if (ret < 0) + return ret; + + ret = lpg_init_triled(lpg); + if (ret < 0) + return ret; + + ret = lpg_init_lut(lpg); + if (ret < 0) + return ret; + + for_each_available_child_of_node(pdev->dev.of_node, np) { + ret = lpg_add_led(lpg, np); + if (ret) + return ret; + } + + for (i = 0; i < lpg->num_channels; i++) + lpg_apply_dtest(&lpg->channels[i]); + + ret = lpg_add_pwm(lpg); + if (ret) + return ret; + + platform_set_drvdata(pdev, lpg); + + return 0; +} + +static int lpg_remove(struct platform_device *pdev) +{ + struct lpg *lpg = platform_get_drvdata(pdev); + + pwmchip_remove(&lpg->pwm); + + return 0; +} + +static const struct lpg_data pm8916_pwm_data = { + .pwm_9bit_mask = BIT(2), + + .num_channels = 1, + .channels = (struct lpg_channel_data[]) { + { .base = 0xbc00 }, + }, +}; + +static const struct lpg_data pm8941_lpg_data = { + .lut_base = 0xb000, + .lut_size = 64, + + .triled_base = 0xd000, + .triled_has_atc_ctl = true, + .triled_has_src_sel = true, + + .pwm_9bit_mask = 3 << 4, + + .num_channels = 8, + .channels = (struct lpg_channel_data[]) { + { .base = 0xb100 }, + { .base = 0xb200 }, + { .base = 0xb300 }, + { .base = 0xb400 }, + { .base = 0xb500, .triled_mask = BIT(5) }, + { .base = 0xb600, .triled_mask = BIT(6) }, + { .base = 0xb700, .triled_mask = BIT(7) }, + { .base = 0xb800 }, + }, +}; + +static const struct lpg_data pm8994_lpg_data = { + .lut_base = 0xb000, + .lut_size = 64, + + .pwm_9bit_mask = 3 << 4, + + .num_channels = 6, + .channels = (struct lpg_channel_data[]) { + { .base = 0xb100 }, + { .base = 0xb200 }, + { .base = 0xb300 }, + { .base = 0xb400 }, + { .base = 0xb500 }, + { .base = 0xb600 }, + }, +}; + +static const struct lpg_data pmi8994_lpg_data = { + .lut_base = 0xb000, + .lut_size = 24, + + .triled_base = 0xd000, + .triled_has_atc_ctl = true, + .triled_has_src_sel = true, + + .pwm_9bit_mask = BIT(4), + + .num_channels = 4, + .channels = (struct lpg_channel_data[]) { + { .base = 0xb100, .triled_mask = BIT(5) }, + { .base = 0xb200, .triled_mask = BIT(6) }, + { .base = 0xb300, .triled_mask = BIT(7) }, + { .base = 0xb400 }, + }, +}; + +static const struct lpg_data pmi8998_lpg_data = { + .lut_base = 0xb000, + .lut_size = 49, + + .triled_base = 0xd000, + + .pwm_9bit_mask = BIT(4), + + .num_channels = 6, + .channels = (struct lpg_channel_data[]) { + { .base = 0xb100 }, + { .base = 0xb200 }, + { .base = 0xb300, .triled_mask = BIT(5) }, + { .base = 0xb400, .triled_mask = BIT(6) }, + { .base = 0xb500, .triled_mask = BIT(7) }, + { .base = 0xb600 }, + }, +}; + +static const struct lpg_data pm8150b_lpg_data = { + .lut_base = 0xb000, + .lut_size = 49, + + .triled_base = 0xd000, + + .pwm_9bit_mask = BIT(4), + + .num_channels = 2, + .channels = (struct lpg_channel_data[]) { + { .base = 0xb100, .triled_mask = BIT(7) }, + { .base = 0xb200, .triled_mask = BIT(6) }, + }, +}; + +static const struct lpg_data pm8150l_lpg_data = { + .lut_base = 0xb000, + .lut_size = 49, + + .triled_base = 0xd000, + + .pwm_9bit_mask = BIT(4), + + .num_channels = 5, + .channels = (struct lpg_channel_data[]) { + { .base = 0xb100, .triled_mask = BIT(7) }, + { .base = 0xb200, .triled_mask = BIT(6) }, + { .base = 0xb300, .triled_mask = BIT(5) }, + { .base = 0xbc00 }, + { .base = 0xbd00 }, + + }, +}; + +static const struct of_device_id lpg_of_table[] = { + { .compatible = "qcom,pm8150b-lpg", .data = &pm8150b_lpg_data }, + { .compatible = "qcom,pm8150l-lpg", .data = &pm8150l_lpg_data }, + { .compatible = "qcom,pm8916-pwm", .data = &pm8916_pwm_data }, + { .compatible = "qcom,pm8941-lpg", .data = &pm8941_lpg_data }, + { .compatible = "qcom,pm8994-lpg", .data = &pm8994_lpg_data }, + { .compatible = "qcom,pmi8994-lpg", .data = &pmi8994_lpg_data }, + { .compatible = "qcom,pmi8998-lpg", .data = &pmi8998_lpg_data }, + {} +}; +MODULE_DEVICE_TABLE(of, lpg_of_table); + +static struct platform_driver lpg_driver = { + .probe = lpg_probe, + .remove = lpg_remove, + .driver = { + .name = "qcom-spmi-lpg", + .of_match_table = lpg_of_table, + }, +}; +module_platform_driver(lpg_driver); + +MODULE_DESCRIPTION("Qualcomm LPG LED driver"); +MODULE_LICENSE("GPL v2"); From e8d3fd463312aab1e800a77d5779bf25476a52e3 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Thu, 29 Apr 2021 14:15:14 -0700 Subject: [PATCH 033/187] arm64: dts: qcom: Add LPG to pm8916, pm8994, pmi8994 and pmi8998 Add PWM/LPG nodes to the PMICs currently supported by the binding. Signed-off-by: Bjorn Andersson (JAMI: fixed up for v5.16-rc1) --- arch/arm64/boot/dts/qcom/pm8916.dtsi | 8 ++++++++ arch/arm64/boot/dts/qcom/pm8994.dtsi | 10 ++++++++++ arch/arm64/boot/dts/qcom/pmi8994.dtsi | 10 ++++++++++ arch/arm64/boot/dts/qcom/pmi8998.dtsi | 9 +++++++++ 4 files changed, 37 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/pm8916.dtsi b/arch/arm64/boot/dts/qcom/pm8916.dtsi index d58902432812..99bf8387bfb6 100644 --- a/arch/arm64/boot/dts/qcom/pm8916.dtsi +++ b/arch/arm64/boot/dts/qcom/pm8916.dtsi @@ -125,6 +125,14 @@ #address-cells = <1>; #size-cells = <0>; + pm8916_pwm: pwm { + compatible = "qcom,pm8916-pwm"; + + #pwm-cells = <2>; + + status = "disabled"; + }; + pm8916_vib: vibrator@c000 { compatible = "qcom,pm8916-vib"; reg = <0xc000>; diff --git a/arch/arm64/boot/dts/qcom/pm8994.dtsi b/arch/arm64/boot/dts/qcom/pm8994.dtsi index 5ab46117d737..ab342397fcd8 100644 --- a/arch/arm64/boot/dts/qcom/pm8994.dtsi +++ b/arch/arm64/boot/dts/qcom/pm8994.dtsi @@ -135,6 +135,16 @@ #address-cells = <1>; #size-cells = <0>; + pm8994_lpg: lpg { + compatible = "qcom,pm8994-lpg"; + + #address-cells = <1>; + #size-cells = <0>; + #pwm-cells = <2>; + + status = "disabled"; + }; + pm8994_spmi_regulators: regulators { compatible = "qcom,pm8994-regulators"; }; diff --git a/arch/arm64/boot/dts/qcom/pmi8994.dtsi b/arch/arm64/boot/dts/qcom/pmi8994.dtsi index 6e7c252568e6..b1b50c99ada2 100644 --- a/arch/arm64/boot/dts/qcom/pmi8994.dtsi +++ b/arch/arm64/boot/dts/qcom/pmi8994.dtsi @@ -27,6 +27,16 @@ #address-cells = <1>; #size-cells = <0>; + pmi8994_lpg: lpg { + compatible = "qcom,pmi8994-lpg"; + + #address-cells = <1>; + #size-cells = <0>; + #pwm-cells = <2>; + + status = "disabled"; + }; + pmi8994_spmi_regulators: regulators { compatible = "qcom,pmi8994-regulators"; #address-cells = <1>; diff --git a/arch/arm64/boot/dts/qcom/pmi8998.dtsi b/arch/arm64/boot/dts/qcom/pmi8998.dtsi index 0fef5f113f05..a5d50d7156cc 100644 --- a/arch/arm64/boot/dts/qcom/pmi8998.dtsi +++ b/arch/arm64/boot/dts/qcom/pmi8998.dtsi @@ -53,5 +53,14 @@ status = "disabled"; }; + pmi8998_lpg: lpg { + compatible = "qcom,pmi8998-lpg"; + + #address-cells = <1>; + #size-cells = <0>; + #pwm-cells = <2>; + + status = "disabled"; + }; }; }; From ccf540041fac7be9a9355ee094e8aec469e01b09 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Thu, 29 Apr 2021 14:15:15 -0700 Subject: [PATCH 034/187] arm64: dts: qcom: sdm845: Enable user LEDs on DB845c The DB845c has 4 "user LEDs", the last one is already supported as it's just wired to a gpio. Now that the LPG binding is in place we can wire up the other 3 LEDs as well. Signed-off-by: Bjorn Andersson --- arch/arm64/boot/dts/qcom/sdm845-db845c.dts | 24 ++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/sdm845-db845c.dts b/arch/arm64/boot/dts/qcom/sdm845-db845c.dts index 13f80a0b6faa..84b822328566 100644 --- a/arch/arm64/boot/dts/qcom/sdm845-db845c.dts +++ b/arch/arm64/boot/dts/qcom/sdm845-db845c.dts @@ -595,6 +595,30 @@ }; }; +&pmi8998_lpg { + status = "okay"; + + qcom,power-source = <1>; + + led@3 { + reg = <3>; + label = "green:user3"; + + linux,default-trigger = "heartbeat"; + default-state = "on"; + }; + + led@4 { + reg = <4>; + label = "green:user2"; + }; + + led@5 { + reg = <5>; + label = "green:user1"; + }; +}; + /* QUAT I2S Uses 4 I2S SD Lines for audio on LT9611 HDMI Bridge */ &q6afedai { qi2s@22 { From aa2fb3fd6140a8e3eeb4cc86bdef9845f70e61b0 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Thu, 29 Apr 2021 14:15:16 -0700 Subject: [PATCH 035/187] arm64: dts: qcom: pmi8994: Define MPP block The pmi8994 has 4 multi-purpose-pins, add these to the definition. Signed-off-by: Bjorn Andersson --- arch/arm64/boot/dts/qcom/pmi8994.dtsi | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/pmi8994.dtsi b/arch/arm64/boot/dts/qcom/pmi8994.dtsi index b1b50c99ada2..97e76f196723 100644 --- a/arch/arm64/boot/dts/qcom/pmi8994.dtsi +++ b/arch/arm64/boot/dts/qcom/pmi8994.dtsi @@ -19,6 +19,18 @@ interrupt-controller; #interrupt-cells = <2>; }; + + pmi8994_mpps: mpps@a000 { + compatible = "qcom,pmi8994-mpp"; + reg = <0xa000>; + gpio-controller; + gpio-ranges = <&pmi8994_mpps 0 0 4>; + #gpio-cells = <2>; + interrupts = <0 0xa0 0 IRQ_TYPE_NONE>, + <0 0xa1 0 IRQ_TYPE_NONE>, + <0 0xa2 0 IRQ_TYPE_NONE>, + <0 0xa3 0 IRQ_TYPE_NONE>; + }; }; pmic@3 { From 6c9da9c1ab19432fdf893ea993eff21bf600ae6c Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Thu, 29 Apr 2021 14:15:17 -0700 Subject: [PATCH 036/187] arm64: dts: qcom: db820c: Add user LEDs The db820c has 4 "user LEDs", all connected to the PMI8994. The first three are connected to the three current sinks provided by the TRILED and the fourth is connected to MPP2. By utilizing the DTEST bus the MPP is fed the control signal from the fourth LPG block, providing a consistent interface to the user. Signed-off-by: Bjorn Andersson (JAMI: fixed up for v5.16-rc1) --- arch/arm64/boot/dts/qcom/apq8096-db820c.dts | 49 +++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/apq8096-db820c.dts b/arch/arm64/boot/dts/qcom/apq8096-db820c.dts index d01a512634cf..cb69fa6e4589 100644 --- a/arch/arm64/boot/dts/qcom/apq8096-db820c.dts +++ b/arch/arm64/boot/dts/qcom/apq8096-db820c.dts @@ -10,6 +10,7 @@ #include "pmi8994.dtsi" #include #include +#include #include #include #include @@ -671,6 +672,54 @@ }; }; +&pmi8994_lpg { + qcom,power-source = <1>; + + pinctrl-names = "default"; + pinctrl-0 = <&pmi8994_mpp2_userled4>; + + qcom,dtest = <0 0 + 0 0 + 0 0 + 4 1>; + + status = "okay"; + + led@1 { + reg = <1>; + label = "green:user1"; + + linux,default-trigger = "heartbeat"; + default-state = "on"; + }; + + led@2 { + reg = <2>; + label = "green:user0"; + default-state = "on"; + }; + + led@3 { + reg = <3>; + label = "green:user2"; + }; + + led@4 { + reg = <4>; + label = "green:user3"; + }; +}; + +&pmi8994_mpps { + pmi8994_mpp2_userled4: mpp2-userled4-state { + pins = "mpp2"; + function = "sink"; + + output-low; + qcom,dtest = <4>; + }; +}; + &pmi8994_spmi_regulators { vdd_gfx: s2@1700 { reg = <0x1700 0x100>; From 623540b77d20b61354884336a8226e08a2644ff6 Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Thu, 8 Apr 2021 19:47:33 +0200 Subject: [PATCH 037/187] NILE/pm660l: LPG --- arch/arm64/boot/dts/qcom/pm660l.dtsi | 9 +++++ .../dts/qcom/sdm630-sony-xperia-nile.dtsi | 33 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/pm660l.dtsi b/arch/arm64/boot/dts/qcom/pm660l.dtsi index cfef42353611..636b8d398d96 100644 --- a/arch/arm64/boot/dts/qcom/pm660l.dtsi +++ b/arch/arm64/boot/dts/qcom/pm660l.dtsi @@ -65,6 +65,15 @@ #address-cells = <1>; #size-cells = <0>; + pm660l_lpg: lpg@b100 { + compatible = "qcom,pm660l-lpg"; + + #address-cells = <1>; + #size-cells = <0>; + + status = "disabled"; + }; + pm660l_wled: leds@d800 { compatible = "qcom,pm660l-wled"; reg = <0xd800 0xd900>; diff --git a/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile.dtsi b/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile.dtsi index 8fab280ad9e8..32c15f7eddb5 100644 --- a/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile.dtsi +++ b/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile.dtsi @@ -10,6 +10,7 @@ #include #include #include +#include / { /* required for bootloader to select correct board */ @@ -259,6 +260,38 @@ status = "okay"; }; +&pm660l_lpg { + qcom,power-source = <1>; + + status = "okay"; + + #address-cells = <1>; + #size-cells = <0>; + + multi-led { + color = ; + function = LED_FUNCTION_STATUS; + + #address-cells = <1>; + #size-cells = <0>; + + led@1 { + reg = <1>; + color = ; + }; + + led@2 { + reg = <2>; + color = ; + }; + + led@3 { + reg = <3>; + color = ; + }; + }; +}; + &pm660l_wled { status = "okay"; }; From c3964aae1c47706fd98aaeeba301aad725b0e9a9 Mon Sep 17 00:00:00 2001 From: Jami Kettunen Date: Sat, 20 Nov 2021 22:40:22 +0200 Subject: [PATCH 038/187] arm64: sdm660_defconfig: Add Combines all of the following from the op5/5.15 branch into a separate file: 4419531c3db8 defconfig: Leds, QCOM_LPG, and all the led triggers! 4121e0bc1cff android-recommended lies: AIO is needed for adb :| 80d6ecc10ab9 defconfig: Disco panel! e5b76990e82b arm64: defconfig: Disable legacy USB networking in favour of configfs 4835b657d60e defconfig: a508 zap, a530 firmware be3b2acd0d41 defconfig: Interconnect f5bd27247f29 defconfig; MMCC and GPUCC 660 8f263ba9515d Android defconfig 328f064e3ac0 660 defconfig --- arch/arm64/configs/sdm660_defconfig | 631 ++++++++++++++++++++++++++++ 1 file changed, 631 insertions(+) create mode 100644 arch/arm64/configs/sdm660_defconfig diff --git a/arch/arm64/configs/sdm660_defconfig b/arch/arm64/configs/sdm660_defconfig new file mode 100644 index 000000000000..4b3ad71efd6d --- /dev/null +++ b/arch/arm64/configs/sdm660_defconfig @@ -0,0 +1,631 @@ +CONFIG_POSIX_MQUEUE=y +CONFIG_AUDIT=y +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_BPF_SYSCALL=y +CONFIG_BPF_JIT=y +CONFIG_BPF_JIT_ALWAYS_ON=y +CONFIG_PREEMPT=y +CONFIG_IRQ_TIME_ACCOUNTING=y +CONFIG_BSD_PROCESS_ACCT=y +CONFIG_BSD_PROCESS_ACCT_V3=y +CONFIG_TASKSTATS=y +CONFIG_TASK_DELAY_ACCT=y +CONFIG_TASK_XACCT=y +CONFIG_TASK_IO_ACCOUNTING=y +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_MEMCG=y +CONFIG_BLK_CGROUP=y +CONFIG_RT_GROUP_SCHED=y +CONFIG_CGROUP_PIDS=y +CONFIG_CGROUP_FREEZER=y +CONFIG_CGROUP_HUGETLB=y +CONFIG_CGROUP_DEVICE=y +CONFIG_CGROUP_CPUACCT=y +CONFIG_CGROUP_PERF=y +CONFIG_CGROUP_BPF=y +CONFIG_CGROUP_DEBUG=y +CONFIG_SCHED_AUTOGROUP=y +CONFIG_BOOT_CONFIG=y +# CONFIG_FHANDLE is not set +CONFIG_KALLSYMS_ALL=y +CONFIG_EMBEDDED=y +# CONFIG_COMPAT_BRK is not set +CONFIG_PROFILING=y +CONFIG_ARCH_QCOM=y +CONFIG_ARM64_VA_BITS_48=y +CONFIG_SCHED_MC=y +CONFIG_NR_CPUS=4 +CONFIG_HZ_100=y +CONFIG_KEXEC=y +CONFIG_CRASH_DUMP=y +CONFIG_ARM64_SW_TTBR0_PAN=y +CONFIG_COMPAT=y +CONFIG_ARMV8_DEPRECATED=y +CONFIG_SWP_EMULATION=y +CONFIG_CP15_BARRIER_EMULATION=y +CONFIG_SETEND_EMULATION=y +CONFIG_RANDOMIZE_BASE=y +# CONFIG_EFI is not set +CONFIG_HIBERNATION=y +CONFIG_PM_AUTOSLEEP=y +CONFIG_PM_WAKELOCKS=y +CONFIG_PM_WAKELOCKS_LIMIT=0 +# CONFIG_PM_WAKELOCKS_GC is not set +CONFIG_PM_DEBUG=y +CONFIG_WQ_POWER_EFFICIENT_DEFAULT=y +CONFIG_ENERGY_MODEL=y +CONFIG_CPU_IDLE=y +CONFIG_ARM_CPUIDLE=y +CONFIG_ARM_PSCI_CPUIDLE=y +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_STAT=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y +CONFIG_CPUFREQ_DT=y +CONFIG_ARM_QCOM_CPUFREQ_NVMEM=y +CONFIG_ARM_QCOM_CPUFREQ_HW=y +CONFIG_QCOM_SCM_DOWNLOAD_MODE_DEFAULT=y +CONFIG_ARM64_CRYPTO=y +CONFIG_CRYPTO_SHA1_ARM64_CE=y +CONFIG_CRYPTO_SHA2_ARM64_CE=y +CONFIG_CRYPTO_SHA512_ARM64_CE=y +CONFIG_CRYPTO_SHA3_ARM64=y +CONFIG_CRYPTO_SM3_ARM64_CE=y +CONFIG_CRYPTO_SM4_ARM64_CE=y +CONFIG_CRYPTO_GHASH_ARM64_CE=y +CONFIG_CRYPTO_AES_ARM64_CE_CCM=y +CONFIG_CRYPTO_AES_ARM64_CE_BLK=y +CONFIG_CRYPTO_POLY1305_NEON=y +CONFIG_CRYPTO_NHPOLY1305_NEON=y +CONFIG_CRYPTO_AES_ARM64_BS=y +CONFIG_JUMP_LABEL=y +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_MODVERSIONS=y +CONFIG_MODULE_SRCVERSION_ALL=y +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +CONFIG_KSM=y +CONFIG_MEMORY_FAILURE=y +CONFIG_TRANSPARENT_HUGEPAGE=y +CONFIG_FRONTSWAP=y +CONFIG_CMA=y +CONFIG_ZSMALLOC=y +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_XFRM_USER=y +CONFIG_NET_KEY=y +CONFIG_INET=y +CONFIG_IP_MULTICAST=y +CONFIG_IP_ADVANCED_ROUTER=y +CONFIG_IP_MULTIPLE_TABLES=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +CONFIG_IP_PNP_BOOTP=y +CONFIG_INET_ESP=y +CONFIG_INET_DIAG_DESTROY=y +CONFIG_IPV6_ROUTER_PREF=y +CONFIG_IPV6_ROUTE_INFO=y +CONFIG_IPV6_OPTIMISTIC_DAD=y +CONFIG_INET6_AH=y +CONFIG_INET6_ESP=y +CONFIG_INET6_IPCOMP=y +CONFIG_IPV6_MIP6=y +CONFIG_IPV6_MULTIPLE_TABLES=y +CONFIG_NETFILTER=y +CONFIG_NF_CONNTRACK=y +CONFIG_NF_CONNTRACK_SECMARK=y +CONFIG_NF_CONNTRACK_EVENTS=y +CONFIG_NF_CONNTRACK_AMANDA=y +CONFIG_NF_CONNTRACK_FTP=y +CONFIG_NF_CONNTRACK_H323=y +CONFIG_NF_CONNTRACK_IRC=y +CONFIG_NF_CONNTRACK_NETBIOS_NS=y +CONFIG_NF_CONNTRACK_PPTP=y +CONFIG_NF_CONNTRACK_SANE=y +CONFIG_NF_CONNTRACK_TFTP=y +CONFIG_NF_CT_NETLINK=y +CONFIG_NETFILTER_XT_TARGET_CHECKSUM=y +CONFIG_NETFILTER_XT_TARGET_CLASSIFY=y +CONFIG_NETFILTER_XT_TARGET_CONNMARK=y +CONFIG_NETFILTER_XT_TARGET_CONNSECMARK=y +CONFIG_NETFILTER_XT_TARGET_IDLETIMER=y +CONFIG_NETFILTER_XT_TARGET_LOG=y +CONFIG_NETFILTER_XT_TARGET_MARK=y +CONFIG_NETFILTER_XT_TARGET_NFLOG=y +CONFIG_NETFILTER_XT_TARGET_NFQUEUE=y +CONFIG_NETFILTER_XT_TARGET_TPROXY=y +CONFIG_NETFILTER_XT_TARGET_TRACE=y +CONFIG_NETFILTER_XT_TARGET_SECMARK=y +CONFIG_NETFILTER_XT_TARGET_TCPMSS=y +CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=y +CONFIG_NETFILTER_XT_MATCH_COMMENT=y +CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=y +CONFIG_NETFILTER_XT_MATCH_CONNMARK=y +CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y +CONFIG_NETFILTER_XT_MATCH_HASHLIMIT=y +CONFIG_NETFILTER_XT_MATCH_HELPER=y +CONFIG_NETFILTER_XT_MATCH_IPRANGE=y +CONFIG_NETFILTER_XT_MATCH_LENGTH=y +CONFIG_NETFILTER_XT_MATCH_LIMIT=y +CONFIG_NETFILTER_XT_MATCH_MAC=y +CONFIG_NETFILTER_XT_MATCH_MARK=y +CONFIG_NETFILTER_XT_MATCH_POLICY=y +CONFIG_NETFILTER_XT_MATCH_PKTTYPE=y +CONFIG_NETFILTER_XT_MATCH_QUOTA=y +CONFIG_NETFILTER_XT_MATCH_SOCKET=y +CONFIG_NETFILTER_XT_MATCH_STATE=y +CONFIG_NETFILTER_XT_MATCH_STATISTIC=y +CONFIG_NETFILTER_XT_MATCH_STRING=y +CONFIG_NETFILTER_XT_MATCH_TIME=y +CONFIG_NETFILTER_XT_MATCH_U32=y +CONFIG_IP_NF_IPTABLES=y +CONFIG_IP_NF_MATCH_AH=y +CONFIG_IP_NF_MATCH_ECN=y +CONFIG_IP_NF_MATCH_TTL=y +CONFIG_IP_NF_FILTER=y +CONFIG_IP_NF_TARGET_REJECT=y +CONFIG_IP_NF_NAT=y +CONFIG_IP_NF_TARGET_MASQUERADE=y +CONFIG_IP_NF_TARGET_NETMAP=y +CONFIG_IP_NF_TARGET_REDIRECT=y +CONFIG_IP_NF_MANGLE=y +CONFIG_IP_NF_RAW=y +CONFIG_IP_NF_SECURITY=y +CONFIG_IP_NF_ARPTABLES=y +CONFIG_IP_NF_ARPFILTER=y +CONFIG_IP_NF_ARP_MANGLE=y +CONFIG_IP6_NF_IPTABLES=y +CONFIG_IP6_NF_FILTER=y +CONFIG_IP6_NF_TARGET_REJECT=y +CONFIG_IP6_NF_MANGLE=y +CONFIG_IP6_NF_RAW=y +CONFIG_IP6_NF_NAT=y +CONFIG_IP6_NF_TARGET_MASQUERADE=y +CONFIG_BRIDGE=y +CONFIG_BRIDGE_VLAN_FILTERING=y +CONFIG_VLAN_8021Q=y +CONFIG_VLAN_8021Q_GVRP=y +CONFIG_VLAN_8021Q_MVRP=y +CONFIG_NET_SCHED=y +CONFIG_NET_SCH_HTB=y +CONFIG_NET_CLS_U32=y +CONFIG_NET_EMATCH=y +CONFIG_NET_EMATCH_U32=y +CONFIG_NET_CLS_ACT=y +CONFIG_QRTR=y +CONFIG_QRTR_SMD=y +CONFIG_QRTR_TUN=y +CONFIG_BT=y +CONFIG_BT_HIDP=y +CONFIG_BT_LEDS=y +CONFIG_BT_HCIBTUSB=y +CONFIG_BT_HCIUART=y +CONFIG_BT_HCIUART_QCA=y +CONFIG_BT_QCOMSMD=y +CONFIG_CFG80211=y +CONFIG_MAC80211=y +CONFIG_MAC80211_LEDS=y +CONFIG_RFKILL=y +CONFIG_NFC=y +CONFIG_NFC_NCI=y +CONFIG_NFC_NXP_NCI=y +CONFIG_NFC_NXP_NCI_I2C=y +CONFIG_NFC_S3FWRN5_I2C=y +CONFIG_PCI=y +CONFIG_PCIE_QCOM=y +CONFIG_UEVENT_HELPER=y +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +CONFIG_EXTRA_FIRMWARE="qcom/a508_zap.b00 qcom/a508_zap.b01 qcom/a508_zap.b02 qcom/a508_zap.elf qcom/a508_zap.mdt qcom/a530_gpmu.fw2 qcom/a530_pfp.fw qcom/a530_pm4.fw" +CONFIG_EXTRA_FIRMWARE_DIR="../firmware/" +CONFIG_FW_LOADER_USER_HELPER=y +CONFIG_FW_LOADER_USER_HELPER_FALLBACK=y +CONFIG_FW_LOADER_COMPRESS=y +# CONFIG_QCOM_EBI2 is not set +CONFIG_OF_OVERLAY=y +CONFIG_ZRAM=y +CONFIG_ZRAM_WRITEBACK=y +CONFIG_ZRAM_MEMORY_TRACKING=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_RAM_SIZE=8192 +CONFIG_QCOM_COINCELL=y +CONFIG_QCOM_FASTRPC=y +CONFIG_MD=y +CONFIG_BLK_DEV_DM=y +CONFIG_DM_CRYPT=y +CONFIG_DM_UEVENT=y +CONFIG_DM_VERITY=y +CONFIG_DM_VERITY_FEC=y +CONFIG_NETDEVICES=y +CONFIG_MACVLAN=y +CONFIG_MACVTAP=y +CONFIG_VXLAN=y +CONFIG_TUN=y +CONFIG_VETH=y +CONFIG_QCOM_IPA=y +CONFIG_PPP=y +CONFIG_PPP_BSDCOMP=y +CONFIG_PPP_DEFLATE=y +CONFIG_PPP_FILTER=y +CONFIG_PPP_MPPE=y +CONFIG_PPP_ASYNC=y +CONFIG_USB_USBNET=y +CONFIG_ATH10K=y +CONFIG_ATH10K_SNOC=y +CONFIG_WCN36XX=y +CONFIG_INPUT_MATRIXKMAP=y +CONFIG_INPUT_EVDEV=y +# CONFIG_KEYBOARD_ATKBD is not set +CONFIG_KEYBOARD_GPIO=y +CONFIG_KEYBOARD_TM2_TOUCHKEY=y +# CONFIG_INPUT_MOUSE is not set +CONFIG_INPUT_JOYSTICK=y +CONFIG_JOYSTICK_XPAD=y +CONFIG_JOYSTICK_XPAD_FF=y +CONFIG_JOYSTICK_XPAD_LEDS=y +CONFIG_INPUT_TABLET=y +CONFIG_TABLET_USB_ACECAD=y +CONFIG_TABLET_USB_AIPTEK=y +CONFIG_TABLET_USB_HANWANG=y +CONFIG_TABLET_USB_KBTAB=y +CONFIG_INPUT_TOUCHSCREEN=y +CONFIG_TOUCHSCREEN_ATMEL_MXT=y +CONFIG_TOUCHSCREEN_MMS114=y +CONFIG_TOUCHSCREEN_EDT_FT5X06=y +CONFIG_INPUT_MISC=y +CONFIG_INPUT_PM8941_PWRKEY=y +CONFIG_INPUT_PM8XXX_VIBRATOR=y +CONFIG_INPUT_GPIO_VIBRA=y +CONFIG_INPUT_UINPUT=y +CONFIG_RMI4_CORE=y +CONFIG_RMI4_I2C=y +CONFIG_RMI4_F11=y +CONFIG_RMI4_F12=y +# CONFIG_SERIO is not set +# CONFIG_VT is not set +# CONFIG_LEGACY_PTYS is not set +CONFIG_SERIAL_MSM=y +CONFIG_SERIAL_MSM_CONSOLE=y +CONFIG_SERIAL_DEV_BUS=y +CONFIG_HW_RANDOM=y +# CONFIG_DEVMEM is not set +CONFIG_I2C_CHARDEV=y +CONFIG_I2C_GPIO=y +CONFIG_I2C_QCOM_CCI=y +CONFIG_I2C_QUP=y +CONFIG_I2C_SLAVE=y +CONFIG_SPI=y +CONFIG_SPI_MEM=y +CONFIG_SPI_BITBANG=y +CONFIG_SPI_QUP=y +CONFIG_SPI_SPIDEV=y +CONFIG_SPMI=y +CONFIG_PTP_1588_CLOCK=y +CONFIG_PINCTRL_SINGLE=y +CONFIG_PINCTRL_MSM=y +CONFIG_PINCTRL_MSM8976=y +CONFIG_PINCTRL_MSM8996=y +CONFIG_PINCTRL_MSM8998=y +CONFIG_PINCTRL_QCOM_SPMI_PMIC=y +CONFIG_PINCTRL_SDM660=y +CONFIG_GPIO_WCD934X=y +CONFIG_POWER_RESET_MSM=y +CONFIG_POWER_RESET_QCOM_PON=y +CONFIG_THERMAL=y +CONFIG_CPU_THERMAL=y +CONFIG_DEVFREQ_THERMAL=y +CONFIG_THERMAL_EMULATION=y +CONFIG_QCOM_TSENS=y +CONFIG_QCOM_SPMI_TEMP_ALARM=y +CONFIG_WATCHDOG=y +CONFIG_WATCHDOG_CORE=y +CONFIG_MFD_QCOM_RPM=y +CONFIG_MFD_SPMI_PMIC=y +CONFIG_MFD_RT5033=y +CONFIG_MFD_WCD934X=y +CONFIG_REGULATOR_FIXED_VOLTAGE=y +CONFIG_REGULATOR_VIRTUAL_CONSUMER=y +CONFIG_REGULATOR_USERSPACE_CONSUMER=y +CONFIG_REGULATOR_GPIO=y +CONFIG_REGULATOR_QCOM_SMD_RPM=y +CONFIG_REGULATOR_QCOM_SPMI=y +CONFIG_REGULATOR_VCTRL=y +CONFIG_REGULATOR_QCOM_LABIBB=y +CONFIG_MEDIA_SUPPORT=y +CONFIG_V4L_PLATFORM_DRIVERS=y +CONFIG_VIDEO_QCOM_CAMSS=y +CONFIG_V4L_MEM2MEM_DRIVERS=y +CONFIG_VIDEO_MEM2MEM_DEINTERLACE=y +CONFIG_VIDEO_QCOM_VENUS=y +CONFIG_VIDEO_IMX219=y +CONFIG_DRM=y +CONFIG_DRM_MSM=y +# CONFIG_DRM_MSM_DSI_20NM_PHY is not set +# CONFIG_DRM_MSM_DSI_28NM_8960_PHY is not set +# CONFIG_DRM_MSM_DSI_7NM_PHY is not set +CONFIG_DRM_PANEL_SIMPLE=y +CONFIG_DRM_PANEL_SONY_DISCOVERY_TD4322_INNOLUX=y +CONFIG_DRM_LEGACY=y +CONFIG_FB=y +CONFIG_LCD_CLASS_DEVICE=y +CONFIG_LCD_PLATFORM=y +CONFIG_BACKLIGHT_CLASS_DEVICE=y +CONFIG_BACKLIGHT_PWM=y +CONFIG_BACKLIGHT_QCOM_WLED=y +CONFIG_BACKLIGHT_GPIO=y +CONFIG_BACKLIGHT_LED=y +CONFIG_SOUND=y +CONFIG_SND=y +CONFIG_SND_DYNAMIC_MINORS=y +CONFIG_SND_SOC=y +CONFIG_SND_SOC_QCOM=y +CONFIG_SND_SOC_APQ8016_SBC=y +CONFIG_SND_SOC_MSM8996=y +CONFIG_SND_SOC_MSM8916_WCD_ANALOG=y +CONFIG_SND_SOC_MSM8916_WCD_DIGITAL=y +CONFIG_SND_SOC_WCD9335=y +CONFIG_HIDRAW=y +CONFIG_UHID=y +CONFIG_HID_A4TECH=y +CONFIG_HID_ACRUX=y +CONFIG_HID_ACRUX_FF=y +CONFIG_HID_APPLE=y +CONFIG_HID_BELKIN=y +CONFIG_HID_CHERRY=y +CONFIG_HID_CHICONY=y +CONFIG_HID_PRODIKEYS=y +CONFIG_HID_CYPRESS=y +CONFIG_HID_DRAGONRISE=y +CONFIG_DRAGONRISE_FF=y +CONFIG_HID_EMS_FF=y +CONFIG_HID_ELECOM=y +CONFIG_HID_EZKEY=y +CONFIG_HID_HOLTEK=y +CONFIG_HID_KEYTOUCH=y +CONFIG_HID_KYE=y +CONFIG_HID_UCLOGIC=y +CONFIG_HID_WALTOP=y +CONFIG_HID_GYRATION=y +CONFIG_HID_TWINHAN=y +CONFIG_HID_KENSINGTON=y +CONFIG_HID_LCPOWER=y +CONFIG_HID_LOGITECH=y +CONFIG_HID_LOGITECH_DJ=y +CONFIG_LOGITECH_FF=y +CONFIG_LOGIRUMBLEPAD2_FF=y +CONFIG_LOGIG940_FF=y +CONFIG_HID_MAGICMOUSE=y +CONFIG_HID_MICROSOFT=y +CONFIG_HID_MONTEREY=y +CONFIG_HID_MULTITOUCH=y +CONFIG_HID_NTRIG=y +CONFIG_HID_ORTEK=y +CONFIG_HID_PANTHERLORD=y +CONFIG_PANTHERLORD_FF=y +CONFIG_HID_PETALYNX=y +CONFIG_HID_PICOLCD=y +CONFIG_HID_PRIMAX=y +CONFIG_HID_ROCCAT=y +CONFIG_HID_SAITEK=y +CONFIG_HID_SAMSUNG=y +CONFIG_HID_SONY=y +CONFIG_HID_SPEEDLINK=y +CONFIG_HID_SUNPLUS=y +CONFIG_HID_GREENASIA=y +CONFIG_GREENASIA_FF=y +CONFIG_HID_SMARTJOYPLUS=y +CONFIG_SMARTJOYPLUS_FF=y +CONFIG_HID_TIVO=y +CONFIG_HID_TOPSEED=y +CONFIG_HID_THRUSTMASTER=y +CONFIG_HID_WACOM=y +CONFIG_HID_WIIMOTE=y +CONFIG_HID_ZEROPLUS=y +CONFIG_HID_ZYDACRON=y +CONFIG_USB_HIDDEV=y +CONFIG_USB_LED_TRIG=y +CONFIG_USB_CONN_GPIO=y +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +CONFIG_USB_OTG=y +CONFIG_USB_OTG_FSM=y +CONFIG_USB_XHCI_HCD=y +CONFIG_USB_EHCI_HCD=y +CONFIG_USB_EHCI_HCD_PLATFORM=y +CONFIG_USB_DWC3=y +CONFIG_USB_DWC3_ULPI=y +CONFIG_USB_CHIPIDEA=y +CONFIG_USB_CHIPIDEA_UDC=y +CONFIG_USB_CHIPIDEA_HOST=y +CONFIG_USB_GPIO_VBUS=y +CONFIG_USB_ULPI=y +CONFIG_USB_GADGET=y +CONFIG_USB_GADGET_VBUS_DRAW=500 +CONFIG_U_SERIAL_CONSOLE=y +CONFIG_USB_CONFIGFS=y +CONFIG_USB_CONFIGFS_SERIAL=y +CONFIG_USB_CONFIGFS_RNDIS=y +CONFIG_USB_CONFIGFS_EEM=y +CONFIG_USB_CONFIGFS_F_FS=y +CONFIG_USB_CONFIGFS_F_MIDI=y +CONFIG_MMC=y +CONFIG_MMC_BLOCK_MINORS=64 +CONFIG_MMC_DEBUG=y +CONFIG_MMC_SDHCI=y +CONFIG_MMC_SDHCI_PLTFM=y +CONFIG_MMC_SDHCI_MSM=y +CONFIG_LEDS_CLASS_MULTICOLOR=y +CONFIG_LEDS_QCOM_LPG=y +CONFIG_LEDS_TRIGGER_TIMER=y +CONFIG_LEDS_TRIGGER_ONESHOT=y +CONFIG_LEDS_TRIGGER_HEARTBEAT=y +CONFIG_LEDS_TRIGGER_CPU=y +CONFIG_LEDS_TRIGGER_ACTIVITY=y +CONFIG_LEDS_TRIGGER_GPIO=y +CONFIG_LEDS_TRIGGER_DEFAULT_ON=y +CONFIG_LEDS_TRIGGER_TRANSIENT=y +CONFIG_LEDS_TRIGGER_CAMERA=y +CONFIG_LEDS_TRIGGER_PANIC=y +CONFIG_LEDS_TRIGGER_NETDEV=y +CONFIG_LEDS_TRIGGER_PATTERN=y +CONFIG_LEDS_TRIGGER_AUDIO=y +CONFIG_LEDS_TRIGGER_TTY=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_PM8XXX=y +CONFIG_DMADEVICES=y +CONFIG_QCOM_BAM_DMA=y +CONFIG_QCOM_HIDMA_MGMT=y +CONFIG_QCOM_HIDMA=y +# CONFIG_VIRTIO_MENU is not set +# CONFIG_VHOST_MENU is not set +CONFIG_STAGING=y +CONFIG_ASHMEM=y +CONFIG_COMMON_CLK_QCOM=y +CONFIG_QCOM_CLK_APCC_MSM8996=y +CONFIG_QCOM_CLK_RPM=y +CONFIG_QCOM_CLK_SMD_RPM=y +CONFIG_MSM_MMCC_8996=y +CONFIG_MSM_GPUCC_8998=y +CONFIG_MSM_MMCC_8998=y +CONFIG_SDM_MMCC_660=y +CONFIG_SDM_GPUCC_660=y +CONFIG_SPMI_PMIC_CLKDIV=y +CONFIG_QCOM_HFPLL=y +CONFIG_HWSPINLOCK=y +CONFIG_HWSPINLOCK_QCOM=y +CONFIG_MAILBOX=y +CONFIG_QCOM_APCS_IPC=y +CONFIG_QCOM_IPCC=y +CONFIG_ARM_SMMU=y +CONFIG_ARM_SMMU_LEGACY_DT_BINDINGS=y +CONFIG_QCOM_IOMMU=y +CONFIG_REMOTEPROC=y +CONFIG_REMOTEPROC_CDEV=y +CONFIG_QCOM_Q6V5_ADSP=y +CONFIG_QCOM_Q6V5_MSS=y +CONFIG_QCOM_Q6V5_PAS=y +CONFIG_QCOM_Q6V5_WCSS=y +CONFIG_QCOM_SYSMON=y +CONFIG_QCOM_WCNSS_PIL=y +CONFIG_RPMSG_CHAR=y +CONFIG_RPMSG_QCOM_GLINK_RPM=y +CONFIG_RPMSG_QCOM_GLINK_SMEM=y +CONFIG_RPMSG_QCOM_SMD=y +CONFIG_SOUNDWIRE=y +CONFIG_SOUNDWIRE_QCOM=y +CONFIG_QCOM_AOSS_QMP=y +CONFIG_QCOM_CPR=y +CONFIG_QCOM_GSBI=y +CONFIG_QCOM_LLCC=y +CONFIG_QCOM_OCMEM=y +CONFIG_QCOM_RMTFS_MEM=y +CONFIG_QCOM_RPMPD=y +CONFIG_QCOM_SMEM=y +CONFIG_QCOM_SMD_RPM=y +CONFIG_QCOM_SMP2P=y +CONFIG_QCOM_SMSM=y +CONFIG_QCOM_SOCINFO=y +CONFIG_QCOM_WCNSS_CTRL=y +CONFIG_QCOM_APR=y +CONFIG_PM_DEVFREQ=y +CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND=y +CONFIG_EXTCON_ADC_JACK=y +CONFIG_EXTCON_GPIO=y +CONFIG_EXTCON_QCOM_SPMI_MISC=y +CONFIG_EXTCON_SM5502=y +CONFIG_EXTCON_USB_GPIO=y +CONFIG_MEMORY=y +CONFIG_IIO=y +CONFIG_BMA180=y +CONFIG_BMC150_ACCEL=y +CONFIG_QCOM_SPMI_IADC=y +CONFIG_QCOM_SPMI_VADC=y +CONFIG_QCOM_SPMI_ADC5=y +CONFIG_BMG160=y +CONFIG_INV_MPU6050_SPI=y +CONFIG_IIO_ST_LSM6DSX=y +CONFIG_LTR501=y +CONFIG_AK09911=y +CONFIG_BMC150_MAGN_I2C=y +CONFIG_PWM=y +CONFIG_QCOM_PDC=y +CONFIG_PHY_QCOM_PCIE2=y +CONFIG_PHY_QCOM_QMP=y +CONFIG_PHY_QCOM_QUSB2=y +CONFIG_PHY_QCOM_USB_HS=y +CONFIG_PHY_QCOM_USB_HSIC=y +CONFIG_PHY_QCOM_USB_HS_28NM=y +CONFIG_POWERCAP=y +CONFIG_IDLE_INJECT=y +CONFIG_ANDROID=y +CONFIG_ANDROID_BINDER_IPC=y +CONFIG_QCOM_QFPROM=y +CONFIG_NVMEM_SPMI_SDAM=y +CONFIG_SLIM_QCOM_CTRL=y +CONFIG_SLIM_QCOM_NGD_CTRL=y +CONFIG_INTERCONNECT=y +CONFIG_INTERCONNECT_QCOM=y +CONFIG_INTERCONNECT_QCOM_SDM660=y +CONFIG_EXT4_FS=y +CONFIG_EXT4_FS_POSIX_ACL=y +CONFIG_EXT4_FS_SECURITY=y +CONFIG_F2FS_FS=y +CONFIG_F2FS_FS_COMPRESSION=y +CONFIG_FANOTIFY=y +CONFIG_FANOTIFY_ACCESS_PERMISSIONS=y +CONFIG_QUOTA=y +CONFIG_QFMT_V1=y +CONFIG_QFMT_V2=y +CONFIG_AUTOFS4_FS=y +CONFIG_FUSE_FS=y +CONFIG_CUSE=y +CONFIG_OVERLAY_FS=y +CONFIG_MSDOS_FS=y +CONFIG_VFAT_FS=y +CONFIG_EXFAT_FS=y +CONFIG_TMPFS_POSIX_ACL=y +CONFIG_HUGETLBFS=y +CONFIG_PSTORE=y +# CONFIG_PSTORE_DEFLATE_COMPRESS is not set +CONFIG_PSTORE_CONSOLE=y +CONFIG_PSTORE_PMSG=y +CONFIG_PSTORE_RAM=y +CONFIG_NLS_CODEPAGE_437=y +CONFIG_NLS_ISO8859_1=y +CONFIG_SECURITY=y +CONFIG_SECURITYFS=y +CONFIG_SECURITY_NETWORK=y +CONFIG_HARDENED_USERCOPY=y +CONFIG_SECURITY_SELINUX=y +CONFIG_SECURITY_SELINUX_BOOTPARAM=y +CONFIG_LSM="selinux" +CONFIG_CRYPTO_XTS=y +CONFIG_CRYPTO_SHA512=y +CONFIG_CRYPTO_LZO=y +CONFIG_CRYPTO_842=y +CONFIG_CRYPTO_LZ4=y +CONFIG_CRYPTO_LZ4HC=y +CONFIG_CRYPTO_ZSTD=y +CONFIG_CRYPTO_ANSI_CPRNG=y +CONFIG_CRYPTO_USER_API_HASH=y +CONFIG_CRYPTO_USER_API_SKCIPHER=y +CONFIG_CRYPTO_USER_API_RNG=y +CONFIG_CRYPTO_USER_API_AEAD=y +CONFIG_CRYPTO_DEV_QCOM_RNG=y +CONFIG_DMA_CMA=y +CONFIG_CMA_SIZE_MBYTES=32 +CONFIG_PRINTK_TIME=y +CONFIG_DYNAMIC_DEBUG=y +CONFIG_DEBUG_INFO=y +CONFIG_MAGIC_SYSRQ=y +CONFIG_DEBUG_FS=y +CONFIG_PANIC_TIMEOUT=5 +# CONFIG_SCHED_DEBUG is not set +CONFIG_SCHEDSTATS=y +# CONFIG_DEBUG_PREEMPT is not set +# CONFIG_FTRACE is not set From b2995ca65c1ac1b552c2f3445b7a5a29828f1608 Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Thu, 8 Apr 2021 19:47:50 +0200 Subject: [PATCH 039/187] leds: qcom-lpg: Add PM660L --- drivers/leds/rgb/leds-qcom-lpg.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/drivers/leds/rgb/leds-qcom-lpg.c b/drivers/leds/rgb/leds-qcom-lpg.c index c68233b43c2c..eb0ab3d1015c 100644 --- a/drivers/leds/rgb/leds-qcom-lpg.c +++ b/drivers/leds/rgb/leds-qcom-lpg.c @@ -1139,6 +1139,25 @@ static int lpg_remove(struct platform_device *pdev) return 0; } +static const struct lpg_data pm660l_lpg_data = { + .lut_base = 0xb000, + .lut_size = 0x100, + + .triled_base = 0xd000, + + // UHH? + // Subtype is 0x11, LPG_LITE, uses this downstream: + .pwm_9bit_mask = BIT(4), + + .num_channels = 4, + .channels = (struct lpg_channel_data[]) { + { .base = 0xb100, .triled_mask = BIT(5) }, + { .base = 0xb200, .triled_mask = BIT(6) }, + { .base = 0xb300, .triled_mask = BIT(7) }, + { .base = 0xb400 }, + }, +}; + static const struct lpg_data pm8916_pwm_data = { .pwm_9bit_mask = BIT(2), @@ -1261,6 +1280,7 @@ static const struct lpg_data pm8150l_lpg_data = { }; static const struct of_device_id lpg_of_table[] = { + { .compatible = "qcom,pm660l-lpg", .data = &pm660l_lpg_data }, { .compatible = "qcom,pm8150b-lpg", .data = &pm8150b_lpg_data }, { .compatible = "qcom,pm8150l-lpg", .data = &pm8150l_lpg_data }, { .compatible = "qcom,pm8916-pwm", .data = &pm8916_pwm_data }, From 1406a16faf913b50bc111f408d39ab9fd3f6748c Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Sat, 1 May 2021 22:57:57 +0200 Subject: [PATCH 040/187] qcom-lpg: Review fixups! --- .../boot/dts/qcom/sdm630-sony-xperia-nile.dtsi | 3 ++- drivers/leds/Kconfig | 13 +++++++++++-- drivers/leds/Makefile | 4 +++- drivers/leds/rgb/leds-qcom-lpg.c | 6 +++--- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile.dtsi b/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile.dtsi index 32c15f7eddb5..b18f69790cd1 100644 --- a/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile.dtsi +++ b/arch/arm64/boot/dts/qcom/sdm630-sony-xperia-nile.dtsi @@ -261,7 +261,8 @@ }; &pm660l_lpg { - qcom,power-source = <1>; + // No idea, and optional... + // qcom,power-source = <1>; status = "okay"; diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index ffb8777842ff..c0de85d3b432 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -876,8 +876,17 @@ source "drivers/leds/blink/Kconfig" comment "Flash and Torch LED drivers" source "drivers/leds/flash/Kconfig" -comment "RGB LED drivers" -source "drivers/leds/rgb/Kconfig" +#comment "RGB LED drivers" +#source "drivers/leds/rgb/Kconfig" + +config LEDS_QCOM_LPG + tristate "LED support for Qualcomm LPG" + depends on LEDS_CLASS_MULTICOLOR + depends on OF + depends on SPMI + help + This option enables support for the Light Pulse Generator found in a + wide variety of Qualcomm PMICs. comment "LED Triggers" source "drivers/leds/trigger/Kconfig" diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 351153f84070..4cd1e18b37e5 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -101,7 +101,9 @@ obj-$(CONFIG_LEDS_USER) += uleds.o obj-$(CONFIG_LEDS_CLASS_FLASH) += flash/ # RGB LED Drivers -obj-$(CONFIG_LEDS_CLASS_MULTICOLOR) += rgb/ +# obj-$(CONFIG_LEDS_CLASS_MULTICOLOR) += rgb/ + +obj-$(CONFIG_LEDS_QCOM_LPG) += rgb/leds-qcom-lpg.o # LED Triggers obj-$(CONFIG_LEDS_TRIGGERS) += trigger/ diff --git a/drivers/leds/rgb/leds-qcom-lpg.c b/drivers/leds/rgb/leds-qcom-lpg.c index eb0ab3d1015c..d35106f86775 100644 --- a/drivers/leds/rgb/leds-qcom-lpg.c +++ b/drivers/leds/rgb/leds-qcom-lpg.c @@ -918,7 +918,7 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np) return ret; } - if (color == LED_COLOR_ID_MULTI) + if (color == LED_COLOR_ID_RGB) num_channels = of_get_available_child_count(np); else num_channels = 1; @@ -930,7 +930,7 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np) led->lpg = lpg; led->num_channels = num_channels; - if (color == LED_COLOR_ID_MULTI) { + if (color == LED_COLOR_ID_RGB) { info = devm_kcalloc(lpg->dev, num_channels, sizeof(*info), GFP_KERNEL); if (!info) return -ENOMEM; @@ -986,7 +986,7 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np) cdev->brightness_set(cdev, cdev->brightness); - if (color == LED_COLOR_ID_MULTI) + if (color == LED_COLOR_ID_RGB) ret = devm_led_classdev_multicolor_register(lpg->dev, &led->mcdev); else ret = devm_led_classdev_register(lpg->dev, &led->cdev); From 5d25ef59ae1d488b3845933af7064308b0b9fdac Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Wed, 4 Aug 2021 16:39:35 +0200 Subject: [PATCH 041/187] iommu/arm-smmu-qcom: Allow choosing a custom bypass emulation context It cannot be taken for granted that the last IOMMU context is free and available for us to use it to emulate bypass streams and, at least on MSM8998's lpass iommu, using the last one will produce a crash; please note that this may not be only dependant on the SoC, but also on the firmware version. To overcome to this issue, allow specifying a different context for bypass emulation with the optional DT property "qcom,bypass-cbndx": if this property is not found this means that we are either booting with ACPI instead or that we don't want to specify a custom cb because the default one (the last context bank) is fine. Signed-off-by: AngeloGioacchino Del Regno --- drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c index ca736b065dd0..e73e9c276e22 100644 --- a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c +++ b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c @@ -267,7 +267,8 @@ static int qcom_smmu_cfg_probe(struct arm_smmu_device *smmu) reg = arm_smmu_gr0_read(smmu, last_s2cr); if (FIELD_GET(ARM_SMMU_S2CR_TYPE, reg) != S2CR_TYPE_BYPASS) { qsmmu->bypass_quirk = true; - qsmmu->bypass_cbndx = smmu->num_context_banks - 1; + if (qsmmu->bypass_cbndx == 0xff) + qsmmu->bypass_cbndx = smmu->num_context_banks - 1; set_bit(qsmmu->bypass_cbndx, smmu->context_map); @@ -387,6 +388,7 @@ static const struct arm_smmu_impl qcom_adreno_smmu_impl = { static struct arm_smmu_device *qcom_smmu_create(struct arm_smmu_device *smmu, const struct arm_smmu_impl *impl) { + const struct device_node *np = smmu->dev->of_node; struct qcom_smmu *qsmmu; /* Check to make sure qcom_scm has finished probing */ @@ -398,6 +400,16 @@ static struct arm_smmu_device *qcom_smmu_create(struct arm_smmu_device *smmu, return ERR_PTR(-ENOMEM); qsmmu->smmu.impl = impl; + qsmmu->bypass_cbndx = 0xff; + + if (np != NULL) { + /* + * This property is optional and we expect to fail finding it if: + * - Using the default bypass_cbndx (in the .cfg_probe cb) is fine; or + * - We are booting on ACPI + */ + of_property_read_u8(np, "qcom,bypass-cbndx", &qsmmu->bypass_cbndx); + } return &qsmmu->smmu; } From 8058f9b46ab7fa7a4ffbb137dbcdb4f99de44bb8 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Fri, 6 Aug 2021 13:14:41 +0200 Subject: [PATCH 042/187] iommu/arm-smmu: Allow skipping context bank disable at reset time On some SoCs some IOMMU context banks are actively used from TZ during system boot, or some hypervisor configurations will trigger a system reset upon disabling some protected/secured CBs. Allow skipping the disablement of such contexts at IOMMU reset time during initialization with a new implementation detail to work around this quirk. Signed-off-by: AngeloGioacchino Del Regno --- drivers/iommu/arm/arm-smmu/arm-smmu.c | 10 ++++++++++ drivers/iommu/arm/arm-smmu/arm-smmu.h | 1 + 2 files changed, 11 insertions(+) diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu.c b/drivers/iommu/arm/arm-smmu/arm-smmu.c index 4bc75c4ce402..2c75ef23deec 100644 --- a/drivers/iommu/arm/arm-smmu/arm-smmu.c +++ b/drivers/iommu/arm/arm-smmu/arm-smmu.c @@ -1622,6 +1622,16 @@ static void arm_smmu_device_reset(struct arm_smmu_device *smmu) /* Make sure all context banks are disabled and clear CB_FSR */ for (i = 0; i < smmu->num_context_banks; ++i) { + /* + * Some context banks cannot be disabled due to hypervisor + * configuration on some systems; if this is the case, + * skip disabling and writing FAULT on the CB FSR in order + * to avoid a system crash. + */ + if (smmu->impl && smmu->impl->reset_cb_nodisable && + smmu->impl->reset_cb_nodisable(smmu, i)) { + continue; + } arm_smmu_write_context_bank(smmu, i); arm_smmu_cb_write(smmu, i, ARM_SMMU_CB_FSR, ARM_SMMU_FSR_FAULT); } diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu.h b/drivers/iommu/arm/arm-smmu/arm-smmu.h index 432de2f742c3..43051ed940e5 100644 --- a/drivers/iommu/arm/arm-smmu/arm-smmu.h +++ b/drivers/iommu/arm/arm-smmu/arm-smmu.h @@ -430,6 +430,7 @@ struct arm_smmu_impl { u64 val); int (*cfg_probe)(struct arm_smmu_device *smmu); int (*reset)(struct arm_smmu_device *smmu); + bool (*reset_cb_nodisable)(struct arm_smmu_device *smmu, int cbndx); int (*init_context)(struct arm_smmu_domain *smmu_domain, struct io_pgtable_cfg *cfg, struct device *dev); void (*tlb_sync)(struct arm_smmu_device *smmu, int page, int sync, From 0dcea98014ed9b86f8cf4a6c0f03e11fb29591a1 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Fri, 6 Aug 2021 13:19:14 +0200 Subject: [PATCH 043/187] iommu/arm-smmu-qcom: Avoid disabling secured context banks Some Qualcomm SoCs' TZ/hypervisor configuration is disallowing the disablement of some context banks, being them used for tzapps and/or remote processors; any attempt to disable such CBs will result in triggering a fault and the system will freeze and/or reset. For this reason, get a list of context banks that should never get disabled during smmu initialization through a DT array property `qcom,reset-nodisable-cbs`. It was chosen to not hardcode the CBs as this is dependant on the SoC's firmware, which may vary on different boards. Signed-off-by: AngeloGioacchino Del Regno --- drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c | 32 ++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c index e73e9c276e22..778939f979b1 100644 --- a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c +++ b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -12,6 +13,7 @@ struct qcom_smmu { struct arm_smmu_device smmu; + DECLARE_BITMAP(reset_cb_nodisable_mask, ARM_SMMU_MAX_CBS); bool bypass_quirk; u8 bypass_cbndx; u32 stall_enabled; @@ -369,11 +371,20 @@ static int qcom_smmu500_reset(struct arm_smmu_device *smmu) return 0; } +static bool qcom_smmu500_reset_cb_nodisable(struct arm_smmu_device *smmu, + int cbndx) +{ + struct qcom_smmu *qsmmu = to_qcom_smmu(smmu); + + return test_bit(cbndx, qsmmu->reset_cb_nodisable_mask); +} + static const struct arm_smmu_impl qcom_smmu_impl = { .init_context = qcom_smmu_init_context, .cfg_probe = qcom_smmu_cfg_probe, .def_domain_type = qcom_smmu_def_domain_type, .reset = qcom_smmu500_reset, + .reset_cb_nodisable = qcom_smmu500_reset_cb_nodisable, .write_s2cr = qcom_smmu_write_s2cr, }; @@ -381,6 +392,7 @@ static const struct arm_smmu_impl qcom_adreno_smmu_impl = { .init_context = qcom_adreno_smmu_init_context, .def_domain_type = qcom_smmu_def_domain_type, .reset = qcom_smmu500_reset, + .reset_cb_nodisable = qcom_smmu500_reset_cb_nodisable, .alloc_context_bank = qcom_adreno_smmu_alloc_context_bank, .write_sctlr = qcom_adreno_smmu_write_sctlr, }; @@ -390,6 +402,8 @@ static struct arm_smmu_device *qcom_smmu_create(struct arm_smmu_device *smmu, { const struct device_node *np = smmu->dev->of_node; struct qcom_smmu *qsmmu; + u8 reset_nodisable_cbs[ARM_SMMU_MAX_CBS]; + int i, sz; /* Check to make sure qcom_scm has finished probing */ if (!qcom_scm_is_available()) @@ -401,6 +415,7 @@ static struct arm_smmu_device *qcom_smmu_create(struct arm_smmu_device *smmu, qsmmu->smmu.impl = impl; qsmmu->bypass_cbndx = 0xff; + bitmap_zero(qsmmu->reset_cb_nodisable_mask, ARM_SMMU_MAX_CBS); if (np != NULL) { /* @@ -409,6 +424,23 @@ static struct arm_smmu_device *qcom_smmu_create(struct arm_smmu_device *smmu, * - We are booting on ACPI */ of_property_read_u8(np, "qcom,bypass-cbndx", &qsmmu->bypass_cbndx); + + /* + * Some context banks may not be disabled because they are + * secured: read from DT a list of secured contexts that cannot + * be disabled without crashing the system. + * This list is optional, as not all firmware configurations do + * require us skipping disablement of context banks. + */ + sz = of_property_read_variable_u8_array(np, "qcom,reset-nodisable-cbs", + reset_nodisable_cbs, + 1, ARM_SMMU_MAX_CBS); + if (sz > 0) { + for (i = 0; i < sz; i++) { + __set_bit(reset_nodisable_cbs[i], + qsmmu->reset_cb_nodisable_mask); + } + } } return &qsmmu->smmu; From b38dd2c5b9533972a981ba6004c4a20c81a36ca0 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Fri, 6 Aug 2021 15:15:33 +0200 Subject: [PATCH 044/187] iommu/arm-smmu-qcom: Don't modify sACR on hypervisor secured iommus Avoid modifying the contents of the secure Auxiliary Control Register on some Qualcomm SoCs: due to a hypervisor configuration on some firmware versions, this would result in a system crash. Signed-off-by: AngeloGioacchino Del Regno --- drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c index 778939f979b1..0eb8ad929659 100644 --- a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c +++ b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c @@ -362,8 +362,15 @@ static int qcom_sdm845_smmu500_reset(struct arm_smmu_device *smmu) static int qcom_smmu500_reset(struct arm_smmu_device *smmu) { const struct device_node *np = smmu->dev->of_node; + struct qcom_smmu *qsmmu = to_qcom_smmu(smmu); - arm_mmu500_reset(smmu); + /* + * Execute the mmu-500 reset implementation detail only if there + * are no secured untouchable contexts in this iommu, otherwise + * the system will crash. + */ + if (bitmap_empty(qsmmu->reset_cb_nodisable_mask, ARM_SMMU_MAX_CBS)) + arm_mmu500_reset(smmu); if (of_device_is_compatible(np, "qcom,sdm845-smmu-500")) return qcom_sdm845_smmu500_reset(smmu); From 4159f402ee1f882d4066868382e50b892a034f7f Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Wed, 4 Aug 2021 17:38:27 +0200 Subject: [PATCH 045/187] arm64: dts: sdm630: Override bypass emulation context for lpass, anoc2 On the LPASS and ANOC2 IOMMUs, we cannot use the last context to emulate bypass streams: set the right context banks to avoid crashes. --- arch/arm64/boot/dts/qcom/sdm630.dtsi | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/sdm630.dtsi b/arch/arm64/boot/dts/qcom/sdm630.dtsi index 523c638063af..15278a1c5010 100644 --- a/arch/arm64/boot/dts/qcom/sdm630.dtsi +++ b/arch/arm64/boot/dts/qcom/sdm630.dtsi @@ -634,6 +634,7 @@ clock-names = "bus"; #global-interrupts = <2>; #iommu-cells = <1>; + qcom,bypass-cbndx = /bits/ 8 <3>; interrupts = , @@ -1186,6 +1187,7 @@ compatible = "qcom,sdm630-smmu-v2", "qcom,smmu-v2"; reg = <0x05100000 0x40000>; #iommu-cells = <1>; + qcom,bypass-cbndx = /bits/ 8 <12>; #global-interrupts = <2>; interrupts = From 76c44337827c89cbbfe94944c7f02dc741db2f83 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Wed, 4 Aug 2021 23:10:04 +0200 Subject: [PATCH 046/187] arm64: dts: qcom: sdm630: Disallow disabling secured iommu context banks Some IOMMU context banks are secured and any attempt to disable them during arm-smmu initialization will result in a system crash. To work around this issue, add a list of context banks that will never be disabled, but only reconfigured, at initialization time. Signed-off-by: AngeloGioacchino Del Regno --- arch/arm64/boot/dts/qcom/sdm630.dtsi | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/qcom/sdm630.dtsi b/arch/arm64/boot/dts/qcom/sdm630.dtsi index 15278a1c5010..b1c1e58310bd 100644 --- a/arch/arm64/boot/dts/qcom/sdm630.dtsi +++ b/arch/arm64/boot/dts/qcom/sdm630.dtsi @@ -634,7 +634,9 @@ clock-names = "bus"; #global-interrupts = <2>; #iommu-cells = <1>; - qcom,bypass-cbndx = /bits/ 8 <3>; + qcom,bypass-cbndx = /bits/ 8 <6>; + qcom,reset-nodisable-cbs = /bits/ 8 <7 8 9 10 11 12 13 14 + 15 16 17 18 19 20>; interrupts = , @@ -1152,6 +1154,7 @@ clock-names = "iface", "mem", "mem_iface"; #global-interrupts = <2>; #iommu-cells = <1>; + qcom,reset-nodisable-cbs = /bits/ 8 <2 3 4>; interrupts = , @@ -1188,6 +1191,7 @@ reg = <0x05100000 0x40000>; #iommu-cells = <1>; qcom,bypass-cbndx = /bits/ 8 <12>; + qcom,reset-nodisable-cbs = /bits/ 8 <13>; #global-interrupts = <2>; interrupts = From af90a1ce6d0ba335492e6bce20b972831287c02a Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Thu, 5 Aug 2021 15:34:21 +0200 Subject: [PATCH 047/187] arm64: dts: qcom: Introduce SDM660 Xiaomi Mi 8 Lite (platina) Add support for the SDM660 Xiaomi Platina (Mi 8 Lite). Signed-off-by: AngeloGioacchino Del Regno --- arch/arm64/boot/dts/qcom/Makefile | 1 + .../boot/dts/qcom/sdm660-xiaomi-platina.dts | 717 ++++++++++++++++++ 2 files changed, 718 insertions(+) create mode 100644 arch/arm64/boot/dts/qcom/sdm660-xiaomi-platina.dts diff --git a/arch/arm64/boot/dts/qcom/Makefile b/arch/arm64/boot/dts/qcom/Makefile index 6b816eb33309..19f4f8884380 100644 --- a/arch/arm64/boot/dts/qcom/Makefile +++ b/arch/arm64/boot/dts/qcom/Makefile @@ -84,6 +84,7 @@ dtb-$(CONFIG_ARCH_QCOM) += sdm630-sony-xperia-nile-pioneer.dtb dtb-$(CONFIG_ARCH_QCOM) += sdm630-sony-xperia-nile-voyager.dtb dtb-$(CONFIG_ARCH_QCOM) += sdm636-sony-xperia-ganges-mermaid.dtb dtb-$(CONFIG_ARCH_QCOM) += sdm660-xiaomi-lavender.dtb +dtb-$(CONFIG_ARCH_QCOM) += sdm660-xiaomi-platina.dtb dtb-$(CONFIG_ARCH_QCOM) += sdm845-cheza-r1.dtb dtb-$(CONFIG_ARCH_QCOM) += sdm845-cheza-r2.dtb dtb-$(CONFIG_ARCH_QCOM) += sdm845-cheza-r3.dtb diff --git a/arch/arm64/boot/dts/qcom/sdm660-xiaomi-platina.dts b/arch/arm64/boot/dts/qcom/sdm660-xiaomi-platina.dts new file mode 100644 index 000000000000..0c7dc90f4661 --- /dev/null +++ b/arch/arm64/boot/dts/qcom/sdm660-xiaomi-platina.dts @@ -0,0 +1,717 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021, AngeloGioacchino Del Regno + * + */ + +/dts-v1/; + +#include "sdm660.dtsi" +#include "pm660.dtsi" +#include "pm660l.dtsi" +#include +#include +#include +#include +#include + +/ { + model = "Xiaomi Mi 8 Lite"; + compatible = "xiaomi,platina", "qcom,sdm660"; + + aliases { + serial0 = &blsp1_uart2; + }; + + chosen { + stdout-path = "serial0:115200n8"; + }; + + reserved-memory { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + ramoops@ffc00000 { + compatible = "ramoops"; + reg = <0x0 0xffc00000 0x0 0x100000>; + record-size = <0x10000>; + console-size = <0x60000>; + ftrace-size = <0x10000>; + pmsg-size = <0x20000>; + ecc-size = <16>; + status = "okay"; + }; + + debug_region@ffb00000 { + reg = <0x00 0xffb00000 0x00 0x100000>; + no-map; + }; + + cdsp-region@94e00000 { + reg = <0x00 0x94e00000 0x00 0x600000>; + no-map; + }; + + removed_region@85800000 { + reg = <0x00 0x85800000 0x00 0x3700000>; + no-map; + }; + }; + + board_vbat: vbat-regulator { + compatible = "regulator-fixed"; + regulator-name = "VBAT"; + + regulator-min-microvolt = <4000000>; + regulator-max-microvolt = <4000000>; + regulator-always-on; + regulator-boot-on; + }; + + cam_avdd_front_vreg: cam-avdd-front-vreg { + compatible = "regulator-fixed"; + regulator-name = "cam-avdd-front-vreg"; + + regulator-min-microvolt = <3600000>; + regulator-max-microvolt = <3600000>; + startup-delay-us = <0>; + enable-active-high; + gpio = <&tlmm 49 GPIO_ACTIVE_HIGH>; + pinctrl-names = "default"; + pinctrl-0 = <&cam_avdd_front_default>; + vin-supply = <&vreg_bob>; + }; + + cam_actuator_rear_vreg: cam-actuator-rear-vreg { + compatible = "regulator-fixed"; + regulator-name = "cam-actuator-rear-vreg"; + + regulator-min-microvolt = <3600000>; + regulator-max-microvolt = <3600000>; + startup-delay-us = <0>; + enable-active-high; + gpio = <&tlmm 50 GPIO_ACTIVE_HIGH>; + pinctrl-names = "default"; + pinctrl-0 = <&cam_actuator_rear_default>; + vin-supply = <&vreg_bob>; + }; + + cam_avdd_rear_vreg: cam-avdd-rear-vreg { + compatible = "regulator-fixed"; + regulator-name = "cam-avdd-rear-vreg"; + + regulator-min-microvolt = <3600000>; + regulator-max-microvolt = <3600000>; + startup-delay-us = <0>; + enable-active-high; + gpio = <&tlmm 51 GPIO_ACTIVE_HIGH>; + pinctrl-names = "default"; + pinctrl-0 = <&cam_avdd_rear_default>; + vin-supply = <&vreg_bob>; + }; + + cam_dvdd_front_vreg: cam-dvdd-front-vreg { + compatible = "regulator-fixed"; + regulator-name = "cam-dvdd-front-vreg"; + + regulator-min-microvolt = <1350000>; + regulator-max-microvolt = <1350000>; + startup-delay-us = <0>; + enable-active-high; + gpio = <&pm660l_gpios 3 GPIO_ACTIVE_HIGH>; + pinctrl-names = "default"; + pinctrl-0 = <&cam_dvdd_front_default>; + vin-supply = <&vreg_s5a_1p35>; + }; + + cam_dvdd_rear_vreg: cam-dvdd-rear-vreg { + compatible = "regulator-fixed"; + regulator-name = "cam-dvdd-rear-vreg"; + + regulator-min-microvolt = <1350000>; + regulator-max-microvolt = <1350000>; + startup-delay-us = <0>; + enable-active-high; + gpio = <&pm660l_gpios 4 GPIO_ACTIVE_HIGH>; + pinctrl-names = "default"; + pinctrl-0 = <&cam_dvdd_rear_default>; + vin-supply = <&vreg_s5a_1p35>; + }; + + disp_vdd_vreg: disp-vdd-vreg { + compatible = "regulator-fixed"; + regulator-name = "display-vdd"; + + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + regulator-boot-on; + }; + + ts_vdd_vreg: ts-vdd-vreg { + compatible = "regulator-fixed"; + regulator-name = "ts-vdd"; + + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + startup-delay-us = <4000>; + enable-active-high; + regulator-boot-on; + gpio = <&tlmm 73 GPIO_ACTIVE_HIGH>; + pinctrl-names = "default"; + pinctrl-0 = <&ts_vdd_default>; + }; + + vph_pwr: vph-pwr-regulator { + compatible = "regulator-fixed"; + regulator-name = "vph_pwr"; + + regulator-always-on; + regulator-boot-on; + }; + + gpio_keys { + status = "okay"; + compatible = "gpio-keys"; + input-name = "gpio-keys"; + #address-cells = <1>; + #size-cells = <0>; + + vol_up { + label = "Volume Up"; + gpios = <&pm660l_gpios 7 GPIO_ACTIVE_LOW>; + linux,input-type = <1>; + linux,code = ; + wakeup-source; + debounce-interval = <15>; + }; + }; + + gpio-hall-sensor { + compatible = "gpio-keys"; + label = "Hall effect sensor"; + + pinctrl-names = "default"; + pinctrl-0 = <&hall_sensor_default>; + + hall-sensor { + label = "Hall Effect Sensor"; + gpios = <&tlmm 75 GPIO_ACTIVE_LOW>; + linux,input-type = ; + linux,code = ; + linux,can-disable; + wakeup-source; + }; + }; + + + /* + * Until we hook up type-c detection, we + * have to stick with this. But it works. + */ + extcon_usb: extcon-usb { + compatible = "linux,extcon-usb-gpio"; + id-gpio = <&tlmm 58 GPIO_ACTIVE_HIGH>; + }; +}; + +&blsp_i2c2 { + status = "okay"; + + /* + * Two instances of SMB1355 charger: 0x8, 0xc but only + * one SMB1355 chip is present on board. What's the + * right address? + */ +}; + +&blsp1_uart2 { + status = "okay"; +}; + +&rpm_requests { + pm660l-regulators { + compatible = "qcom,rpm-pm660l-regulators"; + + vdd_s1-supply = <&vph_pwr>; + vdd_s2-supply = <&vph_pwr>; + vdd_s3_s4-supply = <&vph_pwr>; + vdd_s5-supply = <&vph_pwr>; + vdd_s6-supply = <&vph_pwr>; + + vdd_l1_l9_l10-supply = <&vreg_s2b_1p05>; + vdd_l2-supply = <&vreg_bob>; + vdd_l3_l5_l7_l8-supply = <&vreg_bob>; + vdd_l4_l6-supply = <&vreg_bob>; + vdd_bob-supply = <&vph_pwr>; + + vreg_s1b_1p125: s1 { + regulator-min-microvolt = <1125000>; + regulator-max-microvolt = <1125000>; + regulator-enable-ramp-delay = <200>; + regulator-ramp-delay = <0>; + }; + + vreg_s2b_1p05: s2 { + regulator-min-microvolt = <1050000>; + regulator-max-microvolt = <1050000>; + regulator-enable-ramp-delay = <200>; + regulator-ramp-delay = <0>; + }; + + /* + * At least on Nile's configuration, S3B/S4B (VDD_CX) and + * S5B (VDD_MX) are managed only through RPM Power Domains. + * Trying to set a voltage on the main supply will create + * havoc and freeze the SoC. + * In any case, reference voltages for these regulators are: + * S3B/S4B: 0.870V + * S5B: 0.915V + */ + + /* LDOs */ + vreg_l1b_0p925: l1 { + regulator-min-microvolt = <920000>; + regulator-max-microvolt = <928000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + regulator-allow-set-load; + }; + + vreg_l2b_2p95: l2 { + /* + * This regulator supports 1.648 - 3.104V on this board + * but we set a max voltage of anything less than 2.7V + * to satisfy a condition in sdhci.c that will disable + * 3.3V SDHCI signaling, which happens to be not really + * supported on the Xperia Nile/Ganges platform. + */ + regulator-min-microvolt = <1648000>; + regulator-max-microvolt = <2696000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + regulator-allow-set-load; + }; + + vreg_l3b_3p3: l3 { + regulator-min-microvolt = <3296000>; + regulator-max-microvolt = <3312000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + regulator-min-microamp = <200>; + regulator-max-microamp = <600000>; + regulator-system-load = <100000>; + regulator-allow-set-load; + }; + + vreg_l4b_2p95: l4 { + regulator-min-microvolt = <2944000>; + regulator-max-microvolt = <2952000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + + regulator-min-microamp = <200>; + regulator-max-microamp = <600000>; + regulator-system-load = <570000>; + regulator-allow-set-load; + }; + + /* + * Downstream specifies a range of 1721-3600mV, + * but the only assigned consumers are SDHCI2 VMMC + * and Coresight QPDI that both request pinned 2.95V. + * Tighten the range to 1.8-3.328 (closest to 3.3) to + * make the mmc driver happy. + */ + vreg_l5b_29p5: l5 { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <3328000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + regulator-allow-set-load; + regulator-system-load = <800000>; + }; + + vreg_l7b_3p125: l7 { + regulator-min-microvolt = <2704000>; + regulator-max-microvolt = <3128000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + }; + + vreg_l8b_3p3: l8 { + regulator-min-microvolt = <2800000>; + regulator-max-microvolt = <3400000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + }; + + /* L9B (870mV) is currently unused */ + /* L10B (915mV) is currently unused */ + + vreg_bob: bob { + regulator-min-microvolt = <3304000>; + regulator-max-microvolt = <3624000>; + regulator-enable-ramp-delay = <500>; + regulator-ramp-delay = <0>; + }; + }; + + + pm660-regulators { + compatible = "qcom,rpm-pm660-regulators"; + + vdd_s1-supply = <&vph_pwr>; + vdd_s2-supply = <&vph_pwr>; + vdd_s3-supply = <&vph_pwr>; + vdd_s4-supply = <&vph_pwr>; + vdd_s5-supply = <&vph_pwr>; + vdd_s6-supply = <&vph_pwr>; + + vdd_l1_l6_l7-supply = <&vreg_s5a_1p35>; + vdd_l2_l3-supply = <&vreg_s2b_1p05>; + vdd_l5-supply = <&vreg_s2b_1p05>; + vdd_l8_l9_l10_l11_l12_l13_l14-supply = <&vreg_s4a_2p04>; + vdd_l15_l16_l17_l18_l19-supply = <&vreg_bob>; + + /* + * S1A (FTAPC0), S2A (FTAPC1), S3A (HFAPC1) are managed + * by the Core Power Reduction hardened (CPRh) and the + * Operating State Manager (OSM) HW automatically. + */ + + vreg_s4a_2p04: s4 { + regulator-min-microvolt = <2040000>; + regulator-max-microvolt = <2040000>; + regulator-enable-ramp-delay = <200>; + regulator-ramp-delay = <0>; + regulator-always-on; + }; + + vreg_s5a_1p35: s5 { + regulator-min-microvolt = <1224000>; + regulator-max-microvolt = <1350000>; + regulator-enable-ramp-delay = <200>; + regulator-ramp-delay = <0>; + }; + + vreg_s6a_0p87: s6 { + regulator-min-microvolt = <504000>; + regulator-max-microvolt = <992000>; + regulator-enable-ramp-delay = <150>; + regulator-ramp-delay = <0>; + }; + + /* LDOs */ + vreg_l1a_1p225: l1 { + regulator-min-microvolt = <1226000>; + regulator-max-microvolt = <1250000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + regulator-allow-set-load; + }; + + vreg_l2a_1p0: l2 { + regulator-min-microvolt = <944000>; + regulator-max-microvolt = <1008000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + }; + + vreg_l3a_1p0: l3 { + regulator-min-microvolt = <944000>; + regulator-max-microvolt = <1008000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + }; + + vreg_l5a_0p848: l5 { + regulator-min-microvolt = <800000>; + regulator-max-microvolt = <952000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + }; + + vreg_l6a_1p3: l6 { + regulator-min-microvolt = <1304000>; + regulator-max-microvolt = <1368000>; + regulator-allow-set-load; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + }; + + vreg_l7a_1p2: l7 { + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + }; + + vreg_l8a_1p8: l8 { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + regulator-system-load = <325000>; + regulator-allow-set-load; + }; + + + vreg_l9a_1p8: l9 { + regulator-min-microvolt = <1804000>; + regulator-max-microvolt = <1896000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + regulator-allow-set-load; + }; + + vreg_l10a_1p8: l10 { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1944000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + regulator-allow-set-load; + }; + + vreg_l11a_1p8: l11 { + regulator-min-microvolt = <1784000>; + regulator-max-microvolt = <1944000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + }; + + vreg_l12a_1p8: l12 { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1944000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + }; + + vreg_l13a_1p8: l13 { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1944000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + regulator-boot-on; + regulator-always-on; + }; + + vreg_l14a_1p8: l14 { + regulator-min-microvolt = <1710000>; + regulator-max-microvolt = <1952000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + }; + + vreg_l15a_1p8: l15 { + regulator-min-microvolt = <1648000>; + regulator-max-microvolt = <2952000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + }; + + /* DRX: QM13111 */ + vreg_l16a_2p7: l16 { + regulator-min-microvolt = <2704000>; + regulator-max-microvolt = <2712000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + }; + + vreg_l17a_1p8: l17 { + regulator-min-microvolt = <1648000>; + regulator-max-microvolt = <2952000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + }; + + /* L18A (2.7V) is unused */ + + vreg_l19a_3p3: l19 { + regulator-min-microvolt = <3312000>; + regulator-max-microvolt = <3328000>; + regulator-enable-ramp-delay = <250>; + regulator-ramp-delay = <0>; + regulator-allow-set-load; + }; + }; +}; + +&sdhc_1 { + status = "okay"; + supports-cqe; + + mmc-ddr-1_8v; + mmc-hs400-1_8v; + mmc-hs400-enhanced-strobe; + + vmmc-supply = <&vreg_l4b_2p95>; + vqmmc-supply = <&vreg_l8a_1p8>; +}; + +&sdhc_2 { + status = "disabled"; + + vmmc-supply = <&vreg_l5b_29p5>; + vqmmc-supply = <&vreg_l2b_2p95>; +}; + +&pm660_gpios { + nfc_clk_req_n: nfc-clkreq-n { + pins = "gpio4"; + function = PMIC_GPIO_FUNC_NORMAL; + bias-disable; + input-enable; + power-source = <1>; + }; +}; + +&pm660l_gpios { + cam_dvdd_front_default: cam-dvdd-front-default { + pins = "gpio3"; + function = "normal"; + output-low; + power-source = <0>; + }; + + cam_dvdd_rear_default: cam-dvdd-rear-default { + pins = "gpio4"; + function = "normal"; + output-low; + power-source = <0>; + }; + + vol_key_gpio_default: vol-key-gpio-default { + pins = "gpio7"; + function = "normal"; + bias-pull-up; + input-enable; + qcom,drive-strength = ; + }; +}; + +&tlmm { + gpio-reserved-ranges = <8 4>; + + camera_rear_default: camera-rear-default { + mclk0 { + pins = "gpio32"; + function = "cam_mclk"; + drive-strength = <4>; + bias-disable; + }; + + rst { + pins = "gpio46"; + function = "gpio"; + drive-strength = <16>; + bias-disable; + }; + }; + + camera_front_default: camera-front-default { + mclk1 { + pins = "gpio33"; + function = "cam_mclk"; + drive-strength = <4>; + bias-disable; + }; + + rst { + pins = "gpio47"; + function = "gpio"; + drive-strength = <16>; + bias-disable; + }; + }; + + camera_front_iris_default: camera-front-iris-default { + mclk1 { + pins = "gpio35"; + function = "cam_mclk"; + drive-strength = <4>; + bias-disable; + }; + + rst { + pins = "gpio52"; + function = "gpio"; + drive-strength = <16>; + bias-disable; + }; + }; + + cam_avdd_front_default: cam-avdd-front-default { + pins = "gpio49"; + function = "gpio"; + bias-disable; + drive-strength = <2>; + }; + + cam_actuator_rear_default: cam-actuator-rear-default { + pins = "gpio50"; + function = "gpio"; + bias-disable; + drive-strength = <2>; + }; + + cam_avdd_rear_default: cam-avdd-rear-default { + pins = "gpio51"; + function = "gpio"; + bias-disable; + drive-strength = <2>; + }; + + panel_reset_n: panel-rst-n { + pins = "gpio53"; + function = "gpio"; + drive-strength = <8>; + bias-disable; + }; + + mdp_vsync_n: mdp-vsync-n { + pins = "gpio59"; + function = "mdp_vsync"; + drive-strength = <2>; + bias-pull-down; + }; + + ts_rst_n: ts-rst-n { + pins = "gpio66"; + function = "gpio"; + bias-disable; + drive-strength = <8>; + }; + + ts_int_active: ts-int-active { + pins = "gpio67"; + drive-strength = <16>; + bias-pull-up; + }; + + ts_vdd_default: ts-vdd-default { + pins = "gpio73"; + function = "gpio"; + bias-disable; + drive-strength = <8>; + }; + + hall_sensor_default: hall-sensor-default { + pins = "gpio75"; + function = "gpio"; + drive-strength = <2>; + bias-disable; + input-enable; + }; + +}; + +&usb3 { + status = "okay"; +}; + +&usb3_dwc3 { + dr_mode = "peripheral"; + extcon = <&extcon_usb>; +}; From d6a16a91e00854afb4286789dc17ceed80ba7502 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Thu, 26 Nov 2020 17:40:05 +0100 Subject: [PATCH 048/187] arm64: DT: sdm630: Configure CPRh and cpufreq-hw for CPU scaling Now that the CPR v3/v4/Hardened is ready and the cpufreq-hw driver got OSM programming, we can finally enable CPU scaling on both of the clusters on SDM630. --- arch/arm64/boot/dts/qcom/sdm630.dtsi | 511 ++++++++++++++++++++++++++- 1 file changed, 505 insertions(+), 6 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/sdm630.dtsi b/arch/arm64/boot/dts/qcom/sdm630.dtsi index b1c1e58310bd..1853afd642d9 100644 --- a/arch/arm64/boot/dts/qcom/sdm630.dtsi +++ b/arch/arm64/boot/dts/qcom/sdm630.dtsi @@ -52,6 +52,12 @@ &PERF_CLUSTER_SLEEP_1 &PERF_CLUSTER_SLEEP_2>; capacity-dmips-mhz = <1126>; + dynamic-power-coefficient = <118>; + operating-points-v2 = <&cpu_gold_opp_table>; + power-domains = <&apc_cprh 0>; + power-domain-names = "cprh"; + clocks = <&xo_board>; + qcom,freq-domain = <&cpufreq_hw 1>; #cooling-cells = <2>; next-level-cache = <&L2_1>; L2_1: l2-cache { @@ -71,6 +77,12 @@ &PERF_CLUSTER_SLEEP_1 &PERF_CLUSTER_SLEEP_2>; capacity-dmips-mhz = <1126>; + dynamic-power-coefficient = <118>; + operating-points-v2 = <&cpu_gold_opp_table>; + power-domains = <&apc_cprh 0>; + power-domain-names = "cprh"; + clocks = <&xo_board>; + qcom,freq-domain = <&cpufreq_hw 1>; #cooling-cells = <2>; next-level-cache = <&L2_1>; }; @@ -86,6 +98,12 @@ &PERF_CLUSTER_SLEEP_1 &PERF_CLUSTER_SLEEP_2>; capacity-dmips-mhz = <1126>; + dynamic-power-coefficient = <118>; + operating-points-v2 = <&cpu_gold_opp_table>; + power-domains = <&apc_cprh 0>; + power-domain-names = "cprh"; + clocks = <&xo_board>; + qcom,freq-domain = <&cpufreq_hw 1>; #cooling-cells = <2>; next-level-cache = <&L2_1>; }; @@ -101,6 +119,12 @@ &PERF_CLUSTER_SLEEP_1 &PERF_CLUSTER_SLEEP_2>; capacity-dmips-mhz = <1126>; + dynamic-power-coefficient = <118>; + operating-points-v2 = <&cpu_gold_opp_table>; + power-domains = <&apc_cprh 0>; + power-domain-names = "cprh"; + clocks = <&xo_board>; + qcom,freq-domain = <&cpufreq_hw 1>; #cooling-cells = <2>; next-level-cache = <&L2_1>; }; @@ -116,6 +140,12 @@ &PWR_CLUSTER_SLEEP_1 &PWR_CLUSTER_SLEEP_2>; capacity-dmips-mhz = <1024>; + dynamic-power-coefficient = <100>; + operating-points-v2 = <&cpu_silver_opp_table>; + power-domains = <&apc_cprh 1>; + power-domain-names = "cprh"; + clocks = <&xo_board>; + qcom,freq-domain = <&cpufreq_hw 0>; #cooling-cells = <2>; next-level-cache = <&L2_0>; L2_0: l2-cache { @@ -135,6 +165,12 @@ &PWR_CLUSTER_SLEEP_1 &PWR_CLUSTER_SLEEP_2>; capacity-dmips-mhz = <1024>; + dynamic-power-coefficient = <100>; + operating-points-v2 = <&cpu_silver_opp_table>; + power-domains = <&apc_cprh 1>; + power-domain-names = "cprh"; + clocks = <&xo_board>; + qcom,freq-domain = <&cpufreq_hw 0>; #cooling-cells = <2>; next-level-cache = <&L2_0>; }; @@ -150,6 +186,12 @@ &PWR_CLUSTER_SLEEP_1 &PWR_CLUSTER_SLEEP_2>; capacity-dmips-mhz = <1024>; + dynamic-power-coefficient = <100>; + operating-points-v2 = <&cpu_silver_opp_table>; + power-domains = <&apc_cprh 1>; + power-domain-names = "cprh"; + clocks = <&xo_board>; + qcom,freq-domain = <&cpufreq_hw 0>; #cooling-cells = <2>; next-level-cache = <&L2_0>; }; @@ -165,6 +207,12 @@ &PWR_CLUSTER_SLEEP_1 &PWR_CLUSTER_SLEEP_2>; capacity-dmips-mhz = <1024>; + dynamic-power-coefficient = <100>; + operating-points-v2 = <&cpu_silver_opp_table>; + power-domains = <&apc_cprh 1>; + power-domain-names = "cprh"; + clocks = <&xo_board>; + qcom,freq-domain = <&cpufreq_hw 0>; #cooling-cells = <2>; next-level-cache = <&L2_0>; }; @@ -204,6 +252,7 @@ core3 { cpu = <&CPU3>; }; + }; }; @@ -354,6 +403,175 @@ method = "smc"; }; + cpu_silver_opp_table: cpu-silver-opp-table { + compatible = "operating-points-v2"; + opp-shared; + + opp-1843200000 { + opp-hz = /bits/ 64 <1843200000>; + required-opps = <&cprh_opp8>; + qcom,pll-override = <0x094c004c>; + qcom,spare-data = <3>; + }; + opp-1728000000 { + opp-hz = /bits/ 64 <1728000000>; + required-opps = <&cprh_opp7>; + qcom,pll-override = <0x09480048>; + qcom,spare-data = <2>; + }; + opp-1536000000 { + opp-hz = /bits/ 64 <1536000000>; + required-opps = <&cprh_opp6>; + qcom,pll-override = <0x08400040>; + qcom,spare-data = <2>; + }; + opp-1382400000 { + opp-hz = /bits/ 64 <1382400000>; + required-opps = <&cprh_opp5>; + qcom,pll-override = <0x07390039>; + qcom,spare-data = <2>; + }; + opp-1094400000 { + opp-hz = /bits/ 64 <1094400000>; + required-opps = <&cprh_opp4>; + qcom,pll-override = <0x052e002e>; + qcom,spare-data = <2>; + }; + opp-883200000 { + opp-hz = /bits/ 64 <883200000>; + required-opps = <&cprh_opp3>; + qcom,pll-override = <0x04250025>; + qcom,spare-data = <1>; + }; + opp-614400000 { + opp-hz = /bits/ 64 <614400000>; + required-opps = <&cprh_opp2>; + qcom,pll-override = <0x3200020>; + qcom,pll-div = <1>; + qcom,spare-data = <1>; + }; + opp-300000000 { + opp-hz = /bits/ 64 <300000000>; + required-opps = <&cprh_opp1>; + qcom,pll-override = <0x1200020>; + qcom,spare-data = <1>; + }; + }; + + cpu_gold_opp_table: cpu-gold-opp-table { + compatible = "operating-points-v2"; + opp-shared; + + opp-2208000000 { + opp-hz = /bits/ 64 <2208000000>; + required-opps = <&cprh_opp10>; + qcom,pll-override = <0x0b5c005c>; + qcom,spare-data = <3>; + }; + opp-2150400000 { + opp-hz = /bits/ 64 <2150400000>; + required-opps = <&cprh_opp9>; + qcom,pll-override = <0x0b590059>; + qcom,spare-data = <2>; + }; + opp-2016000000 { + opp-hz = /bits/ 64 <2016000000>; + required-opps = <&cprh_opp8>; + qcom,pll-override = <0x0a540054>; + qcom,spare-data = <2>; + }; + opp-1881600000 { + opp-hz = /bits/ 64 <1881600000>; + required-opps = <&cprh_opp7>; + qcom,pll-override = <0x094e004e>; + qcom,spare-data = <2>; + }; + opp-1670400000 { + opp-hz = /bits/ 64 <1670400000>; + required-opps = <&cprh_opp6>; + qcom,pll-override = <0x08450045>; + qcom,spare-data = <2>; + }; + opp-1516800000 { + opp-hz = /bits/ 64 <1516800000>; + required-opps = <&cprh_opp5>; + qcom,pll-override = <0x073f003f>; + qcom,spare-data = <2>; + }; + opp-1344000000 { + opp-hz = /bits/ 64 <1344000000>; + required-opps = <&cprh_opp4>; + qcom,pll-override = <0x07380038>; + qcom,spare-data = <2>; + + }; + opp-1113600000 { + opp-hz = /bits/ 64 <1113600000>; + required-opps = <&cprh_opp3>; + qcom,pll-override = <0x052e002e>; + qcom,spare-data = <2>; + }; + opp-787200000 { + opp-hz = /bits/ 64 <787200000>; + required-opps = <&cprh_opp2>; + qcom,pll-override = <0x4200020>; + qcom,pll-div = <1>; + qcom,spare-data = <1>; + }; + opp-300000000 { + opp-hz = /bits/ 64 <300000000>; + required-opps = <&cprh_opp1>; + qcom,pll-override = <0x1200020>; + qcom,spare-data = <1>; + }; + }; + + + cprh_opp_table: cpr-gold-opp-table { + compatible = "operating-points-v2-qcom-level"; + + cprh_opp1: opp1 { + opp-level = <1>; + qcom,opp-fuse-level = <1>; + }; + cprh_opp2: opp2 { + opp-level = <2>; + qcom,opp-fuse-level = <1>; + }; + cprh_opp3: opp3 { + opp-level = <3>; + qcom,opp-fuse-level = <2>; + }; + cprh_opp4: opp4 { + opp-level = <4>; + qcom,opp-fuse-level = <2>; + }; + cprh_opp5: opp5 { + opp-level = <5>; + qcom,opp-fuse-level = <3>; + }; + cprh_opp6: opp6 { + opp-level = <6>; + qcom,opp-fuse-level = <3>; + }; + cprh_opp7: opp7 { + opp-level = <7>; + qcom,opp-fuse-level = <4 3>; + }; + cprh_opp8: opp8 { + opp-level = <8>; + qcom,opp-fuse-level = <4 3>; + }; + cprh_opp9: opp9 { + opp-level = <9>; + qcom,opp-fuse-level = <4>; + }; + cprh_opp10: opp10 { + opp-level = <10>; + qcom,opp-fuse-level = <5>; + }; + }; + reserved-memory { #address-cells = <2>; #size-cells = <2>; @@ -574,14 +792,190 @@ #address-cells = <1>; #size-cells = <1>; - qusb2_hstx_trim: hstx-trim@240 { - reg = <0x240 0x1>; - bits = <25 3>; + cpr_efuse_speedbin: speedbin@133 { + reg = <0x133 0x8>; + bits = <5 3>; }; - gpu_speed_bin: gpu-speed-bin@41a0 { - reg = <0x41a0 0x1>; - bits = <21 7>; + /* CPR Quotients: Power Cluster */ + cpr_quot00_pwrcl: quot00_pwrcl@221 { + reg = <0x221 0x4>; + bits = <4 12>; + }; + + cpr_quot01_pwrcl: quot01_pwrcl@239 { + reg = <0x239 0x4>; + bits = <1 12>; + }; + + cpr_quot02_pwrcl: quot02_pwrcl@21d { + reg = <0x21d 0x4>; + bits = <0 12>; + }; + + cpr_quot_offset01_pwrcl: qoff01_pwrcl@23a { + reg = <0x23a 0x4>; + bits = <5 7>; + }; + + cpr_quot_offset02_pwrcl: qoff02_pwrcl@223 { + reg = <0x223 0x4>; + bits = <0 7>; + }; + + /* CPR Quotients: Performance Cluster */ + cpr_quot00_perfcl: quot00_perfcl@22e { + reg = <0x22e 0x4>; + bits = <5 11>; + }; + + cpr_quot10_perfcl: quot10_perfcl@230 { + reg = <0x230 0x4>; + bits = <0 1>; + }; + + cpr_quot01_perfcl: quot01_perfcl@230 { + reg = <0x230 0x4>; + bits = <1 12>; + }; + + cpr_quot02_perfcl: quot02_perfcl@22d { + reg = <0x22d 0x4>; + bits = <1 12>; + }; + + cpr_quot03_perfcl: quot03_perfcl@22b { + reg = <0x22b 0x8>; + bits = <5 12>; + }; + + cpr_quot04_perfcl: quot04_perfcl@23e { + reg = <0x236 0x4>; + bits = <0 12>; + }; + + cpr_quot_offset01_perfcl: qoff01_perfcl@233 { + reg = <0x233 0x4>; + bits = <3 7>; + }; + + cpr_quot_offset02_perfcl: qoff02_perfcl@232 { + reg = <0x232 0x4>; + bits = <4 7>; + }; + + cpr_quot_offset03_perfcl: qoff03_perfcl@231 { + reg = <0x231 0x4>; + bits = <5 7>; + }; + + cpr_quot_offset04_perfcl: qoff04_perfcl@237 { + reg = <0x237 0x4>; + bits = <4 4>; + }; + + cpr_quot_offset14_perfcl: qoff14_perfcl@230 { + reg = <0x230 0x4>; + bits = <0 3>; + }; + + /* CPR Init Voltage: Power Cluster */ + cpr_init_voltage00_pwrcl: ivolt00_pwrcl@21c { + reg = <0x21c 0x4>; + bits = <2 6>; + }; + + cpr_init_voltage01_pwrcl: ivolt01_pwrcl@238 { + reg = <0x238 0x4>; + bits = <3 6>; + }; + + cpr_init_voltage02_pwrcl: ivolt02_pwrcl@219 { + reg = <0x219 0x4>; + bits = <8 6>; + }; + + /* CPR Init Voltage: Performance Cluster */ + cpr_init_voltage00_perfcl: ivolt00_perfcl@22a { + reg = <0x22a 0x4>; + bits = <1 6>; + }; + + cpr_init_voltage01_perfcl: ivolt01_perfcl@22a { + reg = <0x22a 0x4>; + bits = <7 6>; + }; + + cpr_init_voltage02_perfcl: ivolt02_perfcl@229 { + reg = <0x229 0x4>; + bits = <3 6>; + }; + + cpr_init_voltage03_perfcl: ivolt03_perfcl@228 { + reg = <0x228 0x4>; + bits = <5 6>; + }; + + cpr_init_voltage04_perfcl: ivolt04_perfcl@235 { + reg = <0x235 0x4>; + bits = <2 6>; + }; + + /* CPR Ring Oscillator: Power Cluster */ + cpr_ro_sel00_pwrcl: rosel00_pwrcl@219 { + reg = <0x219 0x4>; + bits = <4 4>; + }; + + cpr_ro_sel01_pwrcl: rosel01_pwrcl@20f { + reg = <0x20f 0x4>; + bits = <0 4>; + }; + + cpr_ro_sel02_pwrcl: rosel02_pwrcl@218 { + reg = <0x218 0x4>; + bits = <0 4>; + }; + + /* CPR Ring Oscillator: Performance Cluster */ + cpr_ro_sel00_perfcl: rosel00_perfcl@227 { + reg = <0x227 0x4>; + bits = <5 4>; + }; + + cpr_ro_sel01_perfcl: rosel01_perfcl@228 { + reg = <0x228 0x4>; + bits = <1 4>; + }; + + cpr_ro_sel02_perfcl: rosel02_perfcl@227 { + reg = <0x227 0x4>; + bits = <1 4>; + }; + + cpr_ro_sel03_perfcl: rosel03_perfcl@226 { + reg = <0x226 0x4>; + bits = <5 4>; + }; + + cpr_ro_sel04_perfcl: rosel04_perfcl@211 { + reg = <0x211 0x4>; + bits = <6 4>; + }; + + cpr_fuse_revision: cpr_fusing_rev@23b { + reg = <0x23b 0x1>; + bits = <4 3>; + }; + + qusb2_hstx_trim: hstx-trim@243 { + reg = <0x243 0x1>; + bits = <1 3>; + }; + + gpu_speed_bin: gpu_speed_bin@1a2 { + reg = <0x1a2 0x1>; + bits = <5 7>; }; }; @@ -703,6 +1097,111 @@ <&mmcc AHB_CLK_SRC>; }; + saw1: power-controller@17812000 { + compatible = "qcom,sdm660-gold-saw2-v4.1-l2", "qcom,saw2"; + reg = <0x017812000 0x1000>; + }; + + saw0: power-controller@17912000 { + compatible = "qcom,sdm660-gold-saw2-v4.1-l2", "qcom,saw2"; + reg = <0x017912000 0x1000>; + }; + + cpufreq_hw: cpufreq_hw@17816000 { + compatible = "qcom,cpufreq-hw-8998"; + + reg = <0x0179c0000 0x1000>, <0x0179c1000 0x1000>, + <0x0179c2000 0x1000>, <0x0179c3000 0x1000>; + reg-names = "osm-domain0", "freq-domain0", + "osm-domain1", "freq-domain1"; + + clocks = <&rpmcc RPM_SMD_XO_A_CLK_SRC>, + <&gcc HMSS_GPLL0_CLK_SRC>; + clock-names = "xo", "alternate"; + + #freq-domain-cells = <1>; + }; + + /* Gold and Silver cluster */ + apc_cprh: power-controller@179c4000 { + compatible = "qcom,sdm630-cprh"; + reg = <0x0179c4000 0x4000>, <0x0179c8000 0x4000>; + + clocks = <&gcc GCC_HMSS_RBCPR_CLK>; + clock-names = "ref"; + assigned-clocks = <&gcc GCC_HMSS_RBCPR_CLK>; + assigned-clock-rates = <19200000>; + + #power-domain-cells = <1>; + operating-points-v2 = <&cprh_opp_table>; + + nvmem-cells = <&cpr_efuse_speedbin>, + <&cpr_fuse_revision>, + <&cpr_quot00_perfcl>, + <&cpr_quot01_perfcl>, + <&cpr_quot02_perfcl>, + <&cpr_quot03_perfcl>, + <&cpr_quot04_perfcl>, + <&cpr_quot_offset01_perfcl>, + <&cpr_quot_offset02_perfcl>, + <&cpr_quot_offset03_perfcl>, + <&cpr_quot_offset04_perfcl>, + <&cpr_init_voltage00_perfcl>, + <&cpr_init_voltage01_perfcl>, + <&cpr_init_voltage02_perfcl>, + <&cpr_init_voltage03_perfcl>, + <&cpr_init_voltage04_perfcl>, + <&cpr_ro_sel00_perfcl>, + <&cpr_ro_sel01_perfcl>, + <&cpr_ro_sel02_perfcl>, + <&cpr_ro_sel03_perfcl>, + <&cpr_ro_sel04_perfcl>, + <&cpr_quot00_pwrcl>, + <&cpr_quot01_pwrcl>, + <&cpr_quot02_pwrcl>, + <&cpr_quot_offset01_pwrcl>, + <&cpr_quot_offset02_pwrcl>, + <&cpr_init_voltage00_pwrcl>, + <&cpr_init_voltage01_pwrcl>, + <&cpr_init_voltage02_pwrcl>, + <&cpr_ro_sel00_pwrcl>, + <&cpr_ro_sel01_pwrcl>, + <&cpr_ro_sel02_pwrcl>; + + nvmem-cell-names = "cpr_speed_bin", + "cpr_fuse_revision", + "cpr0_quotient1", + "cpr0_quotient2", + "cpr0_quotient3", + "cpr0_quotient4", + "cpr0_quotient5", + "cpr0_quotient_offset2", + "cpr0_quotient_offset3", + "cpr0_quotient_offset4", + "cpr0_quotient_offset5", + "cpr0_init_voltage1", + "cpr0_init_voltage2", + "cpr0_init_voltage3", + "cpr0_init_voltage4", + "cpr0_init_voltage5", + "cpr0_ring_osc1", + "cpr0_ring_osc2", + "cpr0_ring_osc3", + "cpr0_ring_osc4", + "cpr0_ring_osc5", + "cpr1_quotient1", + "cpr1_quotient2", + "cpr1_quotient3", + "cpr1_quotient_offset2", + "cpr1_quotient_offset3", + "cpr1_init_voltage1", + "cpr1_init_voltage2", + "cpr1_init_voltage3", + "cpr1_ring_osc1", + "cpr1_ring_osc2", + "cpr1_ring_osc3"; + }; + tsens: thermal-sensor@10ae000 { compatible = "qcom,sdm630-tsens", "qcom,tsens-v2"; reg = <0x010ae000 0x1000>, /* TM */ From d22af504894492efd620ba71a7a59f6fa736cda7 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Fri, 13 Nov 2020 22:04:43 +0100 Subject: [PATCH 049/187] cpufreq: Add SDM630/636/660 to cpufreq-dt-platdev blocklist Add the SDM630, SDM636 and SDM660 to the blocklist since the CPU scaling is handled out of this. Signed-off-by: AngeloGioacchino Del Regno --- drivers/cpufreq/cpufreq-dt-platdev.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/cpufreq/cpufreq-dt-platdev.c b/drivers/cpufreq/cpufreq-dt-platdev.c index ca1d103ec449..b255388c235c 100644 --- a/drivers/cpufreq/cpufreq-dt-platdev.c +++ b/drivers/cpufreq/cpufreq-dt-platdev.c @@ -141,6 +141,9 @@ static const struct of_device_id blocklist[] __initconst = { { .compatible = "qcom,sc7180", }, { .compatible = "qcom,sc7280", }, { .compatible = "qcom,sc8180x", }, + { .compatible = "qcom,sdm630", }, + { .compatible = "qcom,sdm636", }, + { .compatible = "qcom,sdm660", }, { .compatible = "qcom,sdm845", }, { .compatible = "qcom,sm6350", }, { .compatible = "qcom,sm8150", }, From 71718edee109c3e64e669eb2ee00870434dde876 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Tue, 15 Dec 2020 01:06:27 +0100 Subject: [PATCH 050/187] cpufreq: Add MSM8998 to cpufreq-dt-platdev blocklist Add the MSM8998 to the blocklist since the CPU scaling is handled out of this. Signed-off-by: AngeloGioacchino Del Regno --- drivers/cpufreq/cpufreq-dt-platdev.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/cpufreq/cpufreq-dt-platdev.c b/drivers/cpufreq/cpufreq-dt-platdev.c index b255388c235c..acd15986deac 100644 --- a/drivers/cpufreq/cpufreq-dt-platdev.c +++ b/drivers/cpufreq/cpufreq-dt-platdev.c @@ -136,6 +136,7 @@ static const struct of_device_id blocklist[] __initconst = { { .compatible = "qcom,apq8096", }, { .compatible = "qcom,msm8996", }, + { .compatible = "qcom,msm8998", }, { .compatible = "qcom,qcs404", }, { .compatible = "qcom,sa8155p" }, { .compatible = "qcom,sc7180", }, From 040a951871d4e8a6e8ab649e93f62601718278c3 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Tue, 20 Oct 2020 21:09:43 +0530 Subject: [PATCH 051/187] dt-bindings: arm: cpus: Document 'qcom,freq-domain' property Add devicetree documentation for 'qcom,freq-domain' property specific to Qualcomm CPUs. This property is used to reference the CPUFREQ node along with Domain ID (0/1). Signed-off-by: Manivannan Sadhasivam Signed-off-by: AngeloGioacchino Del Regno --- Documentation/devicetree/bindings/arm/cpus.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Documentation/devicetree/bindings/arm/cpus.yaml b/Documentation/devicetree/bindings/arm/cpus.yaml index f2ab6423b4af..8c9ef40ee106 100644 --- a/Documentation/devicetree/bindings/arm/cpus.yaml +++ b/Documentation/devicetree/bindings/arm/cpus.yaml @@ -305,6 +305,12 @@ properties: * arm/msm/qcom,kpss-acc.txt + qcom,freq-domain: + $ref: '/schemas/types.yaml#/definitions/phandle-array' + description: | + CPUs supporting freq-domain must set their "qcom,freq-domain" property + with phandle to a cpufreq_hw node followed by the Domain ID(0/1). + rockchip,pmu: $ref: '/schemas/types.yaml#/definitions/phandle' description: | From 0ef5eb8ae146e85301344281148f88d60703faf3 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Tue, 20 Oct 2020 21:09:44 +0530 Subject: [PATCH 052/187] dt-bindings: cpufreq: cpufreq-qcom-hw: Convert to YAML bindings Convert Qualcomm cpufreq devicetree binding to YAML. Signed-off-by: Manivannan Sadhasivam Signed-off-by: AngeloGioacchino Del Regno --- .../bindings/cpufreq/cpufreq-qcom-hw.txt | 172 --------------- .../bindings/cpufreq/cpufreq-qcom-hw.yaml | 204 ++++++++++++++++++ 2 files changed, 204 insertions(+), 172 deletions(-) delete mode 100644 Documentation/devicetree/bindings/cpufreq/cpufreq-qcom-hw.txt create mode 100644 Documentation/devicetree/bindings/cpufreq/cpufreq-qcom-hw.yaml diff --git a/Documentation/devicetree/bindings/cpufreq/cpufreq-qcom-hw.txt b/Documentation/devicetree/bindings/cpufreq/cpufreq-qcom-hw.txt deleted file mode 100644 index 9299028ee712..000000000000 --- a/Documentation/devicetree/bindings/cpufreq/cpufreq-qcom-hw.txt +++ /dev/null @@ -1,172 +0,0 @@ -Qualcomm Technologies, Inc. CPUFREQ Bindings - -CPUFREQ HW is a hardware engine used by some Qualcomm Technologies, Inc. (QTI) -SoCs to manage frequency in hardware. It is capable of controlling frequency -for multiple clusters. - -Properties: -- compatible - Usage: required - Value type: - Definition: must be "qcom,cpufreq-hw" or "qcom,cpufreq-epss". - -- clocks - Usage: required - Value type: From common clock binding. - Definition: clock handle for XO clock and GPLL0 clock. - -- clock-names - Usage: required - Value type: From common clock binding. - Definition: must be "xo", "alternate". - -- reg - Usage: required - Value type: - Definition: Addresses and sizes for the memory of the HW bases in - each frequency domain. -- reg-names - Usage: Optional - Value type: - Definition: Frequency domain name i.e. - "freq-domain0", "freq-domain1". - -- #freq-domain-cells: - Usage: required. - Definition: Number of cells in a freqency domain specifier. - -* Property qcom,freq-domain -Devices supporting freq-domain must set their "qcom,freq-domain" property with -phandle to a cpufreq_hw followed by the Domain ID(0/1) in the CPU DT node. - - -Example: - -Example 1: Dual-cluster, Quad-core per cluster. CPUs within a cluster switch -DCVS state together. - -/ { - cpus { - #address-cells = <2>; - #size-cells = <0>; - - CPU0: cpu@0 { - device_type = "cpu"; - compatible = "qcom,kryo385"; - reg = <0x0 0x0>; - enable-method = "psci"; - next-level-cache = <&L2_0>; - qcom,freq-domain = <&cpufreq_hw 0>; - L2_0: l2-cache { - compatible = "cache"; - next-level-cache = <&L3_0>; - L3_0: l3-cache { - compatible = "cache"; - }; - }; - }; - - CPU1: cpu@100 { - device_type = "cpu"; - compatible = "qcom,kryo385"; - reg = <0x0 0x100>; - enable-method = "psci"; - next-level-cache = <&L2_100>; - qcom,freq-domain = <&cpufreq_hw 0>; - L2_100: l2-cache { - compatible = "cache"; - next-level-cache = <&L3_0>; - }; - }; - - CPU2: cpu@200 { - device_type = "cpu"; - compatible = "qcom,kryo385"; - reg = <0x0 0x200>; - enable-method = "psci"; - next-level-cache = <&L2_200>; - qcom,freq-domain = <&cpufreq_hw 0>; - L2_200: l2-cache { - compatible = "cache"; - next-level-cache = <&L3_0>; - }; - }; - - CPU3: cpu@300 { - device_type = "cpu"; - compatible = "qcom,kryo385"; - reg = <0x0 0x300>; - enable-method = "psci"; - next-level-cache = <&L2_300>; - qcom,freq-domain = <&cpufreq_hw 0>; - L2_300: l2-cache { - compatible = "cache"; - next-level-cache = <&L3_0>; - }; - }; - - CPU4: cpu@400 { - device_type = "cpu"; - compatible = "qcom,kryo385"; - reg = <0x0 0x400>; - enable-method = "psci"; - next-level-cache = <&L2_400>; - qcom,freq-domain = <&cpufreq_hw 1>; - L2_400: l2-cache { - compatible = "cache"; - next-level-cache = <&L3_0>; - }; - }; - - CPU5: cpu@500 { - device_type = "cpu"; - compatible = "qcom,kryo385"; - reg = <0x0 0x500>; - enable-method = "psci"; - next-level-cache = <&L2_500>; - qcom,freq-domain = <&cpufreq_hw 1>; - L2_500: l2-cache { - compatible = "cache"; - next-level-cache = <&L3_0>; - }; - }; - - CPU6: cpu@600 { - device_type = "cpu"; - compatible = "qcom,kryo385"; - reg = <0x0 0x600>; - enable-method = "psci"; - next-level-cache = <&L2_600>; - qcom,freq-domain = <&cpufreq_hw 1>; - L2_600: l2-cache { - compatible = "cache"; - next-level-cache = <&L3_0>; - }; - }; - - CPU7: cpu@700 { - device_type = "cpu"; - compatible = "qcom,kryo385"; - reg = <0x0 0x700>; - enable-method = "psci"; - next-level-cache = <&L2_700>; - qcom,freq-domain = <&cpufreq_hw 1>; - L2_700: l2-cache { - compatible = "cache"; - next-level-cache = <&L3_0>; - }; - }; - }; - - soc { - cpufreq_hw: cpufreq@17d43000 { - compatible = "qcom,cpufreq-hw"; - reg = <0x17d43000 0x1400>, <0x17d45800 0x1400>; - reg-names = "freq-domain0", "freq-domain1"; - - clocks = <&rpmhcc RPMH_CXO_CLK>, <&gcc GPLL0>; - clock-names = "xo", "alternate"; - - #freq-domain-cells = <1>; - }; -} diff --git a/Documentation/devicetree/bindings/cpufreq/cpufreq-qcom-hw.yaml b/Documentation/devicetree/bindings/cpufreq/cpufreq-qcom-hw.yaml new file mode 100644 index 000000000000..bc81b6203e27 --- /dev/null +++ b/Documentation/devicetree/bindings/cpufreq/cpufreq-qcom-hw.yaml @@ -0,0 +1,204 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/cpufreq/cpufreq-qcom-hw.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm Technologies, Inc. CPUFREQ + +maintainers: + - Manivannan Sadhasivam + +description: | + + CPUFREQ HW is a hardware engine used by some Qualcomm Technologies, Inc. (QTI) + SoCs to manage frequency in hardware. It is capable of controlling frequency + for multiple clusters. + +properties: + compatible: + oneOf: + - description: v1 of CPUFREQ HW + items: + - const: qcom,cpufreq-hw + + - description: v2 of CPUFREQ HW (EPSS) + items: + - enum: + - qcom,sm8250-cpufreq-epss + - const: qcom,cpufreq-epss + + reg: + minItems: 2 + maxItems: 3 + items: + - description: Frequency domain 0 register region + - description: Frequency domain 1 register region + - description: Frequency domain 2 register region + + reg-names: + minItems: 2 + maxItems: 3 + items: + - const: freq-domain0 + - const: freq-domain1 + - const: freq-domain2 + + clocks: + items: + - description: XO Clock + - description: GPLL0 Clock + + clock-names: + items: + - const: xo + - const: alternate + + '#freq-domain-cells': + const: 1 + +required: + - compatible + - reg + - reg-names + - clocks + - clock-names + - '#freq-domain-cells' + +additionalProperties: false + +examples: + - | + #include + #include + + // Example 1: Dual-cluster, Quad-core per cluster. CPUs within a cluster + // switch DCVS state together. + cpus { + #address-cells = <2>; + #size-cells = <0>; + + CPU0: cpu@0 { + device_type = "cpu"; + compatible = "qcom,kryo385"; + reg = <0x0 0x0>; + enable-method = "psci"; + next-level-cache = <&L2_0>; + qcom,freq-domain = <&cpufreq_hw 0>; + L2_0: l2-cache { + compatible = "cache"; + next-level-cache = <&L3_0>; + L3_0: l3-cache { + compatible = "cache"; + }; + }; + }; + + CPU1: cpu@100 { + device_type = "cpu"; + compatible = "qcom,kryo385"; + reg = <0x0 0x100>; + enable-method = "psci"; + next-level-cache = <&L2_100>; + qcom,freq-domain = <&cpufreq_hw 0>; + L2_100: l2-cache { + compatible = "cache"; + next-level-cache = <&L3_0>; + }; + }; + + CPU2: cpu@200 { + device_type = "cpu"; + compatible = "qcom,kryo385"; + reg = <0x0 0x200>; + enable-method = "psci"; + next-level-cache = <&L2_200>; + qcom,freq-domain = <&cpufreq_hw 0>; + L2_200: l2-cache { + compatible = "cache"; + next-level-cache = <&L3_0>; + }; + }; + + CPU3: cpu@300 { + device_type = "cpu"; + compatible = "qcom,kryo385"; + reg = <0x0 0x300>; + enable-method = "psci"; + next-level-cache = <&L2_300>; + qcom,freq-domain = <&cpufreq_hw 0>; + L2_300: l2-cache { + compatible = "cache"; + next-level-cache = <&L3_0>; + }; + }; + + CPU4: cpu@400 { + device_type = "cpu"; + compatible = "qcom,kryo385"; + reg = <0x0 0x400>; + enable-method = "psci"; + next-level-cache = <&L2_400>; + qcom,freq-domain = <&cpufreq_hw 1>; + L2_400: l2-cache { + compatible = "cache"; + next-level-cache = <&L3_0>; + }; + }; + + CPU5: cpu@500 { + device_type = "cpu"; + compatible = "qcom,kryo385"; + reg = <0x0 0x500>; + enable-method = "psci"; + next-level-cache = <&L2_500>; + qcom,freq-domain = <&cpufreq_hw 1>; + L2_500: l2-cache { + compatible = "cache"; + next-level-cache = <&L3_0>; + }; + }; + + CPU6: cpu@600 { + device_type = "cpu"; + compatible = "qcom,kryo385"; + reg = <0x0 0x600>; + enable-method = "psci"; + next-level-cache = <&L2_600>; + qcom,freq-domain = <&cpufreq_hw 1>; + L2_600: l2-cache { + compatible = "cache"; + next-level-cache = <&L3_0>; + }; + }; + + CPU7: cpu@700 { + device_type = "cpu"; + compatible = "qcom,kryo385"; + reg = <0x0 0x700>; + enable-method = "psci"; + next-level-cache = <&L2_700>; + qcom,freq-domain = <&cpufreq_hw 1>; + L2_700: l2-cache { + compatible = "cache"; + next-level-cache = <&L3_0>; + }; + }; + }; + + soc { + #address-cells = <1>; + #size-cells = <1>; + + cpufreq@17d43000 { + compatible = "qcom,cpufreq-hw"; + reg = <0x17d43000 0x1400>, <0x17d45800 0x1400>; + reg-names = "freq-domain0", "freq-domain1"; + + clocks = <&rpmhcc RPMH_CXO_CLK>, <&gcc GPLL0>; + clock-names = "xo", "alternate"; + + #freq-domain-cells = <1>; + }; + }; +... From c05f3991a16c1ae416ee7ff572e3155507df4e17 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Thu, 17 Jun 2021 22:35:49 +0200 Subject: [PATCH 053/187] cpufreq: qcom-hw: Add kerneldoc to some functions Some functions may not be very straightforward to understand: add kerneldoc to some ones in order to improve readability. Signed-off-by: AngeloGioacchino Del Regno --- drivers/cpufreq/qcom-cpufreq-hw.c | 54 +++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/drivers/cpufreq/qcom-cpufreq-hw.c b/drivers/cpufreq/qcom-cpufreq-hw.c index a2be0df7e174..fd55ed92a263 100644 --- a/drivers/cpufreq/qcom-cpufreq-hw.c +++ b/drivers/cpufreq/qcom-cpufreq-hw.c @@ -54,6 +54,13 @@ struct qcom_cpufreq_data { static unsigned long cpu_hw_rate, xo_rate; static bool icc_scaling_enabled; +/** + * qcom_cpufreq_set_bw() - Set interconnect bandwidth + * @policy: CPUFreq policy structure + * @freq_khz: CPU Frequency in KHz + * + * Returns: Zero for success, otherwise negative value on errors + */ static int qcom_cpufreq_set_bw(struct cpufreq_policy *policy, unsigned long freq_khz) { @@ -75,6 +82,20 @@ static int qcom_cpufreq_set_bw(struct cpufreq_policy *policy, return ret; } +/** + * qcom_cpufreq_update_opp() - Update CPU OPP tables + * @policy: CPUFreq policy structure + * @freq_khz: CPU Frequency for OPP entry in KHz + * @volt: CPU Voltage for OPP entry in microvolts + * + * The CPU frequencies and voltages are being read from the Operating + * State Manager (OSM) and the related OPPs, read from DT, need to be + * updated to reflect what the hardware will set for each p-state. + * If there is no OPP table specified in DT, then this function will + * add dynamic ones. + * + * Returns: Zero for success, otherwise negative value on errors + */ static int qcom_cpufreq_update_opp(struct device *cpu_dev, unsigned long freq_khz, unsigned long volt) @@ -95,6 +116,17 @@ static int qcom_cpufreq_update_opp(struct device *cpu_dev, return dev_pm_opp_enable(cpu_dev, freq_hz); } +/** + * qcom_cpufreq_hw_target_index() - Set frequency/voltage + * @policy: CPUFreq policy structure + * @index: Performance state index to be set + * + * This function sends a request to the Operating State Manager + * to set a Performance State index, so, to set frequency and + * voltage for the target CPU/cluster. + * + * Returns: Always zero + */ static int qcom_cpufreq_hw_target_index(struct cpufreq_policy *policy, unsigned int index) { @@ -110,6 +142,12 @@ static int qcom_cpufreq_hw_target_index(struct cpufreq_policy *policy, return 0; } +/** + * qcom_cpufreq_hw_get() - Get current Performance State from OSM + * @cpu: CPU number + * + * Returns: Current CPU/Cluster frequency or zero + */ static unsigned int qcom_cpufreq_hw_get(unsigned int cpu) { struct qcom_cpufreq_data *data; @@ -143,6 +181,17 @@ static unsigned int qcom_cpufreq_hw_fast_switch(struct cpufreq_policy *policy, return policy->freq_table[index].frequency; } +/** + * qcom_cpufreq_hw_read_lut() - Read Lookup Table from the OSM + * @cpu_dev: CPU device + * @policy: CPUFreq policy structure + * + * The Operating State Manager Lookup Table can always be read, even + * in case it was pre-programmed by the bootloader or by TrustZone. + * Read the LUT from it in order to build OPPs containing DVFS info. + * + * Returns: Zero for success, otherwise negative number on errors. + */ static int qcom_cpufreq_hw_read_lut(struct device *cpu_dev, struct cpufreq_policy *policy) { @@ -243,6 +292,11 @@ static int qcom_cpufreq_hw_read_lut(struct device *cpu_dev, return 0; } +/* + * qcom_get_related_cpus - Get mask of CPUs in the same frequency domain + * @index: CPU number + * @m: Returned CPU mask + */ static void qcom_get_related_cpus(int index, struct cpumask *m) { struct device_node *cpu_np; From 8a7b3d8b05812d2db26a5d1b389e7bcea1ca76ed Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Thu, 17 Jun 2021 22:42:25 +0200 Subject: [PATCH 054/187] cpufreq: qcom-hw: Implement CPRh aware OSM programming On new SoCs (SDM845 onwards) the Operating State Manager (OSM) is being programmed in the bootloader and write-protected by the hypervisor, leaving to the OS read-only access to some of its registers (in order to read the Lookup Tables and also some status registers) and write access to the p-state register, for for the OS to request a specific performance state to trigger a DVFS switch on the CPU through the OSM hardware. On old SoCs though (MSM8998, SDM630/660 and variants), the bootloader will *not* initialize the OSM (and the CPRh, as it is a requirement for it) before booting the OS, making any request to trigger a performance state change ineffective, as the hardware doesn't have any Lookup Table, nor is storing any parameter to trigger a DVFS switch. In this case, basically all of the OSM registers are *not* write protected for the OS, even though some are - but write access is granted through SCM calls. This commit introduces support for OSM programming, which has to be done on these old SoCs that were distributed (almost?) always with a bootloader that does not do any CPRh nor OSM init before booting the kernel. In order to program the OSM on these SoCs, it is necessary to fullfill a "special" requirement: the Core Power Reduction Hardened (CPRh) hardware block must be initialized, as the OSM is "talking" to it in order to perform the Voltage part of DVFS; here, we are calling initialization of this through Linux generic power domains, specifically by requesting a genpd attach from the qcom-cpufreq-hw driver, which will give back voltages associated to each CPU frequency that has been declared in the OPPs, scaled and interpolated with the previous one, and will also give us parameters for the Array Power Mux (APM) and mem-acc, in order for this driver to be then able to generate the Lookup Tables that will be finally programmed to the OSM hardware. After writing the parameters to the OSM and enabling it, all the programming work will never happen anymore until a OS reboot, so all of the allocations and "the rest" will be disposed-of: this is done mainly to leave the code that was referred only to the new SoCs intact, as to also emphasize on the fact that the OSM HW is, in the end, the exact same; apart some register offsets that are slightly different, the entire logic is the same. This also adds the parameters to support CPU scaling on SDM630 and MSM8998. Signed-off-by: AngeloGioacchino Del Regno [Fixup for 5.15 by Jami] --- drivers/cpufreq/qcom-cpufreq-hw.c | 1078 ++++++++++++++++++++++++++++- 1 file changed, 1057 insertions(+), 21 deletions(-) diff --git a/drivers/cpufreq/qcom-cpufreq-hw.c b/drivers/cpufreq/qcom-cpufreq-hw.c index fd55ed92a263..d88f193b73cd 100644 --- a/drivers/cpufreq/qcom-cpufreq-hw.c +++ b/drivers/cpufreq/qcom-cpufreq-hw.c @@ -1,38 +1,261 @@ // SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2018, The Linux Foundation. All rights reserved. + * + * OSM hardware initial programming + * Copyright (C) 2020, AngeloGioacchino Del Regno + * */ #include #include +#include #include #include #include +#include #include #include #include #include +#include #include #include #include +#include +#include #define LUT_MAX_ENTRIES 40U -#define LUT_SRC GENMASK(31, 30) +#define LUT_SRC_845 GENMASK(31, 30) +#define LUT_SRC_8998 GENMASK(27, 26) +#define LUT_PLL_DIV GENMASK(25, 24) #define LUT_L_VAL GENMASK(7, 0) #define LUT_CORE_COUNT GENMASK(18, 16) +#define LUT_VOLT_VC GENMASK(21, 16) #define LUT_VOLT GENMASK(11, 0) -#define CLK_HW_DIV 2 #define LUT_TURBO_IND 1 +#define OSM_BOOT_TIME_US 5 + +#define CYCLE_COUNTER_CLK_RATIO GENMASK(5, 1) +#define OSM_XO_RATIO_VAL (10 - 1) +#define CYCLE_COUNTER_USE_XO_EDGE BIT(8) + +/* FSM Boost Control */ +#define CC_BOOST_EN BIT(0) +#define PS_BOOST_EN BIT(1) +#define DCVS_BOOST_EN BIT(2) +#define BOOST_TIMER_REG_HI GENMASK(31, 16) +#define BOOST_TIMER_REG_LO GENMASK(15, 0) + +#define PLL_WAIT_LOCK_TIME_NS 2000 +#define SAFE_FREQ_WAIT_NS 1000 +#define DEXT_DECREMENT_WAIT_NS 200 + +#define BOOST_SYNC_DELAY 5 + +#define HYSTERESIS_UP_MASK GENMASK(31, 16) +#define HYSTERESIS_DN_MASK GENMASK(15, 0) +#define HYSTERESIS_CC_NS 200 +#define HYSTERESIS_LLM_NS 65535 + +/* FSM Droop Control */ +#define PC_RET_EXIT_DROOP_EN BIT(3) +#define WFX_DROOP_EN BIT(4) +#define DCVS_DROOP_EN BIT(5) +#define DROOP_TIMER1 GENMASK(31, 16) +#define DROOP_TIMER0 GENMASK(15, 0) +#define DROOP_CTRL_VAL (BIT(3) | BIT(17) | BIT(31)) +#define DROOP_TIMER_NS 100 +#define DROOP_WAIT_RELEASE_TIMER_NS 50 +#define DROOP_RELEASE_TIMER_NS 1 + +/* PLL Override Control */ +#define PLL_OVERRIDE_DROOP_EN BIT(0) + +/* Sequencer */ +#define SEQUENCER_REG(base, n) (base + (n * 4)) +#define SEQ_APM_THRESH_VC 15 +#define SEQ_APM_THRESH_PREVC 31 +#define SEQ_MEM_ACC_LVAL 32 +#define SEQ_MEM_ACC_0 55 +#define SEQ_APM_CROSSOVER_VC 72 +#define SEQ_APM_PARAM 76 +#define SEQ_MEM_ACC_CROSSOVER_VC 88 +#define SEQ_MEM_ACC_MAX_LEVELS 4 +#define SEQ_MEMACC_REG(base, n) SEQUENCER_REG(base, SEQ_MEM_ACC_0 + n) + +/* ACD */ +#define ACD_WRITE_CTL_UPDATE_EN BIT(0) +#define ACD_WRITE_CTL_SELECT_SHIFT 1 + +/** + * struct qcom_cpufreq_soc_setup_data - Register offsets for OSM setup + * + * @reg_osm_sequencer: OSM Sequencer (used to get physical address) + * @reg_override: Override parameters + * @reg_spare: Spare parameters (MEMACC-to-VC) + * @reg_cc_zero_behav: Virtual Corner for cluster power collapse + * @reg_spm_cc_hyst: DCVS-CC Wait time for frequency inc/decrement + * @reg_spm_cc_dcvs_dis: DCVS-CC en/disable control + * @reg_spm_core_ret_map: Treat cores in retention as active/inactive + * @reg_llm_freq_vote_hyst: DCVS-LLM Wait time for frequency inc/decrement + * @reg_llm_volt_vote_hyst: DCVS-LLM Wait time for voltage inc/decrement + * @reg_llm_intf_dcvs_dis: DCVS-LLM en/disable control + * @reg_seq1: Sequencer extra register + * @reg_pdn_fsm_ctrl: Boost and Droop FSMs en/disable control + * @reg_cc_boost_timer: CC-Boost FSM wait first timer register + * @reg_dcvs_boost_timer: DCVS-Boost FSM wait first timer register + * @reg_ps_boost_timer: PS-Boost FSM wait first timer register + * @boost_timer_reg_len: Length of boost timer registers + * @reg_boost_sync_delay: PLL signal timing control for Boost + * @reg_droop_ctrl: Droop control value + * @reg_droop_release_ctrl: Wait for Droop release + * @reg_droop_unstall_ctrl: Wait for Droop unstall + * @reg_droop_wait_release_ctrl: Time to wait for state release + * @reg_droop_timer_ctrl: Droop timer + * @reg_droop_sync_delay: PLL signal timing control for Droop + * @reg_pll_override: PLL Droop Override en/disable control + * @reg_cycle_counter: OSM CPU cycle counter + * + * This structure holds the register offsets that are used to set-up + * the Operating State Manager (OSM) parameters, when it is not (or + * not entirely) configured from the bootloader and TrustZone. + * + * Acronyms used in this documentation: + * CC = Core Count + * PS = Power-Save + * VC = Virtual Corner + * LLM = Limits Load Management + * DCVS = Dynamic Clock and Voltage Scaling + */ +struct qcom_cpufreq_soc_setup_data { + /* OSM phys register offsets */ + u16 reg_osm_sequencer; + + /* Frequency domain register offsets */ + u16 reg_override; + u16 reg_spare; + u16 reg_cc_zero_behav; + u16 reg_spm_cc_hyst; + u16 reg_spm_cc_dcvs_dis; + u16 reg_spm_core_ret_map; + u16 reg_llm_freq_vote_hyst; + u16 reg_llm_volt_vote_hyst; + u16 reg_llm_intf_dcvs_dis; + u16 reg_seq1; + u16 reg_pdn_fsm_ctrl; + u16 reg_cc_boost_timer; + u16 reg_dcvs_boost_timer; + u16 reg_ps_boost_timer; + u16 boost_timer_reg_len; + u16 reg_boost_sync_delay; + u16 reg_droop_ctrl; + u16 reg_droop_release_ctrl; + u16 reg_droop_unstall_ctrl; + u16 reg_droop_wait_release_ctrl; + u16 reg_droop_timer_ctrl; + u16 reg_droop_sync_delay; + u16 reg_pll_override; + u16 reg_cycle_counter; +}; + +/** + * struct qcom_cpufreq_soc_acd_data - Adaptive Clock Distribution data + * + * @tl_delay_reg: Tunable-Length Delay (TLD) register offset + * @acd_ctrl_reg: Control Register (CR) register offset + * @softstart_reg: Soft Start Control Register (SSCR) register offset + * @ext_intf_reg: External interface configuration register offset + * @auto_xfer_reg: Auto Register-Transfer register offset + * @auto_xfer_cfg_reg: Auto Register-Transfer Configuration reg offset + * @auto_xfer_ctl_reg: Auto Register-Transfer Control register offset + * @auto_xfer_sts_reg: Auto Register-Transfer Status register offset + * @dcvs_sw_reg: Software DCVS register offset + * @gfmux_cfg_reg: Glitch-Free MUX configuration register offset + * @write_ctl_reg: Write Control register + * @write_sts_reg: Write Status register + * @tl_delay_val: Tunable-Length Delay (TLD) value + * @acd_ctrl_val: Control Register (CR) value + * @softstart_val: Soft Start Control Register (SSCR) value + * @ext_intf0_val: Initial external interface configuration value + * @ext_intf1_val: Final external interface configuration value + * @auto_xfer_val: Auto-register Transfer Control value + * + * This structure holds the register offsets (from the ACD iospace base) + * and the parameters that are required to configure the OSM to + * initialize the Adaptive Clock Distribution (ACD) system. + */ +struct qcom_cpufreq_soc_acd_data { + u8 tl_delay_reg; + u8 acd_ctrl_reg; + u8 softstart_reg; + u8 ext_intf_reg; + u8 auto_xfer_reg; + u8 auto_xfer_cfg_reg; + u8 auto_xfer_ctl_reg; + u8 auto_xfer_sts_reg; + u8 dcvs_sw_reg; + u8 gfmux_cfg_reg; + u8 write_ctl_reg; + u8 write_sts_reg; + u32 tl_delay_val; + u32 acd_ctrl_val; + u32 softstart_val; + u32 ext_intf0_val; + u32 ext_intf1_val; + u32 auto_xfer_val; +}; + +/** + * struct qcom_cpufreq_hw_params - Operating State Manager (OSM) Parameters + * + * @volt_lut_val: Value composed of: virtual corner (vc) and voltage in mV. + * @freq_lut_val: Value composed of: core count, clock source and output + * frequency in MHz. + * @override_val: PLL parameters that the OSM uses to override the previous + * setting coming from the bootloader, or when uninitialized. + * @spare_val: Spare register, used by both this driver and the OSM HW + * to identify MEM-ACC levels in relation to virtual corners. + * + * This structure holds the parameters to write to the OSM registers for + * one "Virtual Corner" (VC), or one Performance State (p-state). + */ +struct qcom_cpufreq_hw_params { + u32 volt_lut_val; + u32 freq_lut_val; + u32 override_val; + u32 spare_val; +}; #define HZ_PER_KHZ 1000 +/** + * struct qcom_cpufreq_soc_data - SoC specific register offsets of the OSM + * + * @reg_enable: OSM enable status + * @reg_index: Index of the Virtual Corner + * @reg_freq_lut: Frequency Lookup Table + * @reg_freq_lut_src_mask: Frequency Lookup Table clock-source mask + * @reg_volt_lut: Voltage Lookup Table + * @reg_perf_state: Performance State request register + * @lut_row_size: Lookup Table row size + * @clk_hw_div: Divider for "alternate" OSM clock-source + * @uses_tz: OSM already set-up and protected by TrustZone + * @setup_regs: Register offsets for OSM setup + */ struct qcom_cpufreq_soc_data { u32 reg_enable; + u32 reg_index; u32 reg_freq_lut; + u32 reg_freq_lut_src_mask; u32 reg_volt_lut; u32 reg_current_vote; u32 reg_perf_state; u8 lut_row_size; + u8 clk_hw_div; + bool uses_tz; + const struct qcom_cpufreq_soc_setup_data setup_regs; + const struct qcom_cpufreq_soc_acd_data acd_data; }; struct qcom_cpufreq_data { @@ -51,6 +274,7 @@ struct qcom_cpufreq_data { struct cpufreq_policy *policy; }; +static const char *cprh_genpd_names[] = { "cprh", NULL }; static unsigned long cpu_hw_rate, xo_rate; static bool icc_scaling_enabled; @@ -181,6 +405,574 @@ static unsigned int qcom_cpufreq_hw_fast_switch(struct cpufreq_policy *policy, return policy->freq_table[index].frequency; } +/** + * qcom_cpufreq_hw_boost_setup() - Sets up OSM boost timer registers + * @timer0_addr: Start of boost timer0 register group + * @len: Length (size) of "sub" registers in timer0 group + */ +static void qcom_cpufreq_hw_boost_setup(void __iomem *timer0_addr, u32 len) +{ + u32 val; + + /* timer_reg0 */ + val = FIELD_PREP(BOOST_TIMER_REG_LO, PLL_WAIT_LOCK_TIME_NS); + val |= FIELD_PREP(BOOST_TIMER_REG_HI, SAFE_FREQ_WAIT_NS); + writel(val, timer0_addr); + + /* timer_reg1 */ + val = FIELD_PREP(BOOST_TIMER_REG_LO, PLL_WAIT_LOCK_TIME_NS); + val |= FIELD_PREP(BOOST_TIMER_REG_HI, PLL_WAIT_LOCK_TIME_NS); + writel(val, timer0_addr + len); + + /* timer_reg2 */ + val = FIELD_PREP(BOOST_TIMER_REG_LO, DEXT_DECREMENT_WAIT_NS); + writel(val, timer0_addr + (2 * len)); +} + +/** + * qcom_cpufreq_gen_params() - Generate parameters to send to the hardware + * @cpu_dev: CPU device + * @data: SoC specific register offsets + * @hw_tbl: Pointer to return the array of parameters + * @apm_vc: APM Virtual Corner crossover number, returned to the caller + * @acc_vc: MEMACC Virtual Corner crossover number, returned to the caller + * @cpu_count: Number of CPUs in the frequency domain + * @num_entries: Number of allocated (and filled) elements in the table, + * returned to the caller + * + * This function allocates a 'qcom_cpufreq_hw_params' parameters table, + * fills it and returns it to the consumer, ready to get sent to the HW. + * Since the APM threshold is just one + * Freeing the table after usage is left to the caller. + * + * Returns: Zero for success, otherwise negative value on errors. + */ +static int qcom_cpufreq_gen_params(struct device *cpu_dev, + struct qcom_cpufreq_data *data, + struct qcom_cpufreq_hw_params **hw_tbl, + int *apm_vc, int *acc_vc, int cpu_count, + u8 *num_entries) +{ + struct device **genpd_cpr_vdev; + struct platform_device *pdev = cpufreq_get_driver_data(); + const struct qcom_cpufreq_soc_data *soc_data = data->soc_data; + struct cpr_ext_data *cpr_data; + struct dev_pm_opp *genpd_opp; + unsigned long rate; + int apm_uV, acc_uV, i, gpd_opp_cnt, ret = 0; + + ret = devm_pm_opp_attach_genpd(cpu_dev, cprh_genpd_names, &genpd_cpr_vdev); + if (ret) { + dev_err(&pdev->dev, "Could not attach to pm_domain: %d\n", ret); + return ret; + } + + if (IS_ERR_OR_NULL(*genpd_cpr_vdev)) + return -EINVAL; + + /* + * In the CPR3 driver we have assigned data to the genpd newly created + * virtual device: this contains MEMACC and APM thresholds, as passing + * them through OPPs would be an API abuse. + */ + cpr_data = dev_get_drvdata(*genpd_cpr_vdev); + if (cpr_data == NULL) { + dev_err(&pdev->dev, "Cannot get CPR data\n"); + return -ENODATA; + } + + /* Get the count of available OPPs coming from the power domain */ + gpd_opp_cnt = dev_pm_opp_get_opp_count(cpu_dev); + if (gpd_opp_cnt < 2) { + ret = gpd_opp_cnt > 0 ? -EINVAL : gpd_opp_cnt; + goto detach_gpd; + } + + /* If we get no APM voltage, the system is going to be unstable */ + apm_uV = cpr_data->apm_threshold_uV; + if (apm_uV <= 0) { + ret = -EINVAL; + goto detach_gpd; + } + + /* + * Set apm_vc to a less than zero value: this is used later in the + * logic making sure that we're returning the right virtual corner + * for APM switch. + */ + *apm_vc = -1; + + /* + * Get the ACC threshold voltage: this is optional and not every + * SoC, or every SoC version, or every binning, needs it. + */ + if (cpr_data->mem_acc_threshold_uV <= 0) { + acc_uV = INT_MAX; + *acc_vc = U8_MAX; + } else { + acc_uV = cpr_data->mem_acc_threshold_uV; + *acc_vc = -1; + } + + *hw_tbl = devm_kmalloc_array(&pdev->dev, gpd_opp_cnt, + sizeof(**hw_tbl), GFP_KERNEL); + if (!hw_tbl) { + ret = -ENOMEM; + goto detach_gpd; + } + + for (i = 0, rate = 1000; i <= gpd_opp_cnt ; rate++, i++) { + struct qcom_cpufreq_hw_params *entry = *hw_tbl + i; + struct device_node *np; + u32 pll_div, millivolts, f_src; + + /* + * Find the next enabled OPP's frequency (ignores APM/ACC). + * + * We expect to get an error when we try to go past the last + * defined frequency, so we quit the loop gracefully without + * signaling any error, as this is the expected behavior. + */ + genpd_opp = dev_pm_opp_find_freq_ceil(cpu_dev, &rate); + if (IS_ERR(genpd_opp)) + break; + + /* Get mandatory and optional properties from the OPP DT */ + np = dev_pm_opp_get_of_node(genpd_opp); + if (!np) { + ret = -ENOENT; + goto detach_gpd; + } + + if (of_property_read_u32(np, "qcom,pll-override", + &entry->override_val)) { + ret = -EINVAL; + of_node_put(np); + goto detach_gpd; + } + + if (of_property_read_u32(np, "qcom,spare-data", + &entry->spare_val)) + entry->spare_val = 0; + + if (of_property_read_u32(np, "qcom,pll-div", &pll_div)) + pll_div = 0; + + of_node_put(np); + + /* Get voltage in microvolts, then convert to millivolts */ + millivolts = dev_pm_opp_get_voltage(genpd_opp); + if (millivolts >= apm_uV && *apm_vc < 0) + *apm_vc = i; + if (millivolts >= acc_uV && *acc_vc < 0) + *acc_vc = i; + + millivolts /= 1000; + + if (millivolts < 150 || millivolts > 1400) { + dev_err(&pdev->dev, + "Read invalid voltage: %u.\n", millivolts); + return -EINVAL; + } + + /* In the OSM firmware, "Virtual Corner" levels start from 0 */ + entry->volt_lut_val = FIELD_PREP(LUT_VOLT_VC, i); + entry->volt_lut_val |= FIELD_PREP(LUT_VOLT, millivolts); + + /* + * Only the first frequency has alternate source, as it is + * always that one that is used for low power idle states. + */ + f_src = i ? 1 : 0; + f_src <<= ffs(soc_data->reg_freq_lut_src_mask) - 1; + entry->freq_lut_val = f_src | div_u64(rate, xo_rate); + entry->freq_lut_val |= FIELD_PREP(LUT_CORE_COUNT, cpu_count); + + /* + * PLL divider is not always 0 and there is no way to determine + * it automatically, as setting this value higher than DIV1 + * will make the OSM HW to effectively set the PLL at 2-4x + * the CPU frequency and then divide the CPU clock by this div, + * so this value is effectively used as both a multiplier and + * divider. + * This value cannot be calculated because it depends on + * manual calibration and is (most probably) used to choose + * a PLL frequency that gives the least possible jitter. + */ + entry->freq_lut_val |= FIELD_PREP(LUT_PLL_DIV, pll_div); + + dev_dbg(&pdev->dev, + "[%d] freq=0x%x volt=0x%x override=0x%x spare=0x%x\n", + i, entry->freq_lut_val, entry->volt_lut_val, + entry->override_val, entry->spare_val); + dev_pm_opp_put(genpd_opp); + genpd_opp = NULL; + } + + /* + * If we've got a customized mem-acc corner but we couldn't + * find any suitable crossover, or the corner is less than + * the minimum amount of required corners for mem-acc scaling, + * the values are not valid, hence fall back to LUT values. + */ + if (acc_uV != INT_MAX && *acc_vc < SEQ_MEM_ACC_MAX_LEVELS - 1) { + dev_dbg(&pdev->dev, + "MEM-ACC corner: invalid values VC%d %duV\n", + *acc_vc, acc_uV); + *acc_vc = U8_MAX; + } + + /* + * If we have probed less params than what we need, then the + * OPP table that we got from the genpd is malformed for some + * reason: in this case, do not apply the table to the HW. + */ + if (i < gpd_opp_cnt) { + dev_err(&pdev->dev, "Got bad OPP table from power domain.\n"); + ret = -EINVAL; + goto detach_gpd; + } + *num_entries = i; + +detach_gpd: + return ret; +} + +static inline u32 qcom_cpufreq_acd_regbit(u8 acd_reg_offset) +{ + return BIT(acd_reg_offset / 4); +} + +static int qcom_cpufreq_hw_acd_write_autoxfer(struct qcom_cpufreq_data *data, + void __iomem *acd_base, u32 val) +{ + const struct qcom_cpufreq_soc_data *sdata = data->soc_data; + const struct qcom_cpufreq_soc_acd_data *aregs = &sdata->acd_data; + u32 regval = 0; + + writel(val, acd_base + aregs->auto_xfer_cfg_reg); + + /* (Clear, then) Set AUTOXFER START */ + writel(0, acd_base + aregs->auto_xfer_reg); + writel(1, acd_base + aregs->auto_xfer_reg); + + /* Poll for status: if the first bit is set the transfer is done. */ + return readl_poll_timeout(acd_base + aregs->auto_xfer_sts_reg, regval, + regval & BIT(0), 1, 3); +} + +static int qcom_cpufreq_hw_acd_write_xfer(struct qcom_cpufreq_data *data, + void __iomem *acd_base, u8 reg, + u32 val) +{ + const struct qcom_cpufreq_soc_data *sdata = data->soc_data; + const struct qcom_cpufreq_soc_acd_data *aregs = &sdata->acd_data; + u32 regval = 0; + + /* Write to the register, then initiate manual transfer */ + writel(val, acd_base + reg); + + /* Clear write control register */ + writel(0, acd_base + aregs->write_ctl_reg); + + regval = (reg / 4) << ACD_WRITE_CTL_SELECT_SHIFT; + regval |= ACD_WRITE_CTL_UPDATE_EN; + writel(regval, acd_base + aregs->write_ctl_reg); + + /* Wait until ACD Local Transfer is done */ + return readl_poll_timeout(acd_base + aregs->write_sts_reg, regval, + regval & qcom_cpufreq_acd_regbit(reg), 1, 3); +} + +/** + * qcom_cpufreq_hw_acd_init() - Initialize ACD params in the OSM + * @cpu_dev: CPU device + * @policy: CPUFreq policy structure + * @index: Instance number (CPU cluster number) + * + * On some SoCs it is required to send the ACD configuration parameters + * to the OSM. This function takes the parameters from the SoC specific + * configuration and writes them only if a "osm-acdN" iospace has been + * declared (hence, it's present). + * + * Returns: Zero for success, otherwise negative number on error. + */ +static int qcom_cpufreq_hw_acd_init(struct device *cpu_dev, + struct cpufreq_policy *policy, + int index) +{ + struct platform_device *pdev = cpufreq_get_driver_data(); + struct qcom_cpufreq_data *ddata = policy->driver_data; + const struct qcom_cpufreq_soc_data *sdata = ddata->soc_data; + const struct qcom_cpufreq_soc_acd_data *aregs = &sdata->acd_data; + char acd_resname[] = "osm-acdX"; + void __iomem *acd_base; + u32 rmask; + int ret; + + snprintf(acd_resname, sizeof(acd_resname), "osm-acd%d", index); + + acd_base = devm_platform_ioremap_resource_byname(pdev, acd_resname); + if (IS_ERR(acd_base)) { + dev_vdbg(cpu_dev, "Skipping ACD initialization.\n"); + return 0; + } + + writel(aregs->tl_delay_val, acd_base + aregs->tl_delay_reg); + writel(aregs->acd_ctrl_val, acd_base + aregs->acd_ctrl_reg); + writel(aregs->softstart_val, acd_base + aregs->softstart_reg); + writel(aregs->ext_intf0_val, acd_base + aregs->ext_intf_reg); + writel(aregs->auto_xfer_val, acd_base + aregs->auto_xfer_ctl_reg); + + rmask = qcom_cpufreq_acd_regbit(aregs->acd_ctrl_reg) | + qcom_cpufreq_acd_regbit(aregs->tl_delay_reg) | + qcom_cpufreq_acd_regbit(aregs->softstart_reg) | + qcom_cpufreq_acd_regbit(aregs->ext_intf_reg); + ret = qcom_cpufreq_hw_acd_write_autoxfer(ddata, acd_base, rmask); + if (ret) + return ret; + + /* Switch CPUSS clock source to ACD clock */ + ret = qcom_cpufreq_hw_acd_write_xfer(ddata, acd_base, + aregs->gfmux_cfg_reg, 1); + if (ret) + return ret; + + /* (Set, then) Clear DCVS_SW */ + ret = qcom_cpufreq_hw_acd_write_xfer(ddata, acd_base, + aregs->dcvs_sw_reg, 1); + if (ret) + return ret; + ret = qcom_cpufreq_hw_acd_write_xfer(ddata, acd_base, + aregs->dcvs_sw_reg, 0); + if (ret) + return ret; + + /* Wait for clock switch time */ + udelay(1); + + /* Program the final ACD external interface */ + ret = qcom_cpufreq_hw_acd_write_xfer(ddata, acd_base, + aregs->ext_intf_reg, + aregs->ext_intf1_val); + if (ret) + return ret; + + /* Initiate transfer of the final ACD value */ + rmask |= qcom_cpufreq_acd_regbit(aregs->gfmux_cfg_reg); + writel(rmask, acd_base + aregs->auto_xfer_cfg_reg); + + /* Wait for ACD to stabilize. Same wait as the OSM boot time... */ + udelay(OSM_BOOT_TIME_US); + return 0; +} + +/** + * qcom_cpufreq_hw_write_lut() - Write Lookup Table params to the OSM + * @cpu_dev: CPU device + * @policy: CPUFreq policy structure + * @cpu_count: Number of CPUs in the frequency domain + * @index: Instance number (CPU cluster number) + * + * Program all the Lookup Table (LUT) entries and related thresholds + * to the Operating State Manager on platforms where the same hasn't + * been done already by the bootloader or TrustZone before booting + * the operating system's kernel; + * On these platforms, write access to the OSM is (obviously) not + * blocked by the hypervisor. + * + * Returns: Zero for success, otherwise negative number on error. + */ +static int qcom_cpufreq_hw_write_lut(struct device *cpu_dev, + struct cpufreq_policy *policy, + int cpu_count, int index) +{ + struct platform_device *pdev = cpufreq_get_driver_data(); + struct qcom_cpufreq_data *ddata = policy->driver_data; + const struct qcom_cpufreq_soc_data *sdata = ddata->soc_data; + const struct qcom_cpufreq_soc_setup_data *sregs = &sdata->setup_regs; + struct qcom_cpufreq_hw_params *hw_tbl; + struct resource *osm_rsrc; + char osm_resname[] = "osm-domainX"; + u32 sreg, seq_addr, acc_lval = 0, last_spare = 1; + u8 num_entries = 0; + int apm_vc = INT_MAX, acc_vc = U8_MAX, acc_idx = 0; + int acc_val[SEQ_MEM_ACC_MAX_LEVELS], i, ret; + + snprintf(osm_resname, sizeof(osm_resname), "osm-domain%d", index); + + /* + * On some SoCs the OSM is not getting programmed from bootloader + * and needs to be done here: in this case, we need to retrieve + * the base physical address for the "Sequencer", so we will get + * the OSM base phys and apply the sequencer offset. + * + * Note: We are not remapping this iospace because we are really + * sending the physical address through SCM calls later. + */ + osm_rsrc = platform_get_resource_byname(pdev, IORESOURCE_MEM, osm_resname); + if (!osm_rsrc) + return -ENODEV; + + seq_addr = osm_rsrc->start + sregs->reg_osm_sequencer; + + ret = qcom_cpufreq_gen_params(cpu_dev, ddata, &hw_tbl, &apm_vc, + &acc_vc, cpu_count, &num_entries); + if (ret) + return ret; + + /* If we get less than 2 entries, scaling doesn't make sense */ + if (num_entries < 2) { + dev_err(&pdev->dev, "Not enough LUT entries found (%u)\n", num_entries); + return -EINVAL; + } + + for (i = 0; i < LUT_MAX_ENTRIES; i++) { + struct qcom_cpufreq_hw_params *entry; + int pos = i * sdata->lut_row_size; + + /* + * If we have reached the end of the params table, write + * the last valid entry until the end of the OSM table. + */ + if (i < num_entries) + entry = &hw_tbl[i]; + else + entry = &hw_tbl[num_entries - 1]; + + writel(i, ddata->base + sdata->reg_index + pos); + writel(entry->volt_lut_val, ddata->base + sdata->reg_volt_lut + pos); + writel(entry->freq_lut_val, ddata->base + sdata->reg_freq_lut + pos); + writel(entry->override_val, ddata->base + sregs->reg_override + pos); + writel(entry->spare_val, ddata->base + sregs->reg_spare + pos); + + dev_dbg(cpu_dev, "Writing [%d] v:0x%x f:0x%x ovr:0x%x s:0x%x\n", i, + entry->volt_lut_val, entry->freq_lut_val, + entry->override_val, entry->spare_val); + + /* + * MEM-ACC Virtual Corner threshold voltage: this gets set + * as the pairs of corners in which there is a transition + * between one MEM-ACC level and the next one. + * + * Notes: The spare_val can never be zero; + * The first spare_val is always 1; + * The maximum number of pairs is two (four registers). + * + * Example: (C = Corner Level - M = MEM-ACC Level) + * C0 M1 - C1 M1 - C2 M2 - C3 M2 - C4 M2 - C5 M3 + * Pairs: 1-2, 4-5 + */ + if (entry->spare_val <= last_spare || + acc_idx >= SEQ_MEM_ACC_MAX_LEVELS - 1) + continue; + + /* Standard mem-acc pairs using spare_val LUT crossovers */ + last_spare = entry->spare_val; + acc_val[acc_idx] = i - 1; + acc_idx++; + acc_val[acc_idx] = i; + acc_idx++; + } + + /* Sanity check: we *must* have two mem-acc crossovers (four values) */ + if (acc_idx < SEQ_MEM_ACC_MAX_LEVELS - 1) + return -EINVAL; + + /* + * Customized mem-acc corners, if any; in this case, the last corner + * in the external (CPRh) LUT is this one, placed after the APM one. + */ + if (acc_vc > 0 && acc_vc != U8_MAX) { + sreg = SEQUENCER_REG(seq_addr, SEQ_MEM_ACC_CROSSOVER_VC); + ret = qcom_scm_io_writel(sreg, num_entries + 1); + if (ret) + return ret; + + /* + * At the price of very-slightly higher power consumption, + * switch the ACC at one corner lower than what we've found, + * as this seems to be needed on at least some MSM8998 chips + * to achieve full system stability + */ + acc_vc--; + + /* Change only if we have to move the corner down */ + if (acc_vc < acc_val[3]) { + acc_val[2] = acc_vc - 1; + acc_val[3] = acc_vc; + } + + /* If needed, sanitize previously stored vals from the LUT */ + if (acc_val[2] <= acc_val[1]) + acc_val[1] = acc_val[2] - 1; + if (acc_val[1] <= acc_val[0]) + acc_val[0] = acc_val[1] - 1; + } + + for (i = 0; i < SEQ_MEM_ACC_MAX_LEVELS; i++) { + ret = qcom_scm_io_writel(SEQ_MEMACC_REG(seq_addr, i), acc_val[i]); + if (ret) + return ret; + } + dev_dbg(cpu_dev, "Wrote MEM-ACC Pairs: [%u-%u] [%u-%u]\n", + acc_val[0], acc_val[1], acc_val[2], acc_val[3]); + + /* + * Program the L_VAL of the first corner requesting MEM-ACC + * voltage level 3 to the right sequencer register + */ + acc_lval = FIELD_GET(LUT_L_VAL, hw_tbl[acc_val[3]].freq_lut_val); + ret = qcom_scm_io_writel(SEQUENCER_REG(seq_addr, SEQ_MEM_ACC_LVAL), acc_lval); + if (ret) { + dev_dbg(cpu_dev, "Cannot send memacc l_val\n"); + return ret; + } + dev_dbg(cpu_dev, "MEM-ACC L-Val is %u\n", acc_lval); + + /* + * Array Power Mux threshold level: the first virtual corner + * that requires a switch sequence of the APM from MX to APC. + */ + if (apm_vc == INT_MAX) + apm_vc = LUT_MAX_ENTRIES - 1; + + /* + * APM crossover virtual corner refers to CPRh: there, the APM corner + * is always appended to the table (so, at the end of it, right after + * the cluster dvfs entries). + */ + writel(num_entries, ddata->base + sregs->reg_seq1); + ret = qcom_scm_io_writel(SEQUENCER_REG(seq_addr, SEQ_APM_CROSSOVER_VC), num_entries); + if (ret) + return ret; + + ret = qcom_scm_io_writel(SEQUENCER_REG(seq_addr, SEQ_APM_THRESH_VC), apm_vc); + if (ret) + return ret; + + ret = qcom_scm_io_writel(SEQUENCER_REG(seq_addr, SEQ_APM_THRESH_PREVC), apm_vc - 1); + if (ret) + return ret; + + ret = qcom_scm_io_writel(SEQUENCER_REG(seq_addr, SEQ_APM_PARAM), + (0x39 | apm_vc << 6)); + if (ret) + return ret; + dev_dbg(cpu_dev, "Wrote APM Pair: [%u-%u]\n", apm_vc - 1, apm_vc); + + /* + * We succeeded! Dispose of the table that got allocated during + * qcom_cpufreq_gen_params, as that contains parameters that are + * relevant only to the context of OSM programming, which is done + * only once. + */ + if (hw_tbl) + devm_kfree(&pdev->dev, hw_tbl); + + return 0; +} + /** * qcom_cpufreq_hw_read_lut() - Read Lookup Table from the OSM * @cpu_dev: CPU device @@ -229,14 +1021,16 @@ static int qcom_cpufreq_hw_read_lut(struct device *cpu_dev, } for (i = 0; i < LUT_MAX_ENTRIES; i++) { - data = readl_relaxed(drv_data->base + soc_data->reg_freq_lut + - i * soc_data->lut_row_size); - src = FIELD_GET(LUT_SRC, data); + data = readl(drv_data->base + soc_data->reg_freq_lut + + i * soc_data->lut_row_size); + src = data & soc_data->reg_freq_lut_src_mask; + src >>= ffs(soc_data->reg_freq_lut_src_mask) - 1; + lval = FIELD_GET(LUT_L_VAL, data); core_count = FIELD_GET(LUT_CORE_COUNT, data); - data = readl_relaxed(drv_data->base + soc_data->reg_volt_lut + - i * soc_data->lut_row_size); + data = readl(drv_data->base + soc_data->reg_volt_lut + + i * soc_data->lut_row_size); volt = FIELD_GET(LUT_VOLT, data) * 1000; if (src) @@ -274,8 +1068,7 @@ static int qcom_cpufreq_hw_read_lut(struct device *cpu_dev, prev->frequency = prev_freq; prev->flags = CPUFREQ_BOOST_FREQ; } else { - dev_warn(cpu_dev, "failed to update OPP for freq=%d\n", - freq); + dev_warn(cpu_dev, "can't update OPP for freq=%u\n", freq); } } @@ -296,11 +1089,14 @@ static int qcom_cpufreq_hw_read_lut(struct device *cpu_dev, * qcom_get_related_cpus - Get mask of CPUs in the same frequency domain * @index: CPU number * @m: Returned CPU mask + * + * Returns: Count of CPUs inserted in the cpumask or negative number for error. */ -static void qcom_get_related_cpus(int index, struct cpumask *m) +static int qcom_get_related_cpus(int index, struct cpumask *m) { struct device_node *cpu_np; struct of_phandle_args args; + int count = 0; int cpu, ret; for_each_possible_cpu(cpu) { @@ -309,15 +1105,18 @@ static void qcom_get_related_cpus(int index, struct cpumask *m) continue; ret = of_parse_phandle_with_args(cpu_np, "qcom,freq-domain", - "#freq-domain-cells", 0, - &args); + "#freq-domain-cells", 0, &args); of_node_put(cpu_np); if (ret < 0) continue; - if (index == args.args[0]) + if (index == args.args[0]) { cpumask_set_cpu(cpu, m); + count++; + } } + + return count > 0 ? count : -EINVAL; } static unsigned int qcom_lmh_get_throttle_freq(struct qcom_cpufreq_data *data) @@ -404,22 +1203,91 @@ static irqreturn_t qcom_lmh_dcvs_handle_irq(int irq, void *data) static const struct qcom_cpufreq_soc_data qcom_soc_data = { .reg_enable = 0x0, .reg_freq_lut = 0x110, + .reg_freq_lut_src_mask = LUT_SRC_845, .reg_volt_lut = 0x114, .reg_current_vote = 0x704, .reg_perf_state = 0x920, .lut_row_size = 32, + .clk_hw_div = 2, + .uses_tz = true, +}; + +static const struct qcom_cpufreq_soc_data msm8998_soc_data = { + .reg_enable = 0x4, + .reg_index = 0x150, + .reg_freq_lut = 0x154, + .reg_freq_lut_src_mask = LUT_SRC_8998, + .reg_volt_lut = 0x158, + .reg_perf_state = 0xf10, + .lut_row_size = 32, + .clk_hw_div = 1, + .uses_tz = false, + .setup_regs = { + /* Physical offset for sequencer scm calls */ + .reg_osm_sequencer = 0x300, + + /* Frequency domain offsets */ + .reg_override = 0x15c, + .reg_spare = 0x164, + .reg_cc_zero_behav = 0x0c, + .reg_spm_cc_hyst = 0x1c, + .reg_spm_cc_dcvs_dis = 0x20, + .reg_spm_core_ret_map = 0x24, + .reg_llm_freq_vote_hyst = 0x2c, + .reg_llm_volt_vote_hyst = 0x30, + .reg_llm_intf_dcvs_dis = 0x34, + .reg_seq1 = 0x48, + .reg_pdn_fsm_ctrl = 0x70, + .reg_cc_boost_timer = 0x74, + .reg_dcvs_boost_timer = 0x84, + .reg_ps_boost_timer = 0x94, + .boost_timer_reg_len = 0x4, + .reg_boost_sync_delay = 0xa0, + .reg_droop_ctrl = 0xa4, + .reg_droop_release_ctrl = 0xa8, + .reg_droop_unstall_ctrl = 0xac, + .reg_droop_wait_release_ctrl = 0xb0, + .reg_droop_timer_ctrl = 0xb8, + .reg_droop_sync_delay = 0xbc, + .reg_pll_override = 0xc0, + .reg_cycle_counter = 0xf00, + }, + .acd_data = { + .acd_ctrl_reg = 0x4, + .tl_delay_reg = 0x8, + .softstart_reg = 0x28, + .ext_intf_reg = 0x30, + .dcvs_sw_reg = 0x34, + .gfmux_cfg_reg = 0x3c, + .auto_xfer_cfg_reg = 0x80, + .auto_xfer_reg = 0x84, + .auto_xfer_ctl_reg = 0x88, + .auto_xfer_sts_reg = 0x8c, + .write_ctl_reg = 0x90, + .write_sts_reg = 0x94, + .tl_delay_val = 38417, + .acd_ctrl_val = 0x2b5ffd, + .softstart_val = 0x501, + .ext_intf0_val = 0x2cf9ae8, + .ext_intf1_val = 0x2cf9afe, + .auto_xfer_val = 0x15, + }, }; static const struct qcom_cpufreq_soc_data epss_soc_data = { .reg_enable = 0x0, .reg_freq_lut = 0x100, + .reg_freq_lut_src_mask = LUT_SRC_845, .reg_volt_lut = 0x200, .reg_perf_state = 0x320, .lut_row_size = 4, + .clk_hw_div = 2, + .uses_tz = true, }; static const struct of_device_id qcom_cpufreq_hw_match[] = { { .compatible = "qcom,cpufreq-hw", .data = &qcom_soc_data }, + { .compatible = "qcom,cpufreq-hw-8998", .data = &msm8998_soc_data }, { .compatible = "qcom,cpufreq-epss", .data = &epss_soc_data }, {} }; @@ -470,6 +1338,130 @@ static void qcom_cpufreq_hw_lmh_exit(struct qcom_cpufreq_data *data) free_irq(data->throttle_irq, data); } +/** + * qcom_cpufreq_hw_osm_setup() - Setup and enable the OSM + * @cpu_dev: CPU device + * @policy: CPUFreq policy structure + * @cpu_count: Number of CPUs in the frequency domain + * + * On some platforms, the Operating State Manager (OSM) is not getting + * programmed by the bootloader, nor by TrustZone before booting the OS + * and its register space is not write-protected by the hypervisor. + * In this case, to achieve CPU DVFS, it is needed to program it from + * the OS itself, which includes setting LUT and all the various tunables + * that are required for it to manage the CPU frequencies and voltages + * on its own. + * Calling this function on a platform that had the OSM set-up by TZ + * will result in a hypervisor fault with system reboot in most cases. + * + * Returns: Zero for success, otherwise negative number on errors. + */ +static int qcom_cpufreq_hw_osm_setup(struct device *cpu_dev, + struct cpufreq_policy *policy, + int cpu_count, int index) +{ + struct qcom_cpufreq_data *drv_data = policy->driver_data; + const struct qcom_cpufreq_soc_setup_data *setup_regs; + u32 val; + int ret; + + ret = qcom_cpufreq_hw_write_lut(cpu_dev, policy, cpu_count, index); + if (ret) + return ret; + + setup_regs = &drv_data->soc_data->setup_regs; + + /* Set OSM to XO clock ratio and use XO edge for the cycle counter */ + val = FIELD_PREP(CYCLE_COUNTER_CLK_RATIO, OSM_XO_RATIO_VAL); + val |= CYCLE_COUNTER_USE_XO_EDGE; + + /* Enable the CPU cycle counter */ + val |= BIT(0); + writel(val, drv_data->base + setup_regs->reg_cycle_counter); + + /* CoreCount DCVS Policy: Wait time for frequency inc/decrement */ + val = FIELD_PREP(HYSTERESIS_UP_MASK, HYSTERESIS_CC_NS); + val |= FIELD_PREP(HYSTERESIS_DN_MASK, HYSTERESIS_CC_NS); + writel(val, drv_data->base + setup_regs->reg_spm_cc_hyst); + + /* Set the frequency index 0 and override for cluster power collapse */ + writel(BIT(0), drv_data->base + setup_regs->reg_cc_zero_behav); + + /* Treat cores in retention as active */ + writel(0, drv_data->base + setup_regs->reg_spm_core_ret_map); + + /* Enable CoreCount based DCVS */ + writel(0, drv_data->base + setup_regs->reg_spm_cc_dcvs_dis); + + /* CoreCount DCVS-LLM Policy: Wait time for frequency inc/decrement */ + val = FIELD_PREP(HYSTERESIS_UP_MASK, HYSTERESIS_LLM_NS); + val |= FIELD_PREP(HYSTERESIS_DN_MASK, HYSTERESIS_LLM_NS); + writel(val, drv_data->base + setup_regs->reg_llm_freq_vote_hyst); + + /* CoreCount DCVS-LLM Policy: Wait time for voltage inc/decrement */ + val = FIELD_PREP(HYSTERESIS_UP_MASK, HYSTERESIS_LLM_NS); + val |= FIELD_PREP(HYSTERESIS_DN_MASK, HYSTERESIS_LLM_NS); + writel(val, drv_data->base + setup_regs->reg_llm_volt_vote_hyst); + + /* Enable LLM frequency+voltage voting */ + writel(0, drv_data->base + setup_regs->reg_llm_intf_dcvs_dis); + + /* Setup Boost FSM Timers */ + qcom_cpufreq_hw_boost_setup(drv_data->base + setup_regs->reg_cc_boost_timer, + setup_regs->boost_timer_reg_len); + qcom_cpufreq_hw_boost_setup(drv_data->base + setup_regs->reg_dcvs_boost_timer, + setup_regs->boost_timer_reg_len); + qcom_cpufreq_hw_boost_setup(drv_data->base + setup_regs->reg_ps_boost_timer, + setup_regs->boost_timer_reg_len); + + /* PLL signal timing control for Boost */ + writel(BOOST_SYNC_DELAY, drv_data->base + setup_regs->reg_boost_sync_delay); + + /* Setup WFx and PC/RET droop unstall */ + val = FIELD_PREP(DROOP_TIMER1, DROOP_TIMER_NS); + val |= FIELD_PREP(DROOP_TIMER0, DROOP_TIMER_NS); + writel(val, drv_data->base + setup_regs->reg_droop_unstall_ctrl); + + /* Setup WFx and PC/RET droop wait-to-release */ + val = FIELD_PREP(DROOP_TIMER1, DROOP_WAIT_RELEASE_TIMER_NS); + val |= FIELD_PREP(DROOP_TIMER0, DROOP_WAIT_RELEASE_TIMER_NS); + writel(val, drv_data->base + setup_regs->reg_droop_wait_release_ctrl); + + /* PLL signal timing control for Droop */ + writel(1, drv_data->base + setup_regs->reg_droop_sync_delay); + + /* Setup DCVS timers */ + writel(DROOP_RELEASE_TIMER_NS, + drv_data->base + setup_regs->reg_droop_release_ctrl); + writel(DROOP_TIMER_NS, drv_data->base + setup_regs->reg_droop_timer_ctrl); + + /* Setup Droop control */ + val = readl(drv_data->base + setup_regs->reg_droop_ctrl); + val |= DROOP_CTRL_VAL; + writel(val, drv_data->base + setup_regs->reg_droop_ctrl); + + /* Enable CC-Boost, DCVS-Boost, PS-Boost, WFx, PC/RET, DCVS FSM */ + val = readl(drv_data->base + setup_regs->reg_pdn_fsm_ctrl); + val |= CC_BOOST_EN | PS_BOOST_EN | DCVS_BOOST_EN; + val |= WFX_DROOP_EN | PC_RET_EXIT_DROOP_EN | DCVS_DROOP_EN; + writel(val, drv_data->base + setup_regs->reg_pdn_fsm_ctrl); + + /* Enable PLL Droop Override */ + val = PLL_OVERRIDE_DROOP_EN; + writel(val, drv_data->base + setup_regs->reg_pll_override); + + /* Initialize the Adaptive Clock Distribution */ + ret = qcom_cpufreq_hw_acd_init(cpu_dev, policy, index); + if (ret) + return ret; + + /* We're ready: enable the OSM and give it time to boot (5uS) */ + writel(1, drv_data->base + drv_data->soc_data->reg_enable); + udelay(OSM_BOOT_TIME_US); + + return 0; +} + static int qcom_cpufreq_hw_cpu_init(struct cpufreq_policy *policy) { struct platform_device *pdev = cpufreq_get_driver_data(); @@ -480,7 +1472,8 @@ static int qcom_cpufreq_hw_cpu_init(struct cpufreq_policy *policy) struct resource *res; void __iomem *base; struct qcom_cpufreq_data *data; - int ret, index; + char fdom_resname[] = "freq-domainX"; + int cpu_count, index, ret; cpu_dev = get_cpu_device(policy->cpu); if (!cpu_dev) { @@ -501,7 +1494,9 @@ static int qcom_cpufreq_hw_cpu_init(struct cpufreq_policy *policy) index = args.args[0]; - res = platform_get_resource(pdev, IORESOURCE_MEM, index); + snprintf(fdom_resname, sizeof(fdom_resname), "freq-domain%d", index); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, fdom_resname); if (!res) { dev_err(dev, "failed to get mem resource %d\n", index); return -ENODEV; @@ -620,9 +1615,50 @@ static struct cpufreq_driver cpufreq_qcom_hw_driver = { static int qcom_cpufreq_hw_driver_probe(struct platform_device *pdev) { + const struct qcom_cpufreq_soc_data *soc_data; + struct device_node *pd_node; + struct platform_device *pd_dev; struct device *cpu_dev; struct clk *clk; - int ret; + int clk_div, ret; + + cpu_dev = get_cpu_device(0); + if (!cpu_dev) + return -EPROBE_DEFER; + + soc_data = of_device_get_match_data(&pdev->dev); + if (!soc_data) + return -EINVAL; + + if (!soc_data->uses_tz) { + /* + * When the OSM is not pre-programmed from TZ, we will + * need to program the sequencer through SCM calls. + */ + if (!qcom_scm_is_available()) + return -EPROBE_DEFER; + + /* + * If there are no power-domains, OSM programming cannot be + * performed, as in that case, we wouldn't know where to take + * the params from... + */ + pd_node = of_parse_phandle(cpu_dev->of_node, "power-domains", 0); + if (!pd_node) { + ret = PTR_ERR(pd_node); + dev_err(cpu_dev, "power domain not found: %d\n", ret); + return ret; + } + + /* + * If the power domain device is not registered yet, then + * defer probing this driver until that is available. + */ + pd_dev = of_find_device_by_node(pd_node); + if (!pd_dev || !pd_dev->dev.driver || + !device_is_bound(&pd_dev->dev)) + return -EPROBE_DEFER; + } clk = clk_get(&pdev->dev, "xo"); if (IS_ERR(clk)) @@ -635,16 +1671,16 @@ static int qcom_cpufreq_hw_driver_probe(struct platform_device *pdev) if (IS_ERR(clk)) return PTR_ERR(clk); - cpu_hw_rate = clk_get_rate(clk) / CLK_HW_DIV; + clk_div = soc_data->clk_hw_div; + if (clk_div == 0) + clk_div++; + + cpu_hw_rate = clk_get_rate(clk) / clk_div; clk_put(clk); cpufreq_qcom_hw_driver.driver_data = pdev; /* Check for optional interconnect paths on CPU0 */ - cpu_dev = get_cpu_device(0); - if (!cpu_dev) - return -EPROBE_DEFER; - ret = dev_pm_opp_of_find_icc_paths(cpu_dev, NULL); if (ret) return ret; From 819bb21db6bb930972bc9075b41f048ad825ee0a Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Tue, 19 Jan 2021 13:03:11 +0100 Subject: [PATCH 055/187] cpufreq: qcom-hw: Allow getting the maximum transition latency for OPPs In order to fine-tune the frequency scaling from various governors, allow to set a maximum transition latency from OPPs, which may be different depending on the SoC. Signed-off-by: AngeloGioacchino Del Regno [Fixup for 5.15 by Jami] --- drivers/cpufreq/qcom-cpufreq-hw.c | 37 ++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/drivers/cpufreq/qcom-cpufreq-hw.c b/drivers/cpufreq/qcom-cpufreq-hw.c index d88f193b73cd..e215ca0f6560 100644 --- a/drivers/cpufreq/qcom-cpufreq-hw.c +++ b/drivers/cpufreq/qcom-cpufreq-hw.c @@ -1473,6 +1473,7 @@ static int qcom_cpufreq_hw_cpu_init(struct cpufreq_policy *policy) void __iomem *base; struct qcom_cpufreq_data *data; char fdom_resname[] = "freq-domainX"; + unsigned int transition_latency; int cpu_count, index, ret; cpu_dev = get_cpu_device(policy->cpu); @@ -1523,6 +1524,25 @@ static int qcom_cpufreq_hw_cpu_init(struct cpufreq_policy *policy) data->soc_data = of_device_get_match_data(&pdev->dev); data->base = base; data->res = res; + policy->driver_data = data; + + cpu_count = qcom_get_related_cpus(index, policy->cpus); + if (!cpumask_weight(policy->cpus)) { + dev_err(dev, "Domain-%d failed to get related CPUs\n", index); + ret = -ENOENT; + goto error; + } + + policy->dvfs_possible_from_any_cpu = true; + if (!data->soc_data->uses_tz) { + ret = qcom_cpufreq_hw_osm_setup(cpu_dev, policy, + cpu_count, index); + if (ret) { + dev_err(dev, "Cannot setup the OSM for CPU%d: %d\n", + policy->cpu, ret); + goto error; + } + } /* HW should be in enabled state to proceed */ if (!(readl_relaxed(base + data->soc_data->reg_enable) & 0x1)) { @@ -1531,16 +1551,6 @@ static int qcom_cpufreq_hw_cpu_init(struct cpufreq_policy *policy) goto error; } - qcom_get_related_cpus(index, policy->cpus); - if (!cpumask_weight(policy->cpus)) { - dev_err(dev, "Domain-%d failed to get related CPUs\n", index); - ret = -ENOENT; - goto error; - } - - policy->driver_data = data; - policy->dvfs_possible_from_any_cpu = true; - ret = qcom_cpufreq_hw_read_lut(cpu_dev, policy); if (ret) { dev_err(dev, "Domain-%d failed to read LUT\n", index); @@ -1554,6 +1564,12 @@ static int qcom_cpufreq_hw_cpu_init(struct cpufreq_policy *policy) goto error; } + transition_latency = dev_pm_opp_get_max_transition_latency(cpu_dev); + if (!transition_latency) + transition_latency = CPUFREQ_ETERNAL; + + policy->cpuinfo.transition_latency = transition_latency; + if (policy_has_boost_freq(policy)) { ret = cpufreq_enable_boost_support(); if (ret) @@ -1566,6 +1582,7 @@ static int qcom_cpufreq_hw_cpu_init(struct cpufreq_policy *policy) return 0; error: + policy->driver_data = NULL; kfree(data); unmap_base: iounmap(base); From 6adc6091be484280bd13c84f8e5617763fc4dfdd Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Sat, 9 Jan 2021 12:58:27 +0100 Subject: [PATCH 056/187] dt-bindings: cpufreq: qcom-hw: Add bindings for 8998 The OSM programming addition has been done under the qcom,cpufreq-hw-8998 compatible name: specify the requirement of two additional register spaces for this functionality. This implementation, with the same compatible, has been tested on MSM8998 and SDM630. Signed-off-by: AngeloGioacchino Del Regno --- .../bindings/cpufreq/cpufreq-qcom-hw.yaml | 67 ++++++++++++++----- 1 file changed, 52 insertions(+), 15 deletions(-) diff --git a/Documentation/devicetree/bindings/cpufreq/cpufreq-qcom-hw.yaml b/Documentation/devicetree/bindings/cpufreq/cpufreq-qcom-hw.yaml index bc81b6203e27..29b663321a0b 100644 --- a/Documentation/devicetree/bindings/cpufreq/cpufreq-qcom-hw.yaml +++ b/Documentation/devicetree/bindings/cpufreq/cpufreq-qcom-hw.yaml @@ -18,6 +18,10 @@ description: | properties: compatible: oneOf: + - description: Non-secure v1 of CPUFREQ HW + items: + - const: qcom,cpufreq-hw-8998 + - description: v1 of CPUFREQ HW items: - const: qcom,cpufreq-hw @@ -28,21 +32,9 @@ properties: - qcom,sm8250-cpufreq-epss - const: qcom,cpufreq-epss - reg: - minItems: 2 - maxItems: 3 - items: - - description: Frequency domain 0 register region - - description: Frequency domain 1 register region - - description: Frequency domain 2 register region + reg: {} - reg-names: - minItems: 2 - maxItems: 3 - items: - - const: freq-domain0 - - const: freq-domain1 - - const: freq-domain2 + reg-names: {} clocks: items: @@ -57,10 +49,55 @@ properties: '#freq-domain-cells': const: 1 +if: + properties: + compatible: + contains: + const: qcom,cpufreq-hw-8998 +then: + properties: + reg: + minItems: 2 + maxItems: 6 + items: + - description: Frequency domain 0 register region + - description: Operating State Manager domain 0 register region + - description: Frequency domain 1 register region + - description: Operating State Manager domain 1 register region + - description: PLL ACD domain 0 register region (if ACD programming required) + - description: PLL ACD domain 1 register region (if ACD programming required) + + reg-names: + minItems: 2 + maxItems: 6 + items: + - const: "osm-domain0" + - const: "freq-domain0" + - const: "osm-domain1" + - const: "freq-domain1" + - const: "osm-acd0" + - const: "osm-acd1" + +else: + properties: + reg: + minItems: 2 + maxItems: 3 + items: + - description: Frequency domain 0 register region + - description: Frequency domain 1 register region + - description: Frequency domain 2 register region + reg-names: + minItems: 2 + maxItems: 3 + items: + - const: "freq-domain0" + - const: "freq-domain1" + - const: "freq-domain2" + required: - compatible - reg - - reg-names - clocks - clock-names - '#freq-domain-cells' From 786993afffbb821803e7bf88fd28faeab72e66b9 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Thu, 17 Jun 2021 23:05:40 +0200 Subject: [PATCH 057/187] dt-bindings: cpufreq: qcom-hw: Make reg-names a required property The property reg-names is required after the addition of the OSM programming sequence, as that mandates specifying different register domains; to avoid confusion and improve devicetree readability, specifying the regions names was made mandatory. --- Documentation/devicetree/bindings/cpufreq/cpufreq-qcom-hw.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/cpufreq/cpufreq-qcom-hw.yaml b/Documentation/devicetree/bindings/cpufreq/cpufreq-qcom-hw.yaml index 29b663321a0b..17fd6a6cefb0 100644 --- a/Documentation/devicetree/bindings/cpufreq/cpufreq-qcom-hw.yaml +++ b/Documentation/devicetree/bindings/cpufreq/cpufreq-qcom-hw.yaml @@ -98,6 +98,7 @@ else: required: - compatible - reg + - reg-names - clocks - clock-names - '#freq-domain-cells' From f833e21372680135e2f7f965719325cbb319a2f9 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Tue, 24 Nov 2020 20:31:05 +0100 Subject: [PATCH 058/187] soc: qcom: cpr: Move common functions to new file In preparation for implementing a new driver that will be handling CPRv3, CPRv4 and CPR-Hardened, format out common functions to a new file. Signed-off-by: AngeloGioacchino Del Regno [Fixup for 5.15 by Jami] --- drivers/soc/qcom/Makefile | 2 +- drivers/soc/qcom/cpr-common.c | 349 +++++++++++++++++++++++++++++ drivers/soc/qcom/cpr-common.h | 113 ++++++++++ drivers/soc/qcom/cpr.c | 408 +++------------------------------- 4 files changed, 490 insertions(+), 382 deletions(-) create mode 100644 drivers/soc/qcom/cpr-common.c create mode 100644 drivers/soc/qcom/cpr-common.h diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 70d5de69fd7b..bed55881dfd9 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -3,7 +3,7 @@ CFLAGS_rpmh-rsc.o := -I$(src) obj-$(CONFIG_QCOM_AOSS_QMP) += qcom_aoss.o obj-$(CONFIG_QCOM_GENI_SE) += qcom-geni-se.o obj-$(CONFIG_QCOM_COMMAND_DB) += cmd-db.o -obj-$(CONFIG_QCOM_CPR) += cpr.o +obj-$(CONFIG_QCOM_CPR) += cpr-common.o cpr.o obj-$(CONFIG_QCOM_GSBI) += qcom_gsbi.o obj-$(CONFIG_QCOM_MDT_LOADER) += mdt_loader.o obj-$(CONFIG_QCOM_OCMEM) += ocmem.o diff --git a/drivers/soc/qcom/cpr-common.c b/drivers/soc/qcom/cpr-common.c new file mode 100644 index 000000000000..53d4827973cb --- /dev/null +++ b/drivers/soc/qcom/cpr-common.c @@ -0,0 +1,349 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. + * Copyright (c) 2019, Linaro Limited + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cpr-common.h" + +int cpr_populate_ring_osc_idx(struct device *dev, + struct fuse_corner *fuse_corner, + const struct cpr_fuse *cpr_fuse, + int num_fuse_corners) +{ + struct fuse_corner *end = fuse_corner + num_fuse_corners; + u32 data; + int ret; + + for (; fuse_corner < end; fuse_corner++, cpr_fuse++) { + ret = nvmem_cell_read_variable_le_u32(dev, cpr_fuse->ring_osc, &data); + if (ret) + return ret; + fuse_corner->ring_osc_idx = data; + } + + return 0; +} + +int cpr_read_fuse_uV(int init_v_width, int step_size_uV, int ref_uV, + int adj, int step_volt, const char *init_v_efuse, + struct device *dev) +{ + int steps, uV; + u32 bits = 0; + int ret; + + ret = nvmem_cell_read_variable_le_u32(dev, init_v_efuse, &bits); + if (ret) + return ret; + + steps = bits & (BIT(init_v_width - 1) - 1); + /* Not two's complement.. instead highest bit is sign bit */ + if (bits & BIT(init_v_width - 1)) + steps = -steps; + + uV = ref_uV + steps * step_size_uV; + + /* Apply open-loop fixed adjustments to fused values */ + uV += adj; + + return DIV_ROUND_UP(uV, step_volt) * step_volt; +} + +const struct cpr_fuse *cpr_get_fuses(struct device *dev, int tid, + int num_fuse_corners) +{ + struct cpr_fuse *fuses; + int i; + + fuses = devm_kcalloc(dev, num_fuse_corners, + sizeof(struct cpr_fuse), + GFP_KERNEL); + if (!fuses) + return ERR_PTR(-ENOMEM); + + for (i = 0; i < num_fuse_corners; i++) { + char tbuf[50]; + + snprintf(tbuf, sizeof(tbuf), "cpr%d_ring_osc%d", tid, i + 1); + fuses[i].ring_osc = devm_kstrdup(dev, tbuf, GFP_KERNEL); + if (!fuses[i].ring_osc) + return ERR_PTR(-ENOMEM); + + snprintf(tbuf, sizeof(tbuf), + "cpr%d_init_voltage%d", tid, i + 1); + fuses[i].init_voltage = devm_kstrdup(dev, tbuf, + GFP_KERNEL); + if (!fuses[i].init_voltage) + return ERR_PTR(-ENOMEM); + + snprintf(tbuf, sizeof(tbuf), "cpr%d_quotient%d", tid, i + 1); + fuses[i].quotient = devm_kstrdup(dev, tbuf, GFP_KERNEL); + if (!fuses[i].quotient) + return ERR_PTR(-ENOMEM); + + snprintf(tbuf, sizeof(tbuf), + "cpr%d_quotient_offset%d", tid, i + 1); + fuses[i].quotient_offset = devm_kstrdup(dev, tbuf, + GFP_KERNEL); + if (!fuses[i].quotient_offset) + return ERR_PTR(-ENOMEM); + } + + return fuses; +} + +int cpr_populate_fuse_common(struct device *dev, + struct fuse_corner_data *fdata, + const struct cpr_fuse *cpr_fuse, + struct fuse_corner *fuse_corner, + int step_volt, int init_v_width, + int init_v_step) +{ + int uV, ret; + + /* Populate uV */ + uV = cpr_read_fuse_uV(init_v_width, init_v_step, + fdata->ref_uV, fdata->volt_oloop_adjust, + step_volt, cpr_fuse->init_voltage, dev); + if (uV < 0) + return uV; + + /* + * Update SoC voltages: platforms might choose a different + * regulators than the one used to characterize the algorithms + * (ie, init_voltage_step). + */ + fdata->min_uV = roundup(fdata->min_uV, step_volt); + fdata->max_uV = roundup(fdata->max_uV, step_volt); + + fuse_corner->min_uV = fdata->min_uV; + fuse_corner->max_uV = fdata->max_uV; + fuse_corner->uV = clamp(uV, fuse_corner->min_uV, fuse_corner->max_uV); + + /* Populate target quotient by scaling */ + ret = nvmem_cell_read_variable_le_u32(dev, cpr_fuse->quotient, &fuse_corner->quot); + if (ret) + return ret; + + fuse_corner->quot *= fdata->quot_scale; + fuse_corner->quot += fdata->quot_offset; + fuse_corner->quot += fdata->quot_adjust; + + return 0; +} + +/* + * Returns: Index of the initial corner or negative number for error. + */ +int cpr_find_initial_corner(struct device *dev, struct clk *cpu_clk, + struct corner *corners, int num_corners) +{ + unsigned long rate; + struct corner *iter, *corner; + const struct corner *end; + unsigned int ret = 0; + + if (!cpu_clk) + return -EINVAL; + + end = &corners[num_corners - 1]; + rate = clk_get_rate(cpu_clk); + + /* + * Some bootloaders set a CPU clock frequency that is not defined + * in the OPP table. When running at an unlisted frequency, + * cpufreq_online() will change to the OPP which has the lowest + * frequency, at or above the unlisted frequency. + * Since cpufreq_online() always "rounds up" in the case of an + * unlisted frequency, this function always "rounds down" in case + * of an unlisted frequency. That way, when cpufreq_online() + * triggers the first ever call to cpr_set_performance_state(), + * it will correctly determine the direction as UP. + */ + for (iter = corners; iter <= end; iter++) { + if (iter->freq > rate) + break; + ret++; + if (iter->freq == rate) { + corner = iter; + break; + } + if (iter->freq < rate) + corner = iter; + } + + if (!corner) { + dev_err(dev, "boot up corner not found\n"); + return -EINVAL; + } + + dev_dbg(dev, "boot up perf state: %u\n", ret); + + return ret; +} + +u32 cpr_get_fuse_corner(struct dev_pm_opp *opp, u32 tid) +{ + struct device_node *np; + u32 fc; + + np = dev_pm_opp_get_of_node(opp); + if (of_property_read_u32_index(np, "qcom,opp-fuse-level", tid, &fc)) { + pr_debug("%s: missing 'qcom,opp-fuse-level' property\n", + __func__); + fc = 0; + } + + of_node_put(np); + + return fc; +} + +unsigned long cpr_get_opp_hz_for_req(struct dev_pm_opp *ref, + struct device *cpu_dev) +{ + u64 rate = 0; + struct device_node *ref_np; + struct device_node *desc_np; + struct device_node *child_np = NULL; + struct device_node *child_req_np = NULL; + + desc_np = dev_pm_opp_of_get_opp_desc_node(cpu_dev); + if (!desc_np) + return 0; + + ref_np = dev_pm_opp_get_of_node(ref); + if (!ref_np) + goto out_ref; + + do { + of_node_put(child_req_np); + child_np = of_get_next_available_child(desc_np, child_np); + child_req_np = of_parse_phandle(child_np, "required-opps", 0); + } while (child_np && child_req_np != ref_np); + + if (child_np && child_req_np == ref_np) + of_property_read_u64(child_np, "opp-hz", &rate); + + of_node_put(child_req_np); + of_node_put(child_np); + of_node_put(ref_np); +out_ref: + of_node_put(desc_np); + + return (unsigned long) rate; +} + +int cpr_calculate_scaling(const char *quot_offset, + struct device *dev, + const struct fuse_corner_data *fdata, + const struct corner *corner) +{ + u32 quot_diff = 0; + unsigned long freq_diff; + int scaling; + const struct fuse_corner *fuse, *prev_fuse; + int ret; + + fuse = corner->fuse_corner; + prev_fuse = fuse - 1; + + if (quot_offset) { + ret = nvmem_cell_read_variable_le_u32(dev, quot_offset, "_diff); + if (ret) + return ret; + + quot_diff *= fdata->quot_offset_scale; + quot_diff += fdata->quot_offset_adjust; + } else { + quot_diff = fuse->quot - prev_fuse->quot; + } + + freq_diff = fuse->max_freq - prev_fuse->max_freq; + freq_diff /= 1000000; /* Convert to MHz */ + scaling = 1000 * quot_diff / freq_diff; + return min(scaling, fdata->max_quot_scale); +} + +int cpr_interpolate(const struct corner *corner, int step_volt, + const struct fuse_corner_data *fdata) +{ + unsigned long f_high, f_low, f_diff; + int uV_high, uV_low, uV; + u64 temp, temp_limit; + const struct fuse_corner *fuse, *prev_fuse; + + fuse = corner->fuse_corner; + prev_fuse = fuse - 1; + + f_high = fuse->max_freq; + f_low = prev_fuse->max_freq; + uV_high = fuse->uV; + uV_low = prev_fuse->uV; + f_diff = fuse->max_freq - corner->freq; + + /* + * Don't interpolate in the wrong direction. This could happen + * if the adjusted fuse voltage overlaps with the previous fuse's + * adjusted voltage. + */ + if (f_high <= f_low || uV_high <= uV_low || f_high <= corner->freq) + return corner->uV; + + temp = f_diff * (uV_high - uV_low); + do_div(temp, f_high - f_low); + + /* + * max_volt_scale has units of uV/MHz while freq values + * have units of Hz. Divide by 1000000 to convert to. + */ + temp_limit = f_diff * fdata->max_volt_scale; + do_div(temp_limit, 1000000); + + uV = uV_high - min(temp, temp_limit); + return roundup(uV, step_volt); +} + +int cpr_check_vreg_constraints(struct device *dev, struct regulator *vreg, + struct fuse_corner *f) +{ + int ret; + + ret = regulator_is_supported_voltage(vreg, f->min_uV, f->min_uV); + if (!ret) { + dev_err(dev, "min uV: %d not supported by regulator\n", + f->min_uV); + return -EINVAL; + } + + ret = regulator_is_supported_voltage(vreg, f->max_uV, f->max_uV); + if (!ret) { + dev_err(dev, "max uV: %d not supported by regulator\n", + f->max_uV); + return -EINVAL; + } + + return 0; +} diff --git a/drivers/soc/qcom/cpr-common.h b/drivers/soc/qcom/cpr-common.h new file mode 100644 index 000000000000..83a1f7c941b8 --- /dev/null +++ b/drivers/soc/qcom/cpr-common.h @@ -0,0 +1,113 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#include +#include +#include +#include + +enum voltage_change_dir { + NO_CHANGE, + DOWN, + UP, +}; + +struct fuse_corner_data { + int ref_uV; + int max_uV; + int min_uV; + int range_uV; + /* fuse volt: closed/open loop */ + int volt_cloop_adjust; + int volt_oloop_adjust; + int max_volt_scale; + int max_quot_scale; + /* fuse quot */ + int quot_offset; + int quot_scale; + int quot_adjust; + /* fuse quot_offset */ + int quot_offset_scale; + int quot_offset_adjust; +}; + +struct cpr_fuse { + char *ring_osc; + char *init_voltage; + char *quotient; + char *quotient_offset; +}; + +struct fuse_corner { + int min_uV; + int max_uV; + int uV; + int quot; + int step_quot; + const struct reg_sequence *accs; + int num_accs; + unsigned long max_freq; + u8 ring_osc_idx; +}; + +struct corner { + int min_uV; + int max_uV; + int uV; + int last_uV; + int quot_adjust; + u32 save_ctl; + u32 save_irq; + unsigned long freq; + bool is_open_loop; + struct fuse_corner *fuse_corner; +}; + +struct corner_data { + unsigned int fuse_corner; + unsigned long freq; +}; + +struct acc_desc { + unsigned int enable_reg; + u32 enable_mask; + + struct reg_sequence *config; + struct reg_sequence *settings; + int num_regs_per_fuse; +}; + +struct cpr_acc_desc { + const struct cpr_desc *cpr_desc; + const struct acc_desc *acc_desc; +}; + + +int cpr_read_efuse(struct device *dev, const char *cname, u32 *data); +int cpr_populate_ring_osc_idx(struct device *dev, + struct fuse_corner *fuse_corner, + const struct cpr_fuse *cpr_fuse, + int num_fuse_corners); +int cpr_read_fuse_uV(int init_v_width, int step_size_uV, int ref_uV, + int adj, int step_volt, const char *init_v_efuse, + struct device *dev); +const struct cpr_fuse *cpr_get_fuses(struct device *dev, int tid, + int num_fuse_corners); +int cpr_populate_fuse_common(struct device *dev, + struct fuse_corner_data *fdata, + const struct cpr_fuse *cpr_fuse, + struct fuse_corner *fuse_corner, + int step_volt, int init_v_width, + int init_v_step); +int cpr_find_initial_corner(struct device *dev, struct clk *cpu_clk, + struct corner *corners, int num_corners); +u32 cpr_get_fuse_corner(struct dev_pm_opp *opp, u32 tid); +unsigned long cpr_get_opp_hz_for_req(struct dev_pm_opp *ref, + struct device *cpu_dev); +int cpr_calculate_scaling(const char *quot_offset, + struct device *dev, + const struct fuse_corner_data *fdata, + const struct corner *corner); +int cpr_interpolate(const struct corner *corner, int step_volt, + const struct fuse_corner_data *fdata); +int cpr_check_vreg_constraints(struct device *dev, struct regulator *vreg, + struct fuse_corner *f); diff --git a/drivers/soc/qcom/cpr.c b/drivers/soc/qcom/cpr.c index 1d818a8ba208..6c9947ac2455 100644 --- a/drivers/soc/qcom/cpr.c +++ b/drivers/soc/qcom/cpr.c @@ -25,6 +25,7 @@ #include #include #include +#include "cpr-common.h" /* Register Offsets for RB-CPR and Bit Definitions */ @@ -124,45 +125,12 @@ #define FUSE_REVISION_UNKNOWN (-1) -enum voltage_change_dir { - NO_CHANGE, - DOWN, - UP, -}; - -struct cpr_fuse { - char *ring_osc; - char *init_voltage; - char *quotient; - char *quotient_offset; -}; - -struct fuse_corner_data { - int ref_uV; - int max_uV; - int min_uV; - int max_volt_scale; - int max_quot_scale; - /* fuse quot */ - int quot_offset; - int quot_scale; - int quot_adjust; - /* fuse quot_offset */ - int quot_offset_scale; - int quot_offset_adjust; -}; - struct cpr_fuses { int init_voltage_step; int init_voltage_width; struct fuse_corner_data *fuse_corner_data; }; -struct corner_data { - unsigned int fuse_corner; - unsigned long freq; -}; - struct cpr_desc { unsigned int num_fuse_corners; int min_diff_quot; @@ -184,44 +152,6 @@ struct cpr_desc { bool reduce_to_corner_uV; }; -struct acc_desc { - unsigned int enable_reg; - u32 enable_mask; - - struct reg_sequence *config; - struct reg_sequence *settings; - int num_regs_per_fuse; -}; - -struct cpr_acc_desc { - const struct cpr_desc *cpr_desc; - const struct acc_desc *acc_desc; -}; - -struct fuse_corner { - int min_uV; - int max_uV; - int uV; - int quot; - int step_quot; - const struct reg_sequence *accs; - int num_accs; - unsigned long max_freq; - u8 ring_osc_idx; -}; - -struct corner { - int min_uV; - int max_uV; - int uV; - int last_uV; - int quot_adjust; - u32 save_ctl; - u32 save_irq; - unsigned long freq; - struct fuse_corner *fuse_corner; -}; - struct cpr_drv { unsigned int num_corners; unsigned int ref_clk_khz; @@ -801,62 +731,16 @@ unlock: return ret; } -static int -cpr_populate_ring_osc_idx(struct cpr_drv *drv) -{ - struct fuse_corner *fuse = drv->fuse_corners; - struct fuse_corner *end = fuse + drv->desc->num_fuse_corners; - const struct cpr_fuse *fuses = drv->cpr_fuses; - u32 data; - int ret; - - for (; fuse < end; fuse++, fuses++) { - ret = nvmem_cell_read_variable_le_u32(drv->dev, fuses->ring_osc, &data); - if (ret) - return ret; - fuse->ring_osc_idx = data; - } - - return 0; -} - -static int cpr_read_fuse_uV(const struct cpr_desc *desc, - const struct fuse_corner_data *fdata, - const char *init_v_efuse, - int step_volt, - struct cpr_drv *drv) -{ - int step_size_uV, steps, uV; - u32 bits = 0; - int ret; - - ret = nvmem_cell_read_variable_le_u32(drv->dev, init_v_efuse, &bits); - if (ret) - return ret; - - steps = bits & ~BIT(desc->cpr_fuses.init_voltage_width - 1); - /* Not two's complement.. instead highest bit is sign bit */ - if (bits & BIT(desc->cpr_fuses.init_voltage_width - 1)) - steps = -steps; - - step_size_uV = desc->cpr_fuses.init_voltage_step; - - uV = fdata->ref_uV + steps * step_size_uV; - return DIV_ROUND_UP(uV, step_volt) * step_volt; -} - static int cpr_fuse_corner_init(struct cpr_drv *drv) { const struct cpr_desc *desc = drv->desc; - const struct cpr_fuse *fuses = drv->cpr_fuses; + const struct cpr_fuse *cpr_fuse = drv->cpr_fuses; const struct acc_desc *acc_desc = drv->acc_desc; - int i; - unsigned int step_volt; struct fuse_corner_data *fdata; struct fuse_corner *fuse, *end; - int uV; const struct reg_sequence *accs; - int ret; + unsigned int step_volt; + int i, ret; accs = acc_desc->settings; @@ -869,24 +753,16 @@ static int cpr_fuse_corner_init(struct cpr_drv *drv) end = &fuse[desc->num_fuse_corners - 1]; fdata = desc->cpr_fuses.fuse_corner_data; - for (i = 0; fuse <= end; fuse++, fuses++, i++, fdata++) { - /* - * Update SoC voltages: platforms might choose a different - * regulators than the one used to characterize the algorithms - * (ie, init_voltage_step). - */ - fdata->min_uV = roundup(fdata->min_uV, step_volt); - fdata->max_uV = roundup(fdata->max_uV, step_volt); + for (i = 0; fuse <= end; fuse++, cpr_fuse++, i++, fdata++) { + ret = cpr_populate_fuse_common( + drv->dev, fdata, cpr_fuse, + fuse, step_volt, + desc->cpr_fuses.init_voltage_width, + desc->cpr_fuses.init_voltage_step); + if (ret) + return ret; - /* Populate uV */ - uV = cpr_read_fuse_uV(desc, fdata, fuses->init_voltage, - step_volt, drv); - if (uV < 0) - return uV; - - fuse->min_uV = fdata->min_uV; - fuse->max_uV = fdata->max_uV; - fuse->uV = clamp(uV, fuse->min_uV, fuse->max_uV); + fuse->step_quot = desc->step_quot[fuse->ring_osc_idx]; if (fuse == end) { /* @@ -898,16 +774,6 @@ static int cpr_fuse_corner_init(struct cpr_drv *drv) end->max_uV = max(end->max_uV, end->uV); } - /* Populate target quotient by scaling */ - ret = nvmem_cell_read_variable_le_u32(drv->dev, fuses->quotient, &fuse->quot); - if (ret) - return ret; - - fuse->quot *= fdata->quot_scale; - fuse->quot += fdata->quot_offset; - fuse->quot += fdata->quot_adjust; - fuse->step_quot = desc->step_quot[fuse->ring_osc_idx]; - /* Populate acc settings */ fuse->accs = accs; fuse->num_accs = acc_desc->num_regs_per_fuse; @@ -924,25 +790,9 @@ static int cpr_fuse_corner_init(struct cpr_drv *drv) else if (fuse->uV < fuse->min_uV) fuse->uV = fuse->min_uV; - ret = regulator_is_supported_voltage(drv->vdd_apc, - fuse->min_uV, - fuse->min_uV); - if (!ret) { - dev_err(drv->dev, - "min uV: %d (fuse corner: %d) not supported by regulator\n", - fuse->min_uV, i); - return -EINVAL; - } - - ret = regulator_is_supported_voltage(drv->vdd_apc, - fuse->max_uV, - fuse->max_uV); - if (!ret) { - dev_err(drv->dev, - "max uV: %d (fuse corner: %d) not supported by regulator\n", - fuse->max_uV, i); - return -EINVAL; - } + ret = cpr_check_vreg_constraints(drv->dev, drv->vdd_apc, fuse); + if (ret) + return ret; dev_dbg(drv->dev, "fuse corner %d: [%d %d %d] RO%hhu quot %d squot %d\n", @@ -953,126 +803,6 @@ static int cpr_fuse_corner_init(struct cpr_drv *drv) return 0; } -static int cpr_calculate_scaling(const char *quot_offset, - struct cpr_drv *drv, - const struct fuse_corner_data *fdata, - const struct corner *corner) -{ - u32 quot_diff = 0; - unsigned long freq_diff; - int scaling; - const struct fuse_corner *fuse, *prev_fuse; - int ret; - - fuse = corner->fuse_corner; - prev_fuse = fuse - 1; - - if (quot_offset) { - ret = nvmem_cell_read_variable_le_u32(drv->dev, quot_offset, "_diff); - if (ret) - return ret; - - quot_diff *= fdata->quot_offset_scale; - quot_diff += fdata->quot_offset_adjust; - } else { - quot_diff = fuse->quot - prev_fuse->quot; - } - - freq_diff = fuse->max_freq - prev_fuse->max_freq; - freq_diff /= 1000000; /* Convert to MHz */ - scaling = 1000 * quot_diff / freq_diff; - return min(scaling, fdata->max_quot_scale); -} - -static int cpr_interpolate(const struct corner *corner, int step_volt, - const struct fuse_corner_data *fdata) -{ - unsigned long f_high, f_low, f_diff; - int uV_high, uV_low, uV; - u64 temp, temp_limit; - const struct fuse_corner *fuse, *prev_fuse; - - fuse = corner->fuse_corner; - prev_fuse = fuse - 1; - - f_high = fuse->max_freq; - f_low = prev_fuse->max_freq; - uV_high = fuse->uV; - uV_low = prev_fuse->uV; - f_diff = fuse->max_freq - corner->freq; - - /* - * Don't interpolate in the wrong direction. This could happen - * if the adjusted fuse voltage overlaps with the previous fuse's - * adjusted voltage. - */ - if (f_high <= f_low || uV_high <= uV_low || f_high <= corner->freq) - return corner->uV; - - temp = f_diff * (uV_high - uV_low); - do_div(temp, f_high - f_low); - - /* - * max_volt_scale has units of uV/MHz while freq values - * have units of Hz. Divide by 1000000 to convert to. - */ - temp_limit = f_diff * fdata->max_volt_scale; - do_div(temp_limit, 1000000); - - uV = uV_high - min(temp, temp_limit); - return roundup(uV, step_volt); -} - -static unsigned int cpr_get_fuse_corner(struct dev_pm_opp *opp) -{ - struct device_node *np; - unsigned int fuse_corner = 0; - - np = dev_pm_opp_get_of_node(opp); - if (of_property_read_u32(np, "qcom,opp-fuse-level", &fuse_corner)) - pr_err("%s: missing 'qcom,opp-fuse-level' property\n", - __func__); - - of_node_put(np); - - return fuse_corner; -} - -static unsigned long cpr_get_opp_hz_for_req(struct dev_pm_opp *ref, - struct device *cpu_dev) -{ - u64 rate = 0; - struct device_node *ref_np; - struct device_node *desc_np; - struct device_node *child_np = NULL; - struct device_node *child_req_np = NULL; - - desc_np = dev_pm_opp_of_get_opp_desc_node(cpu_dev); - if (!desc_np) - return 0; - - ref_np = dev_pm_opp_get_of_node(ref); - if (!ref_np) - goto out_ref; - - do { - of_node_put(child_req_np); - child_np = of_get_next_available_child(desc_np, child_np); - child_req_np = of_parse_phandle(child_np, "required-opps", 0); - } while (child_np && child_req_np != ref_np); - - if (child_np && child_req_np == ref_np) - of_property_read_u64(child_np, "opp-hz", &rate); - - of_node_put(child_req_np); - of_node_put(child_np); - of_node_put(ref_np); -out_ref: - of_node_put(desc_np); - - return (unsigned long) rate; -} - static int cpr_corner_init(struct cpr_drv *drv) { const struct cpr_desc *desc = drv->desc; @@ -1110,7 +840,7 @@ static int cpr_corner_init(struct cpr_drv *drv) opp = dev_pm_opp_find_level_exact(&drv->pd.dev, level); if (IS_ERR(opp)) return -EINVAL; - fc = cpr_get_fuse_corner(opp); + fc = cpr_get_fuse_corner(opp, 0); if (!fc) { dev_pm_opp_put(opp); return -EINVAL; @@ -1186,7 +916,7 @@ static int cpr_corner_init(struct cpr_drv *drv) corner->uV = fuse->uV; if (prev_fuse && cdata[i - 1].freq == prev_fuse->max_freq) { - scaling = cpr_calculate_scaling(quot_offset, drv, + scaling = cpr_calculate_scaling(quot_offset, drv->dev, fdata, corner); if (scaling < 0) return scaling; @@ -1224,47 +954,6 @@ static int cpr_corner_init(struct cpr_drv *drv) return 0; } -static const struct cpr_fuse *cpr_get_fuses(struct cpr_drv *drv) -{ - const struct cpr_desc *desc = drv->desc; - struct cpr_fuse *fuses; - int i; - - fuses = devm_kcalloc(drv->dev, desc->num_fuse_corners, - sizeof(struct cpr_fuse), - GFP_KERNEL); - if (!fuses) - return ERR_PTR(-ENOMEM); - - for (i = 0; i < desc->num_fuse_corners; i++) { - char tbuf[32]; - - snprintf(tbuf, 32, "cpr_ring_osc%d", i + 1); - fuses[i].ring_osc = devm_kstrdup(drv->dev, tbuf, GFP_KERNEL); - if (!fuses[i].ring_osc) - return ERR_PTR(-ENOMEM); - - snprintf(tbuf, 32, "cpr_init_voltage%d", i + 1); - fuses[i].init_voltage = devm_kstrdup(drv->dev, tbuf, - GFP_KERNEL); - if (!fuses[i].init_voltage) - return ERR_PTR(-ENOMEM); - - snprintf(tbuf, 32, "cpr_quotient%d", i + 1); - fuses[i].quotient = devm_kstrdup(drv->dev, tbuf, GFP_KERNEL); - if (!fuses[i].quotient) - return ERR_PTR(-ENOMEM); - - snprintf(tbuf, 32, "cpr_quotient_offset%d", i + 1); - fuses[i].quotient_offset = devm_kstrdup(drv->dev, tbuf, - GFP_KERNEL); - if (!fuses[i].quotient_offset) - return ERR_PTR(-ENOMEM); - } - - return fuses; -} - static void cpr_set_loop_allowed(struct cpr_drv *drv) { drv->loop_disabled = false; @@ -1296,54 +985,6 @@ static int cpr_init_parameters(struct cpr_drv *drv) return 0; } -static int cpr_find_initial_corner(struct cpr_drv *drv) -{ - unsigned long rate; - const struct corner *end; - struct corner *iter; - unsigned int i = 0; - - if (!drv->cpu_clk) { - dev_err(drv->dev, "cannot get rate from NULL clk\n"); - return -EINVAL; - } - - end = &drv->corners[drv->num_corners - 1]; - rate = clk_get_rate(drv->cpu_clk); - - /* - * Some bootloaders set a CPU clock frequency that is not defined - * in the OPP table. When running at an unlisted frequency, - * cpufreq_online() will change to the OPP which has the lowest - * frequency, at or above the unlisted frequency. - * Since cpufreq_online() always "rounds up" in the case of an - * unlisted frequency, this function always "rounds down" in case - * of an unlisted frequency. That way, when cpufreq_online() - * triggers the first ever call to cpr_set_performance_state(), - * it will correctly determine the direction as UP. - */ - for (iter = drv->corners; iter <= end; iter++) { - if (iter->freq > rate) - break; - i++; - if (iter->freq == rate) { - drv->corner = iter; - break; - } - if (iter->freq < rate) - drv->corner = iter; - } - - if (!drv->corner) { - dev_err(drv->dev, "boot up corner not found\n"); - return -EINVAL; - } - - dev_dbg(drv->dev, "boot up perf state: %u\n", i); - - return 0; -} - static const struct cpr_desc qcs404_cpr_desc = { .num_fuse_corners = 3, .min_diff_quot = CPR_FUSE_MIN_QUOT_DIFF, @@ -1531,8 +1172,9 @@ static int cpr_pd_attach_dev(struct generic_pm_domain *domain, if (ret) goto unlock; - ret = cpr_find_initial_corner(drv); - if (ret) + ret = cpr_find_initial_corner(drv->dev, drv->cpu_clk, drv->corners, + drv->num_corners); + if (ret < 0) goto unlock; if (acc_desc->config) @@ -1616,6 +1258,7 @@ static int cpr_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct cpr_drv *drv; + const struct cpr_desc *desc; int irq, ret; const struct cpr_acc_desc *data; struct device_node *np; @@ -1631,6 +1274,7 @@ static int cpr_probe(struct platform_device *pdev) drv->dev = dev; drv->desc = data->cpr_desc; drv->acc_desc = data->acc_desc; + desc = drv->desc; drv->fuse_corners = devm_kcalloc(dev, drv->desc->num_fuse_corners, sizeof(*drv->fuse_corners), @@ -1670,11 +1314,13 @@ static int cpr_probe(struct platform_device *pdev) if (ret) return ret; - drv->cpr_fuses = cpr_get_fuses(drv); + drv->cpr_fuses = cpr_get_fuses(drv->dev, 0, desc->num_fuse_corners); if (IS_ERR(drv->cpr_fuses)) return PTR_ERR(drv->cpr_fuses); - ret = cpr_populate_ring_osc_idx(drv); + ret = cpr_populate_ring_osc_idx(drv->dev, drv->fuse_corners, + drv->cpr_fuses, + desc->num_fuse_corners); if (ret) return ret; From 6c2c879a78df5d121e17d29195a22b6d18146641 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Wed, 25 Nov 2020 20:29:26 +0100 Subject: [PATCH 059/187] dt-bindings: avs: cpr: Convert binding to YAML schema Convert the qcom,cpr.txt document to YAML schema and place it in the appropriate directory, since this driver was moved from power/avs to soc/qcom, but forgets to move the documentation. Fixes: a7305e684fcf ("PM: AVS: qcom-cpr: Move the driver to the qcom specific drivers") Signed-off-by: AngeloGioacchino Del Regno --- .../bindings/power/avs/qcom,cpr.txt | 131 +------------- .../bindings/soc/qcom/qcom,cpr.yaml | 167 ++++++++++++++++++ MAINTAINERS | 2 +- 3 files changed, 169 insertions(+), 131 deletions(-) create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,cpr.yaml diff --git a/Documentation/devicetree/bindings/power/avs/qcom,cpr.txt b/Documentation/devicetree/bindings/power/avs/qcom,cpr.txt index ab0d5ebbad4e..2ada8cd08949 100644 --- a/Documentation/devicetree/bindings/power/avs/qcom,cpr.txt +++ b/Documentation/devicetree/bindings/power/avs/qcom,cpr.txt @@ -1,130 +1 @@ -QCOM CPR (Core Power Reduction) - -CPR (Core Power Reduction) is a technology to reduce core power on a CPU -or other device. Each OPP of a device corresponds to a "corner" that has -a range of valid voltages for a particular frequency. While the device is -running at a particular frequency, CPR monitors dynamic factors such as -temperature, etc. and suggests adjustments to the voltage to save power -and meet silicon characteristic requirements. - -- compatible: - Usage: required - Value type: - Definition: should be "qcom,qcs404-cpr", "qcom,cpr" for qcs404 - -- reg: - Usage: required - Value type: - Definition: base address and size of the rbcpr register region - -- interrupts: - Usage: required - Value type: - Definition: should specify the CPR interrupt - -- clocks: - Usage: required - Value type: - Definition: phandle to the reference clock - -- clock-names: - Usage: required - Value type: - Definition: must be "ref" - -- vdd-apc-supply: - Usage: required - Value type: - Definition: phandle to the vdd-apc-supply regulator - -- #power-domain-cells: - Usage: required - Value type: - Definition: should be 0 - -- operating-points-v2: - Usage: required - Value type: - Definition: A phandle to the OPP table containing the - performance states supported by the CPR - power domain - -- acc-syscon: - Usage: optional - Value type: - Definition: phandle to syscon for writing ACC settings - -- nvmem-cells: - Usage: required - Value type: - Definition: phandle to nvmem cells containing the data - that makes up a fuse corner, for each fuse corner. - As well as the CPR fuse revision. - -- nvmem-cell-names: - Usage: required - Value type: - Definition: should be "cpr_quotient_offset1", "cpr_quotient_offset2", - "cpr_quotient_offset3", "cpr_init_voltage1", - "cpr_init_voltage2", "cpr_init_voltage3", "cpr_quotient1", - "cpr_quotient2", "cpr_quotient3", "cpr_ring_osc1", - "cpr_ring_osc2", "cpr_ring_osc3", "cpr_fuse_revision" - for qcs404. - -Example: - - cpr_opp_table: cpr-opp-table { - compatible = "operating-points-v2-qcom-level"; - - cpr_opp1: opp1 { - opp-level = <1>; - qcom,opp-fuse-level = <1>; - }; - cpr_opp2: opp2 { - opp-level = <2>; - qcom,opp-fuse-level = <2>; - }; - cpr_opp3: opp3 { - opp-level = <3>; - qcom,opp-fuse-level = <3>; - }; - }; - - power-controller@b018000 { - compatible = "qcom,qcs404-cpr", "qcom,cpr"; - reg = <0x0b018000 0x1000>; - interrupts = <0 15 IRQ_TYPE_EDGE_RISING>; - clocks = <&xo_board>; - clock-names = "ref"; - vdd-apc-supply = <&pms405_s3>; - #power-domain-cells = <0>; - operating-points-v2 = <&cpr_opp_table>; - acc-syscon = <&tcsr>; - - nvmem-cells = <&cpr_efuse_quot_offset1>, - <&cpr_efuse_quot_offset2>, - <&cpr_efuse_quot_offset3>, - <&cpr_efuse_init_voltage1>, - <&cpr_efuse_init_voltage2>, - <&cpr_efuse_init_voltage3>, - <&cpr_efuse_quot1>, - <&cpr_efuse_quot2>, - <&cpr_efuse_quot3>, - <&cpr_efuse_ring1>, - <&cpr_efuse_ring2>, - <&cpr_efuse_ring3>, - <&cpr_efuse_revision>; - nvmem-cell-names = "cpr_quotient_offset1", - "cpr_quotient_offset2", - "cpr_quotient_offset3", - "cpr_init_voltage1", - "cpr_init_voltage2", - "cpr_init_voltage3", - "cpr_quotient1", - "cpr_quotient2", - "cpr_quotient3", - "cpr_ring_osc1", - "cpr_ring_osc2", - "cpr_ring_osc3", - "cpr_fuse_revision"; - }; +This file has been moved to ../../soc/qcom/qcom,cpr.yaml diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,cpr.yaml b/Documentation/devicetree/bindings/soc/qcom/qcom,cpr.yaml new file mode 100644 index 000000000000..f58dc6c138a6 --- /dev/null +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,cpr.yaml @@ -0,0 +1,167 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: "http://devicetree.org/schemas/soc/qcom/qcom,cpr.yaml#" +$schema: "http://devicetree.org/meta-schemas/core.yaml#" + +title: Qualcomm Core Power Reduction (CPR) + +description: | + CPR (Core Power Reduction) is a technology to reduce core power on a CPU + or other device. Each OPP of a device corresponds to a "corner" that has + a range of valid voltages for a particular frequency. While the device is + running at a particular frequency, CPR monitors dynamic factors such as + temperature, etc. and suggests adjustments to the voltage to save power + and meet silicon characteristic requirements. + +maintainers: + - Niklas Cassel + +properties: + compatible: + items: + - enum: + - qcom,qcs404-cpr + - const: qcom,cpr + + reg: + description: Base address and size of the RBCPR register region + maxItems: 1 + + interrupts: + maxItems: 1 + + clock-names: + items: + - const: ref + + clocks: + items: + - description: CPR reference clock + + vdd-apc-supply: + description: Autonomous Phase Control (APC) power supply + + '#power-domain-cells': + const: 0 + + acc-syscon: + description: phandle to syscon for writing ACC settings + + nvmem-cells: + minItems: 9 + maxItems: 32 + description: Cells containing the fuse corners and revision data + + nvmem-cell-names: + minItems: 9 + maxItems: 32 + + operating-points-v2: true + +required: + - compatible + - reg + - interrupts + - clock-names + - clocks + - vdd-apc-supply + - "#power-domain-cells" + - nvmem-cells + - nvmem-cell-names + - operating-points-v2 + +additionalProperties: false + +examples: + - | + #include + + cpus { + #address-cells = <1>; + #size-cells = <0>; + + cpu@100 { + compatible = "arm,cortex-a53"; + device_type = "cpu"; + reg = <0x100>; + operating-points-v2 = <&cpu_opp_table>; + power-domains = <&cpr>; + power-domain-names = "cpr"; + }; + }; + + cpu_opp_table: cpu-opp-table { + compatible = "operating-points-v2-kryo-cpu"; + opp-shared; + + opp-1094400000 { + opp-hz = /bits/ 64 <1094400000>; + required-opps = <&cpr_opp1>; + }; + opp-1248000000 { + opp-hz = /bits/ 64 <1248000000>; + required-opps = <&cpr_opp2>; + }; + opp-1401600000 { + opp-hz = /bits/ 64 <1401600000>; + required-opps = <&cpr_opp3>; + }; + }; + + cpr_opp_table: cpr-opp-table { + compatible = "operating-points-v2-qcom-level"; + + cpr_opp1: opp1 { + opp-level = <1>; + qcom,opp-fuse-level = <1>; + }; + cpr_opp2: opp2 { + opp-level = <2>; + qcom,opp-fuse-level = <2>; + }; + cpr_opp3: opp3 { + opp-level = <3>; + qcom,opp-fuse-level = <3>; + }; + }; + + power-controller@b018000 { + compatible = "qcom,qcs404-cpr", "qcom,cpr"; + reg = <0x0b018000 0x1000>; + interrupts = <0 15 IRQ_TYPE_EDGE_RISING>; + clocks = <&xo_board>; + clock-names = "ref"; + vdd-apc-supply = <&pms405_s3>; + #power-domain-cells = <0>; + operating-points-v2 = <&cpr_opp_table>; + acc-syscon = <&tcsr>; + + nvmem-cells = <&cpr_efuse_quot_offset1>, + <&cpr_efuse_quot_offset2>, + <&cpr_efuse_quot_offset3>, + <&cpr_efuse_init_voltage1>, + <&cpr_efuse_init_voltage2>, + <&cpr_efuse_init_voltage3>, + <&cpr_efuse_quot1>, + <&cpr_efuse_quot2>, + <&cpr_efuse_quot3>, + <&cpr_efuse_ring1>, + <&cpr_efuse_ring2>, + <&cpr_efuse_ring3>, + <&cpr_efuse_revision>; + nvmem-cell-names = "cpr0_quotient_offset1", + "cpr0_quotient_offset2", + "cpr0_quotient_offset3", + "cpr0_init_voltage1", + "cpr0_init_voltage2", + "cpr0_init_voltage3", + "cpr0_quotient1", + "cpr0_quotient2", + "cpr0_quotient3", + "cpr0_ring_osc1", + "cpr0_ring_osc2", + "cpr0_ring_osc3", + "cpr0_fuse_revision"; + }; +... diff --git a/MAINTAINERS b/MAINTAINERS index 8912b2c1260c..070747841bc7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15740,7 +15740,7 @@ M: Niklas Cassel L: linux-pm@vger.kernel.org L: linux-arm-msm@vger.kernel.org S: Maintained -F: Documentation/devicetree/bindings/power/avs/qcom,cpr.txt +F: Documentation/devicetree/bindings/soc/qcom/qcom,cpr.yaml F: drivers/soc/qcom/cpr.c QUALCOMM CPUFREQ DRIVER MSM8996/APQ8096 From 44869d20db54ffa58c960bab696de6d545b3bd47 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Wed, 25 Nov 2020 11:23:17 +0100 Subject: [PATCH 060/187] arm64: qcom: qcs404: Change CPR nvmem-names The CPR driver's common functions were split and put in another file in order to support newer CPR revisions: to simplify the commonization, the expected names of the fuses had to be changed in order for both new and old support to use the same fuse name retrieval function and keeping the naming consistent. The thread id was added to the fuse name and, since CPRv1 does not support threads, it is expected to always read ID 0, which means that the expected name here is now "cpr0_(fuse_name)" instead of "cpr_(fuse_name)": luckily, QCS404 is the only user so change it accordingly. Signed-off-by: AngeloGioacchino Del Regno --- arch/arm64/boot/dts/qcom/qcs404.dtsi | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/qcs404.dtsi b/arch/arm64/boot/dts/qcom/qcs404.dtsi index 6db753b49326..2a7f6ffe1b90 100644 --- a/arch/arm64/boot/dts/qcom/qcs404.dtsi +++ b/arch/arm64/boot/dts/qcom/qcs404.dtsi @@ -1173,19 +1173,19 @@ <&cpr_efuse_ring2>, <&cpr_efuse_ring3>, <&cpr_efuse_revision>; - nvmem-cell-names = "cpr_quotient_offset1", - "cpr_quotient_offset2", - "cpr_quotient_offset3", - "cpr_init_voltage1", - "cpr_init_voltage2", - "cpr_init_voltage3", - "cpr_quotient1", - "cpr_quotient2", - "cpr_quotient3", - "cpr_ring_osc1", - "cpr_ring_osc2", - "cpr_ring_osc3", - "cpr_fuse_revision"; + nvmem-cell-names = "cpr0_quotient_offset1", + "cpr0_quotient_offset2", + "cpr0_quotient_offset3", + "cpr0_init_voltage1", + "cpr0_init_voltage2", + "cpr0_init_voltage3", + "cpr0_quotient1", + "cpr0_quotient2", + "cpr0_quotient3", + "cpr0_ring_osc1", + "cpr0_ring_osc2", + "cpr0_ring_osc3", + "cpr0_fuse_revision"; }; timer@b120000 { From 4a8417d616f598420736848c0a14062f8b6a18f3 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Tue, 24 Nov 2020 20:39:22 +0100 Subject: [PATCH 061/187] soc: qcom: Add support for Core Power Reduction v3, v4 and Hardened This commit introduces a new driver, based on the one for cpr v1, to enable support for the newer Qualcomm Core Power Reduction hardware, known downstream as CPR3, CPR4 and CPRh, and support for MSM8998 and SDM630 CPU power reduction. In these new versions of the hardware, support for various new features was introduced, including voltage reduction for the GPU, security hardening and a new way of controlling CPU DVFS, consisting in internal communication between microcontrollers, specifically the CPR-Hardened and the Operating State Manager. The CPR v3, v4 and CPRh are present in a broad range of SoCs, from the mid-range to the high end ones including, but not limited to, MSM8953/8996/8998, SDM630/636/660/845. Signed-off-by: AngeloGioacchino Del Regno --- drivers/soc/qcom/Kconfig | 17 + drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/cpr-common.c | 35 +- drivers/soc/qcom/cpr-common.h | 4 + drivers/soc/qcom/cpr3.c | 2897 +++++++++++++++++++++++++++++++++ include/soc/qcom/cpr.h | 17 + 6 files changed, 2965 insertions(+), 6 deletions(-) create mode 100644 drivers/soc/qcom/cpr3.c create mode 100644 include/soc/qcom/cpr.h diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index e718b8735444..354b38ef1df0 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -42,6 +42,23 @@ config QCOM_CPR To compile this driver as a module, choose M here: the module will be called qcom-cpr +config QCOM_CPR3 + tristate "QCOM Core Power Reduction (CPR v3/v4/Hardened) support" + depends on ARCH_QCOM && HAS_IOMEM + select PM_OPP + select REGMAP + help + Say Y here to enable support for the CPR hardware found on a broad + variety of Qualcomm SoCs like MSM8996, MSM8998, SDM630, SDM660, + SDM845 and others. + + This driver populates OPP tables and makes adjustments to them + based on feedback from the CPR hardware. If you want to do CPU + and/or GPU frequency scaling say Y here. + + To compile this driver as a module, choose M here: the module will + be called qcom-cpr3 + config QCOM_GENI_SE tristate "QCOM GENI Serial Engine Driver" depends on ARCH_QCOM || COMPILE_TEST diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index bed55881dfd9..31e95892e575 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_QCOM_AOSS_QMP) += qcom_aoss.o obj-$(CONFIG_QCOM_GENI_SE) += qcom-geni-se.o obj-$(CONFIG_QCOM_COMMAND_DB) += cmd-db.o obj-$(CONFIG_QCOM_CPR) += cpr-common.o cpr.o +obj-$(CONFIG_QCOM_CPR3) += cpr-common.o cpr3.o obj-$(CONFIG_QCOM_GSBI) += qcom_gsbi.o obj-$(CONFIG_QCOM_MDT_LOADER) += mdt_loader.o obj-$(CONFIG_QCOM_OCMEM) += ocmem.o diff --git a/drivers/soc/qcom/cpr-common.c b/drivers/soc/qcom/cpr-common.c index 53d4827973cb..e5229a356329 100644 --- a/drivers/soc/qcom/cpr-common.c +++ b/drivers/soc/qcom/cpr-common.c @@ -219,6 +219,29 @@ u32 cpr_get_fuse_corner(struct dev_pm_opp *opp, u32 tid) of_node_put(np); return fc; + +} + +void cpr_get_corner_post_vadj(struct dev_pm_opp *opp, u32 tid, + s32 *open_loop, s32 *closed_loop) +{ + struct device_node *np; + + /* + * There is no of_property_read_s32_index, so we just store the + * result into a s32 variable. After all, the OF API is doing + * the exact same for of_property_read_s32... + */ + np = dev_pm_opp_get_of_node(opp); + if (of_property_read_u32_index(np, "qcom,opp-oloop-vadj", tid, + open_loop)) + *open_loop = 0; + + if (of_property_read_u32_index(np, "qcom,opp-cloop-vadj", tid, + closed_loop)) + *closed_loop = 0; + + of_node_put(np); } unsigned long cpr_get_opp_hz_for_req(struct dev_pm_opp *ref, @@ -261,11 +284,10 @@ int cpr_calculate_scaling(const char *quot_offset, const struct fuse_corner_data *fdata, const struct corner *corner) { - u32 quot_diff = 0; - unsigned long freq_diff; - int scaling; + u64 freq_diff; const struct fuse_corner *fuse, *prev_fuse; - int ret; + u32 quot_diff; + int scaling, ret; fuse = corner->fuse_corner; prev_fuse = fuse - 1; @@ -282,8 +304,9 @@ int cpr_calculate_scaling(const char *quot_offset, } freq_diff = fuse->max_freq - prev_fuse->max_freq; - freq_diff /= 1000000; /* Convert to MHz */ - scaling = 1000 * quot_diff / freq_diff; + freq_diff = div_u64(freq_diff, 1000000); /* Convert to MHz */ + scaling = 1000 * quot_diff; + do_div(scaling, freq_diff); return min(scaling, fdata->max_quot_scale); } diff --git a/drivers/soc/qcom/cpr-common.h b/drivers/soc/qcom/cpr-common.h index 83a1f7c941b8..96ff6301c81e 100644 --- a/drivers/soc/qcom/cpr-common.h +++ b/drivers/soc/qcom/cpr-common.h @@ -65,6 +65,8 @@ struct corner { struct corner_data { unsigned int fuse_corner; unsigned long freq; + int oloop_vadj; + int cloop_vadj; }; struct acc_desc { @@ -101,6 +103,8 @@ int cpr_populate_fuse_common(struct device *dev, int cpr_find_initial_corner(struct device *dev, struct clk *cpu_clk, struct corner *corners, int num_corners); u32 cpr_get_fuse_corner(struct dev_pm_opp *opp, u32 tid); +void cpr_get_corner_post_vadj(struct dev_pm_opp *opp, u32 tid, + s32 *open_loop, s32 *closed_loop); unsigned long cpr_get_opp_hz_for_req(struct dev_pm_opp *ref, struct device *cpu_dev); int cpr_calculate_scaling(const char *quot_offset, diff --git a/drivers/soc/qcom/cpr3.c b/drivers/soc/qcom/cpr3.c new file mode 100644 index 000000000000..7e368b39d07c --- /dev/null +++ b/drivers/soc/qcom/cpr3.c @@ -0,0 +1,2897 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2013-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2019 Linaro Limited + * Copyright (c) 2021, AngeloGioacchino Del Regno + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cpr-common.h" + +#define CPR3_RO_COUNT 16 +#define CPR3_RO_MASK GENMASK(CPR3_RO_COUNT - 1, 0) + +/* CPR3 registers */ +#define CPR3_REG_CPR_VERSION 0x0 +#define CPRH_CPR_VERSION_4P5 0x40050000 + +#define CPR3_REG_CPR_CTL 0x4 +#define CPR3_CPR_CTL_LOOP_EN_MASK BIT(0) +#define CPR3_CPR_CTL_IDLE_CLOCKS_MASK GENMASK(5, 1) +#define CPR3_CPR_CTL_IDLE_CLOCKS_SHIFT 1 +#define CPR3_CPR_CTL_COUNT_MODE_MASK GENMASK(7, 6) +#define CPR3_CPR_CTL_COUNT_MODE_SHIFT 6 +#define CPR3_CPR_CTL_COUNT_MODE_ALL_AT_ONCE_MIN 0 +#define CPR3_CPR_CTL_COUNT_MODE_ALL_AT_ONCE_MAX 1 +#define CPR3_CPR_CTL_COUNT_MODE_STAGGERED 2 +#define CPR3_CPR_CTL_COUNT_MODE_ALL_AT_ONCE_AGE 3 +#define CPR3_CPR_CTL_COUNT_REPEAT_MASK GENMASK(31, 9) +#define CPR3_CPR_CTL_COUNT_REPEAT_SHIFT 9 + +#define CPR3_REG_CPR_STATUS 0x8 +#define CPR3_CPR_STATUS_BUSY_MASK BIT(0) + +/* + * This register is not present on controllers that support HW closed-loop + * except CPR4 APSS controller. + */ +#define CPR3_REG_CPR_TIMER_AUTO_CONT 0xC + +#define CPR3_REG_CPR_STEP_QUOT 0x14 +#define CPR3_CPR_STEP_QUOT_MIN_MASK GENMASK(5, 0) +#define CPR3_CPR_STEP_QUOT_MIN_SHIFT 0 +#define CPR3_CPR_STEP_QUOT_MAX_MASK GENMASK(11, 6) +#define CPR3_CPR_STEP_QUOT_MAX_SHIFT 6 +#define CPRH_DELTA_QUOT_STEP_FACTOR 4 + +#define CPR3_REG_GCNT(ro) (0xA0 + 0x4 * (ro)) +#define CPR3_REG_SENSOR_OWNER(sensor) (0x200 + 0x4 * (sensor)) + +#define CPR3_REG_CONT_CMD 0x800 +#define CPR3_CONT_CMD_ACK 0x1 +#define CPR3_CONT_CMD_NACK 0x0 + +#define CPR3_REG_THRESH(thread) (0x808 + 0x440 * (thread)) +#define CPR3_THRESH_CONS_DOWN_MASK GENMASK(3, 0) +#define CPR3_THRESH_CONS_DOWN_SHIFT 0 +#define CPR3_THRESH_CONS_UP_MASK GENMASK(7, 4) +#define CPR3_THRESH_CONS_UP_SHIFT 4 +#define CPR3_THRESH_DOWN_THRESH_MASK GENMASK(12, 8) +#define CPR3_THRESH_DOWN_THRESH_SHIFT 8 +#define CPR3_THRESH_UP_THRESH_MASK GENMASK(17, 13) +#define CPR3_THRESH_UP_THRESH_SHIFT 13 + +#define CPR3_REG_RO_MASK(thread) (0x80C + 0x440 * (thread)) + +#define CPR3_REG_RESULT0(thread) (0x810 + 0x440 * (thread)) +#define CPR3_RESULT0_BUSY_MASK BIT(0) +#define CPR3_RESULT0_STEP_DN_MASK BIT(1) +#define CPR3_RESULT0_STEP_UP_MASK BIT(2) +#define CPR3_RESULT0_ERROR_STEPS_MASK GENMASK(7, 3) +#define CPR3_RESULT0_ERROR_STEPS_SHIFT 3 +#define CPR3_RESULT0_ERROR_MASK GENMASK(19, 8) +#define CPR3_RESULT0_ERROR_SHIFT 8 + +#define CPR3_REG_RESULT1(thread) (0x814 + 0x440 * (thread)) +#define CPR3_RESULT1_QUOT_MIN_MASK GENMASK(11, 0) +#define CPR3_RESULT1_QUOT_MIN_SHIFT 0 +#define CPR3_RESULT1_QUOT_MAX_MASK GENMASK(23, 12) +#define CPR3_RESULT1_QUOT_MAX_SHIFT 12 +#define CPR3_RESULT1_RO_MIN_MASK GENMASK(27, 24) +#define CPR3_RESULT1_RO_MIN_SHIFT 24 +#define CPR3_RESULT1_RO_MAX_MASK GENMASK(31, 28) +#define CPR3_RESULT1_RO_MAX_SHIFT 28 + +#define CPR3_REG_RESULT2(thread) (0x818 + 0x440 * (thread)) +#define CPR3_RESULT2_STEP_QUOT_MIN_MASK GENMASK(5, 0) +#define CPR3_RESULT2_STEP_QUOT_MIN_SHIFT 0 +#define CPR3_RESULT2_STEP_QUOT_MAX_MASK GENMASK(11, 6) +#define CPR3_RESULT2_STEP_QUOT_MAX_SHIFT 6 +#define CPR3_RESULT2_SENSOR_MIN_MASK GENMASK(23, 16) +#define CPR3_RESULT2_SENSOR_MIN_SHIFT 16 +#define CPR3_RESULT2_SENSOR_MAX_MASK GENMASK(31, 24) +#define CPR3_RESULT2_SENSOR_MAX_SHIFT 24 + +#define CPR3_REG_IRQ_EN 0x81C +#define CPR3_REG_IRQ_CLEAR 0x820 +#define CPR3_REG_IRQ_STATUS 0x824 +#define CPR3_IRQ_UP BIT(3) +#define CPR3_IRQ_MID BIT(2) +#define CPR3_IRQ_DOWN BIT(1) +#define CPR3_IRQ_ALL (CPR3_IRQ_UP | CPR3_IRQ_MID | CPR3_IRQ_DOWN) + +#define CPR3_REG_TARGET_QUOT(thread, ro) (0x840 + 0x440 * (thread) + 0x4 * (ro)) + +/* Registers found only on controllers that support HW closed-loop. */ +#define CPR3_REG_PD_THROTTLE 0xE8 + +#define CPR3_REG_HW_CLOSED_LOOP_DISABLED 0x3000 +#define CPR3_REG_CPR_TIMER_MID_CONT 0x3004 +#define CPR3_REG_CPR_TIMER_UP_DN_CONT 0x3008 + +/* CPR4 controller specific registers and bit definitions */ +#define CPR4_REG_CPR_TIMER_CLAMP 0x10 +#define CPR4_CPR_TIMER_CLAMP_THREAD_AGGREGATION_EN BIT(27) + +#define CPR4_REG_MISC 0x700 +#define CPR4_MISC_RESET_STEP_QUOT_LOOP_EN BIT(2) +#define CPR4_MISC_THREAD_HAS_ALWAYS_VOTE_EN BIT(3) + +#define CPR4_REG_SAW_ERROR_STEP_LIMIT 0x7A4 +#define CPR4_SAW_ERROR_STEP_LIMIT_UP_MASK GENMASK(4, 0) +#define CPR4_SAW_ERROR_STEP_LIMIT_UP_SHIFT 0 +#define CPR4_SAW_ERROR_STEP_LIMIT_DN_MASK GENMASK(9, 5) +#define CPR4_SAW_ERROR_STEP_LIMIT_DN_SHIFT 5 + +#define CPR4_REG_MARGIN_TEMP_CORE_TIMERS 0x7A8 +#define CPR4_MARGIN_TEMP_CORE_TIMERS_SETTLE_VOLTAGE_COUNT_MASK GENMASK(28, 18) +#define CPR4_MARGIN_TEMP_CORE_TIMERS_SETTLE_VOLTAGE_COUNT_SHFT 18 + +#define CPR4_REG_MARGIN_ADJ_CTL 0x7F8 +#define CPR4_MARGIN_ADJ_HW_CLOSED_LOOP_EN BIT(4) +#define CPR4_MARGIN_ADJ_PER_RO_KV_MARGIN_EN BIT(7) +#define CPR4_MARGIN_ADJ_PMIC_STEP_SIZE_MASK GENMASK(16, 12) +#define CPR4_MARGIN_ADJ_PMIC_STEP_SIZE_SHIFT 12 +#define CPR4_MARGIN_ADJ_KV_MARGIN_ADJ_STEP_QUOT_MASK GENMASK(31, 26) +#define CPR4_MARGIN_ADJ_KV_MARGIN_ADJ_STEP_QUOT_SHIFT 26 + +#define CPR4_REG_CPR_MASK_THREAD(thread) (0x80C + 0x440 * (thread)) +#define CPR4_CPR_MASK_THREAD_DISABLE_THREAD BIT(31) +#define CPR4_CPR_MASK_THREAD_RO_MASK4THREAD_MASK GENMASK(15, 0) + +/* CPRh controller specific registers and bit definitions */ +#define __CPRH_REG_CORNER(rbase, tbase, tid, cnum) (rbase + (tbase * tid) + (0x4 * cnum)) +#define CPRH_REG_CORNER(d, t, c) __CPRH_REG_CORNER(d->reg_corner, d->reg_corner_tid, t, c) + +#define CPRH_CTL_OSM_ENABLED BIT(0) +#define CPRH_CTL_BASE_VOLTAGE_MASK GENMASK(10, 1) +#define CPRH_CTL_BASE_VOLTAGE_SHIFT 1 +#define CPRH_CTL_MODE_SWITCH_DELAY_MASK GENMASK(24, 17) +#define CPRH_CTL_MODE_SWITCH_DELAY_SHIFT 17 +#define CPRH_CTL_VOLTAGE_MULTIPLIER_MASK GENMASK(28, 25) +#define CPRH_CTL_VOLTAGE_MULTIPLIER_SHIFT 25 + +#define CPRH_CORNER_INIT_VOLTAGE_MASK GENMASK(7, 0) +#define CPRH_CORNER_INIT_VOLTAGE_SHIFT 0 +#define CPRH_CORNER_FLOOR_VOLTAGE_MASK GENMASK(15, 8) +#define CPRH_CORNER_FLOOR_VOLTAGE_SHIFT 8 +#define CPRH_CORNER_QUOT_DELTA_MASK GENMASK(24, 16) +#define CPRH_CORNER_QUOT_DELTA_SHIFT 16 +#define CPRH_CORNER_RO_SEL_MASK GENMASK(28, 25) +#define CPRH_CORNER_RO_SEL_SHIFT 25 +#define CPRH_CORNER_CPR_CL_DISABLE BIT(29) + +#define CPRH_CORNER_INIT_VOLTAGE_MAX_VALUE 255 +#define CPRH_CORNER_FLOOR_VOLTAGE_MAX_VALUE 255 +#define CPRH_CORNER_QUOT_DELTA_MAX_VALUE 511 + +enum cpr_type { + CTRL_TYPE_CPR3, + CTRL_TYPE_CPR4, + CTRL_TYPE_CPRH, + CTRL_TYPE_MAX, +}; + +/* + * struct cpr_thread_desc - CPR Thread-specific parameters + * + * @controller_id: Identifier of the CPR controller expected by the HW + * @ro_scaling_factor: Scaling factor for each ring oscillator entry + * @hw_tid: Identifier of the CPR thread expected by the HW + * @init_voltage_step: Voltage in uV for number of steps read from fuse array + * @init_voltage_width: Bit-width of the voltage read from the fuse array + * @sensor_range_start: First sensor ID used by a thread + * @sensor_range_end: Last sensor ID used by a thread + * @num_fuse_corners: Number of valid entries in fuse_corner_data + * @step_quot_init_min: Minimum achievable step quotient for this corner + * @step_quot_init_max: Maximum achievable step quotient for this corner + * @fuse_corner_data: Parameters for calculation of each fuse corner + */ +struct cpr_thread_desc { + u8 controller_id; + u8 hw_tid; + const int (*ro_scaling_factor)[CPR3_RO_COUNT]; + int ro_avail_corners; + int init_voltage_step; + int init_voltage_width; + u8 sensor_range_start; + u8 sensor_range_end; + u8 step_quot_init_min; + u8 step_quot_init_max; + unsigned int num_fuse_corners; + struct fuse_corner_data *fuse_corner_data; +}; + +/* + * struct cpr_desc - Driver instance-wide CPR parameters + * + * @cpr_type: Type (base version) of the CPR controller + * @num_threads: Max. number of threads supported by this controller + * @timer_delay_us: Loop delay time in uS + * @timer_updn_delay_us: Voltage after-up/before-down delay time in uS + * @timer_cons_up: Wait between consecutive up requests in uS + * @timer_cons_down: Wait between consecutive down requests in uS + * @up_threshold: Generic corner up threshold + * @down_threshold: Generic corner down threshold + * @idle_clocks: CPR Sensor: idle timer in cpr clocks unit + * @count_mode: CPR Sensor: counting mode + * @count_repeat: CPR Sensor: number of times to repeat reading + * @gcnt_us: CPR measurement interval in uS + * @vreg_step_fixed: Regulator voltage per step (if vreg unusable) + * @vreg_step_up_limit: Num. of steps up at once before re-measuring sensors + * @vreg_step_down_limit: Num. of steps dn at once before re-measuring sensors + * @vdd_settle_time_us: Settling timer to account for one VDD supply step + * @corner_settle_time_us: Settle time for corner switch request + * @mem_acc_threshold: Memory Accelerator (MEM-ACC) voltage threshold + * @apm_threshold: Array Power Mux (APM) voltage threshold + * @apm_crossover: Array Power Mux (APM) corner crossover voltage + * @apm_hysteresis: Hysteresis for APM V-threshold related calculations + * @cpr_base_voltage: Safety: Absolute minimum voltage (uV) on this CPR + * @cpr_max_voltage: Safety: Absolute maximum voltage (uV) on this CPR + * @pd_throttle_val: CPR Power Domain throttle during voltage switch + * @threads: Array containing "CPR Thread" specific parameters + * @reduce_to_fuse_uV: Reduce corner max volts (if higher) to fuse ceiling + * @reduce_to_corner_uV: Reduce corner max volts (if higher) to corner ceil. + * @hw_closed_loop_en: Enable CPR HW Closed-Loop voltage auto-adjustment + */ +struct cpr_desc { + enum cpr_type cpr_type; + unsigned int num_threads; + unsigned int timer_delay_us; + u8 timer_updn_delay_us; + u8 timer_cons_up; + u8 timer_cons_down; + u8 up_threshold; + u8 down_threshold; + u8 idle_clocks; + u8 count_mode; + u8 count_repeat; + u8 gcnt_us; + u16 vreg_step_fixed; + u8 vreg_step_up_limit; + u8 vreg_step_down_limit; + u8 vdd_settle_time_us; + u8 corner_settle_time_us; + int mem_acc_threshold; + int apm_threshold; + int apm_crossover; + int apm_hysteresis; + u32 cpr_base_voltage; + u32 cpr_max_voltage; + u32 pd_throttle_val; + + const struct cpr_thread_desc **threads; + bool reduce_to_fuse_uV; + bool reduce_to_corner_uV; + bool hw_closed_loop_en; +}; + +struct cpr_drv; +struct cpr_thread { + int num_corners; + int id; + bool enabled; + void __iomem *base; + struct clk *cpu_clk; + struct corner *corner; + struct corner *corners; + struct fuse_corner *fuse_corners; + struct cpr_drv *drv; + struct cpr_ext_data ext_data; + struct generic_pm_domain pd; + struct device *attached_cpu_dev; + struct work_struct restart_work; + bool restarting; + + const struct cpr_fuse *cpr_fuses; + const struct cpr_thread_desc *desc; +}; + +struct cpr_drv { + int irq; + unsigned int ref_clk_khz; + struct device *dev; + struct mutex lock; + struct regulator *vreg; + struct regmap *tcsr; + u32 gcnt; + u32 speed_bin; + u32 fusing_rev; + u32 last_uV; + u32 cpr_hw_rev; + u32 reg_corner; + u32 reg_corner_tid; + u32 reg_ctl; + u32 reg_status; + int fuse_level_set; + int extra_corners; + unsigned int vreg_step; + bool enabled; + + struct cpr_thread *threads; + struct genpd_onecell_data cell_data; + + const struct cpr_desc *desc; + const struct acc_desc *acc_desc; + struct dentry *debugfs; +}; + +/** + * cpr_get_ro_factor() - Get fuse corner ring oscillator factor + * + * Not all threads have different scaling factors for each + * Fuse Corner: if the RO factors are the same for all corners, + * then only one is specified, instead of uselessly repeating + * the same array for FC-times. + * This function checks for the same and gives back the right + * factor for the requested ring oscillator. + * + * Return: Ring oscillator factor + */ +static int cpr_get_ro_factor(const struct cpr_thread_desc *tdesc, + int fnum, int ro_idx) +{ + int ro_fnum; + + if (tdesc->ro_avail_corners == tdesc->num_fuse_corners) + ro_fnum = fnum; + else + ro_fnum = 0; + + return tdesc->ro_scaling_factor[ro_fnum][ro_idx]; +} + +static void cpr_write(struct cpr_thread *thread, u32 offset, u32 value) +{ + writel(value, thread->base + offset); +} + +static u32 cpr_read(struct cpr_thread *thread, u32 offset) +{ + return readl(thread->base + offset); +} + +static void +cpr_masked_write(struct cpr_thread *thread, u32 offset, u32 mask, u32 value) +{ + u32 val; + + val = readl(thread->base + offset); + val &= ~mask; + val |= value & mask; + writel(val, thread->base + offset); +} + +static void cpr_irq_clr(struct cpr_thread *thread) +{ + cpr_write(thread, CPR3_REG_IRQ_CLEAR, CPR3_IRQ_ALL); +} + +static void cpr_irq_clr_nack(struct cpr_thread *thread) +{ + cpr_irq_clr(thread); + cpr_write(thread, CPR3_REG_CONT_CMD, CPR3_CONT_CMD_NACK); +} + +static void cpr_irq_clr_ack(struct cpr_thread *thread) +{ + cpr_irq_clr(thread); + cpr_write(thread, CPR3_REG_CONT_CMD, CPR3_CONT_CMD_ACK); +} + +static void cpr_irq_set(struct cpr_thread *thread, u32 int_bits) +{ + /* On CPR-hardened, interrupts are managed by and on firmware */ + if (thread->drv->desc->cpr_type == CTRL_TYPE_CPRH) + return; + + cpr_write(thread, CPR3_REG_IRQ_EN, int_bits); +} + +/** + * cpr_ctl_enable() - Enable CPR thread + * @thread: Structure holding CPR thread-specific parameters + */ +static void cpr_ctl_enable(struct cpr_thread *thread) +{ + if (thread->drv->enabled && !thread->restarting) { + cpr_masked_write(thread, CPR3_REG_CPR_CTL, + CPR3_CPR_CTL_LOOP_EN_MASK, + CPR3_CPR_CTL_LOOP_EN_MASK); + } +} + +/** + * cpr_ctl_disable() - Disable CPR thread + * @thread: Structure holding CPR thread-specific parameters + */ +static void cpr_ctl_disable(struct cpr_thread *thread) +{ + const struct cpr_desc *desc = thread->drv->desc; + + if (desc->cpr_type != CTRL_TYPE_CPRH) { + cpr_irq_set(thread, 0); + cpr_irq_clr(thread); + } + + cpr_masked_write(thread, CPR3_REG_CPR_CTL, + CPR3_CPR_CTL_LOOP_EN_MASK, 0); +} + +/** + * cpr_ctl_is_enabled() - Check if thread is enabled + * @thread: Structure holding CPR thread-specific parameters + * + * Return: true if the CPR is enabled, false if it is disabled. + */ +static bool cpr_ctl_is_enabled(struct cpr_thread *thread) +{ + u32 reg_val; + + reg_val = cpr_read(thread, CPR3_REG_CPR_CTL); + return reg_val & CPR3_CPR_CTL_LOOP_EN_MASK; +} + +/** + * cpr_check_any_thread_busy() - Check if HW is done processing + * @thread: Structure holding CPR thread-specific parameters + * + * Return: true if the CPR is busy, false if it is ready. + */ +static bool cpr_check_any_thread_busy(struct cpr_thread *thread) +{ + int i; + + for (i = 0; i < thread->drv->desc->num_threads; i++) + if (cpr_read(thread, CPR3_REG_RESULT0(i)) & + CPR3_RESULT0_BUSY_MASK) + return true; + + return false; +} + +static void cpr_restart_worker(struct work_struct *work) +{ + struct cpr_thread *thread = container_of(work, struct cpr_thread, + restart_work); + struct cpr_drv *drv = thread->drv; + int i; + + mutex_lock(&drv->lock); + + thread->restarting = true; + cpr_ctl_disable(thread); + disable_irq(drv->irq); + + mutex_unlock(&drv->lock); + + for (i = 0; i < 20; i++) { + u32 cpr_status = cpr_read(thread, CPR3_REG_CPR_STATUS); + u32 ctl = cpr_read(thread, CPR3_REG_CPR_CTL); + + if ((cpr_status & CPR3_CPR_STATUS_BUSY_MASK) && + !(ctl & CPR3_CPR_CTL_LOOP_EN_MASK)) + break; + + udelay(10); + } + + cpr_irq_clr(thread); + + for (i = 0; i < 20; i++) { + u32 status = cpr_read(thread, CPR3_REG_IRQ_STATUS); + + if (!(status & CPR3_IRQ_ALL)) + break; + udelay(10); + } + + mutex_lock(&drv->lock); + + thread->restarting = false; + enable_irq(drv->irq); + cpr_ctl_enable(thread); + + mutex_unlock(&drv->lock); +} + +/** + * cpr_corner_restore() - Restore saved corner level + * @thread: Structure holding CPR thread-specific parameters + * @corner: Structure holding the saved corner level + */ +static void cpr_corner_restore(struct cpr_thread *thread, + struct corner *corner) +{ + struct cpr_drv *drv = thread->drv; + struct fuse_corner *fuse = corner->fuse_corner; + const struct cpr_thread_desc *tdesc = thread->desc; + u32 ro_sel = fuse->ring_osc_idx; + + cpr_write(thread, CPR3_REG_GCNT(ro_sel), drv->gcnt); + + cpr_write(thread, CPR3_REG_RO_MASK(tdesc->hw_tid), + CPR3_RO_MASK & ~BIT(ro_sel)); + + cpr_write(thread, CPR3_REG_TARGET_QUOT(tdesc->hw_tid, ro_sel), + fuse->quot - corner->quot_adjust); + + if (drv->desc->cpr_type == CTRL_TYPE_CPR4) { + cpr_masked_write(thread, + CPR4_REG_CPR_MASK_THREAD(tdesc->hw_tid), + CPR4_CPR_MASK_THREAD_DISABLE_THREAD | + CPR4_CPR_MASK_THREAD_RO_MASK4THREAD_MASK, 0); + } + + thread->corner = corner; + corner->last_uV = corner->uV; +} + +/** + * cpr_set_acc() - Set fuse level to the mem-acc + * @thread: Structure holding CPR thread-specific parameters + * @f: Fuse level + */ +static void cpr_set_acc(struct cpr_drv *drv, int f) +{ + const struct acc_desc *desc = drv->acc_desc; + struct reg_sequence *s = desc->settings; + int n = desc->num_regs_per_fuse; + + if (!s || f == drv->fuse_level_set) + return; + + regmap_multi_reg_write(drv->tcsr, s + (n * f), n); + drv->fuse_level_set = f; +} + +/** + * cpr_post_voltage() - Actions to execute before setting voltage + * @thread: Structure holding CPR thread-specific parameters + * @dir: Enumeration for voltage change direction + * @fuse_level: Fuse corner for mem-acc, if supported. + * + * Return: Zero for success or negative number on errors. + */ +static int cpr_pre_voltage(struct cpr_thread *thread, + enum voltage_change_dir dir, + int fuse_level) +{ + struct cpr_drv *drv = thread->drv; + + if (drv->desc->cpr_type == CTRL_TYPE_CPR3 && + drv->desc->pd_throttle_val) + cpr_write(thread, CPR3_REG_PD_THROTTLE, + drv->desc->pd_throttle_val); + + if (drv->tcsr && dir == DOWN) + cpr_set_acc(drv, fuse_level); + + return 0; +} + +/** + * cpr_post_voltage() - Actions to execute after setting voltage + * @thread: Structure holding CPR thread-specific parameters + * @dir: Enumeration for voltage change direction + * @fuse_level: Fuse corner for mem-acc, if supported. + * + * Return: Zero for success or negative number on errors. + */ +static int cpr_post_voltage(struct cpr_thread *thread, + enum voltage_change_dir dir, + int fuse_level) +{ + struct cpr_drv *drv = thread->drv; + + if (drv->tcsr && dir == UP) + cpr_set_acc(drv, fuse_level); + + if (drv->desc->cpr_type == CTRL_TYPE_CPR3) + cpr_write(thread, CPR3_REG_PD_THROTTLE, 0); + + return 0; +} + +/** + * cpr_commit_state() - Set the newly requested voltage + * @thread: Structure holding CPR thread-specific parameters + * + * Return: IRQ_SUCCESS for success, IRQ_NONE if the CPR is disabled. + */ +static int cpr_commit_state(struct cpr_thread *thread) +{ + struct cpr_drv *drv = thread->drv; + int min_uV = 0, max_uV = 0, new_uV = 0, fuse_level = 0; + enum voltage_change_dir dir; + u32 next_irqmask = 0; + int ret, i; + + /* On CPRhardened, control states are managed in firmware */ + if (drv->desc->cpr_type == CTRL_TYPE_CPRH) + return 0; + + for (i = 0; i < drv->desc->num_threads; i++) { + struct cpr_thread *thread = &drv->threads[i]; + + if (!thread->corner) + continue; + + fuse_level = max(fuse_level, + (int) (thread->corner->fuse_corner - + &thread->fuse_corners[0])); + + max_uV = max(max_uV, thread->corner->max_uV); + min_uV = max(min_uV, thread->corner->min_uV); + new_uV = max(new_uV, thread->corner->last_uV); + } + dev_vdbg(drv->dev, "%s: new uV: %d, last uV: %d\n", + __func__, new_uV, drv->last_uV); + + /* + * Safety measure: if the voltage is out of the globally allowed + * range, then go out and warn the user. + * This should *never* happen. + */ + if (new_uV > drv->desc->cpr_max_voltage || + new_uV < drv->desc->cpr_base_voltage) { + dev_warn(drv->dev, "Voltage (%u uV) out of range.", new_uV); + return -EINVAL; + } + + if (new_uV == drv->last_uV || fuse_level == drv->fuse_level_set) + goto out; + + if (fuse_level > drv->fuse_level_set) + dir = UP; + else + dir = DOWN; + + ret = cpr_pre_voltage(thread, fuse_level, dir); + if (ret) + return ret; + + dev_vdbg(drv->dev, "setting voltage: %d\n", new_uV); + + ret = regulator_set_voltage(drv->vreg, new_uV, new_uV); + if (ret) { + dev_err_ratelimited(drv->dev, "failed to set voltage %d: %d\n", new_uV, ret); + return ret; + } + + ret = cpr_post_voltage(thread, fuse_level, dir); + if (ret) + return ret; + + drv->last_uV = new_uV; +out: + if (new_uV > min_uV) + next_irqmask |= CPR3_IRQ_DOWN; + if (new_uV < max_uV) + next_irqmask |= CPR3_IRQ_UP; + + cpr_irq_set(thread, next_irqmask); + + return 0; +} + +static unsigned int cpr_get_cur_perf_state(struct cpr_thread *thread) +{ + return thread->corner ? thread->corner - thread->corners + 1 : 0; +} + +/** + * cpr_scale() - Calculate new voltage for the received direction + * @thread: Structure holding CPR thread-specific parameters + * @dir: Enumeration for voltage change direction + * + * The CPR scales one by one: this function calculates the new + * voltage to set when a voltage-UP or voltage-DOWN request comes + * and stores it into the per-thread structure that gets passed. + */ +static void cpr_scale(struct cpr_thread *thread, enum voltage_change_dir dir) +{ + struct cpr_drv *drv = thread->drv; + const struct cpr_thread_desc *tdesc = thread->desc; + u32 val, error_steps; + int last_uV, new_uV; + struct corner *corner; + + if (dir != UP && dir != DOWN) + return; + + corner = thread->corner; + val = cpr_read(thread, CPR3_REG_RESULT0(tdesc->hw_tid)); + error_steps = val >> CPR3_RESULT0_ERROR_STEPS_SHIFT; + error_steps &= CPR3_RESULT0_ERROR_STEPS_MASK; + + last_uV = corner->last_uV; + + if (dir == UP) { + if (!(val & CPR3_RESULT0_STEP_UP_MASK)) + return; + + /* Calculate new voltage */ + new_uV = last_uV + drv->vreg_step; + new_uV = min(new_uV, corner->max_uV); + + dev_vdbg(drv->dev, "[T%u] UP - new_uV=%d last_uV=%d p-state=%u st=%u\n", + thread->id, new_uV, last_uV, + cpr_get_cur_perf_state(thread), error_steps); + } else { + if (!(val & CPR3_RESULT0_STEP_DN_MASK)) + return; + + /* Calculate new voltage */ + new_uV = last_uV - drv->vreg_step; + new_uV = max(new_uV, corner->min_uV); + dev_vdbg(drv->dev, "[T%u] DOWN - new_uV=%d last_uV=%d p-state=%u st=%u\n", + thread->id, new_uV, last_uV, + cpr_get_cur_perf_state(thread), error_steps); + } + corner->last_uV = new_uV; +} + +/** + * cpr_irq_handler() - Handle CPR3/CPR4 status interrupts + * @irq: Number of the interrupt + * @dev: Pointer to the cpr_thread structure + * + * Handle the interrupts coming from non-hardened CPR HW as to get + * an ok to scale voltages immediately, or to pass error status to + * the hardware (either success/ACK or failure/NACK). + * + * Return: IRQ_SUCCESS for success, IRQ_NONE if the CPR is disabled. + */ +static irqreturn_t cpr_irq_handler(int irq, void *dev) +{ + struct cpr_thread *thread = dev; + struct cpr_drv *drv = thread->drv; + irqreturn_t ret = IRQ_HANDLED; + int i, rc; + enum voltage_change_dir dir = NO_CHANGE; + u32 val; + + mutex_lock(&drv->lock); + + val = cpr_read(thread, CPR3_REG_IRQ_STATUS); + + dev_vdbg(drv->dev, "IRQ_STATUS = %#02x\n", val); + + if (!cpr_ctl_is_enabled(thread)) { + dev_vdbg(drv->dev, "CPR is disabled\n"); + ret = IRQ_NONE; + } else if (cpr_check_any_thread_busy(thread)) { + cpr_irq_clr_nack(thread); + dev_dbg(drv->dev, "CPR measurement is not ready\n"); + } else { + /* + * Following sequence of handling is as per each IRQ's + * priority + */ + if (val & CPR3_IRQ_UP) + dir = UP; + else if (val & CPR3_IRQ_DOWN) + dir = DOWN; + + if (dir != NO_CHANGE) { + for (i = 0; i < drv->desc->num_threads; i++) { + thread = &drv->threads[i]; + cpr_scale(thread, dir); + } + + rc = cpr_commit_state(thread); + if (rc) + cpr_irq_clr_nack(thread); + else + cpr_irq_clr_ack(thread); + } else if (val & CPR3_IRQ_MID) { + dev_dbg(drv->dev, "IRQ occurred for Mid Flag\n"); + } else { + dev_warn(drv->dev, "IRQ occurred for unknown flag (%#08x)\n", val); + schedule_work(&thread->restart_work); + } + } + + mutex_unlock(&drv->lock); + + return ret; +} + +static int cpr_switch(struct cpr_drv *drv) +{ + int i, ret; + bool enabled = false; + + if (drv->desc->cpr_type == CTRL_TYPE_CPRH) + return 0; + + for (i = 0; i < drv->desc->num_threads && !enabled; i++) + enabled = drv->threads[i].enabled; + + dev_vdbg(drv->dev, "%s: enabled = %d\n", __func__, enabled); + + if (enabled == drv->enabled) + return 0; + + if (enabled) { + ret = regulator_enable(drv->vreg); + if (ret) + return ret; + + drv->enabled = enabled; + + for (i = 0; i < drv->desc->num_threads; i++) + if (drv->threads[i].corner) + break; + + if (i < drv->desc->num_threads) { + cpr_irq_clr(&drv->threads[i]); + + cpr_commit_state(&drv->threads[i]); + cpr_ctl_enable(&drv->threads[i]); + } + } else { + for (i = 0; i < drv->desc->num_threads && !enabled; i++) + cpr_ctl_disable(&drv->threads[i]); + + drv->enabled = enabled; + + ret = regulator_disable(drv->vreg); + if (ret < 0) + return ret; + } + + return 0; +} + +/** + * cpr_enable() - Enables a CPR thread + * @thread: Structure holding CPR thread-specific parameters + * + * Return: Zero for success or negative number on errors. + */ +static int cpr_enable(struct cpr_thread *thread) +{ + struct cpr_drv *drv = thread->drv; + int ret; + + dev_dbg(drv->dev, "Enabling thread %d\n", thread->id); + + mutex_lock(&drv->lock); + + thread->enabled = true; + ret = cpr_switch(thread->drv); + + mutex_unlock(&drv->lock); + + return ret; +} + +/** + * cpr_disable() - Disables a CPR thread + * @thread: Structure holding CPR thread-specific parameters + * + * Return: Zero for success or negative number on errors. + */ +static int cpr_disable(struct cpr_thread *thread) +{ + struct cpr_drv *drv = thread->drv; + int ret; + + dev_dbg(drv->dev, "Disabling thread %d\n", thread->id); + + mutex_lock(&drv->lock); + + thread->enabled = false; + ret = cpr_switch(thread->drv); + + mutex_unlock(&drv->lock); + + return ret; +} + +/** + * cpr_configure() - Configure main HW parameters + * @thread: Structure holding CPR thread-specific parameters + * + * This function configures the main CPR hardware parameters, such as + * internal timers (and delays), sensor ownerships, activates and/or + * deactivates cpr-threads and others, as one sequence for all of the + * versions supported in this driver. By design, the function may + * return a success earlier if the sequence for "a previous version" + * has ended. + * + * Context: The CPR must be clocked before calling this function! + * + * Return: Zero for success or negative number on errors. + */ +static int cpr_configure(struct cpr_thread *thread) +{ + struct cpr_drv *drv = thread->drv; + const struct cpr_desc *desc = drv->desc; + const struct cpr_thread_desc *tdesc = thread->desc; + u32 val; + int i; + + /* Disable interrupt and CPR */ + cpr_irq_set(thread, 0); + cpr_write(thread, CPR3_REG_CPR_CTL, 0); + + /* Init and save gcnt */ + drv->gcnt = drv->ref_clk_khz * desc->gcnt_us; + do_div(drv->gcnt, 1000); + + /* Program the delay count for the timer */ + val = drv->ref_clk_khz * desc->timer_delay_us; + do_div(val, 1000); + if (desc->cpr_type == CTRL_TYPE_CPR3) { + cpr_write(thread, CPR3_REG_CPR_TIMER_MID_CONT, val); + + val = drv->ref_clk_khz * desc->timer_updn_delay_us; + do_div(val, 1000); + cpr_write(thread, CPR3_REG_CPR_TIMER_UP_DN_CONT, val); + } else { + cpr_write(thread, CPR3_REG_CPR_TIMER_AUTO_CONT, val); + } + dev_dbg(drv->dev, "Timer count: %#0x (for %d us)\n", val, + desc->timer_delay_us); + + /* Program the control register */ + val = desc->idle_clocks << CPR3_CPR_CTL_IDLE_CLOCKS_SHIFT; + val |= desc->count_mode << CPR3_CPR_CTL_COUNT_MODE_SHIFT; + val |= desc->count_repeat << CPR3_CPR_CTL_COUNT_REPEAT_SHIFT; + cpr_write(thread, CPR3_REG_CPR_CTL, val); + + /* Configure CPR default step quotients */ + val = tdesc->step_quot_init_min << CPR3_CPR_STEP_QUOT_MIN_SHIFT; + val |= tdesc->step_quot_init_max << CPR3_CPR_STEP_QUOT_MAX_SHIFT; + + cpr_write(thread, CPR3_REG_CPR_STEP_QUOT, val); + + /* + * Configure the CPR sensor ownership always on thread 0 + * TODO: SDM845 has different ownership for sensors!! + */ + for (i = tdesc->sensor_range_start; i < tdesc->sensor_range_end; i++) + cpr_write(thread, CPR3_REG_SENSOR_OWNER(i), 0); + + /* Program Consecutive Up & Down */ + val = desc->timer_cons_up << CPR3_THRESH_CONS_UP_SHIFT; + val |= desc->timer_cons_down << CPR3_THRESH_CONS_DOWN_SHIFT; + val |= desc->up_threshold << CPR3_THRESH_UP_THRESH_SHIFT; + val |= desc->down_threshold << CPR3_THRESH_DOWN_THRESH_SHIFT; + cpr_write(thread, CPR3_REG_THRESH(tdesc->hw_tid), val); + + /* Mask all ring oscillators for all threads initially */ + cpr_write(thread, CPR3_REG_RO_MASK(tdesc->hw_tid), CPR3_RO_MASK); + + /* HW Closed-loop control */ + if (desc->cpr_type == CTRL_TYPE_CPR3) { + cpr_write(thread, CPR3_REG_HW_CLOSED_LOOP_DISABLED, + !desc->hw_closed_loop_en); + } else { + cpr_masked_write(thread, CPR4_REG_MARGIN_ADJ_CTL, + CPR4_MARGIN_ADJ_HW_CLOSED_LOOP_EN, + desc->hw_closed_loop_en ? + CPR4_MARGIN_ADJ_HW_CLOSED_LOOP_EN : 0); + } + + /* Additional configuration for CPR4 and beyond */ + if (desc->cpr_type < CTRL_TYPE_CPR4) + return 0; + + /* Disable threads initially only on non-hardened CPR4 */ + if (desc->cpr_type == CTRL_TYPE_CPR4) { + cpr_masked_write(thread, CPR4_REG_CPR_MASK_THREAD(1), + CPR4_CPR_MASK_THREAD_DISABLE_THREAD | + CPR4_CPR_MASK_THREAD_RO_MASK4THREAD_MASK, + CPR4_CPR_MASK_THREAD_DISABLE_THREAD | + CPR4_CPR_MASK_THREAD_RO_MASK4THREAD_MASK); + } + + if (tdesc->hw_tid > 0) { + cpr_masked_write(thread, CPR4_REG_MISC, + CPR4_MISC_RESET_STEP_QUOT_LOOP_EN | + CPR4_MISC_THREAD_HAS_ALWAYS_VOTE_EN, + CPR4_MISC_RESET_STEP_QUOT_LOOP_EN | + CPR4_MISC_THREAD_HAS_ALWAYS_VOTE_EN); + } + + val = drv->vreg_step; + do_div(val, 1000); + cpr_masked_write(thread, CPR4_REG_MARGIN_ADJ_CTL, + CPR4_MARGIN_ADJ_PMIC_STEP_SIZE_MASK, + val << CPR4_MARGIN_ADJ_PMIC_STEP_SIZE_SHIFT); + + cpr_masked_write(thread, CPR4_REG_SAW_ERROR_STEP_LIMIT, + CPR4_SAW_ERROR_STEP_LIMIT_DN_MASK, + desc->vreg_step_down_limit << + CPR4_SAW_ERROR_STEP_LIMIT_DN_SHIFT); + + cpr_masked_write(thread, CPR4_REG_SAW_ERROR_STEP_LIMIT, + CPR4_SAW_ERROR_STEP_LIMIT_UP_MASK, + desc->vreg_step_up_limit << + CPR4_SAW_ERROR_STEP_LIMIT_UP_SHIFT); + + cpr_masked_write(thread, CPR4_REG_MARGIN_ADJ_CTL, + CPR4_MARGIN_ADJ_PER_RO_KV_MARGIN_EN, + CPR4_MARGIN_ADJ_PER_RO_KV_MARGIN_EN); + + if (tdesc->hw_tid > 0) { + cpr_masked_write(thread, CPR4_REG_CPR_TIMER_CLAMP, + CPR4_CPR_TIMER_CLAMP_THREAD_AGGREGATION_EN, + CPR4_CPR_TIMER_CLAMP_THREAD_AGGREGATION_EN); + } + + /* Settling timer to account for one VDD supply step */ + if (desc->vdd_settle_time_us > 0) { + u32 m = CPR4_MARGIN_TEMP_CORE_TIMERS_SETTLE_VOLTAGE_COUNT_MASK; + u32 s = CPR4_MARGIN_TEMP_CORE_TIMERS_SETTLE_VOLTAGE_COUNT_SHFT; + + cpr_masked_write(thread, CPR4_REG_MARGIN_TEMP_CORE_TIMERS, + m, desc->vdd_settle_time_us << s); + } + + /* Additional configuration for CPR-hardened */ + if (desc->cpr_type < CTRL_TYPE_CPRH) + return 0; + + /* Settling timer to account for one corner-switch request */ + if (desc->corner_settle_time_us > 0) { + cpr_masked_write(thread, drv->reg_ctl, + CPRH_CTL_MODE_SWITCH_DELAY_MASK, + desc->corner_settle_time_us << + CPRH_CTL_MODE_SWITCH_DELAY_SHIFT); + } + + /* Base voltage and multiplier values for CPRh internal calculations */ + cpr_masked_write(thread, drv->reg_ctl, + CPRH_CTL_BASE_VOLTAGE_MASK, + (DIV_ROUND_UP(desc->cpr_base_voltage, + drv->vreg_step) << + CPRH_CTL_BASE_VOLTAGE_SHIFT)); + + cpr_masked_write(thread, drv->reg_ctl, + CPRH_CTL_VOLTAGE_MULTIPLIER_MASK, + DIV_ROUND_UP(drv->vreg_step, 1000) << + CPRH_CTL_VOLTAGE_MULTIPLIER_SHIFT); + + return 0; +} + + +static int cprh_dummy_set_performance_state(struct generic_pm_domain *domain, + unsigned int state) +{ + return 0; +} + +static int cpr_set_performance_state(struct generic_pm_domain *domain, + unsigned int state) +{ + struct cpr_thread *thread = container_of(domain, struct cpr_thread, pd); + struct cpr_drv *drv = thread->drv; + struct corner *corner, *end; + int ret = 0; + + mutex_lock(&drv->lock); + + dev_dbg(drv->dev, "setting perf state: %u (prev state: %u thread: %u)\n", + state, cpr_get_cur_perf_state(thread), thread->id); + + /* + * Determine new corner we're going to. + * Remove one since lowest performance state is 1. + */ + corner = thread->corners + state - 1; + end = &thread->corners[thread->num_corners - 1]; + if (corner > end || corner < thread->corners) { + ret = -EINVAL; + goto unlock; + } + + cpr_ctl_disable(thread); + + cpr_irq_clr(thread); + if (thread->corner != corner) + cpr_corner_restore(thread, corner); + + ret = cpr_commit_state(thread); + if (ret) + goto unlock; + + cpr_ctl_enable(thread); +unlock: + mutex_unlock(&drv->lock); + + dev_dbg(drv->dev, "set perf state %u on thread %u\n", state, thread->id); + + return ret; +} + +/** + * cpr3_adjust_quot - Adjust the closed-loop quotients + * @thread: Structure holding CPR thread-specific parameters + * + * Calculates the quotient adjustment factor based on closed-loop + * quotients and ring oscillator factor. + * + * Return: Adjusted quotient + */ +static int cpr3_adjust_quot(int ring_osc_factor, int volt_closed_loop) +{ + s64 temp; + + if (ring_osc_factor == 0 || volt_closed_loop == 0) + return 0; + + temp = (s64)(ring_osc_factor * volt_closed_loop); + return (int)div_s64(temp, 1000000); +} + +/** + * cpr_fuse_corner_init() - Calculate fuse corner table + * @thread: Structure holding CPR thread-specific parameters + * + * This function populates the fuse corners table by reading the + * values from the fuses, eventually adjusting them with a fixed + * per-corner offset and doing basic checks about them being + * supported by the regulator that is assigned to this CPR - if + * it is available (on CPR-Hardened, there is no usable vreg, as + * that is protected by the hypervisor). + * + * Return: Zero for success, negative number on error + */ +static int cpr_fuse_corner_init(struct cpr_thread *thread) +{ + struct cpr_drv *drv = thread->drv; + const struct cpr_thread_desc *desc = thread->desc; + const struct cpr_fuse *cpr_fuse = thread->cpr_fuses; + struct fuse_corner_data *fdata; + struct fuse_corner *fuse, *prev_fuse, *end; + int i, ret; + + /* Populate fuse_corner members */ + fuse = thread->fuse_corners; + prev_fuse = &fuse[0]; + end = &fuse[desc->num_fuse_corners - 1]; + fdata = desc->fuse_corner_data; + + for (i = 0; fuse <= end; fuse++, cpr_fuse++, i++, fdata++) { + int factor = cpr_get_ro_factor(desc, i, fuse->ring_osc_idx); + + ret = cpr_populate_fuse_common(drv->dev, fdata, cpr_fuse, + fuse, drv->vreg_step, + desc->init_voltage_width, + desc->init_voltage_step); + if (ret) + return ret; + + /* + * Adjust the fuse quot with per-fuse-corner closed-loop + * voltage adjustment parameters. + */ + fuse->quot += cpr3_adjust_quot(factor, fdata->volt_cloop_adjust); + + /* CPRh: no regulator access... */ + if (drv->desc->cpr_type == CTRL_TYPE_CPRH) + goto skip_pvs_restrict; + + /* Re-check if corner voltage range is supported by regulator */ + ret = cpr_check_vreg_constraints(drv->dev, drv->vreg, fuse); + if (ret) + return ret; + +skip_pvs_restrict: + if (fuse->uV < prev_fuse->uV) + fuse->uV = prev_fuse->uV; + prev_fuse = fuse; + dev_dbg(drv->dev, "fuse corner %d: [%d %d %d] RO%hhu quot %d\n", + i, fuse->min_uV, fuse->uV, fuse->max_uV, + fuse->ring_osc_idx, fuse->quot); + + /* Check if constraints are valid */ + if (fuse->uV < fuse->min_uV || fuse->uV > fuse->max_uV) { + dev_err(drv->dev, "fuse corner %d: Bad voltage range.\n", i); + return -EINVAL; + } + } + + return 0; +} + +static void cpr3_restrict_corner(struct corner *corner, int threshold, + int hysteresis, int step) +{ + if (threshold > corner->min_uV && threshold <= corner->max_uV) { + if (corner->uV >= threshold) { + corner->min_uV = max(corner->min_uV, + threshold - hysteresis); + if (corner->min_uV > corner->uV) + corner->uV = corner->min_uV; + } else { + corner->max_uV = threshold; + corner->max_uV -= step; + } + } +} + +/* + * cprh_corner_adjust_opps() - Set voltage on each CPU OPP table entry + * + * On CPR-Hardened, the voltage level is controlled internally through + * the OSM hardware: in order to initialize the latter, we have to + * communicate the voltage to its driver, so that it will be able to + * write the right parameters (as they have to be set both on the CPRh + * and on the OSM) on it. + * This function is called only for CPRh. + * + * Return: Zero for success, negative number for error. + */ +static int cprh_corner_adjust_opps(struct cpr_thread *thread) +{ + struct corner *corner = thread->corners; + struct cpr_drv *drv = thread->drv; + int i, ret; + + for (i = 0; i < thread->num_corners; i++) { + ret = dev_pm_opp_adjust_voltage(thread->attached_cpu_dev, + corner[i].freq, + corner[i].uV, + corner[i].min_uV, + corner[i].max_uV); + if (ret) + break; + + dev_dbg(drv->dev, "OPP voltage adjusted for %lu kHz, %d uV\n", + corner[i].freq, corner[i].uV); + } + + /* If we couldn't adjust voltage for all corners, something went wrong */ + if (i < thread->num_corners) + return -EINVAL; + + return ret; +} + +/** + * cpr3_corner_init() - Calculate and set-up corners for the CPR HW + * @thread: Structure holding CPR thread-specific parameters + * + * This function calculates all the corner parameters by comparing + * and interpolating the values read from the various set-points + * read from the fuses (also called "fuse corners") to generate and + * program to the CPR a lookup table that describes each voltage + * step, mapped to a performance level (or corner number). + * + * It also programs other essential parameters on the CPR and - if + * we are dealing with CPR-Hardened, it will also enable the internal + * interface between the Operating State Manager (OSM) and the CPRh + * in order to achieve CPU DVFS. + * + * Return: Zero for success, negative number on error + */ +static int cpr3_corner_init(struct cpr_thread *thread) +{ + struct cpr_drv *drv = thread->drv; + const struct cpr_desc *desc = drv->desc; + const struct cpr_thread_desc *tdesc = thread->desc; + const struct cpr_fuse *fuses = thread->cpr_fuses; + int i, ret, total_corners, extra_corners, level, scaling = 0; + unsigned int fnum, fc; + const char *quot_offset; + const struct fuse_corner_data *fdata; + struct fuse_corner *fuse, *prev_fuse; + struct corner *corner, *prev_corner, *end; + struct corner_data *cdata; + struct dev_pm_opp *opp; + unsigned long freq; + u32 ring_osc_mask = CPR3_RO_MASK, min_quotient = U32_MAX; + + corner = thread->corners; + prev_corner = &thread->corners[0]; + end = &corner[thread->num_corners - 1]; + + cdata = devm_kcalloc(drv->dev, thread->num_corners + drv->extra_corners, + sizeof(struct corner_data), GFP_KERNEL); + if (!cdata) + return -ENOMEM; + + for (level = 1; level <= thread->num_corners; level++) { + opp = dev_pm_opp_find_level_exact(&thread->pd.dev, level); + if (IS_ERR(opp)) + return -EINVAL; + + /* + * If there is only one specified qcom,opp-fuse-level, then + * it is assumed that this only one is global and valid for + * all IDs, so try to get the specific one but, on failure, + * go for the global one. + */ + fc = cpr_get_fuse_corner(opp, thread->id); + if (fc == 0) { + fc = cpr_get_fuse_corner(opp, 0); + if (fc == 0) { + dev_err(drv->dev, "qcom,opp-fuse-level is missing!\n"); + dev_pm_opp_put(opp); + return -EINVAL; + } + } + fnum = fc - 1; + + freq = cpr_get_opp_hz_for_req(opp, thread->attached_cpu_dev); + if (!freq) { + thread->num_corners = max(level - 1, 0); + end = &thread->corners[thread->num_corners - 1]; + break; + } + + /* + * If any post-vadj (open/closed loop) is not specified, then + * it's zero, meaning that it is not required for this corner. + */ + cpr_get_corner_post_vadj(opp, thread->id, + &cdata[level - 1].oloop_vadj, + &cdata[level - 1].cloop_vadj); + cdata[level - 1].fuse_corner = fnum; + cdata[level - 1].freq = freq; + + fuse = &thread->fuse_corners[fnum]; + dev_dbg(drv->dev, "freq: %lu level: %u fuse level: %u\n", + freq, dev_pm_opp_get_level(opp) - 1, fnum); + if (freq > fuse->max_freq) + fuse->max_freq = freq; + dev_pm_opp_put(opp); + + /* + * Make sure that the frequencies in the table are in ascending + * order, as this is critical for the algorithm to work. + */ + if (cdata[level - 2].freq > freq) { + dev_err(drv->dev, "Frequency table not in ascending order.\n"); + return -EINVAL; + } + } + + if (thread->num_corners < 2) { + dev_err(drv->dev, "need at least 2 OPPs to use CPR\n"); + return -EINVAL; + } + + /* + * Get the quotient adjustment scaling factor, according to: + * + * scaling = min(1000 * (QUOT(corner_N) - QUOT(corner_N-1)) + * / (freq(corner_N) - freq(corner_N-1)), max_factor) + * + * QUOT(corner_N): quotient read from fuse for fuse corner N + * QUOT(corner_N-1): quotient read from fuse for fuse corner (N - 1) + * freq(corner_N): max frequency in MHz supported by fuse corner N + * freq(corner_N-1): max frequency in MHz supported by fuse corner + * (N - 1) + * + * Then walk through the corners mapped to each fuse corner + * and calculate the quotient adjustment for each one using the + * following formula: + * + * quot_adjust = (freq_max - freq_corner) * scaling / 1000 + * + * freq_max: max frequency in MHz supported by the fuse corner + * freq_corner: frequency in MHz corresponding to the corner + * scaling: calculated from above equation + * + * + * + + + * | v | + * q | f c o | f c + * u | c l | c + * o | f t | f + * t | c a | c + * | c f g | c f + * | e | + * +--------------- +---------------- + * 0 1 2 3 4 5 6 0 1 2 3 4 5 6 + * corner corner + * + * c = corner + * f = fuse corner + * + */ + for (i = 0; corner <= end; corner++, i++) { + unsigned long freq_diff_mhz; + int ro_fac, vadj, prev_quot; + + fnum = cdata[i].fuse_corner; + fdata = &tdesc->fuse_corner_data[fnum]; + quot_offset = fuses[fnum].quotient_offset; + fuse = &thread->fuse_corners[fnum]; + ring_osc_mask &= (u16)(~BIT(fuse->ring_osc_idx)); + if (fnum) + prev_fuse = &thread->fuse_corners[fnum - 1]; + else + prev_fuse = NULL; + + corner->fuse_corner = fuse; + corner->freq = cdata[i].freq; + corner->uV = fuse->uV; + + if (prev_fuse) { + if (prev_fuse->ring_osc_idx == fuse->ring_osc_idx) + quot_offset = NULL; + + scaling = cpr_calculate_scaling(quot_offset, drv->dev, + fdata, corner); + if (scaling < 0) + return scaling; + + freq_diff_mhz = fuse->max_freq - corner->freq; + do_div(freq_diff_mhz, 1000000); /* now in MHz */ + + corner->quot_adjust = scaling * freq_diff_mhz; + do_div(corner->quot_adjust, 1000); + + /* Fine-tune QUOT (closed-loop) based on fixed values */ + ro_fac = cpr_get_ro_factor(tdesc, fnum, fuse->ring_osc_idx); + vadj = cdata[i].cloop_vadj; + corner->quot_adjust -= cpr3_adjust_quot(ro_fac, vadj); + dev_vdbg(drv->dev, "Quot fine-tuning to %d for post-vadj=%d\n", + corner->quot_adjust, vadj); + + /* + * Make sure that we scale (up) monotonically. + * P.S.: Fuse quots can never be descending. + */ + prev_quot = prev_corner->fuse_corner->quot; + prev_quot -= prev_corner->quot_adjust; + if (fuse->quot - corner->quot_adjust < prev_quot) { + int new_adj = prev_corner->fuse_corner->quot; + new_adj -= fuse->quot; + dev_vdbg(drv->dev, "Monotonic increase forced: %d->%d\n", + corner->quot_adjust, new_adj); + corner->quot_adjust = new_adj; + } + + corner->uV = cpr_interpolate(corner, + drv->vreg_step, fdata); + } + /* Negative fuse quotients are nonsense. */ + if (fuse->quot < corner->quot_adjust) + return -EINVAL; + + min_quotient = min(min_quotient, + (u32)(fuse->quot - corner->quot_adjust)); + + /* Fine-tune voltages (open-loop) based on fixed values */ + corner->uV += cdata[i].oloop_vadj; + dev_dbg(drv->dev, "Voltage fine-tuning to %d for post-vadj=%d\n", + corner->uV, cdata[i].oloop_vadj); + + corner->max_uV = fuse->max_uV; + corner->min_uV = fuse->min_uV; + corner->uV = clamp(corner->uV, corner->min_uV, corner->max_uV); + dev_vdbg(drv->dev, "Clamped after interpolation: [%d %d %d]\n", + corner->min_uV, corner->uV, corner->max_uV); + + /* Make sure that we scale monotonically here, too. */ + if (corner->uV < prev_corner->uV) + corner->uV = prev_corner->uV; + + corner->last_uV = corner->uV; + + /* Reduce the ceiling voltage if needed */ + if (desc->reduce_to_corner_uV && corner->uV < corner->max_uV) + corner->max_uV = corner->uV; + else if (desc->reduce_to_fuse_uV && fuse->uV < corner->max_uV) + corner->max_uV = max(corner->min_uV, fuse->uV); + + corner->min_uV = max(corner->max_uV - fdata->range_uV, + corner->min_uV); + + /* + * Adjust per-corner floor and ceiling voltages so that + * they do not overlap the memory Array Power Mux (APM) + * nor the Memory Accelerator (MEM-ACC) threshold voltages. + */ + if (desc->apm_threshold) + cpr3_restrict_corner(corner, desc->apm_threshold, + desc->apm_hysteresis, + drv->vreg_step); + if (desc->mem_acc_threshold) + cpr3_restrict_corner(corner, desc->mem_acc_threshold, + 0, drv->vreg_step); + + prev_corner = corner; + dev_dbg(drv->dev, "corner %d: [%d %d %d] scaling %d quot %d\n", i, + corner->min_uV, corner->uV, corner->max_uV, scaling, + fuse->quot - corner->quot_adjust); + } + + /* Additional setup for CPRh only */ + if (desc->cpr_type < CTRL_TYPE_CPRH) + return 0; + + /* If the OPPs can't be adjusted, programming the CPRh is useless */ + ret = cprh_corner_adjust_opps(thread); + if (ret) { + dev_err(drv->dev, "Cannot adjust CPU OPP voltages: %d\n", ret); + return ret; + } + + total_corners = thread->num_corners; + extra_corners = drv->extra_corners; + + /* If the APM extra corner exists, add it now. */ + if (desc->apm_crossover && desc->apm_threshold && extra_corners) { + /* Program the APM crossover corner on the CPR-Hardened */ + thread->corners[total_corners].uV = desc->apm_crossover; + thread->corners[total_corners].min_uV = desc->apm_crossover; + thread->corners[total_corners].max_uV = desc->apm_crossover; + thread->corners[total_corners].is_open_loop = true; + + /* + * We have calculated the APM parameters for this clock plan: + * make the APM *threshold* available to external callers. + * The crossover is used only internally in the CPR. + */ + thread->ext_data.apm_threshold_uV = desc->apm_threshold; + + dev_dbg(drv->dev, "corner %d (APM): [%d %d %d] Open-Loop\n", + total_corners, desc->apm_crossover, + desc->apm_crossover, desc->apm_crossover); + + total_corners++; + extra_corners--; + } + + if (desc->mem_acc_threshold && extra_corners) { + /* Program the Memory Accelerator threshold corner to CPRh */ + thread->corners[total_corners].uV = desc->mem_acc_threshold; + thread->corners[total_corners].min_uV = desc->mem_acc_threshold; + thread->corners[total_corners].max_uV = desc->mem_acc_threshold; + thread->corners[total_corners].is_open_loop = true; + + /* + * We have calculated a mem-acc threshold for this clock plan: + * make it available to external callers. + */ + thread->ext_data.mem_acc_threshold_uV = desc->mem_acc_threshold; + + dev_dbg(drv->dev, "corner %d (MEMACC): [%d %d %d] Open-Loop\n", + total_corners, desc->mem_acc_threshold, + desc->mem_acc_threshold, desc->mem_acc_threshold); + + total_corners++; + extra_corners--; + } + + /* + * If there are any extra corners left, it means that even though we + * expect to fill in both APM and MEM-ACC crossovers, one couldn't + * satisfy requirements, which means that the specified parameters + * are wrong: in this case, inform the user and bail out, otherwise + * if we go on writing the (invalid) table to the CPR-Hardened, the + * hardware (in this case, the CPU) will surely freeze and crash. + */ + if (unlikely(extra_corners)) { + dev_err(drv->dev, "APM/MEM-ACC corners: bad parameters.\n"); + return -EINVAL; + } + /* Reassign extra_corners, as we have to exclude delta_quot for them */ + extra_corners = drv->extra_corners; + + /* Disable the interface between OSM and CPRh */ + cpr_masked_write(thread, drv->reg_ctl, + CPRH_CTL_OSM_ENABLED, 0); + + /* Program the GCNT before unmasking ring oscillator(s) */ + for (i = 0; i < CPR3_RO_COUNT; i++) { + if (!(ring_osc_mask & BIT(i))) { + cpr_write(thread, CPR3_REG_GCNT(i), drv->gcnt); + dev_vdbg(drv->dev, "RO%d gcnt=%d\n", i, drv->gcnt); + } + } + + /* + * Unmask the ring oscillator(s) that we're going to use: it seems + * to be mandatory to do this *before* sending the rest of the + * CPRhardened specific configuration. + */ + dev_dbg(drv->dev, "Unmasking ring oscillators with mask 0x%x\n", ring_osc_mask); + cpr_write(thread, CPR3_REG_RO_MASK(tdesc->hw_tid), ring_osc_mask); + + /* Setup minimum quotients for ring oscillators */ + for (i = 0; i < CPR3_RO_COUNT; i++) { + u32 tgt_quot_reg = CPR3_REG_TARGET_QUOT(tdesc->hw_tid, i); + u32 tgt_quot_val = 0; + + if (!(ring_osc_mask & BIT(i))) + tgt_quot_val = min_quotient; + + cpr_write(thread, tgt_quot_reg, tgt_quot_val); + dev_vdbg(drv->dev, "Programmed min quotient %u for Ring Oscillator %d\n", + tgt_quot_val, tgt_quot_reg); + } + + for (i = 0; i < total_corners; i++) { + int volt_oloop_steps, volt_floor_steps, delta_quot_steps; + int ring_osc; + u32 val; + + fnum = cdata[i].fuse_corner; + fuse = &thread->fuse_corners[fnum]; + + val = thread->corners[i].uV - desc->cpr_base_voltage; + volt_oloop_steps = DIV_ROUND_UP(val, drv->vreg_step); + + val = thread->corners[i].min_uV - desc->cpr_base_voltage; + volt_floor_steps = DIV_ROUND_UP(val, drv->vreg_step); + + /* + * If we are accessing corners that are not used as + * an active DCVS set-point, then always select RO 0 + * and zero out the delta quotient. + */ + if (i >= thread->num_corners) { + ring_osc = 0; + delta_quot_steps = 0; + } else { + ring_osc = fuse->ring_osc_idx; + val = fuse->quot - thread->corners[i].quot_adjust; + val -= min_quotient; + delta_quot_steps = DIV_ROUND_UP(val, + CPRH_DELTA_QUOT_STEP_FACTOR); + } + + if (volt_oloop_steps > CPRH_CORNER_INIT_VOLTAGE_MAX_VALUE || + volt_floor_steps > CPRH_CORNER_FLOOR_VOLTAGE_MAX_VALUE || + delta_quot_steps > CPRH_CORNER_QUOT_DELTA_MAX_VALUE) { + dev_err(drv->dev, "Invalid cfg: oloop=%d, floor=%d, delta=%d\n", + volt_oloop_steps, volt_floor_steps, + delta_quot_steps); + return -EINVAL; + } + /* Green light: Go, Go, Go! */ + + /* Set number of open-loop steps */ + val = volt_oloop_steps << CPRH_CORNER_INIT_VOLTAGE_SHIFT; + val &= CPRH_CORNER_INIT_VOLTAGE_MASK; + + /* Set number of floor voltage steps */ + val |= (volt_floor_steps << CPRH_CORNER_FLOOR_VOLTAGE_SHIFT) & + CPRH_CORNER_FLOOR_VOLTAGE_MASK; + + /* Set number of target quotient delta steps */ + val |= (delta_quot_steps << CPRH_CORNER_QUOT_DELTA_SHIFT) & + CPRH_CORNER_QUOT_DELTA_MASK; + + /* Select ring oscillator for this corner */ + val |= (ring_osc << CPRH_CORNER_RO_SEL_SHIFT) & + CPRH_CORNER_RO_SEL_MASK; + + /* Open loop corner is usually APM/ACC crossover */ + if (thread->corners[i].is_open_loop) { + dev_dbg(drv->dev, "Disabling Closed-Loop on corner %d\n", i); + val |= CPRH_CORNER_CPR_CL_DISABLE; + } + cpr_write(thread, CPRH_REG_CORNER(drv, tdesc->hw_tid, i), val); + + dev_dbg(drv->dev, "steps [%d]: open-loop %d, floor %d, delta_quot %d\n", + i, volt_oloop_steps, volt_floor_steps, + delta_quot_steps); + } + + /* YAY! Setup is done! Enable the internal loop to start CPR. */ + cpr_masked_write(thread, CPR3_REG_CPR_CTL, + CPR3_CPR_CTL_LOOP_EN_MASK, + CPR3_CPR_CTL_LOOP_EN_MASK); + + /* + * All the writes are going through before enabling internal + * communication between the OSM and the CPRh controllers + * because we are never using relaxed accessors, but should + * we use them, it would be critical to issue a barrier here, + * otherwise there is a high risk of hardware lockups due to + * under-voltage for the selected CPU clock. + * + * Please note that the CPR-hardened gets set-up in Linux but + * then gets actually used in firmware (and only by the OSM); + * after handing it off we will have no more control on it. + */ + + /* Enable the interface between OSM and CPRh */ + cpr_masked_write(thread, drv->reg_ctl, + CPRH_CTL_OSM_ENABLED, + CPRH_CTL_OSM_ENABLED); + + /* On success, free cdata manually */ + devm_kfree(drv->dev, cdata); + return 0; +} + +/** + * cpr3_init_parameters() - Initialize CPR global parameters + * @drv: Main driver structure + * + * Initial "integrity" checks and setup for the thread-independent parameters. + * + * Return: Zero for success, negative number on error + */ +static int cpr3_init_parameters(struct cpr_drv *drv) +{ + const struct cpr_desc *desc = drv->desc; + struct clk *clk; + + clk = devm_clk_get(drv->dev, "ref"); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + drv->ref_clk_khz = clk_get_rate(clk); + do_div(drv->ref_clk_khz, 1000); + + /* On CPRh this clock is not always-on... */ + if (desc->cpr_type == CTRL_TYPE_CPRH) + clk_prepare_enable(clk); + else + devm_clk_put(drv->dev, clk); + + if (desc->timer_cons_up > CPR3_THRESH_CONS_UP_MASK || + desc->timer_cons_down > CPR3_THRESH_CONS_DOWN_MASK || + desc->up_threshold > CPR3_THRESH_UP_THRESH_MASK || + desc->down_threshold > CPR3_THRESH_DOWN_THRESH_MASK || + desc->idle_clocks > CPR3_CPR_CTL_IDLE_CLOCKS_MASK || + desc->count_mode > CPR3_CPR_CTL_COUNT_MODE_MASK || + desc->count_repeat > CPR3_CPR_CTL_COUNT_REPEAT_MASK) + return -EINVAL; + + /* + * Read the CPR version register only from CPR3 onwards: + * this is needed to get the additional register offsets. + * + * Note: When threaded, even if multi-controller, there + * is no chance to have different versions at the + * same time in the same domain, so it is safe to + * check this only on the first controller/thread. + */ + drv->cpr_hw_rev = cpr_read(&drv->threads[0], + CPR3_REG_CPR_VERSION); + dev_dbg(drv->dev, "CPR hardware revision: 0x%x\n", drv->cpr_hw_rev); + + if (drv->cpr_hw_rev >= CPRH_CPR_VERSION_4P5) { + drv->reg_corner = 0x3500; + drv->reg_corner_tid = 0xa0; + drv->reg_ctl = 0x3a80; + drv->reg_status = 0x3a84; + } else { + drv->reg_corner = 0x3a00; + drv->reg_corner_tid = 0; + drv->reg_ctl = 0x3aa0; + drv->reg_status = 0x3aa4; + } + + dev_dbg(drv->dev, "up threshold = %u, down threshold = %u\n", + desc->up_threshold, desc->down_threshold); + + return 0; +} + +/** + * cpr3_find_initial_corner() - Finds boot-up p-state and enables CPR + * @thread: Structure holding CPR thread-specific parameters + * + * Differently from CPRv1, from CPRv3 onwards when we successfully find + * the target boot-up performance state, we must refresh the HW + * immediately to guarantee system stability and to avoid overheating + * during the boot process, thing that would more likely happen without + * this driver doing its job. + * + * Return: Zero for success, negative number on error + */ +static int cpr3_find_initial_corner(struct cpr_thread *thread) +{ + struct cpr_drv *drv = thread->drv; + struct corner *corner; + int uV, idx; + + idx = cpr_find_initial_corner(drv->dev, thread->cpu_clk, + thread->corners, + thread->num_corners); + if (idx < 0) + return idx; + + cpr_ctl_disable(thread); + + corner = &thread->corners[idx]; + cpr_corner_restore(thread, corner); + + uV = regulator_get_voltage(drv->vreg); + uV = clamp(uV, corner->min_uV, corner->max_uV); + + corner->last_uV = uV; + if (!drv->last_uV) + drv->last_uV = uV; + + cpr_commit_state(thread); + thread->enabled = true; + cpr_switch(drv); + + return 0; +} + +static const int msm8998_gold_scaling_factor[][CPR3_RO_COUNT] = { + /* Fuse Corner 0 */ + { + 2857, 3057, 2828, 2952, 2699, 2798, 2446, 2631, + 2629, 2578, 2244, 3344, 3289, 3137, 3164, 2655 + }, + /* Fuse Corner 1 */ + { + 2857, 3057, 2828, 2952, 2699, 2798, 2446, 2631, + 2629, 2578, 2244, 3344, 3289, 3137, 3164, 2655 + }, + /* Fuse Corner 2 */ + { + 2603, 2755, 2676, 2777, 2573, 2685, 2465, 2610, + 2312, 2423, 2243, 3104, 3022, 3036, 2740, 2303 + }, + /* Fuse Corner 3 */ + { + 1901, 2016, 2096, 2228, 2034, 2161, 2077, 2188, + 1565, 1870, 1925, 2235, 2205, 2413, 1762, 1478 + } +}; + +static const int msm8998_silver_scaling_factor[][CPR3_RO_COUNT] = { + /* Fuse Corner 0 */ + { + 2595, 2794, 2577, 2762, 2471, 2674, 2199, 2553, + 3189, 3255, 3192, 2962, 3054, 2982, 2042, 2945 + }, + /* Fuse Corner 1 */ + { + 2595, 2794, 2577, 2762, 2471, 2674, 2199, 2553, + 3189, 3255, 3192, 2962, 3054, 2982, 2042, 2945 + }, + /* Fuse Corner 2 */ + { + 2391, 2550, 2483, 2638, 2382, 2564, 2259, 2555, + 2766, 3041, 2988, 2935, 2873, 2688, 2013, 2784 + }, + /* Fuse Corner 3 */ + { + 2066, 2153, 2300, 2434, 2220, 2386, 2288, 2465, + 2028, 2511, 2487, 2734, 2554, 2117, 1892, 2377 + } +}; + +static const struct cpr_thread_desc msm8998_thread_gold = { + .controller_id = 1, + .hw_tid = 0, + .ro_scaling_factor = msm8998_gold_scaling_factor, + .ro_avail_corners = ARRAY_SIZE(msm8998_gold_scaling_factor), + .sensor_range_start = 0, + .sensor_range_end = 9, + .init_voltage_step = 10000, + .init_voltage_width = 6, + .step_quot_init_min = 9, + .step_quot_init_max = 14, + .num_fuse_corners = 4, + .fuse_corner_data = (struct fuse_corner_data[]){ + /* fuse corner 0 */ + { + .ref_uV = 756000, + .max_uV = 828000, + .min_uV = 568000, + .range_uV = 32000, + .volt_cloop_adjust = 0, + .volt_oloop_adjust = 8000, + .max_volt_scale = 4, + .max_quot_scale = 10, + .quot_offset = 0, + .quot_scale = 1, + .quot_adjust = 0, + .quot_offset_scale = 5, + .quot_offset_adjust = 0, + }, + /* fuse corner 1 */ + { + .ref_uV = 756000, + .max_uV = 900000, + .min_uV = 624000, + .range_uV = 32000, + .volt_cloop_adjust = 0, + .volt_oloop_adjust = 0, + .max_volt_scale = 320, + .max_quot_scale = 350, + .quot_offset = 0, + .quot_scale = 1, + .quot_adjust = 0, + .quot_offset_scale = 5, + .quot_offset_adjust = 0, + }, + /* fuse corner 2 */ + { + .ref_uV = 828000, + .max_uV = 952000, + .min_uV = 632000, + .range_uV = 32000, + .volt_cloop_adjust = 12000, + .volt_oloop_adjust = 12000, + .max_volt_scale = 620, + .max_quot_scale = 750, + .quot_offset = 0, + .quot_scale = 1, + .quot_adjust = 0, + .quot_offset_scale = 5, + .quot_offset_adjust = 0, + }, + /* fuse corner 3 */ + { + .ref_uV = 1056000, + .max_uV = 1136000, + .min_uV = 772000, + .range_uV = 40000, + .volt_cloop_adjust = 50000, + .volt_oloop_adjust = 52000, + .max_volt_scale = 580, + .max_quot_scale = 1040, + .quot_offset = 0, + .quot_scale = 1, + .quot_adjust = 0, + .quot_offset_scale = 5, + .quot_offset_adjust = 0, + }, + }, +}; + +static const struct cpr_thread_desc msm8998_thread_silver = { + .controller_id = 0, + .hw_tid = 0, + .ro_scaling_factor = msm8998_silver_scaling_factor, + .ro_avail_corners = ARRAY_SIZE(msm8998_silver_scaling_factor), + .sensor_range_start = 0, + .sensor_range_end = 6, + .init_voltage_step = 10000, + .init_voltage_width = 6, + .step_quot_init_min = 11, + .step_quot_init_max = 12, + .num_fuse_corners = 4, + .fuse_corner_data = (struct fuse_corner_data[]){ + /* fuse corner 0 */ + { + .ref_uV = 688000, + .max_uV = 828000, + .min_uV = 568000, + .range_uV = 32000, + .volt_cloop_adjust = 20000, + .volt_oloop_adjust = 40000, + .max_volt_scale = 4, + .max_quot_scale = 10, + .quot_offset = 0, + .quot_scale = 1, + .quot_adjust = 0, + .quot_offset_scale = 5, + .quot_offset_adjust = 0, + }, + /* fuse corner 1 */ + { + .ref_uV = 756000, + .max_uV = 900000, + .min_uV = 632000, + .range_uV = 32000, + .volt_cloop_adjust = 26000, + .volt_oloop_adjust = 24000, + .max_volt_scale = 500, + .max_quot_scale = 800, + .quot_offset = 0, + .quot_scale = 1, + .quot_adjust = 0, + .quot_offset_scale = 5, + .quot_offset_adjust = 0, + }, + /* fuse corner 2 */ + { + .ref_uV = 828000, + .max_uV = 952000, + .min_uV = 664000, + .range_uV = 32000, + .volt_cloop_adjust = 12000, + .volt_oloop_adjust = 12000, + .max_volt_scale = 280, + .max_quot_scale = 650, + .quot_offset = 0, + .quot_scale = 1, + .quot_adjust = 0, + .quot_offset_scale = 5, + .quot_offset_adjust = 0, + + }, + /* fuse corner 3 */ + { + .ref_uV = 1056000, + .max_uV = 1056000, + .min_uV = 772000, + .range_uV = 40000, + .volt_cloop_adjust = 30000, + .volt_oloop_adjust = 30000, + .max_volt_scale = 430, + .max_quot_scale = 800, + .quot_offset = 0, + .quot_scale = 1, + .quot_adjust = 0, + .quot_offset_scale = 5, + .quot_offset_adjust = 0, + }, + }, +}; + +static const struct cpr_desc msm8998_cpr_desc = { + .cpr_type = CTRL_TYPE_CPRH, + .num_threads = 2, + .mem_acc_threshold = 852000, + .apm_threshold = 800000, + .apm_crossover = 880000, + .apm_hysteresis = 0, + .cpr_base_voltage = 352000, + .cpr_max_voltage = 1200000, + .timer_delay_us = 5000, + .timer_cons_up = 0, + .timer_cons_down = 2, + .up_threshold = 2, + .down_threshold = 2, + .idle_clocks = 15, + .count_mode = CPR3_CPR_CTL_COUNT_MODE_ALL_AT_ONCE_MIN, + .count_repeat = 14, + .gcnt_us = 1, + .vreg_step_fixed = 4000, + .vreg_step_up_limit = 1, + .vreg_step_down_limit = 1, + .vdd_settle_time_us = 34, + .corner_settle_time_us = 6, + .reduce_to_corner_uV = true, + .hw_closed_loop_en = true, + .threads = (const struct cpr_thread_desc *[]) { + &msm8998_thread_silver, + &msm8998_thread_gold, + }, +}; + +static const struct cpr_acc_desc msm8998_cpr_acc_desc = { + .cpr_desc = &msm8998_cpr_desc, +}; + +static const int sdm630_gold_scaling_factor[][CPR3_RO_COUNT] = { + /* Same RO factors for all fuse corners */ + { + 4040, 3230, 0, 2210, 2560, 2450, 2230, 2220, + 2410, 2300, 2560, 2470, 1600, 3120, 2620, 2280 + } +}; + +static const int sdm630_silver_scaling_factor[][CPR3_RO_COUNT] = { + /* Same RO factors for all fuse corners */ + { + 3600, 3600, 3830, 2430, 2520, 2700, 1790, 1760, + 1970, 1880, 2110, 2010, 2510, 4900, 4370, 4780, + } +}; + +static const struct cpr_thread_desc sdm630_thread_gold = { + .controller_id = 0, + .hw_tid = 0, + .ro_scaling_factor = sdm630_gold_scaling_factor, + .ro_avail_corners = ARRAY_SIZE(sdm630_gold_scaling_factor), + .sensor_range_start = 0, + .sensor_range_end = 6, + .init_voltage_step = 10000, + .init_voltage_width = 6, + .step_quot_init_min = 12, + .step_quot_init_max = 14, + .num_fuse_corners = 5, + .fuse_corner_data = (struct fuse_corner_data[]){ + /* fuse corner 0 */ + { + .ref_uV = 644000, + .max_uV = 724000, + .min_uV = 588000, + .range_uV = 40000, + .volt_cloop_adjust = -30000, + .volt_oloop_adjust = 15000, + .max_volt_scale = 10, + .max_quot_scale = 300, + .quot_offset = 0, + .quot_scale = 1, + .quot_adjust = 0, + .quot_offset_scale = 5, + .quot_offset_adjust = 0, + }, + /* fuse corner 1 */ + { + .ref_uV = 788000, + .max_uV = 788000, + .min_uV = 652000, + .range_uV = 40000, + .volt_cloop_adjust = -30000, + .volt_oloop_adjust = 5000, + .max_volt_scale = 320, + .max_quot_scale = 275, + .quot_offset = 0, + .quot_scale = 1, + .quot_adjust = 0, + .quot_offset_scale = 5, + .quot_offset_adjust = 0, + }, + /* fuse corner 2 */ + { + .ref_uV = 868000, + .max_uV = 868000, + .min_uV = 712000, + .range_uV = 40000, + .volt_cloop_adjust = -30000, + .volt_oloop_adjust = 5000, + .max_volt_scale = 350, + .max_quot_scale = 800, + .quot_offset = 0, + .quot_scale = 1, + .quot_adjust = 0, + .quot_offset_scale = 5, + .quot_offset_adjust = 0, + }, + /* fuse corner 3 */ + { + .ref_uV = 988000, + .max_uV = 988000, + .min_uV = 784000, + .range_uV = 66000, + .volt_cloop_adjust = -30000, + .volt_oloop_adjust = 0, + .max_volt_scale = 868, + .max_quot_scale = 980, + .quot_offset = 0, + .quot_scale = 1, + .quot_adjust = 0, + .quot_offset_scale = 5, + .quot_offset_adjust = 0, + }, + /* fuse corner 4 */ + { + .ref_uV = 1068000, + .max_uV = 1068000, + .min_uV = 844000, + .range_uV = 40000, + .volt_cloop_adjust = -30000, + .volt_oloop_adjust = 0, + .max_volt_scale = 868, + .max_quot_scale = 980, + .quot_offset = 0, + .quot_scale = 1, + .quot_adjust = 0, + .quot_offset_scale = 5, + .quot_offset_adjust = 0, + }, + }, +}; + +static const struct cpr_thread_desc sdm630_thread_silver = { + .controller_id = 1, + .hw_tid = 0, + .ro_scaling_factor = sdm630_silver_scaling_factor, + .ro_avail_corners = ARRAY_SIZE(sdm630_silver_scaling_factor), + .sensor_range_start = 0, + .sensor_range_end = 6, + .init_voltage_step = 10000, + .init_voltage_width = 6, + .step_quot_init_min = 12, + .step_quot_init_max = 14, + .num_fuse_corners = 3, + .fuse_corner_data = (struct fuse_corner_data[]){ + /* fuse corner 0 */ + { + .ref_uV = 644000, + .max_uV = 724000, + .min_uV = 588000, + .range_uV = 32000, + .volt_cloop_adjust = -30000, + .volt_oloop_adjust = 0, + .max_volt_scale = 10, + .max_quot_scale = 360, + .quot_offset = 0, + .quot_scale = 1, + .quot_adjust = 0, + .quot_offset_scale = 5, + .quot_offset_adjust = 0, + }, + /* fuse corner 1 */ + { + .ref_uV = 788000, + .max_uV = 788000, + .min_uV = 652000, + .range_uV = 40000, + .volt_cloop_adjust = -30000, + .volt_oloop_adjust = 0, + .max_volt_scale = 500, + .max_quot_scale = 550, + .quot_offset = 0, + .quot_scale = 1, + .quot_adjust = 0, + .quot_offset_scale = 5, + .quot_offset_adjust = 0, + }, + /* fuse corner 2 */ + { + .ref_uV = 1068000, + .max_uV = 1068000, + .min_uV = 800000, + .range_uV = 40000, + .volt_cloop_adjust = -30000, + .volt_oloop_adjust = 0, + .max_volt_scale = 2370, + .max_quot_scale = 550, + .quot_offset = 0, + .quot_scale = 1, + .quot_adjust = 0, + .quot_offset_scale = 5, + .quot_offset_adjust = 0, + }, + }, +}; + +static const struct cpr_desc sdm630_cpr_desc = { + .cpr_type = CTRL_TYPE_CPRH, + .num_threads = 2, + .apm_threshold = 872000, + .apm_crossover = 872000, + .apm_hysteresis = 20000, + .cpr_base_voltage = 400000, + .cpr_max_voltage = 1300000, + .timer_delay_us = 5000, + .timer_cons_up = 0, + .timer_cons_down = 2, + .up_threshold = 2, + .down_threshold = 2, + .idle_clocks = 15, + .count_mode = CPR3_CPR_CTL_COUNT_MODE_ALL_AT_ONCE_MIN, + .count_repeat = 14, + .gcnt_us = 1, + .vreg_step_fixed = 4000, + .vreg_step_up_limit = 1, + .vreg_step_down_limit = 1, + .vdd_settle_time_us = 34, + .corner_settle_time_us = 5, + .reduce_to_corner_uV = true, + .hw_closed_loop_en = true, + .threads = (const struct cpr_thread_desc *[]) { + &sdm630_thread_gold, + &sdm630_thread_silver, + }, +}; + +static const struct cpr_acc_desc sdm630_cpr_acc_desc = { + .cpr_desc = &sdm630_cpr_desc, +}; + +static unsigned int cpr_get_performance_state(struct generic_pm_domain *genpd, + struct dev_pm_opp *opp) +{ + return dev_pm_opp_get_level(opp); +} + +static int cpr_power_off(struct generic_pm_domain *domain) +{ + struct cpr_thread *thread = container_of(domain, struct cpr_thread, pd); + + return cpr_disable(thread); +} + +static int cpr_power_on(struct generic_pm_domain *domain) +{ + struct cpr_thread *thread = container_of(domain, struct cpr_thread, pd); + + return cpr_enable(thread); +} + +static void cpr_pd_detach_dev(struct generic_pm_domain *domain, + struct device *dev) +{ + struct cpr_thread *thread = container_of(domain, struct cpr_thread, pd); + struct cpr_drv *drv = thread->drv; + + mutex_lock(&drv->lock); + + dev_dbg(drv->dev, "detach callback for: %s\n", dev_name(dev)); + thread->attached_cpu_dev = NULL; + + mutex_unlock(&drv->lock); +} + +static int cpr_pd_attach_dev(struct generic_pm_domain *domain, + struct device *dev) +{ + struct cpr_thread *thread = container_of(domain, struct cpr_thread, pd); + struct cpr_drv *drv = thread->drv; + const struct acc_desc *acc_desc = drv->acc_desc; + bool cprh_opp_remove_table = false; + int ret = 0; + + mutex_lock(&drv->lock); + + dev_dbg(drv->dev, "attach callback for: %s\n", dev_name(dev)); + + /* + * This driver only supports scaling voltage for a CPU cluster + * where all CPUs in the cluster share a single regulator. + * Therefore, save the struct device pointer only for the first + * CPU device that gets attached. There is no need to do any + * additional initialization when further CPUs get attached. + * This is not an error condition. + */ + if (thread->attached_cpu_dev) + goto unlock; + + /* + * cpr_scale_voltage() requires the direction (if we are changing + * to a higher or lower OPP). The first time + * cpr_set_performance_state() is called, there is no previous + * performance state defined. Therefore, we call + * cpr_find_initial_corner() that gets the CPU clock frequency + * set by the bootloader, so that we can determine the direction + * the first time cpr_set_performance_state() is called. + */ + thread->cpu_clk = devm_clk_get(dev, NULL); + if (drv->desc->cpr_type < CTRL_TYPE_CPRH && IS_ERR(thread->cpu_clk)) { + ret = PTR_ERR(thread->cpu_clk); + if (ret != -EPROBE_DEFER) + dev_err(drv->dev, "could not get cpu clk: %d\n", ret); + goto unlock; + } + thread->attached_cpu_dev = dev; + + /* + * We are exporting the APM and MEM-ACC thresholds to the caller; + * while APM is necessary in the CPU CPR case, MEM-ACC may not be, + * depending on the SoC and on fuses. + * Initialize both to an invalid value, so that the caller can check + * if they got calculated or read from fuses in this driver. + */ + thread->ext_data.apm_threshold_uV = -1; + thread->ext_data.mem_acc_threshold_uV = -1; + dev_set_drvdata(thread->attached_cpu_dev, &thread->ext_data); + + dev_dbg(drv->dev, "using cpu clk from: %s\n", + dev_name(thread->attached_cpu_dev)); + + /* + * Everything related to (virtual) corners has to be initialized + * here, when attaching to the power domain, since we need to know + * the maximum frequency for each fuse corner, and this is only + * available after the cpufreq driver has attached to us. + * The reason for this is that we need to know the highest + * frequency associated with each fuse corner. + */ + ret = dev_pm_opp_get_opp_count(&thread->pd.dev); + if (ret < 0) { + dev_err(drv->dev, "could not get OPP count\n"); + thread->attached_cpu_dev = NULL; + goto unlock; + } + thread->num_corners = ret; + + thread->corners = devm_kcalloc(drv->dev, + thread->num_corners + + drv->extra_corners, + sizeof(*thread->corners), + GFP_KERNEL); + if (!thread->corners) { + ret = -ENOMEM; + goto unlock; + } + + /* + * If we are on CPR-Hardened we have to make sure that the attached + * device has a OPP table installed, as we're going to modify it here + * with our calculations based on qfprom values. + */ + if (drv->desc->cpr_type == CTRL_TYPE_CPRH) { + ret = dev_pm_opp_of_add_table(dev); + if (ret && ret != -EEXIST) { + dev_err(drv->dev, "Cannot add table: %d\n", ret); + goto unlock; + } + cprh_opp_remove_table = true; + } + + ret = cpr3_corner_init(thread); + if (ret) + goto exit; + + if (drv->desc->cpr_type < CTRL_TYPE_CPRH) { + ret = cpr3_find_initial_corner(thread); + if (ret) + goto exit; + + if (acc_desc->config) + regmap_multi_reg_write(drv->tcsr, acc_desc->config, + acc_desc->num_regs_per_fuse); + + /* Enable ACC if required */ + if (acc_desc->enable_mask) + regmap_update_bits(drv->tcsr, acc_desc->enable_reg, + acc_desc->enable_mask, + acc_desc->enable_mask); + } + dev_info(drv->dev, "thread %d initialized with %u OPPs\n", + thread->id, thread->num_corners); +exit: + /* + * If we are on CPRh and we reached an error condition, we installed + * the OPP table but we haven't done any setup on it, nor we ever will. + * In order to leave a clean state, remove the table. + */ + if (ret && cprh_opp_remove_table) + dev_pm_opp_of_remove_table(thread->attached_cpu_dev); +unlock: + mutex_unlock(&drv->lock); + + return ret; +} + +static int cpr3_debug_info_show(struct seq_file *s, void *unused) +{ + u32 ro_sel, ctl, irq_status, reg, quot; + struct cpr_thread *thread = s->private; + struct corner *corner = thread->corners; + struct fuse_corner *fuse = thread->fuse_corners; + unsigned int i; + + const struct { + const char *name; + uint32_t mask; + uint8_t shift; + } result0_fields[] = { + { "busy", 1, 0 }, + { "step_dn", 1, 1 }, + { "step_up", 1, 2 }, + { "error_steps", CPR3_RESULT0_ERROR_STEPS_MASK, + CPR3_RESULT0_ERROR_STEPS_SHIFT }, + { "error", CPR3_RESULT0_ERROR_MASK, CPR3_RESULT0_ERROR_SHIFT }, + { "negative", 1, 20 }, + }, result1_fields[] = { + { "quot_min", CPR3_RESULT1_QUOT_MIN_MASK, + CPR3_RESULT1_QUOT_MIN_SHIFT }, + { "quot_max", CPR3_RESULT1_QUOT_MAX_MASK, + CPR3_RESULT1_QUOT_MAX_SHIFT }, + { "ro_min", CPR3_RESULT1_RO_MIN_MASK, + CPR3_RESULT1_RO_MIN_SHIFT }, + { "ro_max", CPR3_RESULT1_RO_MAX_MASK, + CPR3_RESULT1_RO_MAX_SHIFT }, + }, result2_fields[] = { + { "qout_step_min", CPR3_RESULT2_STEP_QUOT_MIN_MASK, + CPR3_RESULT2_STEP_QUOT_MIN_SHIFT }, + { "qout_step_max", CPR3_RESULT2_STEP_QUOT_MAX_MASK, + CPR3_RESULT2_STEP_QUOT_MAX_SHIFT }, + { "sensor_min", CPR3_RESULT2_SENSOR_MIN_MASK, + CPR3_RESULT2_SENSOR_MIN_SHIFT }, + { "sensor_max", CPR3_RESULT2_SENSOR_MAX_MASK, + CPR3_RESULT2_SENSOR_MAX_SHIFT }, + }; + + if (thread->drv->desc->cpr_type < CTRL_TYPE_CPRH) + seq_printf(s, "current_volt = %d uV\n", thread->drv->last_uV); + + irq_status = cpr_read(thread, CPR3_REG_IRQ_STATUS); + seq_printf(s, "irq_status = %#02X\n", irq_status); + + ctl = cpr_read(thread, CPR3_REG_CPR_CTL); + seq_printf(s, "cpr_ctl = %#02X\n", ctl); + + seq_printf(s, "thread %d - hw tid: %u - enabled: %d:\n", + thread->id, thread->desc->hw_tid, thread->enabled); + seq_printf(s, "%d corners, derived from %d fuse corners\n", + thread->num_corners, thread->desc->num_fuse_corners); + + for (i = 0; i < thread->num_corners; i++, corner++) + seq_printf(s, "corner %d - uV=[%d %d %d] quot=%d freq=%lu\n", + i, corner->min_uV, corner->uV, corner->max_uV, + corner->quot_adjust, corner->freq); + + for (i = 0; i < thread->desc->num_fuse_corners; i++, fuse++) + seq_printf(s, "fuse %d - uV=[%d %d %d] quot=%d freq=%lu\n", + i, fuse->min_uV, fuse->uV, fuse->max_uV, + fuse->quot, corner->freq); + + seq_printf(s, "requested voltage: %d uV\n", thread->corner->last_uV); + + ro_sel = corner->fuse_corner->ring_osc_idx; + quot = cpr_read(thread, CPR3_REG_TARGET_QUOT(i, ro_sel)); + seq_printf(s, "quot_target (%u) = %#02X\n", ro_sel, quot); + + reg = cpr_read(thread, CPR3_REG_RESULT0(i)); + seq_printf(s, "cpr_result_0 = %#02X\n [", reg); + for (i = 0; i < ARRAY_SIZE(result0_fields); i++) + seq_printf(s, "%s%s = %u", + i ? ", " : "", + result0_fields[i].name, + (reg >> result0_fields[i].shift) & + result0_fields[i].mask); + seq_puts(s, "]\n"); + reg = cpr_read(thread, CPR3_REG_RESULT1(i)); + seq_printf(s, "cpr_result_1 = %#02X\n [", reg); + for (i = 0; i < ARRAY_SIZE(result1_fields); i++) + seq_printf(s, "%s%s = %u", + i ? ", " : "", + result1_fields[i].name, + (reg >> result1_fields[i].shift) & + result1_fields[i].mask); + seq_puts(s, "]\n"); + reg = cpr_read(thread, CPR3_REG_RESULT2(i)); + seq_printf(s, "cpr_result_2 = %#02X\n [", reg); + for (i = 0; i < ARRAY_SIZE(result2_fields); i++) + seq_printf(s, "%s%s = %u", + i ? ", " : "", + result2_fields[i].name, + (reg >> result2_fields[i].shift) & + result2_fields[i].mask); + seq_puts(s, "]\n"); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(cpr3_debug_info); + +static void cpr3_debugfs_init(struct cpr_drv *drv) +{ + int i; + + drv->debugfs = debugfs_create_dir("qcom_cpr3", NULL); + + for (i = 0; i < drv->desc->num_threads; i++) { + char buf[50]; + + snprintf(buf, sizeof(buf), "thread%d", i); + + debugfs_create_file(buf, 0444, drv->debugfs, &drv->threads[i], + &cpr3_debug_info_fops); + } +} + +/** + * cpr_thread_init() - Initialize CPR thread related parameters + * @drv: Main driver structure + * @tid: Thread ID + * + * Return: Zero for success, negative number on error + */ +static int cpr_thread_init(struct cpr_drv *drv, int tid) +{ + const struct cpr_desc *desc = drv->desc; + const struct cpr_thread_desc *tdesc = desc->threads[tid]; + struct cpr_thread *thread = &drv->threads[tid]; + int ret; + + if (tdesc->step_quot_init_min > CPR3_CPR_STEP_QUOT_MIN_MASK || + tdesc->step_quot_init_max > CPR3_CPR_STEP_QUOT_MAX_MASK) + return -EINVAL; + + thread->id = tid; + thread->drv = drv; + thread->desc = tdesc; + thread->fuse_corners = devm_kcalloc(drv->dev, + tdesc->num_fuse_corners + + drv->extra_corners, + sizeof(*thread->fuse_corners), + GFP_KERNEL); + if (!thread->fuse_corners) + return -ENOMEM; + + thread->cpr_fuses = cpr_get_fuses(drv->dev, tid, + tdesc->num_fuse_corners); + if (IS_ERR(thread->cpr_fuses)) + return PTR_ERR(thread->cpr_fuses); + + ret = cpr_populate_ring_osc_idx(thread->drv->dev, thread->fuse_corners, + thread->cpr_fuses, + tdesc->num_fuse_corners); + if (ret) + return ret; + + ret = cpr_fuse_corner_init(thread); + if (ret) + return ret; + + thread->pd.name = devm_kasprintf(drv->dev, GFP_KERNEL, + "%s_thread%d", + drv->dev->of_node->full_name, + thread->id); + if (!thread->pd.name) + return -EINVAL; + + thread->pd.power_off = cpr_power_off; + thread->pd.power_on = cpr_power_on; + thread->pd.opp_to_performance_state = cpr_get_performance_state; + thread->pd.attach_dev = cpr_pd_attach_dev; + thread->pd.detach_dev = cpr_pd_detach_dev; + + /* CPR-Hardened performance states are managed in firmware */ + if (desc->cpr_type == CTRL_TYPE_CPRH) { + thread->pd.set_performance_state = cprh_dummy_set_performance_state; + } else { + thread->pd.set_performance_state = cpr_set_performance_state; + } + + /* Anything later than CPR1 must be always-on for now */ + thread->pd.flags = GENPD_FLAG_ALWAYS_ON; + + drv->cell_data.domains[tid] = &thread->pd; + + ret = pm_genpd_init(&thread->pd, NULL, false); + if (ret) + return ret; + + /* On CPRhardened, the interrupts are managed in firmware */ + if (desc->cpr_type < CTRL_TYPE_CPRH) { + INIT_WORK(&thread->restart_work, cpr_restart_worker); + + ret = devm_request_threaded_irq(drv->dev, drv->irq, + NULL, cpr_irq_handler, + IRQF_ONESHOT | + IRQF_TRIGGER_RISING, + "cpr", drv); + if (ret) + return ret; + } + + return 0; +} + +/** + * cpr3_resources_init() - Initialize resources used by this driver + * @pdev: Platform device + * @drv: Main driver structure + * + * Return: Zero for success, negative number on error + */ +static int cpr3_resources_init(struct platform_device *pdev, + struct cpr_drv *drv) +{ + const struct cpr_desc *desc = drv->desc; + struct cpr_thread *threads = drv->threads; + unsigned int i; + u8 cid_mask = 0; + + /* + * Here, we are accounting for the following usecases: + * - One controller + * - One or multiple threads on the same iospace + * + * - Multiple controllers + * - Each controller has its own iospace and each + * may have one or multiple threads in their + * parent controller's iospace + * + * Then, to avoid complicating the code for no reason, + * this also needs a mandatory order in the list of + * threads which implies that all of them from the same + * controllers are specified sequentially. As an example: + * + * C0-T0, C0-T1...C0-Tn, C1-T0, C1-T1...C1-Tn + */ + for (i = 0; i < desc->num_threads; i++) { + u8 cid = desc->threads[i]->controller_id; + + if (cid_mask & BIT(cid)) { + if (desc->threads[i - 1]->controller_id != cid) { + dev_err(drv->dev, "Bad threads order. Please fix!\n"); + return -EINVAL; + } + threads[i].base = threads[i - 1].base; + continue; + } + threads[i].base = devm_platform_ioremap_resource(pdev, cid); + if (IS_ERR(threads[i].base)) + return PTR_ERR(threads[i].base); + cid_mask |= BIT(cid); + } + return 0; +} + +static int cpr_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct cpr_drv *drv; + const struct cpr_desc *desc; + const struct cpr_acc_desc *data; + struct device_node *np; + unsigned int i; + int ret; + + data = of_device_get_match_data(dev); + if (!data || !data->cpr_desc) + return -EINVAL; + + desc = data->cpr_desc; + + /* CPRh disallows MEM-ACC access from the HLOS */ + if (!data->acc_desc && desc->cpr_type < CTRL_TYPE_CPRH) + return -EINVAL; + + drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL); + if (!drv) + return -ENOMEM; + + drv->dev = dev; + drv->desc = desc; + drv->threads = devm_kcalloc(dev, desc->num_threads, + sizeof(*drv->threads), GFP_KERNEL); + if (!drv->threads) + return -ENOMEM; + + drv->cell_data.num_domains = desc->num_threads; + drv->cell_data.domains = devm_kcalloc(drv->dev, + drv->cell_data.num_domains, + sizeof(*drv->cell_data.domains), + GFP_KERNEL); + if (!drv->cell_data.domains) + return -ENOMEM; + + if (data->acc_desc) + drv->acc_desc = data->acc_desc; + + mutex_init(&drv->lock); + + if (desc->cpr_type < CTRL_TYPE_CPRH) { + np = of_parse_phandle(dev->of_node, "acc-syscon", 0); + if (!np) + return -ENODEV; + + drv->tcsr = syscon_node_to_regmap(np); + of_node_put(np); + if (IS_ERR(drv->tcsr)) + return PTR_ERR(drv->tcsr); + } + + ret = cpr3_resources_init(pdev, drv); + if (ret) + return ret; + + drv->irq = platform_get_irq_optional(pdev, 0); + if ((desc->cpr_type != CTRL_TYPE_CPRH) && (drv->irq < 0)) + return -EINVAL; + + /* On CPRhardened, vreg access it not allowed */ + drv->vreg = devm_regulator_get_optional(dev, "vdd"); + if (desc->cpr_type != CTRL_TYPE_CPRH && IS_ERR(drv->vreg)) + return PTR_ERR(drv->vreg); + + /* + * On at least CPRhardened, vreg is unaccessible and there is no + * way to read linear step from that regulator, hence it is hardcoded + * in the driver; + * When the vreg_step is not declared in the cpr data (or is zero), + * then having access to the vreg regulator is mandatory, as this + * will be retrieved through the regulator API. + */ + if (desc->vreg_step_fixed) + drv->vreg_step = desc->vreg_step_fixed; + else + drv->vreg_step = regulator_get_linear_step(drv->vreg); + + if (!drv->vreg_step) + return -EINVAL; + + /* + * Initialize fuse corners, since it simply depends + * on data in efuses. + * Everything related to (virtual) corners has to be + * initialized after attaching to the power domain, + * since it depends on the CPU's OPP table. + */ + ret = nvmem_cell_read_variable_le_u32(dev, "cpr_fuse_revision", &drv->fusing_rev); + if (ret) + return ret; + + ret = nvmem_cell_read_variable_le_u32(dev, "cpr_speed_bin", &drv->speed_bin); + if (ret) + return ret; + + /* + * Some SoCs require extra corners for MEM-ACC or APM: if + * the related parameters have been specified, then reserve + * a corner for the APM and/or MEM-ACC crossover, used by + * OSM and CPRh HW to set the supply voltage during the APM + * and/or MEM-ACC switch routine. + */ + if (desc->cpr_type == CTRL_TYPE_CPRH) { + if (desc->apm_crossover && desc->apm_hysteresis >= 0) + drv->extra_corners++; + + if (desc->mem_acc_threshold) + drv->extra_corners++; + } + + /* Initialize all threads */ + for (i = 0; i < desc->num_threads; i++) { + ret = cpr_thread_init(drv, i); + if (ret) + return ret; + } + + /* Initialize global parameters */ + ret = cpr3_init_parameters(drv); + if (ret) + return ret; + + /* Write initial configuration on all threads */ + for (i = 0; i < desc->num_threads; i++) { + ret = cpr_configure(&drv->threads[i]); + if (ret) + return ret; + } + + ret = of_genpd_add_provider_onecell(dev->of_node, &drv->cell_data); + if (ret) + return ret; + + platform_set_drvdata(pdev, drv); + cpr3_debugfs_init(drv); + + return 0; +} + +static int cpr_remove(struct platform_device *pdev) +{ + struct cpr_drv *drv = platform_get_drvdata(pdev); + int i; + + of_genpd_del_provider(pdev->dev.of_node); + + for (i = 0; i < drv->desc->num_threads; i++) { + cpr_ctl_disable(&drv->threads[i]); + cpr_irq_set(&drv->threads[i], 0); + pm_genpd_remove(&drv->threads[i].pd); + } + + debugfs_remove_recursive(drv->debugfs); + + return 0; +} + +static const struct of_device_id cpr3_match_table[] = { + { .compatible = "qcom,msm8998-cprh", .data = &msm8998_cpr_acc_desc }, + { .compatible = "qcom,sdm630-cprh", .data = &sdm630_cpr_acc_desc }, + { } +}; +MODULE_DEVICE_TABLE(of, cpr3_match_table); + +static struct platform_driver cpr3_driver = { + .probe = cpr_probe, + .remove = cpr_remove, + .driver = { + .name = "qcom-cpr3", + .of_match_table = cpr3_match_table, + }, +}; +module_platform_driver(cpr3_driver) + +MODULE_DESCRIPTION("Core Power Reduction (CPR) v3/v4 driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/soc/qcom/cpr.h b/include/soc/qcom/cpr.h new file mode 100644 index 000000000000..2ba4324d18f6 --- /dev/null +++ b/include/soc/qcom/cpr.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2013-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2019 Linaro Limited + * Copyright (c) 2021, AngeloGioacchino Del Regno + * + */ + +#ifndef __CPR_H__ +#define __CPR_H__ + +struct cpr_ext_data { + int mem_acc_threshold_uV; + int apm_threshold_uV; +}; + +#endif /* __CPR_H__ */ From 134e7d3c3fdc092e5d69088139ba90b010909659 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Wed, 25 Nov 2020 20:40:26 +0100 Subject: [PATCH 062/187] MAINTAINERS: Add entry for Qualcomm CPRv3/v4/Hardened driver Add maintainers entry for the Qualcomm CPR3/CPR4/CPRh driver. Signed-off-by: AngeloGioacchino Del Regno --- MAINTAINERS | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 070747841bc7..945c6a5e9833 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15743,6 +15743,12 @@ S: Maintained F: Documentation/devicetree/bindings/soc/qcom/qcom,cpr.yaml F: drivers/soc/qcom/cpr.c +QUALCOMM CORE POWER REDUCTION v3/v4/Hardened AVS DRIVER +M: AngeloGioacchino Del Regno +S: Maintained +F: Documentation/devicetree/bindings/soc/qcom/qcom,cpr3.yaml +F: drivers/soc/qcom/cpr3.c + QUALCOMM CPUFREQ DRIVER MSM8996/APQ8096 M: Ilia Lin L: linux-pm@vger.kernel.org From ad3536fd33430a9abf8d99f766a5ac9228344816 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Wed, 25 Nov 2020 20:31:49 +0100 Subject: [PATCH 063/187] dt-bindings: soc: qcom: cpr3: Add bindings for CPR3 driver Add the bindings for the CPR3 driver to the documentation. Signed-off-by: AngeloGioacchino Del Regno Reviewed-by: Rob Herring --- .../bindings/soc/qcom/qcom,cpr3.yaml | 241 ++++++++++++++++++ 1 file changed, 241 insertions(+) create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,cpr3.yaml diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,cpr3.yaml b/Documentation/devicetree/bindings/soc/qcom/qcom,cpr3.yaml new file mode 100644 index 000000000000..e2753740c86b --- /dev/null +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,cpr3.yaml @@ -0,0 +1,241 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: "http://devicetree.org/schemas/soc/qcom/qcom,cpr3.yaml#" +$schema: "http://devicetree.org/meta-schemas/core.yaml#" + +title: Qualcomm Core Power Reduction v3/v4/Hardened (CPR3, CPR4, CPRh) + +description: | + CPR (Core Power Reduction) is a technology to reduce core power on a CPU + or other device. Each OPP of a device corresponds to a "corner" that has + a range of valid voltages for a particular frequency. While the device is + running at a particular frequency, CPR monitors dynamic factors such as + temperature, etc. and suggests or, in the CPR-Hardened case performs, + adjustments to the voltage to save power and meet silicon characteristic + requirements. + +maintainers: + - AngeloGioacchino Del Regno + +properties: + compatible: + oneOf: + - description: CPRv3 controller + items: + - const: qcom,cpr3 + - description: CPRv4 controller + items: + - const: qcom,cpr4 + - description: CPRv4-Hardened controller + items: + - enum: + - qcom,msm8998-cprh + - qcom,sdm630-cprh + - const: qcom,cprh + + reg: + description: Base address and size of the CPR controller(s) + minItems: 1 + maxItems: 2 + + interrupts: + maxItems: 1 + + clock-names: + items: + - const: "ref" + + clocks: + items: + - description: CPR reference clock + + vdd-supply: + description: Autonomous Phase Control (APC) or other power supply + + '#power-domain-cells': + const: 1 + + acc-syscon: + description: phandle to syscon for writing ACC settings + + nvmem-cells: + description: Cells containing the fuse corners and revision data + minItems: 10 + maxItems: 32 + + nvmem-cell-names: + minItems: 10 + maxItems: 32 + + operating-points-v2: true + +required: + - compatible + - reg + - clock-names + - clocks + - "#power-domain-cells" + - nvmem-cells + - nvmem-cell-names + - operating-points-v2 + +additionalProperties: false + +examples: + - | + #include + #include + + cpus { + #address-cells = <2>; + #size-cells = <0>; + + cpu@0 { + compatible = "qcom,kryo280"; + device_type = "cpu"; + reg = <0x0 0x0>; + operating-points-v2 = <&cpu_gold_opp_table>; + power-domains = <&apc_cprh 0>; + power-domain-names = "cprh"; + }; + + cpu@100 { + compatible = "qcom,kryo280"; + device_type = "cpu"; + reg = <0x0 0x0>; + operating-points-v2 = <&cpu_silver_opp_table>; + power-domains = <&apc_cprh 1>; + power-domain-names = "cprh"; + }; + }; + + cpu_silver_opp_table: cpu-silver-opp-table { + compatible = "operating-points-v2"; + opp-shared; + + opp-1843200000 { + opp-hz = /bits/ 64 <1843200000>; + required-opps = <&cprh_opp3>; + }; + opp-1094400000 { + opp-hz = /bits/ 64 <1094400000>; + required-opps = <&cprh_opp2>; + }; + opp-300000000 { + opp-hz = /bits/ 64 <300000000>; + required-opps = <&cprh_opp1>; + }; + }; + + cpu_gold_opp_table: cpu-gold-opp-table { + compatible = "operating-points-v2"; + opp-shared; + + opp-2208000000 { + opp-hz = /bits/ 64 <2208000000>; + required-opps = <&cprh_opp3>; + }; + opp-1113600000 { + opp-hz = /bits/ 64 <1113600000>; + required-opps = <&cprh_opp2>; + }; + opp-300000000 { + opp-hz = /bits/ 64 <300000000>; + required-opps = <&cprh_opp1>; + }; + }; + + cprh_opp_table: cpr-hardened-opp-table { + compatible = "operating-points-v2-qcom-level"; + + cprh_opp1: opp1 { + opp-level = <1>; + qcom,opp-fuse-level = <1>; + }; + cprh_opp2: opp2 { + opp-level = <2>; + qcom,opp-fuse-level = <2>; + }; + cprh_opp3: opp3 { + opp-level = <3>; + qcom,opp-fuse-level = <2 3>; + }; + }; + + apc_cprh: power-controller@179c8000 { + compatible = "qcom,msm8998-cprh", "qcom,cprh"; + reg = <0x0179c8000 0x4000>, <0x0179c4000 0x4000>; + clocks = <&gcc GCC_HMSS_RBCPR_CLK>; + clock-names = "ref"; + + #power-domain-cells = <1>; + operating-points-v2 = <&cprh_opp_table>; + + nvmem-cells = <&cpr_efuse_speedbin>, + <&cpr_fuse_revision>, + <&cpr_quot0_pwrcl>, + <&cpr_quot1_pwrcl>, + <&cpr_quot2_pwrcl>, + <&cpr_quot3_pwrcl>, + <&cpr_quot_offset1_pwrcl>, + <&cpr_quot_offset2_pwrcl>, + <&cpr_quot_offset3_pwrcl>, + <&cpr_init_voltage0_pwrcl>, + <&cpr_init_voltage1_pwrcl>, + <&cpr_init_voltage2_pwrcl>, + <&cpr_init_voltage3_pwrcl>, + <&cpr_ro_sel0_pwrcl>, + <&cpr_ro_sel1_pwrcl>, + <&cpr_ro_sel2_pwrcl>, + <&cpr_ro_sel3_pwrcl>, + <&cpr_quot0_perfcl>, + <&cpr_quot1_perfcl>, + <&cpr_quot2_perfcl>, + <&cpr_quot3_perfcl>, + <&cpr_quot_offset1_perfcl>, + <&cpr_quot_offset2_perfcl>, + <&cpr_quot_offset3_perfcl>, + <&cpr_init_voltage0_perfcl>, + <&cpr_init_voltage1_perfcl>, + <&cpr_init_voltage2_perfcl>, + <&cpr_init_voltage3_perfcl>, + <&cpr_ro_sel0_perfcl>, + <&cpr_ro_sel1_perfcl>, + <&cpr_ro_sel2_perfcl>, + <&cpr_ro_sel3_perfcl>; + + nvmem-cell-names = "cpr_speed_bin", + "cpr_fuse_revision", + "cpr0_quotient1", + "cpr0_quotient2", + "cpr0_quotient3", + "cpr0_quotient4", + "cpr0_quotient_offset2", + "cpr0_quotient_offset3", + "cpr0_quotient_offset4", + "cpr0_init_voltage1", + "cpr0_init_voltage2", + "cpr0_init_voltage3", + "cpr0_init_voltage4", + "cpr0_ring_osc1", + "cpr0_ring_osc2", + "cpr0_ring_osc3", + "cpr0_ring_osc4", + "cpr1_quotient1", + "cpr1_quotient2", + "cpr1_quotient3", + "cpr1_quotient4", + "cpr1_quotient_offset2", + "cpr1_quotient_offset3", + "cpr1_quotient_offset4", + "cpr1_init_voltage1", + "cpr1_init_voltage2", + "cpr1_init_voltage3", + "cpr1_init_voltage4", + "cpr1_ring_osc1", + "cpr1_ring_osc2", + "cpr1_ring_osc3", + "cpr1_ring_osc4"; + }; +... From 7da5abe66e48c931c4271bc782bfd09ebb35361b Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Sat, 7 Aug 2021 16:16:28 +0200 Subject: [PATCH 064/187] arm64: dts: sdm630: Assign 300MHz frequency to HMSS GPLL0 clock At cpufreq_hw initialization, we need to assign a frequency of 300MHz to the HMSS GPLL0 clock in order to achieve the recommended low sleep frequency (and low voltage) for both the gold and silver clusters. Signed-off-by: AngeloGioacchino Del Regno --- arch/arm64/boot/dts/qcom/sdm630.dtsi | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/sdm630.dtsi b/arch/arm64/boot/dts/qcom/sdm630.dtsi index 1853afd642d9..2ca35bc2e419 100644 --- a/arch/arm64/boot/dts/qcom/sdm630.dtsi +++ b/arch/arm64/boot/dts/qcom/sdm630.dtsi @@ -1115,6 +1115,9 @@ reg-names = "osm-domain0", "freq-domain0", "osm-domain1", "freq-domain1"; + assigned-clocks = <&gcc HMSS_GPLL0_CLK_SRC>; + assigned-clock-rates = <300000000>; + clocks = <&rpmcc RPM_SMD_XO_A_CLK_SRC>, <&gcc HMSS_GPLL0_CLK_SRC>; clock-names = "xo", "alternate"; From f1ea33bf02413781612e253e5a0ccf66a0532220 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Sat, 5 Dec 2020 17:53:51 +0100 Subject: [PATCH 065/187] arm64: dts: qcom: Enable panel etc. on MSM8998 Sony Yoshino platform (JAMI: fixed up for v5.16-rc1) --- .../msm8998-sony-xperia-yoshino-maple.dts | 48 +++++ .../msm8998-sony-xperia-yoshino-poplar.dts | 12 ++ .../dts/qcom/msm8998-sony-xperia-yoshino.dtsi | 181 +++++++++++++++++- 3 files changed, 240 insertions(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/qcom/msm8998-sony-xperia-yoshino-maple.dts b/arch/arm64/boot/dts/qcom/msm8998-sony-xperia-yoshino-maple.dts index 978495a8a6b9..90923259bfc3 100644 --- a/arch/arm64/boot/dts/qcom/msm8998-sony-xperia-yoshino-maple.dts +++ b/arch/arm64/boot/dts/qcom/msm8998-sony-xperia-yoshino-maple.dts @@ -26,6 +26,28 @@ }; }; +&dsi0 { + qcom,dual-dsi-mode; + qcom,master-dsi; +}; + +&dsi1 { + vdd-supply = <&vreg_l1a_0p875>; + vdda-supply = <&vreg_l2a_1p2>; + qcom,dual-dsi-mode; + status = "ok"; +}; + +&dsi1_out { + remote-endpoint = <&panel_in1>; + data-lanes = <0 1 2 3>; +}; + +&dsi1_phy { + vdds-supply = <&vreg_l1a_0p875>; + status = "ok"; +}; + &ibb { regulator-min-microvolt = <5600000>; regulator-max-microvolt = <5600000>; @@ -37,6 +59,32 @@ qcom,soft-start-us = <200>; }; +&panel { + compatible = "sharp,ls055d1sx04"; + + dvdd-supply = <&disp_dvdd_vreg>; + ports { + port@1 { + reg = <1>; + panel_in1: endpoint { + remote-endpoint = <&dsi1_out>; + }; + }; + }; +}; + +&pm8005_gpio { + ear_en_default: ear-en-active { + pins = "gpio1"; + function = PMIC_GPIO_FUNC_NORMAL; + output-low; + drive-push-pull; + bias-disable; + qcom,drive-strength = ; + power-source = <1>; + }; +}; + &pmi8998_gpio { disp_dvdd_en: disp-dvdd-en-active { pins = "gpio10"; diff --git a/arch/arm64/boot/dts/qcom/msm8998-sony-xperia-yoshino-poplar.dts b/arch/arm64/boot/dts/qcom/msm8998-sony-xperia-yoshino-poplar.dts index 4a1f98a21031..84911139a7d8 100644 --- a/arch/arm64/boot/dts/qcom/msm8998-sony-xperia-yoshino-poplar.dts +++ b/arch/arm64/boot/dts/qcom/msm8998-sony-xperia-yoshino-poplar.dts @@ -25,6 +25,18 @@ qcom,soft-start-us = <800>; }; +&pm8005_gpio { + ear_en_default: ear-en-active { + pins = "gpio1"; + function = PMIC_GPIO_FUNC_NORMAL; + output-low; + drive-push-pull; + bias-disable; + qcom,drive-strength = ; + power-source = <1>; + }; +}; + &vreg_l18a_2p85 { regulator-min-microvolt = <2850000>; regulator-max-microvolt = <2850000>; diff --git a/arch/arm64/boot/dts/qcom/msm8998-sony-xperia-yoshino.dtsi b/arch/arm64/boot/dts/qcom/msm8998-sony-xperia-yoshino.dtsi index 91e391282181..0a315801ff28 100644 --- a/arch/arm64/boot/dts/qcom/msm8998-sony-xperia-yoshino.dtsi +++ b/arch/arm64/boot/dts/qcom/msm8998-sony-xperia-yoshino.dtsi @@ -20,6 +20,10 @@ qcom,msm-id = <0x124 0x20000>, <0x124 0x20001>; /* 8998v2, v2.1 */ qcom,board-id = <8 0>; + chosen { + bootargs = "clk_ignore_unused"; + }; + clocks { compatible = "simple-bus"; @@ -84,6 +88,15 @@ pinctrl-0 = <&ts_vddio_en>; }; + /* The gpio-vibrator driver enforces requiring a regulator */ + vib_vreg: vib-regulator { + compatible = "regulator-fixed"; + regulator-name = "vib"; + + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + }; + vph_pwr: vph-pwr-regulator { compatible = "regulator-fixed"; regulator-name = "vph_pwr"; @@ -186,11 +199,28 @@ vibrator { compatible = "gpio-vibrator"; enable-gpios = <&pmi8998_gpio 5 GPIO_ACTIVE_HIGH>; + vcc-supply = <&vib_vreg>; pinctrl-names = "default"; pinctrl-0 = <&vib_default>; }; }; +&adreno_gpu { + status = "ok"; + + zap-shader { + memory-region = <&zap_shader_region>; + }; +}; + +&adreno_smmu { + status = "ok"; +}; + +&apc_cprh { + status = "ok"; +}; + &blsp1_i2c5 { status = "okay"; clock-frequency = <355000>; @@ -243,10 +273,75 @@ }; }; +&blsp2_i2c1 { + tof-sensor@29 { + compatible = "st,vl53l0x"; + reg = <0x29>; + interrupt-parent = <&tlmm>; + interrupts = <23 IRQ_TYPE_EDGE_FALLING>; + #io-channel-cells = <1>; + label = "back_camera_tof"; + pinctrl-names = "default"; + pinctrl-0 = <&tof_int_n &tof_rst_n>; + }; +}; + &blsp2_uart1 { status = "okay"; }; +&cpufreq_hw { + status = "ok"; +}; + +&dsi0 { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + vdd-supply = <&vreg_l1a_0p875>; + vdda-supply = <&vreg_l2a_1p2>; + + panel: panel@0 { + reg = <0>; + + backlight = <&pmi8998_wled>; + disp-te-gpios = <&tlmm 10 GPIO_ACTIVE_HIGH>; + reset-gpios = <&tlmm 94 GPIO_ACTIVE_HIGH>; + + avdd-supply = <&lab>; + avee-supply = <&ibb>; + vddio-supply = <&vreg_l14a_1p85>; + tavdd-supply = <&vreg_l28_3p0>; + tvddio-supply = <&touch_vddio_vreg>; + + pinctrl-names = "default"; + pinctrl-0 = <&panel_reset_n &mdp_vsync_n>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + panel_in0: endpoint { + remote-endpoint = <&dsi0_out>; + }; + }; + }; + }; +}; + +&dsi0_out { + remote-endpoint = <&panel_in0>; + data-lanes = <0 1 2 3>; +}; + +&dsi0_phy { + status = "okay"; + vdds-supply = <&vreg_l1a_0p875>; +}; + &ibb { regulator-min-microamp = <800000>; regulator-max-microamp = <800000>; @@ -280,6 +375,10 @@ status = "ok"; }; +&mdss { + status = "okay"; +}; + &pm8005_lsid1 { pm8005-regulators { compatible = "qcom,pm8005-regulators"; @@ -288,7 +387,12 @@ /* VDD_GFX supply */ pm8005_s1: s1 { - regulator-min-microvolt = <524000>; + /* + * HACK: Set enough voltage for max GPU frequency + * and set the regulator always on until the + * GPU Core Power Reduction gets available + */ + regulator-min-microvolt = <988000>; regulator-max-microvolt = <1088000>; regulator-enable-ramp-delay = <500>; regulator-always-on; @@ -326,6 +430,13 @@ function = "func2"; power-source = <0>; }; + + nfc_clk_req_pin: nfc-clk-req-active { + pins = "gpio21"; + function = PMIC_GPIO_FUNC_NORMAL; + input-enable; + power-source = <1>; + }; }; &pmi8998_gpio { @@ -360,6 +471,18 @@ }; }; +&pmi8998_wled { + status = "okay"; + + default-brightness = <3000>; + qcom,switching-freq = <800>; + qcom,ovp-millivolt = <29600>; + qcom,current-boost-limit = <970>; + qcom,current-limit-microamp = <25000>; + qcom,num-strings = <2>; + qcom,enabled-strings = <0 1>; +}; + &qusb2phy { status = "okay"; @@ -367,6 +490,18 @@ vdda-phy-dpdm-supply = <&vreg_l24a_3p075>; }; +&remoteproc_adsp { + firmware-name = "adsp.mdt"; +}; + +&remoteproc_mss { + status = "disabled"; +}; + +&remoteproc_slpi { + firmware-name = "slpi.mdt"; +}; + &rpm_requests { pm8998-regulators { compatible = "qcom,rpm-pm8998-regulators"; @@ -608,6 +743,14 @@ drive-strength = <2>; }; + tof_int_n: tof-int-n { + pins = "gpio22"; + function = "gpio"; + bias-pull-up; + drive-strength = <2>; + input-enable; + }; + cam1_vdig_default: cam1-vdig-default { pins = "gpio25"; function = "gpio"; @@ -615,6 +758,42 @@ drive-strength = <2>; }; + tof_rst_n: tof-rst-n { + pins = "gpio27"; + function = "gpio"; + bias-disable; + drive-strength = <2>; + output-low; + }; + + cam1_rst_default: cam1-rst-n { + pins = "gpio28"; + function = "gpio"; + bias-disable; + drive-strength = <2>; + }; + + cam0_rst_default: cam0-rst-n { + pins = "gpio30"; + function = "gpio"; + bias-disable; + drive-strength = <2>; + }; + + ts_reset_n: ts-reset-n { + pins = "gpio89"; + function = "gpio"; + bias-pull-up; + drive-strength = <8>; + }; + + panel_reset_n: panel-rst-n { + pins = "gpio94"; + function = "gpio"; + bias-disable; + drive-strength = <2>; + }; + hall_sensor0_default: acc-cover-open { pins = "gpio124"; function = "gpio"; From f47f0560028c2b1b4b017f437cb75255abac0d9c Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Sat, 5 Dec 2020 12:45:50 +0100 Subject: [PATCH 066/187] arm64: dts: msm8998: Add disabled configuration for DPU1/DSI This SoC supports both the MDP5 and DPU1 drivers, but the latter was chosen as it's more feature-complete; Configure the DPU1, DSI and related phy and pll in order to achieve display functionality and keep it disabled. Enabling it will be done on board specific DT when needed, as not all boards have a usable display attached to them. Signed-off-by: AngeloGioacchino Del Regno --- arch/arm64/boot/dts/qcom/msm8998.dtsi | 264 +++++++++++++++++++++++++- 1 file changed, 260 insertions(+), 4 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/msm8998.dtsi b/arch/arm64/boot/dts/qcom/msm8998.dtsi index 408f265e277b..6710798e83c4 100644 --- a/arch/arm64/boot/dts/qcom/msm8998.dtsi +++ b/arch/arm64/boot/dts/qcom/msm8998.dtsi @@ -2460,16 +2460,272 @@ "core_bi_pll_test_se"; clocks = <&rpmcc RPM_SMD_XO_CLK_SRC>, <&gcc GCC_MMSS_GPLL0_CLK>, - <0>, - <0>, - <0>, - <0>, + <&dsi0_phy 1>, + <&dsi0_phy 0>, + <&dsi1_phy 1>, + <&dsi1_phy 0>, <0>, <0>, <0>, <0>; }; + dsi_opp_table: dsi-opp-table { + compatible = "operating-points-v2"; + + opp-131250000 { + opp-hz = /bits/ 64 <131250000>; + required-opps = <&rpmpd_opp_low_svs>; + }; + + opp-210000000 { + opp-hz = /bits/ 64 <210000000>; + required-opps = <&rpmpd_opp_svs>; + }; + + opp-312500000 { + opp-hz = /bits/ 64 <312500000>; + required-opps = <&rpmpd_opp_nom>; + }; + }; + + mdss: mdss@c900000 { + compatible = "qcom,mdss-dpu1"; + reg = <0x0c900000 0x1000>; + reg-names = "mdss"; + + clocks = <&mmcc MDSS_AHB_CLK>, + <&mmcc MDSS_AXI_CLK>, + <&mmcc MDSS_MDP_CLK>; + clock-names = "iface", "bus", "core"; + + assigned-clocks = <&mmcc MDSS_MDP_CLK>; + assigned-clock-rates = <300000000>; + interrupts = ; + interrupt-controller; + iommus = <&mmss_smmu 0>; + #interrupt-cells = <1>; + #address-cells = <1>; + #size-cells = <1>; + power-domains = <&mmcc MDSS_GDSC>; + ranges; + status = "disabled"; + + mdss_mdp: mdp@c901000 { + compatible = "qcom,msm8998-dpu"; + reg = <0x0c901000 0x8f000>, + <0x0c9a8e00 0xf0>, + <0x0c9b0000 0x2008>, + <0x0c9b8000 0x1040>; + reg-names = "mdp", "regdma", "vbif", + "vbif_nrt"; + + assigned-clocks = <&mmcc MDSS_MDP_CLK>, + <&mmcc MDSS_VSYNC_CLK>; + assigned-clock-rates = <412500000>, + <19200000>; + + clocks = <&mmcc MDSS_AHB_CLK>, + <&mmcc MDSS_AXI_CLK>, + <&mmcc MNOC_AHB_CLK>, + <&mmcc MDSS_MDP_CLK>, + <&mmcc MDSS_VSYNC_CLK>; + clock-names = "iface", "bus", "mnoc", + "core", "vsync"; + + interrupt-parent = <&mdss>; + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; + operating-points-v2 = <&mdp_opp_table>; + power-domains = <&rpmpd MSM8998_VDDMX>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + dpu_intf1_out: endpoint { + remote-endpoint = <&dsi0_in>; + }; + }; + + port@1 { + reg = <1>; + dpu_intf2_out: endpoint { + remote-endpoint = <&dsi1_in>; + }; + }; + }; + + mdp_opp_table: mdp-opp-table { + compatible = "operating-points-v2"; + + opp-19200000 { + opp-hz = /bits/ 64 <19200000>; + required-opps = <&rpmpd_opp_min_svs>; + }; + + opp-150000000 { + opp-hz = /bits/ 64 <150000000>; + required-opps = <&rpmpd_opp_low_svs>; + }; + + opp-275000000 { + opp-hz = /bits/ 64 <275000000>; + required-opps = <&rpmpd_opp_svs>; + }; + + opp-330000000 { + opp-hz = /bits/ 64 <330000000>; + required-opps = <&rpmpd_opp_nom>; + }; + + opp-412500000 { + opp-hz = /bits/ 64 <412500000>; + required-opps = <&rpmpd_opp_turbo>; + }; + }; + }; + + dsi0: dsi@c994000 { + compatible = "qcom,mdss-dsi-ctrl"; + reg = <0x0c994000 0x400>; + reg-names = "dsi_ctrl"; + + clocks = <&mmcc MDSS_BYTE0_CLK>, + <&mmcc MDSS_BYTE0_INTF_CLK>, + <&mmcc MNOC_AHB_CLK>, + <&mmcc MDSS_AHB_CLK>, + <&mmcc MDSS_AXI_CLK>, + <&mmcc MISC_AHB_CLK>, + <&mmcc MDSS_PCLK0_CLK>, + <&mmcc MDSS_ESC0_CLK>; + clock-names = "byte", + "byte_intf", + "mnoc", + "iface", + "bus", + "core_mmss", + "pixel", + "core"; + + interrupt-parent = <&mdss>; + interrupts = <4 IRQ_TYPE_LEVEL_HIGH>; + operating-points-v2 = <&dsi_opp_table>; + phys = <&dsi0_phy>; + phy-names = "dsi"; + power-domains = <&rpmpd MSM8998_VDDCX>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + dsi0_in: endpoint { + remote-endpoint = <&dpu_intf1_out>; + }; + }; + + port@1 { + reg = <1>; + dsi0_out: endpoint { + }; + }; + }; + }; + + dsi0_phy: dsi-phy@c994400 { + compatible = "qcom,dsi-phy-10nm-8998"; + reg = <0x0c994400 0x200>, + <0x0c994600 0x280>, + <0x0c994a00 0x1c0>; + reg-names = "dsi_phy", + "dsi_phy_lane", + "dsi_pll"; + + #clock-cells = <1>; + #phy-cells = <0>; + + clocks = <&mmcc MDSS_AHB_CLK>, + <&rpmcc RPM_SMD_XO_CLK_SRC>; + clock-names = "iface", "ref"; + power-domains = <&rpmpd MSM8998_VDDMX>; + + status = "disabled"; + }; + + dsi1: dsi@c996000 { + compatible = "qcom,mdss-dsi-ctrl"; + reg = <0x0c996000 0x400>; + reg-names = "dsi_ctrl"; + + clocks = <&mmcc MDSS_BYTE1_CLK>, + <&mmcc MDSS_BYTE1_INTF_CLK>, + <&mmcc MNOC_AHB_CLK>, + <&mmcc MISC_AHB_CLK>, + <&mmcc MDSS_PCLK1_CLK>, + <&mmcc MDSS_ESC1_CLK>, + <&mmcc MDSS_AHB_CLK>, + <&mmcc MDSS_AXI_CLK>; + clock-names = "byte", + "byte_intf", + "mnoc", + "iface_mmss", + "pixel", + "core", + "iface", + "bus"; + + interrupt-parent = <&mdss>; + interrupts = <5 IRQ_TYPE_LEVEL_HIGH>; + operating-points-v2 = <&dsi_opp_table>; + phys = <&dsi1_phy>; + phy-names = "dsi"; + power-domains = <&rpmpd MSM8998_VDDCX>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + dsi1_in: endpoint { + remote-endpoint = <&dpu_intf2_out>; + }; + }; + + port@1 { + reg = <1>; + dsi1_out: endpoint { + }; + }; + }; + }; + + dsi1_phy: dsi-phy@c996400 { + compatible = "qcom,dsi-phy-10nm-8998"; + reg = <0x0c996400 0x200>, + <0x0c996600 0x280>, + <0x0c996a00 0x10e>; + reg-names = "dsi_phy", + "dsi_phy_lane", + "dsi_pll"; + + #clock-cells = <1>; + #phy-cells = <0>; + + clocks = <&mmcc MDSS_AHB_CLK>, + <&rpmcc RPM_SMD_XO_CLK_SRC>; + clock-names = "iface", "ref"; + power-domains = <&rpmpd MSM8998_VDDMX>; + + status = "disabled"; + }; + }; + mmss_smmu: iommu@cd00000 { compatible = "qcom,msm8998-smmu-v2", "qcom,smmu-v2"; reg = <0x0cd00000 0x40000>; From 44ec014f383416ffb5df95c8f08b95ddead92dbc Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Mon, 4 Jan 2021 18:17:58 +0100 Subject: [PATCH 067/187] arm64: dts: msm8998: Add interconnect nodes This SoC features Network-on-Chip (NoC) and Bus Integrated Memory Controller (BIMC) interconnects: add the required nodes now that the driver is present. Signed-off-by: AngeloGioacchino Del Regno --- arch/arm64/boot/dts/qcom/msm8998.dtsi | 68 +++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/msm8998.dtsi b/arch/arm64/boot/dts/qcom/msm8998.dtsi index 6710798e83c4..7b3f37bfbcd6 100644 --- a/arch/arm64/boot/dts/qcom/msm8998.dtsi +++ b/arch/arm64/boot/dts/qcom/msm8998.dtsi @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -882,6 +883,15 @@ }; }; + bimc: interconnect@1008000 { + compatible = "qcom,msm8998-bimc"; + reg = <0x01008000 0x78000>; + #interconnect-cells = <1>; + clock-names = "bus", "bus_a"; + clocks = <&rpmcc RPM_SMD_BIMC_CLK>, + <&rpmcc RPM_SMD_BIMC_A_CLK>; + }; + tsens0: thermal@10ab000 { compatible = "qcom,msm8998-tsens", "qcom,tsens-v2"; reg = <0x010ab000 0x1000>, /* TM */ @@ -904,6 +914,33 @@ #thermal-sensor-cells = <1>; }; + cnoc: interconnect@1500000 { + compatible = "qcom,msm8998-cnoc"; + reg = <0x01500000 0x10000>; + #interconnect-cells = <1>; + clock-names = "bus", "bus_a"; + clocks = <&rpmcc RPM_SMD_CNOC_CLK>, + <&rpmcc RPM_SMD_CNOC_A_CLK>; + }; + + snoc: interconnect@1625000 { + compatible = "qcom,msm8998-snoc"; + reg = <0x01625000 0x6100>; + #interconnect-cells = <1>; + clock-names = "bus", "bus_a"; + clocks = <&rpmcc RPM_SMD_SNOC_CLK>, + <&rpmcc RPM_SMD_SNOC_A_CLK>; + }; + + a1noc: interconnect@1669000 { + compatible = "qcom,msm8998-a1noc"; + reg = <0x01669000 0x5020>; + #interconnect-cells = <1>; + clock-names = "bus", "bus_a"; + clocks = <&rpmcc RPM_SMD_AGGR1_NOC_CLK>, + <&rpmcc RPM_SMD_AGGR1_NOC_A_CLK>; + }; + anoc1_smmu: iommu@1680000 { compatible = "qcom,msm8998-smmu-v2", "qcom,smmu-v2"; reg = <0x01680000 0x10000>; @@ -938,6 +975,25 @@ ; }; + a2noc: interconnect@1705000 { + compatible = "qcom,msm8998-a2noc"; + reg = <0x01705000 0xa090>; + #interconnect-cells = <1>; + clock-names = "bus", "bus_a"; + clocks = <&rpmcc RPM_SMD_AGGR2_NOC_CLK>, + <&rpmcc RPM_SMD_AGGR2_NOC_A_CLK>; + }; + + mnoc: interconnect@1744000 { + compatible = "qcom,msm8998-mnoc"; + reg = <0x01744000 0xb010>; + #interconnect-cells = <1>; + clock-names = "bus", "bus_a", "iface"; + clocks = <&rpmcc RPM_SMD_MMAXI_CLK>, + <&rpmcc RPM_SMD_MMAXI_A_CLK>, + <&mmcc AHB_CLK_SRC>; + }; + pcie0: pci@1c00000 { compatible = "qcom,pcie-msm8996"; reg = <0x01c00000 0x2000>, @@ -2796,6 +2852,18 @@ }; }; + gnoc: interconnect@17900000 { + compatible = "qcom,msm8998-gnoc"; + reg = <0x17900000 0xe000>; + #interconnect-cells = <1>; + /* + * This one apparently features no clocks, + * so let's not mess with the driver needlessly + */ + clock-names = "bus", "bus_a"; + clocks = <&xo>, <&xo>; + }; + apcs_glb: mailbox@17911000 { compatible = "qcom,msm8998-apcs-hmss-global"; reg = <0x17911000 0x1000>; From 8b0d1f9c9c504a7e3b0c4a3610e4500bebf81bf6 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Wed, 20 Jan 2021 15:27:42 +0100 Subject: [PATCH 068/187] arm64: dts: msm8998: Wire up interconnects and OPPs to sdhci port 2 Wire up the OPP table and interconnects to the SDHCI port 2 to improve performance and power consumption. Signed-off-by: AngeloGioacchino Del Regno --- arch/arm64/boot/dts/qcom/msm8998.dtsi | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/msm8998.dtsi b/arch/arm64/boot/dts/qcom/msm8998.dtsi index 7b3f37bfbcd6..098e146af0ed 100644 --- a/arch/arm64/boot/dts/qcom/msm8998.dtsi +++ b/arch/arm64/boot/dts/qcom/msm8998.dtsi @@ -2194,6 +2194,7 @@ reg = <0x0c0a4900 0x314>, <0x0c0a4000 0x800>; reg-names = "hc_mem", "core_mem"; + interconnects = <&a2noc MASTER_SDCC_2 &a2noc SLAVE_A2NOC_SNOC>; interrupts = , ; interrupt-names = "hc_irq", "pwr_irq"; @@ -2204,6 +2205,26 @@ <&xo>; bus-width = <4>; status = "disabled"; + + sdhc2_opp_table: sdhc2-opp-table { + compatible = "operating-points-v2"; + + opp-50000000 { + opp-hz = /bits/ 64 <50000000>; + opp-peak-kBps = <400000>; + opp-avg-kBps = <200000>; + }; + opp-100000000 { + opp-hz = /bits/ 64 <100000000>; + opp-peak-kBps = <800000>; + opp-avg-kBps = <400000>; + }; + opp-200000000 { + opp-hz = /bits/ 64 <200000000>; + opp-peak-kBps = <800000>; + opp-avg-kBps = <800000>; + }; + }; }; blsp1_dma: dma-controller@c144000 { From c2e3861fd7634a3313f134da086d49a922a10353 Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Mon, 14 Dec 2020 17:19:11 +0100 Subject: [PATCH 069/187] arm64: dts: qcom: pm8998: Add VREF_1P25 and REF_GND VADC channels Signed-off-by: Konrad Dybcio --- arch/arm64/boot/dts/qcom/pm8998.dtsi | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/pm8998.dtsi b/arch/arm64/boot/dts/qcom/pm8998.dtsi index 6f5bb6b37ec2..ebe8a918b19f 100644 --- a/arch/arm64/boot/dts/qcom/pm8998.dtsi +++ b/arch/arm64/boot/dts/qcom/pm8998.dtsi @@ -78,6 +78,16 @@ #size-cells = <0>; #io-channel-cells = <1>; + adc-chan@0 { + reg = ; + label = "ref_gnd"; + }; + + adc-chan@1 { + reg = ; + label = "vref_1p25"; + }; + adc-chan@6 { reg = ; label = "die_temp"; From 008a5ed0487a243eec4e290d2d13c5b50f03ed93 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Fri, 4 Dec 2020 23:04:52 +0100 Subject: [PATCH 070/187] arm64: dts: qcom: Enable panel etc. on MSM8998 F(x)tec Pro1 QX1000 (JAMI: fixed up for v5.16-rc1) --- .../boot/dts/qcom/msm8998-fxtec-pro1.dts | 283 ++++++++++++++++++ 1 file changed, 283 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/msm8998-fxtec-pro1.dts b/arch/arm64/boot/dts/qcom/msm8998-fxtec-pro1.dts index 3d495ce3f46a..400f941a3921 100644 --- a/arch/arm64/boot/dts/qcom/msm8998-fxtec-pro1.dts +++ b/arch/arm64/boot/dts/qcom/msm8998-fxtec-pro1.dts @@ -18,6 +18,15 @@ chassis-type = "handset"; qcom,board-id = <0x02000b 0x10>; + /* This part enables graphical output via bootloader-enabled display */ + chosen { + bootargs = "earlycon=tty0 console=tty0 clk_ignore_unused"; + + #address-cells = <2>; + #size-cells = <2>; + ranges; + }; + /* * Until we hook up type-c detection, we * have to stick with this. But it works. @@ -48,6 +57,8 @@ compatible = "gpio-keys"; input-name = "extra-kb-keys"; label = "Keyboard extra keys"; + #address-cells = <1>; + #size-cells = <0>; pinctrl-names = "default"; pinctrl-0 = <&gpio_kb_pins_extra>; @@ -100,6 +111,39 @@ }; }; + gpio_keyboard: gpio-keyboard { + compatible = "gpio-fastmatrix-keyboard"; + label = "F(x)Tec Pro1 Hardware Keyboard"; + row-gpios = + <&gpioext0 0 (GPIO_ACTIVE_LOW | GPIO_OPEN_DRAIN)>, + <&gpioext0 1 (GPIO_ACTIVE_LOW | GPIO_OPEN_DRAIN)>, + <&gpioext0 2 (GPIO_ACTIVE_LOW | GPIO_OPEN_DRAIN)>, + <&gpioext0 3 (GPIO_ACTIVE_LOW | GPIO_OPEN_DRAIN)>, + <&gpioext0 4 (GPIO_ACTIVE_LOW | GPIO_OPEN_DRAIN)>, + <&gpioext0 5 (GPIO_ACTIVE_LOW | GPIO_OPEN_DRAIN)>, + <&gpioext0 6 (GPIO_ACTIVE_LOW | GPIO_OPEN_DRAIN)>, + <&gpioext0 7 (GPIO_ACTIVE_LOW | GPIO_OPEN_DRAIN)>; + + col-gpios = + <&gpioext0 8 GPIO_ACTIVE_LOW>, + <&gpioext0 9 GPIO_ACTIVE_LOW>, + <&gpioext0 10 GPIO_ACTIVE_LOW>, + <&gpioext0 11 GPIO_ACTIVE_LOW>, + <&gpioext0 12 GPIO_ACTIVE_LOW>, + <&gpioext0 13 GPIO_ACTIVE_LOW>, + <&gpioext0 14 GPIO_ACTIVE_LOW>, + <&gpioext0 15 GPIO_ACTIVE_LOW>; + keypad,num-rows = <8>; + keypad,num-columns = <8>; + + pinctrl-names = "default"; + pinctrl-0 = <&keyboard_pins_col>, <&keyboard_pins_row>; + + autorescan-ms = <5>; + debounce-delay-ms = <1>; + col-scan-delay-us = <1500>; + }; + gpio-keys { compatible = "gpio-keys"; input-name = "side-buttons"; @@ -180,6 +224,53 @@ }; }; + disp_vcc_vreg: disp-vcc-regulator { + compatible = "regulator-fixed"; + regulator-name = "disp_vcc"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-boot-on; + regulator-always-on; + }; + + disp_vddio_vreg: disp-vddio-regulator { + compatible = "regulator-fixed"; + regulator-name = "disp_vddio"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-boot-on; + regulator-always-on; + regulator-allow-set-load; + }; + + disp_vci_vreg: disp-vci-regulator { + compatible = "regulator-fixed"; + regulator-name = "disp_vci"; + regulator-min-microvolt = <3000000>; + regulator-max-microvolt = <3000000>; + regulator-boot-on; + regulator-always-on; + regulator-allow-set-load; + }; + + disp_elvdd_vreg: disp-elvdd-regulator { + compatible = "regulator-fixed"; + regulator-name = "disp_elvdd"; + regulator-min-microvolt = <4600000>; + regulator-max-microvolt = <4600000>; + regulator-boot-on; + regulator-always-on; + }; + + disp_elvss_vreg: disp-elvss-regulator { + compatible = "regulator-fixed"; + regulator-name = "disp_elvss"; + regulator-min-microvolt = <2400000>; + regulator-max-microvolt = <2400000>; + regulator-boot-on; + regulator-always-on; + }; + ts_vio_vreg: ts-vio-vreg { compatible = "regulator-fixed"; regulator-name = "ts_vio_reg"; @@ -192,8 +283,61 @@ }; }; +&adreno_gpu { + status = "ok"; + + zap-shader { + memory-region = <&zap_shader_region>; + }; +}; + +&adreno_smmu { + status = "ok"; +}; + +&apc_cprh { + status = "ok"; +}; + +&blsp1_i2c6 { + status = "ok"; + + gpioext0: gpio-expander@58 { + compatible = "awinic,aw9523-pinctrl"; + reg = <0x58>; + interrupt-parent = <&tlmm>; + interrupts = <50 IRQ_TYPE_EDGE_FALLING>; + + gpio-controller; + #gpio-cells = <2>; + gpio-ranges = <&gpioext0 0 0 16>; + interrupt-controller; + #interrupt-cells = <2>; + + pinctrl-0 = <&gpio_expander_int_n>, <&gpio_expander_rst_n>; + pinctrl-names = "default"; + reset-gpios = <&tlmm 51 GPIO_ACTIVE_HIGH>; + + keyboard_pins_col: keyboard-matrix-col-pins { + pins = "gpio8", "gpio9", "gpio10", "gpio11", + "gpio12", "gpio13", "gpio14", "gpio15"; + function = "gpio"; + output-low; + }; + keyboard_pins_row: keyboard-matrix-row-pins { + pins = "gpio0", "gpio1", "gpio2", "gpio3", + "gpio4", "gpio5", "gpio6", "gpio7"; + function = "gpio"; + drive-open-drain; + input-enable; + }; + }; +}; + &blsp2_i2c1 { status = "ok"; + qcom,noise-reject-scl = <3>; + qcom,noise-reject-sda = <3>; touchscreen@14 { compatible = "goodix,gt9286"; @@ -208,6 +352,104 @@ }; }; +&cpufreq_hw { + status = "ok"; +}; + +&dsi0 { + #address-cells = <1>; + #size-cells = <0>; + status = "ok"; + + vdd-supply = <&vreg_l1a_0p875>; + vdda-supply = <&vreg_l2a_1p2>; + + panel: panel@0 { + compatible = "boe,bf060y8m-aj0"; + reg = <0>; + + reset-gpios = <&tlmm 94 GPIO_ACTIVE_LOW>; + disp-te-gpios = <&tlmm 10 GPIO_ACTIVE_LOW>; + no-hpd; + + vcc-supply = <&disp_vcc_vreg>; + vddio-supply = <&disp_vddio_vreg>; + vci-supply = <&disp_vci_vreg>; + elvdd-supply = <&disp_elvdd_vreg>; + elvss-supply = <&disp_elvss_vreg>; + + pinctrl-names = "default"; + pinctrl-0 = <&panel_reset_n &disp_en_default &mdp_vsync_n>; + port { + panel_in: endpoint { + remote-endpoint = <&dsi0_out>; + }; + }; + }; +}; + +&dsi0_phy { + status = "ok"; +}; + +&dsi0_out { + remote-endpoint = <&panel_in>; + data-lanes = <0 1 2 3>; +}; + +&gcc { + status = "ok"; +}; + +&gpucc { + status = "ok"; +}; + +&gpio_keyboard { + linux,keymap = < + MATRIX_KEY(0, 0, KEY_F1) MATRIX_KEY(1, 0, KEY_H) + MATRIX_KEY(2, 0, KEY_B) MATRIX_KEY(3, 0, KEY_7) + MATRIX_KEY(4, 0, KEY_UP) MATRIX_KEY(5, 0, KEY_ENTER) + MATRIX_KEY(6, 0, KEY_Y) MATRIX_KEY(7, 0, KEY_COMMA) + MATRIX_KEY(0, 1, KEY_3) MATRIX_KEY(1, 1, KEY_S) + MATRIX_KEY(2, 1, KEY_Z) MATRIX_KEY(3, 1, KEY_M) + MATRIX_KEY(4, 1, KEY_I) MATRIX_KEY(5, 1, KEY_9) + MATRIX_KEY(6, 1, KEY_W) MATRIX_KEY(7, 1, KEY_J) + MATRIX_KEY(0, 2, KEY_LEFT) MATRIX_KEY(1, 2, KEY_G) + MATRIX_KEY(2, 2, KEY_V) MATRIX_KEY(3, 2, KEY_6) + MATRIX_KEY(4, 2, KEY_RIGHT) MATRIX_KEY(5, 2, KEY_DELETE) + MATRIX_KEY(6, 2, KEY_T) MATRIX_KEY(7, 2, KEY_DOT) + MATRIX_KEY(0, 3, KEY_SLASH) MATRIX_KEY(1, 3, KEY_A) + MATRIX_KEY(2, 3, KEY_RIGHTBRACE) MATRIX_KEY(3, 3, KEY_HOMEPAGE) + MATRIX_KEY(4, 3, KEY_P) MATRIX_KEY(5, 3, KEY_MINUS) + MATRIX_KEY(6, 3, KEY_Q) MATRIX_KEY(7, 3, KEY_L) + MATRIX_KEY(0, 4, KEY_BACKSPACE) MATRIX_KEY(1, 4, KEY_D) + MATRIX_KEY(2, 4, KEY_X) MATRIX_KEY(3, 4, KEY_K) + MATRIX_KEY(4, 4, KEY_SEMICOLON) MATRIX_KEY(5, 4, KEY_EQUAL) + MATRIX_KEY(6, 4, KEY_E) MATRIX_KEY(7, 4, KEY_APOSTROPHE) + MATRIX_KEY(0, 5, KEY_CAPSLOCK) MATRIX_KEY(1, 5, KEY_BACKSLASH) + MATRIX_KEY(2, 5, KEY_LEFTBRACE) MATRIX_KEY(3, 5, KEY_DOWN) + MATRIX_KEY(4, 5, KEY_O) MATRIX_KEY(5, 5, KEY_0) + MATRIX_KEY(6, 5, KEY_GRAVE) MATRIX_KEY(7, 5, KEY_K) + MATRIX_KEY(0, 6, KEY_SPACE) MATRIX_KEY(1, 6, KEY_F) + MATRIX_KEY(2, 6, KEY_C) MATRIX_KEY(3, 6, KEY_N) + MATRIX_KEY(4, 6, KEY_U) MATRIX_KEY(5, 6, KEY_8) + MATRIX_KEY(6, 6, KEY_R) MATRIX_KEY(7, 6, KEY_5) + MATRIX_KEY(0, 7, KEY_ESC) MATRIX_KEY(1, 7, KEY_1) + MATRIX_KEY(2, 7, KEY_RESERVED) MATRIX_KEY(3, 7, KEY_RESERVED) + MATRIX_KEY(4, 7, KEY_2) MATRIX_KEY(5, 7, KEY_4) + MATRIX_KEY(6, 7, KEY_TAB) MATRIX_KEY(7, 7, KEY_RESERVED) + >; +}; + +&mdss { + status = "ok"; +}; + +&mdss_mdp { + status = "ok"; +}; + &mmcc { status = "ok"; }; @@ -216,7 +458,21 @@ status = "ok"; }; +/* HACK! Push GPU voltage high until GPU CPR is hooked up */ +&pm8005_s1 { + regulator-min-microvolt = <988000>; + regulator-max-microvolt = <1100000>; +}; + &pm8998_gpio { + unknown_pin_a: unk-active { + pins = "gpio5"; + function = "normal"; + input-enable; + bias-pull-up; + qcom,drive-strength = ; + }; + vol_up_pin_a: vol-up-active { pins = "gpio6"; function = "normal"; @@ -270,6 +526,26 @@ bias-pull-up; }; + gpio_expander_int_n: gpio-exp-intn-def { + pins = "gpio50"; + function = "gpio"; + drive-strength = <2>; + input-enable; + }; + + gpio_expander_rst_n: gpio-exp-rst-def { + pins = "gpio51"; + function = "gpio"; + drive-strength = <8>; + }; + + disp_en_default: disp-en { + pins = "gpio62"; + function = "gpio"; + drive-strength = <8>; + bias-disable; + }; + ts_vio_default: ts-vio-def { pins = "gpio81"; function = "gpio"; @@ -298,6 +574,13 @@ bias-disable; drive-strength = <8>; }; + + panel_reset_n: panel-rst-n { + pins = "gpio94"; + function = "gpio"; + drive-strength = <8>; + bias-disable; + }; }; &ufshc { From a326baf8f234152fedcd4f7ed072741e9a60bc2c Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Thu, 21 Jan 2021 20:05:15 +0100 Subject: [PATCH 071/187] arm64: dts: msm8998: Wire up interconnects to MDP and GPU Wire up the interconnects to both the MDP and the Adreno GPU in order to get the right balance between performance and power consumption of both devices. --- arch/arm64/boot/dts/qcom/msm8998.dtsi | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/qcom/msm8998.dtsi b/arch/arm64/boot/dts/qcom/msm8998.dtsi index 098e146af0ed..29f653a6edf3 100644 --- a/arch/arm64/boot/dts/qcom/msm8998.dtsi +++ b/arch/arm64/boot/dts/qcom/msm8998.dtsi @@ -1498,6 +1498,8 @@ "rbcpr", "core"; + interconnects = <&bimc MASTER_OXILI &bimc SLAVE_EBI>; + interconnect-names = "gfx-mem"; interrupts = <0 300 IRQ_TYPE_LEVEL_HIGH>; iommus = <&adreno_smmu 0>; operating-points-v2 = <&gpu_opp_table>; @@ -1510,42 +1512,55 @@ opp-710000097 { opp-hz = /bits/ 64 <710000097>; opp-level = ; + opp-peak-kBps = <14432000>; + opp-avg-kBps = <14400000>; opp-supported-hw = <0xFF>; }; opp-670000048 { opp-hz = /bits/ 64 <670000048>; opp-level = ; + opp-peak-kBps = <14432000>; + opp-avg-kBps = <14390000>; opp-supported-hw = <0xFF>; }; - opp-596000097 { opp-hz = /bits/ 64 <596000097>; opp-level = ; + opp-peak-kBps = <14432000>; + opp-avg-kBps = <12440000>; opp-supported-hw = <0xFF>; }; opp-515000097 { opp-hz = /bits/ 64 <515000097>; opp-level = ; + opp-peak-kBps = <14432000>; + opp-avg-kBps = <10368000>; opp-supported-hw = <0xFF>; }; opp-414000000 { opp-hz = /bits/ 64 <414000000>; opp-level = ; + opp-peak-kBps = <12440000>; + opp-avg-kBps = <8136000>; opp-supported-hw = <0xFF>; }; opp-342000000 { opp-hz = /bits/ 64 <342000000>; opp-level = ; + opp-peak-kBps = <8136000>; + opp-avg-kBps = <6144000>; opp-supported-hw = <0xFF>; }; opp-257000000 { opp-hz = /bits/ 64 <257000000>; opp-level = ; + opp-peak-kBps = <6144000>; + opp-avg-kBps = <3296000>; opp-supported-hw = <0xFF>; }; }; @@ -2578,6 +2593,10 @@ assigned-clocks = <&mmcc MDSS_MDP_CLK>; assigned-clock-rates = <300000000>; + interconnects = <&mnoc MASTER_MDP_P0 &bimc SLAVE_EBI>, + <&mnoc MASTER_MDP_P1 &bimc SLAVE_EBI>; + interconnect-names = "mdp0-mem", + "mdp1-mem"; interrupts = ; interrupt-controller; iommus = <&mmss_smmu 0>; From c47e38e029158c2ed672a2565229ba47edcdf234 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Wed, 23 Dec 2020 12:10:53 +0100 Subject: [PATCH 072/187] arm64: dts: msm8998: Add SAW, CPRh and CPUFREQ to enable CPU scaling Add the SAW (SPM), CPR-Hardened, CPUFREQ-HW nodes and relative OPP tables (and also assign them to the CPU nodes, as required) in order to enable CPU scaling on the MSM8998 SoC. The CPR-Hardened and CPUFREQ-HW nodes are disabled by default as to not change the previous default behavior. Since the drivers are not yet accounting for speed-binning, these OPPs are referred to the most common binning for this chip, which I have found on six phones from Sony and one from FxTec (silver bin0, perf bin2). At least until speed-binning gets done in the cpufreq-hw and CPR drivers, users should enable CPR-Hardened and CPUFREQ in their own board DT. This is done like that because these drivers are really big, so the idea is to keep the "base" version easier (but perfectly working), before adding speed-binning "complications", which may... or may not be necessary. Signed-off-by: AngeloGioacchino Del Regno --- arch/arm64/boot/dts/qcom/msm8998.dtsi | 721 +++++++++++++++++++++++++- 1 file changed, 720 insertions(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/qcom/msm8998.dtsi b/arch/arm64/boot/dts/qcom/msm8998.dtsi index 29f653a6edf3..5f1750e172c2 100644 --- a/arch/arm64/boot/dts/qcom/msm8998.dtsi +++ b/arch/arm64/boot/dts/qcom/msm8998.dtsi @@ -10,6 +10,15 @@ #include #include +/* Macro for CPR-Hardened OPP entries - Example phandle: cprh_opp0 */ +#define CPRH_OPP_ENTRY(lvl, _fuselevel, _oadj, _cadj) \ + cprh_opp##lvl##: opp-##lvl { \ + opp-level = ; \ + qcom,opp-fuse-level = <_fuselevel>; \ + qcom,opp-oloop-vadj = <_oadj>; \ + qcom,opp-cloop-vadj = <_cadj>; \ + } + / { interrupt-parent = <&intc>; @@ -136,8 +145,13 @@ reg = <0x0 0x0>; enable-method = "psci"; capacity-dmips-mhz = <1024>; + clocks = <&xo>; cpu-idle-states = <&LITTLE_CPU_SLEEP_0 &LITTLE_CPU_SLEEP_1>; next-level-cache = <&L2_0>; + operating-points-v2 = <&cpu_silver_opp_table>; + power-domains = <&apc_cprh 0>; + power-domain-names = "cprh"; + qcom,freq-domain = <&cpufreq_hw 0>; L2_0: l2-cache { compatible = "arm,arch-cache"; cache-level = <2>; @@ -156,8 +170,13 @@ reg = <0x0 0x1>; enable-method = "psci"; capacity-dmips-mhz = <1024>; + clocks = <&xo>; cpu-idle-states = <&LITTLE_CPU_SLEEP_0 &LITTLE_CPU_SLEEP_1>; next-level-cache = <&L2_0>; + operating-points-v2 = <&cpu_silver_opp_table>; + power-domains = <&apc_cprh 0>; + power-domain-names = "cprh"; + qcom,freq-domain = <&cpufreq_hw 0>; L1_I_1: l1-icache { compatible = "arm,arch-cache"; }; @@ -172,8 +191,13 @@ reg = <0x0 0x2>; enable-method = "psci"; capacity-dmips-mhz = <1024>; + clocks = <&xo>; cpu-idle-states = <&LITTLE_CPU_SLEEP_0 &LITTLE_CPU_SLEEP_1>; next-level-cache = <&L2_0>; + operating-points-v2 = <&cpu_silver_opp_table>; + power-domains = <&apc_cprh 0>; + power-domain-names = "cprh"; + qcom,freq-domain = <&cpufreq_hw 0>; L1_I_2: l1-icache { compatible = "arm,arch-cache"; }; @@ -188,8 +212,13 @@ reg = <0x0 0x3>; enable-method = "psci"; capacity-dmips-mhz = <1024>; + clocks = <&xo>; cpu-idle-states = <&LITTLE_CPU_SLEEP_0 &LITTLE_CPU_SLEEP_1>; next-level-cache = <&L2_0>; + operating-points-v2 = <&cpu_silver_opp_table>; + power-domains = <&apc_cprh 0>; + power-domain-names = "cprh"; + qcom,freq-domain = <&cpufreq_hw 0>; L1_I_3: l1-icache { compatible = "arm,arch-cache"; }; @@ -204,8 +233,13 @@ reg = <0x0 0x100>; enable-method = "psci"; capacity-dmips-mhz = <1536>; + clocks = <&xo>; cpu-idle-states = <&BIG_CPU_SLEEP_0 &BIG_CPU_SLEEP_1>; next-level-cache = <&L2_1>; + operating-points-v2 = <&cpu_gold_opp_table>; + power-domains = <&apc_cprh 1>; + power-domain-names = "cprh"; + qcom,freq-domain = <&cpufreq_hw 1>; L2_1: l2-cache { compatible = "arm,arch-cache"; cache-level = <2>; @@ -224,8 +258,13 @@ reg = <0x0 0x101>; enable-method = "psci"; capacity-dmips-mhz = <1536>; + clocks = <&xo>; cpu-idle-states = <&BIG_CPU_SLEEP_0 &BIG_CPU_SLEEP_1>; next-level-cache = <&L2_1>; + operating-points-v2 = <&cpu_gold_opp_table>; + power-domains = <&apc_cprh 1>; + power-domain-names = "cprh"; + qcom,freq-domain = <&cpufreq_hw 1>; L1_I_101: l1-icache { compatible = "arm,arch-cache"; }; @@ -240,8 +279,13 @@ reg = <0x0 0x102>; enable-method = "psci"; capacity-dmips-mhz = <1536>; + clocks = <&xo>; cpu-idle-states = <&BIG_CPU_SLEEP_0 &BIG_CPU_SLEEP_1>; next-level-cache = <&L2_1>; + operating-points-v2 = <&cpu_gold_opp_table>; + power-domains = <&apc_cprh 1>; + power-domain-names = "cprh"; + qcom,freq-domain = <&cpufreq_hw 1>; L1_I_102: l1-icache { compatible = "arm,arch-cache"; }; @@ -256,8 +300,13 @@ reg = <0x0 0x103>; enable-method = "psci"; capacity-dmips-mhz = <1536>; + clocks = <&xo>; cpu-idle-states = <&BIG_CPU_SLEEP_0 &BIG_CPU_SLEEP_1>; next-level-cache = <&L2_1>; + operating-points-v2 = <&cpu_gold_opp_table>; + power-domains = <&apc_cprh 1>; + power-domain-names = "cprh"; + qcom,freq-domain = <&cpufreq_hw 1>; L1_I_103: l1-icache { compatible = "arm,arch-cache"; }; @@ -351,6 +400,378 @@ }; }; + cpu_gold_opp_table: cpu-gold-opp-table { + compatible = "operating-points-v2"; + opp-shared; + + opp-2361600000 { + opp-hz = /bits/ 64 <2361600000>; + required-opps = <&cprh_opp30>; + qcom,pll-override = <0x0a620062>; + qcom,spare-data = <3>; + }; + opp-2342400000 { + opp-hz = /bits/ 64 <2342400000>; + required-opps = <&cprh_opp29>; + qcom,pll-override = <0x0a620062>; + qcom,spare-data = <3>; + }; + opp-2323200000 { + opp-hz = /bits/ 64 <2323200000>; + required-opps = <&cprh_opp28>; + qcom,pll-override = <0x0a610061>; + qcom,spare-data = <3>; + }; + opp-2265600000 { + opp-hz = /bits/ 64 <2265600000>; + required-opps = <&cprh_opp27>; + qcom,pll-override = <0x0a5e005e>; + qcom,spare-data = <3>; + }; + opp-2208000000 { + opp-hz = /bits/ 64 <2208000000>; + required-opps = <&cprh_opp26>; + qcom,pll-override = <0x0a5c005c>; + qcom,spare-data = <3>; + }; + opp-2112000000 { + opp-hz = /bits/ 64 <2112000000>; + required-opps = <&cprh_opp25>; + qcom,pll-override = <0x0a580058>; + qcom,spare-data = <3>; + }; + opp-2035200000 { + opp-hz = /bits/ 64 <2035200000>; + required-opps = <&cprh_opp24>; + qcom,pll-override = <0x09550055>; + qcom,spare-data = <3>; + }; + opp-1958400000 { + opp-hz = /bits/ 64 <1958400000>; + required-opps = <&cprh_opp23>; + qcom,pll-override = <0x09520052>; + qcom,spare-data = <2>; + }; + opp-1881600000 { + opp-hz = /bits/ 64 <1881600000>; + required-opps = <&cprh_opp22>; + qcom,pll-override = <0x094e004e>; + qcom,spare-data = <2>; + }; + opp-1804800000 { + opp-hz = /bits/ 64 <1804800000>; + required-opps = <&cprh_opp21>; + qcom,pll-override = <0x084b004b>; + qcom,spare-data = <2>; + }; + opp-1728000000 { + opp-hz = /bits/ 64 <1728000000>; + required-opps = <&cprh_opp20>; + qcom,pll-override = <0x08480048>; + qcom,spare-data = <2>; + }; + opp-1651200000 { + opp-hz = /bits/ 64 <1651200000>; + required-opps = <&cprh_opp19>; + qcom,pll-override = <0x07450045>; + qcom,spare-data = <2>; + }; + opp-1574400000 { + opp-hz = /bits/ 64 <1574400000>; + required-opps = <&cprh_opp18>; + qcom,pll-override = <0x07420042>; + qcom,spare-data = <2>; + }; + opp-1497600000 { + opp-hz = /bits/ 64 <1497600000>; + required-opps = <&cprh_opp17>; + qcom,pll-override = <0x073e003e>; + qcom,spare-data = <2>; + }; + opp-1420800000 { + opp-hz = /bits/ 64 <1420800000>; + required-opps = <&cprh_opp16>; + qcom,pll-override = <0x063b003b>; + qcom,spare-data = <2>; + }; + opp-1344000000 { + opp-hz = /bits/ 64 <1344000000>; + required-opps = <&cprh_opp15>; + qcom,pll-override = <0x06380038>; + qcom,spare-data = <2>; + }; + + opp-1267200000 { + opp-hz = /bits/ 64 <1267200000>; + required-opps = <&cprh_opp14>; + qcom,pll-override = <0x06350035>; + qcom,spare-data = <2>; + }; + opp-1190400000 { + opp-hz = /bits/ 64 <1190400000>; + required-opps = <&cprh_opp13>; + qcom,pll-override = <0x05320032>; + qcom,spare-data = <2>; + }; + opp-1132800000 { + opp-hz = /bits/ 64 <1132800000>; + required-opps = <&cprh_opp12>; + qcom,pll-override = <0x052f002f>; + qcom,spare-data = <1>; + }; + opp-1056000000 { + opp-hz = /bits/ 64 <1056000000>; + required-opps = <&cprh_opp11>; + qcom,pll-override = <0x052c002c>; + qcom,spare-data = <1>; + }; + opp-979200000 { + opp-hz = /bits/ 64 <979200000>; + required-opps = <&cprh_opp10>; + qcom,pll-override = <0x4290029>; + qcom,spare-data = <1>; + }; + opp-902400000 { + opp-hz = /bits/ 64 <902400000>; + required-opps = <&cprh_opp9>; + qcom,pll-override = <0x4260026>; + qcom,spare-data = <1>; + }; + opp-806400000 { + opp-hz = /bits/ 64 <806400000>; + required-opps = <&cprh_opp8>; + qcom,pll-override = <0x3200022>; + qcom,pll-div = <1>; + qcom,spare-data = <1>; + }; + opp-729600000 { + opp-hz = /bits/ 64 <729600000>; + required-opps = <&cprh_opp7>; + qcom,pll-override = <0x3200020>; + qcom,pll-div = <1>; + qcom,spare-data = <1>; + }; + opp-652800000 { + opp-hz = /bits/ 64 <652800000>; + required-opps = <&cprh_opp6>; + qcom,pll-override = <0x3200020>; + qcom,pll-div = <1>; + qcom,spare-data = <1>; + }; + opp-576000000 { + opp-hz = /bits/ 64 <576000000>; + required-opps = <&cprh_opp5>; + qcom,pll-override = <0x2200020>; + qcom,pll-div = <1>; + qcom,spare-data = <1>; + }; + opp-499200000 { + opp-hz = /bits/ 64 <499200000>; + required-opps = <&cprh_opp4>; + qcom,pll-override = <0x2200020>; + qcom,pll-div = <1>; + qcom,spare-data = <1>; + }; + opp-422400000 { + opp-hz = /bits/ 64 <422400000>; + required-opps = <&cprh_opp3>; + qcom,pll-override = <0x2200020>; + qcom,pll-div = <1>; + qcom,spare-data = <1>; + }; + opp-345600000 { + opp-hz = /bits/ 64 <345600000>; + required-opps = <&cprh_opp2>; + qcom,pll-override = <0x1200020>; + qcom,pll-div = <1>; + qcom,spare-data = <1>; + }; + opp-300000000 { + opp-hz = /bits/ 64 <300000000>; + required-opps = <&cprh_opp1>; + qcom,pll-override = <0x1200020>; + qcom,spare-data = <1>; + }; + }; + + cpu_silver_opp_table: cpu-silver-opp-table { + compatible = "operating-points-v2"; + opp-shared; + + opp-1900800000 { + opp-hz = /bits/ 64 <1900800000>; + required-opps = <&cprh_opp22>; + qcom,pll-override = <0x094f004f>; + qcom,spare-data = <3>; + }; + opp-1824000000 { + opp-hz = /bits/ 64 <1824000000>; + required-opps = <&cprh_opp21>; + qcom,pll-override = <0x084c004c>; + qcom,spare-data = <3>; + }; + opp-1747200000 { + opp-hz = /bits/ 64 <1747200000>; + required-opps = <&cprh_opp20>; + qcom,pll-override = <0x08490049>; + qcom,spare-data = <2>; + }; + opp-1670400000 { + opp-hz = /bits/ 64 <1670400000>; + required-opps = <&cprh_opp19>; + qcom,pll-override = <0x08460046>; + qcom,spare-data = <2>; + }; + opp-1555200000 { + opp-hz = /bits/ 64 <1555200000>; + required-opps = <&cprh_opp18>; + qcom,pll-override = <0x07410041>; + qcom,spare-data = <2>; + }; + opp-1478400000 { + opp-hz = /bits/ 64 <1478400000>; + required-opps = <&cprh_opp17>; + qcom,pll-override = <0x073e003e>; + qcom,spare-data = <2>; + }; + opp-1401600000 { + opp-hz = /bits/ 64 <1401600000>; + required-opps = <&cprh_opp16>; + qcom,pll-override = <0x063a003a>; + qcom,spare-data = <2>; + }; + opp-1324800000 { + opp-hz = /bits/ 64 <1324800000>; + required-opps = <&cprh_opp15>; + qcom,pll-override = <0x06370037>; + qcom,spare-data = <2>; + }; + opp-1248000000 { + opp-hz = /bits/ 64 <1248000000>; + required-opps = <&cprh_opp14>; + qcom,pll-override = <0x05340034>; + qcom,spare-data = <2>; + }; + opp-1171200000 { + opp-hz = /bits/ 64 <1171200000>; + required-opps = <&cprh_opp13>; + qcom,pll-override = <0x05310031>; + qcom,spare-data = <2>; + }; + opp-1094400000 { + opp-hz = /bits/ 64 <1094400000>; + required-opps = <&cprh_opp12>; + qcom,pll-override = <0x052e002e>; + qcom,spare-data = <2>; + }; + opp-1036800000 { + opp-hz = /bits/ 64 <1036800000>; + required-opps = <&cprh_opp11>; + qcom,pll-override = <0x042b002b>; + qcom,spare-data = <1>; + }; + opp-960000000 { + opp-hz = /bits/ 64 <960000000>; + required-opps = <&cprh_opp10>; + qcom,pll-override = <0x4280028>; + qcom,spare-data = <1>; + }; + opp-883200000 { + opp-hz = /bits/ 64 <883200000>; + required-opps = <&cprh_opp9>; + qcom,pll-override = <0x4250025>; + qcom,spare-data = <1>; + }; + opp-825600000 { + opp-hz = /bits/ 64 <825600000>; + required-opps = <&cprh_opp8>; + qcom,pll-override = <0x3200022>; + qcom,spare-data = <1>; + }; + opp-748800000 { + opp-hz = /bits/ 64 <748800000>; + required-opps = <&cprh_opp7>; + qcom,pll-override = <0x3200020>; + qcom,pll-div = <1>; + qcom,spare-data = <1>; + }; + opp-672000000 { + opp-hz = /bits/ 64 <672000000>; + required-opps = <&cprh_opp6>; + qcom,pll-override = <0x3200020>; + qcom,pll-div = <1>; + qcom,spare-data = <1>; + }; + opp-595200000 { + opp-hz = /bits/ 64 <595200000>; + required-opps = <&cprh_opp5>; + qcom,pll-override = <0x2200020>; + qcom,pll-div = <1>; + qcom,spare-data = <1>; + }; + opp-518400000 { + opp-hz = /bits/ 64 <518400000>; + required-opps = <&cprh_opp4>; + qcom,pll-override = <0x2200020>; + qcom,pll-div = <1>; + qcom,spare-data = <1>; + }; + opp-441600000 { + opp-hz = /bits/ 64 <441600000>; + required-opps = <&cprh_opp3>; + qcom,pll-override = <0x2200020>; + qcom,pll-div = <1>; + qcom,spare-data = <1>; + }; + opp-364800000 { + opp-hz = /bits/ 64 <364800000>; + required-opps = <&cprh_opp2>; + qcom,pll-override = <0x1200020>; + qcom,pll-div = <1>; + qcom,spare-data = <1>; + }; + opp-300000000 { + opp-hz = /bits/ 64 <300000000>; + required-opps = <&cprh_opp1>; + qcom,pll-override = <0x1200020>; + }; + }; + + cprh_opp_table: cpr-opp-table { + compatible = "operating-points-v2-qcom-level"; + + CPRH_OPP_ENTRY(1, 1, 0, 0); + CPRH_OPP_ENTRY(2, 1, 0, 0); + CPRH_OPP_ENTRY(3, 1, 0, 0); + CPRH_OPP_ENTRY(4, 1, 0, 0); + CPRH_OPP_ENTRY(5, 1, 0, 0); + CPRH_OPP_ENTRY(6, 1, 0, 0); + CPRH_OPP_ENTRY(7, 1, 0, 0); + CPRH_OPP_ENTRY(8, 1, 0, 0); + CPRH_OPP_ENTRY(9, 2, 0, 0); + CPRH_OPP_ENTRY(10, 2, 0, 0); + CPRH_OPP_ENTRY(11, 2, 0, 0); + CPRH_OPP_ENTRY(12, 3 2, (-12000) (-8000), (-10000) (-10000)); + CPRH_OPP_ENTRY(13, 3, (-16000) (-16000), (-11000) (-10000)); + CPRH_OPP_ENTRY(14, 3, (-16000) (-12000), (-12000) (-11000)); + CPRH_OPP_ENTRY(15, 3, (-12000) (-16000), (-13000) (-12000)); + CPRH_OPP_ENTRY(16, 3, (-12000) (-16000), (-14000) (-12000)); + CPRH_OPP_ENTRY(17, 3, (-16000) (-12000), (-14000) (-13000)); + CPRH_OPP_ENTRY(18, 3, (-16000) (-16000), (-15000) (-14000)); + CPRH_OPP_ENTRY(19, 4 3, (-20000) (-16000), (-21000) (-14000)); + CPRH_OPP_ENTRY(20, 4 3, (-24000) (-16000), (-24000) (-15000)); + CPRH_OPP_ENTRY(21, 4, (-28000) (-24000), (-26000) (-16000)); + CPRH_OPP_ENTRY(22, 4, (-28000) (-16000), (-28000) (-16000)); + CPRH_OPP_ENTRY(23, 4, 0 (-20000), 0 (-17000)); + CPRH_OPP_ENTRY(24, 4, 0 (-16000), 0 (-15000)); + CPRH_OPP_ENTRY(25, 4, 0 (-12000), 0 (-14000)); + CPRH_OPP_ENTRY(26, 4, 0 (-28000), 0 (-27000)); + CPRH_OPP_ENTRY(27, 4, 0 (-28000), 0 (-27000)); + CPRH_OPP_ENTRY(28, 4, 0 (-28000), 0 (-28000)); + CPRH_OPP_ENTRY(29, 4, 0 (-28000), 0 (-28000)); + CPRH_OPP_ENTRY(30, 4, 0 (-28000), 0 (-28000)); + }; + firmware { scm { compatible = "qcom,scm-msm8998", "qcom,scm"; @@ -873,7 +1294,7 @@ qfprom: qfprom@784000 { compatible = "qcom,qfprom"; - reg = <0x00784000 0x621c>; + reg = <0x00784000 0x221c>; #address-cells = <1>; #size-cells = <1>; @@ -881,6 +1302,186 @@ reg = <0x23a 0x1>; bits = <0 4>; }; + + cpr_efuse_speedbin: speedbin@133 { + reg = <0x133 0x2>; + bits = <5 3>; + }; + + cpr_fuse_revision: cpr_fusing_rev@13e { + reg = <0x13E 0x1>; + bits = <3 3>; + }; + + /* CPR Ring Oscillator: Power Cluster */ + cpr_ro_sel0_pwrcl: rosel0_pwrcl@219 { + reg = <0x219 0x2>; + bits = <4 4>; + }; + + cpr_ro_sel1_pwrcl: rosel1_pwrcl@219 { + reg = <0x219 0x1>; + bits = <0 4>; + }; + + cpr_ro_sel2_pwrcl: rosel2_pwrcl@218 { + reg = <0x218 0x2>; + bits = <4 4>; + }; + + cpr_ro_sel3_pwrcl: rosel3_pwrcl@218 { + reg = <0x218 0x1>; + bits = <0 4>; + }; + + /* CPR Init Voltage: Power Cluster */ + cpr_init_voltage0_pwrcl: ivolt0_pwrcl@21c { + reg = <0x21C 0x2>; + bits = <2 6>; + }; + + cpr_init_voltage1_pwrcl: ivolt1_pwrcl@21b { + reg = <0x21B 0x2>; + bits = <4 6>; + }; + + cpr_init_voltage2_pwrcl: ivolt2_pwrcl@21a { + reg = <0x21A 0x2>; + bits = <6 6>; + }; + + cpr_init_voltage3_pwrcl: ivolt3_pwrcl@21a { + reg = <0x21A 0x1>; + bits = <0 6>; + }; + + /* CPR Target Quotients: Power Cluster */ + cpr_quot0_pwrcl: quot0_pwrcl@222 { + reg = <0x222 0x3>; + bits = <2 12>; + }; + + cpr_quot1_pwrcl: quot1_pwrcl@220 { + reg = <0x220 0x3>; + bits = <6 12>; + }; + + cpr_quot2_pwrcl: quot2_pwrcl@21f { + reg = <0x21F 0x2>; + bits = <2 11>; + }; + + cpr_quot3_pwrcl: quot3_pwrcl@21d { + reg = <0x21D 0x3>; + bits = <6 12>; + }; + + /* CPR Quotient Offsets: Power Cluster */ + cpr_quot_offset1_pwrcl: qoff1_pwrcl@227 { + reg = <0x227 0x2>; + bits = <7 6>; + }; + + cpr_quot_offset2_pwrcl: qoff2_pwrcl@227 { + reg = <0x227 0x1>; + bits = <0 7>; + }; + + cpr_quot_offset3_pwrcl: qoff3_pwrcl@226 { + reg = <0x226 0x2>; + bits = <1 7>; + }; + + /* CPR Aging Quotient Offsets: Power Cluster */ + cpr_aging_quot_off_pwrcl: qoff_aging_pwrcl@228 { + reg = <0x228 0x2>; + bits = <6 8>; + }; + + /* CPR Ring Oscillator: Performance Cluster */ + cpr_ro_sel0_perfcl: rosel0_perfcl@22b { + reg = <0x22B 0x1>; + bits = <2 4>; + }; + + cpr_ro_sel1_perfcl: rosel1_perfcl@22a { + reg = <0x22A 0x2>; + bits = <6 4>; + }; + + cpr_ro_sel2_perfcl: rosel2_perfcl@22a { + reg = <0x22A 0x1>; + bits = <2 4>; + }; + + cpr_ro_sel3_perfcl: rosel3_perfcl@229 { + reg = <0x229 0x2>; + bits = <6 4>; + }; + + /* CPR Init Voltage: Performance Cluster */ + cpr_init_voltage0_perfcl: ivolt0_perfcl@22e { + reg = <0x22E 0x1>; + bits = <0 6>; + }; + + cpr_init_voltage1_perfcl: ivolt1_perfcl@22d { + reg = <0x22D 0x2>; + bits = <2 6>; + }; + + cpr_init_voltage2_perfcl: ivolt2_perfcl@22c { + reg = <0x22C 0x2>; + bits = <4 6>; + }; + + cpr_init_voltage3_perfcl: ivolt3_perfcl@22b { + reg = <0x22B 0x2>; + bits = <6 6>; + }; + + /* CPR Target Quotients: Performance Cluster */ + cpr_quot0_perfcl: quot0_perfcl@234 { + reg = <0x234 0x2>; + bits = <0 12>; + }; + + cpr_quot1_perfcl: quot1_perfcl@232 { + reg = <0x232 0x2>; + bits = <4 12>; + }; + + cpr_quot2_perfcl: quot2_perfcl@231 { + reg = <0x231 0x2>; + bits = <0 12>; + }; + + cpr_quot3_perfcl: quot3_perfcl@22f { + reg = <0x22F 0x2>; + bits = <4 11>; + }; + + /* CPR Quotient Offsets: Performance Cluster */ + cpr_quot_offset1_perfcl: qoff1_perfcl@239 { + reg = <0x239 0x2>; + bits = <5 3>; + }; + + cpr_quot_offset2_perfcl: qoff2_perfcl@238 { + reg = <0x238 0x2>; + bits = <6 7>; + }; + + cpr_quot_offset3_perfcl: qoff3_perfcl@237 { + reg = <0x237 0x2>; + bits = <7 7>; + }; + + /* CPR Aging Quotient Offsets: Performance Cluster */ + cpr_aging_quot_off_perfcl: qoff_aging_perfcl@23b { + reg = <0x23b 0x2>; + bits = <1 8>; + }; }; bimc: interconnect@1008000 { @@ -2904,6 +3505,38 @@ clocks = <&xo>, <&xo>; }; + power-controller@17812000 { + compatible = "qcom,msm8998-gold-saw2-v4.1-l2", + "qcom,saw2"; + reg = <0x017812000 0x1000>; + }; + + power-controller@17912000 { + compatible = "qcom,msm8998-silver-saw2-v4.1-l2", + "qcom,saw2"; + reg = <0x017912000 0x1000>; + }; + + cpufreq_hw: cpufreq_hw@17814800 { + compatible = "qcom,cpufreq-hw-8998"; + reg = <0x017914800 0x100>, <0x017814800 0x100>, + <0x0179c0000 0x1000>, <0x0179c1000 0x1000>, + <0x0179c2000 0x1000>, <0x0179c3000 0x1000>; + reg-names = "osm-acd0", "osm-acd1", + "osm-domain0", "freq-domain0", + "osm-domain1", "freq-domain1"; + + assigned-clocks = <&gcc HMSS_GPLL0_CLK_SRC>; + assigned-clock-rates = <300000000>; + + clocks = <&rpmcc RPM_SMD_XO_A_CLK_SRC>, + <&gcc HMSS_GPLL0_CLK_SRC>; + clock-names = "xo", "alternate"; + + #freq-domain-cells = <1>; + status = "disabled"; + }; + apcs_glb: mailbox@17911000 { compatible = "qcom,msm8998-apcs-hmss-global"; reg = <0x17911000 0x1000>; @@ -2969,6 +3602,92 @@ }; }; + /* Gold and Silver cluster */ + apc_cprh: power-controller@179c8000 { + compatible = "qcom,msm8998-cprh"; + reg = <0x0179c8000 0x4000>, <0x0179c4000 0x4000>; + + assigned-clocks = <&gcc GCC_HMSS_RBCPR_CLK>; + assigned-clock-rates = <19200000>; + clocks = <&gcc GCC_HMSS_RBCPR_CLK>; + clock-names = "ref"; + + operating-points-v2 = <&cprh_opp_table>; + power-domains = <&rpmpd MSM8998_VDDCX_AO>; + #power-domain-cells = <1>; + status = "disabled"; + + nvmem-cells = <&cpr_efuse_speedbin>, + <&cpr_fuse_revision>, + <&cpr_quot0_pwrcl>, + <&cpr_quot1_pwrcl>, + <&cpr_quot2_pwrcl>, + <&cpr_quot3_pwrcl>, + <&cpr_quot_offset1_pwrcl>, + <&cpr_quot_offset2_pwrcl>, + <&cpr_quot_offset3_pwrcl>, + <&cpr_init_voltage0_pwrcl>, + <&cpr_init_voltage1_pwrcl>, + <&cpr_init_voltage2_pwrcl>, + <&cpr_init_voltage3_pwrcl>, + <&cpr_ro_sel0_pwrcl>, + <&cpr_ro_sel1_pwrcl>, + <&cpr_ro_sel2_pwrcl>, + <&cpr_ro_sel3_pwrcl>, + <&cpr_aging_quot_off_pwrcl>, + <&cpr_quot0_perfcl>, + <&cpr_quot1_perfcl>, + <&cpr_quot2_perfcl>, + <&cpr_quot3_perfcl>, + <&cpr_quot_offset1_perfcl>, + <&cpr_quot_offset2_perfcl>, + <&cpr_quot_offset3_perfcl>, + <&cpr_init_voltage0_perfcl>, + <&cpr_init_voltage1_perfcl>, + <&cpr_init_voltage2_perfcl>, + <&cpr_init_voltage3_perfcl>, + <&cpr_ro_sel0_perfcl>, + <&cpr_ro_sel1_perfcl>, + <&cpr_ro_sel2_perfcl>, + <&cpr_ro_sel3_perfcl>, + <&cpr_aging_quot_off_perfcl>; + + nvmem-cell-names = "cpr_speed_bin", + "cpr_fuse_revision", + "cpr0_quotient1", + "cpr0_quotient2", + "cpr0_quotient3", + "cpr0_quotient4", + "cpr0_quotient_offset2", + "cpr0_quotient_offset3", + "cpr0_quotient_offset4", + "cpr0_init_voltage1", + "cpr0_init_voltage2", + "cpr0_init_voltage3", + "cpr0_init_voltage4", + "cpr0_ring_osc1", + "cpr0_ring_osc2", + "cpr0_ring_osc3", + "cpr0_ring_osc4", + "cpr0_aging_quotient", + "cpr1_quotient1", + "cpr1_quotient2", + "cpr1_quotient3", + "cpr1_quotient4", + "cpr1_quotient_offset2", + "cpr1_quotient_offset3", + "cpr1_quotient_offset4", + "cpr1_init_voltage1", + "cpr1_init_voltage2", + "cpr1_init_voltage3", + "cpr1_init_voltage4", + "cpr1_ring_osc1", + "cpr1_ring_osc2", + "cpr1_ring_osc3", + "cpr1_ring_osc4", + "cpr1_aging_quotient"; + }; + intc: interrupt-controller@17a00000 { compatible = "arm,gic-v3"; reg = <0x17a00000 0x10000>, /* GICD */ From 7e3c8189f536e1ae6eea4992ceb1753dbba76404 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Thu, 3 Dec 2020 12:24:21 +0100 Subject: [PATCH 073/187] arm64: dts: qcom: pmi8998: Add node for WLED The PMI8998 PMIC has a WLED backlight controller, which is used on most MSM8998 and SDM845 based devices: add a base configuration for it and keep it disabled. This contains only the PMIC specific configuration that does not change across boards; parameters like number of strings, OVP and current limits are product specific and shall be specified in the product DT in order to achieve functionality. Signed-off-by: AngeloGioacchino Del Regno --- arch/arm64/boot/dts/qcom/pmi8998.dtsi | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/pmi8998.dtsi b/arch/arm64/boot/dts/qcom/pmi8998.dtsi index a5d50d7156cc..f597c371c792 100644 --- a/arch/arm64/boot/dts/qcom/pmi8998.dtsi +++ b/arch/arm64/boot/dts/qcom/pmi8998.dtsi @@ -62,5 +62,17 @@ status = "disabled"; }; + + pmi8998_wled: leds@d800 { + compatible = "qcom,pm660l-wled"; + reg = <0xd800 0xd900>; + interrupts = <0x3 0xd8 0x1 IRQ_TYPE_EDGE_RISING>, + <0x3 0xd8 0x2 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "ovp", "short"; + label = "backlight"; + + status = "disabled"; + }; + }; }; From 75d376a90e2ca03d370546aba4d8cf3f50eccc05 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Sat, 9 Jan 2021 14:37:35 +0100 Subject: [PATCH 074/187] drm/msm/dpu: Add a function to retrieve the current CTL status Add a function that returns whether the requested CTL is active or not: this will be used in a later commit to fix command mode panel issues. Signed-off-by: AngeloGioacchino Del Regno --- drivers/gpu/drm/msm/disp/dpu1/dpu_hw_ctl.c | 6 ++++++ drivers/gpu/drm/msm/disp/dpu1/dpu_hw_ctl.h | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_ctl.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_ctl.c index 64740ddb983e..3b6fd73eb3a8 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_ctl.c +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_ctl.c @@ -91,6 +91,11 @@ static inline void dpu_hw_ctl_trigger_start(struct dpu_hw_ctl *ctx) DPU_REG_WRITE(&ctx->hw, CTL_START, 0x1); } +static inline bool dpu_hw_ctl_is_started(struct dpu_hw_ctl *ctx) +{ + return !!(DPU_REG_READ(&ctx->hw, CTL_START) & BIT(0)); +} + static inline void dpu_hw_ctl_trigger_pending(struct dpu_hw_ctl *ctx) { trace_dpu_hw_ctl_trigger_prepare(ctx->pending_flush_mask, @@ -579,6 +584,7 @@ static void _setup_ctl_ops(struct dpu_hw_ctl_ops *ops, ops->get_pending_flush = dpu_hw_ctl_get_pending_flush; ops->get_flush_register = dpu_hw_ctl_get_flush_register; ops->trigger_start = dpu_hw_ctl_trigger_start; + ops->is_started = dpu_hw_ctl_is_started; ops->trigger_pending = dpu_hw_ctl_trigger_pending; ops->reset = dpu_hw_ctl_reset_control; ops->wait_reset_status = dpu_hw_ctl_wait_reset_status; diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_ctl.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_ctl.h index 806c171e5df2..ac1544474022 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_ctl.h +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_ctl.h @@ -61,6 +61,13 @@ struct dpu_hw_ctl_ops { */ void (*trigger_start)(struct dpu_hw_ctl *ctx); + /** + * check if the ctl is started + * @ctx : ctl path ctx pointer + * @Return: true if started, false if stopped + */ + bool (*is_started)(struct dpu_hw_ctl *ctx); + /** * kickoff prepare is in progress hw operation for sw * controlled interfaces: DSI cmd mode and WB interface From c4e8ede8cf326419a9cce41471374b6ec1b632f8 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Sat, 9 Jan 2021 14:37:36 +0100 Subject: [PATCH 075/187] drm/msm/dpu: Fix timeout issues on command mode panels In function dpu_encoder_phys_cmd_wait_for_commit_done we are always checking if the relative CTL is started by waiting for an interrupt to fire: it is fine to do that, but then sometimes we call this function while the CTL is up and has never been put down, but that interrupt gets raised only when the CTL gets a state change from 0 to 1 (disabled to enabled), so we're going to wait for something that will never happen on its own. Solving this while avoiding to restart the CTL is actually possible and can be done by just checking if it is already up and running when the wait_for_commit_done function is called: in this case, so, if the CTL was already running, we can say that the commit is done if the command transmission is complete (in other terms, if the interface has been flushed). Signed-off-by: AngeloGioacchino Del Regno --- drivers/gpu/drm/msm/disp/dpu1/dpu_encoder_phys_cmd.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder_phys_cmd.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder_phys_cmd.c index 34a6940d12c5..72f06aa46a64 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder_phys_cmd.c +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder_phys_cmd.c @@ -682,6 +682,9 @@ static int dpu_encoder_phys_cmd_wait_for_commit_done( if (!dpu_encoder_phys_cmd_is_master(phys_enc)) return 0; + if (phys_enc->hw_ctl->ops.is_started) + return dpu_encoder_phys_cmd_wait_for_tx_complete(phys_enc); + return _dpu_encoder_phys_cmd_wait_for_ctl_start(phys_enc); } From 2f458a6272611c19612452ebb6e57540f5c62a8f Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Fri, 11 Dec 2020 19:24:41 +0100 Subject: [PATCH 076/187] clk: qcom: smd: Add missing MSM8998 RPM clocks Signed-off-by: Konrad Dybcio JAMI: fixed for a0384ecfe2aa ("clk: qcom: smd-rpm: De-duplicate identical entries") JAMI: fixed for 36354c32bd76 ("clk: qcom: smd-rpm: Add .recalc_rate hook for clk_smd_rpm_branch_ops") --- drivers/clk/qcom/clk-smd-rpm.c | 68 ++++++++++++++++++++-------------- 1 file changed, 41 insertions(+), 27 deletions(-) diff --git a/drivers/clk/qcom/clk-smd-rpm.c b/drivers/clk/qcom/clk-smd-rpm.c index 5776d85a1e5c..b4aaa9c5ffe8 100644 --- a/drivers/clk/qcom/clk-smd-rpm.c +++ b/drivers/clk/qcom/clk-smd-rpm.c @@ -820,15 +820,20 @@ static const struct rpm_smd_clk_desc rpm_clk_qcs404 = { .num_clks = ARRAY_SIZE(qcs404_clks), }; -DEFINE_CLK_SMD_RPM_XO_BUFFER_PINCTRL(msm8998, ln_bb_clk3_pin, ln_bb_clk3_a_pin, - 3, 19200000); +DEFINE_CLK_SMD_RPM_BRANCH(msm8998, bi_tcxo, bi_tcxo_a, QCOM_SMD_RPM_MISC_CLK, 0, + 19200000); +DEFINE_CLK_SMD_RPM_XO_BUFFER(msm8998, ln_bb_clk3, ln_bb_clk3_a, 3, 19200000); +DEFINE_CLK_SMD_RPM_XO_BUFFER_PINCTRL(msm8998, ln_bb_clk3_pin, ln_bb_clk3_a_pin, 3, 19200000); DEFINE_CLK_SMD_RPM(msm8998, aggre1_noc_clk, aggre1_noc_a_clk, QCOM_SMD_RPM_AGGR_CLK, 1); DEFINE_CLK_SMD_RPM(msm8998, aggre2_noc_clk, aggre2_noc_a_clk, QCOM_SMD_RPM_AGGR_CLK, 2); DEFINE_CLK_SMD_RPM_XO_BUFFER(msm8998, rf_clk3, rf_clk3_a, 6, 19200000); DEFINE_CLK_SMD_RPM_XO_BUFFER_PINCTRL(msm8998, rf_clk3_pin, rf_clk3_a_pin, 6, 19200000); + static struct clk_smd_rpm *msm8998_clks[] = { + [RPM_SMD_XO_CLK_SRC] = &msm8998_bi_tcxo, + [RPM_SMD_XO_A_CLK_SRC] = &msm8998_bi_tcxo_a, [RPM_SMD_BIMC_CLK] = &msm8916_bimc_clk, [RPM_SMD_BIMC_A_CLK] = &msm8916_bimc_a_clk, [RPM_SMD_PCNOC_CLK] = &msm8916_pcnoc_clk, @@ -841,12 +846,22 @@ static struct clk_smd_rpm *msm8998_clks[] = { [RPM_SMD_CE1_A_CLK] = &msm8992_ce1_a_clk, [RPM_SMD_DIV_CLK1] = &msm8974_div_clk1, [RPM_SMD_DIV_A_CLK1] = &msm8974_div_a_clk1, + [RPM_SMD_DIV_CLK2] = &msm8974_div_clk2, + [RPM_SMD_DIV_A_CLK2] = &msm8974_div_a_clk2, + [RPM_SMD_DIV_CLK3] = &msm8992_div_clk3, + [RPM_SMD_DIV_A_CLK3] = &msm8992_div_clk3_a, [RPM_SMD_IPA_CLK] = &msm8976_ipa_clk, [RPM_SMD_IPA_A_CLK] = &msm8976_ipa_a_clk, [RPM_SMD_LN_BB_CLK1] = &msm8916_bb_clk1, [RPM_SMD_LN_BB_CLK1_A] = &msm8916_bb_clk1_a, [RPM_SMD_LN_BB_CLK2] = &msm8916_bb_clk2, [RPM_SMD_LN_BB_CLK2_A] = &msm8916_bb_clk2_a, + [RPM_SMD_LN_BB_CLK3] = &msm8998_ln_bb_clk3, + [RPM_SMD_LN_BB_CLK3_A] = &msm8998_ln_bb_clk3_a, + [RPM_SMD_LN_BB_CLK1_PIN] = &msm8916_bb_clk1_pin, + [RPM_SMD_LN_BB_CLK1_A_PIN] = &msm8916_bb_clk1_a_pin, + [RPM_SMD_LN_BB_CLK2_PIN] = &msm8916_bb_clk2_pin, + [RPM_SMD_LN_BB_CLK2_A_PIN] = &msm8916_bb_clk2_a_pin, [RPM_SMD_LN_BB_CLK3_PIN] = &msm8998_ln_bb_clk3_pin, [RPM_SMD_LN_BB_CLK3_A_PIN] = &msm8998_ln_bb_clk3_a_pin, [RPM_SMD_MMAXI_CLK] = &msm8996_mmssnoc_axi_rpm_clk, @@ -859,10 +874,14 @@ static struct clk_smd_rpm *msm8998_clks[] = { [RPM_SMD_QDSS_A_CLK] = &msm8916_qdss_a_clk, [RPM_SMD_RF_CLK1] = &msm8916_rf_clk1, [RPM_SMD_RF_CLK1_A] = &msm8916_rf_clk1_a, - [RPM_SMD_RF_CLK2_PIN] = &msm8916_rf_clk2_pin, - [RPM_SMD_RF_CLK2_A_PIN] = &msm8916_rf_clk2_a_pin, + [RPM_SMD_RF_CLK2] = &msm8916_rf_clk2, + [RPM_SMD_RF_CLK2_A] = &msm8916_rf_clk2_a, [RPM_SMD_RF_CLK3] = &msm8998_rf_clk3, [RPM_SMD_RF_CLK3_A] = &msm8998_rf_clk3_a, + [RPM_SMD_RF_CLK1_PIN] = &msm8916_rf_clk1_pin, + [RPM_SMD_RF_CLK1_A_PIN] = &msm8916_rf_clk1_a_pin, + [RPM_SMD_RF_CLK2_PIN] = &msm8916_rf_clk2_pin, + [RPM_SMD_RF_CLK2_A_PIN] = &msm8916_rf_clk2_a_pin, [RPM_SMD_RF_CLK3_PIN] = &msm8998_rf_clk3_pin, [RPM_SMD_RF_CLK3_A_PIN] = &msm8998_rf_clk3_a_pin, }; @@ -872,14 +891,9 @@ static const struct rpm_smd_clk_desc rpm_clk_msm8998 = { .num_clks = ARRAY_SIZE(msm8998_clks), }; -DEFINE_CLK_SMD_RPM_BRANCH(sdm660, bi_tcxo, bi_tcxo_a, QCOM_SMD_RPM_MISC_CLK, 0, - 19200000); -DEFINE_CLK_SMD_RPM_XO_BUFFER(sdm660, ln_bb_clk3, ln_bb_clk3_a, 3, 19200000); -DEFINE_CLK_SMD_RPM_XO_BUFFER_PINCTRL(sdm660, ln_bb_clk3_pin, ln_bb_clk3_pin_a, 3, 19200000); - static struct clk_smd_rpm *sdm660_clks[] = { - [RPM_SMD_XO_CLK_SRC] = &sdm660_bi_tcxo, - [RPM_SMD_XO_A_CLK_SRC] = &sdm660_bi_tcxo_a, + [RPM_SMD_XO_CLK_SRC] = &msm8998_bi_tcxo, + [RPM_SMD_XO_A_CLK_SRC] = &msm8998_bi_tcxo_a, [RPM_SMD_SNOC_CLK] = &msm8916_snoc_clk, [RPM_SMD_SNOC_A_CLK] = &msm8916_snoc_a_clk, [RPM_SMD_CNOC_CLK] = &msm8974_cnoc_clk, @@ -906,16 +920,16 @@ static struct clk_smd_rpm *sdm660_clks[] = { [RPM_SMD_LN_BB_A_CLK] = &msm8916_bb_clk1_a, [RPM_SMD_LN_BB_CLK2] = &msm8916_bb_clk2, [RPM_SMD_LN_BB_CLK2_A] = &msm8916_bb_clk2_a, - [RPM_SMD_LN_BB_CLK3] = &sdm660_ln_bb_clk3, - [RPM_SMD_LN_BB_CLK3_A] = &sdm660_ln_bb_clk3_a, + [RPM_SMD_LN_BB_CLK3] = &msm8998_ln_bb_clk3, + [RPM_SMD_LN_BB_CLK3_A] = &msm8998_ln_bb_clk3_a, [RPM_SMD_RF_CLK1_PIN] = &msm8916_rf_clk1_pin, [RPM_SMD_RF_CLK1_A_PIN] = &msm8916_rf_clk1_a_pin, [RPM_SMD_LN_BB_CLK1_PIN] = &msm8916_bb_clk1_pin, [RPM_SMD_LN_BB_CLK1_A_PIN] = &msm8916_bb_clk1_a_pin, [RPM_SMD_LN_BB_CLK2_PIN] = &msm8916_bb_clk2_pin, [RPM_SMD_LN_BB_CLK2_A_PIN] = &msm8916_bb_clk2_a_pin, - [RPM_SMD_LN_BB_CLK3_PIN] = &sdm660_ln_bb_clk3_pin, - [RPM_SMD_LN_BB_CLK3_A_PIN] = &sdm660_ln_bb_clk3_pin_a, + [RPM_SMD_LN_BB_CLK3_PIN] = &msm8998_ln_bb_clk3_pin, + [RPM_SMD_LN_BB_CLK3_A_PIN] = &msm8998_ln_bb_clk3_a_pin, }; static const struct rpm_smd_clk_desc rpm_clk_sdm660 = { @@ -924,8 +938,8 @@ static const struct rpm_smd_clk_desc rpm_clk_sdm660 = { }; static struct clk_smd_rpm *mdm9607_clks[] = { - [RPM_SMD_XO_CLK_SRC] = &sdm660_bi_tcxo, - [RPM_SMD_XO_A_CLK_SRC] = &sdm660_bi_tcxo_a, + [RPM_SMD_XO_CLK_SRC] = &msm8998_bi_tcxo, + [RPM_SMD_XO_A_CLK_SRC] = &msm8998_bi_tcxo_a, [RPM_SMD_PCNOC_CLK] = &msm8916_pcnoc_clk, [RPM_SMD_PCNOC_A_CLK] = &msm8916_pcnoc_a_clk, [RPM_SMD_BIMC_CLK] = &msm8916_bimc_clk, @@ -946,8 +960,8 @@ static const struct rpm_smd_clk_desc rpm_clk_mdm9607 = { }; static struct clk_smd_rpm *msm8953_clks[] = { - [RPM_SMD_XO_CLK_SRC] = &sdm660_bi_tcxo, - [RPM_SMD_XO_A_CLK_SRC] = &sdm660_bi_tcxo_a, + [RPM_SMD_XO_CLK_SRC] = &msm8998_bi_tcxo, + [RPM_SMD_XO_A_CLK_SRC] = &msm8998_bi_tcxo_a, [RPM_SMD_PCNOC_CLK] = &msm8916_pcnoc_clk, [RPM_SMD_PCNOC_A_CLK] = &msm8916_pcnoc_a_clk, [RPM_SMD_SNOC_CLK] = &msm8916_snoc_clk, @@ -995,8 +1009,8 @@ DEFINE_CLK_SMD_RPM(sm6125, snoc_lpass_clk, snoc_lpass_a_clk, QCOM_SMD_RPM_BUS_CLK, 5); static struct clk_smd_rpm *sm6125_clks[] = { - [RPM_SMD_XO_CLK_SRC] = &sdm660_bi_tcxo, - [RPM_SMD_XO_A_CLK_SRC] = &sdm660_bi_tcxo_a, + [RPM_SMD_XO_CLK_SRC] = &msm8998_bi_tcxo, + [RPM_SMD_XO_A_CLK_SRC] = &msm8998_bi_tcxo_a, [RPM_SMD_SNOC_CLK] = &sm6125_snoc_clk, [RPM_SMD_SNOC_A_CLK] = &sm6125_snoc_a_clk, [RPM_SMD_BIMC_CLK] = &msm8916_bimc_clk, @@ -1017,8 +1031,8 @@ static struct clk_smd_rpm *sm6125_clks[] = { [RPM_SMD_LN_BB_CLK1_A] = &msm8916_bb_clk1_a, [RPM_SMD_LN_BB_CLK2] = &msm8916_bb_clk2, [RPM_SMD_LN_BB_CLK2_A] = &msm8916_bb_clk2_a, - [RPM_SMD_LN_BB_CLK3] = &sdm660_ln_bb_clk3, - [RPM_SMD_LN_BB_CLK3_A] = &sdm660_ln_bb_clk3_a, + [RPM_SMD_LN_BB_CLK3] = &msm8998_ln_bb_clk3, + [RPM_SMD_LN_BB_CLK3_A] = &msm8998_ln_bb_clk3_a, [RPM_SMD_QUP_CLK] = &sm6125_qup_clk, [RPM_SMD_QUP_A_CLK] = &sm6125_qup_a_clk, [RPM_SMD_MMRT_CLK] = &sm6125_mmrt_clk, @@ -1038,8 +1052,8 @@ static const struct rpm_smd_clk_desc rpm_clk_sm6125 = { /* SM6115 */ static struct clk_smd_rpm *sm6115_clks[] = { - [RPM_SMD_XO_CLK_SRC] = &sdm660_bi_tcxo, - [RPM_SMD_XO_A_CLK_SRC] = &sdm660_bi_tcxo_a, + [RPM_SMD_XO_CLK_SRC] = &msm8998_bi_tcxo, + [RPM_SMD_XO_A_CLK_SRC] = &msm8998_bi_tcxo_a, [RPM_SMD_SNOC_CLK] = &sm6125_snoc_clk, [RPM_SMD_SNOC_A_CLK] = &sm6125_snoc_a_clk, [RPM_SMD_BIMC_CLK] = &msm8916_bimc_clk, @@ -1090,8 +1104,8 @@ DEFINE_CLK_SMD_RPM(qcm2290, bimc_gpu_clk, bimc_gpu_a_clk, QCOM_SMD_RPM_MEM_CLK, 2); static struct clk_smd_rpm *qcm2290_clks[] = { - [RPM_SMD_XO_CLK_SRC] = &sdm660_bi_tcxo, - [RPM_SMD_XO_A_CLK_SRC] = &sdm660_bi_tcxo_a, + [RPM_SMD_XO_CLK_SRC] = &msm8998_bi_tcxo, + [RPM_SMD_XO_A_CLK_SRC] = &msm8998_bi_tcxo_a, [RPM_SMD_SNOC_CLK] = &sm6125_snoc_clk, [RPM_SMD_SNOC_A_CLK] = &sm6125_snoc_a_clk, [RPM_SMD_BIMC_CLK] = &msm8916_bimc_clk, From 47babd6872df595f4a3461cd1176c4777fd1e123 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Thu, 10 Dec 2020 17:27:40 +0100 Subject: [PATCH 077/187] drm/msm/dpu1: Add MSM8998 to hw catalog Bringup functionality for MSM8998 in the DPU, driver which is mostly the same as SDM845 (just a few variations). Signed-off-by: AngeloGioacchino Del Regno (JAMI: fixed up for v5.16-rc1) --- .../gpu/drm/msm/disp/dpu1/dpu_hw_catalog.c | 329 +++++++++++++++++- drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c | 1 + 2 files changed, 319 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.c index ce6f32a919e5..4bd7001be2ef 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.c +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.c @@ -16,6 +16,9 @@ BIT(DPU_SSPP_CSC_10BIT) | BIT(DPU_SSPP_CDP) |\ BIT(DPU_SSPP_TS_PREFILL) | BIT(DPU_SSPP_EXCL_RECT)) +#define VIG_MSM8998_MASK \ + (VIG_MASK | BIT(DPU_SSPP_SCALER_QSEED3)) + #define VIG_SDM845_MASK \ (VIG_MASK | BIT(DPU_SSPP_QOS_8LVL) | BIT(DPU_SSPP_SCALER_QSEED3)) @@ -25,6 +28,11 @@ #define VIG_SM8250_MASK \ (VIG_MASK | BIT(DPU_SSPP_QOS_8LVL) | BIT(DPU_SSPP_SCALER_QSEED3LITE)) +#define DMA_MSM8998_MASK \ + (BIT(DPU_SSPP_SRC) | BIT(DPU_SSPP_QOS) |\ + BIT(DPU_SSPP_TS_PREFILL) | BIT(DPU_SSPP_TS_PREFILL_REC1) |\ + BIT(DPU_SSPP_CDP) | BIT(DPU_SSPP_EXCL_RECT)) + #define DMA_SDM845_MASK \ (BIT(DPU_SSPP_SRC) | BIT(DPU_SSPP_QOS) | BIT(DPU_SSPP_QOS_8LVL) |\ BIT(DPU_SSPP_TS_PREFILL) | BIT(DPU_SSPP_TS_PREFILL_REC1) |\ @@ -33,6 +41,9 @@ #define DMA_CURSOR_SDM845_MASK \ (DMA_SDM845_MASK | BIT(DPU_SSPP_CURSOR)) +#define DMA_CURSOR_MSM8998_MASK \ + (DMA_MSM8998_MASK | BIT(DPU_SSPP_CURSOR)) + #define MIXER_SDM845_MASK \ (BIT(DPU_MIXER_SOURCESPLIT) | BIT(DPU_DIM_LAYER)) @@ -49,6 +60,8 @@ #define MERGE_3D_SM8150_MASK (0) +#define DSPP_MSM8998_MASK BIT(DPU_DSPP_PCC) | BIT(DPU_DSPP_GC) + #define DSPP_SC7180_MASK BIT(DPU_DSPP_PCC) #define INTF_SDM845_MASK (0) @@ -181,6 +194,22 @@ static const uint32_t plane_formats_yuv[] = { * DPU sub blocks config *************************************************************/ /* DPU top level caps */ +static const struct dpu_caps msm8998_dpu_caps = { + .max_mixer_width = DEFAULT_DPU_OUTPUT_LINE_WIDTH, + .max_mixer_blendstages = 0x7, + .qseed_type = DPU_SSPP_SCALER_QSEED3, + .smart_dma_rev = DPU_SSPP_SMART_DMA_V1, + .ubwc_version = DPU_HW_UBWC_VER_10, + .has_src_split = true, + .has_dim_layer = true, + .has_idle_pc = true, + .has_3d_merge = true, + .max_linewidth = DEFAULT_DPU_OUTPUT_LINE_WIDTH, + .pixel_ram_size = DEFAULT_PIXEL_RAM_SIZE, + .max_hdeci_exp = MAX_HORZ_DECIMATION, + .max_vdeci_exp = MAX_VERT_DECIMATION, +}; + static const struct dpu_caps sdm845_dpu_caps = { .max_mixer_width = DEFAULT_DPU_OUTPUT_LINE_WIDTH, .max_mixer_blendstages = 0xb, @@ -251,6 +280,35 @@ static const struct dpu_caps sc7280_dpu_caps = { .pixel_ram_size = DEFAULT_PIXEL_RAM_SIZE, }; +static const struct dpu_mdp_cfg msm8998_mdp[] = { + { + .name = "top_0", .id = MDP_TOP, + .base = 0x0, .len = 0x458, + .features = 0, + .highest_bank_bit = 0x2, + .clk_ctrls[DPU_CLK_CTRL_VIG0] = { + .reg_off = 0x2AC, .bit_off = 0}, + .clk_ctrls[DPU_CLK_CTRL_VIG1] = { + .reg_off = 0x2B4, .bit_off = 0}, + .clk_ctrls[DPU_CLK_CTRL_VIG2] = { + .reg_off = 0x2BC, .bit_off = 0}, + .clk_ctrls[DPU_CLK_CTRL_VIG3] = { + .reg_off = 0x2C4, .bit_off = 0}, + .clk_ctrls[DPU_CLK_CTRL_DMA0] = { + .reg_off = 0x2AC, .bit_off = 8}, + .clk_ctrls[DPU_CLK_CTRL_DMA1] = { + .reg_off = 0x2B4, .bit_off = 8}, + .clk_ctrls[DPU_CLK_CTRL_DMA2] = { + .reg_off = 0x2C4, .bit_off = 8}, + .clk_ctrls[DPU_CLK_CTRL_DMA3] = { + .reg_off = 0x2C4, .bit_off = 12}, + .clk_ctrls[DPU_CLK_CTRL_CURSOR0] = { + .reg_off = 0x3A8, .bit_off = 15}, + .clk_ctrls[DPU_CLK_CTRL_CURSOR1] = { + .reg_off = 0x3B0, .bit_off = 15}, + }, +}; + static const struct dpu_mdp_cfg sdm845_mdp[] = { { .name = "top_0", .id = MDP_TOP, @@ -339,6 +397,39 @@ static const struct dpu_mdp_cfg sc7280_mdp[] = { /************************************************************* * CTL sub blocks config *************************************************************/ +static const struct dpu_ctl_cfg msm8998_ctl[] = { + { + .name = "ctl_0", .id = CTL_0, + .base = 0x1000, .len = 0x94, + .features = BIT(DPU_CTL_SPLIT_DISPLAY), + .intr_start = DPU_IRQ_IDX(MDP_SSPP_TOP0_INTR2, 9), + }, + { + .name = "ctl_1", .id = CTL_1, + .base = 0x1200, .len = 0x94, + .features = 0, + .intr_start = DPU_IRQ_IDX(MDP_SSPP_TOP0_INTR2, 10), + }, + { + .name = "ctl_2", .id = CTL_2, + .base = 0x1400, .len = 0x94, + .features = BIT(DPU_CTL_SPLIT_DISPLAY), + .intr_start = DPU_IRQ_IDX(MDP_SSPP_TOP0_INTR2, 11), + }, + { + .name = "ctl_3", .id = CTL_3, + .base = 0x1600, .len = 0x94, + .features = 0, + .intr_start = DPU_IRQ_IDX(MDP_SSPP_TOP0_INTR2, 12), + }, + { + .name = "ctl_4", .id = CTL_4, + .base = 0x1800, .len = 0x94, + .features = 0, + .intr_start = DPU_IRQ_IDX(MDP_SSPP_TOP0_INTR2, 13), + }, +}; + static const struct dpu_ctl_cfg sdm845_ctl[] = { { .name = "ctl_0", .id = CTL_0, @@ -497,6 +588,15 @@ static const struct dpu_ctl_cfg sc7280_ctl[] = { .virt_num_formats = ARRAY_SIZE(plane_formats), \ } +static const struct dpu_sspp_sub_blks msm8998_vig_sblk_0 = + _VIG_SBLK("0", 0, DPU_SSPP_SCALER_QSEED3); +static const struct dpu_sspp_sub_blks msm8998_vig_sblk_1 = + _VIG_SBLK("1", 0, DPU_SSPP_SCALER_QSEED3); +static const struct dpu_sspp_sub_blks msm8998_vig_sblk_2 = + _VIG_SBLK("2", 0, DPU_SSPP_SCALER_QSEED3); +static const struct dpu_sspp_sub_blks msm8998_vig_sblk_3 = + _VIG_SBLK("3", 0, DPU_SSPP_SCALER_QSEED3); + static const struct dpu_sspp_sub_blks sdm845_vig_sblk_0 = _VIG_SBLK("0", 5, DPU_SSPP_SCALER_QSEED3); static const struct dpu_sspp_sub_blks sdm845_vig_sblk_1 = @@ -523,6 +623,25 @@ static const struct dpu_sspp_sub_blks sdm845_dma_sblk_3 = _DMA_SBLK("11", 4); .clk_ctrl = _clkctrl \ } +static const struct dpu_sspp_cfg msm8998_sspp[] = { + SSPP_BLK("sspp_0", SSPP_VIG0, 0x4000, VIG_MSM8998_MASK, + msm8998_vig_sblk_0, 0, SSPP_TYPE_VIG, DPU_CLK_CTRL_VIG0), + SSPP_BLK("sspp_1", SSPP_VIG1, 0x6000, VIG_MSM8998_MASK, + msm8998_vig_sblk_1, 4, SSPP_TYPE_VIG, DPU_CLK_CTRL_VIG1), + SSPP_BLK("sspp_2", SSPP_VIG2, 0x8000, VIG_MSM8998_MASK, + msm8998_vig_sblk_2, 8, SSPP_TYPE_VIG, DPU_CLK_CTRL_VIG2), + SSPP_BLK("sspp_3", SSPP_VIG3, 0xa000, VIG_MSM8998_MASK, + msm8998_vig_sblk_3, 12, SSPP_TYPE_VIG, DPU_CLK_CTRL_VIG3), + SSPP_BLK("sspp_8", SSPP_DMA0, 0x24000, DMA_MSM8998_MASK, + sdm845_dma_sblk_0, 1, SSPP_TYPE_DMA, DPU_CLK_CTRL_DMA0), + SSPP_BLK("sspp_9", SSPP_DMA1, 0x26000, DMA_MSM8998_MASK, + sdm845_dma_sblk_1, 5, SSPP_TYPE_DMA, DPU_CLK_CTRL_DMA1), + SSPP_BLK("sspp_10", SSPP_DMA2, 0x28000, DMA_CURSOR_MSM8998_MASK, + sdm845_dma_sblk_2, 9, SSPP_TYPE_DMA, DPU_CLK_CTRL_CURSOR0), + SSPP_BLK("sspp_11", SSPP_DMA3, 0x2a000, DMA_CURSOR_MSM8998_MASK, + sdm845_dma_sblk_3, 13, SSPP_TYPE_DMA, DPU_CLK_CTRL_CURSOR1), +}; + static const struct dpu_sspp_cfg sdm845_sspp[] = { SSPP_BLK("sspp_0", SSPP_VIG0, 0x4000, VIG_SDM845_MASK, sdm845_vig_sblk_0, 0, SSPP_TYPE_VIG, DPU_CLK_CTRL_VIG0), @@ -599,17 +718,6 @@ static const struct dpu_sspp_cfg sc7280_sspp[] = { * MIXER sub blocks config *************************************************************/ -/* SDM845 */ - -static const struct dpu_lm_sub_blks sdm845_lm_sblk = { - .maxwidth = DEFAULT_DPU_OUTPUT_LINE_WIDTH, - .maxblendstages = 11, /* excluding base layer */ - .blendstage_base = { /* offsets relative to mixer base */ - 0x20, 0x38, 0x50, 0x68, 0x80, 0x98, - 0xb0, 0xc8, 0xe0, 0xf8, 0x110 - }, -}; - #define LM_BLK(_name, _id, _base, _fmask, _sblk, _pp, _lmpair, _dspp) \ { \ .name = _name, .id = _id, \ @@ -621,6 +729,43 @@ static const struct dpu_lm_sub_blks sdm845_lm_sblk = { .dspp = _dspp \ } +/* MSM8998 */ + +static const struct dpu_lm_sub_blks msm8998_lm_sblk = { + .maxwidth = DEFAULT_DPU_OUTPUT_LINE_WIDTH, + .maxblendstages = 7, /* excluding base layer */ + .blendstage_base = { /* offsets relative to mixer base */ + 0x20, 0x50, 0x80, 0xb0, 0x230, + 0x260, 0x290 + }, +}; + +static const struct dpu_lm_cfg msm8998_lm[] = { + LM_BLK("lm_0", LM_0, 0x44000, MIXER_SDM845_MASK, + &msm8998_lm_sblk, PINGPONG_0, LM_2, DSPP_0), + LM_BLK("lm_1", LM_1, 0x45000, MIXER_SDM845_MASK, + &msm8998_lm_sblk, PINGPONG_1, LM_5, DSPP_1), + LM_BLK("lm_2", LM_2, 0x46000, MIXER_SDM845_MASK, + &msm8998_lm_sblk, PINGPONG_2, LM_0, 0), + LM_BLK("lm_3", LM_3, 0x47000, MIXER_SDM845_MASK, + &msm8998_lm_sblk, PINGPONG_MAX, 0, 0), + LM_BLK("lm_4", LM_4, 0x48000, MIXER_SDM845_MASK, + &msm8998_lm_sblk, PINGPONG_MAX, 0, 0), + LM_BLK("lm_5", LM_5, 0x49000, MIXER_SDM845_MASK, + &msm8998_lm_sblk, PINGPONG_3, LM_1, 0), +}; + +/* SDM845 */ + +static const struct dpu_lm_sub_blks sdm845_lm_sblk = { + .maxwidth = DEFAULT_DPU_OUTPUT_LINE_WIDTH, + .maxblendstages = 11, /* excluding base layer */ + .blendstage_base = { /* offsets relative to mixer base */ + 0x20, 0x38, 0x50, 0x68, 0x80, 0x98, + 0xb0, 0xc8, 0xe0, 0xf8, 0x110 + }, +}; + static const struct dpu_lm_cfg sdm845_lm[] = { LM_BLK("lm_0", LM_0, 0x44000, MIXER_SDM845_MASK, &sdm845_lm_sblk, PINGPONG_0, LM_1, 0), @@ -682,6 +827,13 @@ static const struct dpu_lm_cfg sc7280_lm[] = { /************************************************************* * DSPP sub blocks config *************************************************************/ +static const struct dpu_dspp_sub_blks msm8998_dspp_sblk = { + .pcc = {.id = DPU_DSPP_PCC, .base = 0x1700, + .len = 0x90, .version = 0x10007}, + .gc = { .id = DPU_DSPP_GC, .base = 0x17c0, + .len = 0x90, .version = 0x10007}, +}; + static const struct dpu_dspp_sub_blks sc7180_dspp_sblk = { .pcc = {.id = DPU_DSPP_PCC, .base = 0x1700, .len = 0x90, .version = 0x10000}, @@ -700,6 +852,13 @@ static const struct dpu_dspp_sub_blks sm8150_dspp_sblk = { .sblk = _sblk \ } +static const struct dpu_dspp_cfg msm8998_dspp[] = { + DSPP_BLK("dspp_0", DSPP_0, 0x54000, DSPP_MSM8998_MASK, + &msm8998_dspp_sblk), + DSPP_BLK("dspp_1", DSPP_1, 0x56000, DSPP_MSM8998_MASK, + &msm8998_dspp_sblk), +}; + static const struct dpu_dspp_cfg sc7180_dspp[] = { DSPP_BLK("dspp_0", DSPP_0, 0x54000, DSPP_SC7180_MASK, &sc7180_dspp_sblk), @@ -836,6 +995,13 @@ static const struct dpu_pingpong_cfg sc7280_pp[] = { .intr_vsync = DPU_IRQ_IDX(_reg, _vsync_bit), \ } +static const struct dpu_intf_cfg msm8998_intf[] = { + INTF_BLK("intf_0", INTF_0, 0x6A000, INTF_DP, 0, 25, INTF_SDM845_MASK, MDP_SSPP_TOP0_INTR, 24, 25), + INTF_BLK("intf_1", INTF_1, 0x6A800, INTF_DSI, 0, 25, INTF_SDM845_MASK, MDP_SSPP_TOP0_INTR, 26, 27), + INTF_BLK("intf_2", INTF_2, 0x6B000, INTF_DSI, 1, 25, INTF_SDM845_MASK, MDP_SSPP_TOP0_INTR, 28, 29), + INTF_BLK("intf_3", INTF_3, 0x6B800, INTF_HDMI, 0, 25, INTF_SDM845_MASK, MDP_SSPP_TOP0_INTR, 30, 31), +}; + static const struct dpu_intf_cfg sdm845_intf[] = { INTF_BLK("intf_0", INTF_0, 0x6A000, INTF_DP, 0, 24, INTF_SDM845_MASK, MDP_SSPP_TOP0_INTR, 24, 25), INTF_BLK("intf_1", INTF_1, 0x6A800, INTF_DSI, 0, 24, INTF_SDM845_MASK, MDP_SSPP_TOP0_INTR, 26, 27), @@ -865,9 +1031,55 @@ static const struct dpu_intf_cfg sc7280_intf[] = { * VBIF sub blocks config *************************************************************/ /* VBIF QOS remap */ +static const u32 msm8998_rt_pri_lvl[] = {1, 2, 2, 2}; +static const u32 msm8998_nrt_pri_lvl[] = {1, 1, 1, 1}; static const u32 sdm845_rt_pri_lvl[] = {3, 3, 4, 4, 5, 5, 6, 6}; static const u32 sdm845_nrt_pri_lvl[] = {3, 3, 3, 3, 3, 3, 3, 3}; +static const struct dpu_vbif_dynamic_ot_cfg msm8998_ot_rdwr_cfg[] = { + { + .pps = 1088 * 1920 * 30, + .ot_limit = 2, + }, + { + .pps = 1088 * 1920 * 60, + .ot_limit = 6, + }, + { + .pps = 3840 * 2160 * 30, + .ot_limit = 16, + }, +}; + +static const struct dpu_vbif_cfg msm8998_vbif[] = { + { + .name = "vbif_0", .id = VBIF_0, + .base = 0, .len = 0x1040, + .default_ot_rd_limit = 32, + .default_ot_wr_limit = 32, + .features = BIT(DPU_VBIF_QOS_REMAP) | BIT(DPU_VBIF_QOS_OTLIM), + .xin_halt_timeout = 0x4000, + .dynamic_ot_rd_tbl = { + .count = ARRAY_SIZE(msm8998_ot_rdwr_cfg), + .cfg = msm8998_ot_rdwr_cfg, + }, + .dynamic_ot_wr_tbl = { + .count = ARRAY_SIZE(msm8998_ot_rdwr_cfg), + .cfg = msm8998_ot_rdwr_cfg, + }, + .qos_rt_tbl = { + .npriority_lvl = ARRAY_SIZE(msm8998_rt_pri_lvl), + .priority_lvl = msm8998_rt_pri_lvl, + }, + .qos_nrt_tbl = { + .npriority_lvl = ARRAY_SIZE(msm8998_nrt_pri_lvl), + .priority_lvl = msm8998_nrt_pri_lvl, + }, + .memtype_count = 14, + .memtype = {2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}, + }, +}; + static const struct dpu_vbif_cfg sdm845_vbif[] = { { .name = "vbif_0", .id = VBIF_0, @@ -908,6 +1120,22 @@ static const struct dpu_reg_dma_cfg sm8250_regdma = { *************************************************************/ /* SSPP QOS LUTs */ +static const struct dpu_qos_lut_entry msm8998_qos_linear[] = { + {.fl = 4, .lut = 0x1b}, + {.fl = 5, .lut = 0x5b}, + {.fl = 6, .lut = 0x15b}, + {.fl = 7, .lut = 0x55b}, + {.fl = 8, .lut = 0x155b}, + {.fl = 9, .lut = 0x555b}, + {.fl = 10, .lut = 0x1555b}, + {.fl = 11, .lut = 0x5555b}, + {.fl = 12, .lut = 0x15555b}, + {.fl = 13, .lut = 0x55555b}, + {.fl = 14, .lut = 0}, + {.fl = 1, .lut = 0x1b}, + {.fl = 0, .lut = 0} +}; + static const struct dpu_qos_lut_entry sdm845_qos_linear[] = { {.fl = 4, .lut = 0x357}, {.fl = 5, .lut = 0x3357}, @@ -923,6 +1151,15 @@ static const struct dpu_qos_lut_entry sdm845_qos_linear[] = { {.fl = 0, .lut = 0x11222222223357} }; +static const struct dpu_qos_lut_entry msm8998_qos_macrotile[] = { + {.fl = 10, .lut = 0x1aaff}, + {.fl = 11, .lut = 0x5aaff}, + {.fl = 12, .lut = 0x15aaff}, + {.fl = 13, .lut = 0x55aaff}, + {.fl = 1, .lut = 0x1aaff}, + {.fl = 0, .lut = 0}, +}; + static const struct dpu_qos_lut_entry sc7180_qos_linear[] = { {.fl = 0, .lut = 0x0011222222335777}, }; @@ -944,6 +1181,10 @@ static const struct dpu_qos_lut_entry sc7180_qos_macrotile[] = { {.fl = 0, .lut = 0x0011223344556677}, }; +static const struct dpu_qos_lut_entry msm8998_qos_nrt[] = { + {.fl = 0, .lut = 0x0}, +}; + static const struct dpu_qos_lut_entry sdm845_qos_nrt[] = { {.fl = 0, .lut = 0x0}, }; @@ -952,6 +1193,42 @@ static const struct dpu_qos_lut_entry sc7180_qos_nrt[] = { {.fl = 0, .lut = 0x0}, }; +static const struct dpu_perf_cfg msm8998_perf_data = { + .max_bw_low = 6700000, + .max_bw_high = 6700000, + .min_core_ib = 2400000, + .min_llcc_ib = 800000, + .min_dram_ib = 800000, + .undersized_prefill_lines = 2, + .xtra_prefill_lines = 2, + .dest_scale_prefill_lines = 3, + .macrotile_prefill_lines = 4, + .yuv_nv12_prefill_lines = 8, + .linear_prefill_lines = 1, + .downscaling_prefill_lines = 1, + .amortizable_threshold = 25, + .min_prefill_lines = 25, + .danger_lut_tbl = {0xf, 0xffff, 0x0}, + .safe_lut_tbl = {0xfffc, 0xff00, 0xffff}, + .qos_lut_tbl = { + {.nentry = ARRAY_SIZE(msm8998_qos_linear), + .entries = msm8998_qos_linear + }, + {.nentry = ARRAY_SIZE(msm8998_qos_macrotile), + .entries = msm8998_qos_macrotile + }, + {.nentry = ARRAY_SIZE(msm8998_qos_nrt), + .entries = msm8998_qos_nrt + }, + }, + .cdp_cfg = { + {.rd_enable = 1, .wr_enable = 1}, + {.rd_enable = 1, .wr_enable = 0} + }, + .clk_inefficiency_factor = 200, + .bw_inefficiency_factor = 120, +}; + static const struct dpu_perf_cfg sdm845_perf_data = { .max_bw_low = 6800000, .max_bw_high = 6800000, @@ -1106,6 +1383,34 @@ static const struct dpu_perf_cfg sc7280_perf_data = { * Hardware catalog init *************************************************************/ +/* + * msm8998_cfg_init(): populate sdm845 dpu sub-blocks reg offsets + * and instance counts. + */ +static void msm8998_cfg_init(struct dpu_mdss_cfg *dpu_cfg) +{ + *dpu_cfg = (struct dpu_mdss_cfg){ + .caps = &msm8998_dpu_caps, + .mdp_count = ARRAY_SIZE(msm8998_mdp), + .mdp = msm8998_mdp, + .ctl_count = ARRAY_SIZE(msm8998_ctl), + .ctl = msm8998_ctl, + .sspp_count = ARRAY_SIZE(msm8998_sspp), + .sspp = msm8998_sspp, + .mixer_count = ARRAY_SIZE(msm8998_lm), + .mixer = msm8998_lm, + .pingpong_count = ARRAY_SIZE(sdm845_pp), + .pingpong = sdm845_pp, + .intf_count = ARRAY_SIZE(msm8998_intf), + .intf = msm8998_intf, + .vbif_count = ARRAY_SIZE(msm8998_vbif), + .vbif = msm8998_vbif, + .reg_dma_count = 0, + .perf = msm8998_perf_data, + .mdss_irqs = IRQ_SM8250_MASK, + }; +} + /* * sdm845_cfg_init(): populate sdm845 dpu sub-blocks reg offsets * and instance counts. @@ -1256,6 +1561,8 @@ static void sc7280_cfg_init(struct dpu_mdss_cfg *dpu_cfg) } static const struct dpu_mdss_hw_cfg_handler cfg_handler[] = { + { .hw_rev = DPU_HW_VER_300, .cfg_init = msm8998_cfg_init}, + { .hw_rev = DPU_HW_VER_301, .cfg_init = msm8998_cfg_init}, { .hw_rev = DPU_HW_VER_400, .cfg_init = sdm845_cfg_init}, { .hw_rev = DPU_HW_VER_401, .cfg_init = sdm845_cfg_init}, { .hw_rev = DPU_HW_VER_500, .cfg_init = sm8150_cfg_init}, diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c index a15b26428280..fbbb28e0def7 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c @@ -1286,6 +1286,7 @@ static const struct dev_pm_ops dpu_pm_ops = { }; static const struct of_device_id dpu_dt_match[] = { + { .compatible = "qcom,msm8998-dpu", }, { .compatible = "qcom,sdm845-dpu", }, { .compatible = "qcom,sc7180-dpu", }, { .compatible = "qcom,sc7280-dpu", }, From 68fb74869b5c56b98838ba2e798ef320ccdc685d Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Tue, 13 Oct 2020 18:58:43 +0200 Subject: [PATCH 078/187] media: i2c: Add driver for the Sony Exmor-RS IMX300 camera sensor This is a custom multi-aspect 25MegaPixels sensor from Sony, found in many Sony Xperia smartphones from various eras. The camera assembly for this sensor usually (at least in Xperia phones) has a lens that does not cover the entire sensor area, which means that the real corners are blind and that, in many lighting conditions, some more pixels in the corners are very getting obscured (as no decent amount of light can get in)... so, the maximum resolution that can produce a good image is: - In 4:3 aspect ratio, 5520x4160 (23.0MP) - In 16:9 aspect ratio, 5984x3392 (20.3MP). This sensor supports high frame rates (>=60FPS) when in binning mode and both RAW8 and RAW10 output modes. In this version of the driver, support has been provided for the following resolutions: W x H SZ MAX_FPS BINNING - 5520x4160 23.0MP 23 No - 5984x3392 20.3MP 26 No - 2992x1696 3.8MP 60 Yes - 1424x800 1.2MP 120 Yes Note 1: The "standard" camera assy for IMX300 also contains an actuator (to focus the image), but this driver only manages the actual image sensor. Note 2: The command tables for this sensor were reverse engineered from a downstream "userspace driver" that has been released in various versions on various Xperia smartphones. Register layout seems to be only vaguely similar to IMX219, which has a public datasheet from where some names for the figured out registers were taken and added to the driver: these names are probably not the right ones, but they surely represent the intended thing. Signed-off-by: AngeloGioacchino Del Regno --- drivers/media/i2c/Kconfig | 13 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/imx300.c | 3084 ++++++++++++++++++++++++++++++++++++ 3 files changed, 3098 insertions(+) create mode 100644 drivers/media/i2c/imx300.c diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index d6a5d4ca439a..5323bc911321 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -832,6 +832,19 @@ config VIDEO_IMX290 To compile this driver as a module, choose M here: the module will be called imx290. +config VIDEO_IMX300 + tristate "Sony IMX300 Exmor RS sensor support" + depends on I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + This is a Video4Linux2 sensor driver for the Sony + IMX300 Exmor RS multi-aspect sensor. + + To compile this driver as a module, choose M here: the + module will be called imx300. + config VIDEO_IMX319 tristate "Sony IMX319 sensor support" depends on I2C && VIDEO_V4L2 diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 4d4fe08d7a6a..ab6eb4c2173a 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -125,6 +125,7 @@ obj-$(CONFIG_VIDEO_IMX219) += imx219.o obj-$(CONFIG_VIDEO_IMX258) += imx258.o obj-$(CONFIG_VIDEO_IMX274) += imx274.o obj-$(CONFIG_VIDEO_IMX290) += imx290.o +obj-$(CONFIG_VIDEO_IMX300) += imx300.o obj-$(CONFIG_VIDEO_IMX319) += imx319.o obj-$(CONFIG_VIDEO_IMX334) += imx334.o obj-$(CONFIG_VIDEO_IMX335) += imx335.o diff --git a/drivers/media/i2c/imx300.c b/drivers/media/i2c/imx300.c new file mode 100644 index 000000000000..e9eba755b063 --- /dev/null +++ b/drivers/media/i2c/imx300.c @@ -0,0 +1,3084 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A V4L2 driver for Sony IMX300 Exmor RS multi-aspect image sensors. + * Copyright (C) 2020, AngeloGioacchino Del Regno + * + * + * Based on Sony imx219 camera driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define IMX300_REG_VALUE_08BIT 1 +#define IMX300_REG_VALUE_16BIT 2 + +/* + * Supported external clock frequency is from (around) 6 to 26MHz + * but there is no information about how to configure this sensor + * for anything else but 24MHz, since there is no datasheet... + */ +#define IMX300_XCLK_FREQ_24M 24000000 + +/* Delay after XCLK/RESET during power up for sensor boot/stabilization */ +#define IMX300_XCLK_STABLE_DELAY_US 10000 +#define IMX300_XCLK_DELAY_RANGE_US 1000 +#define IMX300_XCLR_MIN_DELAY_US 25000 +#define IMX300_XCLR_DELAY_RANGE_US 1000 + +/* + * Pixel rates: max resolution + max FPS uses high bw; low resolution + * can use low bw in order to save power and limit sensor heating + */ +#define IMX300_HIGH_BW_PIXEL_RATE 624000000 +#define IMX300_LOW_BW_PIXEL_RATE 384000000 +#define IMX300_HIGH_BW_LINK_FREQ 780000000 +#define IMX300_LOW_BW_LINK_FREQ 480000000 + +/* + * About the Chip ID: + * + * IMX300 seems to be sort of flawed... scanning the registers reveals + * that there's no reg having the expected 0x300 ChipID, like literally + * all of the other Sony IMX sensors. + * There seem to be no meaningful values and, even during reverse + * engineering of three "userspace drivers" for this sensor, found on + * three different series of smartphones, there is no trace of any ChipID. + * These userspace drivers seem to be reading the Sensor ID at register + * 0x0016 and comparing it to zero: it looks like being a dummy and not + * reliable at all, since I imagine that *many* camera sensors may contain + * zero at that register address, but that's still better than nothing... + * + * After all, if you don't have this camera sensor attached to your board, + * you shouldn't be adding it in your DT... :( + */ +#define IMX300_REG_CHIP_ID 0x0016 +#define IMX300_CHIP_ID 0x0 + +#define IMX300_REG_MODE_SELECT 0x0100 +#define IMX300_MODE_STANDBY 0x00 +#define IMX300_MODE_STREAMING 0x01 + +/* Orientation: changes bayer order */ +#define IMX300_REG_ORIENTATION 0x0101 + +/* SW Reset: set to 1 to reset. Gets automatically set back to 0. */ +#define IMX300_REG_SW_RESET 0x0103 + +/* Data format */ +#define IMX300_REG_CSI_DATA_FORMAT_HI 0x0112 +#define IMX300_REG_CSI_DATA_FORMAT_LO 0x0113 +#define IMX300_CSI_DATA_FMT_8BIT 8 +#define IMX300_CSI_DATA_FMT_10BIT 10 + +/* Output Set-up */ +#define IMX300_REG_CSI_LANE_MODE 0x0114 +#define IMX300_CSI_LANE_MODE_2LANE BIT(0) +#define IMX300_CSI_LANE_MODE_4LANE (BIT(0) | BIT(1)) + +/* EXCK: Min 6MHz, Max 27MHz */ +#define IMX300_REG_EXCK_FREQ_MHZ 0x0136 +#define IMX300_REG_TEMP_SENSOR 0x0138 + +/* Exposure control */ +#define IMX300_REG_EXPOSURE 0x0202 +#define IMX300_EXPOSURE_MIN 4 +#define IMX300_EXPOSURE_STEP 1 +#define IMX300_EXPOSURE_DEFAULT 3556 +#define IMX300_EXPOSURE_MAX 65535 + +/* Analog gain control */ +#define IMX300_REG_ANALOG_GAIN 0x0204 +#define IMX300_ANA_GAIN_MIN 0 +#define IMX300_ANA_GAIN_MAX 0x1fff +#define IMX300_ANA_GAIN_STEP 1 +#define IMX300_ANA_GAIN_DEFAULT 0x0 + +/* Digital gain control */ +#define IMX300_REG_GR_DIGITAL_GAIN 0x020e +#define IMX300_REG_R_DIGITAL_GAIN 0x0210 +#define IMX300_REG_B_DIGITAL_GAIN 0x0212 +#define IMX300_REG_GB_DIGITAL_GAIN 0x0214 +#define IMX300_DGTL_GAIN_MIN 0 +#define IMX300_DGTL_GAIN_MAX 4095 +#define IMX300_DGTL_GAIN_DEFAULT 1024 +#define IMX300_DGTL_GAIN_STEP 1 + +/* Clock generator */ +#define IMX300_REG_OP_PIX_CLK_DIV 0x0309 +#define IMX300_REG_OP_SYS_CLK_DIV 0x030b + +/* V_TIMING internal */ +#define IMX300_REG_VTS 0x0340 +#define IMX300_VTS_MIN 41 +#define IMX300_VTS_MAX (131071 - IMX300_VTS_MIN) + +/* Line Length */ +#define IMX300_REG_LINE_LEN_PCK 0x0342 +#define IMX300_LINELEN_MIN 7000 +#define IMX300_LINELEN_MAX 8230 +#define IMX300_LINELEN_STEP 1 +#define IMX300_LINELEN_DEFAULT 7096 + +/* Output configuration */ +#define IMX300_REG_X_ADDR_START 0x0344 +#define IMX300_REG_Y_ADDR_START 0x0346 +#define IMX300_REG_X_ADDR_END 0x0348 +#define IMX300_REG_Y_ADDR_END 0x034a +#define IMX300_REG_X_OUTPUT_SIZE 0x034c +#define IMX300_REG_Y_OUTPUT_SIZE 0x034e +#define IMX300_REG_X_ODD_INC 0x0383 +#define IMX300_REG_Y_ODD_INC 0x0387 + +/* HBLANK control - read only */ +#define IMX300_PPL_DEFAULT 8224 + +/* Test Pattern Control */ +#define IMX300_REG_TEST_PATTERN 0x0600 +#define IMX300_REG_TEST_PATTERN_WIDTH 0x0624 +#define IMX300_REG_TEST_PATTERN_HEIGHT 0x0626 +#define IMX300_TEST_PATTERN_DISABLE 0 +#define IMX300_TEST_PATTERN_SOLID_COLOR 1 +#define IMX300_TEST_PATTERN_COLOR_BARS 2 +#define IMX300_TEST_PATTERN_GREY_COLOR 3 +#define IMX300_TEST_PATTERN_PN9 4 + +/* Test pattern colour components */ +#define IMX300_REG_TESTP_RED 0x0602 +#define IMX300_REG_TESTP_GREENR 0x0604 +#define IMX300_REG_TESTP_BLUE 0x0606 +#define IMX300_REG_TESTP_GREENB 0x0608 +#define IMX300_TESTP_COLOUR_MIN 0 +#define IMX300_TESTP_COLOUR_MAX 0x03ff +#define IMX300_TESTP_COLOUR_STEP 1 +#define IMX300_TESTP_RED_DEFAULT IMX300_TESTP_COLOUR_MAX +#define IMX300_TESTP_GREENR_DEFAULT 0 +#define IMX300_TESTP_BLUE_DEFAULT 0 +#define IMX300_TESTP_GREENB_DEFAULT 0 + +/* Binning */ +#define IMX300_REG_BINNING_EN 0x0900 +#define IMX300_REG_BINNING_TYPE 0x0902 + +/* + * ** IMX300 native and active pixel array size ** + * + * Being this a multi-aspect sensor, the following native W/H apply, but + * beware: the module assembly usually has a (round) lens that is shadowing + * or covering the corners of the (square) image sensor, so the maximum + * output resolution must be lower than the maximum sensor resolution + * otherwise we get something like a view from a porthole... :) + * + * For 4:3 aspect ratio, max is: 5984x4140 (25MP) + * For 16:9 aspect ratio, max is: 5984x3392 (20.3MP) + */ +#define IMX300_NATIVE_WIDTH 5984U +#define IMX300_NATIVE_HEIGHT 4140U +#define IMX300_PIXEL_ARRAY_LEFT 0U +#define IMX300_PIXEL_ARRAY_TOP 0U +#define IMX300_PIXEL_ARRAY_WIDTH 5984U +#define IMX300_PIXEL_ARRAY_HEIGHT 4140U + +struct imx300_reg { + u16 address; + u16 val; + u8 reg_len; +}; + +struct imx300_reg_list { + unsigned int num_of_regs; + const struct imx300_reg *regs; +}; + +/* Mode : resolution and related config&values */ +struct imx300_mode { + /* Frame width */ + unsigned int width; + /* Frame height */ + unsigned int height; + /* Maximum achievable FPS */ + unsigned int max_fps; + /* Needs high data rate */ + bool high_bw; + /* Needs binning setup */ + bool binned; + + /* Analog crop rectangle. */ + struct v4l2_rect crop; + + /* V-timing default */ + unsigned int vts_def; + + /* Default register values */ + struct imx300_reg_list reg_list; +}; + +/* + * I have no idea what this very long initialization sequence is for... + * but missing writes in this makes the sensor to output corrupted + * frames or nothing at all... + */ +static const struct imx300_reg init_sequence[] = { + { IMX300_REG_EXCK_FREQ_MHZ, 24, IMX300_REG_VALUE_08BIT }, + { 0x0137, 0x00, IMX300_REG_VALUE_08BIT }, + { IMX300_REG_ORIENTATION, 0x03, IMX300_REG_VALUE_08BIT }, + { IMX300_REG_TEMP_SENSOR, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x3154, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x3155, 0x1a, IMX300_REG_VALUE_08BIT }, + { 0x3156, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x3157, 0x66, IMX300_REG_VALUE_08BIT }, + { 0x305d, 0x03, IMX300_REG_VALUE_08BIT }, + { 0x31b0, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x3208, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x3210, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x406c, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x406d, 0x39, IMX300_REG_VALUE_08BIT }, + { 0x430d, 0xaa, IMX300_REG_VALUE_08BIT }, + { 0x4313, 0x20, IMX300_REG_VALUE_08BIT }, + { 0x431e, 0x88, IMX300_REG_VALUE_08BIT }, + { 0x431f, 0x8f, IMX300_REG_VALUE_08BIT }, + { 0x4321, 0x20, IMX300_REG_VALUE_08BIT }, + { 0x4324, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x4325, 0x05, IMX300_REG_VALUE_08BIT }, + { 0x4326, 0x28, IMX300_REG_VALUE_08BIT }, + { 0x4328, 0x0d, IMX300_REG_VALUE_08BIT }, + { 0x4329, 0x5b, IMX300_REG_VALUE_08BIT }, + { 0x432c, 0x06, IMX300_REG_VALUE_08BIT }, + { 0x432d, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x432e, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x4554, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x4811, 0x99, IMX300_REG_VALUE_08BIT }, + { 0x4819, 0x99, IMX300_REG_VALUE_08BIT }, + { 0x481d, 0x9d, IMX300_REG_VALUE_08BIT }, + { 0x481f, 0x99, IMX300_REG_VALUE_08BIT }, + { 0x4838, 0x03, IMX300_REG_VALUE_08BIT }, + { 0x4839, 0xff, IMX300_REG_VALUE_08BIT }, + { 0x483a, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x483b, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x4871, 0x99, IMX300_REG_VALUE_08BIT }, + { 0x4877, 0x9b, IMX300_REG_VALUE_08BIT }, + { 0x487d, 0x99, IMX300_REG_VALUE_08BIT }, + { 0x4883, 0x99, IMX300_REG_VALUE_08BIT }, + { 0x4891, 0x99, IMX300_REG_VALUE_08BIT }, + { 0x4893, 0x9d, IMX300_REG_VALUE_08BIT }, + { 0x48ad, 0x9d, IMX300_REG_VALUE_08BIT }, + { 0x48af, 0x99, IMX300_REG_VALUE_08BIT }, + { 0x48b1, 0x99, IMX300_REG_VALUE_08BIT }, + { 0x48b5, 0x99, IMX300_REG_VALUE_08BIT }, + { 0x48bb, 0x9b, IMX300_REG_VALUE_08BIT }, + { 0x48c1, 0x99, IMX300_REG_VALUE_08BIT }, + { 0x48c7, 0x9b, IMX300_REG_VALUE_08BIT }, + { 0x48c9, 0x95, IMX300_REG_VALUE_08BIT }, + { 0x48cb, 0x94, IMX300_REG_VALUE_08BIT }, + { 0x4bd7, 0x24, IMX300_REG_VALUE_08BIT }, + { 0x48dd, 0x95, IMX300_REG_VALUE_08BIT }, + { 0x48e5, 0x97, IMX300_REG_VALUE_08BIT }, + { 0x49d1, 0x9a, IMX300_REG_VALUE_08BIT }, + { 0x4a00, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x4a01, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x4a02, 0x03, IMX300_REG_VALUE_08BIT }, + { 0x4a03, 0xff, IMX300_REG_VALUE_08BIT }, + { 0x4a1d, 0x99, IMX300_REG_VALUE_08BIT }, + { 0x4a1f, 0x9b, IMX300_REG_VALUE_08BIT }, + { 0x4a27, 0x97, IMX300_REG_VALUE_08BIT }, + { 0x4a29, 0x12, IMX300_REG_VALUE_08BIT }, + { 0x4a2f, 0x97, IMX300_REG_VALUE_08BIT }, + { 0x4a4d, 0x9d, IMX300_REG_VALUE_08BIT }, + { 0x4a4f, 0x99, IMX300_REG_VALUE_08BIT }, + { 0x4a51, 0x9d, IMX300_REG_VALUE_08BIT }, + { 0x4a53, 0x99, IMX300_REG_VALUE_08BIT }, + { 0x4a6d, 0x9e, IMX300_REG_VALUE_08BIT }, + { 0x4a6f, 0x9d, IMX300_REG_VALUE_08BIT }, + { 0x4be7, 0x24, IMX300_REG_VALUE_08BIT }, + { 0x4c07, 0x24, IMX300_REG_VALUE_08BIT }, + { 0x4c17, 0x24, IMX300_REG_VALUE_08BIT }, + { 0x4c97, 0x18, IMX300_REG_VALUE_08BIT }, + { 0x4ca7, 0x18, IMX300_REG_VALUE_08BIT }, + { 0x4cc7, 0x18, IMX300_REG_VALUE_08BIT }, + { 0x4cd7, 0x18, IMX300_REG_VALUE_08BIT }, + { 0x4d57, 0x12, IMX300_REG_VALUE_08BIT }, + { 0x4d67, 0x12, IMX300_REG_VALUE_08BIT }, + { 0x4d87, 0x12, IMX300_REG_VALUE_08BIT }, + { 0x4d97, 0x12, IMX300_REG_VALUE_08BIT }, + { 0x4db7, 0x10, IMX300_REG_VALUE_08BIT }, + { 0x4dc7, 0x10, IMX300_REG_VALUE_08BIT }, + { 0x4de7, 0x10, IMX300_REG_VALUE_08BIT }, + { 0x4df7, 0x10, IMX300_REG_VALUE_08BIT }, + { 0x4e15, 0x43, IMX300_REG_VALUE_08BIT }, + { 0x4e25, 0x43, IMX300_REG_VALUE_08BIT }, + { 0x4e45, 0x43, IMX300_REG_VALUE_08BIT }, + { 0x4e55, 0x43, IMX300_REG_VALUE_08BIT }, + { 0x4f40, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x4f42, 0x04, IMX300_REG_VALUE_08BIT }, + { 0x4f48, 0x04, IMX300_REG_VALUE_08BIT }, + { 0x4f4e, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x505c, 0x35, IMX300_REG_VALUE_08BIT }, + { 0x505d, 0x36, IMX300_REG_VALUE_08BIT }, + { 0x505f, 0x35, IMX300_REG_VALUE_08BIT }, + { 0x5069, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x5e6a, 0xfe, IMX300_REG_VALUE_08BIT }, + { 0x5e70, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x6153, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x6156, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x656a, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x656b, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x7300, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9006, 0x09, IMX300_REG_VALUE_08BIT }, + { 0xb000, 0x6e, IMX300_REG_VALUE_08BIT }, + { 0xb001, 0xfd, IMX300_REG_VALUE_08BIT }, + { 0xb002, 0x65, IMX300_REG_VALUE_08BIT }, + { 0xb003, 0x2a, IMX300_REG_VALUE_08BIT }, + { 0xb004, 0x83, IMX300_REG_VALUE_08BIT }, + { 0xb005, 0x41, IMX300_REG_VALUE_08BIT }, + { 0xb006, 0x83, IMX300_REG_VALUE_08BIT }, + { 0xb007, 0x43, IMX300_REG_VALUE_08BIT }, + { 0xb008, 0x82, IMX300_REG_VALUE_08BIT }, + { 0xb009, 0x49, IMX300_REG_VALUE_08BIT }, + { 0xb00a, 0x82, IMX300_REG_VALUE_08BIT }, + { 0xb00b, 0x4a, IMX300_REG_VALUE_08BIT }, + { 0xb00c, 0x6e, IMX300_REG_VALUE_08BIT }, + { 0xb00d, 0x93, IMX300_REG_VALUE_08BIT }, + { 0xb00e, 0x82, IMX300_REG_VALUE_08BIT }, + { 0xb00f, 0x41, IMX300_REG_VALUE_08BIT }, + { 0xb010, 0x82, IMX300_REG_VALUE_08BIT }, + { 0xb011, 0x4b, IMX300_REG_VALUE_08BIT }, + { 0xb100, 0x35, IMX300_REG_VALUE_08BIT }, + { 0xb101, 0x5e, IMX300_REG_VALUE_08BIT }, + { 0xb102, 0x80, IMX300_REG_VALUE_08BIT }, + { 0xb103, 0x9a, IMX300_REG_VALUE_08BIT }, + { 0xb104, 0xaf, IMX300_REG_VALUE_08BIT }, + { 0xb105, 0xc0, IMX300_REG_VALUE_08BIT }, + { 0xb106, 0xcd, IMX300_REG_VALUE_08BIT }, + { 0xb107, 0xd7, IMX300_REG_VALUE_08BIT }, + { 0xb123, 0x55, IMX300_REG_VALUE_08BIT }, + { 0xb125, 0x55, IMX300_REG_VALUE_08BIT }, + { 0xb127, 0x55, IMX300_REG_VALUE_08BIT }, + { 0xb129, 0x55, IMX300_REG_VALUE_08BIT }, + { 0xb12b, 0x55, IMX300_REG_VALUE_08BIT }, + { 0xb136, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0xb137, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0xb138, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0xb139, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0xb13a, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0xb13b, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0xb13c, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0xb13d, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0xb13e, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0xb13f, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0xb140, 0x04, IMX300_REG_VALUE_08BIT }, + { 0xb141, 0x04, IMX300_REG_VALUE_08BIT }, + { 0xb142, 0x04, IMX300_REG_VALUE_08BIT }, + { 0xb143, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0xb144, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0xb145, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0xb146, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0xb147, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0xb148, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0xb149, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0xb14a, 0x05, IMX300_REG_VALUE_08BIT }, + { 0xb14b, 0x05, IMX300_REG_VALUE_08BIT }, + { 0xb14c, 0x05, IMX300_REG_VALUE_08BIT }, + { 0xb14d, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0xb14e, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0xb14f, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0xb150, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0xb151, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0xb152, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0xb153, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0xb154, 0x03, IMX300_REG_VALUE_08BIT }, + { 0xb155, 0x03, IMX300_REG_VALUE_08BIT }, + { 0xb156, 0x03, IMX300_REG_VALUE_08BIT }, + { 0xb157, 0x05, IMX300_REG_VALUE_08BIT }, + { 0xb158, 0x05, IMX300_REG_VALUE_08BIT }, + { 0xb159, 0x05, IMX300_REG_VALUE_08BIT }, + { 0xb15a, 0x05, IMX300_REG_VALUE_08BIT }, + { 0xb15b, 0x05, IMX300_REG_VALUE_08BIT }, + { 0xb15c, 0x05, IMX300_REG_VALUE_08BIT }, + { 0xb15d, 0x05, IMX300_REG_VALUE_08BIT }, + { 0xb210, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x313c, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3198, 0x0f, IMX300_REG_VALUE_08BIT }, + { 0x31a0, 0x02, IMX300_REG_VALUE_08BIT }, + { 0x31a1, 0x02, IMX300_REG_VALUE_08BIT }, + { 0x31a2, 0x02, IMX300_REG_VALUE_08BIT }, + { 0x31a3, 0x03, IMX300_REG_VALUE_08BIT }, + { 0x31a8, 0x18, IMX300_REG_VALUE_08BIT }, + { 0x3290, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x6d20, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x6d21, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x6d22, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x6d23, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x6d26, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x6d27, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x6e07, 0x10, IMX300_REG_VALUE_08BIT }, + { 0x6e35, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x6e55, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x6e7c, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x6e93, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x6efb, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x7c68, 0x03, IMX300_REG_VALUE_08BIT }, + { 0x845c, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x8469, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x9257, 0x96, IMX300_REG_VALUE_08BIT }, + { 0x9258, 0x03, IMX300_REG_VALUE_08BIT }, + { 0x933a, 0x02, IMX300_REG_VALUE_08BIT }, + { 0x933b, 0x02, IMX300_REG_VALUE_08BIT }, + { 0x933d, 0x05, IMX300_REG_VALUE_08BIT }, + { 0x933e, 0x05, IMX300_REG_VALUE_08BIT }, + { 0x933f, 0x05, IMX300_REG_VALUE_08BIT }, + { 0x934b, 0x1b, IMX300_REG_VALUE_08BIT }, + { 0x934c, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0x9356, 0x8c, IMX300_REG_VALUE_08BIT }, + { 0x9357, 0x50, IMX300_REG_VALUE_08BIT }, + { 0x9358, 0x1b, IMX300_REG_VALUE_08BIT }, + { 0x9359, 0x8c, IMX300_REG_VALUE_08BIT }, + { 0x935a, 0x1b, IMX300_REG_VALUE_08BIT }, + { 0x935b, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0x9360, 0x1b, IMX300_REG_VALUE_08BIT }, + { 0x9361, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0x9362, 0x8c, IMX300_REG_VALUE_08BIT }, + { 0x9363, 0x50, IMX300_REG_VALUE_08BIT }, + { 0x9364, 0x1b, IMX300_REG_VALUE_08BIT }, + { 0x9365, 0x8c, IMX300_REG_VALUE_08BIT }, + { 0x9366, 0x1b, IMX300_REG_VALUE_08BIT }, + { 0x9367, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0x940d, 0x07, IMX300_REG_VALUE_08BIT }, + { 0x940e, 0x07, IMX300_REG_VALUE_08BIT }, + { 0x9414, 0x06, IMX300_REG_VALUE_08BIT }, + { 0x942b, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x942c, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x942d, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x942e, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x942f, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x9430, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x9431, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x9432, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x9433, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x9434, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x9435, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x9436, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x9437, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x9438, 0x0b, IMX300_REG_VALUE_08BIT }, + { 0x9439, 0x0b, IMX300_REG_VALUE_08BIT }, + { 0x943b, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x943d, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x943f, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x9441, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x9443, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x9445, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x9447, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x9449, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x944b, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x944d, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x944f, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x9451, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x9452, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0x9454, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x9456, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x9457, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x9458, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x9459, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x945a, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x945b, 0x07, IMX300_REG_VALUE_08BIT }, + { 0x945c, 0x0b, IMX300_REG_VALUE_08BIT }, + { 0x945d, 0x07, IMX300_REG_VALUE_08BIT }, + { 0x945e, 0x0b, IMX300_REG_VALUE_08BIT }, + { 0x945f, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x9460, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x9461, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x9462, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x9463, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x9464, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x9465, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x9466, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x947c, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x947d, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x9480, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x9481, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x9503, 0x05, IMX300_REG_VALUE_08BIT }, + { 0x9504, 0x05, IMX300_REG_VALUE_08BIT }, + { 0x9505, 0x05, IMX300_REG_VALUE_08BIT }, + { 0x9506, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9507, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9508, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9526, 0x18, IMX300_REG_VALUE_08BIT }, + { 0x9527, 0x18, IMX300_REG_VALUE_08BIT }, + { 0x9528, 0x18, IMX300_REG_VALUE_08BIT }, + { 0x9619, 0xa0, IMX300_REG_VALUE_08BIT }, + { 0x961b, 0xa0, IMX300_REG_VALUE_08BIT }, + { 0x961d, 0xa0, IMX300_REG_VALUE_08BIT }, + { 0x961f, 0x20, IMX300_REG_VALUE_08BIT }, + { 0x9621, 0x20, IMX300_REG_VALUE_08BIT }, + { 0x9623, 0x20, IMX300_REG_VALUE_08BIT }, + { 0x9625, 0xa0, IMX300_REG_VALUE_08BIT }, + { 0x9627, 0xa0, IMX300_REG_VALUE_08BIT }, + { 0x9629, 0xa0, IMX300_REG_VALUE_08BIT }, + { 0x962b, 0x20, IMX300_REG_VALUE_08BIT }, + { 0x962d, 0x20, IMX300_REG_VALUE_08BIT }, + { 0x962f, 0x20, IMX300_REG_VALUE_08BIT }, + { 0x9719, 0xa0, IMX300_REG_VALUE_08BIT }, + { 0x971b, 0xa0, IMX300_REG_VALUE_08BIT }, + { 0x971d, 0xa0, IMX300_REG_VALUE_08BIT }, + { 0x971f, 0x20, IMX300_REG_VALUE_08BIT }, + { 0x9721, 0x20, IMX300_REG_VALUE_08BIT }, + { 0x9723, 0x20, IMX300_REG_VALUE_08BIT }, + { 0x9725, 0xa0, IMX300_REG_VALUE_08BIT }, + { 0x9727, 0xa0, IMX300_REG_VALUE_08BIT }, + { 0x9729, 0xa0, IMX300_REG_VALUE_08BIT }, + { 0x972b, 0x20, IMX300_REG_VALUE_08BIT }, + { 0x972d, 0x20, IMX300_REG_VALUE_08BIT }, + { 0x972f, 0x20, IMX300_REG_VALUE_08BIT }, + { 0x9901, 0x35, IMX300_REG_VALUE_08BIT }, + { 0x9903, 0x23, IMX300_REG_VALUE_08BIT }, + { 0x9905, 0x23, IMX300_REG_VALUE_08BIT }, + { 0x9906, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9907, 0x31, IMX300_REG_VALUE_08BIT }, + { 0x9908, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9909, 0x1b, IMX300_REG_VALUE_08BIT }, + { 0x990a, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x990b, 0x15, IMX300_REG_VALUE_08BIT }, + { 0x990d, 0x3f, IMX300_REG_VALUE_08BIT }, + { 0x990f, 0x3f, IMX300_REG_VALUE_08BIT }, + { 0x9911, 0x3f, IMX300_REG_VALUE_08BIT }, + { 0x9913, 0x64, IMX300_REG_VALUE_08BIT }, + { 0x9915, 0x64, IMX300_REG_VALUE_08BIT }, + { 0x9917, 0x64, IMX300_REG_VALUE_08BIT }, + { 0x9919, 0x50, IMX300_REG_VALUE_08BIT }, + { 0x991b, 0x60, IMX300_REG_VALUE_08BIT }, + { 0x991d, 0x65, IMX300_REG_VALUE_08BIT }, + { 0x991f, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x9921, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x9923, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x9925, 0x23, IMX300_REG_VALUE_08BIT }, + { 0x9927, 0x23, IMX300_REG_VALUE_08BIT }, + { 0x9929, 0x23, IMX300_REG_VALUE_08BIT }, + { 0x992b, 0x2f, IMX300_REG_VALUE_08BIT }, + { 0x992d, 0x1a, IMX300_REG_VALUE_08BIT }, + { 0x992f, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9931, 0x3f, IMX300_REG_VALUE_08BIT }, + { 0x9933, 0x3f, IMX300_REG_VALUE_08BIT }, + { 0x9935, 0x3f, IMX300_REG_VALUE_08BIT }, + { 0x9937, 0x6b, IMX300_REG_VALUE_08BIT }, + { 0x9939, 0x7c, IMX300_REG_VALUE_08BIT }, + { 0x993b, 0x81, IMX300_REG_VALUE_08BIT }, + { 0x9943, 0x0f, IMX300_REG_VALUE_08BIT }, + { 0x9945, 0x0f, IMX300_REG_VALUE_08BIT }, + { 0x9947, 0x0f, IMX300_REG_VALUE_08BIT }, + { 0x9949, 0x0f, IMX300_REG_VALUE_08BIT }, + { 0x994b, 0x0f, IMX300_REG_VALUE_08BIT }, + { 0x994d, 0x0f, IMX300_REG_VALUE_08BIT }, + { 0x994f, 0x42, IMX300_REG_VALUE_08BIT }, + { 0x9951, 0x0f, IMX300_REG_VALUE_08BIT }, + { 0x9953, 0x0b, IMX300_REG_VALUE_08BIT }, + { 0x9955, 0x5a, IMX300_REG_VALUE_08BIT }, + { 0x9957, 0x13, IMX300_REG_VALUE_08BIT }, + { 0x9959, 0x0c, IMX300_REG_VALUE_08BIT }, + { 0x995a, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x995b, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x995c, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x996b, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x996d, 0x10, IMX300_REG_VALUE_08BIT }, + { 0x996f, 0x10, IMX300_REG_VALUE_08BIT }, + { 0x9971, 0xc8, IMX300_REG_VALUE_08BIT }, + { 0x9973, 0x32, IMX300_REG_VALUE_08BIT }, + { 0x9975, 0x04, IMX300_REG_VALUE_08BIT }, + { 0x9976, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0x9979, 0xe0, IMX300_REG_VALUE_08BIT }, + { 0x997a, 0xe0, IMX300_REG_VALUE_08BIT }, + { 0x997b, 0xe0, IMX300_REG_VALUE_08BIT }, + { 0x9981, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9983, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9985, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x99a4, 0x2f, IMX300_REG_VALUE_08BIT }, + { 0x99a5, 0x2f, IMX300_REG_VALUE_08BIT }, + { 0x99a6, 0x2f, IMX300_REG_VALUE_08BIT }, + { 0x99a7, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0x99a8, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0x99a9, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0x99aa, 0x2f, IMX300_REG_VALUE_08BIT }, + { 0x99ab, 0x2f, IMX300_REG_VALUE_08BIT }, + { 0x99ac, 0x2f, IMX300_REG_VALUE_08BIT }, + { 0x99ad, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x99ae, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x99af, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x99b0, 0x40, IMX300_REG_VALUE_08BIT }, + { 0x99b1, 0x40, IMX300_REG_VALUE_08BIT }, + { 0x99b2, 0x40, IMX300_REG_VALUE_08BIT }, + { 0x99b3, 0x30, IMX300_REG_VALUE_08BIT }, + { 0x99b4, 0x30, IMX300_REG_VALUE_08BIT }, + { 0x99b5, 0x30, IMX300_REG_VALUE_08BIT }, + { 0x99bb, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0x99bd, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0x99bf, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0x99c0, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x99c1, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x99c2, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x99c6, 0x3c, IMX300_REG_VALUE_08BIT }, + { 0x99c7, 0x3c, IMX300_REG_VALUE_08BIT }, + { 0x99c8, 0x3c, IMX300_REG_VALUE_08BIT }, + { 0x99c9, 0xff, IMX300_REG_VALUE_08BIT }, + { 0x99ca, 0xff, IMX300_REG_VALUE_08BIT }, + { 0x99cb, 0xff, IMX300_REG_VALUE_08BIT }, + { 0x9a01, 0x35, IMX300_REG_VALUE_08BIT }, + { 0x9a03, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9a05, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9a07, 0x31, IMX300_REG_VALUE_08BIT }, + { 0x9a09, 0x1b, IMX300_REG_VALUE_08BIT }, + { 0x9a0b, 0x15, IMX300_REG_VALUE_08BIT }, + { 0x9a0d, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9a0f, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9a11, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9a13, 0x64, IMX300_REG_VALUE_08BIT }, + { 0x9a15, 0x64, IMX300_REG_VALUE_08BIT }, + { 0x9a17, 0x64, IMX300_REG_VALUE_08BIT }, + { 0x9a19, 0x50, IMX300_REG_VALUE_08BIT }, + { 0x9a1b, 0x60, IMX300_REG_VALUE_08BIT }, + { 0x9a1d, 0x65, IMX300_REG_VALUE_08BIT }, + { 0x9a1f, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x9a21, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x9a23, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x9a25, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9a27, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9a29, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9a2b, 0x2f, IMX300_REG_VALUE_08BIT }, + { 0x9a2d, 0x1a, IMX300_REG_VALUE_08BIT }, + { 0x9a2f, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9a31, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9a33, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9a35, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9a37, 0x6b, IMX300_REG_VALUE_08BIT }, + { 0x9a39, 0x7c, IMX300_REG_VALUE_08BIT }, + { 0x9a3b, 0x81, IMX300_REG_VALUE_08BIT }, + { 0x9a3d, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9a3f, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9a41, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9a4f, 0x42, IMX300_REG_VALUE_08BIT }, + { 0x9a51, 0x0f, IMX300_REG_VALUE_08BIT }, + { 0x9a53, 0x0b, IMX300_REG_VALUE_08BIT }, + { 0x9a55, 0x5a, IMX300_REG_VALUE_08BIT }, + { 0x9a57, 0x13, IMX300_REG_VALUE_08BIT }, + { 0x9a59, 0x0c, IMX300_REG_VALUE_08BIT }, + { 0x9a5a, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9a5b, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9a5c, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9a6b, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9a6d, 0x10, IMX300_REG_VALUE_08BIT }, + { 0x9a6f, 0x10, IMX300_REG_VALUE_08BIT }, + { 0x9a71, 0xc8, IMX300_REG_VALUE_08BIT }, + { 0x9a73, 0x32, IMX300_REG_VALUE_08BIT }, + { 0x9a75, 0x04, IMX300_REG_VALUE_08BIT }, + { 0x9a79, 0xe0, IMX300_REG_VALUE_08BIT }, + { 0x9a7a, 0xe0, IMX300_REG_VALUE_08BIT }, + { 0x9a7b, 0xe0, IMX300_REG_VALUE_08BIT }, + { 0x9a81, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9a83, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9a85, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9aa4, 0x3f, IMX300_REG_VALUE_08BIT }, + { 0x9aa5, 0x3f, IMX300_REG_VALUE_08BIT }, + { 0x9aa6, 0x3f, IMX300_REG_VALUE_08BIT }, + { 0x9aa7, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0x9aa8, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0x9aa9, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0x9aaa, 0x3f, IMX300_REG_VALUE_08BIT }, + { 0x9aab, 0x3f, IMX300_REG_VALUE_08BIT }, + { 0x9aac, 0x3f, IMX300_REG_VALUE_08BIT }, + { 0x9aad, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9aae, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9aaf, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9ab0, 0x40, IMX300_REG_VALUE_08BIT }, + { 0x9ab1, 0x40, IMX300_REG_VALUE_08BIT }, + { 0x9ab2, 0x40, IMX300_REG_VALUE_08BIT }, + { 0x9ab3, 0x30, IMX300_REG_VALUE_08BIT }, + { 0x9ab4, 0x30, IMX300_REG_VALUE_08BIT }, + { 0x9ab5, 0x30, IMX300_REG_VALUE_08BIT }, + { 0x9ab6, 0xa0, IMX300_REG_VALUE_08BIT }, + { 0x9ab7, 0xa0, IMX300_REG_VALUE_08BIT }, + { 0x9ab8, 0xa0, IMX300_REG_VALUE_08BIT }, + { 0x9abb, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0x9abd, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0x9abf, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0x9ac0, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x9ac1, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x9ac2, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x9ac6, 0x2d, IMX300_REG_VALUE_08BIT }, + { 0x9ac7, 0x2d, IMX300_REG_VALUE_08BIT }, + { 0x9ac8, 0x2d, IMX300_REG_VALUE_08BIT }, + { 0x9ac9, 0xff, IMX300_REG_VALUE_08BIT }, + { 0x9aca, 0xff, IMX300_REG_VALUE_08BIT }, + { 0x9acb, 0xff, IMX300_REG_VALUE_08BIT }, + { 0x9b01, 0x35, IMX300_REG_VALUE_08BIT }, + { 0x9b03, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9b05, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9b07, 0x31, IMX300_REG_VALUE_08BIT }, + { 0x9b08, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x9b09, 0x1b, IMX300_REG_VALUE_08BIT }, + { 0x9b0a, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x9b0b, 0x15, IMX300_REG_VALUE_08BIT }, + { 0x9b0d, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9b0f, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9b11, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9b13, 0x64, IMX300_REG_VALUE_08BIT }, + { 0x9b15, 0x64, IMX300_REG_VALUE_08BIT }, + { 0x9b17, 0x64, IMX300_REG_VALUE_08BIT }, + { 0x9b19, 0x50, IMX300_REG_VALUE_08BIT }, + { 0x9b1b, 0x60, IMX300_REG_VALUE_08BIT }, + { 0x9b1d, 0x65, IMX300_REG_VALUE_08BIT }, + { 0x9b1f, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x9b21, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x9b23, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x9b25, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9b27, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9b29, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9b2b, 0x2f, IMX300_REG_VALUE_08BIT }, + { 0x9b2d, 0x1a, IMX300_REG_VALUE_08BIT }, + { 0x9b2f, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9b31, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9b33, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9b35, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9b37, 0x6b, IMX300_REG_VALUE_08BIT }, + { 0x9b39, 0x7c, IMX300_REG_VALUE_08BIT }, + { 0x9b3b, 0x81, IMX300_REG_VALUE_08BIT }, + { 0x9b43, 0x0f, IMX300_REG_VALUE_08BIT }, + { 0x9b45, 0x0f, IMX300_REG_VALUE_08BIT }, + { 0x9b47, 0x0f, IMX300_REG_VALUE_08BIT }, + { 0x9b49, 0x0f, IMX300_REG_VALUE_08BIT }, + { 0x9b4b, 0x0f, IMX300_REG_VALUE_08BIT }, + { 0x9b4d, 0x0f, IMX300_REG_VALUE_08BIT }, + { 0x9b4f, 0x2d, IMX300_REG_VALUE_08BIT }, + { 0x9b51, 0x0b, IMX300_REG_VALUE_08BIT }, + { 0x9b53, 0x08, IMX300_REG_VALUE_08BIT }, + { 0x9b55, 0x40, IMX300_REG_VALUE_08BIT }, + { 0x9b57, 0x0d, IMX300_REG_VALUE_08BIT }, + { 0x9b59, 0x08, IMX300_REG_VALUE_08BIT }, + { 0x9b5a, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9b5b, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9b5c, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9b5d, 0x08, IMX300_REG_VALUE_08BIT }, + { 0x9b5e, 0x0e, IMX300_REG_VALUE_08BIT }, + { 0x9b60, 0x08, IMX300_REG_VALUE_08BIT }, + { 0x9b61, 0x0e, IMX300_REG_VALUE_08BIT }, + { 0x9b6b, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9b6d, 0x10, IMX300_REG_VALUE_08BIT }, + { 0x9b6f, 0x10, IMX300_REG_VALUE_08BIT }, + { 0x9b71, 0xc8, IMX300_REG_VALUE_08BIT }, + { 0x9b73, 0x32, IMX300_REG_VALUE_08BIT }, + { 0x9b75, 0x04, IMX300_REG_VALUE_08BIT }, + { 0x9b76, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0x9b79, 0xe0, IMX300_REG_VALUE_08BIT }, + { 0x9b7a, 0xe0, IMX300_REG_VALUE_08BIT }, + { 0x9b7b, 0xe0, IMX300_REG_VALUE_08BIT }, + { 0x9b81, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9b83, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9b85, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9bb0, 0x40, IMX300_REG_VALUE_08BIT }, + { 0x9bb1, 0x40, IMX300_REG_VALUE_08BIT }, + { 0x9bb2, 0x40, IMX300_REG_VALUE_08BIT }, + { 0x9bb3, 0x30, IMX300_REG_VALUE_08BIT }, + { 0x9bb4, 0x30, IMX300_REG_VALUE_08BIT }, + { 0x9bb5, 0x30, IMX300_REG_VALUE_08BIT }, + { 0x9bbb, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0x9bbd, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0x9bbf, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0x9bc0, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x9bc1, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x9bc2, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x9bc6, 0x18, IMX300_REG_VALUE_08BIT }, + { 0x9bc7, 0x18, IMX300_REG_VALUE_08BIT }, + { 0x9bc8, 0x18, IMX300_REG_VALUE_08BIT }, + { 0x9bc9, 0xff, IMX300_REG_VALUE_08BIT }, + { 0x9bca, 0xff, IMX300_REG_VALUE_08BIT }, + { 0x9bcb, 0xff, IMX300_REG_VALUE_08BIT }, + { 0x9bcc, 0x20, IMX300_REG_VALUE_08BIT }, + { 0x9bcd, 0x20, IMX300_REG_VALUE_08BIT }, + { 0x9bce, 0x20, IMX300_REG_VALUE_08BIT }, + { 0x9c01, 0x35, IMX300_REG_VALUE_08BIT }, + { 0x9c03, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9c05, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9c07, 0x31, IMX300_REG_VALUE_08BIT }, + { 0x9c09, 0x1b, IMX300_REG_VALUE_08BIT }, + { 0x9c0b, 0x15, IMX300_REG_VALUE_08BIT }, + { 0x9c0d, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9c0f, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9c11, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9c13, 0x64, IMX300_REG_VALUE_08BIT }, + { 0x9c15, 0x64, IMX300_REG_VALUE_08BIT }, + { 0x9c17, 0x64, IMX300_REG_VALUE_08BIT }, + { 0x9c19, 0x50, IMX300_REG_VALUE_08BIT }, + { 0x9c1b, 0x60, IMX300_REG_VALUE_08BIT }, + { 0x9c1d, 0x65, IMX300_REG_VALUE_08BIT }, + { 0x9c1f, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x9c21, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x9c23, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x9c25, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9c27, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9c29, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9c2b, 0x2f, IMX300_REG_VALUE_08BIT }, + { 0x9c2d, 0x1a, IMX300_REG_VALUE_08BIT }, + { 0x9c2f, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9c31, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9c33, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9c35, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9c37, 0x6b, IMX300_REG_VALUE_08BIT }, + { 0x9c39, 0x7c, IMX300_REG_VALUE_08BIT }, + { 0x9c3b, 0x81, IMX300_REG_VALUE_08BIT }, + { 0x9c3d, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9c3f, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9c41, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9c4f, 0x42, IMX300_REG_VALUE_08BIT }, + { 0x9c51, 0x0b, IMX300_REG_VALUE_08BIT }, + { 0x9c53, 0x08, IMX300_REG_VALUE_08BIT }, + { 0x9c55, 0x5a, IMX300_REG_VALUE_08BIT }, + { 0x9c57, 0x0d, IMX300_REG_VALUE_08BIT }, + { 0x9c59, 0x08, IMX300_REG_VALUE_08BIT }, + { 0x9c5a, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9c5b, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9c5c, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9c6b, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9c6d, 0x10, IMX300_REG_VALUE_08BIT }, + { 0x9c6f, 0x10, IMX300_REG_VALUE_08BIT }, + { 0x9c71, 0xc8, IMX300_REG_VALUE_08BIT }, + { 0x9c73, 0x32, IMX300_REG_VALUE_08BIT }, + { 0x9c75, 0x04, IMX300_REG_VALUE_08BIT }, + { 0x9c79, 0xe0, IMX300_REG_VALUE_08BIT }, + { 0x9c7a, 0xe0, IMX300_REG_VALUE_08BIT }, + { 0x9c7b, 0xe0, IMX300_REG_VALUE_08BIT }, + { 0x9c81, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9c83, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9c85, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9c87, 0x48, IMX300_REG_VALUE_08BIT }, + { 0x9c89, 0x48, IMX300_REG_VALUE_08BIT }, + { 0x9c8b, 0x48, IMX300_REG_VALUE_08BIT }, + { 0x9ca4, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9ca5, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9ca6, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9ca7, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9ca8, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9ca9, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9caa, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9cab, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9cac, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9cad, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9cae, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9caf, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9cb0, 0x50, IMX300_REG_VALUE_08BIT }, + { 0x9cb1, 0x50, IMX300_REG_VALUE_08BIT }, + { 0x9cb2, 0x50, IMX300_REG_VALUE_08BIT }, + { 0x9cb3, 0x40, IMX300_REG_VALUE_08BIT }, + { 0x9cb4, 0x40, IMX300_REG_VALUE_08BIT }, + { 0x9cb5, 0x40, IMX300_REG_VALUE_08BIT }, + { 0x9cbb, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0x9cbd, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0x9cbf, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0x9cc0, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x9cc1, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x9cc2, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x9cc6, 0x18, IMX300_REG_VALUE_08BIT }, + { 0x9cc7, 0x18, IMX300_REG_VALUE_08BIT }, + { 0x9cc8, 0x18, IMX300_REG_VALUE_08BIT }, + { 0x9cc9, 0xff, IMX300_REG_VALUE_08BIT }, + { 0x9cca, 0xff, IMX300_REG_VALUE_08BIT }, + { 0x9ccb, 0xff, IMX300_REG_VALUE_08BIT }, + { 0x9d01, 0x35, IMX300_REG_VALUE_08BIT }, + { 0x9d03, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9d05, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9d07, 0x31, IMX300_REG_VALUE_08BIT }, + { 0x9d09, 0x1b, IMX300_REG_VALUE_08BIT }, + { 0x9d0b, 0x15, IMX300_REG_VALUE_08BIT }, + { 0x9d0d, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9d0f, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9d11, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9d13, 0x64, IMX300_REG_VALUE_08BIT }, + { 0x9d15, 0x64, IMX300_REG_VALUE_08BIT }, + { 0x9d17, 0x64, IMX300_REG_VALUE_08BIT }, + { 0x9d19, 0x50, IMX300_REG_VALUE_08BIT }, + { 0x9d1b, 0x60, IMX300_REG_VALUE_08BIT }, + { 0x9d1d, 0x65, IMX300_REG_VALUE_08BIT }, + { 0x9d1f, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x9d21, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x9d23, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x9d25, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9d27, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9d29, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9d2b, 0x2f, IMX300_REG_VALUE_08BIT }, + { 0x9d2d, 0x1a, IMX300_REG_VALUE_08BIT }, + { 0x9d2f, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9d31, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9d33, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9d35, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9d37, 0x6b, IMX300_REG_VALUE_08BIT }, + { 0x9d39, 0x7c, IMX300_REG_VALUE_08BIT }, + { 0x9d3b, 0x81, IMX300_REG_VALUE_08BIT }, + { 0x9d3d, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9d3f, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9d41, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9d4f, 0x42, IMX300_REG_VALUE_08BIT }, + { 0x9d50, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9d51, 0x0f, IMX300_REG_VALUE_08BIT }, + { 0x9d53, 0x0b, IMX300_REG_VALUE_08BIT }, + { 0x9d55, 0x5a, IMX300_REG_VALUE_08BIT }, + { 0x9d57, 0x13, IMX300_REG_VALUE_08BIT }, + { 0x9d59, 0x0c, IMX300_REG_VALUE_08BIT }, + { 0x9d5b, 0x35, IMX300_REG_VALUE_08BIT }, + { 0x9d5d, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9d5f, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9d61, 0x35, IMX300_REG_VALUE_08BIT }, + { 0x9d63, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9d65, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9d67, 0x31, IMX300_REG_VALUE_08BIT }, + { 0x9d69, 0x1b, IMX300_REG_VALUE_08BIT }, + { 0x9d6b, 0x15, IMX300_REG_VALUE_08BIT }, + { 0x9d6d, 0x31, IMX300_REG_VALUE_08BIT }, + { 0x9d6f, 0x1b, IMX300_REG_VALUE_08BIT }, + { 0x9d71, 0x15, IMX300_REG_VALUE_08BIT }, + { 0x9d73, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9d75, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9d77, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9d79, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9d7b, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9d7d, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9d7f, 0x64, IMX300_REG_VALUE_08BIT }, + { 0x9d81, 0x64, IMX300_REG_VALUE_08BIT }, + { 0x9d83, 0x64, IMX300_REG_VALUE_08BIT }, + { 0x9d85, 0x64, IMX300_REG_VALUE_08BIT }, + { 0x9d87, 0x64, IMX300_REG_VALUE_08BIT }, + { 0x9d89, 0x64, IMX300_REG_VALUE_08BIT }, + { 0x9d8b, 0x50, IMX300_REG_VALUE_08BIT }, + { 0x9d8d, 0x60, IMX300_REG_VALUE_08BIT }, + { 0x9d8f, 0x65, IMX300_REG_VALUE_08BIT }, + { 0x9d91, 0x50, IMX300_REG_VALUE_08BIT }, + { 0x9d93, 0x60, IMX300_REG_VALUE_08BIT }, + { 0x9d95, 0x65, IMX300_REG_VALUE_08BIT }, + { 0x9d97, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x9d99, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x9d9b, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x9d9d, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x9d9f, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x9da1, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x9e01, 0x35, IMX300_REG_VALUE_08BIT }, + { 0x9e03, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9e05, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9e07, 0x31, IMX300_REG_VALUE_08BIT }, + { 0x9e09, 0x1b, IMX300_REG_VALUE_08BIT }, + { 0x9e0b, 0x15, IMX300_REG_VALUE_08BIT }, + { 0x9e0d, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9e0f, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9e11, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9e13, 0x64, IMX300_REG_VALUE_08BIT }, + { 0x9e15, 0x64, IMX300_REG_VALUE_08BIT }, + { 0x9e17, 0x64, IMX300_REG_VALUE_08BIT }, + { 0x9e19, 0x50, IMX300_REG_VALUE_08BIT }, + { 0x9e1b, 0x60, IMX300_REG_VALUE_08BIT }, + { 0x9e1d, 0x65, IMX300_REG_VALUE_08BIT }, + { 0x9e1f, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x9e21, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x9e23, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x9e25, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9e27, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9e29, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9e2b, 0x2f, IMX300_REG_VALUE_08BIT }, + { 0x9e2d, 0x1a, IMX300_REG_VALUE_08BIT }, + { 0x9e2f, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9e31, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9e33, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9e35, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9e37, 0x6b, IMX300_REG_VALUE_08BIT }, + { 0x9e39, 0x7c, IMX300_REG_VALUE_08BIT }, + { 0x9e3b, 0x81, IMX300_REG_VALUE_08BIT }, + { 0x9e3d, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9e3f, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9e41, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9e4f, 0x42, IMX300_REG_VALUE_08BIT }, + { 0x9e51, 0x0b, IMX300_REG_VALUE_08BIT }, + { 0x9e53, 0x08, IMX300_REG_VALUE_08BIT }, + { 0x9e55, 0x5a, IMX300_REG_VALUE_08BIT }, + { 0x9e57, 0x0d, IMX300_REG_VALUE_08BIT }, + { 0x9e59, 0x08, IMX300_REG_VALUE_08BIT }, + { 0x9e5b, 0x35, IMX300_REG_VALUE_08BIT }, + { 0x9e5d, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9e5f, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9e61, 0x35, IMX300_REG_VALUE_08BIT }, + { 0x9e63, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9e65, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9e67, 0x31, IMX300_REG_VALUE_08BIT }, + { 0x9e69, 0x1b, IMX300_REG_VALUE_08BIT }, + { 0x9e6b, 0x15, IMX300_REG_VALUE_08BIT }, + { 0x9e6d, 0x31, IMX300_REG_VALUE_08BIT }, + { 0x9e6f, 0x1b, IMX300_REG_VALUE_08BIT }, + { 0x9e71, 0x15, IMX300_REG_VALUE_08BIT }, + { 0x9e73, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9e75, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9e77, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9e79, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9e7b, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9e7d, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9e7f, 0x64, IMX300_REG_VALUE_08BIT }, + { 0x9e81, 0x64, IMX300_REG_VALUE_08BIT }, + { 0x9e83, 0x64, IMX300_REG_VALUE_08BIT }, + { 0x9e85, 0x64, IMX300_REG_VALUE_08BIT }, + { 0x9e87, 0x64, IMX300_REG_VALUE_08BIT }, + { 0x9e89, 0x64, IMX300_REG_VALUE_08BIT }, + { 0x9e8b, 0x50, IMX300_REG_VALUE_08BIT }, + { 0x9e8d, 0x60, IMX300_REG_VALUE_08BIT }, + { 0x9e8f, 0x65, IMX300_REG_VALUE_08BIT }, + { 0x9e91, 0x50, IMX300_REG_VALUE_08BIT }, + { 0x9e93, 0x60, IMX300_REG_VALUE_08BIT }, + { 0x9e95, 0x65, IMX300_REG_VALUE_08BIT }, + { 0x9e97, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x9e99, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x9e9b, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x9e9d, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x9e9f, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x9ea1, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x9f01, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9f03, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9f05, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9f07, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9f09, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9f0b, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9f0d, 0x2f, IMX300_REG_VALUE_08BIT }, + { 0x9f0f, 0x1a, IMX300_REG_VALUE_08BIT }, + { 0x9f11, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9f13, 0x2f, IMX300_REG_VALUE_08BIT }, + { 0x9f15, 0x1a, IMX300_REG_VALUE_08BIT }, + { 0x9f17, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9f19, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9f1b, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9f1d, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9f1f, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9f21, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9f23, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x9f25, 0x6b, IMX300_REG_VALUE_08BIT }, + { 0x9f27, 0x7c, IMX300_REG_VALUE_08BIT }, + { 0x9f29, 0x81, IMX300_REG_VALUE_08BIT }, + { 0x9f2b, 0x6b, IMX300_REG_VALUE_08BIT }, + { 0x9f2d, 0x7c, IMX300_REG_VALUE_08BIT }, + { 0x9f2f, 0x81, IMX300_REG_VALUE_08BIT }, + { 0x9f31, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9f33, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9f35, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9f37, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9f39, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9f3b, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9f3c, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9f3d, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9f3e, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9f41, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9f43, 0x10, IMX300_REG_VALUE_08BIT }, + { 0x9f45, 0x10, IMX300_REG_VALUE_08BIT }, + { 0x9f47, 0xc8, IMX300_REG_VALUE_08BIT }, + { 0x9f49, 0x32, IMX300_REG_VALUE_08BIT }, + { 0x9f4b, 0x04, IMX300_REG_VALUE_08BIT }, + { 0x9f4d, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9f4f, 0x10, IMX300_REG_VALUE_08BIT }, + { 0x9f51, 0x10, IMX300_REG_VALUE_08BIT }, + { 0x9f53, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9f55, 0x10, IMX300_REG_VALUE_08BIT }, + { 0x9f57, 0x10, IMX300_REG_VALUE_08BIT }, + { 0x9f59, 0x20, IMX300_REG_VALUE_08BIT }, + { 0x9f5b, 0x04, IMX300_REG_VALUE_08BIT }, + { 0x9f5d, 0x04, IMX300_REG_VALUE_08BIT }, + { 0x9f5f, 0x20, IMX300_REG_VALUE_08BIT }, + { 0x9f61, 0x04, IMX300_REG_VALUE_08BIT }, + { 0x9f63, 0x04, IMX300_REG_VALUE_08BIT }, + { 0x9f64, 0xe0, IMX300_REG_VALUE_08BIT }, + { 0x9f65, 0xe0, IMX300_REG_VALUE_08BIT }, + { 0x9f66, 0xe0, IMX300_REG_VALUE_08BIT }, + { 0x9f6a, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9f6b, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9f6c, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9f6d, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9f6e, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9f6f, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x9f77, 0x42, IMX300_REG_VALUE_08BIT }, + { 0x9f78, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9f79, 0x0f, IMX300_REG_VALUE_08BIT }, + { 0x9f7b, 0x0b, IMX300_REG_VALUE_08BIT }, + { 0x9f7d, 0x42, IMX300_REG_VALUE_08BIT }, + { 0x9f7e, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9f7f, 0x0f, IMX300_REG_VALUE_08BIT }, + { 0x9f81, 0x0b, IMX300_REG_VALUE_08BIT }, + { 0x9f83, 0x5a, IMX300_REG_VALUE_08BIT }, + { 0x9f85, 0x13, IMX300_REG_VALUE_08BIT }, + { 0x9f87, 0x0c, IMX300_REG_VALUE_08BIT }, + { 0x9f89, 0x5a, IMX300_REG_VALUE_08BIT }, + { 0x9f8b, 0x13, IMX300_REG_VALUE_08BIT }, + { 0x9f8d, 0x0c, IMX300_REG_VALUE_08BIT }, + { 0x9f8f, 0xa0, IMX300_REG_VALUE_08BIT }, + { 0x9fa6, 0x3f, IMX300_REG_VALUE_08BIT }, + { 0x9fa7, 0x3f, IMX300_REG_VALUE_08BIT }, + { 0x9fa8, 0x3f, IMX300_REG_VALUE_08BIT }, + { 0x9fa9, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0x9faa, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0x9fab, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0x9fac, 0x3f, IMX300_REG_VALUE_08BIT }, + { 0x9fad, 0x3f, IMX300_REG_VALUE_08BIT }, + { 0x9fae, 0x3f, IMX300_REG_VALUE_08BIT }, + { 0x9faf, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9fb0, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x9fb1, 0x00, IMX300_REG_VALUE_08BIT }, + { 0xa001, 0x14, IMX300_REG_VALUE_08BIT }, + { 0xa003, 0x14, IMX300_REG_VALUE_08BIT }, + { 0xa005, 0x14, IMX300_REG_VALUE_08BIT }, + { 0xa007, 0x14, IMX300_REG_VALUE_08BIT }, + { 0xa009, 0x14, IMX300_REG_VALUE_08BIT }, + { 0xa00b, 0x14, IMX300_REG_VALUE_08BIT }, + { 0xa00d, 0x2f, IMX300_REG_VALUE_08BIT }, + { 0xa00f, 0x1a, IMX300_REG_VALUE_08BIT }, + { 0xa011, 0x14, IMX300_REG_VALUE_08BIT }, + { 0xa013, 0x2f, IMX300_REG_VALUE_08BIT }, + { 0xa015, 0x1a, IMX300_REG_VALUE_08BIT }, + { 0xa017, 0x14, IMX300_REG_VALUE_08BIT }, + { 0xa019, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0xa01b, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0xa01d, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0xa01f, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0xa021, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0xa023, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0xa025, 0x6b, IMX300_REG_VALUE_08BIT }, + { 0xa027, 0x7c, IMX300_REG_VALUE_08BIT }, + { 0xa029, 0x81, IMX300_REG_VALUE_08BIT }, + { 0xa02b, 0x6b, IMX300_REG_VALUE_08BIT }, + { 0xa02d, 0x7c, IMX300_REG_VALUE_08BIT }, + { 0xa02f, 0x81, IMX300_REG_VALUE_08BIT }, + { 0xa031, 0x00, IMX300_REG_VALUE_08BIT }, + { 0xa033, 0x00, IMX300_REG_VALUE_08BIT }, + { 0xa035, 0x00, IMX300_REG_VALUE_08BIT }, + { 0xa037, 0x00, IMX300_REG_VALUE_08BIT }, + { 0xa039, 0x00, IMX300_REG_VALUE_08BIT }, + { 0xa03b, 0x00, IMX300_REG_VALUE_08BIT }, + { 0xa03c, 0x00, IMX300_REG_VALUE_08BIT }, + { 0xa03d, 0x00, IMX300_REG_VALUE_08BIT }, + { 0xa03e, 0x00, IMX300_REG_VALUE_08BIT }, + { 0xa041, 0x00, IMX300_REG_VALUE_08BIT }, + { 0xa043, 0x10, IMX300_REG_VALUE_08BIT }, + { 0xa045, 0x10, IMX300_REG_VALUE_08BIT }, + { 0xa047, 0xc8, IMX300_REG_VALUE_08BIT }, + { 0xa049, 0x32, IMX300_REG_VALUE_08BIT }, + { 0xa04b, 0x04, IMX300_REG_VALUE_08BIT }, + { 0xa04d, 0x00, IMX300_REG_VALUE_08BIT }, + { 0xa04f, 0x10, IMX300_REG_VALUE_08BIT }, + { 0xa051, 0x10, IMX300_REG_VALUE_08BIT }, + { 0xa053, 0x00, IMX300_REG_VALUE_08BIT }, + { 0xa055, 0x10, IMX300_REG_VALUE_08BIT }, + { 0xa057, 0x10, IMX300_REG_VALUE_08BIT }, + { 0xa059, 0x20, IMX300_REG_VALUE_08BIT }, + { 0xa05b, 0x04, IMX300_REG_VALUE_08BIT }, + { 0xa05d, 0x04, IMX300_REG_VALUE_08BIT }, + { 0xa05f, 0x20, IMX300_REG_VALUE_08BIT }, + { 0xa061, 0x04, IMX300_REG_VALUE_08BIT }, + { 0xa063, 0x04, IMX300_REG_VALUE_08BIT }, + { 0xa064, 0xe0, IMX300_REG_VALUE_08BIT }, + { 0xa065, 0xe0, IMX300_REG_VALUE_08BIT }, + { 0xa066, 0xe0, IMX300_REG_VALUE_08BIT }, + { 0xa067, 0x20, IMX300_REG_VALUE_08BIT }, + { 0xa068, 0x20, IMX300_REG_VALUE_08BIT }, + { 0xa069, 0x20, IMX300_REG_VALUE_08BIT }, + { 0xa06b, 0x14, IMX300_REG_VALUE_08BIT }, + { 0xa06d, 0x14, IMX300_REG_VALUE_08BIT }, + { 0xa06f, 0x14, IMX300_REG_VALUE_08BIT }, + { 0xa071, 0x48, IMX300_REG_VALUE_08BIT }, + { 0xa073, 0x48, IMX300_REG_VALUE_08BIT }, + { 0xa075, 0x48, IMX300_REG_VALUE_08BIT }, + { 0xa077, 0x42, IMX300_REG_VALUE_08BIT }, + { 0xa079, 0x0b, IMX300_REG_VALUE_08BIT }, + { 0xa07b, 0x08, IMX300_REG_VALUE_08BIT }, + { 0xa07d, 0x42, IMX300_REG_VALUE_08BIT }, + { 0xa07f, 0x0b, IMX300_REG_VALUE_08BIT }, + { 0xa081, 0x08, IMX300_REG_VALUE_08BIT }, + { 0xa083, 0x5a, IMX300_REG_VALUE_08BIT }, + { 0xa085, 0x0d, IMX300_REG_VALUE_08BIT }, + { 0xa087, 0x08, IMX300_REG_VALUE_08BIT }, + { 0xa089, 0x5a, IMX300_REG_VALUE_08BIT }, + { 0xa08b, 0x0d, IMX300_REG_VALUE_08BIT }, + { 0xa08d, 0x08, IMX300_REG_VALUE_08BIT }, + { 0xa08f, 0xa0, IMX300_REG_VALUE_08BIT }, + { 0xa091, 0x3a, IMX300_REG_VALUE_08BIT }, + { 0xa093, 0x3a, IMX300_REG_VALUE_08BIT }, + { 0xa095, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0xa097, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0xa099, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0xa0a9, 0x00, IMX300_REG_VALUE_08BIT }, + { 0xa0aa, 0x00, IMX300_REG_VALUE_08BIT }, + { 0xa0ab, 0x00, IMX300_REG_VALUE_08BIT }, + { 0xa0af, 0x00, IMX300_REG_VALUE_08BIT }, + { 0xa0b0, 0x00, IMX300_REG_VALUE_08BIT }, + { 0xa0b1, 0x00, IMX300_REG_VALUE_08BIT }, + { 0xf800, 0x5c, IMX300_REG_VALUE_08BIT }, + { 0xf801, 0x5c, IMX300_REG_VALUE_08BIT }, + { 0xf802, 0x92, IMX300_REG_VALUE_08BIT }, + { 0xf803, 0x00, IMX300_REG_VALUE_08BIT }, + { 0xf804, 0x55, IMX300_REG_VALUE_08BIT }, + { 0xf805, 0xbc, IMX300_REG_VALUE_08BIT }, + { 0xf806, 0x22, IMX300_REG_VALUE_08BIT }, + { 0xf807, 0x00, IMX300_REG_VALUE_08BIT }, + { 0xf808, 0x1c, IMX300_REG_VALUE_08BIT }, + { 0xf809, 0x80, IMX300_REG_VALUE_08BIT }, + { 0xf80a, 0xfa, IMX300_REG_VALUE_08BIT }, + { 0xf80b, 0x21, IMX300_REG_VALUE_08BIT }, + { 0xf80c, 0x55, IMX300_REG_VALUE_08BIT }, + { 0xf80d, 0x14, IMX300_REG_VALUE_08BIT }, + { 0xf80e, 0xba, IMX300_REG_VALUE_08BIT }, + { 0xf80f, 0x00, IMX300_REG_VALUE_08BIT }, + { 0xf810, 0x81, IMX300_REG_VALUE_08BIT }, + { 0xf811, 0xd1, IMX300_REG_VALUE_08BIT }, + { 0xf812, 0x31, IMX300_REG_VALUE_08BIT }, + { 0xf813, 0x02, IMX300_REG_VALUE_08BIT }, + { 0x5869, 0x01, IMX300_REG_VALUE_08BIT }, +}; + +/* + * Magic registers sequence, common for all of the specified resolutions. + * + * Note: Binaries seem to send data to 0x0220, 0x0221 when enabling HDR mode... + */ +static const struct imx300_reg mode_common_regs[] = { + { IMX300_REG_ORIENTATION, 0x03, IMX300_REG_VALUE_08BIT }, + { IMX300_REG_CSI_DATA_FORMAT_HI, 10, IMX300_REG_VALUE_08BIT }, + { IMX300_REG_CSI_DATA_FORMAT_LO, 10, IMX300_REG_VALUE_08BIT }, + { IMX300_REG_CSI_LANE_MODE, IMX300_CSI_LANE_MODE_4LANE, + IMX300_REG_VALUE_08BIT }, + { 0x0221, 0x11, IMX300_REG_VALUE_08BIT }, + { 0x0381, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x0383, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x0385, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x0387, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x0401, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x0404, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x0405, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x0408, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x0409, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x040a, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x040b, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x040c, 0x15, IMX300_REG_VALUE_08BIT }, + { 0x040d, 0x90, IMX300_REG_VALUE_08BIT }, + { 0x040e, 0x10, IMX300_REG_VALUE_08BIT }, + { 0x040f, 0x40, IMX300_REG_VALUE_08BIT }, + { 0x30e4, 0x15, IMX300_REG_VALUE_08BIT }, + { 0x30e5, 0x90, IMX300_REG_VALUE_08BIT }, + { 0x30e6, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x30e7, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x30e8, 0x15, IMX300_REG_VALUE_08BIT }, + { 0x30e9, 0x90, IMX300_REG_VALUE_08BIT }, + { 0x30ea, 0x05, IMX300_REG_VALUE_08BIT }, + { 0x30eb, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x30ec, 0x15, IMX300_REG_VALUE_08BIT }, + { 0x30ed, 0x90, IMX300_REG_VALUE_08BIT }, + { 0x30ee, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x30ef, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x30f4, 0x15, IMX300_REG_VALUE_08BIT }, + { 0x30f5, 0x90, IMX300_REG_VALUE_08BIT }, + { 0x30f6, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x30f7, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3294, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3295, 0xe8, IMX300_REG_VALUE_08BIT }, + { 0x3296, 0x16, IMX300_REG_VALUE_08BIT }, + { 0x3297, 0x77, IMX300_REG_VALUE_08BIT }, + { 0x0210, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x0211, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x0212, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x0213, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x0214, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x0215, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x0216, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x0217, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3220, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3006, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x3007, 0x02, IMX300_REG_VALUE_08BIT }, + { 0x31e0, 0x03, IMX300_REG_VALUE_08BIT }, + { 0x31e1, 0xff, IMX300_REG_VALUE_08BIT }, + { 0x31e4, 0x02, IMX300_REG_VALUE_08BIT }, + { 0x4301, 0x94, IMX300_REG_VALUE_08BIT }, + { 0x4302, 0x15, IMX300_REG_VALUE_08BIT }, + { 0x4303, 0x44, IMX300_REG_VALUE_08BIT }, + { 0x4304, 0x09, IMX300_REG_VALUE_08BIT }, + { 0x4305, 0x07, IMX300_REG_VALUE_08BIT }, + { 0x4306, 0x87, IMX300_REG_VALUE_08BIT }, + { 0x4307, 0x88, IMX300_REG_VALUE_08BIT }, + { 0x4308, 0xba, IMX300_REG_VALUE_08BIT }, + { 0x4309, 0x21, IMX300_REG_VALUE_08BIT }, + { 0x4f7e, 0x12, IMX300_REG_VALUE_08BIT }, + { 0x0220, 0x02, IMX300_REG_VALUE_08BIT }, + { 0x0222, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x0224, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x0225, 0xf4, IMX300_REG_VALUE_08BIT }, + { 0x3000, 0x54, IMX300_REG_VALUE_08BIT }, + { 0x3001, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x0600, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x0601, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3022, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x0b05, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x0b06, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x3018, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x3019, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x301a, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x3025, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x3130, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x3004, 0x03, IMX300_REG_VALUE_08BIT }, + { 0x30a2, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x0b00, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3250, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3251, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3011, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3013, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x3129, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3125, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3127, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x3140, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3141, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x3142, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x314f, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x0b8e, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x0b8f, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x0b90, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x0b91, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x0b92, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x0b93, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x0b94, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x0b95, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3121, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x3123, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x31b0, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x3158, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3159, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x315a, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x315b, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x315c, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x315d, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x315e, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x315f, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3160, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3161, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3162, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3163, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3164, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3165, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3166, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3167, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3168, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3169, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x316a, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x316b, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x316c, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x316d, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x316e, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x316f, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3170, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3171, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3172, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3173, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3174, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3175, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3176, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3177, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3178, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3179, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x317a, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x317b, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x317c, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x317d, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x317e, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x317f, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3180, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3181, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3182, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3183, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3184, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3185, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3186, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3187, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x31b4, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x31b5, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x31b6, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x31b7, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x31b8, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x31b9, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x31ba, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x31bb, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3291, 0x01, IMX300_REG_VALUE_08BIT }, +}; + +/* Data rate configuration: HIGH (780MHz) */ +static const struct imx300_reg mipi_data_rate_1560mbps[] = { + { 0x0301, 0x04, IMX300_REG_VALUE_08BIT }, + { 0x0303, 0x02, IMX300_REG_VALUE_08BIT }, + { 0x0305, 0x03, IMX300_REG_VALUE_08BIT }, + { 0x0306, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x0307, 0xbe, IMX300_REG_VALUE_08BIT }, + { IMX300_REG_OP_PIX_CLK_DIV, 10, IMX300_REG_VALUE_08BIT }, + { IMX300_REG_OP_SYS_CLK_DIV, 1, IMX300_REG_VALUE_08BIT }, + { 0x030d, 0x03, IMX300_REG_VALUE_08BIT }, + { 0x030e, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x030f, 0xbb, IMX300_REG_VALUE_08BIT }, +}; + +/* Data rate configuration: LOW (480MHz) */ +static const struct imx300_reg mipi_data_rate_960mbps[] = { + { 0x0301, 0x04, IMX300_REG_VALUE_08BIT }, + { 0x0303, 0x02, IMX300_REG_VALUE_08BIT }, + { 0x0305, 0x03, IMX300_REG_VALUE_08BIT }, + { 0x0306, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x0307, 0xbe, IMX300_REG_VALUE_08BIT }, + { IMX300_REG_OP_PIX_CLK_DIV, 10, IMX300_REG_VALUE_08BIT }, + { IMX300_REG_OP_SYS_CLK_DIV, 1, IMX300_REG_VALUE_08BIT }, + { 0x030d, 0x03, IMX300_REG_VALUE_08BIT }, + { 0x030e, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x030f, 0x4b, IMX300_REG_VALUE_08BIT }, +}; + +static const struct imx300_reg binning_mode_off[] = { + { IMX300_REG_BINNING_EN, 0, IMX300_REG_VALUE_08BIT }, + { 0x0901, 0x11, IMX300_REG_VALUE_08BIT }, + { IMX300_REG_BINNING_TYPE, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3a00, 0x19, IMX300_REG_VALUE_08BIT }, + { 0x3a01, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3a02, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3a03, 0x08, IMX300_REG_VALUE_08BIT }, + { 0x3a04, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3a05, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3a06, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3a07, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3a08, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x6dc2, 0x04, IMX300_REG_VALUE_08BIT }, + { 0x6dc3, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x6dc4, 0x60, IMX300_REG_VALUE_08BIT }, + { 0x6dc7, 0x04, IMX300_REG_VALUE_08BIT }, + { 0x6dca, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x6dcb, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x6dcc, 0x01, IMX300_REG_VALUE_08BIT }, +}; + +static const struct imx300_reg binning_mode_2x2[] = { + { IMX300_REG_BINNING_EN, 1, IMX300_REG_VALUE_08BIT }, + { 0x0901, 0x22, IMX300_REG_VALUE_08BIT }, + { IMX300_REG_BINNING_TYPE, 0x02, IMX300_REG_VALUE_08BIT }, + { 0x3a00, 0x19, IMX300_REG_VALUE_08BIT }, + { 0x3a01, 0x0a, IMX300_REG_VALUE_08BIT }, + { 0x3a02, 0x0c, IMX300_REG_VALUE_08BIT }, + { 0x3a03, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x3a04, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3a05, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3a06, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3a07, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x3a08, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x6dc2, 0x04, IMX300_REG_VALUE_08BIT }, + { 0x6dc3, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x6dc4, 0x60, IMX300_REG_VALUE_08BIT }, + { 0x6dc7, 0x04, IMX300_REG_VALUE_08BIT }, + { 0x6dca, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x6dcb, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x6dcc, 0x01, IMX300_REG_VALUE_08BIT }, +}; + + +/* Output resolution configuration */ +static const struct imx300_reg mode_5520x4160[] = { + { IMX300_REG_EXPOSURE, 4230, IMX300_REG_VALUE_16BIT }, + { IMX300_REG_ANALOG_GAIN, 0, IMX300_REG_VALUE_16BIT }, + + { IMX300_REG_VTS, 4250, IMX300_REG_VALUE_16BIT }, + { IMX300_REG_LINE_LEN_PCK, 8008, IMX300_REG_VALUE_16BIT }, + { IMX300_REG_X_ADDR_START, 232, IMX300_REG_VALUE_16BIT }, + { IMX300_REG_Y_ADDR_START, 0, IMX300_REG_VALUE_16BIT }, + { IMX300_REG_X_ADDR_END, 5751, IMX300_REG_VALUE_16BIT }, + { IMX300_REG_Y_ADDR_END, 4159, IMX300_REG_VALUE_16BIT }, + { IMX300_REG_X_OUTPUT_SIZE, 5520, IMX300_REG_VALUE_16BIT }, + { IMX300_REG_Y_OUTPUT_SIZE, 4160, IMX300_REG_VALUE_16BIT }, + + { 0x0408, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x0409, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x040a, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x040b, 0x00, IMX300_REG_VALUE_08BIT }, + + { 0x040c, 5520, IMX300_REG_VALUE_16BIT }, + { 0x040e, 4160, IMX300_REG_VALUE_16BIT }, + + { 0x3150, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3151, 0x49, IMX300_REG_VALUE_08BIT }, + { 0x3152, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3153, 0x20, IMX300_REG_VALUE_08BIT }, + { 0x3154, 0x02, IMX300_REG_VALUE_08BIT }, + { 0x3155, 0x9f, IMX300_REG_VALUE_08BIT }, + { 0x3156, 0x02, IMX300_REG_VALUE_08BIT }, + { 0x3157, 0xaa, IMX300_REG_VALUE_08BIT }, + + { 0x31e8, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x31e9, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x31ec, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x31f0, 0x02, IMX300_REG_VALUE_08BIT }, + { 0x31f1, 0xbc, IMX300_REG_VALUE_08BIT }, + { 0x3221, 0x01, IMX300_REG_VALUE_08BIT }, + + { 0x7ea0, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x7ea3, 0x08, IMX300_REG_VALUE_08BIT }, + { 0x7ea5, 0x0f, IMX300_REG_VALUE_08BIT }, + { 0x7ea6, 0x60, IMX300_REG_VALUE_08BIT }, + { 0x7ea8, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x7ea9, 0x78, IMX300_REG_VALUE_08BIT }, + { 0x7eac, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x7eb3, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x7eb4, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x7eb5, 0x00, IMX300_REG_VALUE_08BIT }, +}; + + +static const struct imx300_reg mode_5984x3392[] = { + { IMX300_REG_EXPOSURE, 3556, IMX300_REG_VALUE_16BIT }, + { IMX300_REG_ANALOG_GAIN, 0, IMX300_REG_VALUE_16BIT }, + + { IMX300_REG_VTS, 3576, IMX300_REG_VALUE_16BIT }, + { IMX300_REG_LINE_LEN_PCK, 8224, IMX300_REG_VALUE_16BIT }, + { IMX300_REG_X_ADDR_START, 0, IMX300_REG_VALUE_16BIT }, + { IMX300_REG_Y_ADDR_START, 384, IMX300_REG_VALUE_16BIT }, + { IMX300_REG_X_ADDR_END, 5751, IMX300_REG_VALUE_16BIT }, + { IMX300_REG_Y_ADDR_END, 3775, IMX300_REG_VALUE_16BIT }, + { IMX300_REG_X_OUTPUT_SIZE, 5984, IMX300_REG_VALUE_16BIT }, + { IMX300_REG_Y_OUTPUT_SIZE, 3392, IMX300_REG_VALUE_16BIT }, + + { 0x0408, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x0409, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x040a, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x040b, 0x00, IMX300_REG_VALUE_08BIT }, + + { 0x040c, 5984, IMX300_REG_VALUE_16BIT }, + { 0x040e, 3392, IMX300_REG_VALUE_16BIT }, + + { 0x3150, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3151, 0x50, IMX300_REG_VALUE_08BIT }, + { 0x3152, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3153, 0x1a, IMX300_REG_VALUE_08BIT }, + { 0x3154, 0x02, IMX300_REG_VALUE_08BIT }, + { 0x3155, 0xd8, IMX300_REG_VALUE_08BIT }, + { 0x3156, 0x02, IMX300_REG_VALUE_08BIT }, + { 0x3157, 0x2c, IMX300_REG_VALUE_08BIT }, + + { 0x31e8, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x31e9, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x31ec, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x31f0, 0x03, IMX300_REG_VALUE_08BIT }, + { 0x31f1, 0x84, IMX300_REG_VALUE_08BIT }, + { 0x3221, 0x00, IMX300_REG_VALUE_08BIT }, + + { 0x7ea0, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x7ea3, 0x08, IMX300_REG_VALUE_08BIT }, + { 0x7ea5, 0x0f, IMX300_REG_VALUE_08BIT }, + { 0x7ea6, 0x60, IMX300_REG_VALUE_08BIT }, + { 0x7ea8, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x7ea9, 0x78, IMX300_REG_VALUE_08BIT }, + { 0x7eac, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x7eb3, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x7eb4, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x7eb5, 0x00, IMX300_REG_VALUE_08BIT }, +}; + +static const struct imx300_reg mode_2992x1696[] = { + { IMX300_REG_EXPOSURE, 3566, IMX300_REG_VALUE_16BIT }, + { IMX300_REG_ANALOG_GAIN, 0, IMX300_REG_VALUE_16BIT }, + + { IMX300_REG_VTS, 1772, IMX300_REG_VALUE_16BIT }, + { IMX300_REG_LINE_LEN_PCK, 7064, IMX300_REG_VALUE_16BIT }, + { IMX300_REG_X_ADDR_START, 0, IMX300_REG_VALUE_16BIT }, + { IMX300_REG_Y_ADDR_START, 384, IMX300_REG_VALUE_16BIT }, + { IMX300_REG_X_ADDR_END, 5983, IMX300_REG_VALUE_16BIT }, + { IMX300_REG_Y_ADDR_END, 3775, IMX300_REG_VALUE_16BIT }, + { IMX300_REG_X_OUTPUT_SIZE, 2992, IMX300_REG_VALUE_16BIT }, + { IMX300_REG_Y_OUTPUT_SIZE, 1696, IMX300_REG_VALUE_16BIT }, + + { 0x0408, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x0409, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x040a, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x040b, 0x00, IMX300_REG_VALUE_08BIT }, + + { 0x040c, 2992, IMX300_REG_VALUE_16BIT }, + { 0x040e, 1696, IMX300_REG_VALUE_16BIT }, + + { 0x3150, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3151, 0x28, IMX300_REG_VALUE_08BIT }, + { 0x3152, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3153, 0x0d, IMX300_REG_VALUE_08BIT }, + { 0x3154, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x3155, 0x6c, IMX300_REG_VALUE_08BIT }, + { 0x3156, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x3157, 0x16, IMX300_REG_VALUE_08BIT }, + + { 0x31e8, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x31e9, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x31ec, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x31f0, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x31f1, 0x14, IMX300_REG_VALUE_08BIT }, + { 0x3221, 0x00, IMX300_REG_VALUE_08BIT }, + + { 0x7ea0, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x7ea3, 0x05, IMX300_REG_VALUE_08BIT }, + { 0x7ea5, 0x1e, IMX300_REG_VALUE_08BIT }, + { 0x7ea6, 0x60, IMX300_REG_VALUE_08BIT }, + { 0x7ea8, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x7ea9, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x7eac, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x7eb3, 0x04, IMX300_REG_VALUE_08BIT }, + { 0x7eb4, 0x0c, IMX300_REG_VALUE_08BIT }, + { 0x7eb5, 0x10, IMX300_REG_VALUE_08BIT }, +}; + +static const struct imx300_reg mode_1424x800[] = { + { IMX300_REG_EXPOSURE, 3692, IMX300_REG_VALUE_16BIT }, + { IMX300_REG_ANALOG_GAIN, 0, IMX300_REG_VALUE_16BIT }, + + { IMX300_REG_VTS, 3712, IMX300_REG_VALUE_16BIT }, + { IMX300_REG_LINE_LEN_PCK, 7064, IMX300_REG_VALUE_16BIT }, + { IMX300_REG_X_ADDR_START, 1568, IMX300_REG_VALUE_16BIT }, + { IMX300_REG_Y_ADDR_START, 1280, IMX300_REG_VALUE_16BIT }, + { IMX300_REG_X_ADDR_END, 4415, IMX300_REG_VALUE_16BIT }, + { IMX300_REG_Y_ADDR_END, 2879, IMX300_REG_VALUE_16BIT }, + { IMX300_REG_X_OUTPUT_SIZE, 1424, IMX300_REG_VALUE_16BIT }, + { IMX300_REG_Y_OUTPUT_SIZE, 800, IMX300_REG_VALUE_16BIT }, + + { 0x0408, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x0409, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x040a, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x040b, 0x00, IMX300_REG_VALUE_08BIT }, + + { 0x040c, 1424, IMX300_REG_VALUE_16BIT }, + { 0x040e, 800, IMX300_REG_VALUE_16BIT }, + + { 0x3150, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3151, 0x13, IMX300_REG_VALUE_08BIT }, + { 0x3152, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3153, 0x06, IMX300_REG_VALUE_08BIT }, + { 0x3154, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3155, 0xad, IMX300_REG_VALUE_08BIT }, + { 0x3156, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x3157, 0x83, IMX300_REG_VALUE_08BIT }, + + { 0x31e8, 0x01, IMX300_REG_VALUE_08BIT }, + { 0x31e9, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x31ec, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x31f0, 0x02, IMX300_REG_VALUE_08BIT }, + { 0x31f1, 0xbc, IMX300_REG_VALUE_08BIT }, + { 0x3221, 0x01, IMX300_REG_VALUE_08BIT }, + + { 0x7ea0, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x7ea3, 0x08, IMX300_REG_VALUE_08BIT }, + { 0x7ea5, 0x0f, IMX300_REG_VALUE_08BIT }, + { 0x7ea6, 0x60, IMX300_REG_VALUE_08BIT }, + { 0x7ea8, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x7ea9, 0x78, IMX300_REG_VALUE_08BIT }, + { 0x7eac, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x7eb3, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x7eb4, 0x00, IMX300_REG_VALUE_08BIT }, + { 0x7eb5, 0x00, IMX300_REG_VALUE_08BIT }, +}; + +/* Output bits per sample: This sensor support RAW8 and RAW10 */ +static const struct imx300_reg raw8_framefmt_regs[] = { + { IMX300_REG_CSI_DATA_FORMAT_HI, 8, IMX300_REG_VALUE_08BIT }, + { IMX300_REG_CSI_DATA_FORMAT_LO, 8, IMX300_REG_VALUE_08BIT }, + { IMX300_REG_OP_PIX_CLK_DIV, 8, IMX300_REG_VALUE_08BIT }, +}; + +static const struct imx300_reg raw10_framefmt_regs[] = { + { IMX300_REG_CSI_DATA_FORMAT_HI, 10, IMX300_REG_VALUE_08BIT }, + { IMX300_REG_CSI_DATA_FORMAT_LO, 10, IMX300_REG_VALUE_08BIT }, + { IMX300_REG_OP_PIX_CLK_DIV, 10, IMX300_REG_VALUE_08BIT }, +}; + +static const char * const imx300_test_pattern_menu[] = { + "Disabled", + "Solid Color", + "Color Bars Solid", + "Color Bars Fade To Gray", + "Pseudorandom Sequence (PN9)" +}; + +static const int imx300_test_pattern_val[] = { + IMX300_TEST_PATTERN_DISABLE, + IMX300_TEST_PATTERN_SOLID_COLOR, + IMX300_TEST_PATTERN_COLOR_BARS, + IMX300_TEST_PATTERN_GREY_COLOR, + IMX300_TEST_PATTERN_PN9, +}; + +/* regulator supplies */ +static const char * const imx300_supply_name[] = { + /* Supplies can be enabled in any order */ + "VANA", /* Analog (2.2V) supply */ + "VDIG", /* Digital Core (1.15-1.20V) supply */ + "VDDL", /* IF (1.8V) supply */ +}; + +#define IMX300_NUM_SUPPLIES ARRAY_SIZE(imx300_supply_name) + +/* + * The supported formats. + * This table MUST contain 4 entries per format, to cover the various flip + * combinations in the order + * - no flip + * - h flip + * - v flip + * - h&v flips + */ +static const u32 codes[] = { + MEDIA_BUS_FMT_SRGGB10_1X10, + MEDIA_BUS_FMT_SGRBG10_1X10, + MEDIA_BUS_FMT_SGBRG10_1X10, + MEDIA_BUS_FMT_SBGGR10_1X10, + + MEDIA_BUS_FMT_SRGGB8_1X8, + MEDIA_BUS_FMT_SGRBG8_1X8, + MEDIA_BUS_FMT_SGBRG8_1X8, + MEDIA_BUS_FMT_SBGGR8_1X8, +}; + +/* Mode configs */ +static const struct imx300_mode supported_modes[] = { + { + /* 23MP 23fps mode */ + .width = 5520, + .height = 4160, + .max_fps = 23, + .crop = { + .left = IMX300_PIXEL_ARRAY_LEFT, + .top = IMX300_PIXEL_ARRAY_TOP, + .width = 5520, + .height = 4160 + }, + .vts_def = 4250, + .high_bw = true, + .binned = false, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_5520x4160), + .regs = mode_5520x4160, + }, + }, + { + /* 20.3MP 26fps mode */ + .width = 5984, + .height = 3392, + .max_fps = 26, + .crop = { + .left = IMX300_PIXEL_ARRAY_LEFT, + .top = 384, + .width = 5984, + .height = 3392 + }, + .vts_def = 3576, + .high_bw = true, + .binned = false, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_5984x3392), + .regs = mode_5984x3392, + }, + }, + { + /* 3.8MP 60fps 2x2 binning */ + .width = 2992, + .height = 1696, + .max_fps = 60, + .crop = { + .left = IMX300_PIXEL_ARRAY_LEFT, + .top = 384, + .width = 2292, + .height = 1696 + }, + .vts_def = 1792, + .high_bw = true, + .binned = true, + .reg_list = { + .num_of_regs = + ARRAY_SIZE(mode_2992x1696), + .regs = mode_2992x1696, + }, + }, + { + /* 1.2MP binned 120fps mode */ + .width = 1424, + .height = 800, + .max_fps = 120, + .crop = { + .left = IMX300_PIXEL_ARRAY_LEFT, + .top = 384, + .width = 1424, + .height = 800, + }, + .vts_def = 896, + .high_bw = false, + .binned = true, + .reg_list = { + .num_of_regs = + ARRAY_SIZE(mode_1424x800), + .regs = mode_1424x800, + }, + }, +}; + +struct imx300 { + struct v4l2_subdev sd; + struct media_pad pad; + + struct v4l2_mbus_framefmt fmt; + + struct clk *xclk; /* system clock to IMX300 */ + u32 xclk_freq; + + struct gpio_desc *reset_gpio; + struct regulator_bulk_data supplies[IMX300_NUM_SUPPLIES]; + + struct v4l2_ctrl_handler ctrl_handler; + /* V4L2 Controls */ + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + + /* Frame rate */ + struct v4l2_fract frame_rate; + + /* Current mode */ + const struct imx300_mode *mode; + u32 cur_bps; + + /* + * Mutex for serialized access: + * Protect sensor module set pad format and start/stop streaming safely. + */ + struct mutex mutex; + + /* Streaming on/off */ + bool streaming; +}; + +static inline struct imx300 *to_imx300(struct v4l2_subdev *_sd) +{ + return container_of(_sd, struct imx300, sd); +} + +static s64 get_pixel_rate(struct imx300 *imx300) +{ + s64 prate; + + if (imx300->mode->high_bw) + prate = IMX300_HIGH_BW_PIXEL_RATE; + else + prate = IMX300_LOW_BW_PIXEL_RATE; + + /* Satisfy the settle time for 8bits */ + if (imx300->cur_bps == 8) { + prate = div_s64(prate, (s32)10); + prate *= 12; + } + + return prate; +} + +/* Read registers up to 2 at a time */ +static int imx300_read_reg(struct imx300 *imx300, u16 reg, u32 len, u32 *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx300->sd); + struct i2c_msg msgs[2]; + u8 addr_buf[2] = { reg >> 8, reg & 0xff }; + u8 data_buf[4] = { 0, }; + int ret; + + if (len > 4) + return -EINVAL; + + /* Write register address */ + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = ARRAY_SIZE(addr_buf); + msgs[0].buf = addr_buf; + + /* Read data from register */ + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = &data_buf[4 - len]; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *val = get_unaligned_be32(data_buf); + + return 0; +} + +/* Write registers up to 4 at a time */ +static int imx300_write_reg(struct imx300 *imx300, u16 reg, u32 len, u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx300->sd); + u8 buf[6]; + + if (len > 4) + return -EINVAL; + + put_unaligned_be16(reg, buf); + put_unaligned_be32(val << (8 * (4 - len)), buf + 2); + if (i2c_master_send(client, buf, len + 2) != len + 2) + return -EIO; + + return 0; +} + +/* Write a list of registers */ +static int imx300_write_regs(struct imx300 *imx300, + const struct imx300_reg *regs, u32 len) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx300->sd); + unsigned int i; + int ret; + + for (i = 0; i < len; i++) { + ret = imx300_write_reg(imx300, regs[i].address, + regs[i].reg_len, + regs[i].val); + if (ret) { + dev_err_ratelimited(&client->dev, + "Cannot write reg 0x%4.4x. (%d)\n", + regs[i].address, ret); + + return ret; + } + } + + return 0; +} + +/* Get bayer order based on flip setting. */ +static u32 imx300_get_format_code(struct imx300 *imx300, u32 code) +{ + unsigned int i; + + lockdep_assert_held(&imx300->mutex); + + for (i = 0; i < ARRAY_SIZE(codes); i++) + if (codes[i] == code) + break; + + if (i >= ARRAY_SIZE(codes)) + i = 0; + + i = (i & ~3) | (imx300->vflip->val ? 2 : 0) | + (imx300->hflip->val ? 1 : 0); + + return codes[i]; +} + +static void imx300_set_default_format(struct imx300 *imx300) +{ + struct v4l2_mbus_framefmt *fmt; + + fmt = &imx300->fmt; + fmt->code = MEDIA_BUS_FMT_SRGGB10_1X10; + fmt->colorspace = V4L2_COLORSPACE_SRGB; + fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace); + fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true, + fmt->colorspace, + fmt->ycbcr_enc); + fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace); + fmt->width = supported_modes[0].width; + fmt->height = supported_modes[0].height; + fmt->field = V4L2_FIELD_NONE; +} + +static int imx300_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct imx300 *imx300 = to_imx300(sd); + struct v4l2_mbus_framefmt *try_fmt = + v4l2_subdev_get_try_format(sd, fh->pad, 0); + struct v4l2_rect *try_crop; + + mutex_lock(&imx300->mutex); + + /* Initialize try_fmt */ + try_fmt->width = supported_modes[0].width; + try_fmt->height = supported_modes[0].height; + try_fmt->code = imx300_get_format_code(imx300, + MEDIA_BUS_FMT_SRGGB10_1X10); + try_fmt->field = V4L2_FIELD_NONE; + + /* Initialize try_crop rectangle. */ + try_crop = v4l2_subdev_get_try_crop(sd, fh->pad, 0); + try_crop->top = IMX300_PIXEL_ARRAY_TOP; + try_crop->left = IMX300_PIXEL_ARRAY_LEFT; + try_crop->width = IMX300_PIXEL_ARRAY_WIDTH; + try_crop->height = IMX300_PIXEL_ARRAY_HEIGHT; + + mutex_unlock(&imx300->mutex); + + return 0; +} + +static int imx300_update_digital_gain(struct imx300 *imx300, u32 gain) +{ + int ret; + + ret = imx300_write_reg(imx300, IMX300_REG_GR_DIGITAL_GAIN, + IMX300_REG_VALUE_16BIT, + gain); + if (ret) + return ret; + + ret = imx300_write_reg(imx300, IMX300_REG_GB_DIGITAL_GAIN, + IMX300_REG_VALUE_16BIT, + gain); + if (ret) + return ret; + + ret = imx300_write_reg(imx300, IMX300_REG_R_DIGITAL_GAIN, + IMX300_REG_VALUE_16BIT, + gain); + if (ret) + return ret; + + ret = imx300_write_reg(imx300, IMX300_REG_B_DIGITAL_GAIN, + IMX300_REG_VALUE_16BIT, + gain); + if (ret) + return ret; + + return 0; +} + +static int imx300_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct imx300 *imx300 = + container_of(ctrl->handler, struct imx300, ctrl_handler); + struct i2c_client *client = v4l2_get_subdevdata(&imx300->sd); + int ret; + + if (ctrl->id == V4L2_CID_VBLANK) { + int exposure_max, exposure_def; + + /* Update max exposure while meeting expected vblanking */ + exposure_max = imx300->mode->height + ctrl->val - 10; + exposure_def = (exposure_max < IMX300_EXPOSURE_DEFAULT) ? + exposure_max : IMX300_EXPOSURE_DEFAULT; + __v4l2_ctrl_modify_range(imx300->exposure, + imx300->exposure->minimum, + exposure_max, imx300->exposure->step, + exposure_def); + } + + /* + * Applying V4L2 control value only happens + * when power is up for streaming + */ + if (pm_runtime_get_if_in_use(&client->dev) == 0) + return 0; + + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + ret = imx300_write_reg(imx300, IMX300_REG_ANALOG_GAIN, + IMX300_REG_VALUE_16BIT, + ctrl->val); + break; + case V4L2_CID_EXPOSURE: + ret = imx300_write_reg(imx300, IMX300_REG_EXPOSURE, + IMX300_REG_VALUE_16BIT, + ctrl->val); + break; + case V4L2_CID_DIGITAL_GAIN: + ret = imx300_update_digital_gain(imx300, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: + ret = imx300_write_reg(imx300, IMX300_REG_TEST_PATTERN, + IMX300_REG_VALUE_16BIT, + imx300_test_pattern_val[ctrl->val]); + break; + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + ret = imx300_write_reg(imx300, IMX300_REG_ORIENTATION, + IMX300_REG_VALUE_08BIT, + imx300->hflip->val | + imx300->vflip->val << 1); + break; + case V4L2_CID_VBLANK: + ret = imx300_write_reg(imx300, IMX300_REG_VTS, + IMX300_REG_VALUE_16BIT, + imx300->mode->height + ctrl->val); + break; + case V4L2_CID_TEST_PATTERN_RED: + ret = imx300_write_reg(imx300, IMX300_REG_TESTP_RED, + IMX300_REG_VALUE_16BIT, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN_GREENR: + ret = imx300_write_reg(imx300, IMX300_REG_TESTP_GREENR, + IMX300_REG_VALUE_16BIT, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN_BLUE: + ret = imx300_write_reg(imx300, IMX300_REG_TESTP_BLUE, + IMX300_REG_VALUE_16BIT, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN_GREENB: + ret = imx300_write_reg(imx300, IMX300_REG_TESTP_GREENB, + IMX300_REG_VALUE_16BIT, ctrl->val); + break; + default: + dev_info(&client->dev, + "ctrl(id:0x%x,val:0x%x) is not handled\n", + ctrl->id, ctrl->val); + ret = -EINVAL; + break; + } + + pm_runtime_put(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops imx300_ctrl_ops = { + .s_ctrl = imx300_set_ctrl, +}; + +static int imx300_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct imx300 *imx300 = to_imx300(sd); + + if (code->index >= (ARRAY_SIZE(codes) / 4)) + return -EINVAL; + + code->code = imx300_get_format_code(imx300, codes[code->index * 4]); + + return 0; +} + +static int imx300_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct imx300 *imx300 = to_imx300(sd); + + if (fse->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + + if (fse->code != imx300_get_format_code(imx300, fse->code)) + return -EINVAL; + + fse->min_width = supported_modes[fse->index].width; + fse->max_width = fse->min_width; + fse->min_height = supported_modes[fse->index].height; + fse->max_height = fse->min_height; + + return 0; +} + +static int imx300_enum_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_interval_enum *fie) +{ + unsigned int i; + + if (fie->pad || fie->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(supported_modes); i++) + if (fie->width == supported_modes[i].width && + fie->height == supported_modes[i].height) + break; + + if (i == ARRAY_SIZE(supported_modes)) + return -EINVAL; + + fie->interval.numerator = 1; + fie->interval.denominator = supported_modes[i].max_fps; + + return 0; +} + +static int imx300_g_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *ival) +{ + struct imx300 *imx300 = to_imx300(sd); + + ival->interval.numerator = imx300->frame_rate.denominator; + ival->interval.denominator = imx300->frame_rate.numerator; + + return 0; +} + +static int imx300_s_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *ival) +{ + struct imx300 *imx300 = to_imx300(sd); + const struct imx300_mode *cur_mode = imx300->mode; + struct v4l2_fract *tpf = &ival->interval; + int exposure_max, exposure_def; + u32 new_vts; + u32 vblank = 0; + + if (tpf->numerator == 0 || tpf->denominator == 0 || + (tpf->denominator > tpf->numerator * cur_mode->max_fps)) { + /* reset to max frame rate */ + tpf->numerator = 1; + tpf->denominator = cur_mode->max_fps; + new_vts = cur_mode->vts_def; + } else { + /* Approximation of new VTS: recalculate default vblank */ + vblank = cur_mode->vts_def - cur_mode->height; + + /* Avoid floating point */ + new_vts = vblank * 1000; + new_vts = new_vts / cur_mode->max_fps; + new_vts = (new_vts * tpf->denominator) / 1000; + new_vts += vblank + cur_mode->height; + } + + imx300->frame_rate.numerator = tpf->numerator; + imx300->frame_rate.denominator = tpf->denominator; + + /* + * Note: VTS cannot be less than cur_mode->height, but that's useless + * to check at this point, since we are surely complying here. + * + * Now that we've got a new VTS, let's update the exposure control + * min/max in order to avoid impossible and/or useless combinations. + */ + exposure_max = new_vts - 4; + exposure_def = (exposure_max < IMX300_EXPOSURE_DEFAULT) ? + exposure_max : IMX300_EXPOSURE_DEFAULT; + __v4l2_ctrl_modify_range(imx300->exposure, + imx300->exposure->minimum, + exposure_max, imx300->exposure->step, + exposure_def); + + return imx300_write_reg(imx300, IMX300_REG_VTS, + IMX300_REG_VALUE_16BIT, + new_vts); +} + +static void imx300_reset_colorspace(struct v4l2_mbus_framefmt *fmt) +{ + fmt->colorspace = V4L2_COLORSPACE_SRGB; + fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace); + fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true, + fmt->colorspace, + fmt->ycbcr_enc); + fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace); +} + +static void imx300_update_pad_format(struct imx300 *imx300, + const struct imx300_mode *mode, + struct v4l2_subdev_format *fmt) +{ + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.field = V4L2_FIELD_NONE; + imx300_reset_colorspace(&fmt->format); +} + +static int __imx300_get_pad_format(struct imx300 *imx300, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_mbus_framefmt *try_fmt = + v4l2_subdev_get_try_format(&imx300->sd, cfg, fmt->pad); + /* update the code which could change due to vflip or hflip: */ + try_fmt->code = imx300_get_format_code(imx300, try_fmt->code); + fmt->format = *try_fmt; + } else { + imx300_update_pad_format(imx300, imx300->mode, fmt); + fmt->format.code = imx300_get_format_code(imx300, + imx300->fmt.code); + } + + return 0; +} + +static int imx300_get_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct imx300 *imx300 = to_imx300(sd); + int ret; + + mutex_lock(&imx300->mutex); + ret = __imx300_get_pad_format(imx300, cfg, fmt); + mutex_unlock(&imx300->mutex); + + return ret; +} + +static int imx300_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct imx300 *imx300 = to_imx300(sd); + const struct imx300_mode *mode; + struct v4l2_mbus_framefmt *framefmt; + int exposure_max, exposure_def, hblank; + unsigned int i; + + mutex_lock(&imx300->mutex); + + for (i = 0; i < ARRAY_SIZE(codes); i++) + if (codes[i] == fmt->format.code) + break; + if (i >= ARRAY_SIZE(codes)) + i = 0; + + /* Bayer order varies with flips */ + fmt->format.code = imx300_get_format_code(imx300, codes[i]); + + mode = v4l2_find_nearest_size(supported_modes, + ARRAY_SIZE(supported_modes), + width, height, + fmt->format.width, fmt->format.height); + imx300_update_pad_format(imx300, mode, fmt); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + framefmt = v4l2_subdev_get_try_format(sd, cfg, fmt->pad); + *framefmt = fmt->format; + } else if (imx300->mode != mode || + imx300->fmt.code != fmt->format.code) { + imx300->fmt = fmt->format; + imx300->mode = mode; + /* Update limits and set FPS to default */ + __v4l2_ctrl_modify_range(imx300->vblank, IMX300_VTS_MIN, + IMX300_VTS_MAX - mode->height, 1, + mode->vts_def - mode->height); + __v4l2_ctrl_s_ctrl(imx300->vblank, + mode->vts_def - mode->height); + /* Update max exposure while meeting expected vblanking */ + exposure_max = mode->vts_def - 4; + exposure_def = (exposure_max < IMX300_EXPOSURE_DEFAULT) ? + exposure_max : IMX300_EXPOSURE_DEFAULT; + __v4l2_ctrl_modify_range(imx300->exposure, + imx300->exposure->minimum, + exposure_max, imx300->exposure->step, + exposure_def); + /* + * Currently PPL is fixed to IMX300_PPL_DEFAULT, so hblank + * depends on mode->width only, and is not changeble in any + * way other than changing the mode. + */ + hblank = IMX300_PPL_DEFAULT - mode->width; + __v4l2_ctrl_modify_range(imx300->hblank, hblank, hblank, 1, + hblank); + } + + mutex_unlock(&imx300->mutex); + + return 0; +} + +static int imx300_set_framefmt(struct imx300 *imx300) +{ + int ret; + + switch (imx300->fmt.code) { + case MEDIA_BUS_FMT_SRGGB8_1X8: + case MEDIA_BUS_FMT_SGRBG8_1X8: + case MEDIA_BUS_FMT_SGBRG8_1X8: + case MEDIA_BUS_FMT_SBGGR8_1X8: + ret = imx300_write_regs(imx300, raw8_framefmt_regs, + ARRAY_SIZE(raw8_framefmt_regs)); + if (ret) + return ret; + imx300->cur_bps = 8; + break; + case MEDIA_BUS_FMT_SRGGB10_1X10: + case MEDIA_BUS_FMT_SGRBG10_1X10: + case MEDIA_BUS_FMT_SGBRG10_1X10: + case MEDIA_BUS_FMT_SBGGR10_1X10: + ret = imx300_write_regs(imx300, raw10_framefmt_regs, + ARRAY_SIZE(raw10_framefmt_regs)); + if (ret) + return ret; + imx300->cur_bps = 10; + break; + } + + /* Update the pixel rate to eventually save some power */ + __v4l2_ctrl_s_ctrl_int64(imx300->pixel_rate, get_pixel_rate(imx300)); + + return ret; +} + +static const struct v4l2_rect * +__imx300_get_pad_crop(struct imx300 *imx300, struct v4l2_subdev_pad_config *cfg, + unsigned int pad, enum v4l2_subdev_format_whence which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_crop(&imx300->sd, cfg, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &imx300->mode->crop; + } + + return NULL; +} + +static int imx300_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + switch (sel->target) { + case V4L2_SEL_TGT_CROP: { + struct imx300 *imx300 = to_imx300(sd); + + mutex_lock(&imx300->mutex); + sel->r = *__imx300_get_pad_crop(imx300, cfg, sel->pad, + sel->which); + mutex_unlock(&imx300->mutex); + + return 0; + } + + case V4L2_SEL_TGT_NATIVE_SIZE: + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = IMX300_NATIVE_WIDTH; + sel->r.height = IMX300_NATIVE_HEIGHT; + + return 0; + + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.top = IMX300_PIXEL_ARRAY_TOP; + sel->r.left = IMX300_PIXEL_ARRAY_LEFT; + sel->r.width = IMX300_PIXEL_ARRAY_WIDTH; + sel->r.height = IMX300_PIXEL_ARRAY_HEIGHT; + + return 0; + } + + return -EINVAL; +} + +static int imx300_start_streaming(struct imx300 *imx300) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx300->sd); + const struct imx300_reg_list *reg_list; + int ret; + + ret = imx300_write_regs(imx300, init_sequence, + ARRAY_SIZE(init_sequence)); + if (ret) { + dev_err(&client->dev, + "Cannot write init sequence\n"); + return ret; + } + + if (imx300->mode->high_bw) + ret = imx300_write_regs(imx300, mipi_data_rate_1560mbps, + ARRAY_SIZE(mipi_data_rate_1560mbps)); + else + ret = imx300_write_regs(imx300, mipi_data_rate_960mbps, + ARRAY_SIZE(mipi_data_rate_960mbps)); + if (ret) { + dev_err(&client->dev, "Failed to set MIPI data rate\n"); + return ret; + } + + /* Common between all resolutions */ + ret = imx300_write_regs(imx300, mode_common_regs, + ARRAY_SIZE(mode_common_regs)); + if (ret) { + dev_err(&client->dev, + "Cannot write init sequence\n"); + return ret; + } + + /* Apply default values of the selected mode */ + reg_list = &imx300->mode->reg_list; + ret = imx300_write_regs(imx300, reg_list->regs, reg_list->num_of_regs); + if (ret) { + dev_err(&client->dev, "Failed to set resolution\n"); + return ret; + } + + if (imx300->mode->binned) + ret = imx300_write_regs(imx300, binning_mode_2x2, + ARRAY_SIZE(binning_mode_2x2)); + else + ret = imx300_write_regs(imx300, binning_mode_off, + ARRAY_SIZE(binning_mode_off)); + if (ret) { + dev_err(&client->dev, "Failed to set binning mode\n"); + return ret; + } + + ret = imx300_set_framefmt(imx300); + if (ret) { + dev_err(&client->dev, "%s failed to set frame format: %d\n", + __func__, ret); + return ret; + } + + /* Apply customized values from user */ + ret = __v4l2_ctrl_handler_setup(imx300->sd.ctrl_handler); + if (ret) + return ret; + + /* set stream on register */ + return imx300_write_reg(imx300, IMX300_REG_MODE_SELECT, + IMX300_REG_VALUE_08BIT, IMX300_MODE_STREAMING); +} + +static void imx300_stop_streaming(struct imx300 *imx300) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx300->sd); + int ret; + + /* set stream off register */ + ret = imx300_write_reg(imx300, IMX300_REG_MODE_SELECT, + IMX300_REG_VALUE_08BIT, IMX300_MODE_STANDBY); + if (ret) + dev_err(&client->dev, "%s failed to set stream\n", __func__); +} + +static int imx300_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct imx300 *imx300 = to_imx300(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret = 0; + + mutex_lock(&imx300->mutex); + if (imx300->streaming == enable) { + mutex_unlock(&imx300->mutex); + return 0; + } + + if (enable) { + ret = pm_runtime_get_sync(&client->dev); + if (ret < 0) { + pm_runtime_put_noidle(&client->dev); + goto err_unlock; + } + + /* + * Apply default & customized values + * and then start streaming. + */ + ret = imx300_start_streaming(imx300); + if (ret) + goto err_rpm_put; + } else { + imx300_stop_streaming(imx300); + pm_runtime_put(&client->dev); + } + + imx300->streaming = enable; + + /* vflip and hflip cannot change during streaming */ + __v4l2_ctrl_grab(imx300->vflip, enable); + __v4l2_ctrl_grab(imx300->hflip, enable); + + mutex_unlock(&imx300->mutex); + + return ret; + +err_rpm_put: + pm_runtime_put(&client->dev); +err_unlock: + mutex_unlock(&imx300->mutex); + + return ret; +} + +/* Power/clock management functions */ +static int imx300_power_on(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx300 *imx300 = to_imx300(sd); + int ret; + + ret = regulator_bulk_enable(IMX300_NUM_SUPPLIES, + imx300->supplies); + if (ret) { + dev_err(&client->dev, "%s: failed to enable regulators\n", + __func__); + return ret; + } + + ret = clk_prepare_enable(imx300->xclk); + if (ret) { + dev_err(&client->dev, "%s: failed to enable clock\n", + __func__); + goto reg_off; + } + + /* Wait for the internal PLLs stabilization time */ + usleep_range(IMX300_XCLK_STABLE_DELAY_US, + IMX300_XCLK_STABLE_DELAY_US + IMX300_XCLK_DELAY_RANGE_US); + + /* PLLs are stable now: get out of reset! */ + gpiod_set_value_cansleep(imx300->reset_gpio, 1); + usleep_range(IMX300_XCLR_MIN_DELAY_US, + IMX300_XCLR_MIN_DELAY_US + IMX300_XCLR_DELAY_RANGE_US); + + return 0; + +reg_off: + regulator_bulk_disable(IMX300_NUM_SUPPLIES, imx300->supplies); + + return ret; +} + +static int imx300_power_off(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx300 *imx300 = to_imx300(sd); + + gpiod_set_value_cansleep(imx300->reset_gpio, 0); + regulator_bulk_disable(IMX300_NUM_SUPPLIES, imx300->supplies); + clk_disable_unprepare(imx300->xclk); + + return 0; +} + +static int __maybe_unused imx300_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx300 *imx300 = to_imx300(sd); + + if (imx300->streaming) + imx300_stop_streaming(imx300); + + return 0; +} + +static int __maybe_unused imx300_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx300 *imx300 = to_imx300(sd); + int ret; + + if (imx300->streaming) { + ret = imx300_start_streaming(imx300); + if (ret) + goto error; + } + + return 0; + +error: + imx300_stop_streaming(imx300); + imx300->streaming = false; + + return ret; +} + +static int imx300_get_regulators(struct imx300 *imx300) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx300->sd); + unsigned int i; + + for (i = 0; i < IMX300_NUM_SUPPLIES; i++) + imx300->supplies[i].supply = imx300_supply_name[i]; + + return devm_regulator_bulk_get(&client->dev, + IMX300_NUM_SUPPLIES, + imx300->supplies); +} + +/* Verify chip ID */ +static int imx300_identify_module(struct imx300 *imx300) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx300->sd); + int ret; + u32 val; + + ret = imx300_read_reg(imx300, IMX300_REG_CHIP_ID, + IMX300_REG_VALUE_16BIT, &val); + if (ret) { + dev_err(&client->dev, "failed to read chip id %x\n", + IMX300_CHIP_ID); + return ret; + } + + if (val != IMX300_CHIP_ID) { + dev_err(&client->dev, "chip id mismatch: %x!=%x\n", + IMX300_CHIP_ID, val); + return -EIO; + } + + return 0; +} + +static const struct v4l2_subdev_core_ops imx300_core_ops = { + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_video_ops imx300_video_ops = { + .s_stream = imx300_set_stream, + .g_frame_interval = imx300_g_frame_interval, + .s_frame_interval = imx300_s_frame_interval, +}; + +static const struct v4l2_subdev_pad_ops imx300_pad_ops = { + .enum_mbus_code = imx300_enum_mbus_code, + .get_fmt = imx300_get_pad_format, + .set_fmt = imx300_set_pad_format, + .get_selection = imx300_get_selection, + .enum_frame_size = imx300_enum_frame_size, + .enum_frame_interval = imx300_enum_frame_interval, +}; + +static const struct v4l2_subdev_ops imx300_subdev_ops = { + .core = &imx300_core_ops, + .video = &imx300_video_ops, + .pad = &imx300_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops imx300_internal_ops = { + .open = imx300_open, +}; + +/* Initialize control handlers */ +static int imx300_init_controls(struct imx300 *imx300) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx300->sd); + struct v4l2_ctrl_handler *ctrl_hdlr; + unsigned int height = imx300->mode->height; + struct v4l2_fwnode_device_properties props; + int exposure_max, exposure_def, hblank; + int i, ret; + + ctrl_hdlr = &imx300->ctrl_handler; + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 11); + if (ret) + return ret; + + mutex_init(&imx300->mutex); + ctrl_hdlr->lock = &imx300->mutex; + + /* By default, PIXEL_RATE is read only */ + imx300->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &imx300_ctrl_ops, + V4L2_CID_PIXEL_RATE, + IMX300_LOW_BW_PIXEL_RATE, + IMX300_HIGH_BW_PIXEL_RATE, 1, + IMX300_HIGH_BW_PIXEL_RATE); + + /* Initial vblank/hblank/exposure parameters based on current mode */ + imx300->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx300_ctrl_ops, + V4L2_CID_VBLANK, IMX300_VTS_MIN, + IMX300_VTS_MAX - height, 1, + imx300->mode->vts_def - height); + hblank = IMX300_PPL_DEFAULT - imx300->mode->width; + imx300->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx300_ctrl_ops, + V4L2_CID_HBLANK, hblank, hblank, + 1, hblank); + if (imx300->hblank) + imx300->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + exposure_max = imx300->mode->vts_def - 4; + exposure_def = (exposure_max < IMX300_EXPOSURE_DEFAULT) ? + exposure_max : IMX300_EXPOSURE_DEFAULT; + imx300->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &imx300_ctrl_ops, + V4L2_CID_EXPOSURE, + IMX300_EXPOSURE_MIN, exposure_max, + IMX300_EXPOSURE_STEP, + exposure_def); + + v4l2_ctrl_new_std(ctrl_hdlr, &imx300_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + IMX300_ANA_GAIN_MIN, IMX300_ANA_GAIN_MAX, + IMX300_ANA_GAIN_STEP, IMX300_ANA_GAIN_DEFAULT); + + v4l2_ctrl_new_std(ctrl_hdlr, &imx300_ctrl_ops, V4L2_CID_DIGITAL_GAIN, + IMX300_DGTL_GAIN_MIN, IMX300_DGTL_GAIN_MAX, + IMX300_DGTL_GAIN_STEP, IMX300_DGTL_GAIN_DEFAULT); + + imx300->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx300_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + if (imx300->hflip) + imx300->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + imx300->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx300_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + if (imx300->vflip) + imx300->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &imx300_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(imx300_test_pattern_menu) - 1, + 0, 0, imx300_test_pattern_menu); + for (i = 0; i < 4; i++) { + /* + * The assumption is that + * V4L2_CID_TEST_PATTERN_GREENR == V4L2_CID_TEST_PATTERN_RED + 1 + * V4L2_CID_TEST_PATTERN_BLUE == V4L2_CID_TEST_PATTERN_RED + 2 + * V4L2_CID_TEST_PATTERN_GREENB == V4L2_CID_TEST_PATTERN_RED + 3 + */ + v4l2_ctrl_new_std(ctrl_hdlr, &imx300_ctrl_ops, + V4L2_CID_TEST_PATTERN_RED + i, + IMX300_TESTP_COLOUR_MIN, + IMX300_TESTP_COLOUR_MAX, + IMX300_TESTP_COLOUR_STEP, + IMX300_TESTP_COLOUR_MAX); + /* The "Solid color" pattern is white by default */ + } + + if (ctrl_hdlr->error) { + ret = ctrl_hdlr->error; + dev_err(&client->dev, "%s control init failed (%d)\n", + __func__, ret); + goto error; + } + + ret = v4l2_fwnode_device_parse(&client->dev, &props); + if (ret) + goto error; + + ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &imx300_ctrl_ops, + &props); + if (ret) + goto error; + + imx300->sd.ctrl_handler = ctrl_hdlr; + + return 0; + +error: + v4l2_ctrl_handler_free(ctrl_hdlr); + mutex_destroy(&imx300->mutex); + + return ret; +} + +static void imx300_free_controls(struct imx300 *imx300) +{ + v4l2_ctrl_handler_free(imx300->sd.ctrl_handler); + mutex_destroy(&imx300->mutex); +} + +static int imx300_match_link_freq(u64 link_freq) +{ + if (link_freq == IMX300_HIGH_BW_LINK_FREQ || + link_freq == IMX300_LOW_BW_LINK_FREQ) + return 0; + + return -EINVAL; +} + +static int imx300_check_hwcfg(struct device *dev, struct imx300 *imx300) +{ + struct fwnode_handle *endpoint; + struct v4l2_fwnode_endpoint ep_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + int ret = -EINVAL; + + endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL); + if (!endpoint) { + dev_err(dev, "Endpoint node not found\n"); + return -EINVAL; + } + + if (v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep_cfg)) { + dev_err(dev, "Cannot parse endpoint\n"); + goto error_out; + } + + /* Check the link frequency set in device tree */ + if (ep_cfg.nr_of_link_frequencies != 2) { + dev_err(dev, "This sensor uses two link frequencies.\n"); + goto error_out; + } + + /* Check the number of MIPI CSI2 data lanes */ + if (ep_cfg.bus.mipi_csi2.num_data_lanes != 4) { + dev_err(dev, "This sensor needs 4 MIPI Lanes!\n"); + goto error_out; + } + + if (imx300_match_link_freq(ep_cfg.link_frequencies[0]) || + imx300_match_link_freq(ep_cfg.link_frequencies[1])) { + dev_err(dev, "Unsupported link frequencies.\n"); + goto error_out; + } + + ret = 0; + +error_out: + v4l2_fwnode_endpoint_free(&ep_cfg); + fwnode_handle_put(endpoint); + + return ret; +} + +static int imx300_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct imx300 *imx300; + int ret; + + imx300 = devm_kzalloc(&client->dev, sizeof(*imx300), GFP_KERNEL); + if (!imx300) + return -ENOMEM; + + v4l2_i2c_subdev_init(&imx300->sd, client, &imx300_subdev_ops); + + /* Check the hardware configuration in device tree */ + if (imx300_check_hwcfg(dev, imx300)) + return -EINVAL; + + /* Get system clock (xclk) */ + imx300->xclk = devm_clk_get(dev, NULL); + if (IS_ERR(imx300->xclk)) { + dev_err(dev, "failed to get xclk\n"); + return PTR_ERR(imx300->xclk); + } + + imx300->xclk_freq = clk_get_rate(imx300->xclk); + if (imx300->xclk_freq != IMX300_XCLK_FREQ_24M) { + dev_err(dev, "xclk frequency not supported: %d Hz\n", + imx300->xclk_freq); + return -EINVAL; + } + + ret = imx300_get_regulators(imx300); + if (ret) { + dev_err(dev, "failed to get regulators\n"); + return ret; + } + + /* Request optional enable pin */ + imx300->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + + /* + * The sensor must be powered for imx300_identify_module() + * to be able to read the CHIP_ID register + */ + ret = imx300_power_on(dev); + if (ret) + return ret; + + ret = imx300_identify_module(imx300); + if (ret) + goto error_power_off; + + /* Set default mode to max resolution */ + imx300->mode = &supported_modes[0]; + + /* Default is 10 bits per sample */ + imx300->cur_bps = 10; + + /* + * Sensor doesn't enter LP-11 state upon power up until and unless + * streaming is started, so upon power up switch the modes to: + * streaming -> standby + */ + ret = imx300_write_reg(imx300, IMX300_REG_MODE_SELECT, + IMX300_REG_VALUE_08BIT, IMX300_MODE_STREAMING); + if (ret < 0) + goto error_power_off; + + /* put sensor back to standby mode */ + ret = imx300_write_reg(imx300, IMX300_REG_MODE_SELECT, + IMX300_REG_VALUE_08BIT, IMX300_MODE_STANDBY); + if (ret < 0) + goto error_power_off; + + ret = imx300_init_controls(imx300); + if (ret) + goto error_power_off; + + /* Initialize subdev */ + imx300->sd.internal_ops = &imx300_internal_ops; + imx300->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + imx300->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + /* Initialize source pad */ + imx300->pad.flags = MEDIA_PAD_FL_SOURCE; + + /* Initialize default format */ + imx300_set_default_format(imx300); + + ret = media_entity_pads_init(&imx300->sd.entity, 1, &imx300->pad); + if (ret) { + dev_err(dev, "failed to init entity pads: %d\n", ret); + goto error_handler_free; + } + + ret = v4l2_async_register_subdev_sensor_common(&imx300->sd); + if (ret < 0) { + dev_err(dev, "failed to register sensor subdevice: %d\n", ret); + goto error_media_entity; + } + + /* Enable runtime PM and turn off the device */ + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); + + /* Due to the sensor id being odd, let's inform about us... */ + dev_info(dev, "Sony Exmor-RS IMX300 camera sensor is up.\n"); + + return 0; + +error_media_entity: + media_entity_cleanup(&imx300->sd.entity); + +error_handler_free: + imx300_free_controls(imx300); + +error_power_off: + imx300_power_off(dev); + + return ret; +} + +static int imx300_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx300 *imx300 = to_imx300(sd); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + imx300_free_controls(imx300); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + imx300_power_off(&client->dev); + pm_runtime_set_suspended(&client->dev); + + return 0; +} + +static const struct of_device_id imx300_dt_ids[] = { + { .compatible = "sony,imx300" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx300_dt_ids); + +static const struct dev_pm_ops imx300_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(imx300_suspend, imx300_resume) + SET_RUNTIME_PM_OPS(imx300_power_off, imx300_power_on, NULL) +}; + +static struct i2c_driver imx300_i2c_driver = { + .driver = { + .name = "imx300", + .of_match_table = imx300_dt_ids, + .pm = &imx300_pm_ops, + }, + .probe_new = imx300_probe, + .remove = imx300_remove, +}; + +module_i2c_driver(imx300_i2c_driver); + +MODULE_AUTHOR("AngeloGioacchino Del Regno "); +MODULE_DESCRIPTION("Sony Exmor-RS IMX300 camera sensor driver"); +MODULE_LICENSE("GPL v2"); From 932b09a72018011a8846a57e6a5241bebd676eec Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Sun, 18 Oct 2020 00:03:02 +0200 Subject: [PATCH 079/187] media: dt-bindings: media: i2c: Add IMX300 CMOS sensor binding Add YAML device tree binding for IMX300 CMOS image sensor, and the relevant MAINTAINERS entries. Signed-off-by: AngeloGioacchino Del Regno --- .../bindings/media/i2c/sony,imx300.yaml | 112 ++++++++++++++++++ MAINTAINERS | 7 ++ 2 files changed, 119 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/i2c/sony,imx300.yaml diff --git a/Documentation/devicetree/bindings/media/i2c/sony,imx300.yaml b/Documentation/devicetree/bindings/media/i2c/sony,imx300.yaml new file mode 100644 index 000000000000..4fa767feea80 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/sony,imx300.yaml @@ -0,0 +1,112 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/sony,imx300.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Sony 1/2.3-Inch 25Mpixel Stacked CMOS Digital Image Sensor + +maintainers: + - AngeloGioacchino Del Regno + +description: |- + The Sony IMX300 is a 1/2.3-inch Stacked CMOS (Exmor-RS) digital image + sensor with a pixel size of 1.08um and an active array size of + 5948H x 4140V. It is programmable through I2C interface at address 0x10. + Image data is sent through MIPI CSI-2, which is configured as either 2 or + 4 data lanes. + +properties: + compatible: + const: sony,imx300 + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + vdig-supply: + description: + Digital I/O voltage supply, 1.15-1.20 volts + + vana-supply: + description: + Analog voltage supply, 2.2 volts + + vddl-supply: + description: + Digital core voltage supply, 1.8 volts + + reset-gpios: + description: |- + Reference to the GPIO connected to the xclr pin, if any. + Must be released (set high) after all supplies are applied. + + # See ../video-interfaces.txt for more details + port: + type: object + properties: + endpoint: + type: object + + properties: + data-lanes: + description: |- + The driver only supports four-lane operation. + items: + - const: 0 + - const: 1 + - const: 2 + - const: 3 + + clock-noncontinuous: true + + link-frequencies: + $ref: /schemas/types.yaml#/definitions/uint64-array + description: + Allowed data bus frequencies. The driver currently needs + to switch between 780000000 and 480000000 Hz in order to + guarantee functionality of all modes. + + required: + - data-lanes + - link-frequencies + +required: + - compatible + - reg + - clocks + - vana-supply + - vdig-supply + - vddl-supply + - port + +additionalProperties: false + +examples: + - | + i2c0 { + #address-cells = <1>; + #size-cells = <0>; + + imx300: sensor@10 { + compatible = "sony,imx300"; + reg = <0x10>; + clocks = <&imx300_xclk>; + vana-supply = <&imx300_vana>; /* 2.2v */ + vdig-supply = <&imx300_vdig>; /* 1.2v */ + vddl-supply = <&imx300_vddl>; /* 1.8v */ + + port { + imx300_0: endpoint { + remote-endpoint = <&csi1_ep>; + data-lanes = <0 1 2 3>; + clock-noncontinuous; + link-frequencies = /bits/ 64 <780000000 480000000>; + }; + }; + }; + }; + +... diff --git a/MAINTAINERS b/MAINTAINERS index 945c6a5e9833..f9f02b7fdcc9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17764,6 +17764,13 @@ T: git git://linuxtv.org/media_tree.git F: Documentation/devicetree/bindings/media/i2c/imx290.txt F: drivers/media/i2c/imx290.c +SONY IMX300 SENSOR DRIVER +M: AngeloGioacchino Del Regno +L: linux-media@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/media/i2c/sony,imx300.yaml +F: drivers/media/i2c/imx300.c + SONY IMX319 SENSOR DRIVER M: Bingbu Cao L: linux-media@vger.kernel.org From 298422843e459aa896f2ed498855a324ef1213d9 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Wed, 23 Sep 2020 16:54:33 +0200 Subject: [PATCH 080/187] dt-bindings: Add vendor prefix for Novatek Microelectronics Corp. Add prefix for Novatek Microelectronics Corp. Signed-off-by: AngeloGioacchino Del Regno --- Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index 66d6432fd781..789027c95871 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -828,6 +828,8 @@ patternProperties: description: Nokia "^nordic,.*": description: Nordic Semiconductor + "^novatek,.*": + description: Novatek Microelectronics Corp. "^novtech,.*": description: NovTech, Inc. "^nutsboard,.*": From b2d9f95754e7850b69117154682fdb1429e0c5a1 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Wed, 23 Sep 2020 06:14:29 +0200 Subject: [PATCH 081/187] Input: Add Novatek NT36xxx touchscreen driver This is a driver for the Novatek in-cell touch controller and supports various chips from the NT36xxx family, currently including NT36525, NT36672A, NT36676F, NT36772 and NT36870. Functionality like wake gestures and firmware flashing is not included: I am not aware of any of these DrIC+Touch combo chips not including a non-volatile memory and it should be highly unlikely to find one, since the touch firmware is embedded into the DriverIC one, which is obviously necessary to drive the display unit. However, the necessary address for the firmware update procedure was included into the address table in this driver so, in the event that someone finds the need to implement it for a reason or another, it will be pretty straightforward to. This driver is lightly based on the downstream implementation [1]. [1] https://github.com/Rasenkai/caf-tsoft-Novatek-nt36xxx Signed-off-by: AngeloGioacchino Del Regno --- drivers/input/touchscreen/Kconfig | 12 + drivers/input/touchscreen/Makefile | 1 + drivers/input/touchscreen/nt36xxx.c | 894 ++++++++++++++++++++++++++++ drivers/input/touchscreen/nt36xxx.h | 122 ++++ 4 files changed, 1029 insertions(+) create mode 100644 drivers/input/touchscreen/nt36xxx.c create mode 100644 drivers/input/touchscreen/nt36xxx.h diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index 2f6adfb7b938..720a568fdccf 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -638,6 +638,18 @@ config TOUCHSCREEN_MTOUCH To compile this driver as a module, choose M here: the module will be called mtouch. +config TOUCHSCREEN_NT36XXX + tristate "Novatek NT36XXX In-Cell I2C touchscreen controller" + depends on I2C + help + Say Y here if you have a Novatek NT36xxx series In-Cell + touchscreen connected to your system over I2C. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called nt36xxx. + config TOUCHSCREEN_IMX6UL_TSC tristate "Freescale i.MX6UL touchscreen controller" depends on ((OF && GPIOLIB) || COMPILE_TEST) && HAS_IOMEM diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 39a8127cf6a5..f2515b5cb99d 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -64,6 +64,7 @@ obj-$(CONFIG_TOUCHSCREEN_MMS114) += mms114.o obj-$(CONFIG_TOUCHSCREEN_MSG2638) += msg2638.o obj-$(CONFIG_TOUCHSCREEN_MTOUCH) += mtouch.o obj-$(CONFIG_TOUCHSCREEN_MK712) += mk712.o +obj-$(CONFIG_TOUCHSCREEN_NT36XXX) += nt36xxx.o obj-$(CONFIG_TOUCHSCREEN_HP600) += hp680_ts_input.o obj-$(CONFIG_TOUCHSCREEN_HP7XX) += jornada720_ts.o obj-$(CONFIG_TOUCHSCREEN_IPAQ_MICRO) += ipaq-micro-ts.o diff --git a/drivers/input/touchscreen/nt36xxx.c b/drivers/input/touchscreen/nt36xxx.c new file mode 100644 index 000000000000..a572d2b87464 --- /dev/null +++ b/drivers/input/touchscreen/nt36xxx.c @@ -0,0 +1,894 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for Novatek NT36xxx series touchscreens + * + * Copyright (C) 2010 - 2017 Novatek, Inc. + * Copyright (C) 2020 AngeloGioacchino Del Regno + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* FW Param address */ +#define NT36XXX_FW_ADDR 0x01 + +/* Number of bytes for chip identification */ +#define NT36XXX_ID_LEN_MAX 6 + +/* Touch info */ +#define TOUCH_DEFAULT_MAX_WIDTH 1080 +#define TOUCH_DEFAULT_MAX_HEIGHT 2246 +#define TOUCH_MAX_FINGER_NUM 10 +#define TOUCH_MAX_PRESSURE 1000 + +/* Point data length */ +#define POINT_DATA_LEN 65 + +/* Global pages */ +#define NT36XXX_PAGE_CHIP_INFO 0x0001f64e +#define NT36XXX_PAGE_CRC 0x0003f135 + +/* Misc */ +#define NT36XXX_NUM_SUPPLIES 2 +#define NT36XXX_MAX_RETRIES 5 +#define NT36XXX_MAX_FW_RST_RETRY 50 + +struct nt36xxx_abs_object { + u16 x; + u16 y; + u16 z; + u8 tm; +}; + +struct nt36xxx_fw_info { + u8 fw_ver; + u8 x_num; + u8 y_num; + u8 max_buttons; + u16 abs_x_max; + u16 abs_y_max; + u16 nvt_pid; +}; + +struct nt36xxx_mem_map { + u32 evtbuf_addr; + u32 pipe0_addr; + u32 pipe1_addr; + u32 flash_csum_addr; + u32 flash_data_addr; +}; + +struct nt36xxx_i2c { + struct i2c_client *hw_client; + struct i2c_client *fw_client; + struct regmap *regmap; + struct regmap *fw_regmap; + struct input_dev *input; + struct regulator_bulk_data *supplies; + struct gpio_desc *reset_gpio; + + struct mutex lock; + + struct touchscreen_properties prop; + struct nt36xxx_fw_info fw_info; + struct nt36xxx_abs_object abs_obj; + + const struct nt36xxx_mem_map *mmap; +}; + +enum nt36xxx_chips { + NT36525_IC = 0, + NT36672A_IC, + NT36676F_IC, + NT36772_IC, + NT36870_IC, + NTMAX_IC, +}; + +struct nt36xxx_trim_table { + u8 id[NT36XXX_ID_LEN_MAX]; + u8 mask[NT36XXX_ID_LEN_MAX]; + enum nt36xxx_chips mapid; +}; + +enum nt36xxx_cmds { + NT36XXX_CMD_ENTER_SLEEP = 0x11, + NT36XXX_CMD_ENTER_WKUP_GESTURE = 0x13, + NT36XXX_CMD_UNLOCK = 0x35, + NT36XXX_CMD_BOOTLOADER_RESET = 0x69, + NT36XXX_CMD_SW_RESET = 0xa5, + NT36XXX_CMD_SET_PAGE = 0xff, +}; + +/** + * enum nt36xxx_fw_state - Firmware state + * @NT36XXX_STATE_INIT: IC Reset + * @NT36XXX_STATE_REK: ReK baseline + * @NT36XXX_STATE_REK_FINISH: Baseline is ready + * @NT36XXX_STATE_NORMAL_RUN: Firmware is running + */ +enum nt36xxx_fw_state { + NT36XXX_STATE_INIT = 0xa0, + NT36XXX_STATE_REK, + NT36XXX_STATE_REK_FINISH, + NT36XXX_STATE_NORMAL_RUN, + NT36XXX_STATE_MAX = 0xaf +}; + +enum nt36xxx_i2c_events { + NT36XXX_EVT_REPORT = 0x00, + NT36XXX_EVT_CRC = 0x35, + NT36XXX_EVT_CHIPID = 0x4e, + NT36XXX_EVT_HOST_CMD = 0x50, + NT36XXX_EVT_HS_OR_SUBCMD = 0x51, /* Handshake or subcommand byte */ + NT36XXX_EVT_RESET_COMPLETE = 0x60, + NT36XXX_EVT_FWINFO = 0x78, + NT36XXX_EVT_PROJECTID = 0x9a, +}; + +static const struct nt36xxx_mem_map nt36xxx_memory_maps[] = { + [NT36525_IC] = { 0x11a00, 0x10000, 0x12000, 0x14000, 0x14002 }, + [NT36672A_IC] = { 0x21c00, 0x20000, 0x23000, 0x24000, 0x24002 }, + [NT36676F_IC] = { 0x11a00, 0x10000, 0x12000, 0x14000, 0x14002 }, + [NT36772_IC] = { 0x11e00, 0x10000, 0x12000, 0x14000, 0x14002 }, + [NT36870_IC] = { 0x25000, 0x20000, 0x23000, 0x24000, 0x24002 }, +}; + +static const struct nt36xxx_trim_table trim_id_table[] = { + { + .id = { 0x0A, 0xFF, 0xFF, 0x72, 0x66, 0x03 }, + .mask = { 1, 0, 0, 1, 1, 1 }, + .mapid = NT36672A_IC, + }, + { + .id = { 0x55, 0x00, 0xFF, 0x00, 0x00, 0x00 }, + .mask = { 1, 1, 0, 1, 1, 1 }, + .mapid = NT36772_IC, + }, + { + .id = { 0x55, 0x72, 0xFF, 0x00, 0x00, 0x00 }, + .mask = { 1, 1, 0, 1, 1, 1 }, + .mapid = NT36772_IC, + }, + { + .id = { 0xAA, 0x00, 0xFF, 0x00, 0x00, 0x00 }, + .mask = { 1, 1, 0, 1, 1, 1 }, + .mapid = NT36772_IC, + }, + { + .id = { 0xAA, 0x72, 0xFF, 0x00, 0x00, 0x00 }, + .mask = { 1, 1, 0, 1, 1, 1 }, + .mapid = NT36772_IC, + }, + { + .id = { 0xFF, 0xFF, 0xFF, 0x72, 0x67, 0x03 }, + .mask = { 0, 0, 0, 1, 1, 1 }, + .mapid = NT36772_IC, + }, + { + .id = { 0xFF, 0xFF, 0xFF, 0x70, 0x66, 0x03 }, + .mask = { 0, 0, 0, 1, 1, 1 }, + .mapid = NT36772_IC, + }, + { + .id = { 0xFF, 0xFF, 0xFF, 0x70, 0x67, 0x03 }, + .mask = { 0, 0, 0, 1, 1, 1 }, + .mapid = NT36772_IC, + }, + { + .id = { 0xFF, 0xFF, 0xFF, 0x72, 0x66, 0x03 }, + .mask = { 0, 0, 0, 1, 1, 1 }, + .mapid = NT36772_IC, + }, + { + .id = { 0xFF, 0xFF, 0xFF, 0x25, 0x65, 0x03 }, + .mask = { 0, 0, 0, 1, 1, 1 }, + .mapid = NT36772_IC, + }, + { + .id = { 0xFF, 0xFF, 0xFF, 0x70, 0x68, 0x03 }, + .mask = { 0, 0, 0, 1, 1, 1 }, + .mapid = NT36772_IC, + }, + { + .id = { 0xFF, 0xFF, 0xFF, 0x76, 0x66, 0x03 }, + .mask = { 0, 0, 0, 1, 1, 1 }, + .mapid = NT36676F_IC, + }, +}; + +/** + * nt36xxx_set_page - Set page number for read/write + * @ts: Main driver structure + * + * Return: Always zero for success, negative number for error + */ +static int nt36xxx_set_page(struct nt36xxx_i2c *ts, u32 pageaddr) +{ + u32 data = cpu_to_be32(pageaddr) >> 8; + int ret; + + ret = regmap_noinc_write(ts->fw_regmap, NT36XXX_CMD_SET_PAGE, + &data, sizeof(data)); + if (ret) + return ret; + + usleep_range(100, 200); + return ret; +} + +/** + * nt36xxx_sw_reset_idle - Warm restart the firmware + * @ts: Main driver structure + * + * This function restarts the running firmware without rebooting to + * the bootloader (warm restart) + * + * Return: Always zero for success, negative number for error + */ +static int nt36xxx_sw_reset_idle(struct nt36xxx_i2c *ts) +{ + int ret; + + ret = regmap_write(ts->regmap, ts->hw_client->addr, + NT36XXX_CMD_SW_RESET); + if (ret) + return ret; + + /* Wait until the MCU resets the fw state */ + usleep_range(15000, 16000); + return ret; +} + +/** + * nt36xxx_bootloader_reset - Reset MCU to bootloader + * @ts: Main driver structure + * + * Return: Always zero for success, negative number for error + */ +static int nt36xxx_bootloader_reset(struct nt36xxx_i2c *ts) +{ + int ret; + + ret = regmap_write(ts->regmap, ts->hw_client->addr, + NT36XXX_CMD_BOOTLOADER_RESET); + if (ret) + return ret; + + /* MCU has to reboot from bootloader: this is the typical boot time */ + msleep(35); + return ret; +} + +/** + * nt36xxx_check_reset_state - Check the boot state during reset + * @ts: Main driver structure + * @fw_state: Enumeration containing firmware states + * + * Return: Always zero for success, negative number for error + */ +static int nt36xxx_check_reset_state(struct nt36xxx_i2c *ts, + enum nt36xxx_fw_state fw_state) +{ + u8 buf[2] = { 0 }; + int ret, retry = NT36XXX_MAX_FW_RST_RETRY; + + do { + ret = regmap_noinc_read(ts->fw_regmap, + NT36XXX_EVT_RESET_COMPLETE, + buf, sizeof(buf)); + if (likely(ret == 0) && + (buf[0] >= fw_state) && + (buf[0] <= NT36XXX_STATE_MAX)) { + ret = 0; + break; + } + usleep_range(10000, 11000); + } while (--retry); + + if (!retry) { + dev_err(&ts->hw_client->dev, "Firmware reset failed.\n"); + ret = -EBUSY; + } + + return ret; +} + +/** + * nt36xxx_read_pid - Read Novatek Project ID + * @ts: Main driver structure + * + * Return: Always zero for success, negative number for error + */ +static int nt36xxx_read_pid(struct nt36xxx_i2c *ts) +{ + __be16 pid; + int ret; + + ret = nt36xxx_set_page(ts, ts->mmap->evtbuf_addr); + if (ret) + return ret; + + ret = regmap_noinc_read(ts->fw_regmap, NT36XXX_EVT_PROJECTID, + &pid, sizeof(pid)); + if (ret < 0) + return ret; + + ts->fw_info.nvt_pid = be16_to_cpu(pid); + return 0; +} + +/** + * __nt36xxx_get_fw_info - Get working params from firmware + * @ts: Main driver structure + * + * Return: Always zero for success, negative number for error + */ +static int __nt36xxx_get_fw_info(struct nt36xxx_i2c *ts) +{ + struct nt36xxx_fw_info *fwi = &ts->fw_info; + u8 buf[11] = { 0 }; + int ret = 0; + + ret = nt36xxx_set_page(ts, ts->mmap->evtbuf_addr); + if (ret) + return ret; + + ret = regmap_noinc_read(ts->fw_regmap, NT36XXX_EVT_FWINFO, + buf, sizeof(buf)); + if (ret) + return ret; + + fwi->fw_ver = buf[0]; + fwi->x_num = buf[2]; + fwi->y_num = buf[3]; + fwi->abs_x_max = get_unaligned_be16(&buf[4]); + fwi->abs_y_max = get_unaligned_be16(&buf[6]); + fwi->max_buttons = buf[10]; + + /* Check fw info integrity and clear x_num, y_num if broken */ + if ((buf[0] + buf[1]) != 0xFF) { + dev_err(&ts->hw_client->dev, + "FW info is broken! fw_ver=0x%02X, ~fw_ver=0x%02X\n", + buf[0], buf[1]); + fwi->fw_ver = 0; + fwi->x_num = 18; + fwi->y_num = 32; + fwi->abs_x_max = TOUCH_DEFAULT_MAX_WIDTH; + fwi->abs_y_max = TOUCH_DEFAULT_MAX_HEIGHT; + fwi->max_buttons = 0; + return -EINVAL; + } + + /* Get Novatek ProjectID */ + return nt36xxx_read_pid(ts); +} + +static int nt36xxx_get_fw_info(struct nt36xxx_i2c *ts) +{ + struct nt36xxx_fw_info *fwi = &ts->fw_info; + int i, ret = 0; + + for (i = 0; i < NT36XXX_MAX_RETRIES; i++) { + ret = __nt36xxx_get_fw_info(ts); + if (ret == 0) + break; + } + + dev_dbg(&ts->hw_client->dev, + "FW Info: PID=0x%x, ver=0x%x res=%ux%u max=%ux%u buttons=%u", + fwi->nvt_pid, fwi->fw_ver, fwi->x_num, fwi->y_num, + fwi->abs_x_max, fwi->abs_y_max, fwi->max_buttons); + + return ret; +} + +/** + * nt36xxx_report - Report touch events + * @ts: Main driver structure + * + * Return: Always zero for success, negative number for error + */ +static void nt36xxx_report(struct nt36xxx_i2c *ts) +{ + struct nt36xxx_abs_object *obj = &ts->abs_obj; + struct input_dev *input = ts->input; + u8 input_id = 0; + u8 point[POINT_DATA_LEN + 1] = { 0 }; + unsigned int ppos = 0; + int i, ret, finger_cnt = 0; + + mutex_lock(&ts->lock); + + ret = regmap_noinc_read(ts->fw_regmap, NT36XXX_EVT_REPORT, + point, sizeof(point)); + if (ret < 0) { + dev_err(&ts->hw_client->dev, + "Cannot read touch point data: %d\n", ret); + goto xfer_error; + } + + for (i = 0; i < TOUCH_MAX_FINGER_NUM; i++) { + ppos = 6 * i; + input_id = point[ppos + 0] >> 3; + if ((input_id == 0) || (input_id > TOUCH_MAX_FINGER_NUM)) + continue; + + if (((point[ppos] & 0x07) == 0x01) || + ((point[ppos] & 0x07) == 0x02)) { + obj->x = (point[ppos + 1] << 4) + + (point[ppos + 3] >> 4); + obj->y = (point[ppos + 2] << 4) + + (point[ppos + 3] & 0xf); + if ((obj->x > ts->prop.max_x) || + (obj->y > ts->prop.max_y)) + continue; + + obj->tm = point[ppos + 4]; + if (obj->tm == 0) + obj->tm = 1; + + obj->z = point[ppos + 5]; + if (i < 2) { + obj->z += point[i + 63] << 8; + if (obj->z > TOUCH_MAX_PRESSURE) + obj->z = TOUCH_MAX_PRESSURE; + } + + if (obj->z == 0) + obj->z = 1; + + input_mt_slot(input, input_id - 1); + input_mt_report_slot_state(input, + MT_TOOL_FINGER, true); + touchscreen_report_pos(input, &ts->prop, obj->x, + obj->y, true); + + input_report_abs(input, ABS_MT_TOUCH_MAJOR, obj->tm); + input_report_abs(input, ABS_MT_PRESSURE, obj->z); + + finger_cnt++; + } + } + input_mt_sync_frame(input); + input_sync(input); + +xfer_error: + enable_irq(ts->hw_client->irq); + + mutex_unlock(&ts->lock); +} + +static irqreturn_t nt36xxx_i2c_irq_handler(int irq, void *dev_id) +{ + struct nt36xxx_i2c *ts = dev_id; + + disable_irq_nosync(ts->hw_client->irq); + nt36xxx_report(ts); + + return IRQ_HANDLED; +} + +static bool nt36xxx_in_crc_reboot_loop(u8 *buf) +{ + return ((buf[0] == 0xFC) && (buf[1] == 0xFC) && (buf[2] == 0xFC)) || + ((buf[0] == 0xFF) && (buf[1] == 0xFF) && (buf[2] == 0xFF)); +} + +/** + * nt36xxx_stop_crc_reboot - Stop CRC reboot loop and warm-reboot the firmware + * @ts: Main driver structure + * + * Return: Always zero for success, negative number for error + */ +static int nt36xxx_stop_crc_reboot(struct nt36xxx_i2c *ts) +{ + u8 buf[3] = { 0 }; + u8 val; + int ret, retry = NT36XXX_MAX_RETRIES; + + /* Read dummy buffer to check CRC fail reboot is happening or not */ + + /* Change I2C index to prevent getting 0xFF, but not 0xFC */ + ret = nt36xxx_set_page(ts, NT36XXX_PAGE_CHIP_INFO); + if (ret) { + dev_dbg(&ts->hw_client->dev, + "CRC reset failed: Cannot select page.\n"); + return ret; + } + + /* If ChipID command returns 0xFC or 0xFF, the MCU is in CRC reboot */ + ret = regmap_noinc_read(ts->fw_regmap, NT36XXX_EVT_CHIPID, + buf, sizeof(buf)); + if (ret) + return ret; + + if (!nt36xxx_in_crc_reboot_loop(buf)) + return 0; + + /* IC is in CRC fail reboot loop, needs to be stopped! */ + do { + /* Special reset-idle sequence for CRC failure */ + ret = regmap_write(ts->regmap, ts->hw_client->addr, + NT36XXX_CMD_SW_RESET); + if (ret) + dev_dbg(&ts->hw_client->dev, + "SW Reset 1 failed: may not recover\n"); + + ret = regmap_write(ts->regmap, ts->hw_client->addr, + NT36XXX_CMD_SW_RESET); + if (ret) + dev_dbg(&ts->hw_client->dev, + "SW Reset 2 failed: may not recover\n"); + usleep_range(1000, 1100); + + /* Clear CRC_ERR_FLAG */ + ret = nt36xxx_set_page(ts, NT36XXX_PAGE_CRC); + if (ret) + continue; + + val = 0xA5; + ret = regmap_raw_write(ts->fw_regmap, NT36XXX_EVT_CRC, + &val, sizeof(val)); + if (ret) + continue; + + /* Check CRC_ERR_FLAG */ + ret = nt36xxx_set_page(ts, NT36XXX_PAGE_CRC); + if (ret) + continue; + + ret = regmap_noinc_read(ts->fw_regmap, NT36XXX_EVT_CRC, + &buf, sizeof(buf)); + if (ret) + continue; + + if (buf[0] == 0xA5) + break; + } while (--retry); + + if (retry == 0) { + dev_err(&ts->hw_client->dev, + "CRC reset failed: buf=0x%2ph\n", buf); + } + + return ret; +} + +/** + * nt36xxx_i2c_chip_version_init - Detect Novatek NT36xxx family IC + * @ts: Main driver structure + * + * This function reads the ChipID from the IC and sets the right + * memory map for the detected chip. + * + * Return: Always zero for success, negative number for error + */ +static int nt36xxx_i2c_chip_version_init(struct nt36xxx_i2c *ts) +{ + u8 buf[7] = { 0 }; + int retry = NT36XXX_MAX_RETRIES; + int sz = sizeof(trim_id_table) / sizeof(struct nt36xxx_trim_table); + int i, list, mapid, ret; + + ret = nt36xxx_bootloader_reset(ts); + if (ret) { + dev_err(&ts->hw_client->dev, "Can't reset the nvt IC\n"); + return ret; + } + + do { + ret = nt36xxx_sw_reset_idle(ts); + if (ret) + continue; + + ret = regmap_write(ts->regmap, ts->hw_client->addr, NT36XXX_CMD_UNLOCK); + if (ret) + continue; + usleep_range(10000, 11000); + + ret = nt36xxx_set_page(ts, NT36XXX_PAGE_CHIP_INFO); + if (ret) + continue; + + memset(buf, 0, ARRAY_SIZE(buf)); + ret = regmap_noinc_read(ts->fw_regmap, NT36XXX_EVT_CHIPID, + buf, sizeof(buf)); + if (ret) + continue; + + /* Compare read chip id with trim list */ + for (list = 0; list < sz; list++) { + /* Compare each not masked byte */ + for (i = 0; i < NT36XXX_ID_LEN_MAX; i++) { + if (trim_id_table[list].mask[i] && + buf[i] != trim_id_table[list].id[i]) + break; + } + + if (i == NT36XXX_ID_LEN_MAX) { + mapid = trim_id_table[list].mapid; + ts->mmap = &nt36xxx_memory_maps[mapid]; + return 0; + } + + ts->mmap = NULL; + ret = -ENOENT; + } + + /* Stop CRC check to prevent IC auto reboot */ + if (nt36xxx_in_crc_reboot_loop(buf)) { + ret = nt36xxx_stop_crc_reboot(ts); + if (ret) + continue; + } + + usleep_range(10000, 11000); + } while (--retry); + + return ret; +} + +static const struct regmap_config nt36xxx_i2c_regmap_hw_config = { + .name = "nt36xxx_i2c_hw", + .reg_bits = 8, + .val_bits = 8, + .cache_type = REGCACHE_NONE, +}; + +static const struct regmap_config nt36xxx_i2c_regmap_fw_config = { + .name = "nt36xxx_i2c_fw", + .reg_bits = 8, + .val_bits = 8, + .cache_type = REGCACHE_NONE, +}; + +static void nt36xxx_disable_regulators(void *data) +{ + struct nt36xxx_i2c *ts = data; + + regulator_bulk_disable(NT36XXX_NUM_SUPPLIES, ts->supplies); +} + +static int nt36xxx_i2c_probe(struct i2c_client *hw_client, + const struct i2c_device_id *id) +{ + struct nt36xxx_i2c *ts; + struct input_dev *input; + int ret; + + if (!i2c_check_functionality(hw_client->adapter, I2C_FUNC_I2C)) { + dev_err(&hw_client->dev, "i2c_check_functionality error\n"); + return -EIO; + } + + if (!hw_client->irq) { + dev_err(&hw_client->dev, "No irq specified\n"); + return -EINVAL; + } + + ts = devm_kzalloc(&hw_client->dev, sizeof(*ts), GFP_KERNEL); + if (!ts) + return -ENOMEM; + + ts->supplies = devm_kcalloc(&hw_client->dev, + NT36XXX_NUM_SUPPLIES, + sizeof(*ts->supplies), + GFP_KERNEL); + if (!ts->supplies) + return -ENOMEM; + + input = devm_input_allocate_device(&hw_client->dev); + if (!input) + return -ENOMEM; + + ts->fw_client = i2c_new_dummy_device(hw_client->adapter, + NT36XXX_FW_ADDR); + if (IS_ERR(ts->fw_client)) { + dev_err(&hw_client->dev, "Cannot add FW I2C device\n"); + return PTR_ERR(ts->fw_client); + } + + ts->hw_client = hw_client; + ts->input = input; + i2c_set_clientdata(ts->hw_client, ts); + i2c_set_clientdata(ts->fw_client, ts); + + ts->reset_gpio = devm_gpiod_get_optional(&hw_client->dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(ts->reset_gpio)) + return PTR_ERR(ts->reset_gpio); + gpiod_set_consumer_name(ts->reset_gpio, "nt36xxx reset"); + + /* These supplies are optional */ + ts->supplies[0].supply = "vdd"; + ts->supplies[1].supply = "vio"; + ret = devm_regulator_bulk_get(&hw_client->dev, + NT36XXX_NUM_SUPPLIES, + ts->supplies); + if (ret) + return dev_err_probe(&hw_client->dev, ret, + "Cannot get supplies: %d\n", ret); + + ts->regmap = devm_regmap_init_i2c(ts->hw_client, + &nt36xxx_i2c_regmap_hw_config); + if (IS_ERR(ts->regmap)) { + dev_err(&hw_client->dev, "regmap (hw-addr) init failed\n"); + return PTR_ERR(ts->regmap); + } + + ts->fw_regmap = devm_regmap_init_i2c(ts->fw_client, + &nt36xxx_i2c_regmap_fw_config); + if (IS_ERR(ts->fw_regmap)) { + dev_err(&hw_client->dev, "regmap (fw-addr) init failed\n"); + return PTR_ERR(ts->fw_regmap); + } + + ret = regulator_bulk_enable(NT36XXX_NUM_SUPPLIES, ts->supplies); + if (ret) + return ret; + + usleep_range(10000, 11000); + + ret = devm_add_action_or_reset(&hw_client->dev, + nt36xxx_disable_regulators, ts); + if (ret) + return ret; + + mutex_init(&ts->lock); + + /* Set memory maps for the specific chip version */ + ret = nt36xxx_i2c_chip_version_init(ts); + if (ret) { + dev_err(&hw_client->dev, "Failed to check chip version\n"); + return ret; + } + + /* Reset the MCU */ + ret = nt36xxx_bootloader_reset(ts); + if (ret < 0) + return ret; + + /* Check and eventually wait until the MCU goes in reset state */ + ret = nt36xxx_check_reset_state(ts, NT36XXX_STATE_INIT); + if (ret < 0) + return ret; + + /* Get informations from the TS firmware */ + ret = nt36xxx_get_fw_info(ts); + if (ret < 0) + return ret; + + input->phys = devm_kasprintf(&hw_client->dev, GFP_KERNEL, + "%s/input0", dev_name(&hw_client->dev)); + if (!input->phys) + return -ENOMEM; + + input->name = "Novatek NT36XXX Touchscreen"; + input->id.bustype = BUS_I2C; + input->dev.parent = &hw_client->dev; + + __set_bit(EV_KEY, input->evbit); + __set_bit(EV_ABS, input->evbit); + input_set_capability(input, EV_KEY, BTN_TOUCH); + + ret = input_mt_init_slots(input, TOUCH_MAX_FINGER_NUM, + INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED); + if (ret) { + dev_err(&hw_client->dev, "Cannot init MT slots (%d)\n", ret); + return ret; + } + + input_set_abs_params(input, ABS_MT_PRESSURE, 0, + TOUCH_MAX_PRESSURE, 0, 0); + input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0); + + input_set_abs_params(input, ABS_MT_POSITION_X, 0, + ts->fw_info.abs_x_max - 1, 0, 0); + input_set_abs_params(input, ABS_MT_POSITION_Y, 0, + ts->fw_info.abs_y_max - 1, 0, 0); + + /* Override the firmware defaults, if needed */ + touchscreen_parse_properties(input, true, &ts->prop); + + input_set_drvdata(input, ts); + + ret = input_register_device(ts->input); + if (ret) { + dev_err(&hw_client->dev, "Failed to register input device: %d\n", + ret); + return ret; + } + + ret = devm_request_threaded_irq(&hw_client->dev, hw_client->irq, NULL, + nt36xxx_i2c_irq_handler, IRQF_ONESHOT, + hw_client->name, ts); + if (ret) { + dev_err(&hw_client->dev, "request irq failed: %d\n", ret); + return ret; + } + + return ret; +} + +static int __maybe_unused nt36xxx_i2c_suspend(struct device *dev) +{ + struct nt36xxx_i2c *ts = i2c_get_clientdata(to_i2c_client(dev)); + int ret; + + disable_irq(ts->hw_client->irq); + + ret = regmap_write(ts->fw_regmap, NT36XXX_EVT_HOST_CMD, + NT36XXX_CMD_ENTER_SLEEP); + if (ret) { + dev_err(&ts->hw_client->dev, "Cannot enter suspend!!\n"); + return ret; + } + + gpiod_set_value(ts->reset_gpio, 1); + + return 0; +} + +static int __maybe_unused nt36xxx_i2c_resume(struct device *dev) +{ + struct nt36xxx_i2c *ts = i2c_get_clientdata(to_i2c_client(dev)); + int ret; + + mutex_lock(&ts->lock); + + gpiod_set_value(ts->reset_gpio, 0); + + /* Reboot the MCU (also recalibrates the TS) */ + ret = nt36xxx_bootloader_reset(ts); + if (ret < 0) + goto end; + + ret = nt36xxx_check_reset_state(ts, NT36XXX_STATE_REK); + if (ret < 0) + goto end; + + enable_irq(ts->hw_client->irq); +end: + mutex_unlock(&ts->lock); + return ret; +} + +static SIMPLE_DEV_PM_OPS(nt36xxx_i2c_pm, + nt36xxx_i2c_suspend, nt36xxx_i2c_resume); + +static const struct of_device_id nt36xxx_of_match[] = { + { .compatible = "novatek,nt36525" }, + { } +}; +MODULE_DEVICE_TABLE(of, nt36xxx_of_match); + +static const struct i2c_device_id nt36xxx_i2c_ts_id[] = { + { "NVT-ts", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, nt36xxx_i2c_ts_id); + +static struct i2c_driver nt36xxx_i2c_ts_driver = { + .driver = { + .name = "nt36xxx_ts", + .pm = &nt36xxx_i2c_pm, + .of_match_table = nt36xxx_of_match, + }, + .id_table = nt36xxx_i2c_ts_id, + .probe = nt36xxx_i2c_probe, +}; +module_i2c_driver(nt36xxx_i2c_ts_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Novatek NT36XXX Touchscreen Driver"); +MODULE_AUTHOR("AngeloGioacchino Del Regno "); diff --git a/drivers/input/touchscreen/nt36xxx.h b/drivers/input/touchscreen/nt36xxx.h new file mode 100644 index 000000000000..6f03dfb45656 --- /dev/null +++ b/drivers/input/touchscreen/nt36xxx.h @@ -0,0 +1,122 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2010 - 2017 Novatek, Inc. + * Copyright (C) 2020 AngeloGioacchino Del Regno + */ + +#ifndef NT36XXX_H +#define NT36XXX_H + +#define NT36XXX_INPUT_DEVICE_NAME "Novatek NT36XXX Touch Sensor" + +/* These chips have this fixed address when in bootloader :( */ +#define NT36XXX_BLDR_ADDR 0x01 + +/* Input device info */ +#define NVT_TS_NAME "NVTCapacitiveTouchScreen" + +/* Number of bytes for chip identification */ +#define NT36XXX_ID_LEN_MAX 6 + +/* Touch info */ +#define TOUCH_DEFAULT_MAX_WIDTH 1080 +#define TOUCH_DEFAULT_MAX_HEIGHT 2246 +#define TOUCH_MAX_FINGER_NUM 10 +#define TOUCH_MAX_PRESSURE 1000 + +/* Point data length */ +#define POINT_DATA_LEN 65 + +/* Global pages */ +#define NT36XXX_PAGE_CHIP_INFO 0x0001f64e +#define NT36XXX_PAGE_CRC 0x0003f135 + +/* Misc */ +#define NT36XXX_NUM_SUPPLIES 2 +#define NT36XXX_MAX_RETRIES 5 +#define NT36XXX_MAX_FW_RST_RETRY 50 + +struct nt36xxx_abs_object { + u16 x; + u16 y; + u16 z; + u8 tm; +}; + +struct nt36xxx_fw_info { + u8 fw_ver; + u8 x_num; + u8 y_num; + u8 max_buttons; + u16 abs_x_max; + u16 abs_y_max; + u16 nvt_pid; +}; + +struct nt36xxx_mem_map { + u32 evtbuf_addr; + u32 pipe0_addr; + u32 pipe1_addr; + u32 flash_csum_addr; + u32 flash_data_addr; +}; + +struct nt36xxx_i2c { + struct i2c_client *client; + struct input_dev *input; + struct regulator_bulk_data *supplies; + struct gpio_desc *reset_gpio; + + struct work_struct ts_work; + struct workqueue_struct *ts_workq; + struct mutex lock; + + struct nt36xxx_fw_info fw_info; + struct nt36xxx_abs_object abs_obj; + + const struct nt36xxx_mem_map *mmap; + u8 max_fingers; +}; + +enum nt36xxx_chips { + NT36525_IC = 0, + NT36672A_IC, + NT36676F_IC, + NT36772_IC, + NT36870_IC, + NTMAX_IC, +}; + +struct nt36xxx_trim_table { + u8 id[NT36XXX_ID_LEN_MAX]; + u8 mask[NT36XXX_ID_LEN_MAX]; + enum nt36xxx_chips mapid; +}; + +enum nt36xxx_cmds { + NT36XXX_CMD_ENTER_SLEEP = 0x11, + NT36XXX_CMD_ENTER_WKUP_GESTURE = 0x13, + NT36XXX_CMD_UNLOCK = 0x35, + NT36XXX_CMD_BOOTLOADER_RESET = 0x69, + NT36XXX_CMD_SW_RESET = 0xa5, + NT36XXX_CMD_SET_PAGE = 0xff, +}; + +enum nt36xxx_fw_state { + NT36XXX_STATE_INIT = 0xa0, /* IC reset */ + NT36XXX_STATE_REK, /* ReK baseline */ + NT36XXX_STATE_REK_FINISH, /* Baseline is ready */ + NT36XXX_STATE_NORMAL_RUN, /* Normal run */ + NT36XXX_STATE_MAX = 0xaf +}; + +enum nt36xxx_i2c_events { + NT36XXX_EVT_CHIPID = 0x4e, + NT36XXX_EVT_HOST_CMD = 0x50, + NT36XXX_EVT_HS_OR_SUBCMD = 0x51, /* Handshake or subcommand byte */ + NT36XXX_EVT_RESET_COMPLETE = 0x60, + NT36XXX_EVT_FWINFO = 0x78, + NT36XXX_EVT_PROJECTID = 0x9a, +}; + +#endif From 316da4338e6aeee5ce803ab199a9f86fc2652842 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Thu, 24 Sep 2020 13:42:31 +0200 Subject: [PATCH 082/187] dt-bindings: touchscreen: Add binding for Novatek NT36xxx series driver Add binding for the Novatek NT36xxx series touchscreen driver. Signed-off-by: AngeloGioacchino Del Regno Reviewed-by: Krzysztof Kozlowski Reviewed-by: Rob Herring --- .../bindings/input/touchscreen/nt36xxx.yaml | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 Documentation/devicetree/bindings/input/touchscreen/nt36xxx.yaml diff --git a/Documentation/devicetree/bindings/input/touchscreen/nt36xxx.yaml b/Documentation/devicetree/bindings/input/touchscreen/nt36xxx.yaml new file mode 100644 index 000000000000..a360a9f5d43b --- /dev/null +++ b/Documentation/devicetree/bindings/input/touchscreen/nt36xxx.yaml @@ -0,0 +1,59 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/input/touchscreen/nt36xxx.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Novatek NT36xxx series touchscreen controller Bindings + +maintainers: + - AngeloGioacchino Del Regno + +allOf: + - $ref: touchscreen.yaml# + +properties: + compatible: + const: novatek,nt36525 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + reset-gpios: + maxItems: 1 + + vdd-supply: + description: Power supply regulator for VDD pin + + vio-supply: + description: Power supply regulator on VDD-IO pin + +unevaluatedProperties: false + +required: + - compatible + - reg + - interrupts + +examples: + - | + #include + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + touchscreen@62 { + compatible = "novatek,nt36525"; + reg = <0x62>; + interrupt-parent = <&tlmm>; + interrupts = <45 IRQ_TYPE_EDGE_RISING>; + reset-gpios = <&tlmm 102 GPIO_ACTIVE_HIGH>; + }; + }; + +... From 2d498601d742f691c061234e3ccbc194deaaeb9b Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Wed, 13 Jan 2021 18:56:29 +0100 Subject: [PATCH 083/187] dt-bindings: i2c: qcom,i2c-qup: Convert txt to YAML schema Convert the qcom,i2c-qup binding to YAML schema. Signed-off-by: AngeloGioacchino Del Regno --- .../devicetree/bindings/i2c/qcom,i2c-qup.txt | 40 --------- .../devicetree/bindings/i2c/qcom,i2c-qup.yaml | 87 +++++++++++++++++++ 2 files changed, 87 insertions(+), 40 deletions(-) delete mode 100644 Documentation/devicetree/bindings/i2c/qcom,i2c-qup.txt create mode 100644 Documentation/devicetree/bindings/i2c/qcom,i2c-qup.yaml diff --git a/Documentation/devicetree/bindings/i2c/qcom,i2c-qup.txt b/Documentation/devicetree/bindings/i2c/qcom,i2c-qup.txt deleted file mode 100644 index dc71754a56af..000000000000 --- a/Documentation/devicetree/bindings/i2c/qcom,i2c-qup.txt +++ /dev/null @@ -1,40 +0,0 @@ -Qualcomm Universal Peripheral (QUP) I2C controller - -Required properties: - - compatible: Should be: - * "qcom,i2c-qup-v1.1.1" for 8660, 8960 and 8064. - * "qcom,i2c-qup-v2.1.1" for 8974 v1. - * "qcom,i2c-qup-v2.2.1" for 8974 v2 and later. - - reg: Should contain QUP register address and length. - - interrupts: Should contain I2C interrupt. - - - clocks: A list of phandles + clock-specifiers, one for each entry in - clock-names. - - clock-names: Should contain: - * "core" for the core clock - * "iface" for the AHB clock - - - #address-cells: Should be <1> Address cells for i2c device address - - #size-cells: Should be <0> as i2c addresses have no size component - -Optional properties: - - clock-frequency: Should specify the desired i2c bus clock frequency in Hz, - defaults to 100kHz if omitted. - -Child nodes should conform to i2c bus binding. - -Example: - - i2c@f9924000 { - compatible = "qcom,i2c-qup-v2.2.1"; - reg = <0xf9924000 0x1000>; - interrupts = <0 96 0>; - - clocks = <&gcc GCC_BLSP1_QUP2_I2C_APPS_CLK>, <&gcc GCC_BLSP1_AHB_CLK>; - clock-names = "core", "iface"; - - clock-frequency = <355000>; - - #address-cells = <1>; - #size-cells = <0>; - }; diff --git a/Documentation/devicetree/bindings/i2c/qcom,i2c-qup.yaml b/Documentation/devicetree/bindings/i2c/qcom,i2c-qup.yaml new file mode 100644 index 000000000000..c5c7db3ac2a6 --- /dev/null +++ b/Documentation/devicetree/bindings/i2c/qcom,i2c-qup.yaml @@ -0,0 +1,87 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- + +$id: http://devicetree.org/schemas/i2c/qcom,i2c-qup.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm Universal Peripheral (QUP) I2C controller + +maintainers: + - Andy Gross + - Bjorn Andersson + +description: Binding for Qualcomm "QUP" I2C controllers + +allOf: + - $ref: /schemas/i2c/i2c-controller.yaml# + +properties: + compatible: + enum: + - qcom,i2c-qup-v1.1.1 + - qcom,i2c-qup-v2.1.1 + - qcom,i2c-qup-v2.2.1 + + reg: + items: + - description: QUP I2C register iospace + + clocks: + items: + - description: Core QUP I2C clock + - description: AHB clock + + clock-names: + items: + - const: core + - const: iface + + clock-frequency: + minimum: 100000 + maximum: 1000000 + default: 100000 + + dmas: + items: + - description: RX DMA Channel phandle + - description: TX DMA Channel phandle + + dma-names: + items: + - const: rx + - const: tx + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + +required: + - compatible + - clocks + - clock-names + - reg + +unevaluatedProperties: false + +examples: + - | + #include + #include + #include + + i2c@c175000 { + compatible = "qcom,i2c-qup-v2.2.1"; + reg = <0x0c175000 0x600>; + interrupts = ; + clocks = <&gcc GCC_BLSP1_QUP1_I2C_APPS_CLK>, + <&gcc GCC_BLSP1_AHB_CLK>; + clock-names = "core", "iface"; + clock-frequency = <400000>; + dmas = <&blsp_dma 4>, <&blsp_dma 5>; + dma-names = "rx", "tx"; + #address-cells = <1>; + #size-cells = <0>; + }; From 9d4e49ab2cbbccc17caeb3a08e4bd7b457d9bc7d Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Wed, 13 Jan 2021 19:22:24 +0100 Subject: [PATCH 084/187] i2c: qup: Introduce SCL/SDA noise rejection Some I2C devices may be glitchy due to electrical noise coming from the device itself or because of possible board design issues. To overcome this issue, the QUP's I2C in Qualcomm SoCs supports a noise rejection setting for both SCL and SDA lines. Introduce a setting for noise rejection through device properties, "qcom,noise-reject-sda" and "qcom,noise-reject-scl", which will be used to set the level of noise rejection sensitivity. If the properties are not specified, noise rejection will not be enabled. Signed-off-by: AngeloGioacchino Del Regno --- drivers/i2c/busses/i2c-qup.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/i2c/busses/i2c-qup.c b/drivers/i2c/busses/i2c-qup.c index 69e9f3ecf87d..f64f7e0d2620 100644 --- a/drivers/i2c/busses/i2c-qup.c +++ b/drivers/i2c/busses/i2c-qup.c @@ -39,6 +39,8 @@ #define QUP_MX_READ_CNT 0x208 #define QUP_IN_FIFO_BASE 0x218 #define QUP_I2C_CLK_CTL 0x400 +#define QUP_I2C_CLK_CTL_SDA_NR GENMASK(27, 26) +#define QUP_I2C_CLK_CTL_SCL_NR GENMASK(25, 24) #define QUP_I2C_STATUS 0x404 #define QUP_I2C_MASTER_GEN 0x408 @@ -1663,6 +1665,7 @@ static int qup_i2c_probe(struct platform_device *pdev) int ret, fs_div, hs_div; u32 src_clk_freq = DEFAULT_SRC_CLK; u32 clk_freq = DEFAULT_CLK_FREQ; + u32 noise_reject_scl = 0, noise_reject_sda = 0; int blocks; bool is_qup_v1; @@ -1860,6 +1863,19 @@ nodma: qup->clk_ctl = ((fs_div / 2) << 16) | (hs_div << 8) | (fs_div & 0xff); } + /* SCL/SDA Noise rejection (optional) */ + ret = device_property_read_u32(qup->dev, "qcom,noise-reject-scl", + &noise_reject_scl); + if (ret == 0) + qup->clk_ctl |= FIELD_PREP(QUP_I2C_CLK_CTL_SCL_NR, + noise_reject_scl); + + ret = device_property_read_u32(qup->dev, "qcom,noise-reject-sda", + &noise_reject_sda); + if (ret == 0) + qup->clk_ctl |= FIELD_PREP(QUP_I2C_CLK_CTL_SDA_NR, + noise_reject_sda); + /* * Time it takes for a byte to be clocked out on the bus. * Each byte takes 9 clock cycles (8 bits + 1 ack). From fb54cf55d4d9884723b5a15c48acd9a09191a3bc Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Wed, 13 Jan 2021 19:24:33 +0100 Subject: [PATCH 085/187] dt-bindings: i2c: qcom,i2c-qup: Document noise rejection properties Document the new noise rejection properties "qcom,noise-reject-sda" and "qcom,noise-reject-scl". Signed-off-by: AngeloGioacchino Del Regno --- .../devicetree/bindings/i2c/qcom,i2c-qup.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Documentation/devicetree/bindings/i2c/qcom,i2c-qup.yaml b/Documentation/devicetree/bindings/i2c/qcom,i2c-qup.yaml index c5c7db3ac2a6..3f14dd65c6b9 100644 --- a/Documentation/devicetree/bindings/i2c/qcom,i2c-qup.yaml +++ b/Documentation/devicetree/bindings/i2c/qcom,i2c-qup.yaml @@ -58,6 +58,20 @@ properties: '#size-cells': const: 0 + qcom,noise-reject-sda: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Noise rejection level for the SDA line. + minimum: 0 + maximum: 3 + default: 0 + + qcom,noise-reject-scl: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Noise rejection level for the SCL line. + minimum: 0 + maximum: 3 + default: 0 + required: - compatible - clocks From 770ff643635f3c0029e82ff0fe7eccb16f549c79 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Wed, 20 Jan 2021 13:32:32 +0100 Subject: [PATCH 086/187] dt-bindings: interconnect: Add bindings for Qualcomm MSM8998 NoC Add the bindings for the Qualcomm MSM8998 NoC interconnects. Signed-off-by: AngeloGioacchino Del Regno --- .../bindings/interconnect/qcom,msm8998.yaml | 158 ++++++++++++++++++ .../dt-bindings/interconnect/qcom,msm8998.h | 128 ++++++++++++++ 2 files changed, 286 insertions(+) create mode 100644 Documentation/devicetree/bindings/interconnect/qcom,msm8998.yaml create mode 100644 include/dt-bindings/interconnect/qcom,msm8998.h diff --git a/Documentation/devicetree/bindings/interconnect/qcom,msm8998.yaml b/Documentation/devicetree/bindings/interconnect/qcom,msm8998.yaml new file mode 100644 index 000000000000..3bea90cbe053 --- /dev/null +++ b/Documentation/devicetree/bindings/interconnect/qcom,msm8998.yaml @@ -0,0 +1,158 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/interconnect/qcom,msm8998.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm MSM8998 Network-On-Chip interconnect + +maintainers: + - AngeloGioacchino Del Regno + - Konrad Dybcio + +description: | + The Qualcomm MSM8998 interconnect providers support adjusting the + bandwidth requirements between the various NoC fabrics. + +properties: + reg: + maxItems: 1 + + compatible: + enum: + - qcom,msm8998-a1noc + - qcom,msm8998-a2noc + - qcom,msm8998-bimc + - qcom,msm8998-cnoc + - qcom,msm8998-gnoc + - qcom,msm8998-mnoc + - qcom,msm8998-snoc + + '#interconnect-cells': + const: 1 + + clocks: + minItems: 1 + maxItems: 3 + + clock-names: + minItems: 1 + maxItems: 3 + +required: + - compatible + - reg + - '#interconnect-cells' + - clock-names + - clocks + +additionalProperties: false + +allOf: + - if: + properties: + compatible: + contains: + enum: + - qcom,msm8998-mnoc + then: + properties: + clocks: + items: + - description: Bus Clock. + - description: Bus A Clock. + - description: CPU-NoC High-performance Bus Clock. + clock-names: + items: + - const: bus + - const: bus_a + - const: iface + + - if: + properties: + compatible: + contains: + enum: + - qcom,msm8998-a2noc + - qcom,msm8998-bimc + - qcom,msm8998-cnoc + - qcom,msm8998-gnoc + - qcom,msm8998-snoc + then: + properties: + clocks: + items: + - description: Bus Clock. + - description: Bus A Clock. + clock-names: + items: + - const: bus + - const: bus_a + +examples: + - | + #include + #include + + bimc: interconnect@1008000 { + compatible = "qcom,msm8998-bimc"; + reg = <0x01008000 0x78000>; + #interconnect-cells = <1>; + clock-names = "bus", "bus_a"; + clocks = <&rpmcc RPM_SMD_BIMC_CLK>, + <&rpmcc RPM_SMD_BIMC_A_CLK>; + }; + + cnoc: interconnect@1500000 { + compatible = "qcom,msm8998-cnoc"; + reg = <0x01500000 0x10000>; + #interconnect-cells = <1>; + clock-names = "bus", "bus_a"; + clocks = <&rpmcc RPM_SMD_CNOC_CLK>, + <&rpmcc RPM_SMD_CNOC_A_CLK>; + }; + + snoc: interconnect@1625000 { + compatible = "qcom,msm8998-snoc"; + reg = <0x01625000 0x6100>; + #interconnect-cells = <1>; + clock-names = "bus", "bus_a"; + clocks = <&rpmcc RPM_SMD_SNOC_CLK>, + <&rpmcc RPM_SMD_SNOC_A_CLK>; + }; + + a1noc: interconnect@1669000 { + compatible = "qcom,msm8998-a1noc"; + reg = <0x01669000 0x5020>; + #interconnect-cells = <1>; + clock-names = "bus", "bus_a"; + clocks = <&rpmcc RPM_SMD_AGGR1_NOC_CLK>, + <&rpmcc RPM_SMD_AGGR1_NOC_A_CLK>; + }; + + a2noc: interconnect@1705000 { + compatible = "qcom,msm8998-a2noc"; + reg = <0x01705000 0xa090>; + #interconnect-cells = <1>; + clock-names = "bus", "bus_a"; + clocks = <&rpmcc RPM_SMD_AGGR2_NOC_CLK>, + <&rpmcc RPM_SMD_AGGR2_NOC_A_CLK>; + }; + + mnoc: interconnect@1744000 { + compatible = "qcom,msm8998-mnoc"; + reg = <0x01744000 0xb010>; + #interconnect-cells = <1>; + clock-names = "bus", "bus_a", "iface"; + clocks = <&rpmcc RPM_SMD_MMAXI_CLK>, + <&rpmcc RPM_SMD_MMAXI_A_CLK>, + <&mmcc AHB_CLK_SRC>; + }; + + gnoc: interconnect@17900000 { + compatible = "qcom,msm8998-gnoc"; + reg = <0x17900000 0xe000>; + #interconnect-cells = <1>; + clock-names = "bus", "bus_a"; + clocks = <&xo_board>, <&xo_board>; + }; diff --git a/include/dt-bindings/interconnect/qcom,msm8998.h b/include/dt-bindings/interconnect/qcom,msm8998.h new file mode 100644 index 000000000000..5b94403ef178 --- /dev/null +++ b/include/dt-bindings/interconnect/qcom,msm8998.h @@ -0,0 +1,128 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* MSM8998 interconnect IDs */ + +#ifndef __DT_BINDINGS_INTERCONNECT_QCOM_MSM8998_H +#define __DT_BINDINGS_INTERCONNECT_QCOM_MSM8998_H + +/* A1NOC */ +#define MASTER_PCIE_0 0 +#define MASTER_USB3 1 +#define MASTER_UFS 2 +#define MASTER_BLSP_2 3 +#define SLAVE_A1NOC_SNOC 4 + +/* A2NOC */ +#define MASTER_IPA 0 +#define MASTER_CNOC_A2NOC 1 +#define MASTER_SDCC_2 2 +#define MASTER_SDCC_4 3 +#define MASTER_TSIF 4 +#define MASTER_BLSP_1 5 +#define MASTER_CRVIRT_A2NOC 6 +#define MASTER_CRYPTO_C0 7 +#define SLAVE_A2NOC_SNOC 8 +#define SLAVE_CRVIRT_A2NOC 9 + +/* BIMC */ +#define MASTER_GNOC_BIMC 0 +#define MASTER_OXILI 1 +#define MASTER_MNOC_BIMC 2 +#define MASTER_SNOC_BIMC 3 +#define SLAVE_EBI 4 +#define SLAVE_HMSS_L3 5 +#define SLAVE_BIMC_SNOC_0 6 +#define SLAVE_BIMC_SNOC_1 7 + +/* CNOC */ +#define MASTER_SNOC_CNOC 0 +#define MASTER_QDSS_DAP 1 +#define SLAVE_CNOC_A2NOC 2 +#define SLAVE_SSC_CFG 3 +#define SLAVE_MPM 4 +#define SLAVE_PMIC_ARB 5 +#define SLAVE_TLMM_NORTH 6 +#define SLAVE_PIMEM_CFG 7 +#define SLAVE_IMEM_CFG 8 +#define SLAVE_MESSAGE_RAM 9 +#define SLAVE_SKL 10 +#define SLAVE_BIMC_CFG 11 +#define SLAVE_PRNG 12 +#define SLAVE_A2NOC_CFG 13 +#define SLAVE_IPA 14 +#define SLAVE_TCSR 15 +#define SLAVE_SNOC_CFG 16 +#define SLAVE_CLK_CTL 17 +#define SLAVE_GLM 18 +#define SLAVE_SPDM 19 +#define SLAVE_GPUSS_CFG 20 +#define SLAVE_CNOC_MNOC_CFG 21 +#define SLAVE_QM_CFG 22 +#define SLAVE_MSS_CFG 23 +#define SLAVE_UFS_CFG 24 +#define SLAVE_TLMM_WEST 25 +#define SLAVE_A1NOC_CFG 26 +#define SLAVE_AHB2PHY 27 +#define SLAVE_BLSP_2 28 +#define SLAVE_PDM 29 +#define SLAVE_USB3_0 30 +#define SLAVE_A1NOC_SMMU_CFG 31 +#define SLAVE_BLSP_1 32 +#define SLAVE_SDCC_2 33 +#define SLAVE_SDCC_4 34 +#define SLAVE_TSIF 35 +#define SLAVE_QDSS_CFG 36 +#define SLAVE_TLMM_EAST 37 +#define SLAVE_CNOC_MNOC_MMSS_CFG 38 +#define SLAVE_SRVC_CNOC 39 + +/* GNOC */ +#define MASTER_APSS_PROC 0 +#define SLAVE_GNOC_BIMC 1 + +/* MNOC */ +#define MASTER_CNOC_MNOC_CFG 0 +#define MASTER_CPP 1 +#define MASTER_JPEG 2 +#define MASTER_MDP_P0 3 +#define MASTER_MDP_P1 4 +#define MASTER_ROTATOR 5 +#define MASTER_VENUS 6 +#define MASTER_VFE 7 +#define MASTER_VENUS_VMEM 8 +#define SLAVE_MNOC_BIMC 9 +#define SLAVE_VMEM 10 +#define SLAVE_SRVC_MNOC 11 +#define MASTER_CNOC_MNOC_MMSS_CFG 12 +#define SLAVE_CAMERA_CFG 13 +#define SLAVE_CAMERA_THROTTLE_CFG 14 +#define SLAVE_MISC_CFG 15 +#define SLAVE_VENUS_THROTTLE_CFG 16 +#define SLAVE_VENUS_CFG 17 +#define SLAVE_VMEM_CFG 18 +#define SLAVE_MMSS_CLK_XPU_CFG 19 +#define SLAVE_MMSS_CLK_CFG 20 +#define SLAVE_DISPLAY_CFG 21 +#define SLAVE_DISPLAY_THROTTLE_CFG 22 +#define SLAVE_SMMU_CFG 23 + +/* SNOC */ +#define MASTER_HMSS 0 +#define MASTER_QDSS_BAM 1 +#define MASTER_SNOC_CFG 2 +#define MASTER_BIMC_SNOC_0 3 +#define MASTER_BIMC_SNOC_1 4 +#define MASTER_A1NOC_SNOC 5 +#define MASTER_A2NOC_SNOC 6 +#define MASTER_QDSS_ETR 7 +#define SLAVE_HMSS 8 +#define SLAVE_LPASS 9 +#define SLAVE_WLAN 10 +#define SLAVE_SNOC_BIMC 11 +#define SLAVE_SNOC_CNOC 12 +#define SLAVE_IMEM 13 +#define SLAVE_PIMEM 14 +#define SLAVE_QDSS_STM 15 +#define SLAVE_PCIE_0 16 +#define SLAVE_SRVC_SNOC 17 + +#endif From 203c84bacbbd8c7d28b1d89941990f7ca1cfe62f Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Wed, 20 Jan 2021 13:33:59 +0100 Subject: [PATCH 087/187] interconnect: qcom: Add MSM8998 interconnect provider driver Introduce a driver for the Qualcomm interconnect busses found in the MSM/APQ8998 SoCs. The topology consists of several NoCs that are controlled by a remote processor that collects the aggregated bandwidth for each master-slave pairs. On a note, these chips are managing the "bus QoS" in a "hybrid" fashion: some of the paths in the topology are managed through and by, of course) the RPM uC, while some others are "AP Owned", meaning that the AP shall do direct writes to the appropriate QoS registers for the specific paths and ports, instead of sending an indication to the RPM and leaving the job to that one. Co-authored-by: AngeloGioacchino Del Regno Signed-off-by: Konrad Dybcio Signed-off-by: AngeloGioacchino Del Regno --- drivers/interconnect/qcom/Kconfig | 9 + drivers/interconnect/qcom/Makefile | 2 + drivers/interconnect/qcom/msm8998.c | 981 ++++++++++++++++++++++++++++ 3 files changed, 992 insertions(+) create mode 100644 drivers/interconnect/qcom/msm8998.c diff --git a/drivers/interconnect/qcom/Kconfig b/drivers/interconnect/qcom/Kconfig index daf1e25f6042..56391f3c5312 100644 --- a/drivers/interconnect/qcom/Kconfig +++ b/drivers/interconnect/qcom/Kconfig @@ -35,6 +35,15 @@ config INTERCONNECT_QCOM_MSM8974 This is a driver for the Qualcomm Network-on-Chip on msm8974-based platforms. +config INTERCONNECT_QCOM_MSM8998 + tristate "Qualcomm MSM8998 interconnect driver" + depends on INTERCONNECT_QCOM + depends on QCOM_SMD_RPM + select INTERCONNECT_QCOM_SMD_RPM + help + This is a driver for the Qualcomm Network-on-Chip on msm8998-based + platforms. + config INTERCONNECT_QCOM_OSM_L3 tristate "Qualcomm OSM L3 interconnect driver" depends on INTERCONNECT_QCOM || COMPILE_TEST diff --git a/drivers/interconnect/qcom/Makefile b/drivers/interconnect/qcom/Makefile index 69300b1d48ef..3a7591ca60d5 100644 --- a/drivers/interconnect/qcom/Makefile +++ b/drivers/interconnect/qcom/Makefile @@ -4,6 +4,7 @@ icc-bcm-voter-objs := bcm-voter.o qnoc-msm8916-objs := msm8916.o qnoc-msm8939-objs := msm8939.o qnoc-msm8974-objs := msm8974.o +qnoc-msm8998-objs := msm8998.o icc-osm-l3-objs := osm-l3.o qnoc-qcs404-objs := qcs404.o icc-rpmh-obj := icc-rpmh.o @@ -22,6 +23,7 @@ obj-$(CONFIG_INTERCONNECT_QCOM_BCM_VOTER) += icc-bcm-voter.o obj-$(CONFIG_INTERCONNECT_QCOM_MSM8916) += qnoc-msm8916.o obj-$(CONFIG_INTERCONNECT_QCOM_MSM8939) += qnoc-msm8939.o obj-$(CONFIG_INTERCONNECT_QCOM_MSM8974) += qnoc-msm8974.o +obj-$(CONFIG_INTERCONNECT_QCOM_MSM8998) += qnoc-msm8998.o obj-$(CONFIG_INTERCONNECT_QCOM_OSM_L3) += icc-osm-l3.o obj-$(CONFIG_INTERCONNECT_QCOM_QCS404) += qnoc-qcs404.o obj-$(CONFIG_INTERCONNECT_QCOM_RPMH) += icc-rpmh.o diff --git a/drivers/interconnect/qcom/msm8998.c b/drivers/interconnect/qcom/msm8998.c new file mode 100644 index 000000000000..764c2fc18a62 --- /dev/null +++ b/drivers/interconnect/qcom/msm8998.c @@ -0,0 +1,981 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Qualcomm MSM8998 Network-on-Chip (NoC) QoS driver + * Copyright (c) 2020, AngeloGioacchino Del Regno + * + * Copyright (C) 2020, Konrad Dybcio + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "smd-rpm.h" + +#define RPM_BUS_MASTER_REQ 0x73616d62 +#define RPM_BUS_SLAVE_REQ 0x766c7362 + +/* BIMC QoS */ +#define M_BKE_REG_BASE(n) (0x300 + (0x4000 * n)) +#define M_BKE_EN_ADDR(n) (M_BKE_REG_BASE(n)) +#define M_BKE_HEALTH_CFG_ADDR(i, n) (M_BKE_REG_BASE(n) + 0x40 + (0x4 * i)) + +#define M_BKE_HEALTH_CFG_LIMITCMDS_MASK 0x80000000 +#define M_BKE_HEALTH_CFG_AREQPRIO_MASK 0x300 +#define M_BKE_HEALTH_CFG_PRIOLVL_MASK 0x3 +#define M_BKE_HEALTH_CFG_AREQPRIO_SHIFT 0x8 +#define M_BKE_HEALTH_CFG_LIMITCMDS_SHIFT 0x1f + +#define M_BKE_EN_EN_BMASK 0x1 + +/* Valid for both NoC and BIMC */ +#define NOC_QOS_MODE_FIXED 0x0 +#define NOC_QOS_MODE_LIMITER 0x1 +#define NOC_QOS_MODE_BYPASS 0x2 + +/* NoC QoS */ +#define NOC_PERM_MODE_FIXED 1 +#define NOC_PERM_MODE_BYPASS (1 << NOC_QOS_MODE_BYPASS) + +#define NOC_QOS_PRIORITYn_ADDR(n) (0x8 + (n * 0x1000)) +#define NOC_QOS_PRIORITY_MASK 0xf +#define NOC_QOS_PRIORITY_P1_SHIFT 0x2 +#define NOC_QOS_PRIORITY_P0_SHIFT 0x3 + +#define NOC_QOS_MODEn_ADDR(n) (0xc + (n * 0x1000)) +#define NOC_QOS_MODEn_MASK 0x3 + +enum { + MSM8998_MASTER_IPA = 1, + MSM8998_MASTER_CNOC_A2NOC, + MSM8998_MASTER_SDCC_2, + MSM8998_MASTER_SDCC_4, + MSM8998_MASTER_BLSP_1, + MSM8998_MASTER_BLSP_2, + MSM8998_MASTER_UFS, + MSM8998_MASTER_USB_HS, + MSM8998_MASTER_USB3, + MSM8998_MASTER_CRYPTO_C0, + MSM8998_MASTER_GNOC_BIMC, + MSM8998_MASTER_OXILI, + MSM8998_MASTER_MNOC_BIMC, + MSM8998_MASTER_SNOC_BIMC, + MSM8998_MASTER_PIMEM, + MSM8998_MASTER_SNOC_CNOC, + MSM8998_MASTER_QDSS_DAP, + MSM8998_MASTER_APPS_PROC, + MSM8998_MASTER_CNOC_MNOC_MMSS_CFG, + MSM8998_MASTER_CNOC_MNOC_CFG, + MSM8998_MASTER_CPP, + MSM8998_MASTER_JPEG, + MSM8998_MASTER_MDP_P0, + MSM8998_MASTER_MDP_P1, + MSM8998_MASTER_VENUS, + MSM8998_MASTER_VFE, + MSM8998_MASTER_QDSS_ETR, + MSM8998_MASTER_QDSS_BAM, + MSM8998_MASTER_SNOC_CFG, + MSM8998_MASTER_BIMC_SNOC, + MSM8998_MASTER_A1NOC_SNOC, + MSM8998_MASTER_A2NOC_SNOC, + MSM8998_MASTER_GNOC_SNOC, + MSM8998_MASTER_PCIE_0, + MSM8998_MASTER_A2NOC_TSIF, + MSM8998_MASTER_CRVIRT_A2NOC, + MSM8998_MASTER_ROTATOR, + MSM8998_MASTER_VENUS_VMEM, + MSM8998_MASTER_HMSS, + MSM8998_MASTER_BIMC_SNOC_0, + MSM8998_MASTER_BIMC_SNOC_1, + + MSM8998_SLAVE_A1NOC_SNOC, + MSM8998_SLAVE_A2NOC_SNOC, + MSM8998_SLAVE_EBI, + MSM8998_SLAVE_HMSS_L3, + MSM8998_SLAVE_CNOC_A2NOC, + MSM8998_SLAVE_MPM, + MSM8998_SLAVE_PMIC_ARB, + MSM8998_SLAVE_TLMM_NORTH, + MSM8998_SLAVE_TCSR, + MSM8998_SLAVE_PIMEM_CFG, + MSM8998_SLAVE_IMEM_CFG, + MSM8998_SLAVE_MESSAGE_RAM, + MSM8998_SLAVE_GLM, + MSM8998_SLAVE_BIMC_CFG, + MSM8998_SLAVE_PRNG, + MSM8998_SLAVE_SPDM, + MSM8998_SLAVE_QDSS_CFG, + MSM8998_SLAVE_CNOC_MNOC_CFG, + MSM8998_SLAVE_SNOC_CFG, + MSM8998_SLAVE_QM_CFG, + MSM8998_SLAVE_CLK_CTL, + MSM8998_SLAVE_MSS_CFG, + MSM8998_SLAVE_UFS_CFG, + MSM8998_SLAVE_A2NOC_CFG, + MSM8998_SLAVE_A2NOC_SMMU_CFG, + MSM8998_SLAVE_GPUSS_CFG, + MSM8998_SLAVE_AHB2PHY, + MSM8998_SLAVE_BLSP_1, + MSM8998_SLAVE_SDCC_2, + MSM8998_SLAVE_SDCC_4, + MSM8998_SLAVE_BLSP_2, + MSM8998_SLAVE_PDM, + MSM8998_SLAVE_CNOC_MNOC_MMSS_CFG, + MSM8998_SLAVE_USB_HS, + MSM8998_SLAVE_USB3_0, + MSM8998_SLAVE_SRVC_CNOC, + MSM8998_SLAVE_GNOC_BIMC, + MSM8998_SLAVE_GNOC_SNOC, + MSM8998_SLAVE_CAMERA_CFG, + MSM8998_SLAVE_CAMERA_THROTTLE_CFG, + MSM8998_SLAVE_MISC_CFG, + MSM8998_SLAVE_VENUS_THROTTLE_CFG, + MSM8998_SLAVE_VENUS_CFG, + MSM8998_SLAVE_MMSS_CLK_XPU_CFG, + MSM8998_SLAVE_MMSS_CLK_CFG, + MSM8998_SLAVE_MNOC_MPU_CFG, + MSM8998_SLAVE_DISPLAY_CFG, + MSM8998_SLAVE_CSI_PHY_CFG, + MSM8998_SLAVE_DISPLAY_THROTTLE_CFG, + MSM8998_SLAVE_SMMU_CFG, + MSM8998_SLAVE_MNOC_BIMC, + MSM8998_SLAVE_SRVC_MNOC, + MSM8998_SLAVE_HMSS, + MSM8998_SLAVE_LPASS, + MSM8998_SLAVE_WLAN, + MSM8998_SLAVE_CDSP, + MSM8998_SLAVE_IPA, + MSM8998_SLAVE_SNOC_BIMC, + MSM8998_SLAVE_SNOC_CNOC, + MSM8998_SLAVE_IMEM, + MSM8998_SLAVE_PIMEM, + MSM8998_SLAVE_QDSS_STM, + MSM8998_SLAVE_SRVC_SNOC, + MSM8998_SLAVE_BIMC_SNOC_0, + MSM8998_SLAVE_BIMC_SNOC_1, + MSM8998_SLAVE_SSC_CFG, + MSM8998_SLAVE_SKL, + MSM8998_SLAVE_TLMM_WEST, + MSM8998_SLAVE_A1NOC_CFG, + MSM8998_SLAVE_A1NOC_SMMU_CFG, + MSM8998_SLAVE_TSIF, + MSM8998_SLAVE_TLMM_EAST, + MSM8998_SLAVE_CRVIRT_A2NOC, + MSM8998_SLAVE_VMEM_CFG, + MSM8998_SLAVE_VMEM, + MSM8998_SLAVE_PCIE_0, +}; + +#define to_qcom_provider(_provider) \ + container_of(_provider, struct qcom_icc_provider, provider) + +static const struct clk_bulk_data bus_clocks[] = { + { .id = "bus" }, + { .id = "bus_a" }, +}; + +static const struct clk_bulk_data bus_mm_clocks[] = { + { .id = "bus" }, + { .id = "bus_a" }, + { .id = "iface" }, +}; + +/** + * struct qcom_icc_provider - Qualcomm specific interconnect provider + * @provider: generic interconnect provider + * @bus_clks: the clk_bulk_data table of bus clocks + * @num_clks: the total number of clk_bulk_data entries + * @is_bimc_node: indicates whether to use bimc specific setting + * @mmio: NoC base iospace + */ +struct qcom_icc_provider { + struct icc_provider provider; + struct clk_bulk_data *bus_clks; + int num_clks; + bool is_bimc_node; + struct regmap *regmap; + void __iomem *mmio; +}; + +#define MSM8998_MAX_LINKS 38 + +/** + * struct qcom_icc_qos - Qualcomm specific interconnect QoS parameters + * @areq_prio: node requests priority + * @prio_level: priority level for bus communication + * @limit_commands: activate/deactivate limiter mode during runtime + * @ap_owned: indicates if the node is owned by the AP or by the RPM + * @qos_mode: default qos mode for this node + * @qos_port: qos port number for finding qos registers of this node + */ +struct qcom_icc_qos { + u32 areq_prio; + u32 prio_level; + bool limit_commands; + bool ap_owned; + int qos_mode; + int qos_port; +}; + +/** + * struct qcom_icc_node - Qualcomm specific interconnect nodes + * @name: the node name used in debugfs + * @id: a unique node identifier + * @links: an array of nodes where we can go next while traversing + * @num_links: the total number of @links + * @buswidth: width of the interconnect between a node and the bus (bytes) + * @mas_rpm_id: RPM id for devices that are bus masters + * @slv_rpm_id: RPM id for devices that are bus slaves + * @qos: NoC QoS setting parameters + * @rate: current bus clock rate in Hz + */ +struct qcom_icc_node { + unsigned char *name; + u16 id; + u16 links[MSM8998_MAX_LINKS]; + u16 num_links; + u16 buswidth; + int mas_rpm_id; + int slv_rpm_id; + struct qcom_icc_qos qos; + u64 rate; +}; + +struct qcom_icc_desc { + struct qcom_icc_node **nodes; + size_t num_nodes; + const struct regmap_config *regmap_cfg; +}; + +#define DEFINE_QNODE(_name, _id, _buswidth, _mas_rpm_id, _slv_rpm_id, \ + _ap_owned, _qos_mode, _qos_prio, _qos_port, ...) \ + static struct qcom_icc_node _name = { \ + .name = #_name, \ + .id = _id, \ + .buswidth = _buswidth, \ + .mas_rpm_id = _mas_rpm_id, \ + .slv_rpm_id = _slv_rpm_id, \ + .qos.ap_owned = _ap_owned, \ + .qos.qos_mode = _qos_mode, \ + .qos.areq_prio = _qos_prio, \ + .qos.prio_level = _qos_prio, \ + .qos.qos_port = _qos_port, \ + .num_links = ARRAY_SIZE(((int[]){ __VA_ARGS__ })), \ + .links = { __VA_ARGS__ }, \ + } + +/* masters */ +DEFINE_QNODE(mas_pcie_0, MSM8998_MASTER_PCIE_0, 16, 45, -1, true, NOC_QOS_MODE_FIXED, 1, 1, MSM8998_SLAVE_A1NOC_SNOC); +DEFINE_QNODE(mas_usb3, MSM8998_MASTER_USB3, 16, 32, -1, true, NOC_QOS_MODE_FIXED, 1, 2, MSM8998_SLAVE_A1NOC_SNOC); +DEFINE_QNODE(mas_ufs, MSM8998_MASTER_UFS, 16, 68, -1, true, NOC_QOS_MODE_FIXED, 1, 2, MSM8998_SLAVE_A1NOC_SNOC); +DEFINE_QNODE(mas_blsp_2, MSM8998_MASTER_BLSP_2, 16, 39, -1, false, NOC_QOS_MODE_FIXED, 0, 4, MSM8998_SLAVE_A1NOC_SNOC); +DEFINE_QNODE(mas_cnoc_a2noc, MSM8998_MASTER_CNOC_A2NOC, 8, 146, -1, true, -1, 0, -1, MSM8998_SLAVE_A2NOC_SNOC); +DEFINE_QNODE(mas_ipa, MSM8998_MASTER_IPA, 8, 59, -1, true, NOC_QOS_MODE_FIXED, 1, 1, MSM8998_SLAVE_A2NOC_SNOC); +DEFINE_QNODE(mas_sdcc_2, MSM8998_MASTER_SDCC_2, 8, 35, -1, false, NOC_QOS_MODE_FIXED, 0, 6, MSM8998_SLAVE_A2NOC_SNOC); +DEFINE_QNODE(mas_sdcc_4, MSM8998_MASTER_SDCC_4, 8, 36, -1, false, NOC_QOS_MODE_FIXED, 0, 7, MSM8998_SLAVE_A2NOC_SNOC); +DEFINE_QNODE(mas_tsif, MSM8998_MASTER_A2NOC_TSIF, 4, 37, -1, true, -1, 0, -1, MSM8998_SLAVE_A2NOC_SNOC); +DEFINE_QNODE(mas_blsp_1, MSM8998_MASTER_BLSP_1, 16, 41, -1, false, NOC_QOS_MODE_FIXED, 0, 8, MSM8998_SLAVE_A2NOC_SNOC); +DEFINE_QNODE(mas_crvirt_a2noc, MSM8998_MASTER_CRVIRT_A2NOC, 8, 145, -1, false, NOC_QOS_MODE_FIXED, 0, 9, MSM8998_SLAVE_A2NOC_SNOC); +DEFINE_QNODE(mas_gnoc_bimc, MSM8998_MASTER_GNOC_BIMC, 8, 144, -1, true, NOC_QOS_MODE_FIXED, 0, 0, MSM8998_SLAVE_EBI, MSM8998_SLAVE_BIMC_SNOC_0); +DEFINE_QNODE(mas_oxili, MSM8998_MASTER_OXILI, 8, 6, -1, true, NOC_QOS_MODE_BYPASS, 0, 1, MSM8998_SLAVE_BIMC_SNOC_1, MSM8998_SLAVE_HMSS_L3, MSM8998_SLAVE_EBI, MSM8998_SLAVE_BIMC_SNOC_0); +DEFINE_QNODE(mas_mnoc_bimc, MSM8998_MASTER_MNOC_BIMC, 8, 2, -1, true, NOC_QOS_MODE_BYPASS, 0, 2, MSM8998_SLAVE_BIMC_SNOC_1, MSM8998_SLAVE_HMSS_L3, MSM8998_SLAVE_EBI, MSM8998_SLAVE_BIMC_SNOC_0); +DEFINE_QNODE(mas_snoc_bimc, MSM8998_MASTER_SNOC_BIMC, 8, 3, -1, false, NOC_QOS_MODE_BYPASS, 0, 3, MSM8998_SLAVE_HMSS_L3, MSM8998_SLAVE_EBI); +DEFINE_QNODE(mas_snoc_cnoc, MSM8998_MASTER_SNOC_CNOC, 8, 52, -1, true, -1, 0, -1, MSM8998_SLAVE_SKL, MSM8998_SLAVE_BLSP_2, MSM8998_SLAVE_MESSAGE_RAM, MSM8998_SLAVE_TLMM_WEST, MSM8998_SLAVE_TSIF, MSM8998_SLAVE_MPM, MSM8998_SLAVE_BIMC_CFG, MSM8998_SLAVE_TLMM_EAST, MSM8998_SLAVE_SPDM, MSM8998_SLAVE_PIMEM_CFG, MSM8998_SLAVE_A1NOC_SMMU_CFG, MSM8998_SLAVE_BLSP_1, MSM8998_SLAVE_CLK_CTL, MSM8998_SLAVE_PRNG, MSM8998_SLAVE_USB3_0, MSM8998_SLAVE_QDSS_CFG, MSM8998_SLAVE_QM_CFG, MSM8998_SLAVE_A2NOC_CFG, MSM8998_SLAVE_PMIC_ARB, MSM8998_SLAVE_UFS_CFG, MSM8998_SLAVE_SRVC_CNOC, MSM8998_SLAVE_AHB2PHY, MSM8998_SLAVE_IPA, MSM8998_SLAVE_GLM, MSM8998_SLAVE_SNOC_CFG, MSM8998_SLAVE_SSC_CFG, MSM8998_SLAVE_SDCC_2, MSM8998_SLAVE_SDCC_4, MSM8998_SLAVE_PDM, MSM8998_SLAVE_CNOC_MNOC_MMSS_CFG, MSM8998_SLAVE_CNOC_MNOC_CFG, MSM8998_SLAVE_MSS_CFG, MSM8998_SLAVE_IMEM_CFG, MSM8998_SLAVE_A1NOC_CFG, MSM8998_SLAVE_GPUSS_CFG, MSM8998_SLAVE_TCSR, MSM8998_SLAVE_TLMM_NORTH); +DEFINE_QNODE(mas_qdss_dap, MSM8998_MASTER_QDSS_DAP, 8, 49, -1, true, -1, 0, -1, MSM8998_SLAVE_SKL, MSM8998_SLAVE_BLSP_2, MSM8998_SLAVE_MESSAGE_RAM, MSM8998_SLAVE_TLMM_WEST, MSM8998_SLAVE_TSIF, MSM8998_SLAVE_MPM, MSM8998_SLAVE_BIMC_CFG, MSM8998_SLAVE_TLMM_EAST, MSM8998_SLAVE_SPDM, MSM8998_SLAVE_PIMEM_CFG, MSM8998_SLAVE_A1NOC_SMMU_CFG, MSM8998_SLAVE_BLSP_1, MSM8998_SLAVE_CLK_CTL, MSM8998_SLAVE_PRNG, MSM8998_SLAVE_USB3_0, MSM8998_SLAVE_QDSS_CFG, MSM8998_SLAVE_QM_CFG, MSM8998_SLAVE_A2NOC_CFG, MSM8998_SLAVE_PMIC_ARB, MSM8998_SLAVE_UFS_CFG, MSM8998_SLAVE_SRVC_CNOC, MSM8998_SLAVE_AHB2PHY, MSM8998_SLAVE_IPA, MSM8998_SLAVE_GLM, MSM8998_SLAVE_SNOC_CFG, MSM8998_SLAVE_SDCC_2, MSM8998_SLAVE_SDCC_4, MSM8998_SLAVE_PDM, MSM8998_SLAVE_CNOC_MNOC_MMSS_CFG, MSM8998_SLAVE_CNOC_MNOC_CFG, MSM8998_SLAVE_MSS_CFG, MSM8998_SLAVE_IMEM_CFG, MSM8998_SLAVE_A1NOC_CFG, MSM8998_SLAVE_GPUSS_CFG, MSM8998_SLAVE_SSC_CFG, MSM8998_SLAVE_TCSR, MSM8998_SLAVE_TLMM_NORTH, MSM8998_SLAVE_CNOC_A2NOC); +DEFINE_QNODE(mas_crypto, MSM8998_MASTER_CRYPTO_C0, 650, 23, -1, false, -1, 0, -1, MSM8998_MASTER_CRVIRT_A2NOC); +DEFINE_QNODE(mas_apss_proc, MSM8998_MASTER_APPS_PROC, 32, 0, -1, true, -1, 0, -1, MSM8998_SLAVE_GNOC_BIMC); +DEFINE_QNODE(mas_cnoc_mnoc_mmss_cfg, MSM8998_MASTER_CNOC_MNOC_MMSS_CFG, 8, 4, -1, true, -1, 0, -1, MSM8998_SLAVE_CAMERA_THROTTLE_CFG, MSM8998_SLAVE_VENUS_CFG, MSM8998_SLAVE_MISC_CFG, MSM8998_SLAVE_CAMERA_CFG, MSM8998_SLAVE_DISPLAY_THROTTLE_CFG, MSM8998_SLAVE_VENUS_THROTTLE_CFG, MSM8998_SLAVE_DISPLAY_CFG, MSM8998_SLAVE_MMSS_CLK_CFG, MSM8998_SLAVE_VMEM_CFG, MSM8998_SLAVE_MMSS_CLK_XPU_CFG, MSM8998_SLAVE_SMMU_CFG); +DEFINE_QNODE(mas_cnoc_mnoc_cfg, MSM8998_MASTER_CNOC_MNOC_CFG, 8, 5, -1, true, -1, 0, -1, MSM8998_SLAVE_SRVC_MNOC); +DEFINE_QNODE(mas_cpp, MSM8998_MASTER_CPP, 32, 115, -1, true, NOC_QOS_MODE_BYPASS, 0, 5, MSM8998_SLAVE_MNOC_BIMC); +DEFINE_QNODE(mas_jpeg, MSM8998_MASTER_JPEG, 32, 7, -1, true, NOC_QOS_MODE_BYPASS, 0, 7, MSM8998_SLAVE_MNOC_BIMC); +DEFINE_QNODE(mas_mdp_p0, MSM8998_MASTER_MDP_P0, 32, 8, -1, true, NOC_QOS_MODE_BYPASS, 0, 1, MSM8998_SLAVE_MNOC_BIMC); /* vrail-comp???? */ +DEFINE_QNODE(mas_mdp_p1, MSM8998_MASTER_MDP_P1, 32, 61, -1, true, NOC_QOS_MODE_BYPASS, 0, 2, MSM8998_SLAVE_MNOC_BIMC); /* vrail-comp??? */ +DEFINE_QNODE(mas_rotator, MSM8998_MASTER_ROTATOR, 32, 120, -1, true, NOC_QOS_MODE_BYPASS, 0, 0, MSM8998_SLAVE_MNOC_BIMC); +DEFINE_QNODE(mas_venus, MSM8998_MASTER_VENUS, 32, 9, -1, true, NOC_QOS_MODE_BYPASS, 0, 3, MSM8998_SLAVE_MNOC_BIMC); +DEFINE_QNODE(mas_vfe, MSM8998_MASTER_VFE, 32, 11, -1, true, NOC_QOS_MODE_BYPASS, 0, 6, MSM8998_SLAVE_MNOC_BIMC); +DEFINE_QNODE(mas_venus_vmem, MSM8998_MASTER_VENUS_VMEM, 32, 121, -1, true, -1, 0, -1, MSM8998_SLAVE_VMEM); +DEFINE_QNODE(mas_hmss, MSM8998_MASTER_HMSS, 16, 118, -1, true, NOC_QOS_MODE_FIXED, 1, 3, MSM8998_SLAVE_PIMEM, MSM8998_SLAVE_IMEM, MSM8998_SLAVE_SNOC_BIMC); +DEFINE_QNODE(mas_qdss_bam, MSM8998_MASTER_QDSS_BAM, 16, 19, -1, true, NOC_QOS_MODE_FIXED, 1, 1, MSM8998_SLAVE_IMEM, MSM8998_SLAVE_PIMEM, MSM8998_SLAVE_SNOC_CNOC, MSM8998_SLAVE_SNOC_BIMC); +DEFINE_QNODE(mas_snoc_cfg, MSM8998_MASTER_SNOC_CFG, 16, 20, -1, false, -1, 0, -1, MSM8998_SLAVE_SRVC_SNOC); +DEFINE_QNODE(mas_bimc_snoc_0, MSM8998_MASTER_BIMC_SNOC_0, 16, 21, -1, false, -1, 0, -1, MSM8998_SLAVE_PIMEM, MSM8998_SLAVE_LPASS, MSM8998_SLAVE_HMSS, MSM8998_SLAVE_WLAN, MSM8998_SLAVE_SNOC_CNOC, MSM8998_SLAVE_IMEM, MSM8998_SLAVE_QDSS_STM); +DEFINE_QNODE(mas_bimc_snoc_1, MSM8998_MASTER_BIMC_SNOC_1, 16, 109, -1, true, -1, 0, -1, MSM8998_SLAVE_PCIE_0); +DEFINE_QNODE(mas_a1noc_snoc, MSM8998_MASTER_A1NOC_SNOC, 16, 111, -1, false, -1, 0, -1, MSM8998_SLAVE_PIMEM, MSM8998_SLAVE_PCIE_0, MSM8998_SLAVE_LPASS, MSM8998_SLAVE_HMSS, MSM8998_SLAVE_SNOC_BIMC, MSM8998_SLAVE_SNOC_CNOC, MSM8998_SLAVE_IMEM, MSM8998_SLAVE_QDSS_STM); +DEFINE_QNODE(mas_a2noc_snoc, MSM8998_MASTER_A2NOC_SNOC, 16, 112, -1, false, -1, 0, -1, MSM8998_SLAVE_PIMEM, MSM8998_SLAVE_PCIE_0, MSM8998_SLAVE_LPASS, MSM8998_SLAVE_HMSS, MSM8998_SLAVE_SNOC_BIMC, MSM8998_SLAVE_WLAN, MSM8998_SLAVE_SNOC_CNOC, MSM8998_SLAVE_IMEM, MSM8998_SLAVE_QDSS_STM); +DEFINE_QNODE(mas_qdss_etr, MSM8998_MASTER_QDSS_ETR, 16, 31, -1, true, NOC_QOS_MODE_FIXED, 1, 2, MSM8998_SLAVE_IMEM, MSM8998_MASTER_PIMEM, MSM8998_SLAVE_SNOC_CNOC, MSM8998_SLAVE_SNOC_BIMC); + +/* slaves */ +DEFINE_QNODE(slv_a1noc_snoc, MSM8998_SLAVE_A1NOC_SNOC, 16, -1, 142, false, -1, 0, -1, MSM8998_MASTER_A1NOC_SNOC); +DEFINE_QNODE(slv_a2noc_snoc, MSM8998_SLAVE_A2NOC_SNOC, 16, -1, 143, false, -1, 0, -1, MSM8998_MASTER_A2NOC_SNOC); +DEFINE_QNODE(slv_ebi, MSM8998_SLAVE_EBI, 8, -1, 0, false, -1, 0, -1, 0); +DEFINE_QNODE(slv_hmss_l3, MSM8998_SLAVE_HMSS_L3, 8, -1, 160, false, -1, 0, -1, 0); +DEFINE_QNODE(slv_bimc_snoc_0, MSM8998_SLAVE_BIMC_SNOC_0, 8, -1, 2, false, -1, 0, -1, MSM8998_MASTER_BIMC_SNOC_0); +DEFINE_QNODE(slv_bimc_snoc_1, MSM8998_SLAVE_BIMC_SNOC_1, 8, -1, 138, true, -1, 0, -1, MSM8998_MASTER_BIMC_SNOC_1); +DEFINE_QNODE(slv_cnoc_a2noc, MSM8998_SLAVE_CNOC_A2NOC, 4, -1, 208, true, -1, 0, -1, MSM8998_MASTER_CNOC_A2NOC); +DEFINE_QNODE(slv_ssc_cfg, MSM8998_SLAVE_SSC_CFG, 4, -1, 177, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_mpm, MSM8998_SLAVE_MPM, 4, -1, 62, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_pmic_arb, MSM8998_SLAVE_PMIC_ARB, 4, -1, 59, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_tlmm_north, MSM8998_SLAVE_TLMM_NORTH, 4, -1, 214, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_pimem_cfg, MSM8998_SLAVE_PIMEM_CFG, 4, -1, 167, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_imem_cfg, MSM8998_SLAVE_IMEM_CFG, 4, -1, 54, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_message_ram, MSM8998_SLAVE_MESSAGE_RAM, 4, -1, 55, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_skl, MSM8998_SLAVE_SKL, 4, -1, 196, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_bimc_cfg, MSM8998_SLAVE_BIMC_CFG, 4, -1, 56, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_prng, MSM8998_SLAVE_PRNG, 4, -1, 44, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_a2noc_cfg, MSM8998_SLAVE_A2NOC_CFG, 4, -1, 150, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_ipa, MSM8998_SLAVE_IPA, 4, -1, 183, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_tcsr, MSM8998_SLAVE_TCSR, 4, -1, 50, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_snoc_cfg, MSM8998_SLAVE_SNOC_CFG, 4, -1, 70, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_clk_ctl, MSM8998_SLAVE_CLK_CTL, 4, -1, 47, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_glm, MSM8998_SLAVE_GLM, 4, -1, 209, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_spdm, MSM8998_SLAVE_SPDM, 4, -1, 60, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_gpuss_cfg, MSM8998_SLAVE_GPUSS_CFG, 4, -1, 11, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_cnoc_mnoc_cfg, MSM8998_SLAVE_BLSP_1, 4, -1, 66, true, -1, 0, -1, MSM8998_MASTER_CNOC_MNOC_CFG); +DEFINE_QNODE(slv_qm_cfg, MSM8998_SLAVE_QM_CFG, 4, -1, 212, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_mss_cfg, MSM8998_SLAVE_MSS_CFG, 4, -1, 48, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_ufs_cfg, MSM8998_SLAVE_UFS_CFG, 4, -1, 92, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_tlmm_west, MSM8998_SLAVE_TLMM_WEST, 4, -1, 215, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_a1noc_cfg, MSM8998_SLAVE_A1NOC_CFG, 4, -1, 147, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_ahb2phy, MSM8998_SLAVE_AHB2PHY, 4, -1, 163, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_blsp_2, MSM8998_SLAVE_BLSP_2, 4, -1, 37, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_pdm, MSM8998_SLAVE_PDM, 4, -1, 41, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_usb3_0, MSM8998_SLAVE_USB3_0, 4, -1, 22, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_a1noc_smmu_cfg, MSM8998_SLAVE_A1NOC_SMMU_CFG, 8, -1, 149, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_blsp_1, MSM8998_SLAVE_BLSP_1, 4, -1, 39, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_sdcc_2, MSM8998_SLAVE_SDCC_2, 4, -1, 33, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_sdcc_4, MSM8998_SLAVE_SDCC_4, 4, -1, 34, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_tsif, MSM8998_SLAVE_TSIF, 4, -1, 35, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_qdss_cfg, MSM8998_SLAVE_QDSS_CFG, 4, -1, 63, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_tlmm_east, MSM8998_SLAVE_TLMM_EAST, 4, -1, 213, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_cnoc_mnoc_mmss_cfg, MSM8998_SLAVE_CNOC_MNOC_MMSS_CFG, 8, -1, 58, true, -1, 0, -1, MSM8998_MASTER_CNOC_MNOC_MMSS_CFG); +DEFINE_QNODE(slv_srvc_cnoc, MSM8998_SLAVE_SRVC_CNOC, 4, -1, 76, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_crvirt_a2noc, MSM8998_SLAVE_CRVIRT_A2NOC, 8, -1, 207, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_gnoc_bimc, MSM8998_SLAVE_GNOC_BIMC, 32, -1, 210, true, -1, 0, -1, MSM8998_MASTER_GNOC_BIMC); +DEFINE_QNODE(slv_camera_cfg, MSM8998_SLAVE_CAMERA_CFG, 8, -1, 3, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_camera_throttle_cfg, MSM8998_SLAVE_CAMERA_THROTTLE_CFG, 8, -1, 154, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_misc_cfg, MSM8998_SLAVE_MISC_CFG, 8, -1, 8, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_venus_throttle_cfg, MSM8998_SLAVE_VENUS_THROTTLE_CFG, 8, -1, 178, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_venus_cfg, MSM8998_SLAVE_VENUS_CFG, 8, -1, 10, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_vmem_cfg, MSM8998_SLAVE_VMEM_CFG, 8, -1, 180, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_mmss_clk_xpu_cfg, MSM8998_SLAVE_MMSS_CLK_XPU_CFG, 8, -1, 13, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_mmss_clk_cfg, MSM8998_SLAVE_MMSS_CLK_CFG, 8, -1, 12, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_display_cfg, MSM8998_SLAVE_DISPLAY_CFG, 8, -1, 4, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_display_throttle_cfg, MSM8998_SLAVE_DISPLAY_THROTTLE_CFG, 4, -1, 156, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_smmu_cfg, MSM8998_SLAVE_SMMU_CFG, 8, -1, 205, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_mnoc_bimc, MSM8998_SLAVE_MNOC_BIMC, 32, -1, 16, true, -1, 0, -1, MSM8998_MASTER_MNOC_BIMC); +DEFINE_QNODE(slv_vmem, MSM8998_SLAVE_VMEM, 32, -1, 179, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_srvc_mnoc, MSM8998_SLAVE_SRVC_MNOC, 8, -1, 17, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_hmss, MSM8998_SLAVE_HMSS, 16, -1, 20, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_lpass, MSM8998_SLAVE_LPASS, 16, -1, 21, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_wlan, MSM8998_SLAVE_WLAN, 16, -1, 206, false, -1, 0, -1, 0); +DEFINE_QNODE(slv_snoc_bimc, MSM8998_SLAVE_SNOC_BIMC, 32, -1, 24, false, -1, 0, -1, MSM8998_MASTER_SNOC_BIMC); +DEFINE_QNODE(slv_snoc_cnoc, MSM8998_SLAVE_SNOC_CNOC, 16, -1, 25, false, -1, 0, -1, MSM8998_MASTER_SNOC_CNOC); +DEFINE_QNODE(slv_imem, MSM8998_SLAVE_IMEM, 16, -1, 26, false, -1, 0, -1, 0); +DEFINE_QNODE(slv_pimem, MSM8998_SLAVE_PIMEM, 16, -1, 166, false, -1, 0, -1, 0); +DEFINE_QNODE(slv_qdss_stm, MSM8998_SLAVE_QDSS_STM, 16, -1, 30, false, -1, 0, -1, 0); +DEFINE_QNODE(slv_pcie_0, MSM8998_SLAVE_PCIE_0, 16, -1, 84, true, -1, 0, -1, 0); +DEFINE_QNODE(slv_srvc_snoc, MSM8998_SLAVE_SRVC_SNOC, 16, -1, 29, false, -1, 0, -1, 0); + +static struct qcom_icc_node *msm8998_a1noc_nodes[] = { + [MASTER_PCIE_0] = &mas_pcie_0, + [MASTER_USB3] = &mas_usb3, + [MASTER_UFS] = &mas_ufs, + [MASTER_BLSP_2] = &mas_blsp_2, + [SLAVE_A1NOC_SNOC] = &slv_a1noc_snoc, +}; + +static const struct regmap_config msm8998_a1noc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x60000, + .fast_io = true, +}; + +static struct qcom_icc_desc msm8998_a1noc = { + .nodes = msm8998_a1noc_nodes, + .num_nodes = ARRAY_SIZE(msm8998_a1noc_nodes), + .regmap_cfg = &msm8998_a1noc_regmap_config, +}; + +static struct qcom_icc_node *msm8998_a2noc_nodes[] = { + [MASTER_IPA] = &mas_ipa, + [MASTER_CNOC_A2NOC] = &mas_cnoc_a2noc, + [MASTER_SDCC_2] = &mas_sdcc_2, + [MASTER_SDCC_4] = &mas_sdcc_4, + [MASTER_TSIF] = &mas_tsif, + [MASTER_BLSP_1] = &mas_blsp_1, + [MASTER_CRVIRT_A2NOC] = &mas_crvirt_a2noc, + [MASTER_CRYPTO_C0] = &mas_crypto, + [SLAVE_A2NOC_SNOC] = &slv_a2noc_snoc, + [SLAVE_CRVIRT_A2NOC] = &slv_crvirt_a2noc, +}; + +static const struct regmap_config msm8998_a2noc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x60000, + .fast_io = true, +}; + +static struct qcom_icc_desc msm8998_a2noc = { + .nodes = msm8998_a2noc_nodes, + .num_nodes = ARRAY_SIZE(msm8998_a2noc_nodes), + .regmap_cfg = &msm8998_a2noc_regmap_config, +}; + +static struct qcom_icc_node *msm8998_bimc_nodes[] = { + [MASTER_GNOC_BIMC] = &mas_gnoc_bimc, + [MASTER_OXILI] = &mas_oxili, + [MASTER_MNOC_BIMC] = &mas_mnoc_bimc, + [MASTER_SNOC_BIMC] = &mas_snoc_bimc, + [SLAVE_EBI] = &slv_ebi, + [SLAVE_HMSS_L3] = &slv_hmss_l3, + [SLAVE_BIMC_SNOC_0] = &slv_bimc_snoc_0, + [SLAVE_BIMC_SNOC_1] = &slv_bimc_snoc_1, +}; + +static const struct regmap_config msm8998_bimc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x80000, + .fast_io = true, +}; + +static struct qcom_icc_desc msm8998_bimc = { + .nodes = msm8998_bimc_nodes, + .num_nodes = ARRAY_SIZE(msm8998_bimc_nodes), + .regmap_cfg = &msm8998_bimc_regmap_config, +}; + +static struct qcom_icc_node *msm8998_cnoc_nodes[] = { + [MASTER_SNOC_CNOC] = &mas_snoc_cnoc, + [MASTER_QDSS_DAP] = &mas_qdss_dap, + [SLAVE_CNOC_A2NOC] = &slv_cnoc_a2noc, + [SLAVE_SSC_CFG] = &slv_ssc_cfg, + [SLAVE_MPM] = &slv_mpm, + [SLAVE_PMIC_ARB] = &slv_pmic_arb, + [SLAVE_TLMM_NORTH] = &slv_tlmm_north, + [SLAVE_PIMEM_CFG] = &slv_pimem_cfg, + [SLAVE_IMEM_CFG] = &slv_imem_cfg, + [SLAVE_MESSAGE_RAM] = &slv_message_ram, + [SLAVE_SKL] = &slv_skl, + [SLAVE_BIMC_CFG] = &slv_bimc_cfg, + [SLAVE_PRNG] = &slv_prng, + [SLAVE_A2NOC_CFG] = &slv_a2noc_cfg, + [SLAVE_IPA] = &slv_ipa, + [SLAVE_TCSR] = &slv_tcsr, + [SLAVE_SNOC_CFG] = &slv_snoc_cfg, + [SLAVE_CLK_CTL] = &slv_clk_ctl, + [SLAVE_GLM] = &slv_glm, + [SLAVE_SPDM] = &slv_spdm, + [SLAVE_GPUSS_CFG] = &slv_gpuss_cfg, + [SLAVE_CNOC_MNOC_CFG] = &slv_cnoc_mnoc_cfg, + [SLAVE_QM_CFG] = &slv_qm_cfg, + [SLAVE_MSS_CFG] = &slv_mss_cfg, + [SLAVE_UFS_CFG] = &slv_ufs_cfg, + [SLAVE_TLMM_WEST] = &slv_tlmm_west, + [SLAVE_A1NOC_CFG] = &slv_a1noc_cfg, + [SLAVE_AHB2PHY] = &slv_ahb2phy, + [SLAVE_BLSP_2] = &slv_blsp_2, + [SLAVE_PDM] = &slv_pdm, + [SLAVE_USB3_0] = &slv_usb3_0, + [SLAVE_A1NOC_SMMU_CFG] = &slv_a1noc_smmu_cfg, + [SLAVE_BLSP_1] = &slv_blsp_1, + [SLAVE_SDCC_2] = &slv_sdcc_2, + [SLAVE_SDCC_4] = &slv_sdcc_4, + [SLAVE_TSIF] = &slv_tsif, + [SLAVE_QDSS_CFG] = &slv_qdss_cfg, + [SLAVE_TLMM_EAST] = &slv_tlmm_east, + [SLAVE_CNOC_MNOC_MMSS_CFG] = &slv_cnoc_mnoc_mmss_cfg, + [SLAVE_SRVC_CNOC] = &slv_srvc_cnoc, +}; + +static const struct regmap_config msm8998_cnoc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x10000, + .fast_io = true, +}; + +static struct qcom_icc_desc msm8998_cnoc = { + .nodes = msm8998_cnoc_nodes, + .num_nodes = ARRAY_SIZE(msm8998_cnoc_nodes), + .regmap_cfg = &msm8998_cnoc_regmap_config, +}; + +static struct qcom_icc_node *msm8998_gnoc_nodes[] = { + [MASTER_APSS_PROC] = &mas_apss_proc, + [SLAVE_GNOC_BIMC] = &slv_gnoc_bimc, +}; + +static const struct regmap_config msm8998_gnoc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x10000, + .fast_io = true, +}; + +static struct qcom_icc_desc msm8998_gnoc = { + .nodes = msm8998_gnoc_nodes, + .num_nodes = ARRAY_SIZE(msm8998_gnoc_nodes), + .regmap_cfg = &msm8998_gnoc_regmap_config, +}; + +static struct qcom_icc_node *msm8998_mnoc_nodes[] = { + [MASTER_CNOC_MNOC_CFG] = &mas_cnoc_mnoc_cfg, + [MASTER_CPP] = &mas_cpp, + [MASTER_JPEG] = &mas_jpeg, + [MASTER_MDP_P0] = &mas_mdp_p0, + [MASTER_MDP_P1] = &mas_mdp_p1, + [MASTER_ROTATOR] = &mas_rotator, + [MASTER_VENUS] = &mas_venus, + [MASTER_VFE] = &mas_vfe, + [MASTER_VENUS_VMEM] = &mas_venus_vmem, + [SLAVE_MNOC_BIMC] = &slv_mnoc_bimc, + [SLAVE_VMEM] = &slv_vmem, + [SLAVE_SRVC_MNOC] = &slv_srvc_mnoc, + [MASTER_CNOC_MNOC_MMSS_CFG] = &mas_cnoc_mnoc_mmss_cfg, + [SLAVE_CAMERA_CFG] = &slv_camera_cfg, + [SLAVE_CAMERA_THROTTLE_CFG] = &slv_camera_throttle_cfg, + [SLAVE_MISC_CFG] = &slv_misc_cfg, + [SLAVE_VENUS_THROTTLE_CFG] = &slv_venus_throttle_cfg, + [SLAVE_VENUS_CFG] = &slv_venus_cfg, + [SLAVE_VMEM_CFG] = &slv_vmem_cfg, + [SLAVE_MMSS_CLK_XPU_CFG] = &slv_mmss_clk_xpu_cfg, + [SLAVE_MMSS_CLK_CFG] = &slv_mmss_clk_cfg, + [SLAVE_DISPLAY_CFG] = &slv_display_cfg, + [SLAVE_DISPLAY_THROTTLE_CFG] = &slv_display_throttle_cfg, + [SLAVE_SMMU_CFG] = &slv_smmu_cfg, +}; + +static const struct regmap_config msm8998_mnoc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x10000, + .fast_io = true, +}; + +static struct qcom_icc_desc msm8998_mnoc = { + .nodes = msm8998_mnoc_nodes, + .num_nodes = ARRAY_SIZE(msm8998_mnoc_nodes), + .regmap_cfg = &msm8998_mnoc_regmap_config, +}; + +static struct qcom_icc_node *msm8998_snoc_nodes[] = { + [MASTER_HMSS] = &mas_hmss, + [MASTER_QDSS_BAM] = &mas_qdss_bam, + [MASTER_SNOC_CFG] = &mas_snoc_cfg, + [MASTER_BIMC_SNOC_0] = &mas_bimc_snoc_0, + [MASTER_BIMC_SNOC_1] = &mas_bimc_snoc_1, + [MASTER_A1NOC_SNOC] = &mas_a1noc_snoc, + [MASTER_A2NOC_SNOC] = &mas_a2noc_snoc, + [MASTER_QDSS_ETR] = &mas_qdss_etr, + [SLAVE_HMSS] = &slv_hmss, + [SLAVE_LPASS] = &slv_lpass, + [SLAVE_WLAN] = &slv_wlan, + [SLAVE_SNOC_BIMC] = &slv_snoc_bimc, + [SLAVE_SNOC_CNOC] = &slv_snoc_cnoc, + [SLAVE_IMEM] = &slv_imem, + [SLAVE_PIMEM] = &slv_pimem, + [SLAVE_QDSS_STM] = &slv_qdss_stm, + [SLAVE_PCIE_0] = &slv_pcie_0, + [SLAVE_SRVC_SNOC] = &slv_srvc_snoc, +}; + +static const struct regmap_config msm8998_snoc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x40000, + .fast_io = true, +}; + +static struct qcom_icc_desc msm8998_snoc = { + .nodes = msm8998_snoc_nodes, + .num_nodes = ARRAY_SIZE(msm8998_snoc_nodes), + .regmap_cfg = &msm8998_snoc_regmap_config, +}; + +static int qcom_icc_bimc_set_qos_health(struct regmap *rmap, + struct qcom_icc_qos *qos, + int regnum) +{ + u32 val; + u32 mask; + + val = qos->prio_level; + mask = M_BKE_HEALTH_CFG_PRIOLVL_MASK; + + val |= qos->areq_prio << M_BKE_HEALTH_CFG_AREQPRIO_SHIFT; + mask |= M_BKE_HEALTH_CFG_AREQPRIO_MASK; + + /* LIMITCMDS is not present on M_BKE_HEALTH_3 */ + if (regnum != 3) { + val |= qos->limit_commands << M_BKE_HEALTH_CFG_LIMITCMDS_SHIFT; + mask |= M_BKE_HEALTH_CFG_LIMITCMDS_MASK; + } + + return regmap_update_bits(rmap, + M_BKE_HEALTH_CFG_ADDR(regnum, qos->qos_port), + mask, val); +} + +static int qcom_icc_set_bimc_qos(struct icc_node *src, u64 max_bw, + bool bypass_mode) +{ + struct qcom_icc_provider *qp; + struct qcom_icc_node *qn; + struct icc_provider *provider; + u32 mode = NOC_QOS_MODE_BYPASS; + u32 val = 0; + int i, rc = 0; + + qn = src->data; + provider = src->provider; + qp = to_qcom_provider(provider); + + if (qn->qos.qos_mode != -1) + mode = qn->qos.qos_mode; + + /* + * QoS Priority: The QoS Health parameters are getting considered + * only if we are NOT in Bypass Mode. + */ + if (mode != NOC_QOS_MODE_BYPASS) { + for (i = 3; i >= 0; i--) { + rc = qcom_icc_bimc_set_qos_health(qp->regmap, + &qn->qos, i); + if (rc) + return rc; + } + + /* Set BKE_EN to 1 when Fixed, Regulator or Limiter Mode */ + val = 1; + } + + return regmap_update_bits(qp->regmap, M_BKE_EN_ADDR(qn->qos.qos_port), + M_BKE_EN_EN_BMASK, val); +} + +static int qcom_icc_noc_set_qos_priority(struct regmap *rmap, + struct qcom_icc_qos *qos) +{ + u32 val; + int rc; + + /* Must be updated one at a time, P1 first, P0 last */ + val = qos->areq_prio << NOC_QOS_PRIORITY_P1_SHIFT; + rc = regmap_update_bits(rmap, NOC_QOS_PRIORITYn_ADDR(qos->qos_port), + NOC_QOS_PRIORITY_MASK, val); + if (rc) + return rc; + + val = qos->prio_level << NOC_QOS_PRIORITY_P0_SHIFT; + return regmap_update_bits(rmap, NOC_QOS_PRIORITYn_ADDR(qos->qos_port), + NOC_QOS_PRIORITY_MASK, val); +} + +static int qcom_icc_set_noc_qos(struct icc_node *src, u64 max_bw) +{ + struct qcom_icc_provider *qp; + struct qcom_icc_node *qn; + struct icc_provider *provider; + u32 mode = NOC_QOS_MODE_BYPASS; + int rc = 0; + + qn = src->data; + provider = src->provider; + qp = to_qcom_provider(provider); + + if (qn->qos.qos_port < 0) { + dev_dbg(src->provider->dev, + "NoC QoS: Skipping %s: vote aggregated on parent.\n", + qn->name); + return 0; + } + + if (qn->qos.qos_mode != -1) + mode = qn->qos.qos_mode; + + if (mode == NOC_QOS_MODE_FIXED) { + dev_dbg(src->provider->dev, "NoC QoS: %s: Set Fixed mode\n", + qn->name); + rc = qcom_icc_noc_set_qos_priority(qp->regmap, &qn->qos); + if (rc) + return rc; + } else if (mode == NOC_QOS_MODE_BYPASS) { + dev_dbg(src->provider->dev, "NoC QoS: %s: Set Bypass mode\n", + qn->name); + } + + return regmap_update_bits(qp->regmap, + NOC_QOS_MODEn_ADDR(qn->qos.qos_port), + NOC_QOS_MODEn_MASK, mode); +} + +static int qcom_icc_qos_set(struct icc_node *node, u64 sum_bw) +{ + struct qcom_icc_provider *qp = to_qcom_provider(node->provider); + struct qcom_icc_node *qn = node->data; + + dev_dbg(node->provider->dev, "Setting QoS for %s\n", qn->name); + + if (qp->is_bimc_node) + return qcom_icc_set_bimc_qos(node, sum_bw, + (qn->qos.qos_mode == NOC_QOS_MODE_BYPASS)); + + return qcom_icc_set_noc_qos(node, sum_bw); +} + +static int qcom_icc_rpm_set(int mas_rpm_id, int slv_rpm_id, u64 sum_bw) +{ + int ret = 0; + + if (mas_rpm_id != -1) { + ret = qcom_icc_rpm_smd_send(QCOM_SMD_RPM_ACTIVE_STATE, + RPM_BUS_MASTER_REQ, + mas_rpm_id, + sum_bw); + if (ret) { + pr_err("qcom_icc_rpm_smd_send mas %d error %d\n", + mas_rpm_id, ret); + return ret; + } + } + + if (slv_rpm_id != -1) { + ret = qcom_icc_rpm_smd_send(QCOM_SMD_RPM_ACTIVE_STATE, + RPM_BUS_SLAVE_REQ, + slv_rpm_id, + sum_bw); + if (ret) { + pr_err("qcom_icc_rpm_smd_send slv %d error %d\n", + slv_rpm_id, ret); + return ret; + } + } + + return ret; +} + +static int qcom_icc_set(struct icc_node *src, struct icc_node *dst) +{ + struct qcom_icc_provider *qp; + struct qcom_icc_node *qn; + struct icc_provider *provider; + struct icc_node *n; + u64 sum_bw; + u64 max_peak_bw; + u64 rate; + u32 agg_avg = 0; + u32 agg_peak = 0; + int ret, i; + + qn = src->data; + provider = src->provider; + qp = to_qcom_provider(provider); + + list_for_each_entry(n, &provider->nodes, node_list) + provider->aggregate(n, 0, n->avg_bw, n->peak_bw, + &agg_avg, &agg_peak); + + sum_bw = icc_units_to_bps(agg_avg); + max_peak_bw = icc_units_to_bps(agg_peak); + + if (!qn->qos.ap_owned) { + /* send bandwidth request message to the RPM processor */ + ret = qcom_icc_rpm_set(qn->mas_rpm_id, qn->slv_rpm_id, sum_bw); + if (ret) + return ret; + } else if (qn->qos.qos_mode != -1) { + /* set bandwidth directly from the AP */ + ret = qcom_icc_qos_set(src, sum_bw); + if (ret) + return ret; + } + + rate = max(sum_bw, max_peak_bw); + + do_div(rate, qn->buswidth); + + if (qn->rate == rate) + return 0; + + for (i = 0; i < qp->num_clks; i++) { + ret = clk_set_rate(qp->bus_clks[i].clk, rate); + if (ret) { + pr_err("%s clk_set_rate error: %d\n", + qp->bus_clks[i].id, ret); + return ret; + } + } + + qn->rate = rate; + + return 0; +} + +static int qnoc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct qcom_icc_desc *desc; + struct icc_onecell_data *data; + struct icc_provider *provider; + struct qcom_icc_node **qnodes; + struct qcom_icc_provider *qp; + struct icc_node *node; + struct resource *res; + size_t num_nodes, i; + int ret; + + /* wait for the RPM proxy */ + if (!qcom_icc_rpm_smd_available()) + return -EPROBE_DEFER; + + desc = of_device_get_match_data(dev); + if (!desc) + return -EINVAL; + + qnodes = desc->nodes; + num_nodes = desc->num_nodes; + + qp = devm_kzalloc(dev, sizeof(*qp), GFP_KERNEL); + if (!qp) + return -ENOMEM; + + data = devm_kzalloc(dev, struct_size(data, nodes, num_nodes), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + if (of_device_is_compatible(dev->of_node, "qcom,msm8998-mnoc")) { + qp->bus_clks = devm_kmemdup(dev, bus_mm_clocks, + sizeof(bus_mm_clocks), GFP_KERNEL); + qp->num_clks = ARRAY_SIZE(bus_mm_clocks); + } else { + if (of_device_is_compatible(dev->of_node, "qcom,msm8998-bimc")) + qp->is_bimc_node = true; + + qp->bus_clks = devm_kmemdup(dev, bus_clocks, sizeof(bus_clocks), + GFP_KERNEL); + qp->num_clks = ARRAY_SIZE(bus_clocks); + } + if (!qp->bus_clks) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + qp->mmio = devm_ioremap_resource(dev, res); + if (IS_ERR(qp->mmio)) { + dev_err(dev, "Cannot ioremap interconnect bus resource\n"); + return PTR_ERR(qp->mmio); + } + + qp->regmap = devm_regmap_init_mmio(dev, qp->mmio, desc->regmap_cfg); + if (IS_ERR(qp->regmap)) { + dev_err(dev, "Cannot regmap interconnect bus resource\n"); + return PTR_ERR(qp->regmap); + } + + ret = devm_clk_bulk_get(dev, qp->num_clks, qp->bus_clks); + if (ret) + return ret; + + ret = clk_bulk_prepare_enable(qp->num_clks, qp->bus_clks); + if (ret) + return ret; + + provider = &qp->provider; + INIT_LIST_HEAD(&provider->nodes); + provider->dev = dev; + provider->set = qcom_icc_set; + provider->aggregate = icc_std_aggregate; + provider->xlate = of_icc_xlate_onecell; + provider->data = data; + + ret = icc_provider_add(provider); + if (ret) { + dev_err(dev, "error adding interconnect provider: %d\n", ret); + clk_bulk_disable_unprepare(qp->num_clks, qp->bus_clks); + return ret; + } + + for (i = 0; i < num_nodes; i++) { + size_t j; + + node = icc_node_create(qnodes[i]->id); + if (IS_ERR(node)) { + ret = PTR_ERR(node); + goto err; + } + + node->name = qnodes[i]->name; + node->data = qnodes[i]; + icc_node_add(node, provider); + + for (j = 0; j < qnodes[i]->num_links; j++) + icc_link_create(node, qnodes[i]->links[j]); + + data->nodes[i] = node; + } + data->num_nodes = num_nodes; + platform_set_drvdata(pdev, qp); + + return 0; +err: + icc_nodes_remove(provider); + clk_bulk_disable_unprepare(qp->num_clks, qp->bus_clks); + icc_provider_del(provider); + + return ret; +} + +static int qnoc_remove(struct platform_device *pdev) +{ + struct qcom_icc_provider *qp = platform_get_drvdata(pdev); + + icc_nodes_remove(&qp->provider); + clk_bulk_disable_unprepare(qp->num_clks, qp->bus_clks); + return icc_provider_del(&qp->provider); +} + +static const struct of_device_id msm8998_noc_of_match[] = { + { .compatible = "qcom,msm8998-a1noc", .data = &msm8998_a1noc }, + { .compatible = "qcom,msm8998-a2noc", .data = &msm8998_a2noc }, + { .compatible = "qcom,msm8998-bimc", .data = &msm8998_bimc }, + { .compatible = "qcom,msm8998-cnoc", .data = &msm8998_cnoc }, + { .compatible = "qcom,msm8998-gnoc", .data = &msm8998_gnoc }, + { .compatible = "qcom,msm8998-mnoc", .data = &msm8998_mnoc }, + { .compatible = "qcom,msm8998-snoc", .data = &msm8998_snoc }, + { }, +}; +MODULE_DEVICE_TABLE(of, msm8998_noc_of_match); + +static struct platform_driver msm8998_noc_driver = { + .probe = qnoc_probe, + .remove = qnoc_remove, + .driver = { + .name = "qnoc-msm8998", + .of_match_table = msm8998_noc_of_match, + .sync_state = icc_sync_state, + }, +}; +module_platform_driver(msm8998_noc_driver); +MODULE_DESCRIPTION("Qualcomm msm8998 NoC driver"); +MODULE_LICENSE("GPL v2"); From d805ce6ecec8e2722877205f0ee60f34f540e6e3 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Fri, 4 Dec 2020 22:34:58 +0100 Subject: [PATCH 088/187] pinctrl: Add driver for Awinic AW9523/B I2C GPIO Expander The Awinic AW9523(B) is a multi-function I2C gpio expander in a TQFN-24L package, featuring PWM (max 37mA per pin, or total max power 3.2Watts) for LED driving capability. It has two ports with 8 pins per port (for a total of 16 pins), configurable as either PWM with 1/256 stepping or GPIO input/output, 1.8V logic input; each GPIO can be configured as input or output independently from each other. This IC also has an internal interrupt controller, which is capable of generating an interrupt for each GPIO, depending on the configuration, and will raise an interrupt on the INTN pin to advertise this to an external interrupt controller. Signed-off-by: AngeloGioacchino Del Regno --- drivers/pinctrl/Kconfig | 17 + drivers/pinctrl/Makefile | 1 + drivers/pinctrl/pinctrl-aw9523.c | 1122 ++++++++++++++++++++++++++++++ 3 files changed, 1140 insertions(+) create mode 100644 drivers/pinctrl/pinctrl-aw9523.c diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig index 6a961d5f8726..50720c779b8c 100644 --- a/drivers/pinctrl/Kconfig +++ b/drivers/pinctrl/Kconfig @@ -127,6 +127,23 @@ config PINCTRL_AMD Requires ACPI/FDT device enumeration code to set up a platform device. +config PINCTRL_AW9523 + bool "Awinic AW9523/AW9523B I2C GPIO expander pinctrl driver" + depends on OF && I2C + select PINMUX + select PINCONF + select GENERIC_PINCONF + select GPIOLIB + select GPIOLIB_IRQCHIP + select REGMAP + help + The Awinic AW9523/AW9523B is a multi-function I2C GPIO + expander with PWM functionality. This driver bundles a + pinctrl driver to select the function muxing and a GPIO + driver to handle GPIO, when the GPIO function is selected. + + Say yes to enable pinctrl and GPIO support for the AW9523(B). + config PINCTRL_BM1880 bool "Bitmain BM1880 Pinctrl driver" depends on OF && (ARCH_BITMAIN || COMPILE_TEST) diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile index 5e63de2ffcf4..693316ca373c 100644 --- a/drivers/pinctrl/Makefile +++ b/drivers/pinctrl/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_PINCTRL_AXP209) += pinctrl-axp209.o obj-$(CONFIG_PINCTRL_AT91) += pinctrl-at91.o obj-$(CONFIG_PINCTRL_AT91PIO4) += pinctrl-at91-pio4.o obj-$(CONFIG_PINCTRL_AMD) += pinctrl-amd.o +obj-$(CONFIG_PINCTRL_AW9523) += pinctrl-aw9523.o obj-$(CONFIG_PINCTRL_BM1880) += pinctrl-bm1880.o obj-$(CONFIG_PINCTRL_DA850_PUPD) += pinctrl-da850-pupd.o obj-$(CONFIG_PINCTRL_DA9062) += pinctrl-da9062.o diff --git a/drivers/pinctrl/pinctrl-aw9523.c b/drivers/pinctrl/pinctrl-aw9523.c new file mode 100644 index 000000000000..bc8e1e9d6876 --- /dev/null +++ b/drivers/pinctrl/pinctrl-aw9523.c @@ -0,0 +1,1122 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Awinic AW9523B i2c pin controller driver + * Copyright (c) 2020, AngeloGioacchino Del Regno + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "pinconf.h" +#include "pinctrl-utils.h" + +#define AW9523_MAX_FUNCS 2 +#define AW9523_NUM_PORTS 2 +#define AW9523_PINS_PER_PORT 8 + +/* + * HW needs at least 20uS for reset and at least 1-2uS to recover from + * reset, but we have to account for eventual board quirks, if any: + * for this reason, keep reset asserted for 50uS and wait for 20uS + * to recover from the reset. + */ +#define AW9523_HW_RESET_US 50 +#define AW9523_HW_RESET_RECOVERY_US 20 + +/* Port 0: P0_0...P0_7 - Port 1: P1_0...P1_7 */ +#define AW9523_PIN_TO_PORT(pin) (pin >> 3) +#define AW9523_REG_IN_STATE(pin) (0x00 + AW9523_PIN_TO_PORT(pin)) +#define AW9523_REG_OUT_STATE(pin) (0x02 + AW9523_PIN_TO_PORT(pin)) +#define AW9523_REG_CONF_STATE(pin) (0x04 + AW9523_PIN_TO_PORT(pin)) +#define AW9523_REG_INTR_DIS(pin) (0x06 + AW9523_PIN_TO_PORT(pin)) +#define AW9523_REG_CHIPID 0x10 +#define AW9523_VAL_EXPECTED_CHIPID 0x23 + +#define AW9523_REG_GCR 0x11 +#define AW9523_GCR_ISEL_MASK GENMASK(0, 1) +#define AW9523_GCR_GPOMD_MASK BIT(4) + +#define AW9523_REG_PORT_MODE(pin) (0x12 + AW9523_PIN_TO_PORT(pin)) +#define AW9523_REG_SOFT_RESET 0x7f +#define AW9523_VAL_RESET 0x00 + +/* + * struct aw9523_irq - Interrupt controller structure + * @lock: mutex locking for the irq bus + * @irqchip: structure holding irqchip params + * @cached_gpio: stores the previous gpio status for bit comparison + */ +struct aw9523_irq { + struct mutex lock; + struct irq_chip *irqchip; + u16 cached_gpio; +}; + +/* + * struct aw9523_pinmux - Pin mux params + * @name: Name of the mux + * @grps: Groups of the mux + * @num_grps: Number of groups (sizeof array grps) + */ +struct aw9523_pinmux { + const char *name; + const char * const *grps; + const u8 num_grps; +}; + +/* + * struct aw9523 - Main driver structure + * @dev: device handle + * @regmap: regmap handle for current device + * @i2c_lock: Mutex lock for i2c operations + * @reset_gpio: Hardware reset (RSTN) signal GPIO + * @vio_vreg: VCC regulator (Optional) + * @pctl: pinctrl handle for current device + * @gpio: structure holding gpiochip params + * @irq: Interrupt controller structure + */ +struct aw9523 { + struct device *dev; + struct regmap *regmap; + struct mutex i2c_lock; + struct gpio_desc *reset_gpio; + struct regulator *vio_vreg; + struct pinctrl_dev *pctl; + struct gpio_chip gpio; + struct aw9523_irq *irq; +}; + +static const struct pinctrl_pin_desc aw9523_pins[] = { + /* Port 0 */ + PINCTRL_PIN(0, "gpio0"), + PINCTRL_PIN(1, "gpio1"), + PINCTRL_PIN(2, "gpio2"), + PINCTRL_PIN(3, "gpio3"), + PINCTRL_PIN(4, "gpio4"), + PINCTRL_PIN(5, "gpio5"), + PINCTRL_PIN(6, "gpio6"), + PINCTRL_PIN(7, "gpio7"), + + /* Port 1 */ + PINCTRL_PIN(8, "gpio8"), + PINCTRL_PIN(9, "gpio9"), + PINCTRL_PIN(10, "gpio10"), + PINCTRL_PIN(11, "gpio11"), + PINCTRL_PIN(12, "gpio12"), + PINCTRL_PIN(13, "gpio13"), + PINCTRL_PIN(14, "gpio14"), + PINCTRL_PIN(15, "gpio15"), +}; + +static int aw9523_pinctrl_get_groups_count(struct pinctrl_dev *pctldev) +{ + return ARRAY_SIZE(aw9523_pins); +} + +static const char *aw9523_pinctrl_get_group_name(struct pinctrl_dev *pctldev, + unsigned int selector) +{ + return aw9523_pins[selector].name; +} + +static int aw9523_pinctrl_get_group_pins(struct pinctrl_dev *pctldev, + unsigned int selector, + const unsigned int **pins, + unsigned int *num_pins) +{ + *pins = &aw9523_pins[selector].number; + *num_pins = 1; + return 0; +} + +static const struct pinctrl_ops aw9523_pinctrl_ops = { + .get_groups_count = aw9523_pinctrl_get_groups_count, + .get_group_pins = aw9523_pinctrl_get_group_pins, + .get_group_name = aw9523_pinctrl_get_group_name, + .dt_node_to_map = pinconf_generic_dt_node_to_map_pin, + .dt_free_map = pinconf_generic_dt_free_map, +}; + +static const char * const gpio_pwm_groups[] = { + "gpio0", "gpio1", "gpio2", "gpio3", "gpio4", "gpio5", + "gpio6", "gpio7", "gpio8", "gpio9", "gpio10", "gpio11", + "gpio12", "gpio13", "gpio14", "gpio15" +}; + +/* Warning: Do NOT reorder this array */ +static const struct aw9523_pinmux aw9523_pmx[] = { + { + .name = "pwm", + .grps = gpio_pwm_groups, + .num_grps = ARRAY_SIZE(gpio_pwm_groups), + }, + { + .name = "gpio", + .grps = gpio_pwm_groups, + .num_grps = ARRAY_SIZE(gpio_pwm_groups), + }, +}; + +static int aw9523_pmx_get_funcs_count(struct pinctrl_dev *pctl) +{ + return ARRAY_SIZE(aw9523_pmx); +} + +static const char *aw9523_pmx_get_fname(struct pinctrl_dev *pctl, + unsigned int sel) +{ + return aw9523_pmx[sel].name; +} + +static int aw9523_pmx_get_groups(struct pinctrl_dev *pctl, unsigned int sel, + const char * const **groups, + unsigned int * const num_groups) +{ + *groups = aw9523_pmx[sel].grps; + *num_groups = aw9523_pmx[sel].num_grps; + return 0; +} + +static int aw9523_pmx_set_mux(struct pinctrl_dev *pctl, unsigned int fsel, + unsigned int grp) +{ + struct aw9523 *awi = pinctrl_dev_get_drvdata(pctl); + int ret, pin = aw9523_pins[grp].number % AW9523_PINS_PER_PORT; + + if (fsel >= ARRAY_SIZE(aw9523_pmx)) + return -EINVAL; + + /* + * This maps directly to the aw9523_pmx array: programming a + * high bit means "gpio" and a low bit means "pwm". + */ + mutex_lock(&awi->i2c_lock); + ret = regmap_update_bits(awi->regmap, AW9523_REG_PORT_MODE(pin), + BIT(pin), (fsel ? BIT(pin) : 0)); + mutex_unlock(&awi->i2c_lock); + return ret; +} + +static const struct pinmux_ops aw9523_pinmux_ops = { + .get_functions_count = aw9523_pmx_get_funcs_count, + .get_function_name = aw9523_pmx_get_fname, + .get_function_groups = aw9523_pmx_get_groups, + .set_mux = aw9523_pmx_set_mux, +}; + +static int aw9523_pcfg_param_to_reg(enum pin_config_param pcp, int pin, u8 *r) +{ + u8 reg; + + switch (pcp) { + case PIN_CONFIG_BIAS_PULL_PIN_DEFAULT: + case PIN_CONFIG_BIAS_PULL_DOWN: + case PIN_CONFIG_BIAS_PULL_UP: + reg = AW9523_REG_IN_STATE(pin); + break; + case PIN_CONFIG_DRIVE_OPEN_DRAIN: + case PIN_CONFIG_DRIVE_PUSH_PULL: + reg = AW9523_REG_GCR; + break; + case PIN_CONFIG_INPUT_ENABLE: + case PIN_CONFIG_OUTPUT_ENABLE: + reg = AW9523_REG_CONF_STATE(pin); + break; + case PIN_CONFIG_OUTPUT: + reg = AW9523_REG_OUT_STATE(pin); + break; + default: + return -ENOTSUPP; + } + *r = reg; + + return 0; +} + +static int aw9523_pconf_get(struct pinctrl_dev *pctldev, unsigned int pin, + unsigned long *config) +{ + struct aw9523 *awi = pinctrl_dev_get_drvdata(pctldev); + enum pin_config_param param = pinconf_to_config_param(*config); + int regbit = pin % AW9523_PINS_PER_PORT; + unsigned int val; + u8 reg; + int rc; + + rc = aw9523_pcfg_param_to_reg(param, pin, ®); + if (rc) + return rc; + + mutex_lock(&awi->i2c_lock); + rc = regmap_read(awi->regmap, reg, &val); + mutex_unlock(&awi->i2c_lock); + if (rc) + return rc; + + switch (param) { + case PIN_CONFIG_BIAS_PULL_UP: + case PIN_CONFIG_INPUT_ENABLE: + case PIN_CONFIG_OUTPUT: + val &= BIT(regbit); + break; + case PIN_CONFIG_BIAS_PULL_DOWN: + case PIN_CONFIG_OUTPUT_ENABLE: + val &= BIT(regbit); + val = !val; + break; + case PIN_CONFIG_DRIVE_OPEN_DRAIN: + if (pin >= AW9523_PINS_PER_PORT) + val = 0; + else + val = !FIELD_GET(AW9523_GCR_GPOMD_MASK, val); + break; + case PIN_CONFIG_DRIVE_PUSH_PULL: + if (pin >= AW9523_PINS_PER_PORT) + val = 1; + else + val = FIELD_GET(AW9523_GCR_GPOMD_MASK, val); + break; + default: + return -ENOTSUPP; + } + if (val < 1) + return -EINVAL; + + *config = pinconf_to_config_packed(param, !!val); + + return rc; +} + +static int aw9523_pconf_set(struct pinctrl_dev *pctldev, unsigned int pin, + unsigned long *configs, unsigned int num_configs) +{ + struct aw9523 *awi = pinctrl_dev_get_drvdata(pctldev); + enum pin_config_param param; + int regbit = pin % AW9523_PINS_PER_PORT; + u32 arg; + u8 reg; + unsigned int mask, val; + int i, rc; + + mutex_lock(&awi->i2c_lock); + for (i = 0; i < num_configs; i++) { + param = pinconf_to_config_param(configs[i]); + arg = pinconf_to_config_argument(configs[i]); + + rc = aw9523_pcfg_param_to_reg(param, pin, ®); + if (rc) + goto end; + + switch (param) { + case PIN_CONFIG_OUTPUT: + /* First, enable pin output */ + rc = regmap_update_bits(awi->regmap, + AW9523_REG_CONF_STATE(pin), + BIT(regbit), 0); + if (rc) + goto end; + + /* Then, fall through to config output level */ + fallthrough; + case PIN_CONFIG_OUTPUT_ENABLE: + arg = !arg; + fallthrough; + case PIN_CONFIG_BIAS_PULL_PIN_DEFAULT: + case PIN_CONFIG_BIAS_PULL_DOWN: + case PIN_CONFIG_BIAS_PULL_UP: + case PIN_CONFIG_INPUT_ENABLE: + mask = BIT(regbit); + val = arg ? BIT(regbit) : 0; + break; + case PIN_CONFIG_DRIVE_OPEN_DRAIN: + /* Open-Drain is supported only on port 0 */ + if (pin >= AW9523_PINS_PER_PORT) { + rc = -ENOTSUPP; + goto end; + } + mask = AW9523_GCR_GPOMD_MASK; + val = 0; + break; + case PIN_CONFIG_DRIVE_PUSH_PULL: + /* Port 1 is always Push-Pull */ + if (pin >= AW9523_PINS_PER_PORT) { + mask = 0; + val = 0; + continue; + } + mask = AW9523_GCR_GPOMD_MASK; + val = AW9523_GCR_GPOMD_MASK; + break; + default: + rc = -ENOTSUPP; + goto end; + } + + rc = regmap_update_bits(awi->regmap, reg, mask, val); + if (rc) + goto end; + } +end: + mutex_unlock(&awi->i2c_lock); + return rc; +} + +static const struct pinconf_ops aw9523_pinconf_ops = { + .pin_config_get = aw9523_pconf_get, + .pin_config_set = aw9523_pconf_set, + .is_generic = true, +}; + +/* + * aw9523_get_pin_direction - Get pin direction + * @regmap: Regmap structure + * @pin: gpiolib pin number + * @n: pin index in port register + * + * Return: Pin direction for success or negative number for error + */ +static int aw9523_get_pin_direction(struct regmap *regmap, u8 pin, u8 n) +{ + int ret; + + ret = regmap_test_bits(regmap, AW9523_REG_CONF_STATE(pin), BIT(n)); + if (ret < 0) + return ret; + + return ret ? GPIO_LINE_DIRECTION_IN : GPIO_LINE_DIRECTION_OUT; +} + +/* + * aw9523_get_port_state - Get input or output state for entire port + * @regmap: Regmap structure + * @pin: gpiolib pin number + * @regbit: hw pin index, used to retrieve port number + * @state: returned port state + * + * Return: Zero for success or negative number for error + */ +static int aw9523_get_port_state(struct regmap *regmap, u8 pin, + u8 regbit, unsigned int *state) +{ + u8 reg; + int dir; + + dir = aw9523_get_pin_direction(regmap, pin, regbit); + if (dir < 0) + return dir; + + if (dir == GPIO_LINE_DIRECTION_IN) + reg = AW9523_REG_IN_STATE(pin); + else + reg = AW9523_REG_OUT_STATE(pin); + + return regmap_read(regmap, reg, state); +} + +static int aw9523_gpio_irq_type(struct irq_data *d, unsigned int type) +{ + switch (type) { + case IRQ_TYPE_NONE: + case IRQ_TYPE_EDGE_BOTH: + return 0; + default: + return -EINVAL; + }; +} + +/* + * aw9523_irq_mask - Mask interrupt + * @d: irq data + * + * Sets which interrupt to mask in the bitmap; + * The interrupt will be masked when unlocking the irq bus. + */ +static void aw9523_irq_mask(struct irq_data *d) +{ + struct aw9523 *awi = gpiochip_get_data(irq_data_get_irq_chip_data(d)); + unsigned int n = d->hwirq % AW9523_PINS_PER_PORT; + + regmap_update_bits(awi->regmap, + AW9523_REG_INTR_DIS(d->hwirq), + BIT(n), BIT(n)); +} + +/* + * aw9523_irq_unmask - Unmask interrupt + * @d: irq data + * + * Sets which interrupt to unmask in the bitmap; + * The interrupt will be masked when unlocking the irq bus. + */ +static void aw9523_irq_unmask(struct irq_data *d) +{ + struct aw9523 *awi = gpiochip_get_data(irq_data_get_irq_chip_data(d)); + unsigned int n = d->hwirq % AW9523_PINS_PER_PORT; + + regmap_update_bits(awi->regmap, + AW9523_REG_INTR_DIS(d->hwirq), + BIT(n), 0); +} + +static irqreturn_t aw9523_irq_thread_func(int irq, void *dev_id) +{ + struct aw9523 *awi = (struct aw9523 *)dev_id; + unsigned long n, val = 0; + unsigned long changed_gpio; + unsigned int tmp, port_pin, i, ret; + + for (i = 0; i < AW9523_NUM_PORTS; i++) { + port_pin = i * AW9523_PINS_PER_PORT; + ret = regmap_read(awi->regmap, + AW9523_REG_IN_STATE(port_pin), + &tmp); + if (ret) + return ret; + val |= (u8)tmp << (i * 8); + } + + /* Handle GPIO input release interrupt as well */ + changed_gpio = awi->irq->cached_gpio ^ val; + awi->irq->cached_gpio = val; + + /* + * To avoid up to four *slow* i2c reads from any driver hooked + * up to our interrupts, just check for the irq_find_mapping + * result: if the interrupt is not mapped, then we don't want + * to care about it. + */ + for_each_set_bit(n, &changed_gpio, awi->gpio.ngpio) { + tmp = irq_find_mapping(awi->gpio.irq.domain, n); + if (tmp <= 0) + continue; + handle_nested_irq(tmp); + } + + return IRQ_HANDLED; +} + +/* + * aw9523_irq_bus_lock - Grab lock for interrupt operation + * @d: irq data + */ +static void aw9523_irq_bus_lock(struct irq_data *d) +{ + struct aw9523 *awi = gpiochip_get_data(irq_data_get_irq_chip_data(d)); + + mutex_lock(&awi->irq->lock); + regcache_cache_only(awi->regmap, true); +} + +/* + * aw9523_irq_bus_sync_unlock - Synchronize state and unlock + * @d: irq data + * + * Writes the interrupt mask bits (found in the bit map) to the + * hardware, then unlocks the bus. + */ +static void aw9523_irq_bus_sync_unlock(struct irq_data *d) +{ + struct aw9523 *awi = gpiochip_get_data(irq_data_get_irq_chip_data(d)); + + regcache_cache_only(awi->regmap, false); + regcache_sync(awi->regmap); + mutex_unlock(&awi->irq->lock); +} + +static int aw9523_gpio_get_direction(struct gpio_chip *chip, + unsigned int offset) +{ + struct aw9523 *awi = gpiochip_get_data(chip); + u8 regbit = offset % AW9523_PINS_PER_PORT; + int ret; + + mutex_lock(&awi->i2c_lock); + ret = aw9523_get_pin_direction(awi->regmap, offset, regbit); + mutex_unlock(&awi->i2c_lock); + + return ret; +} + +static int aw9523_gpio_get(struct gpio_chip *chip, unsigned int offset) +{ + struct aw9523 *awi = gpiochip_get_data(chip); + u8 regbit = offset % AW9523_PINS_PER_PORT; + unsigned int val; + int ret; + + mutex_lock(&awi->i2c_lock); + ret = aw9523_get_port_state(awi->regmap, offset, regbit, &val); + mutex_unlock(&awi->i2c_lock); + if (ret) + return ret; + + return !!(val & BIT(regbit)); +} + +/** + * _aw9523_gpio_get_multiple - Get I/O state for an entire port + * @regmap: Regmap structure + * @pin: gpiolib pin number + * @regbit: hw pin index, used to retrieve port number + * @state: returned port I/O state + * + * Return: Zero for success or negative number for error + */ +static int _aw9523_gpio_get_multiple(struct aw9523 *awi, u8 regbit, + u8 *state, u8 mask) +{ + u32 dir_in, val; + u8 m; + int ret; + + /* Registers are 8-bits wide */ + ret = regmap_read(awi->regmap, AW9523_REG_CONF_STATE(regbit), &dir_in); + if (ret) + return ret; + *state = 0; + + m = mask & dir_in; + if (m) { + ret = regmap_read(awi->regmap, AW9523_REG_IN_STATE(regbit), + &val); + if (ret) + return ret; + *state |= (u8)val & m; + } + + m = mask & ~dir_in; + if (m) { + ret = regmap_read(awi->regmap, AW9523_REG_OUT_STATE(regbit), + &val); + if (ret) + return ret; + *state |= (u8)val & m; + } + + return 0; +} + +static int aw9523_gpio_get_multiple(struct gpio_chip *chip, + unsigned long *mask, + unsigned long *bits) +{ + struct aw9523 *awi = gpiochip_get_data(chip); + u8 m, state = 0; + int ret; + + mutex_lock(&awi->i2c_lock); + + /* Port 0 (gpio 0-7) */ + m = *mask & U8_MAX; + if (m) { + ret = _aw9523_gpio_get_multiple(awi, 0, &state, m); + if (ret) + goto out; + } + *bits = state; + + /* Port 1 (gpio 8-15) */ + m = (*mask >> 8) & U8_MAX; + if (m) { + ret = _aw9523_gpio_get_multiple(awi, AW9523_PINS_PER_PORT, + &state, m); + if (ret) + goto out; + + *bits |= (state << 8); + } +out: + mutex_unlock(&awi->i2c_lock); + return ret; +} + +static void aw9523_gpio_set_multiple(struct gpio_chip *chip, + unsigned long *mask, + unsigned long *bits) +{ + struct aw9523 *awi = gpiochip_get_data(chip); + u8 mask_lo, mask_hi, bits_lo, bits_hi; + unsigned int reg; + int ret = 0; + + mask_lo = *mask & U8_MAX; + mask_hi = (*mask >> 8) & U8_MAX; + mutex_lock(&awi->i2c_lock); + if (mask_hi) { + reg = AW9523_REG_OUT_STATE(AW9523_PINS_PER_PORT); + bits_hi = (*bits >> 8) & U8_MAX; + + ret = regmap_write_bits(awi->regmap, reg, mask_hi, bits_hi); + if (ret) { + dev_warn(awi->dev, "Cannot write port1 out level\n"); + goto out; + } + } + if (mask_lo) { + reg = AW9523_REG_OUT_STATE(0); + bits_lo = *bits & U8_MAX; + ret = regmap_write_bits(awi->regmap, reg, mask_lo, bits_lo); + if (ret) + dev_warn(awi->dev, "Cannot write port0 out level\n"); + } +out: + mutex_unlock(&awi->i2c_lock); +} + +static void aw9523_gpio_set(struct gpio_chip *chip, + unsigned int offset, int value) +{ + struct aw9523 *awi = gpiochip_get_data(chip); + u8 regbit = offset % AW9523_PINS_PER_PORT; + + mutex_lock(&awi->i2c_lock); + regmap_update_bits(awi->regmap, AW9523_REG_OUT_STATE(offset), + BIT(regbit), value ? BIT(regbit) : 0); + mutex_unlock(&awi->i2c_lock); +} + + +static int aw9523_direction_input(struct gpio_chip *chip, unsigned int offset) +{ + struct aw9523 *awi = gpiochip_get_data(chip); + u8 regbit = offset % AW9523_PINS_PER_PORT; + int ret; + + mutex_lock(&awi->i2c_lock); + ret = regmap_update_bits(awi->regmap, AW9523_REG_CONF_STATE(offset), + BIT(regbit), BIT(regbit)); + mutex_unlock(&awi->i2c_lock); + + return ret; +} + +static int aw9523_direction_output(struct gpio_chip *chip, + unsigned int offset, int value) +{ + struct aw9523 *awi = gpiochip_get_data(chip); + u8 regbit = offset % AW9523_PINS_PER_PORT; + int ret; + + mutex_lock(&awi->i2c_lock); + ret = regmap_update_bits(awi->regmap, AW9523_REG_OUT_STATE(offset), + BIT(regbit), value ? BIT(regbit) : 0); + if (ret) + goto end; + + ret = regmap_update_bits(awi->regmap, AW9523_REG_CONF_STATE(offset), + BIT(regbit), 0); +end: + mutex_unlock(&awi->i2c_lock); + return ret; +} + +static int aw9523_drive_reset_gpio(struct aw9523 *awi) +{ + unsigned int chip_id; + int ret; + + /* + * If the chip is already configured for any reason, then we + * will probably succeed in sending the soft reset signal to + * the hardware through I2C: this operation takes less time + * compared to a full HW reset and it gives the same results. + */ + ret = regmap_write(awi->regmap, AW9523_REG_SOFT_RESET, 0); + if (ret == 0) + goto done; + + dev_dbg(awi->dev, "Cannot execute soft reset: trying hard reset\n"); + ret = gpiod_direction_output(awi->reset_gpio, 0); + if (ret) + return ret; + + /* The reset pulse has to be longer than 20uS due to deglitch */ + usleep_range(AW9523_HW_RESET_US, AW9523_HW_RESET_US + 1); + + ret = gpiod_direction_output(awi->reset_gpio, 1); + if (ret) + return ret; +done: + /* The HW needs at least 1uS to reliably recover after reset */ + usleep_range(AW9523_HW_RESET_RECOVERY_US, + AW9523_HW_RESET_RECOVERY_US + 1); + + /* Check the ChipID */ + ret = regmap_read(awi->regmap, AW9523_REG_CHIPID, &chip_id); + if (ret) { + dev_err(awi->dev, "Cannot read Chip ID: %d\n", ret); + return ret; + } + if (chip_id != AW9523_VAL_EXPECTED_CHIPID) { + dev_err(awi->dev, "Bad ChipID; read 0x%x, expected 0x%x\n", + chip_id, AW9523_VAL_EXPECTED_CHIPID); + return -EINVAL; + } + + return 0; +} + +static int aw9523_hw_reset(struct aw9523 *awi) +{ + int ret, max_retries = 2; + + /* Sometimes the chip needs more than one reset cycle */ + do { + ret = aw9523_drive_reset_gpio(awi); + if (ret == 0) + break; + max_retries--; + } while (max_retries); + + return ret; +} + +static int aw9523_init_gpiochip(struct aw9523 *awi, unsigned int npins) +{ + struct device *dev = awi->dev; + struct gpio_chip *gpiochip = &awi->gpio; + + gpiochip->label = devm_kstrdup(dev, dev_name(dev), GFP_KERNEL); + if (!gpiochip->label) + return -ENOMEM; + + gpiochip->base = -1; + gpiochip->ngpio = npins; + gpiochip->get_direction = aw9523_gpio_get_direction; + gpiochip->direction_input = aw9523_direction_input; + gpiochip->direction_output = aw9523_direction_output; + gpiochip->get = aw9523_gpio_get; + gpiochip->get_multiple = aw9523_gpio_get_multiple; + gpiochip->set = aw9523_gpio_set; + gpiochip->set_multiple = aw9523_gpio_set_multiple; + gpiochip->set_config = gpiochip_generic_config; + gpiochip->parent = dev; + gpiochip->of_node = dev->of_node; + gpiochip->owner = THIS_MODULE; + gpiochip->can_sleep = false; + + return 0; +} + +static int aw9523_init_irq(struct aw9523 *awi, int irq) +{ + struct device *dev = awi->dev; + struct gpio_irq_chip *gpioirq; + struct irq_chip *irqchip; + int ret; + + if (!device_property_read_bool(dev, "interrupt-controller")) + return 0; + + irqchip = devm_kzalloc(dev, sizeof(*irqchip), GFP_KERNEL); + if (!irqchip) + return -ENOMEM; + + awi->irq = devm_kzalloc(dev, sizeof(*awi->irq), GFP_KERNEL); + if (!awi->irq) + return -ENOMEM; + + irqchip->name = devm_kstrdup(dev, dev_name(dev), GFP_KERNEL); + if (!irqchip->name) + return -ENOMEM; + + irqchip->irq_mask = aw9523_irq_mask; + irqchip->irq_unmask = aw9523_irq_unmask; + irqchip->irq_bus_lock = aw9523_irq_bus_lock; + irqchip->irq_bus_sync_unlock = aw9523_irq_bus_sync_unlock; + irqchip->irq_set_type = aw9523_gpio_irq_type; + awi->irq->irqchip = irqchip; + mutex_init(&awi->irq->lock); + + ret = devm_request_threaded_irq(dev, irq, NULL, aw9523_irq_thread_func, + IRQF_ONESHOT, dev_name(dev), awi); + if (ret) { + dev_err(dev, "Failed to request irq %d\n", irq); + return ret; + } + + gpioirq = &awi->gpio.irq; + gpioirq->chip = irqchip; + gpioirq->parent_handler = NULL; + gpioirq->num_parents = 0; + gpioirq->parents = NULL; + gpioirq->default_type = IRQ_TYPE_LEVEL_MASK; + gpioirq->handler = handle_simple_irq; + gpioirq->threaded = true; + gpioirq->first = 0; + + return 0; +} + +static bool aw9523_is_reg_hole(unsigned int reg) +{ + return (reg > AW9523_REG_PORT_MODE(AW9523_PINS_PER_PORT) && + reg < AW9523_REG_SOFT_RESET) || + (reg > AW9523_REG_INTR_DIS(AW9523_PINS_PER_PORT) && + reg < AW9523_REG_CHIPID); +} + +static bool aw9523_readable_reg(struct device *dev, unsigned int reg) +{ + /* All available registers (minus holes) can be read */ + return !aw9523_is_reg_hole(reg); +} + +static bool aw9523_volatile_reg(struct device *dev, unsigned int reg) +{ + return aw9523_is_reg_hole(reg) || + reg == AW9523_REG_IN_STATE(0) || + reg == AW9523_REG_IN_STATE(AW9523_PINS_PER_PORT) || + reg == AW9523_REG_CHIPID || + reg == AW9523_REG_SOFT_RESET; +} + +static bool aw9523_writeable_reg(struct device *dev, unsigned int reg) +{ + return !aw9523_is_reg_hole(reg) && reg != AW9523_REG_CHIPID; +} + +static bool aw9523_precious_reg(struct device *dev, unsigned int reg) +{ + /* Reading AW9523_REG_IN_STATE clears interrupt status */ + return aw9523_is_reg_hole(reg) || + reg == AW9523_REG_IN_STATE(0) || + reg == AW9523_REG_IN_STATE(AW9523_PINS_PER_PORT); +} + +static const struct regmap_config aw9523_regmap = { + .reg_bits = 8, + .val_bits = 8, + .reg_stride = 1, + + .precious_reg = aw9523_precious_reg, + .readable_reg = aw9523_readable_reg, + .volatile_reg = aw9523_volatile_reg, + .writeable_reg = aw9523_writeable_reg, + + .cache_type = REGCACHE_FLAT, + .disable_locking = true, + + .num_reg_defaults_raw = AW9523_REG_SOFT_RESET, +}; + +static int aw9523_hw_init(struct aw9523 *awi) +{ + u8 p1_pin = AW9523_PINS_PER_PORT; + unsigned int val; + int ret; + + /* No register caching during initialization */ + regcache_cache_bypass(awi->regmap, true); + + /* Bring up the chip */ + ret = aw9523_hw_reset(awi); + if (ret) { + dev_err(awi->dev, "HW Reset failed: %d\n", ret); + return ret; + } + + /* + * This is the expected chip and it is running: it's time to + * set a safe default configuration in case the user doesn't + * configure (all of the available) pins in this chip. + * P.S.: The writes order doesn't matter. + */ + + /* Set all pins as GPIO */ + ret = regmap_write(awi->regmap, AW9523_REG_PORT_MODE(0), U8_MAX); + if (ret) + return ret; + ret = regmap_write(awi->regmap, AW9523_REG_PORT_MODE(p1_pin), U8_MAX); + if (ret) + return ret; + + /* Set Open-Drain mode on Port 0 (Port 1 is always P-P) */ + ret = regmap_write(awi->regmap, AW9523_REG_GCR, 0); + if (ret) + return ret; + + /* Set all pins as inputs */ + ret = regmap_write(awi->regmap, AW9523_REG_CONF_STATE(0), U8_MAX); + if (ret) + return ret; + ret = regmap_write(awi->regmap, AW9523_REG_CONF_STATE(p1_pin), U8_MAX); + if (ret) + return ret; + + /* Disable all interrupts to avoid unreasoned wakeups */ + ret = regmap_write(awi->regmap, AW9523_REG_INTR_DIS(0), U8_MAX); + if (ret) + return ret; + ret = regmap_write(awi->regmap, AW9523_REG_INTR_DIS(p1_pin), U8_MAX); + if (ret) + return ret; + + /* Clear setup-generated interrupts by performing a port state read */ + ret = aw9523_get_port_state(awi->regmap, 0, 0, &val); + if (ret) + return ret; + ret = aw9523_get_port_state(awi->regmap, p1_pin, 0, &val); + if (ret) + return ret; + + /* Everything went fine: activate and reinitialize register cache */ + regcache_cache_bypass(awi->regmap, false); + return regmap_reinit_cache(awi->regmap, &aw9523_regmap); +} + +static int aw9523_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct pinctrl_desc *pdesc; + struct aw9523 *awi; + int ret; + + awi = devm_kzalloc(dev, sizeof(*awi), GFP_KERNEL); + if (!awi) + return -ENOMEM; + + i2c_set_clientdata(client, awi); + + awi->dev = dev; + awi->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(awi->reset_gpio)) + return PTR_ERR(awi->reset_gpio); + gpiod_set_consumer_name(awi->reset_gpio, "aw9523 reset"); + + awi->regmap = devm_regmap_init_i2c(client, &aw9523_regmap); + if (IS_ERR(awi->regmap)) + return PTR_ERR(awi->regmap); + + awi->vio_vreg = devm_regulator_get_optional(dev, "vio"); + if (IS_ERR(awi->vio_vreg)) { + if (PTR_ERR(awi->vio_vreg) == -EPROBE_DEFER) + return -EPROBE_DEFER; + awi->vio_vreg = NULL; + } else { + ret = regulator_enable(awi->vio_vreg); + if (ret) + return ret; + } + + mutex_init(&awi->i2c_lock); + lockdep_set_subclass(&awi->i2c_lock, + i2c_adapter_depth(client->adapter)); + + pdesc = devm_kzalloc(dev, sizeof(*pdesc), GFP_KERNEL); + if (!pdesc) + return -ENOMEM; + + ret = aw9523_hw_init(awi); + if (ret) + goto err_disable_vregs; + + pdesc->name = dev_name(dev); + pdesc->owner = THIS_MODULE; + pdesc->pctlops = &aw9523_pinctrl_ops; + pdesc->pmxops = &aw9523_pinmux_ops; + pdesc->confops = &aw9523_pinconf_ops; + pdesc->pins = aw9523_pins; + pdesc->npins = ARRAY_SIZE(aw9523_pins); + + ret = aw9523_init_gpiochip(awi, pdesc->npins); + if (ret) + goto err_disable_vregs; + + if (client->irq) { + ret = aw9523_init_irq(awi, client->irq); + if (ret) + goto err_disable_vregs; + } + + awi->pctl = devm_pinctrl_register(dev, pdesc, awi); + if (IS_ERR(awi->pctl)) { + ret = PTR_ERR(awi->pctl); + dev_err(dev, "Cannot register pinctrl: %d", ret); + goto err_disable_vregs; + } + + ret = devm_gpiochip_add_data(dev, &awi->gpio, awi); + if (ret) + goto err_disable_vregs; + + return ret; + +err_disable_vregs: + if (awi->vio_vreg) + regulator_disable(awi->vio_vreg); + mutex_destroy(&awi->i2c_lock); + return ret; +} + +static int aw9523_remove(struct i2c_client *client) +{ + struct aw9523 *awi = i2c_get_clientdata(client); + int ret; + + if (!awi) + return 0; + + /* + * If the chip VIO is connected to a regulator that we can turn + * off, life is easy... otherwise, reinitialize the chip and + * set the pins to hardware defaults before removing the driver + * to leave it in a clean, safe and predictable state. + */ + if (awi->vio_vreg) { + regulator_disable(awi->vio_vreg); + } else { + mutex_lock(&awi->i2c_lock); + ret = aw9523_hw_init(awi); + mutex_unlock(&awi->i2c_lock); + if (ret) + return ret; + } + + mutex_destroy(&awi->i2c_lock); + return 0; +} + +static const struct i2c_device_id aw9523_i2c_id_table[] = { + { "aw9523_i2c", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, aw9523_i2c_id_table); + +static const struct of_device_id of_aw9523_i2c_match[] = { + { .compatible = "awinic,aw9523-pinctrl", }, +}; +MODULE_DEVICE_TABLE(of, of_aw9523_i2c_match); + +static struct i2c_driver aw9523_driver = { + .driver = { + .name = "aw9523-pinctrl", + .of_match_table = of_aw9523_i2c_match, + }, + .probe = aw9523_probe, + .remove = aw9523_remove, + .id_table = aw9523_i2c_id_table, +}; +module_i2c_driver(aw9523_driver); + +MODULE_DESCRIPTION("Awinic AW9523 I2C GPIO Expander driver"); +MODULE_AUTHOR("AngeloGioacchino Del Regno "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:aw9523"); From 5ccc15fc562441496421fef81cb755f224b3b0b5 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Wed, 23 Dec 2020 14:54:10 +0100 Subject: [PATCH 089/187] dt-bindings: pinctrl: Add bindings for Awinic AW9523/AW9523B Add bindings for the Awinic AW9523/AW9523B I2C GPIO Expander driver. Signed-off-by: AngeloGioacchino Del Regno --- .../pinctrl/awinic,aw9523-pinctrl.yaml | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 Documentation/devicetree/bindings/pinctrl/awinic,aw9523-pinctrl.yaml diff --git a/Documentation/devicetree/bindings/pinctrl/awinic,aw9523-pinctrl.yaml b/Documentation/devicetree/bindings/pinctrl/awinic,aw9523-pinctrl.yaml new file mode 100644 index 000000000000..640d4d7e4cab --- /dev/null +++ b/Documentation/devicetree/bindings/pinctrl/awinic,aw9523-pinctrl.yaml @@ -0,0 +1,139 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/pinctrl/awinic,aw9523-pinctrl.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Awinic AW9523/AW9523B I2C GPIO Expander + +maintainers: + - AngeloGioacchino Del Regno + +description: | + The Awinic AW9523/AW9523B I2C GPIO Expander featuring 16 multi-function + I/O, 256 steps PWM mode and interrupt support. + +properties: + compatible: + const: awinic,aw9523-pinctrl + + reg: + maxItems: 1 + + '#gpio-cells': + description: | + Specifying the pin number and flags, as defined in + include/dt-bindings/gpio/gpio.h + const: 2 + + gpio-controller: true + + gpio-ranges: + maxItems: 1 + + interrupt-controller: true + + interrupts: + maxItems: 1 + description: Specifies the INTN pin IRQ. + + '#interrupt-cells': + description: + Specifies the PIN numbers and Flags, as defined in defined in + include/dt-bindings/interrupt-controller/irq.h + const: 2 + + reset-gpios: + maxItems: 1 + +#PIN CONFIGURATION NODES +patternProperties: + '-pins$': + type: object + description: + Pinctrl node's client devices use subnodes for desired pin configuration. + Client device subnodes use below standard properties. + $ref: "/schemas/pinctrl/pincfg-node.yaml" + + properties: + pins: + description: + List of gpio pins affected by the properties specified in + this subnode. + items: + pattern: "^gpio([0-9]|1[0-5])$" + minItems: 1 + maxItems: 16 + + function: + description: + Specify the alternative function to be configured for the + specified pins. + + enum: [ gpio, pwm ] + + bias-disable: true + bias-pull-down: true + bias-pull-up: true + drive-open-drain: true + drive-push-pull: true + input-enable: true + input-disable: true + output-high: true + output-low: true + + required: + - pins + - function + + additionalProperties: false + +required: + - compatible + - reg + - gpio-controller + - '#gpio-cells' + - gpio-ranges + +additionalProperties: false + +examples: + # Example configuration to drive pins for a keyboard matrix + - | + #include + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + aw9523: gpio-expander@58 { + compatible = "awinic,aw9523-pinctrl"; + reg = <0x58>; + interrupt-parent = <&tlmm>; + interrupts = <50 IRQ_TYPE_EDGE_FALLING>; + gpio-controller; + #gpio-cells = <2>; + gpio-ranges = <&tlmm 0 0 16>; + interrupt-controller; + #interrupt-cells = <2>; + reset-gpios = <&tlmm 51 GPIO_ACTIVE_HIGH>; + + keyboard-matrix-col-pins { + pins = "gpio8", "gpio9", "gpio10", "gpio11", + "gpio12", "gpio13", "gpio14", "gpio15"; + function = "gpio"; + input-disable; + output-low; + }; + + keyboard-matrix-row-pins { + pins = "gpio0", "gpio1", "gpio2", "gpio3", + "gpio4", "gpio5", "gpio6", "gpio7"; + function = "gpio"; + bias-pull-up; + drive-open-drain; + input-enable; + }; + }; + }; From 36e759dcab3703a536ce1da9673ef046a8994c68 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Thu, 6 Aug 2020 20:09:51 +0200 Subject: [PATCH 090/187] media: venus: core: Add sdm660 DT compatible and resource struct Add the SDM660 DT compatible and its resource structure, also including support for the Venus pmdomains, in order to support the Venus block in SDM630, SDM636, SDM660 and SDA variants. This SoC features Venus 4.4 (HFI3XX), with one vcodec used for both encoding and decoding, switched on through two GDSCs. The core clock for this Venus chip is powered by the RPM VDD_CX power domain. Signed-off-by: AngeloGioacchino Del Regno --- drivers/media/platform/qcom/venus/core.c | 65 ++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/drivers/media/platform/qcom/venus/core.c b/drivers/media/platform/qcom/venus/core.c index f5fa81896012..f2cce79281bf 100644 --- a/drivers/media/platform/qcom/venus/core.c +++ b/drivers/media/platform/qcom/venus/core.c @@ -704,6 +704,71 @@ static const struct venus_resources sdm845_res_v2 = { .fwname = "qcom/venus-5.2/venus.mdt", }; +static const struct freq_tbl sdm660_freq_table[] = { + { 0, 518400000 }, + { 0, 441600000 }, + { 0, 404000000 }, + { 0, 320000000 }, + { 0, 269330000 }, + { 0, 133330000 }, +}; + +static const struct reg_val sdm660_reg_preset[] = { + { 0x80010, 0x001f001f }, + { 0x80018, 0x00000156 }, + { 0x8001C, 0x00000156 }, +}; + +static const struct bw_tbl sdm660_bw_table_enc[] = { + { 979200, 1044000, 0, 2446336, 0 }, /* 4k UHD @ 30 */ + { 864000, 887000, 0, 2108416, 0 }, /* 720p @ 240 */ + { 489600, 666000, 0, 1207296, 0 }, /* 1080p @ 60 */ + { 432000, 578000, 0, 1058816, 0 }, /* 720p @ 120 */ + { 244800, 346000, 0, 616448, 0 }, /* 1080p @ 30 */ + { 216000, 293000, 0, 534528, 0 }, /* 720p @ 60 */ + { 108000, 151000, 0, 271360, 0 }, /* 720p @ 30 */ +}; + +static const struct bw_tbl sdm660_bw_table_dec[] = { + { 979200, 2365000, 0, 1892000, 0 }, /* 4k UHD @ 30 */ + { 864000, 1978000, 0, 1554000, 0 }, /* 720p @ 240 */ + { 489600, 1133000, 0, 895000, 0 }, /* 1080p @ 60 */ + { 432000, 994000, 0, 781000, 0 }, /* 720p @ 120 */ + { 244800, 580000, 0, 460000, 0 }, /* 1080p @ 30 */ + { 216000, 501000, 0, 301000, 0 }, /* 720p @ 60 */ + { 108000, 255000, 0, 202000, 0 }, /* 720p @ 30 */ +}; + +static const struct venus_resources sdm660_res = { + .freq_tbl = sdm660_freq_table, + .freq_tbl_size = ARRAY_SIZE(sdm660_freq_table), + .reg_tbl = sdm660_reg_preset, + .reg_tbl_size = ARRAY_SIZE(sdm660_reg_preset), + .bw_tbl_enc = sdm660_bw_table_enc, + .bw_tbl_enc_size = ARRAY_SIZE(sdm660_bw_table_enc), + .bw_tbl_dec = sdm660_bw_table_dec, + .bw_tbl_dec_size = ARRAY_SIZE(sdm660_bw_table_dec), + .clks = {"core", "iface", "bus_throttle", "bus" }, + .clks_num = 4, + .vcodec0_clks = { "vcodec0_core" }, + .vcodec_clks_num = 1, + .vcodec_pmdomains = { "venus", "vcodec0" }, + .vcodec_pmdomains_num = 2, + .opp_pmdomain = (const char *[]) { "cx", NULL }, + .vcodec_num = 1, + .max_load = 1036800, + .hfi_version = HFI_VERSION_3XX, + .vmem_id = VIDC_RESOURCE_NONE, + .vmem_size = 0, + .vmem_addr = 0, + .cp_start = 0, + .cp_size = 0x79000000, + .cp_nonpixel_start = 0x1000000, + .cp_nonpixel_size = 0x28000000, + .dma_mask = 0xd9000000 - 1, + .fwname = "qcom/venus-4.4/venus.mdt", +}; + static const struct freq_tbl sc7180_freq_table[] = { { 0, 500000000 }, { 0, 434000000 }, From 2a3eeaa730651b62624e347d812aec304ecfea40 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Wed, 23 Dec 2020 13:02:27 +0100 Subject: [PATCH 091/187] drm/panel: Add BOE BF060Y8M-AJ0 5.99" AMOLED panel driver This adds support for the BOE BF060Y8M-AJ0 5.99" AMOLED module that can be found in some F(x)Tec Pro1 and Elephone U1 devices. Signed-off-by: AngeloGioacchino Del Regno --- drivers/gpu/drm/panel/Kconfig | 11 + drivers/gpu/drm/panel/Makefile | 1 + .../gpu/drm/panel/panel-boe-bf060y8m-aj0.c | 445 ++++++++++++++++++ 3 files changed, 457 insertions(+) create mode 100644 drivers/gpu/drm/panel/panel-boe-bf060y8m-aj0.c diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig index be072b396443..2c4110468246 100644 --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig @@ -37,6 +37,17 @@ config DRM_PANEL_ASUS_Z00T_TM5P5_NT35596 NT35596 1080x1920 video mode panel as found in some Asus Zenfone 2 Laser Z00T devices. +config DRM_PANEL_BOE_BF060Y8M_AJ0 + tristate "Boe BF060Y8M-AJ0 panel" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y here if you want to enable support for Boe BF060Y8M-AJ0 + 5.99" AMOLED modules. The panel has a 1080x2160 resolution and + uses 24 bit RGB per pixel. It provides a MIPI DSI interface to + the host and backlight is controlled through DSI commands. + config DRM_PANEL_BOE_HIMAX8279D tristate "Boe Himax8279d panel" depends on OF diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile index 0d6e2be47647..5f8176d76442 100644 --- a/drivers/gpu/drm/panel/Makefile +++ b/drivers/gpu/drm/panel/Makefile @@ -2,6 +2,7 @@ obj-$(CONFIG_DRM_PANEL_ABT_Y030XX067A) += panel-abt-y030xx067a.o obj-$(CONFIG_DRM_PANEL_ARM_VERSATILE) += panel-arm-versatile.o obj-$(CONFIG_DRM_PANEL_ASUS_Z00T_TM5P5_NT35596) += panel-asus-z00t-tm5p5-n35596.o +obj-$(CONFIG_DRM_PANEL_BOE_BF060Y8M_AJ0) += panel-boe-bf060y8m-aj0.o obj-$(CONFIG_DRM_PANEL_BOE_HIMAX8279D) += panel-boe-himax8279d.o obj-$(CONFIG_DRM_PANEL_BOE_TV101WUM_NL6) += panel-boe-tv101wum-nl6.o obj-$(CONFIG_DRM_PANEL_DSI_CM) += panel-dsi-cm.o diff --git a/drivers/gpu/drm/panel/panel-boe-bf060y8m-aj0.c b/drivers/gpu/drm/panel/panel-boe-bf060y8m-aj0.c new file mode 100644 index 000000000000..cb4fbd21c69e --- /dev/null +++ b/drivers/gpu/drm/panel/panel-boe-bf060y8m-aj0.c @@ -0,0 +1,445 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * BOE BF060Y8M-AJ0 5.99" MIPI-DSI OLED Panel on SW43404 DriverIC + * + * Copyright (c) 2020 AngeloGioacchino Del Regno + * + */ + +#include +#include +#include +#include +#include +#include +#include