* Re: [PATCH v6 3/6] interconnect: icc-clk: Add devm_icc_clk_register
From: Dmitry Baryshkov @ 2024-04-02 10:40 UTC (permalink / raw)
To: Varadarajan Narayanan
Cc: andersson, konrad.dybcio, mturquette, sboyd, robh,
krzysztof.kozlowski+dt, conor+dt, djakov, quic_anusha,
linux-arm-msm, linux-clk, devicetree, linux-kernel, linux-pm
In-Reply-To: <20240402103406.3638821-4-quic_varada@quicinc.com>
On Tue, 2 Apr 2024 at 13:34, Varadarajan Narayanan
<quic_varada@quicinc.com> wrote:
>
> Wrap icc_clk_register to create devm_icc_clk_register to be
> able to release the resources properly.
>
> Signed-off-by: Varadarajan Narayanan <quic_varada@quicinc.com>
> ---
> v5: Introduced devm_icc_clk_register
> ---
> drivers/interconnect/icc-clk.c | 29 +++++++++++++++++++++++++++++
> include/linux/interconnect-clk.h | 4 ++++
> 2 files changed, 33 insertions(+)
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
--
With best wishes
Dmitry
^ permalink raw reply
* Re: [PATCH v6 3/6] interconnect: icc-clk: Add devm_icc_clk_register
From: Dmitry Baryshkov @ 2024-04-02 10:48 UTC (permalink / raw)
To: Varadarajan Narayanan
Cc: andersson, konrad.dybcio, mturquette, sboyd, robh,
krzysztof.kozlowski+dt, conor+dt, djakov, quic_anusha,
linux-arm-msm, linux-clk, devicetree, linux-kernel, linux-pm
In-Reply-To: <CAA8EJpphk_kqzBE7cKb73ipdpTi29t9ZSOOdSfq7pAGSs5NKeg@mail.gmail.com>
On Tue, 2 Apr 2024 at 13:40, Dmitry Baryshkov
<dmitry.baryshkov@linaro.org> wrote:
>
> On Tue, 2 Apr 2024 at 13:34, Varadarajan Narayanan
> <quic_varada@quicinc.com> wrote:
> >
> > Wrap icc_clk_register to create devm_icc_clk_register to be
> > able to release the resources properly.
> >
> > Signed-off-by: Varadarajan Narayanan <quic_varada@quicinc.com>
> > ---
> > v5: Introduced devm_icc_clk_register
> > ---
> > drivers/interconnect/icc-clk.c | 29 +++++++++++++++++++++++++++++
> > include/linux/interconnect-clk.h | 4 ++++
> > 2 files changed, 33 insertions(+)
>
> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
Wait. Actually,
Unreviewed-by: me
Please return int from devm_icc_clk_register instead of returning the pointer.
--
With best wishes
Dmitry
^ permalink raw reply
* Re: [PATCH v6 4/6] clk: qcom: common: Add interconnect clocks support
From: Dmitry Baryshkov @ 2024-04-02 10:48 UTC (permalink / raw)
To: Varadarajan Narayanan
Cc: andersson, konrad.dybcio, mturquette, sboyd, robh,
krzysztof.kozlowski+dt, conor+dt, djakov, quic_anusha,
linux-arm-msm, linux-clk, devicetree, linux-kernel, linux-pm
In-Reply-To: <20240402103406.3638821-5-quic_varada@quicinc.com>
On Tue, 2 Apr 2024 at 13:34, Varadarajan Narayanan
<quic_varada@quicinc.com> wrote:
>
> Unlike MSM platforms that manage NoC related clocks and scaling
> from RPM, IPQ SoCs dont involve RPM in managing NoC related
> clocks and there is no NoC scaling.
>
> However, there is a requirement to enable some NoC interface
> clocks for accessing the peripheral controllers present on
> these NoCs. Though exposing these as normal clocks would work,
> having a minimalistic interconnect driver to handle these clocks
> would make it consistent with other Qualcomm platforms resulting
> in common code paths. This is similar to msm8996-cbf's usage of
> icc-clk framework.
>
> Signed-off-by: Varadarajan Narayanan <quic_varada@quicinc.com>
> ---
> v6: first_id -> icc_first_node_id
> Remove clock get so that the peripheral that uses the clock
> can do the clock get
> v5: Split changes in common.c to separate patch
> Fix error handling
> Use devm_icc_clk_register instead of icc_clk_register
> v4: Use clk_hw instead of indices
> Do icc register in qcom_cc_probe() call stream
> Add icc clock info to qcom_cc_desc structure
> v3: Use indexed identifiers here to avoid confusion
> Fix error messages and move to common.c
> v2: Move DTS to separate patch
> Update commit log
> Auto select CONFIG_INTERCONNECT & CONFIG_INTERCONNECT_CLK to fix build error
> ---
> drivers/clk/qcom/common.c | 38 +++++++++++++++++++++++++++++++++++++-
> drivers/clk/qcom/common.h | 3 +++
> 2 files changed, 40 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/clk/qcom/common.c b/drivers/clk/qcom/common.c
> index 75f09e6e057e..d5c008048994 100644
> --- a/drivers/clk/qcom/common.c
> +++ b/drivers/clk/qcom/common.c
> @@ -8,6 +8,7 @@
> #include <linux/regmap.h>
> #include <linux/platform_device.h>
> #include <linux/clk-provider.h>
> +#include <linux/interconnect-clk.h>
> #include <linux/reset-controller.h>
> #include <linux/of.h>
>
> @@ -234,6 +235,41 @@ static struct clk_hw *qcom_cc_clk_hw_get(struct of_phandle_args *clkspec,
> return cc->rclks[idx] ? &cc->rclks[idx]->hw : NULL;
> }
>
> +static int qcom_cc_icc_register(struct device *dev,
> + const struct qcom_cc_desc *desc)
> +{
> + struct icc_clk_data *icd;
> + int i;
> +
> + if (!IS_ENABLED(CONFIG_INTERCONNECT_CLK))
> + return 0;
> +
> + if (!desc->icc_hws)
> + return 0;
> +
> + icd = devm_kcalloc(dev, desc->num_icc_hws, sizeof(*icd), GFP_KERNEL);
> + if (!icd)
> + return -ENOMEM;
> +
> + for (i = 0; i < desc->num_icc_hws; i++) {
> + /*
> + * get_clk will be done by the peripheral device using this
> + * clock with devm_clk_hw_get_clk() so that we can associate
> + * the clk handle with the consumer device. It would also help
> + * us make it so that drivers defer probe until their
> + * clk isn't an orphan.
How the clock instance returned to the peripheral driver is supposed
to correspond to the clock instance used by the icc-clk?
> + */
> + icd[i].clk = desc->icc_hws[i]->clk;
You again are abusing clk_hw->clk. Please don't do that.
> + if (!icd[i].clk)
> + return dev_err_probe(dev, -ENOENT,
> + "(%d) clock entry is null\n", i);
> + icd[i].name = clk_hw_get_name(desc->icc_hws[i]);
> + }
> +
> + return PTR_ERR_OR_ZERO(devm_icc_clk_register(dev, desc->icc_first_node_id,
> + desc->num_icc_hws, icd));
> +}
> +
> int qcom_cc_really_probe(struct platform_device *pdev,
> const struct qcom_cc_desc *desc, struct regmap *regmap)
> {
> @@ -303,7 +339,7 @@ int qcom_cc_really_probe(struct platform_device *pdev,
> if (ret)
> return ret;
>
> - return 0;
> + return qcom_cc_icc_register(dev, desc);
> }
> EXPORT_SYMBOL_GPL(qcom_cc_really_probe);
>
> diff --git a/drivers/clk/qcom/common.h b/drivers/clk/qcom/common.h
> index 9c8f7b798d9f..9058ffd46260 100644
> --- a/drivers/clk/qcom/common.h
> +++ b/drivers/clk/qcom/common.h
> @@ -29,6 +29,9 @@ struct qcom_cc_desc {
> size_t num_gdscs;
> struct clk_hw **clk_hws;
> size_t num_clk_hws;
> + struct clk_hw **icc_hws;
> + size_t num_icc_hws;
> + unsigned int icc_first_node_id;
> };
>
> /**
> --
> 2.34.1
>
--
With best wishes
Dmitry
^ permalink raw reply
* Re: [PATCH v6 6/6] arm64: dts: qcom: ipq9574: Add icc provider ability to gcc
From: Dmitry Baryshkov @ 2024-04-02 10:48 UTC (permalink / raw)
To: Varadarajan Narayanan
Cc: andersson, konrad.dybcio, mturquette, sboyd, robh,
krzysztof.kozlowski+dt, conor+dt, djakov, quic_anusha,
linux-arm-msm, linux-clk, devicetree, linux-kernel, linux-pm
In-Reply-To: <20240402103406.3638821-7-quic_varada@quicinc.com>
On Tue, 2 Apr 2024 at 13:34, Varadarajan Narayanan
<quic_varada@quicinc.com> wrote:
>
> IPQ SoCs dont involve RPM in managing NoC related clocks and
> there is no NoC scaling. Linux itself handles these clocks.
> However, these should not be exposed as just clocks and align
> with other Qualcomm SoCs that handle these clocks from a
> interconnect provider.
>
> Hence include icc provider capability to the gcc node so that
> peripherals can use the interconnect facility to enable these
> clocks.
>
> Signed-off-by: Varadarajan Narayanan <quic_varada@quicinc.com>
> ---
> arch/arm64/boot/dts/qcom/ipq9574.dtsi | 2 ++
> 1 file changed, 2 insertions(+)
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
--
With best wishes
Dmitry
^ permalink raw reply
* [PATCH v2 0/4] HID: Add support for Himax HX83102j touchscreen
From: Allen_Lin @ 2024-04-02 10:49 UTC (permalink / raw)
To: dmitry.torokhov, robh, krzysztof.kozlowski+dt, conor, jikos,
benjamin.tissoires, linux-input, devicetree, linux-kernel
Cc: Allen_Lin
Hi,
This driver implements for Himax HID touchscreen HX83102j.
Using SPI interface to receive/send HID packets.
Changes in v2 :
-Added power description in YAML document.
-Added ddreset-gpios property in YAML document.
-Added firmware-name property in YAML document.
-Modified the description of pid.
-Modified the example.
Allen_Lin (4):
dt-bindings: input: Add Himax HX83102J touchscreen
HID: Add Himax HX83102J touchscreen driver
HID: Add DRM panel follower function
HID: Load firmware directly from file to IC
.../input/touchscreen/himax,hx83102j.yaml | 100 +
MAINTAINERS | 7 +
drivers/hid/Kconfig | 7 +
drivers/hid/Makefile | 2 +
drivers/hid/hid-himax-83102j.c | 3071 +++++++++++++++++
drivers/hid/hid-himax-83102j.h | 460 +++
6 files changed, 3647 insertions(+)
create mode 100644 Documentation/devicetree/bindings/input/touchscreen/himax,hx83102j.yaml
create mode 100644 drivers/hid/hid-himax-83102j.c
create mode 100644 drivers/hid/hid-himax-83102j.h
--
2.34.1
^ permalink raw reply
* [PATCH v2 1/4] dt-bindings: input: Add Himax HX83102J touchscreen
From: Allen_Lin @ 2024-04-02 10:49 UTC (permalink / raw)
To: dmitry.torokhov, robh, krzysztof.kozlowski+dt, conor, jikos,
benjamin.tissoires, linux-input, devicetree, linux-kernel
Cc: Allen_Lin
In-Reply-To: <20240402104930.1053016-1-allencl_lin@hotmail.com>
Add the HX83102j touchscreen device tree bindings documents.
HX83102j is a Himax TDDI touchscreen controller.
It's power sequence should be bound with a lcm driver, thus it
needs to be a panel follower. Others are the same as normal SPI
touchscreen controller.
Signed-off-by: Allen_Lin <allencl_lin@hotmail.com>
---
.../input/touchscreen/himax,hx83102j.yaml | 100 ++++++++++++++++++
MAINTAINERS | 6 ++
2 files changed, 106 insertions(+)
create mode 100644 Documentation/devicetree/bindings/input/touchscreen/himax,hx83102j.yaml
diff --git a/Documentation/devicetree/bindings/input/touchscreen/himax,hx83102j.yaml b/Documentation/devicetree/bindings/input/touchscreen/himax,hx83102j.yaml
new file mode 100644
index 000000000000..fe79129f704a
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/touchscreen/himax,hx83102j.yaml
@@ -0,0 +1,100 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/input/touchscreen/himax,hx83102j.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Himax hx83102j touchscreen
+
+maintainers:
+ - Allen Lin <allencl_lin@hotmail.com>
+
+description:
+ This Himax hx83102j touchscreen uses the spi protocol.
+
+allOf:
+ - $ref: /schemas/input/touchscreen/touchscreen.yaml#
+ - $ref: /schemas/spi/spi-peripheral-props.yaml#
+
+properties:
+ compatible:
+ const: himax,hx83102j
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ reset-gpios:
+ maxItems: 1
+
+ vccd-supply:
+ description: A phandle for the regualtor supplying IO power.
+
+ vsn-supply:
+ description: Negative supply regulator.
+
+ vsp-supply:
+ description: Positive supply regulator.
+
+ ddreset-gpios:
+ description: A phandle of gpio for display reset controlled by the LCD driver.
+ This is the master reset, if this reset is triggered, the TP reset will
+ also be triggered.
+
+ spi-cpha: true
+
+ spi-cpol: true
+
+ spi-max-frequency: true
+
+ panel: true
+
+ himax,firmware-name:
+ $ref: /schemas/types.yaml#/definitions/string
+ description:
+ Specify the file name for firmware loading.
+
+ himax,pid:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description:
+ PID for HID device, used to validate firmware.
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - reset-gpios
+ - panel
+ - vccd-supply
+ - vsn-supply
+ - vsp-supply
+ - ddreset-gpios
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ ap_ts: touchscreen@0 {
+ compatible = "himax,hx83102j";
+ reg = <0>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&touch_int0 &touch_reset>;
+ reset-gpios = <&gpio1 8 GPIO_ACTIVE_LOW>;
+ spi-cpha;
+ spi-cpol;
+ interrupt-parent = <&gpio1>;
+ interrupts = <7 IRQ_TYPE_LEVEL_LOW>;
+ panel = <&panel>;
+ vccd-supply = <®ulator>;
+ vsn-supply = <®ulator>;
+ vsp-supply = <®ulator>;
+ ddreset-gpios = <&gpio1>;
+ };
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 43b39956694a..aa51c60fd66d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9669,6 +9669,12 @@ L: linux-kernel@vger.kernel.org
S: Maintained
F: drivers/misc/hisi_hikey_usb.c
+HIMAX HID HX83102J TOUCHSCREEN
+M: Allen Lin <allencl_lin@hotmail.com>
+L: linux-input@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/input/touchscreen/himax,hx83102j.yaml
+
HIMAX HX83112B TOUCHSCREEN SUPPORT
M: Job Noorman <job@noorman.info>
L: linux-input@vger.kernel.org
--
2.34.1
^ permalink raw reply related
* [PATCH v2 2/4] HID: Add Himax HX83102J touchscreen driver
From: Allen_Lin @ 2024-04-02 10:49 UTC (permalink / raw)
To: dmitry.torokhov, robh, krzysztof.kozlowski+dt, conor, jikos,
benjamin.tissoires, linux-input, devicetree, linux-kernel
Cc: Allen_Lin
In-Reply-To: <20240402104930.1053016-1-allencl_lin@hotmail.com>
Add a new driver for Himax HX83102J touchscreen controllers.
This driver supports Himax IC using the SPI interface to
acquire HID packets.
After confirmed the IC's exsitence the driver loads the firmware
image from flash to get the HID report descriptor, VID and PID.
And use those information to register HID device.
Signed-off-by: Allen_Lin <allencl_lin@hotmail.com>
---
MAINTAINERS | 1 +
drivers/hid/Kconfig | 7 +
drivers/hid/Makefile | 2 +
drivers/hid/hid-himax-83102j.c | 1695 ++++++++++++++++++++++++++++++++
drivers/hid/hid-himax-83102j.h | 286 ++++++
5 files changed, 1991 insertions(+)
create mode 100644 drivers/hid/hid-himax-83102j.c
create mode 100644 drivers/hid/hid-himax-83102j.h
diff --git a/MAINTAINERS b/MAINTAINERS
index aa51c60fd66d..200db95b501b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9674,6 +9674,7 @@ M: Allen Lin <allencl_lin@hotmail.com>
L: linux-input@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/input/touchscreen/himax,hx83102j.yaml
+F: drivers/hid/hid-himax-83102j.*
HIMAX HX83112B TOUCHSCREEN SUPPORT
M: Job Noorman <job@noorman.info>
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 4c682c650704..94eacf63da21 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -376,6 +376,13 @@ config HID_GLORIOUS
Support for Glorious PC Gaming Race mice such as
the Glorious Model O, O- and D.
+config HID_HIMAX_HX83102J
+ tristate "Himax hx83102j touchscreen"
+ depends on HID
+ depends on SPI
+ help
+ Support for Himax TDDI hx83102j touchscreen.
+
config HID_HOLTEK
tristate "Holtek HID devices"
depends on USB_HID
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 082a728eac60..0f6cb9ccb093 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -170,3 +170,5 @@ obj-$(INTEL_ISH_FIRMWARE_DOWNLOADER) += intel-ish-hid/
obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/
obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/
+
+obj-$(CONFIG_HID_HIMAX_HX83102J) += hid-himax-83102j.o
diff --git a/drivers/hid/hid-himax-83102j.c b/drivers/hid/hid-himax-83102j.c
new file mode 100644
index 000000000000..aa8d0b6677bb
--- /dev/null
+++ b/drivers/hid/hid-himax-83102j.c
@@ -0,0 +1,1695 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Himax hx83102j SPI Driver Code for HID.
+ *
+ * Copyright (C) 2024 Himax Corporation.
+ */
+
+#include "hid-himax-83102j.h"
+
+static int himax_chip_init(struct himax_ts_data *ts);
+static void himax_ts_work(struct himax_ts_data *ts);
+
+/**
+ * himax_spi_read() - Read data from SPI
+ * @ts: Himax touch screen data
+ * @cmd_len: Length of command
+ * @buf: Buffer to store data
+ * @len: Length of data to read
+ *
+ * Himax spi_sync wrapper for read. Read protocol start with write command,
+ * and received the data after that.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_spi_read(struct himax_ts_data *ts, u8 cmd_len, u8 *buf, u32 len)
+{
+ int ret;
+ int retry_cnt;
+ struct spi_message msg;
+ struct spi_transfer xfer = {
+ .len = cmd_len + len,
+ .tx_buf = ts->xfer_tx_data,
+ .rx_buf = ts->xfer_rx_data
+ };
+
+ spi_message_init(&msg);
+ spi_message_add_tail(&xfer, &msg);
+
+ for (retry_cnt = 0; retry_cnt < HIMAX_BUS_RETRY; retry_cnt++) {
+ ret = spi_sync(ts->spi, &msg);
+ if (!ret)
+ break;
+ }
+
+ if (retry_cnt == HIMAX_BUS_RETRY) {
+ dev_err(ts->dev, "%s: SPI read error retry over %d\n", __func__, HIMAX_BUS_RETRY);
+ return -EIO;
+ }
+
+ if (ret < 0)
+ return ret;
+
+ if (msg.status < 0)
+ return msg.status;
+
+ memcpy(buf, ts->xfer_rx_data + cmd_len, len);
+
+ return 0;
+}
+
+/**
+ * himax_spi_write() - Write data to SPI
+ * @ts: Himax touch screen data
+ * @tx_buf: Buffer to write
+ * @tx_len: Length of data to write
+ * @written: Length of data written
+ *
+ * Himax spi_sync wrapper for write.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_spi_write(struct himax_ts_data *ts, u8 *tx_buf, u32 tx_len, u32 *written)
+{
+ int ret;
+ struct spi_message msg;
+ struct spi_transfer xfer = {
+ .tx_buf = tx_buf,
+ .len = tx_len,
+ };
+
+ spi_message_init(&msg);
+ spi_message_add_tail(&xfer, &msg);
+
+ *written = 0;
+ ret = spi_sync(ts->spi, &msg);
+
+ if (ret < 0)
+ return ret;
+
+ if (msg.status < 0)
+ return msg.status;
+
+ *written = msg.actual_length;
+
+ return 0;
+}
+
+/**
+ * himax_read() - Read data from Himax bus
+ * @ts: Himax touch screen data
+ * @cmd: Command to send
+ * @buf: Buffer to store data, caller should allocate the buffer
+ * @len: Length of data to read
+ *
+ * Basic read operation for Himax SPI bus. Which start with a 3 bytes command,
+ * 1st byte is the spi function select, 2nd byte is the command belong to the
+ * spi function and 3rd byte is the dummy byte for IC to process the command.
+ *
+ * The IC takes 1 basic operation at a time, so the read/write operation
+ * is proctected by rw_lock mutex_unlock. Also the buffer xfer_rx/tx_data is
+ * shared between read and write operation, protected by the same mutex lock.
+ * The xfer data limit by SPI constroller max xfer size + BUS_R/W_HLEN
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_read(struct himax_ts_data *ts, u8 cmd, u8 *buf, u32 len)
+{
+ int ret;
+
+ if (len + HIMAX_BUS_R_HLEN > ts->spi_xfer_max_sz) {
+ dev_err(ts->dev, "%s, len[%u] is over %u\n", __func__,
+ len + HIMAX_BUS_R_HLEN, ts->spi_xfer_max_sz);
+ return -EINVAL;
+ }
+
+ mutex_lock(&ts->rw_lock);
+
+ memset(ts->xfer_rx_data, 0, HIMAX_BUS_R_HLEN + len);
+ ts->xfer_tx_data[0] = HIMAX_SPI_FUNCTION_READ;
+ ts->xfer_tx_data[1] = cmd;
+ ts->xfer_tx_data[2] = 0x00;
+ ret = himax_spi_read(ts, HIMAX_BUS_R_HLEN, buf, len);
+
+ mutex_unlock(&ts->rw_lock);
+ if (ret < 0)
+ dev_err(ts->dev, "%s: failed = %d\n", __func__, ret);
+
+ return ret;
+}
+
+/**
+ * himax_write() - Write data to Himax bus
+ * @ts: Himax touch screen data
+ * @cmd: Command to send
+ * @addr: Address to write
+ * @data: Data to write
+ * @len: Length of data to write
+ *
+ * Basic write operation for Himax IC. Which start with a 2 bytes command,
+ * 1st byte is the spi function select and 2nd byte is the command belong to the
+ * spi function. Else is the data to write.
+ *
+ * The IC takes 1 basic operation at a time, so the read/write operation
+ * is proctected by rw_lock mutex_unlock. Also the buffer xfer_tx_data is
+ * shared between read and write operation, protected by the same mutex lock.
+ * The xfer data limit by SPI constroller max xfer size + HIMAX_BUS_W_HLEN
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_write(struct himax_ts_data *ts, u8 cmd, u8 *addr, const u8 *data, u32 len)
+{
+ int ret;
+ u8 offset;
+ u32 written;
+ u32 tmp_len;
+
+ if (len + HIMAX_BUS_W_HLEN > ts->spi_xfer_max_sz) {
+ dev_err(ts->dev, "%s: len[%u] is over %u\n", __func__,
+ len + HIMAX_BUS_W_HLEN, ts->spi_xfer_max_sz);
+ return -EFAULT;
+ }
+
+ mutex_lock(&ts->rw_lock);
+
+ memset(ts->xfer_tx_data, 0, len + HIMAX_BUS_W_HLEN);
+ ts->xfer_tx_data[0] = HIMAX_SPI_FUNCTION_WRITE;
+ ts->xfer_tx_data[1] = cmd;
+ offset = HIMAX_BUS_W_HLEN;
+ tmp_len = len;
+
+ if (addr) {
+ memcpy(ts->xfer_tx_data + offset, addr, 4);
+ offset += 4;
+ tmp_len -= 4;
+ }
+
+ if (data)
+ memcpy(ts->xfer_tx_data + offset, data, tmp_len);
+
+ ret = himax_spi_write(ts, ts->xfer_tx_data, len + HIMAX_BUS_W_HLEN, &written);
+
+ mutex_unlock(&ts->rw_lock);
+
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: failed, ret = %d\n", __func__, ret);
+ return ret;
+ }
+
+ if (written != len + HIMAX_BUS_W_HLEN) {
+ dev_err(ts->dev, "%s: actual write length mismatched: %u != %u\n",
+ __func__, written, len + HIMAX_BUS_W_HLEN);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+/**
+ * himax_mcu_set_burst_mode() - Set burst mode for MCU
+ * @ts: Himax touch screen data
+ * @auto_add_4_byte: Enable auto add 4 byte mode
+ *
+ * Set burst mode for MCU, which is used for read/write data from/to MCU.
+ * HIMAX_AHB_ADDR_CONTI config the IC to take data continuously,
+ * HIMAX_AHB_ADDR_INCR4 config the IC to auto increment the address by 4 byte when
+ * each 4 bytes read/write.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_mcu_set_burst_mode(struct himax_ts_data *ts, bool auto_add_4_byte)
+{
+ int ret;
+ u8 tmp_data[HIMAX_REG_SZ];
+
+ tmp_data[0] = HIMAX_AHB_CMD_CONTI;
+
+ ret = himax_write(ts, HIMAX_AHB_ADDR_CONTI, NULL, tmp_data, 1);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: write ahb_addr_conti failed\n", __func__);
+ return ret;
+ }
+
+ tmp_data[0] = HIMAX_AHB_CMD_INCR4;
+ if (auto_add_4_byte)
+ tmp_data[0] |= HIMAX_AHB_CMD_INCR4_ADD_4_BYTE;
+
+ ret = himax_write(ts, HIMAX_AHB_ADDR_INCR4, NULL, tmp_data, 1);
+ if (ret < 0)
+ dev_err(ts->dev, "%s: write ahb_addr_incr4 failed\n", __func__);
+
+ return ret;
+}
+
+/**
+ * himax_burst_mode_enable() - Enable burst mode for MCU if possible
+ * @ts: Himax touch screen data
+ * @addr: Address to read/write
+ * @len: Length of data to read/write
+ *
+ * Enable burst mode for MCU, helper function to determine the burst mode
+ * operation for MCU. When the address is HIMAX_REG_ADDR_SPI200_DATA, the burst
+ * mode is disabled. When the length of data is over HIMAX_REG_SZ, the burst
+ * mode is enabled. Else the burst mode is disabled.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_burst_mode_enable(struct himax_ts_data *ts, u32 addr, u32 len)
+{
+ int ret;
+
+ if (addr == HIMAX_REG_ADDR_SPI200_DATA)
+ ret = himax_mcu_set_burst_mode(ts, false);
+ else if (len > HIMAX_REG_SZ)
+ ret = himax_mcu_set_burst_mode(ts, true);
+ else
+ ret = himax_mcu_set_burst_mode(ts, false);
+
+ if (ret)
+ dev_err(ts->dev, "%s: burst enable fail!\n", __func__);
+
+ return ret;
+}
+
+/**
+ * himax_mcu_register_read() - Read data from IC register/sram
+ * @ts: Himax touch screen data
+ * @addr: Address to read
+ * @buf: Buffer to store data, caller should allocate the buffer
+ * @len: Length of data to read
+ *
+ * Himax TP IC has its internal register and SRAM, this function is used to
+ * read data from it. The reading protocol require a sequence of write and read,
+ * which include write address to IC and read data from IC. Thus the read/write
+ * operation is proctected by reg_lock mutex_unlock to protect the sequence.
+ * The first step is to set the burst mode for MCU, then write the address to
+ * AHB register to tell where to read. Then set the access direction to read,
+ * and read the data from AHB register. The max length of data to read is decided
+ * by AHB register max transfer size, but if it could't bigger then SPI controller
+ * max transfer size. When the length of data is over the max transfer size,
+ * the data will be read in multiple times.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_mcu_register_read(struct himax_ts_data *ts, u32 addr, u8 *buf, u32 len)
+{
+ int i;
+ int ret;
+ u8 direction_switch = HIMAX_AHB_CMD_ACCESS_DIRECTION_READ;
+ u32 read_sz;
+ const u32 max_trans_sz =
+ min(HIMAX_HX83102J_REG_XFER_MAX, ts->spi_xfer_max_sz - HIMAX_BUS_R_HLEN);
+ union himax_dword_data target_addr;
+
+ mutex_lock(&ts->reg_lock);
+
+ ret = himax_burst_mode_enable(ts, addr, len);
+ if (ret)
+ goto read_end;
+
+ for (i = 0; i < len; i += read_sz) {
+ target_addr.dword = cpu_to_le32(addr + i);
+ ret = himax_write(ts, HIMAX_AHB_ADDR_BYTE_0, target_addr.byte, NULL, 4);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: write ahb_addr_byte_0 failed\n", __func__);
+ goto read_end;
+ }
+
+ ret = himax_write(ts, HIMAX_AHB_ADDR_ACCESS_DIRECTION, NULL,
+ &direction_switch, 1);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: write ahb_addr_access_direction failed\n", __func__);
+ goto read_end;
+ }
+
+ read_sz = min((len - i), max_trans_sz);
+ ret = himax_read(ts, HIMAX_AHB_ADDR_RDATA_BYTE_0, buf + i, read_sz);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: read ahb_addr_rdata_byte_0 failed\n", __func__);
+ goto read_end;
+ }
+ }
+
+read_end:
+ mutex_unlock(&ts->reg_lock);
+ if (ret < 0)
+ dev_err(ts->dev, "%s: addr = 0x%08X, len = %u, ret = %d\n", __func__,
+ addr, len, ret);
+
+ return ret;
+}
+
+/**
+ * himax_mcu_register_write() - Write data to IC register/sram
+ * @ts: Himax touch screen data
+ * @addr: Address to write
+ * @buf: Data to write
+ * @len: Length of data to write
+ *
+ * Himax TP IC has its internal register and SRAM, this function is used to
+ * write data to it. The writing protocol require a sequence of write, which
+ * include write address to IC and write data to IC. Thus the write operation
+ * is proctected by reg_lock mutex_unlock to protect the sequence. The first
+ * step is to set the burst mode for MCU, then write the address and data to
+ * AHB register. The max length of data to read is decided by AHB register max
+ * transfer size, but if it could't bigger then SPI controller max transfer
+ * size. When the length of data is over the max transfer size, the data will
+ * be written in multiple times.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_mcu_register_write(struct himax_ts_data *ts, u32 addr, const u8 *buf, u32 len)
+{
+ int i;
+ int ret;
+ u32 write_sz;
+ const u32 max_trans_sz = min(HIMAX_HX83102J_REG_XFER_MAX,
+ ts->spi_xfer_max_sz - HIMAX_BUS_W_HLEN - HIMAX_REG_SZ);
+ union himax_dword_data target_addr;
+
+ mutex_lock(&ts->reg_lock);
+
+ ret = himax_burst_mode_enable(ts, addr, len);
+ if (ret)
+ goto write_end;
+
+ for (i = 0; i < len; i += max_trans_sz) {
+ write_sz = min((len - i), max_trans_sz);
+ target_addr.dword = cpu_to_le32(addr + i);
+ ret = himax_write(ts, HIMAX_AHB_ADDR_BYTE_0,
+ target_addr.byte, buf + i, write_sz + HIMAX_REG_SZ);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: write ahb_addr_byte_0 failed\n", __func__);
+ break;
+ }
+ }
+
+write_end:
+ mutex_unlock(&ts->reg_lock);
+ if (ret < 0)
+ dev_err(ts->dev, "%s: addr = 0x%08X, len = %u, ret = %d\n", __func__,
+ addr, len, ret);
+
+ return ret;
+}
+
+/**
+ * himax_mcu_interface_on() - Wakeup IC bus interface
+ * @ts: Himax touch screen data
+ *
+ * This function is used to wakeup IC bus interface. The IC may enter sleep mode
+ * and need to wakeup before any operation. The wakeup process is to read a dummy
+ * AHB register to wakeup the IC bus interface. Also, the function setup the burst
+ * mode as default for MCU and read back the burst mode setting to confirm the
+ * setting is written. The action is a double check to confirm the IC bus interface
+ * is ready for operation.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_mcu_interface_on(struct himax_ts_data *ts)
+{
+ int ret;
+ u8 buf[2][HIMAX_REG_SZ];
+ u32 retry_cnt;
+ const u32 burst_retry_limit = 10;
+
+ mutex_lock(&ts->reg_lock);
+ /* Read a dummy register to wake up BUS. */
+ ret = himax_read(ts, HIMAX_AHB_ADDR_RDATA_BYTE_0, buf[0], 4);
+ mutex_unlock(&ts->reg_lock);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: read ahb_addr_rdata_byte_0 failed\n", __func__);
+ return ret;
+ }
+
+ for (retry_cnt = 0; retry_cnt < burst_retry_limit; retry_cnt++) {
+ /* AHB: read/write to SRAM in sequential order */
+ buf[0][0] = HIMAX_AHB_CMD_CONTI;
+ ret = himax_write(ts, HIMAX_AHB_ADDR_CONTI, NULL, buf[0], 1);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: write ahb_addr_conti failed\n", __func__);
+ return ret;
+ }
+
+ /* AHB: Auto increment SRAM addr+4 while each 4 bytes read/write */
+ buf[0][0] = HIMAX_AHB_CMD_INCR4;
+ ret = himax_write(ts, HIMAX_AHB_ADDR_INCR4, NULL, buf[0], 1);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: write ahb_addr_incr4 failed\n", __func__);
+ return ret;
+ }
+
+ /* Check cmd */
+ ret = himax_read(ts, HIMAX_AHB_ADDR_CONTI, buf[0], 1);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: read ahb_addr_conti failed\n", __func__);
+ return ret;
+ }
+
+ ret = himax_read(ts, HIMAX_AHB_ADDR_INCR4, buf[1], 1);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: read ahb_addr_incr4 failed\n", __func__);
+ return ret;
+ }
+
+ if (buf[0][0] == HIMAX_AHB_CMD_CONTI && buf[1][0] == HIMAX_AHB_CMD_INCR4)
+ return 0;
+
+ usleep_range(1000, 1100);
+ }
+
+ dev_err(ts->dev, "%s: failed!\n", __func__);
+
+ return -EIO;
+}
+
+/**
+ * hx83102j_pin_reset() - Reset the touch chip by hardware pin
+ * @ts: Himax touch screen data
+ *
+ * This function is used to hardware reset the touch chip. By pull down the
+ * reset pin to low over 20ms, ensure the reset circuit perform a complete reset
+ * to the touch chip.
+ *
+ * Return: None
+ */
+static void hx83102j_pin_reset(struct himax_ts_data *ts)
+{
+ gpiod_set_value(ts->pdata.gpiod_rst, 1);
+ usleep_range(10000, 10100);
+ gpiod_set_value(ts->pdata.gpiod_rst, 0);
+ usleep_range(20000, 20100);
+}
+
+/**
+ * himax_int_enable() - Enable/Disable interrupt
+ * @ts: Himax touch screen data
+ * @enable: true for enable, false for disable
+ *
+ * This function is used to enable or disable the interrupt.
+ *
+ * Return: None
+ */
+static void himax_int_enable(struct himax_ts_data *ts, bool enable)
+{
+ int irqnum = ts->himax_irq;
+ unsigned long flags;
+
+ spin_lock_irqsave(&ts->irq_lock, flags);
+ if (enable && atomic_read(&ts->irq_state) == 0) {
+ atomic_set(&ts->irq_state, 1);
+ enable_irq(irqnum);
+ } else if (!enable && atomic_read(&ts->irq_state) == 1) {
+ atomic_set(&ts->irq_state, 0);
+ disable_irq_nosync(irqnum);
+ }
+ spin_unlock_irqrestore(&ts->irq_lock, flags);
+ dev_info(ts->dev, "%s: Interrupt %s\n", __func__,
+ atomic_read(&ts->irq_state) ? "enabled" : "disabled");
+}
+
+/**
+ * himax_mcu_ic_reset() - Reset the touch chip and disable/enable interrupt
+ * @ts: Himax touch screen data
+ * @int_off: true for disable/enable interrupt, false for not
+ *
+ * This function is used to reset the touch chip with interrupt control. The
+ * TPIC will pull low the interrupt pin when IC is reset. When the ISR has been
+ * set and need to be take care of, the caller could set int_off to true to disable
+ * the interrupt before reset and enable the interrupt after reset.
+ *
+ * Return: None
+ */
+static void himax_mcu_ic_reset(struct himax_ts_data *ts, bool int_off)
+{
+ if (int_off)
+ himax_int_enable(ts, false);
+
+ hx83102j_pin_reset(ts);
+
+ if (int_off)
+ himax_int_enable(ts, true);
+}
+
+/**
+ * hx83102j_sense_off() - Stop MCU and enter safe mode
+ * @ts: Himax touch screen data
+ * @check_en: Check if need to ensure FW is stopped by its owne process
+ *
+ * Sense off is a process to make sure the MCU inside the touch chip is stopped.
+ * The process has two stage, first stage is to request FW to stop. Write
+ * HIMAX_REG_DATA_FW_GO_SAFEMODE to HIMAX_REG_ADDR_CTRL_FW tells the FW to stop by its own.
+ * Then read back the FW status to confirm the FW is stopped. When check_en is true,
+ * the function will resend the stop FW command until the retry limit reached.
+ * There maybe a chance that the FW is not stopped by its own, in this case, the
+ * safe mode in next stage still stop the MCU, but FW internal flag may not be
+ * configured correctly. The second stage is to enter safe mode and reset TCON.
+ * Safe mode is a mode that the IC circuit ensure the internal MCU is stopped.
+ * Since this IC is TDDI, the TCON need to be reset to make sure the IC is ready
+ * for next operation.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int hx83102j_sense_off(struct himax_ts_data *ts, bool check_en)
+{
+ int ret;
+ u32 retry_cnt;
+ const u32 stop_fw_retry_limit = 35;
+ const u32 enter_safe_mode_retry_limit = 5;
+ const union himax_dword_data safe_mode = {
+ .dword = cpu_to_le32(HIMAX_REG_DATA_FW_GO_SAFEMODE)
+ };
+ union himax_dword_data data;
+
+ dev_info(ts->dev, "%s: check %s\n", __func__, check_en ? "True" : "False");
+ if (!check_en)
+ goto without_check;
+
+ for (retry_cnt = 0; retry_cnt < stop_fw_retry_limit; retry_cnt++) {
+ if (retry_cnt == 0 ||
+ (data.byte[0] != HIMAX_REG_DATA_FW_GO_SAFEMODE &&
+ data.byte[0] != HIMAX_REG_DATA_FW_RE_INIT &&
+ data.byte[0] != HIMAX_REG_DATA_FW_IN_SAFEMODE)) {
+ ret = himax_mcu_register_write(ts, HIMAX_REG_ADDR_CTRL_FW,
+ safe_mode.byte, 4);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: stop FW failed\n", __func__);
+ return ret;
+ }
+ }
+ usleep_range(10000, 11000);
+
+ ret = himax_mcu_register_read(ts, HIMAX_REG_ADDR_FW_STATUS, data.byte, 4);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: read central state failed\n", __func__);
+ return ret;
+ }
+ if (data.byte[0] != HIMAX_REG_DATA_FW_STATE_RUNNING) {
+ dev_info(ts->dev, "%s: Do not need wait FW, Status = 0x%02X!\n", __func__,
+ data.byte[0]);
+ break;
+ }
+
+ ret = himax_mcu_register_read(ts, HIMAX_REG_ADDR_CTRL_FW, data.byte, 4);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: read ctrl FW failed\n", __func__);
+ return ret;
+ }
+ if (data.byte[0] == HIMAX_REG_DATA_FW_IN_SAFEMODE)
+ break;
+ }
+
+ if (data.byte[0] != HIMAX_REG_DATA_FW_IN_SAFEMODE)
+ dev_warn(ts->dev, "%s: Failed to stop FW!\n", __func__);
+
+without_check:
+ for (retry_cnt = 0; retry_cnt < enter_safe_mode_retry_limit; retry_cnt++) {
+ /* set Enter safe mode : 0x31 ==> 0x9527 */
+ data.word[0] = cpu_to_le16(HIMAX_HX83102J_SAFE_MODE_PASSWORD);
+ ret = himax_write(ts, HIMAX_AHB_ADDR_PSW_LB, NULL, data.byte, 2);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: enter safe mode failed\n", __func__);
+ return ret;
+ }
+
+ /* Check enter_save_mode */
+ ret = himax_mcu_register_read(ts, HIMAX_REG_ADDR_FW_STATUS, data.byte, 4);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: read central state failed\n", __func__);
+ return ret;
+ }
+
+ if (data.byte[0] == HIMAX_REG_DATA_FW_STATE_SAFE_MODE) {
+ dev_info(ts->dev, "%s: Safe mode entered\n", __func__);
+ /* Reset TCON */
+ data.dword = cpu_to_le32(HIMAX_REG_DATA_TCON_RST);
+ ret = himax_mcu_register_write(ts, HIMAX_HX83102J_REG_ADDR_TCON_RST,
+ data.byte, 4);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: reset TCON failed\n", __func__);
+ return ret;
+ }
+ usleep_range(1000, 1100);
+ return 0;
+ }
+ usleep_range(5000, 5100);
+ hx83102j_pin_reset(ts);
+ }
+ dev_err(ts->dev, "%s: failed!\n", __func__);
+
+ return -EIO;
+}
+
+/**
+ * hx83102j_chip_detect() - Check if the touch chip is HX83102J
+ * @ts: Himax touch screen data
+ *
+ * This function is used to check if the touch chip is HX83102J. The process
+ * start with a hardware reset to the touch chip, then knock the IC bus interface
+ * to wakeup the IC bus interface. Then sense off the MCU to prevent bus conflict
+ * when reading the IC ID. The IC ID is read from the IC register, and compare
+ * with the expected ID. If the ID is matched, the chip is HX83102J. Due to display
+ * IC initial code may not ready before the IC ID is read, the function will retry
+ * to read the IC ID for several times to make sure the IC ID is read correctly.
+ * In any case, the SPI bus shouldn't have error when reading the IC ID, so the
+ * function will return error if the SPI bus has error. When the IC is not HX83102J,
+ * the function will return -ENODEV.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int hx83102j_chip_detect(struct himax_ts_data *ts)
+{
+ int ret;
+ u32 retry_cnt;
+ const u32 read_icid_retry_limit = 5;
+ const u32 ic_id_mask = GENMASK(31, 8);
+ union himax_dword_data data;
+
+ hx83102j_pin_reset(ts);
+ ret = himax_mcu_interface_on(ts);
+ if (ret)
+ return ret;
+
+ ret = hx83102j_sense_off(ts, false);
+ if (ret)
+ return ret;
+
+ for (retry_cnt = 0; retry_cnt < read_icid_retry_limit; retry_cnt++) {
+ ret = himax_mcu_register_read(ts, HIMAX_REG_ADDR_ICID, data.byte, 4);
+ if (ret) {
+ dev_err(ts->dev, "%s: Read IC ID Fail\n", __func__);
+ return ret;
+ }
+
+ data.dword = le32_to_cpu(data.dword);
+ if ((data.dword & ic_id_mask) == HIMAX_REG_DATA_ICID) {
+ ts->ic_data.icid = data.dword;
+ dev_info(ts->dev, "%s: Detect IC HX83102J successfully\n", __func__);
+ return 0;
+ }
+ }
+ dev_err(ts->dev, "%s: Read driver ID register Fail! IC ID = %X,%X,%X\n", __func__,
+ data.byte[3], data.byte[2], data.byte[1]);
+
+ return -ENODEV;
+}
+
+/**
+ * himax_ts_thread() - Thread for interrupt handling
+ * @irq: Interrupt number
+ * @ptr: Pointer to the touch screen data
+ *
+ * This function is used to handle the interrupt. The function will call himax_ts_work()
+ * to handle the interrupt.
+ *
+ * Return: IRQ_HANDLED
+ */
+static irqreturn_t himax_ts_thread(int irq, void *ptr)
+{
+ himax_ts_work((struct himax_ts_data *)ptr);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * __himax_ts_register_interrupt() - Register interrupt trigger
+ * @ts: Himax touch screen data
+ *
+ * This function is used to register the interrupt. The function will call
+ * devm_request_threaded_irq() to register the interrupt by the trigger type.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int __himax_ts_register_interrupt(struct himax_ts_data *ts)
+{
+ if (ts->ic_data.interrupt_is_edge)
+ return devm_request_threaded_irq(ts->dev, ts->himax_irq, NULL,
+ himax_ts_thread,
+ IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ ts->dev->driver->name, ts);
+
+ return devm_request_threaded_irq(ts->dev, ts->himax_irq, NULL,
+ himax_ts_thread,
+ IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+ ts->dev->driver->name, ts);
+}
+
+/**
+ * himax_ts_register_interrupt() - Register interrupt
+ * @ts: Himax touch screen data
+ *
+ * This function is a wrapper to call __himax_ts_register_interrupt() to register the
+ * interrupt and set irq_state.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_ts_register_interrupt(struct himax_ts_data *ts)
+{
+ int ret;
+
+ if (!ts || !ts->himax_irq) {
+ dev_err(ts->dev, "%s: ts or ts->himax_irq invalid!\n", __func__);
+ return -EINVAL;
+ }
+
+ ret = __himax_ts_register_interrupt(ts);
+ if (!ret) {
+ atomic_set(&ts->irq_state, 1);
+ dev_info(ts->dev, "%s: irq enabled at: %d\n", __func__, ts->himax_irq);
+ return 0;
+ }
+
+ atomic_set(&ts->irq_state, 0);
+ dev_err(ts->dev, "%s: request_irq failed\n", __func__);
+
+ return ret;
+}
+
+/**
+ * hx83102j_read_event_stack() - Read event stack from touch chip
+ * @ts: Himax touch screen data
+ * @buf: Buffer to store the data
+ * @length: Length of data to read
+ *
+ * This function is used to read the event stack from the touch chip. The event stack
+ * is an AHB output buffer, which store the touch report data.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int hx83102j_read_event_stack(struct himax_ts_data *ts, u8 *buf, u32 length)
+{
+ u32 i;
+ int ret;
+ const u32 max_trunk_sz = ts->spi_xfer_max_sz - HIMAX_BUS_R_HLEN;
+
+ for (i = 0; i < length; i += max_trunk_sz) {
+ ret = himax_read(ts, HIMAX_AHB_ADDR_EVENT_STACK, buf + i,
+ min(length - i, max_trunk_sz));
+ if (ret) {
+ dev_err(ts->dev, "%s: read event stack error!\n", __func__);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * himax_touch_get() - Get touch data from touch chip
+ * @ts: Himax touch screen data
+ * @buf: Buffer to store the data
+ *
+ * This function is a wrapper to call hx83102j_read_event_stack() to read the touch
+ * data from the touch chip. The touch_data_sz is the size of the touch data to read,
+ * which is calculated by hid report descriptor provided by the firmware.
+ *
+ * Return: HIMAX_TS_SUCCESS on success, negative error code on failure. We categorize
+ * the error code into HIMAX_TS_GET_DATA_FAIL when the read fails, and HIMAX_TS_SUCCESS
+ * when the read is successful. The reason is that the may need special handling when
+ * the read fails.
+ */
+static int himax_touch_get(struct himax_ts_data *ts, u8 *buf)
+{
+ if (hx83102j_read_event_stack(ts, buf, ts->touch_data_sz)) {
+ dev_err(ts->dev, "can't read data from chip!");
+ return HIMAX_TS_GET_DATA_FAIL;
+ }
+
+ return HIMAX_TS_SUCCESS;
+}
+
+/**
+ * himax_bin_desc_data_get() - Parse descriptor data from firmware token
+ * @ts: Himax touch screen data
+ * @addr: Address of the data in firmware image
+ * @descript_buf: token for parsing
+ *
+ * This function is used to parse the descriptor data from the firmware token. The
+ * descriptors are mappings of information in the firmware image. The function will
+ * check checksum of each token first, and then parse the token to get the related
+ * data. The data includes CID version, FW version, CFG version, touch config table,
+ * HID table, HID descriptor, and HID read descriptor.
+ *
+ * Return: true on success, false on failure
+ */
+static bool himax_bin_desc_data_get(struct himax_ts_data *ts, u32 addr, u8 *descript_buf)
+{
+ u16 chk_end;
+ u16 chk_sum;
+ u32 hid_table_addr;
+ u32 i, j;
+ u32 image_offset;
+ u32 map_code;
+ const u32 data_sz = 16;
+ const u32 report_desc_offset = 24;
+ union {
+ u8 *buf;
+ u32 *word;
+ } map_data;
+
+ /* looking for mapping in page, each mapping is 16 bytes */
+ for (i = 0; i < HIMAX_HX83102J_PAGE_SIZE; i = i + data_sz) {
+ chk_end = 0;
+ chk_sum = 0;
+ for (j = i; j < (i + data_sz); j++) {
+ chk_end |= descript_buf[j];
+ chk_sum += descript_buf[j];
+ }
+ if (!chk_end) { /* 1. Check all zero */
+ return false;
+ } else if (chk_sum % 0x100) { /* 2. Check sum */
+ dev_warn(ts->dev, "%s: chk sum failed in %X\n", __func__, i + addr);
+ } else { /* 3. get data */
+ map_data.buf = &descript_buf[i];
+ map_code = le32_to_cpup(map_data.word);
+ map_data.buf = &descript_buf[i + 4];
+ image_offset = le32_to_cpup(map_data.word);
+ /* 4. load info from FW image by specified mapping offset */
+ switch (map_code) {
+ /* Config ID */
+ case HIMAX_FW_CID:
+ ts->fw_info_table.addr_cid_ver_major = image_offset;
+ ts->fw_info_table.addr_cid_ver_minor = image_offset + 1;
+ break;
+ /* FW version */
+ case HIMAX_FW_VER:
+ ts->fw_info_table.addr_fw_ver_major = image_offset;
+ ts->fw_info_table.addr_fw_ver_minor = image_offset + 1;
+ break;
+ /* Config version */
+ case HIMAX_CFG_VER:
+ ts->fw_info_table.addr_cfg_ver_major = image_offset;
+ ts->fw_info_table.addr_cfg_ver_minor = image_offset + 1;
+ break;
+ /* Touch config table */
+ case HIMAX_TP_CONFIG_TABLE:
+ ts->fw_info_table.addr_cfg_table = image_offset;
+ break;
+ /* HID table */
+ case HIMAX_HID_TABLE:
+ ts->fw_info_table.addr_hid_table = image_offset;
+ hid_table_addr = image_offset;
+ ts->fw_info_table.addr_hid_desc = hid_table_addr;
+ ts->fw_info_table.addr_hid_rd_desc =
+ hid_table_addr + report_desc_offset;
+ break;
+ }
+ }
+ }
+
+ return true;
+}
+
+/**
+ * himax_mcu_bin_desc_get() - Check and get the bin description from the data
+ * @fw: Firmware data
+ * @ts: Himax touch screen data
+ * @max_sz: Maximum size to check
+ *
+ * This function is used to check and get the bin description from the firmware data.
+ * It will check the given data to see if it match the bin description format, and
+ * call himax_bin_desc_data_get() to get the related data.
+ *
+ * Return: true on mapping_count > 0, false on otherwise
+ */
+static bool himax_mcu_bin_desc_get(unsigned char *fw, struct himax_ts_data *ts, u32 max_sz)
+{
+ bool keep_on_flag;
+ u32 addr;
+ u32 mapping_count;
+ unsigned char *fw_buf;
+ const u8 header_id = 0x87;
+ const u8 header_id_loc = 0x0e;
+ const u8 header_sz = 8;
+ const u8 header[8] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+
+ /* Check bin is with description table or not */
+ if (!(memcmp(fw, header, header_sz) == 0 && fw[header_id_loc] == header_id)) {
+ dev_err(ts->dev, "%s: No description table\n", __func__);
+ return false;
+ }
+
+ for (addr = 0, mapping_count = 0; addr < max_sz; addr += HIMAX_HX83102J_PAGE_SIZE) {
+ fw_buf = &fw[addr];
+ /* Get related data */
+ keep_on_flag = himax_bin_desc_data_get(ts, addr, fw_buf);
+ if (keep_on_flag)
+ mapping_count++;
+ else
+ break;
+ }
+
+ return mapping_count > 0;
+}
+
+/**
+ * himax_hid_parse() - Parse the HID report descriptor
+ * @hid: HID device
+ *
+ * This function is used to parse the HID report descriptor.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_hid_parse(struct hid_device *hid)
+{
+ int ret;
+ struct himax_ts_data *ts;
+
+ if (!hid)
+ return -ENODEV;
+
+ ts = hid->driver_data;
+ if (!ts)
+ return -EINVAL;
+
+ ret = hid_parse_report(hid, ts->hid_rd_data.rd_data, ts->hid_rd_data.rd_length);
+ if (ret) {
+ dev_err(ts->dev, "%s: failed parse report\n", __func__);
+ return ret;
+ }
+
+ return 0;
+}
+
+/**
+ * himax_hid_start - Start the HID device
+ * @hid: HID device
+ *
+ * The function for hid_ll_driver.start to start the HID device.
+ * This driver does not need to do anything here.
+ *
+ * Return: 0 for success
+ */
+static int himax_hid_start(struct hid_device *hid)
+{
+ return 0;
+}
+
+/**
+ * himax_hid_stop - Stop the HID device
+ * @hid: HID device
+ *
+ * The function for hid_ll_driver.stop to stop the HID device.
+ * This driver does not need to do anything here.
+ *
+ * Return: None
+ */
+static void himax_hid_stop(struct hid_device *hid)
+{
+}
+
+/**
+ * himax_hid_open - Open the HID device
+ * @hid: HID device
+ *
+ * The function for hid_ll_driver.open to open the HID device.
+ * This driver does not need to do anything here.
+ *
+ * Return: 0 for success
+ */
+static int himax_hid_open(struct hid_device *hid)
+{
+ return 0;
+}
+
+/**
+ * himax_hid_close - Close the HID device
+ * @hid: HID device
+ *
+ * The function for hid_ll_driver.close to close the HID device.
+ * This driver does not need to do anything here.
+ *
+ * Return: None
+ */
+static void himax_hid_close(struct hid_device *hid)
+{
+}
+
+/**
+ * himax_hid_get_raw_report - Process hidraw GET REPORT operation
+ * @hid: HID device
+ * @reportnum: Report ID
+ * @buf: Buffer for communication
+ * @len: Length of data in the buffer
+ * @report_type: Report type
+ *
+ * The function for hid_ll_driver.get_raw_report to handle the HIDRAW ioctl
+ * get report request. The report number to handle is based on the report
+ * descriptor of the HID device. The buf is used to communicate with user
+ * program, user pass the ID and parameters to the driver use this buf, and
+ * the driver will return the result to user also use this buf. The len is
+ * the length of data in the buf, passed by user program. The report_type is
+ * not used in this driver. We currently support the following report number:
+ * - HIMAX_ID_CONTACT_COUNT: Report the maximum number of touch points
+ * Case not listed here will return -EINVAL.
+ *
+ * Return: The length of the data in the buf on success, negative error code
+ */
+static int himax_hid_get_raw_report(const struct hid_device *hid,
+ unsigned char reportnum, __u8 *buf,
+ size_t len, unsigned char report_type)
+{
+ struct himax_ts_data *ts;
+
+ ts = hid->driver_data;
+ if (!ts) {
+ dev_err(ts->dev, "hid->driver_data is NULL");
+ return -EINVAL;
+ }
+
+ switch (reportnum) {
+ case HIMAX_ID_CONTACT_COUNT:
+ /* buf[0] is ID, keep it; buf[1] and later used as parameters for ID */
+ buf[1] = ts->ic_data.max_point;
+ return len;
+ };
+
+ return -EINVAL;
+}
+
+/**
+ * himax_raw_request - Handle the HIDRAW ioctl request
+ * @hid: HID device
+ * @reportnum: Report ID
+ * @buf: Buffer for communication
+ * @len: Length of data in the buffer
+ * @rtype: Report type
+ * @reqtype: Request type
+ *
+ * The function for hid_ll_driver.raw_request to handle the HIDRAW ioctl
+ * request. We handle only the GET_REPORT and SET_REPORT request.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_raw_request(struct hid_device *hid, unsigned char reportnum, __u8 *buf,
+ size_t len, unsigned char rtype, int reqtype)
+{
+ switch (reqtype) {
+ case HID_REQ_GET_REPORT:
+ return himax_hid_get_raw_report(hid, reportnum, buf, len, rtype);
+ default:
+ return -EIO;
+ }
+
+ return -EINVAL;
+}
+
+static struct hid_ll_driver himax_hid_ll_driver = {
+ .parse = himax_hid_parse,
+ .start = himax_hid_start,
+ .stop = himax_hid_stop,
+ .open = himax_hid_open,
+ .close = himax_hid_close,
+ .raw_request = himax_raw_request
+};
+
+/**
+ * himax_hid_report() - Report input data to HID core
+ * @ts: Himax touch screen data
+ * @data: Data to report
+ * @len: Length of the data
+ *
+ * This function is a wrapper to report input data to HID core.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_hid_report(const struct himax_ts_data *ts, u8 *data, s32 len)
+{
+ return hid_input_report(ts->hid, HID_INPUT_REPORT, data, len, 1);
+}
+
+/**
+ * himax_hid_probe() - Probe the HID device
+ * @ts: Himax touch screen data
+ *
+ * This function is used to probe the HID device.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_hid_probe(struct himax_ts_data *ts)
+{
+ int ret;
+ struct hid_device *hid;
+
+ hid = ts->hid;
+ if (hid) {
+ dev_warn(ts->dev, "%s: hid device already exist!\n", __func__);
+ hid_destroy_device(hid);
+ hid = NULL;
+ }
+
+ hid = hid_allocate_device();
+ if (IS_ERR(hid)) {
+ ret = PTR_ERR(hid);
+ return ret;
+ }
+
+ hid->driver_data = ts;
+ hid->ll_driver = &himax_hid_ll_driver;
+ hid->bus = BUS_SPI;
+ hid->dev.parent = &ts->spi->dev;
+
+ hid->version = ts->hid_desc.bcd_version;
+ hid->vendor = ts->hid_desc.vendor_id;
+ hid->product = ts->hid_desc.product_id;
+ snprintf(hid->name, sizeof(hid->name), "%s %04X:%04X", "hid-hxtp",
+ hid->vendor, hid->product);
+
+ ret = hid_add_device(hid);
+ if (ret) {
+ dev_err(ts->dev, "%s: failed add hid device\n", __func__);
+ goto err_hid_data;
+ }
+ ts->hid = hid;
+
+ return 0;
+
+err_hid_data:
+ hid_destroy_device(hid);
+
+ return ret;
+}
+
+/**
+ * himax_hid_remove() - Remove the HID device
+ * @ts: Himax touch screen data
+ *
+ * This function is used to remove the HID device.
+ *
+ * Return: None
+ */
+static void himax_hid_remove(struct himax_ts_data *ts)
+{
+ if (ts && ts->hid)
+ hid_destroy_device(ts->hid);
+ else
+ return;
+
+ ts->hid = NULL;
+}
+
+/**
+ * himax_ts_operation() - Process the touch interrupt data
+ * @ts: Himax touch screen data
+ *
+ * This function is used to process the touch interrupt data. It will
+ * call the himax_touch_get() to get the touch data.
+ * If the hid is probed, it will call the himax_hid_report() to report the
+ * touch data to the HID core. Due to the report data must match the HID
+ * report descriptor, the size of report data is fixed. To prevent next report
+ * data being contaminated by the previous data, all the data must be reported
+ * wheather previous data is valid or not.
+ *
+ * Return: HIMAX_TS_SUCCESS on success, negative error code in
+ * himax_touch_report_status on failure
+ */
+static int himax_ts_operation(struct himax_ts_data *ts)
+{
+ int ret;
+ u32 offset;
+
+ memset(ts->xfer_buf, 0x00, ts->xfer_buf_sz);
+ ret = himax_touch_get(ts, ts->xfer_buf);
+ if (ret == HIMAX_TS_GET_DATA_FAIL)
+ return ret;
+ if (ts->hid_probed) {
+ offset = ts->hid_desc.max_input_length;
+ if (ts->ic_data.stylus_function) {
+ ret += himax_hid_report(ts,
+ ts->xfer_buf + offset + HIMAX_HID_REPORT_HDR_SZ,
+ ts->hid_desc.max_input_length -
+ HIMAX_HID_REPORT_HDR_SZ);
+ offset += ts->hid_desc.max_input_length;
+ }
+ }
+
+ if (ret != 0)
+ return HIMAX_TS_GET_DATA_FAIL;
+
+ return HIMAX_TS_SUCCESS;
+}
+
+/**
+ * himax_ts_work() - Work function for the touch screen
+ * @ts: Himax touch screen data
+ *
+ * This function is used to handle interrupt bottom half work. It will
+ * call the himax_ts_operation() to get the touch data, dispatch the data
+ * to HID core. If the touch data is not valid, it will reset the TPIC.
+ *
+ * Return: void
+ */
+static void himax_ts_work(struct himax_ts_data *ts)
+{
+ if (himax_ts_operation(ts) == HIMAX_TS_GET_DATA_FAIL) {
+ dev_info(ts->dev, "%s: Now reset the Touch chip\n", __func__);
+ himax_mcu_ic_reset(ts, true);
+ }
+}
+
+/**
+ * himax_hid_rd_init() - Initialize the HID report descriptor
+ * @ts: Himax touch screen data
+ *
+ * The function is used to calculate the size of the HID report descriptor,
+ * allocate the memory and copy the report descriptor from firmware data to
+ * the allocated memory for later hid device registration.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_hid_rd_init(struct himax_ts_data *ts)
+{
+ u32 rd_sz;
+
+ /* The rd_sz is taken from RD size in FW hid report table. */
+ rd_sz = ts->hid_desc.report_desc_length;
+ /* fw_info_table should contain address of hid_rd_desc in FW image */
+ if (ts->fw_info_table.addr_hid_rd_desc != 0) {
+ /* if rd_sz has been change, need to release old one */
+ if (ts->hid_rd_data.rd_data &&
+ rd_sz != ts->hid_rd_data.rd_length) {
+ devm_kfree(ts->dev, ts->hid_rd_data.rd_data);
+ ts->hid_rd_data.rd_data = NULL;
+ }
+
+ if (!ts->hid_rd_data.rd_data) {
+ ts->hid_rd_data.rd_data = devm_kzalloc(ts->dev, rd_sz, GFP_KERNEL);
+ if (!ts->hid_rd_data.rd_data)
+ return -ENOMEM;
+ }
+ /* Copy the base RD from firmware table */
+ memcpy((void *)ts->hid_rd_data.rd_data,
+ &ts->himax_fw_data[ts->fw_info_table.addr_hid_rd_desc],
+ ts->hid_desc.report_desc_length);
+ ts->hid_rd_data.rd_length = ts->hid_desc.report_desc_length;
+ }
+
+ return 0;
+}
+
+/**
+ * himax_hid_register() - Register the HID device
+ * @ts: Himax touch screen data
+ *
+ * The function is used to register the HID device. If the hid is probed,
+ * it will destroy the previous hid device and re-probe the hid device.
+ *
+ * Return: None
+ */
+static void himax_hid_register(struct himax_ts_data *ts)
+{
+ if (ts->hid_probed) {
+ hid_destroy_device(ts->hid);
+ ts->hid = NULL;
+ ts->hid_probed = false;
+ }
+
+ if (himax_hid_probe(ts)) {
+ dev_err(ts->dev, "%s: hid probe fail\n", __func__);
+ ts->hid_probed = false;
+ } else {
+ ts->hid_probed = true;
+ }
+}
+
+/**
+ * himax_hid_report_data_init() - Calculate the size of the HID report data
+ * @ts: Himax touch screen data
+ *
+ * The function is used to calculate the final size of the HID report data.
+ * The base size is equal to the max_input_length of the hid descriptor.
+ * If the size of the HID report data is not equal to the previous size, it
+ * will free the previous allocated memory and allocate the new memory which
+ * size is equal to the final size of touch_data_sz.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_hid_report_data_init(struct himax_ts_data *ts)
+{
+ ts->touch_data_sz = ts->hid_desc.max_input_length;
+ if (ts->ic_data.stylus_function)
+ ts->touch_data_sz += ts->hid_desc.max_input_length;
+ if (ts->touch_data_sz != ts->xfer_buf_sz) {
+ kfree(ts->xfer_buf);
+ ts->xfer_buf_sz = 0;
+ ts->xfer_buf = kzalloc(ts->touch_data_sz, GFP_KERNEL);
+ if (!ts->xfer_buf)
+ return -ENOMEM;
+ ts->xfer_buf_sz = ts->touch_data_sz;
+ }
+
+ return 0;
+}
+
+/* load firmware data from flash, parse HID info and register HID */
+/**
+ * himax_load_config() - Load the firmware from the flash
+ * @ts: Himax touch screen data
+ *
+ * This function is used to load the firmware from the flash. It will read
+ * the firmware from the flash and parse the HID info. If the HID info is
+ * valid, it will initialize the HID report descriptor and register the HID
+ * device. If the HID device is probed, it will initialize the report data
+ * and enable the interrupt.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_load_config(struct himax_ts_data *ts)
+{
+ int ret;
+ s32 i;
+ s32 page_sz = (s32)HIMAX_HX83102J_PAGE_SIZE;
+ s32 flash_sz = (s32)HIMAX_HX83102J_FLASH_SIZE;
+ bool fw_load_status = false;
+ const u32 fw_bin_header_sz = 1024;
+
+ ts->ic_boot_done = false;
+
+ ts->himax_fw_data = devm_kzalloc(ts->dev, HIMAX_HX83102J_FLASH_SIZE, GFP_KERNEL);
+ if (!ts->himax_fw_data)
+ return -ENOMEM;
+
+ for (i = 0; i < flash_sz; i += page_sz) {
+ ret = himax_mcu_register_read(ts, i, ts->himax_fw_data + i,
+ (flash_sz - i) > page_sz ? page_sz : (flash_sz - i));
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: read FW from flash fail!\n", __func__);
+ return ret;
+ }
+ }
+ /* Search mapping table in 1k header */
+ fw_load_status = himax_mcu_bin_desc_get((unsigned char *)ts->himax_fw_data,
+ ts, fw_bin_header_sz);
+ if (!fw_load_status) {
+ dev_err(ts->dev, "%s: FW load status fail!\n", __func__);
+ return -EINVAL;
+ }
+
+ if (ts->fw_info_table.addr_hid_desc != 0) {
+ memcpy(&ts->hid_desc,
+ &ts->himax_fw_data[ts->fw_info_table.addr_hid_desc],
+ sizeof(struct himax_hid_desc));
+ ts->hid_desc.desc_length =
+ le16_to_cpu(ts->hid_desc.desc_length);
+ ts->hid_desc.bcd_version =
+ le16_to_cpu(ts->hid_desc.bcd_version);
+ ts->hid_desc.report_desc_length =
+ le16_to_cpu(ts->hid_desc.report_desc_length);
+ ts->hid_desc.max_input_length =
+ le16_to_cpu(ts->hid_desc.max_input_length);
+ ts->hid_desc.max_output_length =
+ le16_to_cpu(ts->hid_desc.max_output_length);
+ ts->hid_desc.max_fragment_length =
+ le16_to_cpu(ts->hid_desc.max_fragment_length);
+ ts->hid_desc.vendor_id =
+ le16_to_cpu(ts->hid_desc.vendor_id);
+ ts->hid_desc.product_id =
+ le16_to_cpu(ts->hid_desc.product_id);
+ ts->hid_desc.version_id =
+ le16_to_cpu(ts->hid_desc.version_id);
+ ts->hid_desc.flags =
+ le16_to_cpu(ts->hid_desc.flags);
+ }
+
+ if (himax_hid_rd_init(ts)) {
+ dev_err(ts->dev, "%s: hid rd init fail\n", __func__);
+ goto err_hid_rd_init_failed;
+ }
+
+ himax_hid_register(ts);
+ if (!ts->hid_probed) {
+ goto err_hid_probe_failed;
+ } else {
+ if (himax_hid_report_data_init(ts)) {
+ dev_err(ts->dev, "%s: report data init fail\n", __func__);
+ goto err_report_data_init_failed;
+ }
+ }
+
+ ts->himax_fw_data = NULL;
+ ts->ic_boot_done = true;
+ himax_int_enable(ts, true);
+
+ return 0;
+
+err_report_data_init_failed:
+ himax_hid_remove(ts);
+ ts->hid_probed = false;
+err_hid_probe_failed:
+err_hid_rd_init_failed:
+
+ return -EINVAL;
+}
+
+/**
+ * himax_chip_init() - Initialize the Himax touch screen
+ * @ts: Himax touch screen data
+ *
+ * This function is used to initialize the Himax touch screen. It will
+ * initialize interrupt lock, register the interrupt, and disable the
+ * interrupt. If later part of initialization succeed, then interrupt will
+ * be enabled.
+ * It will also load the firmware from the flash, parse the HID info, and
+ * register the HID device by calling the himax_load_config().
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_chip_init(struct himax_ts_data *ts)
+{
+ int ret;
+
+ if (himax_ts_register_interrupt(ts)) {
+ dev_err(ts->dev, "%s: register interrupt failed\n", __func__);
+ return -EIO;
+ }
+ himax_int_enable(ts, false);
+ ret = himax_load_config(ts);
+ if (ret < 0)
+ return ret;
+ ts->initialized = true;
+
+ return 0;
+}
+
+/**
+ * himax_platform_deinit() - Deinitialize the platform related settings
+ * @ts: Pointer to the himax_ts_data structure
+ *
+ * This function deinitializes the platform related settings, frees the
+ * xfer_buf.
+ *
+ * Return: None
+ */
+static void himax_platform_deinit(struct himax_ts_data *ts)
+{
+ if (ts->xfer_buf_sz) {
+ kfree(ts->xfer_buf);
+ ts->xfer_buf = NULL;
+ ts->xfer_buf_sz = 0;
+ }
+}
+
+/**
+ * himax_platform_init - Initialize the platform related settings
+ * @ts: Pointer to the himax_ts_data structure
+ *
+ * This function initializes the platform related settings.
+ * The xfer_buf is used for interrupt data receive. gpio reset pin is set to
+ * active and then deactivate to reset the IC.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_platform_init(struct himax_ts_data *ts)
+{
+ ts->xfer_buf_sz = 0;
+ ts->xfer_buf = kzalloc(HIMAX_HX83102J_FULL_STACK_SZ, GFP_KERNEL);
+ if (!ts->xfer_buf)
+ return -ENOMEM;
+ ts->xfer_buf_sz = HIMAX_HX83102J_FULL_STACK_SZ;
+
+ hx83102j_pin_reset(ts);
+
+ return 0;
+}
+
+/**
+ * himax_spi_drv_probe - Probe function for the SPI driver
+ * @spi: Pointer to the spi_device structure
+ *
+ * This function is called when the SPI driver is probed. It initializes the
+ * himax_ts_data structure and assign the settings from spi device to
+ * himax_ts_data. The buffer for SPI transfer is allocate here. The SPI
+ * transfer settings also setup before any communication starts.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_spi_drv_probe(struct spi_device *spi)
+{
+ int ret;
+ struct himax_ts_data *ts;
+ static struct himax_platform_data *pdata;
+
+ dev_info(&spi->dev, "%s: Himax SPI driver probe\n", __func__);
+ ts = devm_kzalloc(&spi->dev, sizeof(struct himax_ts_data), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+ if (spi->master->flags & SPI_MASTER_HALF_DUPLEX) {
+ dev_err(ts->dev, "%s: Full duplex not supported by host\n", __func__);
+ return -EIO;
+ }
+ pdata = &ts->pdata;
+ ts->dev = &spi->dev;
+ if (!spi->irq) {
+ dev_err(ts->dev, "%s: no IRQ?\n", __func__);
+ return -EINVAL;
+ }
+ ts->himax_irq = spi->irq;
+ pdata->gpiod_rst = devm_gpiod_get(ts->dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(pdata->gpiod_rst)) {
+ dev_err(ts->dev, "%s: gpio-rst value is not valid\n", __func__);
+ return -EIO;
+ }
+
+ spi->bits_per_word = 8;
+ spi->mode = SPI_MODE_3;
+ spi->cs_setup.value = HIMAX_SPI_CS_SETUP_TIME;
+
+ ts->spi = spi;
+ /*
+ * The max_transfer_size is used to allocate the buffer for SPI transfer.
+ * The size should be given by the SPI master driver, but if not available
+ * then use the HIMAX_MAX_TP_EV_STACK_SZ as default. Which is the least size for
+ * each TP event data.
+ */
+ if (spi->master->max_transfer_size)
+ ts->spi_xfer_max_sz = spi->master->max_transfer_size(spi);
+ else
+ ts->spi_xfer_max_sz = HIMAX_MAX_TP_EV_STACK_SZ;
+
+ ts->spi_xfer_max_sz = min(ts->spi_xfer_max_sz, HIMAX_BUS_RW_MAX_LEN);
+ /* SPI full-duplex rx_buf and tx_buf should be equal */
+ ts->xfer_rx_data = devm_kzalloc(ts->dev, ts->spi_xfer_max_sz, GFP_KERNEL);
+ if (!ts->xfer_rx_data)
+ return -ENOMEM;
+
+ ts->xfer_tx_data = devm_kzalloc(ts->dev, ts->spi_xfer_max_sz, GFP_KERNEL);
+ if (!ts->xfer_tx_data)
+ return -ENOMEM;
+
+ spin_lock_init(&ts->irq_lock);
+ mutex_init(&ts->rw_lock);
+ mutex_init(&ts->reg_lock);
+ dev_set_drvdata(&spi->dev, ts);
+ spi_set_drvdata(spi, ts);
+
+ ts->probe_finish = false;
+ ts->initialized = false;
+ ts->ic_boot_done = false;
+
+ ret = himax_platform_init(ts);
+ if (ret) {
+ dev_err(ts->dev, "%s: platform init failed\n", __func__);
+ return ret;
+ }
+
+ ret = hx83102j_chip_detect(ts);
+ if (ret) {
+ dev_err(ts->dev, "%s: IC detect failed\n", __func__);
+ return ret;
+ }
+
+ ret = himax_chip_init(ts);
+ if (ret < 0)
+ return ret;
+ ts->probe_finish = true;
+
+ return ret;
+ himax_platform_deinit(ts);
+}
+
+/**
+ * himax_spi_drv_remove - Remove function for the SPI driver
+ * @spi: Pointer to the spi_device structure
+ *
+ * This function is called when the SPI driver is removed. It deinitializes the
+ * himax_ts_data structure and free the resources allocated for the SPI
+ * communication.
+ */
+static void himax_spi_drv_remove(struct spi_device *spi)
+{
+ struct himax_ts_data *ts = spi_get_drvdata(spi);
+
+ if (ts->probe_finish) {
+ if (ts->ic_boot_done) {
+ himax_int_enable(ts, false);
+
+ if (ts->hid_probed)
+ himax_hid_remove(ts);
+ }
+ himax_platform_deinit(ts);
+ }
+}
+
+/**
+ * himax_shutdown - Shutdown the touch screen
+ * @spi: Himax touch screen spi device
+ *
+ * This function is used to shutdown the touch screen. It will disable the
+ * interrupt, set the reset pin to activate state. Then remove the hid device.
+ *
+ * Return: None
+ */
+static void himax_shutdown(struct spi_device *spi)
+{
+ struct himax_ts_data *ts = spi_get_drvdata(spi);
+
+ if (!ts->initialized) {
+ dev_err(ts->dev, "%s: init not ready, skip!\n", __func__);
+ return;
+ }
+
+ himax_int_enable(ts, false);
+ gpiod_set_value(ts->pdata.gpiod_rst, 1);
+ himax_hid_remove(ts);
+}
+
+#if defined(CONFIG_OF)
+static const struct of_device_id himax_table[] = {
+ { .compatible = "himax,hx83102j" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, himax_table);
+#endif
+
+static struct spi_driver himax_hid_over_spi_driver = {
+ .driver = {
+ .name = "hx83102j",
+ .owner = THIS_MODULE,
+#if defined(CONFIG_OF)
+ .of_match_table = of_match_ptr(himax_table),
+#endif
+ },
+ .probe = himax_spi_drv_probe,
+ .remove = himax_spi_drv_remove,
+ .shutdown = himax_shutdown,
+};
+
+static int __init himax_ic_init(void)
+{
+ return spi_register_driver(&himax_hid_over_spi_driver);
+}
+
+static void __exit himax_ic_exit(void)
+{
+ spi_unregister_driver(&himax_hid_over_spi_driver);
+}
+
+module_init(himax_ic_init);
+module_exit(himax_ic_exit);
+
+MODULE_VERSION("1.3.4");
+MODULE_DESCRIPTION("Himax HX83102J SPI driver for HID");
+MODULE_AUTHOR("Himax, Inc.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-himax-83102j.h b/drivers/hid/hid-himax-83102j.h
new file mode 100644
index 000000000000..1b8e8904e9ab
--- /dev/null
+++ b/drivers/hid/hid-himax-83102j.h
@@ -0,0 +1,286 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Himax hx83102j SPI Driver Code for HID.
+ *
+ * Copyright (C) 2024 Himax Corporation.
+ */
+
+#ifndef __HID_HIMAX_83102J_H__
+#define __HID_HIMAX_83102J_H__
+// #define HX_PWR_CONFIG
+
+#include <linux/delay.h>
+#include <linux/hid.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/spi/spi.h>
+
+/* Constants */
+#define HIMAX_BUS_RETRY 3
+/* SPI bus read header length */
+#define HIMAX_BUS_R_HLEN 3U
+/* SPI bus write header length */
+#define HIMAX_BUS_W_HLEN 2U
+/* TP SRAM address size and data size */
+#define HIMAX_REG_SZ 4U
+/* TP MAX TX/RX number */
+#define HIMAX_MAX_RX 144U
+#define HIMAX_MAX_TX 48U
+/* TP FW main code size */
+#define HIMAX_FW_MAIN_CODE_SZ 0x20000U
+/* TP max event data size */
+#define HIMAX_MAX_TP_EVENT_SZ 128U
+/* TP max interrupt data size : event size + raw data size(rx * tx 16bits) */
+#define HIMAX_MAX_TP_EV_STACK_SZ (HIMAX_BUS_R_HLEN + \
+ HIMAX_MAX_TP_EVENT_SZ + \
+ (HIMAX_MAX_RX * HIMAX_MAX_TX) * 2)
+/* SPI Max read/write size for max possible usage, write FW main code */
+#define HIMAX_BUS_RW_MAX_LEN (HIMAX_FW_MAIN_CODE_SZ + \
+ HIMAX_BUS_W_HLEN + HIMAX_REG_SZ)
+/* SPI CS setup time */
+#define HIMAX_SPI_CS_SETUP_TIME 300
+/* HIDRAW report header size */
+#define HIMAX_HID_REPORT_HDR_SZ 2U
+/* hx83102j IC parameters */
+#define HIMAX_HX83102J_DSRAM_SZ 73728U
+#define HIMAX_HX83102J_FLASH_SIZE 261120U
+#define HIMAX_HX83102J_MAX_RX_NUM 50U
+#define HIMAX_HX83102J_MAX_TX_NUM 32U
+#define HIMAX_HX83102J_PAGE_SIZE 128U
+#define HIMAX_HX83102J_SAFE_MODE_PASSWORD 0x9527
+#define HIMAX_HX83102J_STACK_SIZE 128U
+#define HIMAX_HX83102J_FULL_STACK_SZ \
+ (HIMAX_HX83102J_STACK_SIZE + \
+ (2 + HIMAX_HX83102J_MAX_RX_NUM * HIMAX_HX83102J_MAX_TX_NUM + \
+ HIMAX_HX83102J_MAX_TX_NUM + HIMAX_HX83102J_MAX_RX_NUM) * 2)
+#define HIMAX_HX83102J_REG_XFER_MAX 4096U
+/* AHB register addresses */
+#define HIMAX_AHB_ADDR_BYTE_0 0x00
+#define HIMAX_AHB_ADDR_RDATA_BYTE_0 0x08
+#define HIMAX_AHB_ADDR_ACCESS_DIRECTION 0x0c
+#define HIMAX_AHB_ADDR_INCR4 0x0d
+#define HIMAX_AHB_ADDR_CONTI 0x13
+#define HIMAX_AHB_ADDR_EVENT_STACK 0x30
+#define HIMAX_AHB_ADDR_PSW_LB 0x31
+/* AHB register values/commands */
+#define HIMAX_AHB_CMD_ACCESS_DIRECTION_READ 0x00
+#define HIMAX_AHB_CMD_CONTI 0x31
+#define HIMAX_AHB_CMD_INCR4 0x10
+#define HIMAX_AHB_CMD_INCR4_ADD_4_BYTE 0x01
+#define HIMAX_AHB_CMD_LEAVE_SAFE_MODE 0x0000
+/* hx83102j-specific register/dsram flags/data */
+#define HIMAX_HX83102J_REG_ADDR_TCON_RST 0x80020004
+/* hardware register addresses */
+#define HIMAX_REG_ADDR_SPI200_DATA 0x8000002c
+#define HIMAX_REG_ADDR_CTRL_FW 0x9000005c
+#define HIMAX_REG_ADDR_FW_STATUS 0x900000a8
+#define HIMAX_REG_ADDR_ICID 0x900000d0
+/* hardware reg data/flags */
+#define HIMAX_REG_DATA_FW_STATE_RUNNING 0x05
+#define HIMAX_REG_DATA_FW_STATE_SAFE_MODE 0x0c
+#define HIMAX_REG_DATA_FW_RE_INIT 0x00
+#define HIMAX_REG_DATA_FW_GO_SAFEMODE 0xa5
+#define HIMAX_REG_DATA_FW_IN_SAFEMODE 0x87
+#define HIMAX_REG_DATA_ICID 0x83102900
+#define HIMAX_REG_DATA_TCON_RST 0x00000000
+/* HIMAX SPI function select, 1st byte of any SPI command sequence */
+#define HIMAX_SPI_FUNCTION_READ 0xf3
+#define HIMAX_SPI_FUNCTION_WRITE 0xf2
+/* Map code of FW 1k header */
+#define HIMAX_TP_CONFIG_TABLE 0x00000a00
+#define HIMAX_FW_CID 0x10000000
+#define HIMAX_FW_VER 0x10000100
+#define HIMAX_CFG_VER 0x10000600
+#define HIMAX_HID_TABLE 0x30000100
+#define HIMAX_FW_BIN_DESC 0x10000000
+
+/**
+ * enum himax_hidraw_id_function - HIDRAW report IDs
+ * @HIMAX_ID_CONTACT_COUNT: Contact count report ID
+ */
+enum himax_hidraw_id_function {
+ HIMAX_ID_CONTACT_COUNT = 0x03,
+};
+
+/**
+ * enum himax_touch_report_status - ts operation return code for touch report
+ * @HIMAX_TS_GET_DATA_FAIL: Get touch data fail
+ * @HIMAX_TS_SUCCESS: Get touch data success
+ */
+enum himax_touch_report_status {
+ HIMAX_TS_GET_DATA_FAIL = -4,
+ HIMAX_TS_SUCCESS = 0,
+};
+
+/**
+ * struct himax_fw_address_table - address/offset in firmware image
+ * @addr_fw_ver_major: Address to Major version of firmware
+ * @addr_fw_ver_minor: Address to Minor version of firmware
+ * @addr_cfg_ver_major: Address to Major version of config
+ * @addr_cfg_ver_minor: Address to Minor version of config
+ * @addr_cid_ver_major: Address to Major version of Customer ID
+ * @addr_cid_ver_minor: Address to Minor version of Customer ID
+ * @addr_cfg_table: Address to Configuration table
+ * @addr_hid_table: Address to HID tables start offset
+ * @addr_hid_desc: Address to HID descriptor table
+ * @addr_hid_rd_desc: Address to HID report descriptor table
+ */
+struct himax_fw_address_table {
+ u32 addr_fw_ver_major;
+ u32 addr_fw_ver_minor;
+ u32 addr_cfg_ver_major;
+ u32 addr_cfg_ver_minor;
+ u32 addr_cid_ver_major;
+ u32 addr_cid_ver_minor;
+ u32 addr_cfg_table;
+ u32 addr_hid_table;
+ u32 addr_hid_desc;
+ u32 addr_hid_rd_desc;
+};
+
+/**
+ * struct himax_hid_rd_data - HID report descriptor data
+ * @rd_data: Point to report description data
+ * @rd_length: Length of report description data
+ */
+struct himax_hid_rd_data {
+ u8 *rd_data;
+ u32 rd_length;
+};
+
+/**
+ * union himax_dword_data - 4 bytes data union
+ * @dword: 1 dword data
+ * @word: 2 words data in word array
+ * @byte: 4 bytes data in byte array
+ */
+union himax_dword_data {
+ u32 dword;
+ u16 word[2];
+ u8 byte[4];
+};
+
+/**
+ * struct himax_ic_data - IC information holder
+ * @stylus_ratio: Stylus ratio
+ * @rx_num: Number of RX
+ * @tx_num: Number of TX
+ * @button_num: Number of buttons
+ * @max_point: Maximum touch point
+ * @icid: IC ID
+ * @interrupt_is_edge: Interrupt is edge otherwise level
+ * @stylus_function: Stylus function available or not
+ * @stylus_v2: Is stylus version 2
+ */
+struct himax_ic_data {
+ u8 stylus_ratio;
+ u32 rx_num;
+ u32 tx_num;
+ u32 button_num;
+ u32 max_point;
+ u32 icid;
+ bool interrupt_is_edge;
+ bool stylus_function;
+ bool stylus_v2;
+};
+
+/**
+ * struct himax_hid_desc - HID descriptor
+ * @desc_length: Length of HID descriptor
+ * @bcd_version: BCD version
+ * @report_desc_length: Length of report descriptor
+ * @max_input_length: Maximum input length
+ * @max_output_length: Maximum output length
+ * @max_fragment_length: Maximum fragment length
+ * @vendor_id: Vendor ID
+ * @product_id: Product ID
+ * @version_id: Version ID
+ * @flags: Flags
+ * @reserved: Reserved
+ *
+ * This structure is used to hold the HID descriptor.
+ * It directly maps to a sequence of bytes in firmware image,
+ * thus need to be packed.
+ */
+struct himax_hid_desc {
+ u16 desc_length;
+ u16 bcd_version;
+ u16 report_desc_length;
+ u16 max_input_length;
+ u16 max_output_length;
+ u16 max_fragment_length;
+ u16 vendor_id;
+ u16 product_id;
+ u16 version_id;
+ u16 flags;
+ u32 reserved;
+} __packed;
+
+/**
+ * struct himax_platform_data - Platform data holder
+ * @gpiod_rst: GPIO reset
+ *
+ * This structure is used to hold the platform related data.
+ */
+struct himax_platform_data {
+ struct gpio_desc *gpiod_rst;
+};
+
+/**
+ * struct himax_ts_data - Touchscreen data holder
+ * @xfer_buf: Interrupt data buffer
+ * @xfer_rx_data: SPI Transfer receive data buffer
+ * @xfer_tx_data: SPI Transfer transmit data buffer
+ * @himax_fw_data: Firmware data holder from flash
+ * @himax_irq: IRQ number
+ * @spi_xfer_max_sz: Size of SPI controller max transfer size
+ * @xfer_buf_sz: Size of interrupt data buffer
+ * @irq_state: IRQ state
+ * @irq_lock: Spin lock for irq
+ * @initialized: Indicate the driver is initialized
+ * @probe_finish: Indicate the driver probe is finished
+ * @ic_boot_done: Indicate the IC boot is done
+ * @hid_probed: Indicate the HID device is probed
+ * @touch_data_sz: Size of each interrupt data from IC
+ * @dev: Device pointer
+ * @spi: SPI device pointer
+ * @hid: HID device pointer
+ * @reg_lock: Mutex lock for reg access
+ * @rw_lock: Mutex lock for read/write action
+ * @ic_data: IC information holder
+ * @pdata: Platform data holder
+ * @fw_info_table: Firmware information address table of firmware image
+ * @hid_desc: HID descriptor
+ * @hid_rd_data: HID report descriptor data
+ */
+struct himax_ts_data {
+ u8 *xfer_buf;
+ u8 *xfer_rx_data;
+ u8 *xfer_tx_data;
+ u8 *himax_fw_data;
+ s32 himax_irq;
+ u32 spi_xfer_max_sz;
+ u32 xfer_buf_sz;
+ atomic_t irq_state;
+ /* lock for irq_save */
+ spinlock_t irq_lock;
+ bool initialized;
+ bool probe_finish;
+ bool ic_boot_done;
+ bool hid_probed;
+ int touch_data_sz;
+ struct device *dev;
+ struct spi_device *spi;
+ struct hid_device *hid;
+ /* lock for register operation */
+ struct mutex reg_lock;
+ /* lock for bus read/write action */
+ struct mutex rw_lock;
+ struct himax_ic_data ic_data;
+ struct himax_platform_data pdata;
+ struct himax_fw_address_table fw_info_table;
+ struct himax_hid_desc hid_desc;
+ struct himax_hid_rd_data hid_rd_data;
+};
+#endif
--
2.34.1
^ permalink raw reply related
* [PATCH v2 3/4] HID: Add DRM panel follower function
From: Allen_Lin @ 2024-04-02 10:49 UTC (permalink / raw)
To: dmitry.torokhov, robh, krzysztof.kozlowski+dt, conor, jikos,
benjamin.tissoires, linux-input, devicetree, linux-kernel
Cc: Allen_Lin
In-Reply-To: <20240402104930.1053016-1-allencl_lin@hotmail.com>
Add DRM panel follower[1] to trigger suspend/resume due to TDDI nature,
TP need to follow panel power sequence.
[1]:
https://lore.kernel.org/all/20230727171750.633410-1-dianders@chromium.org
Signed-off-by: Allen_Lin <allencl_lin@hotmail.com>
---
drivers/hid/hid-himax-83102j.c | 289 +++++++++++++++++++++++++++++++--
drivers/hid/hid-himax-83102j.h | 10 ++
2 files changed, 283 insertions(+), 16 deletions(-)
diff --git a/drivers/hid/hid-himax-83102j.c b/drivers/hid/hid-himax-83102j.c
index aa8d0b6677bb..0a2be071a6c4 100644
--- a/drivers/hid/hid-himax-83102j.c
+++ b/drivers/hid/hid-himax-83102j.c
@@ -8,6 +8,7 @@
#include "hid-himax-83102j.h"
static int himax_chip_init(struct himax_ts_data *ts);
+static int himax_platform_init(struct himax_ts_data *ts);
static void himax_ts_work(struct himax_ts_data *ts);
/**
@@ -1135,7 +1136,6 @@ static int himax_hid_probe(struct himax_ts_data *ts)
hid = ts->hid;
if (hid) {
- dev_warn(ts->dev, "%s: hid device already exist!\n", __func__);
hid_destroy_device(hid);
hid = NULL;
}
@@ -1443,6 +1443,151 @@ static int himax_load_config(struct himax_ts_data *ts)
return -EINVAL;
}
+/**
+ * himax_ap_notify_fw_suspend() - Notify the FW of AP suspend status
+ * @ts: Himax touch screen data
+ * @suspend: Suspend status, true for suspend, false for resume
+ *
+ * This function is used to notify the FW of AP suspend status. It will write
+ * the suspend status to the DSRAM and read the status back to check if the
+ * status is written successfully. If IC is powered off when suspend, this
+ * function will only be used when resume.
+ *
+ * Return: None
+ */
+static void himax_ap_notify_fw_suspend(struct himax_ts_data *ts, bool suspend)
+{
+ int ret;
+ u32 retry_cnt;
+ const u32 retry_limit = 10;
+ union himax_dword_data rdata, data;
+
+ if (suspend)
+ data.dword = cpu_to_le32(HIMAX_DSRAM_DATA_AP_NOTIFY_FW_SUSPEND);
+ else
+ data.dword = cpu_to_le32(HIMAX_DSRAM_DATA_AP_NOTIFY_FW_RESUME);
+
+ for (retry_cnt = 0; retry_cnt < retry_limit; retry_cnt++) {
+ ret = himax_mcu_register_write(ts, HIMAX_DSRAM_ADDR_AP_NOTIFY_FW_SUSPEND,
+ data.byte, 4);
+ if (ret) {
+ dev_err(ts->dev, "%s: write suspend status failed!\n", __func__);
+ return;
+ }
+ usleep_range(1000, 1100);
+ ret = himax_mcu_register_read(ts, HIMAX_DSRAM_ADDR_AP_NOTIFY_FW_SUSPEND,
+ rdata.byte, 4);
+ if (ret) {
+ dev_err(ts->dev, "%s: read suspend status failed!\n", __func__);
+ return;
+ }
+
+ if (rdata.dword == data.dword)
+ break;
+ }
+}
+
+/**
+ * himax_resume_proc() - Chip resume procedure of touch screen
+ * @ts: Himax touch screen data
+ *
+ * This function is used to resume the touch screen. It will call the
+ * himax_ap_notify_fw_suspend() to notify the FW of AP resume status.
+ *
+ * Return: None
+ */
+static void himax_resume_proc(struct himax_ts_data *ts)
+{
+ himax_ap_notify_fw_suspend(ts, false);
+}
+
+/**
+ * himax_chip_suspend() - Suspend the touch screen
+ * @ts: Himax touch screen data
+ *
+ * This function is used to suspend the touch screen. It will disable the
+ * interrupt and set the reset pin to activate state. Remove the HID at
+ * the end, to prevent stuck finger when resume.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_chip_suspend(struct himax_ts_data *ts)
+{
+ himax_int_enable(ts, false);
+ gpiod_set_value(ts->pdata.gpiod_rst, 1);
+ himax_hid_remove(ts);
+
+ return 0;
+}
+
+/**
+ * himax_chip_resume() - Setup flags, I/O and resume
+ * @ts: Himax touch screen data
+ *
+ * This function is used to resume the touch screen. It will set the resume
+ * success flag to false, and disable reset pin. Then call the himax_resume_proc()
+ * to process detailed resume procedure.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_chip_resume(struct himax_ts_data *ts)
+{
+ gpiod_set_value(ts->pdata.gpiod_rst, 0);
+ himax_resume_proc(ts);
+ himax_hid_probe(ts);
+ himax_int_enable(ts, true);
+
+ return 0;
+}
+
+/**
+ * himax_suspend() - Suspend the touch screen
+ * @dev: Device structure
+ *
+ * Wrapper function for himax_chip_suspend() to be called by the PM or
+ * the DRM panel notifier.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_suspend(struct device *dev)
+{
+ struct himax_ts_data *ts = dev_get_drvdata(dev);
+
+ if (!ts->initialized) {
+ dev_err(ts->dev, "%s: init not ready, skip!\n", __func__);
+ return -ECANCELED;
+ }
+ himax_chip_suspend(ts);
+
+ return 0;
+}
+
+/**
+ * himax_resume() - Resume the touch screen
+ * @dev: Device structure
+ *
+ * Wrapper function for himax_chip_resume() to be called by the PM or
+ * the DRM panel notifier.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_resume(struct device *dev)
+{
+ int ret;
+ struct himax_ts_data *ts = dev_get_drvdata(dev);
+
+ if (!ts->initialized) {
+ if (himax_chip_init(ts))
+ return -ECANCELED;
+ }
+
+ ret = himax_chip_resume(ts);
+ if (ret < 0)
+ dev_err(ts->dev, "%s: resume failed!\n", __func__);
+
+ return ret;
+}
+
/**
* himax_chip_init() - Initialize the Himax touch screen
* @ts: Himax touch screen data
@@ -1473,6 +1618,130 @@ static int himax_chip_init(struct himax_ts_data *ts)
return 0;
}
+/**
+ * __himax_initial_power_up() - Initial power up of the Himax touch screen
+ * @ts: Himax touch screen data
+ *
+ * This function is used to perform the initial power up sequence of the Himax
+ * touch screen for DRM panel notifier.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int __himax_initial_power_up(struct himax_ts_data *ts)
+{
+ int ret;
+
+ ret = himax_platform_init(ts);
+ if (ret) {
+ dev_err(ts->dev, "%s: platform init failed\n", __func__);
+ return ret;
+ }
+
+ ret = hx83102j_chip_detect(ts);
+ if (ret) {
+ dev_err(ts->dev, "%s: IC detect failed\n", __func__);
+ return ret;
+ }
+
+ ret = himax_chip_init(ts);
+ if (ret) {
+ dev_err(ts->dev, "%s: chip init failed\n", __func__);
+ return ret;
+ }
+ ts->probe_finish = true;
+
+ return 0;
+}
+
+/**
+ * himax_panel_prepared() - Panel prepared callback
+ * @follower: DRM panel follower
+ *
+ * This function is called when the panel is prepared. It will call the
+ * __himax_initial_power_up() when the probe is not finished which means
+ * the first time driver start. Otherwise, it will call the himax_resume()
+ * to performed resume process.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_panel_prepared(struct drm_panel_follower *follower)
+{
+ struct himax_platform_data *pdata =
+ container_of(follower, struct himax_platform_data, panel_follower);
+ struct himax_ts_data *ts = container_of(pdata, struct himax_ts_data, pdata);
+
+ if (!ts->probe_finish)
+ return __himax_initial_power_up(ts);
+ else
+ return himax_resume(ts->dev);
+}
+
+/**
+ * himax_panel_unpreparing() - Panel unpreparing callback
+ * @follower: DRM panel follower
+ *
+ * This function is called when the panel is unpreparing. It will call the
+ * himax_suspend() to perform the suspend process.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_panel_unpreparing(struct drm_panel_follower *follower)
+{
+ struct himax_platform_data *pdata =
+ container_of(follower, struct himax_platform_data, panel_follower);
+ struct himax_ts_data *ts = container_of(pdata, struct himax_ts_data, pdata);
+
+ return himax_suspend(ts->dev);
+}
+
+/* Panel follower function table */
+static const struct drm_panel_follower_funcs himax_panel_follower_funcs = {
+ .panel_prepared = himax_panel_prepared,
+ .panel_unpreparing = himax_panel_unpreparing,
+};
+
+/**
+ * himax_register_panel_follower() - Register the panel follower
+ * @ts: Himax touch screen data
+ *
+ * This function is used to register the panel follower. It will set the
+ * pdata.is_panel_follower to true and register the panel follower.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_register_panel_follower(struct himax_ts_data *ts)
+{
+ struct device *dev = ts->dev;
+
+ ts->pdata.is_panel_follower = true;
+ ts->pdata.panel_follower.funcs = &himax_panel_follower_funcs;
+
+ if (device_can_wakeup(dev)) {
+ dev_warn(ts->dev, "Can't wakeup if following panel");
+ device_set_wakeup_capable(dev, false);
+ }
+
+ return drm_panel_add_follower(dev, &ts->pdata.panel_follower);
+}
+
+/**
+ * himax_initial_power_up() - Initial power up of the Himax touch screen
+ * @ts: Himax touch screen data
+ *
+ * This function checks if the device is a panel follower and calls
+ * himax_register_panel_follower() if it is. Otherwise, it calls
+ * __himax_initial_power_up().
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_initial_power_up(struct himax_ts_data *ts)
+{
+ if (drm_is_panel_follower(ts->dev))
+ return himax_register_panel_follower(ts);
+ else
+ return __himax_initial_power_up(ts);
+}
+
/**
* himax_platform_deinit() - Deinitialize the platform related settings
* @ts: Pointer to the himax_ts_data structure
@@ -1588,25 +1857,13 @@ static int himax_spi_drv_probe(struct spi_device *spi)
ts->initialized = false;
ts->ic_boot_done = false;
- ret = himax_platform_init(ts);
- if (ret) {
- dev_err(ts->dev, "%s: platform init failed\n", __func__);
- return ret;
- }
-
- ret = hx83102j_chip_detect(ts);
+ ret = himax_initial_power_up(ts);
if (ret) {
- dev_err(ts->dev, "%s: IC detect failed\n", __func__);
- return ret;
+ dev_err(ts->dev, "%s: initial power up failed\n", __func__);
+ return -ENODEV;
}
- ret = himax_chip_init(ts);
- if (ret < 0)
- return ret;
- ts->probe_finish = true;
-
return ret;
- himax_platform_deinit(ts);
}
/**
diff --git a/drivers/hid/hid-himax-83102j.h b/drivers/hid/hid-himax-83102j.h
index 1b8e8904e9ab..eef55c45b1d4 100644
--- a/drivers/hid/hid-himax-83102j.h
+++ b/drivers/hid/hid-himax-83102j.h
@@ -9,6 +9,7 @@
#define __HID_HIMAX_83102J_H__
// #define HX_PWR_CONFIG
+#include <drm/drm_panel.h>
#include <linux/delay.h>
#include <linux/hid.h>
#include <linux/interrupt.h>
@@ -69,6 +70,11 @@
#define HIMAX_AHB_CMD_INCR4 0x10
#define HIMAX_AHB_CMD_INCR4_ADD_4_BYTE 0x01
#define HIMAX_AHB_CMD_LEAVE_SAFE_MODE 0x0000
+/* DSRAM flag addresses */
+#define HIMAX_DSRAM_ADDR_AP_NOTIFY_FW_SUSPEND 0x10007fd0
+/* dsram flag data */
+#define HIMAX_DSRAM_DATA_AP_NOTIFY_FW_SUSPEND 0xa55aa55a
+#define HIMAX_DSRAM_DATA_AP_NOTIFY_FW_RESUME 0x00000000
/* hx83102j-specific register/dsram flags/data */
#define HIMAX_HX83102J_REG_ADDR_TCON_RST 0x80020004
/* hardware register addresses */
@@ -219,11 +225,15 @@ struct himax_hid_desc {
/**
* struct himax_platform_data - Platform data holder
+ * @is_panel_follower: Is panel follower enabled
+ * @panel_follower: DRM panel follower
* @gpiod_rst: GPIO reset
*
* This structure is used to hold the platform related data.
*/
struct himax_platform_data {
+ bool is_panel_follower;
+ struct drm_panel_follower panel_follower;
struct gpio_desc *gpiod_rst;
};
--
2.34.1
^ permalink raw reply related
* [PATCH v2 4/4] HID: Load firmware directly from file to IC
From: Allen_Lin @ 2024-04-02 10:49 UTC (permalink / raw)
To: dmitry.torokhov, robh, krzysztof.kozlowski+dt, conor, jikos,
benjamin.tissoires, linux-input, devicetree, linux-kernel
Cc: Allen_Lin
In-Reply-To: <20240402104930.1053016-1-allencl_lin@hotmail.com>
For HX83102J, often shipped without flash for costdown purpose.
This patch implement the function to load firmware file from
user-space, write into IC sram and make it running.
The procedure as following:
1. Check OF to get FW PID specified: himax_parse_dt
2. Load PID specified FW: i_get_FW, upload process use workqueue
himax_boot_upgrade
3. Write FW into IC sram: i_update_FW
3-1. FW file contain a mapping table to indicate various section,
such code, data, version info and HID info
3-2. Call update process himax_mcu_firmware_update_0f, which stop
the internal MCU: i. system reset. ii. hx83102j_sense_off
3-3. Parsing FW and upload file: himax_zf_part_info. info[0] contain
the code section and will upload to ISRAM. Other info[x] are
data sections will be combined and upload to DSRAM separately.
After upload FW, driver will use crc to check integrity:
himax_sram_write_crc_check for code(HW CRC result must be 0),
himax_mcu_calculate_crc_with_ap for pre-calculate data CRC[1],
Upload data section by himax_mcu_register_write,
himax_mcu_check_crc use HW calculate CRC and compared with [1]
4. Disable HW reload FW from flash function before MCU start running:
himax_disable_fw_reload
5. Start running the FW: himax_mcu_power_on_init
5-1. Setting varies FW settings before FW start running
5-2. Start FW: hx83102j_sense_on
5-3. Wait until FW initial process complete: Read 0x72C0 from IC
6. Read HID info from FW
7. Read TP properties from IC: himax_mcu_tp_info_check
8. Read FW information fromIC: himax_mcu_read_FW_ver
9. Release FW, IC initial complete
The resume procedure do part of boot process, it also need to reload
FW from user-space into IC sram: himax_0f_op_file_dirly
1. Request FW
2. Upload FW: himax_mcu_firmware_update_0f
3. Disable HW reload FW from flash function: himax_disable_fw_reload
4. Start running the FW: himax_mcu_power_on_init
Signed-off-by: Allen_Lin <allencl_lin@hotmail.com>
---
drivers/hid/hid-himax-83102j.c | 1515 +++++++++++++++++++++++++++-----
drivers/hid/hid-himax-83102j.h | 168 +++-
2 files changed, 1483 insertions(+), 200 deletions(-)
diff --git a/drivers/hid/hid-himax-83102j.c b/drivers/hid/hid-himax-83102j.c
index 0a2be071a6c4..5cae979a3052 100644
--- a/drivers/hid/hid-himax-83102j.c
+++ b/drivers/hid/hid-himax-83102j.c
@@ -532,6 +532,92 @@ static void himax_mcu_ic_reset(struct himax_ts_data *ts, bool int_off)
himax_int_enable(ts, true);
}
+/**
+ * hx83102j_reload_to_active() - Reload to active mode
+ * @ts: Himax touch screen data
+ *
+ * This function is used to write a flag to the IC register to make MCU restart without
+ * reload the firmware.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int hx83102j_reload_to_active(struct himax_ts_data *ts)
+{
+ int ret;
+ u32 retry_cnt;
+ const u32 addr = HIMAX_REG_ADDR_RELOAD_TO_ACTIVE;
+ const u32 reload_to_active_cmd = 0xec;
+ const u32 reload_to_active_done = 0x01ec;
+ const u32 retry_limit = 5;
+ union himax_dword_data data;
+
+ for (retry_cnt = 0; retry_cnt < retry_limit; retry_cnt++) {
+ data.dword = cpu_to_le32(reload_to_active_cmd);
+ ret = himax_mcu_register_write(ts, addr, data.byte, 4);
+ if (ret < 0)
+ return ret;
+ usleep_range(1000, 1100);
+ ret = himax_mcu_register_read(ts, addr, data.byte, 4);
+ if (ret < 0)
+ return ret;
+ data.dword = le32_to_cpu(data.dword);
+ if (data.word[0] == reload_to_active_done)
+ break;
+ }
+
+ if (data.word[0] != reload_to_active_done) {
+ dev_err(ts->dev, "%s: Reload to active failed!\n", __func__);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/**
+ * hx83102j_en_hw_crc() - Enable/Disable HW CRC
+ * @ts: Himax touch screen data
+ * @en: true for enable, false for disable
+ *
+ * This function is used to enable or disable the HW CRC. The HW CRC
+ * is used to protect the SRAM data.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int hx83102j_en_hw_crc(struct himax_ts_data *ts, bool en)
+{
+ int ret;
+ u32 retry_cnt;
+ const u32 addr = HIMAX_HX83102J_REG_ADDR_HW_CRC;
+ const u32 retry_limit = 5;
+ union himax_dword_data data, wrt_data;
+
+ if (en)
+ data.dword = cpu_to_le32(HIMAX_HX83102J_REG_DATA_HW_CRC);
+ else
+ data.dword = cpu_to_le32(HIMAX_HX83102J_REG_DATA_HW_CRC_DISABLE);
+
+ wrt_data.dword = data.dword;
+ for (retry_cnt = 0; retry_cnt < retry_limit; retry_cnt++) {
+ ret = himax_mcu_register_write(ts, addr, data.byte, 4);
+ if (ret < 0)
+ return ret;
+ usleep_range(1000, 1100);
+ ret = himax_mcu_register_read(ts, addr, data.byte, 4);
+ if (ret < 0)
+ return ret;
+
+ if (data.word[0] == wrt_data.word[0])
+ break;
+ }
+
+ if (data.word[0] != wrt_data.word[0]) {
+ dev_err(ts->dev, "%s: ECC fail!\n", __func__);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
/**
* hx83102j_sense_off() - Stop MCU and enter safe mode
* @ts: Himax touch screen data
@@ -641,6 +727,56 @@ static int hx83102j_sense_off(struct himax_ts_data *ts, bool check_en)
return -EIO;
}
+/**
+ * hx83102j_sense_on() - Sense on the touch chip
+ * @ts: Himax touch screen data
+ * @sw_reset: true for software reset, false for hardware reset
+ *
+ * This function is used to sense on the touch chip, which means to start running the
+ * FW. The process begin with wakeup the IC bus interface, then write a flag to the IC
+ * register to make MCU restart running the FW. When sw_reset is true, the function will
+ * send a command to the IC to leave safe mode. Otherwise, the function will call
+ * himax_mcu_ic_reset() to reset the touch chip by hardware pin.
+ * Then enable the HW CRC to protect sram data, and reload to active to make the MCU
+ * start running without reload the firmware.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int hx83102j_sense_on(struct himax_ts_data *ts, bool sw_reset)
+{
+ int ret;
+ const union himax_dword_data re_init = {
+ .dword = cpu_to_le32(HIMAX_REG_DATA_FW_RE_INIT)
+ };
+ union himax_dword_data data;
+
+ dev_info(ts->dev, "%s: software reset %s\n", __func__, sw_reset ? "true" : "false");
+ ret = himax_mcu_interface_on(ts);
+ if (ret < 0)
+ return ret;
+
+ ret = himax_mcu_register_write(ts, HIMAX_REG_ADDR_CTRL_FW, re_init.byte, 4);
+ if (ret < 0)
+ return ret;
+ usleep_range(10000, 11000);
+ if (!sw_reset) {
+ himax_mcu_ic_reset(ts, false);
+ } else {
+ data.word[0] = cpu_to_le16(HIMAX_AHB_CMD_LEAVE_SAFE_MODE);
+ ret = himax_write(ts, HIMAX_AHB_ADDR_PSW_LB, NULL, data.byte, 2);
+ if (ret < 0)
+ return ret;
+ }
+ ret = hx83102j_en_hw_crc(ts, true);
+ if (ret < 0)
+ return ret;
+ ret = hx83102j_reload_to_active(ts);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
/**
* hx83102j_chip_detect() - Check if the touch chip is HX83102J
* @ts: Himax touch screen data
@@ -783,166 +919,980 @@ static int hx83102j_read_event_stack(struct himax_ts_data *ts, u8 *buf, u32 leng
int ret;
const u32 max_trunk_sz = ts->spi_xfer_max_sz - HIMAX_BUS_R_HLEN;
- for (i = 0; i < length; i += max_trunk_sz) {
- ret = himax_read(ts, HIMAX_AHB_ADDR_EVENT_STACK, buf + i,
- min(length - i, max_trunk_sz));
+ for (i = 0; i < length; i += max_trunk_sz) {
+ ret = himax_read(ts, HIMAX_AHB_ADDR_EVENT_STACK, buf + i,
+ min(length - i, max_trunk_sz));
+ if (ret) {
+ dev_err(ts->dev, "%s: read event stack error!\n", __func__);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * hx83102j_chip_init_data() - Initialize the touch chip data
+ * @ts: Himax touch screen data
+ *
+ * This function is used to initialize hx83102j touch specific data in himax_ts_data.
+ * The chip_max_dsram_size is the maximum size of the DSRAM of hx83102j.
+ *
+ * Return: None
+ */
+static void hx83102j_chip_init_data(struct himax_ts_data *ts)
+{
+ ts->chip_max_dsram_size = HIMAX_HX83102J_DSRAM_SZ;
+}
+
+/**
+ * himax_touch_get() - Get touch data from touch chip
+ * @ts: Himax touch screen data
+ * @buf: Buffer to store the data
+ *
+ * This function is a wrapper to call hx83102j_read_event_stack() to read the touch
+ * data from the touch chip. The touch_data_sz is the size of the touch data to read,
+ * which is calculated by hid report descriptor provided by the firmware.
+ *
+ * Return: HIMAX_TS_SUCCESS on success, negative error code on failure. We categorize
+ * the error code into HIMAX_TS_GET_DATA_FAIL when the read fails, and HIMAX_TS_SUCCESS
+ * when the read is successful. The reason is that the may need special handling when
+ * the read fails.
+ */
+static int himax_touch_get(struct himax_ts_data *ts, u8 *buf)
+{
+ if (hx83102j_read_event_stack(ts, buf, ts->touch_data_sz)) {
+ dev_err(ts->dev, "can't read data from chip!");
+ return HIMAX_TS_GET_DATA_FAIL;
+ }
+
+ return HIMAX_TS_SUCCESS;
+}
+
+/**
+ * himax_mcu_assign_sorting_mode() - Write sorting mode to dsram and verify
+ * @ts: Himax touch screen data
+ * @tmp_data_in: password to write
+ *
+ * This function is used to write the sorting mode password to dsram and verify the
+ * password is written correctly. The sorting mode password is used as a flag to
+ * FW to let it know which mode the touch chip is working on.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_mcu_assign_sorting_mode(struct himax_ts_data *ts, u8 *tmp_data_in)
+{
+ int ret;
+ u8 rdata[4];
+ u32 retry_cnt;
+ const u32 retry_limit = 3;
+
+ for (retry_cnt = 0; retry_cnt < retry_limit; retry_cnt++) {
+ ret = himax_mcu_register_write(ts, HIMAX_DSRAM_ADDR_SORTING_MODE_EN,
+ tmp_data_in, HIMAX_REG_SZ);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: write sorting mode fail\n", __func__);
+ return ret;
+ }
+ usleep_range(1000, 1100);
+ ret = himax_mcu_register_read(ts, HIMAX_DSRAM_ADDR_SORTING_MODE_EN,
+ rdata, HIMAX_REG_SZ);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: read sorting mode fail\n", __func__);
+ return ret;
+ }
+
+ if (!memcmp(tmp_data_in, rdata, HIMAX_REG_SZ))
+ return 0;
+ }
+ dev_err(ts->dev, "%s: fail to write sorting mode\n", __func__);
+
+ return -EINVAL;
+}
+
+/**
+ * himax_mcu_read_FW_status() - Read FW status from touch chip
+ * @ts: Himax touch screen data
+ *
+ * This function is used to read the FW status from the touch chip. The FW status is
+ * values from dsram and register from TPIC. Which shows the FW vital working status
+ * for developer debug.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_mcu_read_FW_status(struct himax_ts_data *ts)
+{
+ int i;
+ int ret;
+ size_t len;
+ u8 data[4];
+ const char * const reg_name[] = {
+ "DBG_MSG",
+ "FW_STATUS",
+ "DD_STATUS",
+ "RESET_FLAG"
+ };
+ const u32 dbg_reg_array[] = {
+ HIMAX_DSRAM_ADDR_DBG_MSG,
+ HIMAX_REG_ADDR_FW_STATUS,
+ HIMAX_REG_ADDR_DD_STATUS,
+ HIMAX_REG_ADDR_RESET_FLAG
+ };
+
+ len = ARRAY_SIZE(dbg_reg_array);
+
+ for (i = 0; i < len; i++) {
+ ret = himax_mcu_register_read(ts, dbg_reg_array[i], data, HIMAX_REG_SZ);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: read FW status fail\n", __func__);
+ return ret;
+ }
+
+ dev_info(ts->dev, "%s: %10s(0x%08X) = 0x%02X, 0x%02X, 0x%02X, 0x%02X\n",
+ __func__, reg_name[i], dbg_reg_array[i],
+ data[0], data[1], data[2], data[3]);
+ }
+
+ return 0;
+}
+
+/**
+ * himax_mcu_power_on_init() - Power on initialization
+ * @ts: Himax touch screen data
+ *
+ * This function is used to do the power on initialization after firmware has been
+ * loaded to sram. The process initialize varies IC register and dsram to make sure
+ * FW start running correctly. When all set, sense on the touch chip to make the FW
+ * start running and wait for the FW reload done password.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_mcu_power_on_init(struct himax_ts_data *ts)
+{
+ int ret;
+ u32 retry_cnt;
+ const u32 retry_limit = 30;
+ union himax_dword_data data;
+
+ /* RawOut select initial */
+ data.dword = cpu_to_le32(HIMAX_DATA_CLEAR);
+ ret = himax_mcu_register_write(ts, HIMAX_HX83102J_DSRAM_ADDR_RAW_OUT_SEL, data.byte, 4);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: set RawOut select fail\n", __func__);
+ return ret;
+ }
+ /* Initial sorting mode password to normal mode */
+ ret = himax_mcu_assign_sorting_mode(ts, data.byte);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: assign sorting mode fail\n", __func__);
+ return ret;
+ }
+ /* N frame initial */
+ /* reset N frame back to default value 1 for normal mode */
+ data.dword = cpu_to_le32(1);
+ ret = himax_mcu_register_write(ts, HIMAX_DSRAM_ADDR_SET_NFRAME, data.byte, 4);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: set N frame fail\n", __func__);
+ return ret;
+ }
+ /* Initial FW reload status */
+ data.dword = cpu_to_le32(HIMAX_DATA_CLEAR);
+ ret = himax_mcu_register_write(ts, HIMAX_DSRAM_ADDR_2ND_FLASH_RELOAD, data.byte, 4);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: initial FW reload status fail\n", __func__);
+ return ret;
+ }
+
+ ret = hx83102j_sense_on(ts, false);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: sense on fail\n", __func__);
+ return ret;
+ }
+
+ dev_info(ts->dev, "%s: waiting for FW reload data\n", __func__);
+ for (retry_cnt = 0; retry_cnt < retry_limit; retry_cnt++) {
+ ret = himax_mcu_register_read(ts, HIMAX_DSRAM_ADDR_2ND_FLASH_RELOAD, data.byte, 4);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: read FW reload status fail\n", __func__);
+ return ret;
+ }
+
+ /* use all 4 bytes to compare */
+ if (le32_to_cpu(data.dword) == HIMAX_DSRAM_DATA_FW_RELOAD_DONE) {
+ dev_info(ts->dev, "%s: FW reload done\n", __func__);
+ break;
+ }
+ dev_info(ts->dev, "%s: wait FW reload %u times\n", __func__, retry_cnt);
+ ret = himax_mcu_read_FW_status(ts);
+ if (ret < 0)
+ dev_err(ts->dev, "%s: read FW status fail\n", __func__);
+
+ usleep_range(10000, 11000);
+ }
+
+ if (retry_cnt == retry_limit) {
+ dev_err(ts->dev, "%s: FW reload fail!\n", __func__);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/**
+ * himax_mcu_calculate_crc() - Calculate CRC-32 of given data
+ * @data: Data to calculate CRC
+ * @len: Length of data
+ *
+ * This function is used to calculate the CRC-32 of the given data. The function
+ * calculate the CRC-32 value by the polynomial 0x82f63b78.
+ *
+ * Return: CRC-32 value
+ */
+static u32 himax_mcu_calculate_crc(const u8 *data, int len)
+{
+ int i, j, length;
+ u32 crc = GENMASK(31, 0);
+ u32 current_data;
+ u32 tmp;
+ const u32 mask = GENMASK(30, 0);
+
+ length = len / 4;
+
+ for (i = 0; i < length; i++) {
+ current_data = data[i * 4];
+
+ for (j = 1; j < 4; j++) {
+ tmp = data[i * 4 + j];
+ current_data += (tmp) << (8 * j);
+ }
+ crc = current_data ^ crc;
+ for (j = 0; j < 32; j++) {
+ if ((crc % 2) != 0)
+ crc = ((crc >> 1) & mask) ^ CRC32C_POLY_LE;
+ else
+ crc = (((crc >> 1) & mask));
+ }
+ }
+
+ return crc;
+}
+
+/**
+ * himax_mcu_check_crc() - Let TPIC check CRC itself
+ * @ts: Himax touch screen data
+ * @start_addr: Start address of the data in sram to check
+ * @reload_length: Length of the data to check
+ * @crc_result: CRC result for return
+ *
+ * This function is used to let TPIC check the CRC of the given data in sram. The
+ * function write the start address and length of the data to the TPIC, and wait for
+ * the TPIC to finish the CRC check. When the CRC check is done, the function read
+ * the CRC result from the TPIC.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_mcu_check_crc(struct himax_ts_data *ts, u32 start_addr,
+ int reload_length, u32 *crc_result)
+{
+ int ret;
+ int length = reload_length / HIMAX_REG_SZ;
+ u32 retry_cnt;
+ const u32 retry_limit = 100;
+ union himax_dword_data data, addr;
+
+ addr.dword = cpu_to_le32(start_addr);
+ ret = himax_mcu_register_write(ts, HIMAX_REG_ADDR_RELOAD_ADDR_FROM, addr.byte, 4);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: write reload start address fail\n", __func__);
+ return ret;
+ }
+
+ data.word[1] = cpu_to_le16(HIMAX_REG_DATA_RELOAD_PASSWORD);
+ data.word[0] = cpu_to_le16(length);
+ ret = himax_mcu_register_write(ts, HIMAX_REG_ADDR_RELOAD_ADDR_CMD_BEAT, data.byte, 4);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: write reload length and password fail!\n", __func__);
+ return ret;
+ }
+
+ ret = himax_mcu_register_read(ts, HIMAX_REG_ADDR_RELOAD_ADDR_CMD_BEAT, data.byte, 4);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: read reload length and password fail!\n", __func__);
+ return ret;
+ }
+
+ if (le16_to_cpu(data.word[0]) != length) {
+ dev_err(ts->dev, "%s: length verify failed!\n", __func__);
+ return -EINVAL;
+ }
+
+ for (retry_cnt = 0; retry_cnt < retry_limit; retry_cnt++) {
+ ret = himax_mcu_register_read(ts, HIMAX_REG_ADDR_RELOAD_STATUS, data.byte, 4);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: read reload status fail!\n", __func__);
+ return ret;
+ }
+
+ data.dword = le32_to_cpu(data.dword);
+ if ((data.byte[0] & HIMAX_REG_DATA_RELOAD_DONE) != HIMAX_REG_DATA_RELOAD_DONE) {
+ ret = himax_mcu_register_read(ts, HIMAX_REG_ADDR_RELOAD_CRC32_RESULT,
+ data.byte, HIMAX_REG_SZ);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: read crc32 result fail!\n", __func__);
+ return ret;
+ }
+ *crc_result = le32_to_cpu(data.dword);
+ return 0;
+ }
+
+ dev_info(ts->dev, "%s: Waiting for HW ready!\n", __func__);
+ usleep_range(1000, 1100);
+ }
+
+ if (retry_cnt == retry_limit) {
+ ret = himax_mcu_read_FW_status(ts);
+ if (ret < 0)
+ dev_err(ts->dev, "%s: read FW status fail\n", __func__);
+ }
+
+ return -EINVAL;
+}
+
+/**
+ * himax_mcu_read_FW_ver() - Read varies version from touch chip
+ * @ts: Himax touch screen data
+ *
+ * This function is used to read the firmware version, config version, touch config
+ * version, display config version, customer ID, customer info, and project info from
+ * the touch chip. The function will call himax_mcu_register_read() to read the data
+ * from the TPIC, and store the data to the IC data in himax_ts_data.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_mcu_read_FW_ver(struct himax_ts_data *ts)
+{
+ int ret;
+ u8 data[HIMAX_TP_INFO_STR_LEN];
+
+ ret = himax_mcu_register_read(ts, HIMAX_DSRAM_ADDR_FW_VER, data, 4);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: read FW version fail\n", __func__);
+ return ret;
+ }
+ ts->ic_data.vendor_panel_ver = data[0];
+ ts->ic_data.vendor_fw_ver = data[1] << 8 | data[2];
+ dev_info(ts->dev, "%s: PANEL_VER: %X\n", __func__, ts->ic_data.vendor_panel_ver);
+ dev_info(ts->dev, "%s: FW_VER: %X\n", __func__, ts->ic_data.vendor_fw_ver);
+
+ ret = himax_mcu_register_read(ts, HIMAX_DSRAM_ADDR_CFG, data, 4);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: read CFG version fail\n", __func__);
+ return ret;
+ }
+ ts->ic_data.vendor_config_ver = data[2] << 8 | data[3];
+ ts->ic_data.vendor_touch_cfg_ver = data[2];
+ dev_info(ts->dev, "%s: TOUCH_VER: %X\n", __func__, ts->ic_data.vendor_touch_cfg_ver);
+ ts->ic_data.vendor_display_cfg_ver = data[3];
+ dev_info(ts->dev, "%s: DISPLAY_VER: %X\n", __func__, ts->ic_data.vendor_display_cfg_ver);
+
+ ret = himax_mcu_register_read(ts, HIMAX_DSRAM_ADDR_VENDOR, data, 4);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: read customer ID fail\n", __func__);
+ return ret;
+ }
+ ts->ic_data.vendor_cid_maj_ver = data[2];
+ ts->ic_data.vendor_cid_min_ver = data[3];
+ dev_info(ts->dev, "%s: CID_VER: %X\n", __func__, (ts->ic_data.vendor_cid_maj_ver << 8
+ | ts->ic_data.vendor_cid_min_ver));
+
+ ret = himax_mcu_register_read(ts, HIMAX_DSRAM_ADDR_CUS_INFO, data, HIMAX_TP_INFO_STR_LEN);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: read customer info fail\n", __func__);
+ return ret;
+ }
+ memcpy(ts->ic_data.vendor_cus_info, data, HIMAX_TP_INFO_STR_LEN);
+ dev_info(ts->dev, "%s: Cusomer ID : %s\n", __func__, ts->ic_data.vendor_cus_info);
+
+ ret = himax_mcu_register_read(ts, HIMAX_DSRAM_ADDR_PROJ_INFO, data, HIMAX_TP_INFO_STR_LEN);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: read project info fail\n", __func__);
+ return ret;
+ }
+ memcpy(ts->ic_data.vendor_proj_info, data, HIMAX_TP_INFO_STR_LEN);
+ dev_info(ts->dev, "%s: Project ID : %s\n", __func__, ts->ic_data.vendor_proj_info);
+
+ return 0;
+}
+
+/**
+ * himax_bin_desc_data_get() - Parse descriptor data from firmware token
+ * @ts: Himax touch screen data
+ * @addr: Address of the data in firmware image
+ * @descript_buf: token for parsing
+ *
+ * This function is used to parse the descriptor data from the firmware token. The
+ * descriptors are mappings of information in the firmware image. The function will
+ * check checksum of each token first, and then parse the token to get the related
+ * data. The data includes CID version, FW version, CFG version, touch config table,
+ * HID table, HID descriptor, and HID read descriptor.
+ *
+ * Return: true on success, false on failure
+ */
+static bool himax_bin_desc_data_get(struct himax_ts_data *ts, u32 addr, u8 *descript_buf)
+{
+ u16 chk_end;
+ u16 chk_sum;
+ u32 hid_table_addr;
+ u32 i, j;
+ u32 image_offset;
+ u32 map_code;
+ const u32 data_sz = 16;
+ const u32 report_desc_offset = 24;
+ union {
+ u8 *buf;
+ u32 *word;
+ } map_data;
+
+ /* looking for mapping in page, each mapping is 16 bytes */
+ for (i = 0; i < HIMAX_HX83102J_PAGE_SIZE; i = i + data_sz) {
+ chk_end = 0;
+ chk_sum = 0;
+ for (j = i; j < (i + data_sz); j++) {
+ chk_end |= descript_buf[j];
+ chk_sum += descript_buf[j];
+ }
+ if (!chk_end) { /* 1. Check all zero */
+ return false;
+ } else if (chk_sum % 0x100) { /* 2. Check sum */
+ dev_warn(ts->dev, "%s: chk sum failed in %X\n", __func__, i + addr);
+ } else { /* 3. get data */
+ map_data.buf = &descript_buf[i];
+ map_code = le32_to_cpup(map_data.word);
+ map_data.buf = &descript_buf[i + 4];
+ image_offset = le32_to_cpup(map_data.word);
+ /* 4. load info from FW image by specified mapping offset */
+ switch (map_code) {
+ /* Config ID */
+ case HIMAX_FW_CID:
+ ts->fw_info_table.addr_cid_ver_major = image_offset;
+ ts->fw_info_table.addr_cid_ver_minor = image_offset + 1;
+ break;
+ /* FW version */
+ case HIMAX_FW_VER:
+ ts->fw_info_table.addr_fw_ver_major = image_offset;
+ ts->fw_info_table.addr_fw_ver_minor = image_offset + 1;
+ break;
+ /* Config version */
+ case HIMAX_CFG_VER:
+ ts->fw_info_table.addr_cfg_ver_major = image_offset;
+ ts->fw_info_table.addr_cfg_ver_minor = image_offset + 1;
+ break;
+ /* Touch config table */
+ case HIMAX_TP_CONFIG_TABLE:
+ ts->fw_info_table.addr_cfg_table = image_offset;
+ break;
+ /* HID table */
+ case HIMAX_HID_TABLE:
+ ts->fw_info_table.addr_hid_table = image_offset;
+ hid_table_addr = image_offset;
+ ts->fw_info_table.addr_hid_desc = hid_table_addr;
+ ts->fw_info_table.addr_hid_rd_desc =
+ hid_table_addr + report_desc_offset;
+ break;
+ }
+ }
+ }
+
+ return true;
+}
+
+/**
+ * himax_mcu_bin_desc_get() - Check and get the bin description from the data
+ * @fw: Firmware data
+ * @ts: Himax touch screen data
+ * @max_sz: Maximum size to check
+ *
+ * This function is used to check and get the bin description from the firmware data.
+ * It will check the given data to see if it match the bin description format, and
+ * call himax_bin_desc_data_get() to get the related data.
+ *
+ * Return: true on mapping_count > 0, false on otherwise
+ */
+static bool himax_mcu_bin_desc_get(unsigned char *fw, struct himax_ts_data *ts, u32 max_sz)
+{
+ bool keep_on_flag;
+ u32 addr;
+ u32 mapping_count;
+ unsigned char *fw_buf;
+ const u8 header_id = 0x87;
+ const u8 header_id_loc = 0x0e;
+ const u8 header_sz = 8;
+ const u8 header[8] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+
+ /* Check bin is with description table or not */
+ if (!(memcmp(fw, header, header_sz) == 0 && fw[header_id_loc] == header_id)) {
+ dev_err(ts->dev, "%s: No description table\n", __func__);
+ return false;
+ }
+
+ for (addr = 0, mapping_count = 0; addr < max_sz; addr += HIMAX_HX83102J_PAGE_SIZE) {
+ fw_buf = &fw[addr];
+ /* Get related data */
+ keep_on_flag = himax_bin_desc_data_get(ts, addr, fw_buf);
+ if (keep_on_flag)
+ mapping_count++;
+ else
+ break;
+ }
+
+ return mapping_count > 0;
+}
+
+/**
+ * himax_mcu_tp_info_check() - Read touch information from touch chip
+ * @ts: Himax touch screen data
+ *
+ * This function is used to read the touch information from the touch chip. The
+ * information includes the touch resolution, touch point number, interrupt type,
+ * button number, stylus function, stylus version, and stylus ratio. These information
+ * is filled by FW after the FW initialized, so it must be called after FW finish
+ * loading.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_mcu_tp_info_check(struct himax_ts_data *ts)
+{
+ int ret;
+ char data[HIMAX_REG_SZ];
+ u8 stylus_ratio;
+ u32 button_num;
+ u32 max_pt;
+ u32 rx_num;
+ u32 tx_num;
+ u32 x_res;
+ u32 y_res;
+ const u32 button_num_mask = 0x03;
+ const u32 interrupt_type_mask = 0x01;
+ const u32 interrupt_type_edge = 0x01;
+ bool int_is_edge;
+ bool stylus_func;
+ bool stylus_id_v2;
+
+ ret = himax_mcu_register_read(ts, HIMAX_DSRAM_ADDR_RXNUM_TXNUM, data, 4);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: read rx/tx num fail\n", __func__);
+ return ret;
+ }
+ rx_num = data[2];
+ tx_num = data[3];
+
+ ret = himax_mcu_register_read(ts, HIMAX_DSRAM_ADDR_MAXPT_XYRVS, data, 4);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: read max touch point fail\n", __func__);
+ return ret;
+ }
+ max_pt = data[0];
+
+ ret = himax_mcu_register_read(ts, HIMAX_DSRAM_ADDR_X_Y_RES, data, 4);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: read x/y resolution fail\n", __func__);
+ return ret;
+ }
+ y_res = be16_to_cpup((u16 *)&data[0]);
+ x_res = be16_to_cpup((u16 *)&data[2]);
+
+ ret = himax_mcu_register_read(ts, HIMAX_DSRAM_ADDR_INT_IS_EDGE, data, 4);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: read interrupt type fail\n", __func__);
+ return ret;
+ }
+ if ((data[1] & interrupt_type_mask) == interrupt_type_edge)
+ int_is_edge = true;
+ else
+ int_is_edge = false;
+
+ ret = himax_mcu_register_read(ts, HIMAX_DSRAM_ADDR_MKEY, data, 4);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: read button number fail\n", __func__);
+ return ret;
+ }
+ button_num = data[0] & button_num_mask;
+
+ ret = himax_mcu_register_read(ts, HIMAX_DSRAM_ADDR_STYLUS_FUNCTION, data, 4);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: read stylus function fail\n", __func__);
+ return ret;
+ }
+ stylus_func = data[3] ? true : false;
+
+ if (stylus_func) {
+ ret = himax_mcu_register_read(ts, HIMAX_DSRAM_ADDR_STYLUS_VERSION, data, 4);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: read stylus version fail\n", __func__);
+ return ret;
+ }
+ /* dsram_addr_stylus_version + 2 : 0=off 1=on */
+ stylus_id_v2 = data[2] ? true : false;
+ /* dsram_addr_stylus_version + 3 : 0=ratio_1 10=ratio_10 */
+ stylus_ratio = data[3];
+ }
+
+ ts->ic_data.button_num = button_num;
+ ts->ic_data.stylus_function = stylus_func;
+ ts->ic_data.rx_num = rx_num;
+ ts->ic_data.tx_num = tx_num;
+ ts->ic_data.x_res = x_res;
+ ts->ic_data.y_res = y_res;
+ ts->ic_data.max_point = max_pt;
+ ts->ic_data.interrupt_is_edge = int_is_edge;
+ if (stylus_func) {
+ ts->ic_data.stylus_v2 = stylus_id_v2;
+ ts->ic_data.stylus_ratio = stylus_ratio;
+ } else {
+ ts->ic_data.stylus_v2 = false;
+ ts->ic_data.stylus_ratio = 0;
+ }
+
+ dev_info(ts->dev, "%s: rx_num = %u, tx_num = %u\n", __func__,
+ ts->ic_data.rx_num, ts->ic_data.tx_num);
+ dev_info(ts->dev, "%s: max_point = %u\n", __func__, ts->ic_data.max_point);
+ dev_info(ts->dev, "%s: interrupt_is_edge = %s, stylus_function = %s\n", __func__,
+ ts->ic_data.interrupt_is_edge ? "true" : "false",
+ ts->ic_data.stylus_function ? "true" : "false");
+ dev_info(ts->dev, "%s: stylus_v2 = %s, stylus_ratio = %u\n", __func__,
+ ts->ic_data.stylus_v2 ? "true" : "false", ts->ic_data.stylus_ratio);
+ dev_info(ts->dev, "%s: TOUCH INFO updated\n", __func__);
+
+ return 0;
+}
+
+/**
+ * himax_disable_fw_reload() - Disable the FW reload data from flash
+ * @ts: Himax touch screen data
+ *
+ * This function is used to tell FW not to reload data from flash. It needs to be
+ * set before FW start running.
+ *
+ * return: 0 on success, negative error code on failure
+ */
+static int himax_disable_fw_reload(struct himax_ts_data *ts)
+{
+ union himax_dword_data data = {
+ /*
+ * HIMAX_DSRAM_ADDR_FLASH_RELOAD: 0x10007f00
+ * 0x10007f00 <= 0x9aa9, let FW know there's no flash
+ * <= 0x5aa5, there has flash, but not reload
+ * <= 0x0000, there has flash, and reload
+ */
+ .dword = cpu_to_le32(HIMAX_DSRAM_DATA_DISABLE_FLASH_RELOAD)
+ };
+
+ /* Disable Flash Reload */
+ return himax_mcu_register_write(ts, HIMAX_DSRAM_ADDR_FLASH_RELOAD, data.byte, 4);
+}
+
+/**
+ * himax_sram_write_crc_check() - Write the data to SRAM and check the CRC by hardware
+ * @ts: Himax touch screen data
+ * @addr: Address to write to
+ * @data: Data to write
+ * @len: Length of data
+ *
+ * This function is use to write FW code/data to SRAM and check the CRC by hardware to make
+ * sure the written data is correct. The FW code is designed to be CRC result 0, so if the
+ * CRC result is not 0, it means the written data is not correct.
+ *
+ * return: 0 on success, negative error code on failure
+ */
+static int himax_sram_write_crc_check(struct himax_ts_data *ts, u32 addr, const u8 *data, u32 len)
+{
+ int ret;
+ u32 crc;
+ u32 retry_cnt;
+ const u32 retry_limit = 3;
+
+ for (retry_cnt = 0; retry_cnt < retry_limit; retry_cnt++) {
+ dev_info(ts->dev, "%s: Write FW to SRAM - total write size = %u\n", __func__, len);
+ ret = himax_mcu_register_write(ts, addr, data, len);
+ if (ret) {
+ dev_err(ts->dev, "%s: write FW to SRAM fail\n", __func__);
+ return ret;
+ }
+ ret = himax_mcu_check_crc(ts, addr, len, &crc);
+ if (ret) {
+ dev_err(ts->dev, "%s: check CRC fail\n", __func__);
+ return ret;
+ }
+ dev_info(ts->dev, "%s: HW CRC %s in %u time\n", __func__,
+ crc == 0 ? "OK" : "Fail", retry_cnt);
+
+ if (crc == 0)
+ break;
+ }
+
+ if (crc != 0) {
+ dev_err(ts->dev, "%s: HW CRC fail\n", __func__);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/**
+ * himax_zf_part_info() - Get and write the partition from the firmware to SRAM
+ * @fw: Firmware data
+ * @ts: Himax touch screen data
+ *
+ * This function is used to get the partition information from the firmware and write
+ * the partition to SRAM. The partition information includes the DSRAM address, the
+ * firmware offset, and the write size. The function will get the partition information
+ * into a table, and then write the partition to SRAM according to the table. After
+ * writing the partition to SRAM, the function will check the CRC by hardware to make
+ * sure the written data is correct.
+ *
+ * return: 0 on success, negative error code on failure
+ */
+static int himax_zf_part_info(const struct firmware *fw, struct himax_ts_data *ts)
+{
+ int i;
+ int i_max = -1;
+ int i_min = -1;
+ int pnum;
+ int ret;
+ u8 buf[HIMAX_ZF_PARTITION_DESC_SZ];
+ u32 cfg_crc_sw;
+ u32 cfg_crc_hw;
+ u32 cfg_sz;
+ u32 dsram_base = 0xffffffff;
+ u32 dsram_max = 0;
+ u32 retry_cnt = 0;
+ u32 sram_min;
+ const u32 retry_limit = 3;
+ const u32 table_addr = ts->fw_info_table.addr_cfg_table;
+ struct himax_zf_info *info;
+
+ /* 1. initial check */
+ ret = hx83102j_en_hw_crc(ts, true);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: Failed to enable HW CRC\n", __func__);
+ return ret;
+ }
+ pnum = fw->data[table_addr + HIMAX_ZF_PARTITION_AMOUNT_OFFSET];
+ if (pnum < 2) {
+ dev_err(ts->dev, "%s: partition number is not correct\n", __func__);
+ return -EINVAL;
+ }
+
+ info = kcalloc(pnum, sizeof(struct himax_zf_info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ memset(info, 0, pnum * sizeof(struct himax_zf_info));
+
+ /*
+ * 2. record partition information:
+ * partition 0: FW main code
+ */
+ memcpy(buf, &fw->data[table_addr], HIMAX_ZF_PARTITION_DESC_SZ);
+ memcpy(info[0].sram_addr, buf, 4);
+ info[0].write_size = le32_to_cpup((u32 *)&buf[4]);
+ info[0].fw_addr = le32_to_cpup((u32 *)&buf[8]);
+
+ /* partition 1 ~ n: config data */
+ for (i = 1; i < pnum; i++) {
+ memcpy(buf, &fw->data[i * HIMAX_ZF_PARTITION_DESC_SZ + table_addr],
+ HIMAX_ZF_PARTITION_DESC_SZ);
+ memcpy(info[i].sram_addr, buf, 4);
+ info[i].write_size = le32_to_cpup((u32 *)&buf[4]);
+ info[i].fw_addr = le32_to_cpup((u32 *)&buf[8]);
+ info[i].cfg_addr = le32_to_cpup((u32 *)&info[i].sram_addr[0]);
+
+ /* Write address must be multiple of 4 */
+ if (info[i].cfg_addr % 4 != 0) {
+ info[i].cfg_addr -= (info[i].cfg_addr % 4);
+ info[i].fw_addr -= (info[i].cfg_addr % 4);
+ info[i].write_size += (info[i].cfg_addr % 4);
+ }
+
+ if (dsram_base > info[i].cfg_addr) {
+ dsram_base = info[i].cfg_addr;
+ i_min = i;
+ }
+ if (dsram_max < info[i].cfg_addr) {
+ dsram_max = info[i].cfg_addr;
+ i_max = i;
+ }
+ }
+
+ if (i_min < 0 || i_max < 0) {
+ dev_err(ts->dev, "%s: DSRAM address invalid!\n", __func__);
+ return -EINVAL;
+ }
+
+ /* 3. prepare data to update */
+ sram_min = info[i_min].cfg_addr;
+
+ cfg_sz = (dsram_max - dsram_base) + info[i_max].write_size;
+ /* Wrtie size must be multiple of 4 */
+ if (cfg_sz % 4 != 0)
+ cfg_sz = cfg_sz + 4 - (cfg_sz % 4);
+
+ dev_info(ts->dev, "%s: main code sz = %d, config sz = %d\n", __func__,
+ info[0].write_size, cfg_sz);
+ /* config size should be smaller than DSRAM size */
+ if (cfg_sz > ts->chip_max_dsram_size) {
+ dev_err(ts->dev, "%s: config size error[%d, %u]!!\n", __func__,
+ cfg_sz, ts->chip_max_dsram_size);
+ ret = -EINVAL;
+ goto alloc_cfg_buffer_failed;
+ }
+
+ memset(ts->zf_update_cfg_buffer, 0x00,
+ ts->chip_max_dsram_size * sizeof(u8));
+
+ /* Collect all partition in FW for DSRAM in a cfg buffer */
+ for (i = 1; i < pnum; i++)
+ memcpy(&ts->zf_update_cfg_buffer[info[i].cfg_addr - dsram_base],
+ &fw->data[info[i].fw_addr], info[i].write_size);
+
+ /*
+ * 4. write to sram
+ * First, write FW main code and check CRC by HW
+ */
+ ret = himax_sram_write_crc_check(ts, le32_to_cpup((u32 *)info[0].sram_addr),
+ &fw->data[info[0].fw_addr], info[0].write_size);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: HW CRC fail\n", __func__);
+ goto write_main_code_failed;
+ }
+
+ /*
+ * Second, FW config data: Calculate CRC of CFG data which is going to write.
+ * CFG data don't have CRC pre-defined in FW and need to be calculated by driver.
+ */
+ cfg_crc_sw = himax_mcu_calculate_crc(ts->zf_update_cfg_buffer, cfg_sz);
+ for (retry_cnt = 0; retry_cnt < retry_limit; retry_cnt++) {
+ /* Write hole cfg data to DSRAM */
+ dev_info(ts->dev, "%s: Write cfg to SRAM - total write size = %d\n",
+ __func__, cfg_sz);
+ ret = himax_mcu_register_write(ts, sram_min, ts->zf_update_cfg_buffer, cfg_sz);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: write cfg to SRAM fail\n", __func__);
+ goto write_cfg_failed;
+ }
+ /*
+ * Check CRC: Tell HW to calculate CRC from CFG start address in SRAM and check
+ * size is equal to size of CFG buffer written. Then we compare the two CRC data
+ * make sure data written is correct.
+ */
+ ret = himax_mcu_check_crc(ts, sram_min, cfg_sz, &cfg_crc_hw);
if (ret) {
- dev_err(ts->dev, "%s: read event stack error!\n", __func__);
- return ret;
+ dev_err(ts->dev, "%s: check CRC failed!\n", __func__);
+ goto crc_failed;
}
- }
- return 0;
-}
+ if (cfg_crc_hw != cfg_crc_sw)
+ dev_err(ts->dev, "%s: Cfg CRC FAIL, HWCRC = %X, SWCRC = %X, retry = %u\n",
+ __func__, cfg_crc_hw, cfg_crc_sw, retry_cnt);
+ else
+ break;
+ }
-/**
- * himax_touch_get() - Get touch data from touch chip
- * @ts: Himax touch screen data
- * @buf: Buffer to store the data
- *
- * This function is a wrapper to call hx83102j_read_event_stack() to read the touch
- * data from the touch chip. The touch_data_sz is the size of the touch data to read,
- * which is calculated by hid report descriptor provided by the firmware.
- *
- * Return: HIMAX_TS_SUCCESS on success, negative error code on failure. We categorize
- * the error code into HIMAX_TS_GET_DATA_FAIL when the read fails, and HIMAX_TS_SUCCESS
- * when the read is successful. The reason is that the may need special handling when
- * the read fails.
- */
-static int himax_touch_get(struct himax_ts_data *ts, u8 *buf)
-{
- if (hx83102j_read_event_stack(ts, buf, ts->touch_data_sz)) {
- dev_err(ts->dev, "can't read data from chip!");
- return HIMAX_TS_GET_DATA_FAIL;
+ if (retry_cnt == retry_limit && cfg_crc_hw != cfg_crc_sw) {
+ dev_err(ts->dev, "%s: Write cfg to SRAM fail\n", __func__);
+ ret = -EINVAL;
+ goto crc_not_match;
}
- return HIMAX_TS_SUCCESS;
+crc_not_match:
+crc_failed:
+write_cfg_failed:
+write_main_code_failed:
+alloc_cfg_buffer_failed:
+ kfree(info);
+
+ return ret;
}
/**
- * himax_bin_desc_data_get() - Parse descriptor data from firmware token
+ * himax_mcu_firmware_update_zf() - Update the firmware to the touch chip
+ * @fw: Firmware data
* @ts: Himax touch screen data
- * @addr: Address of the data in firmware image
- * @descript_buf: token for parsing
*
- * This function is used to parse the descriptor data from the firmware token. The
- * descriptors are mappings of information in the firmware image. The function will
- * check checksum of each token first, and then parse the token to get the related
- * data. The data includes CID version, FW version, CFG version, touch config table,
- * HID table, HID descriptor, and HID read descriptor.
+ * This function is used to update the firmware to the touch chip. The first step is
+ * to reset the touch chip, stop the MCU and then write the firmware to the touch chip.
*
- * Return: true on success, false on failure
+ * return: 0 on success, negative error code on failure
*/
-static bool himax_bin_desc_data_get(struct himax_ts_data *ts, u32 addr, u8 *descript_buf)
+static int himax_mcu_firmware_update_zf(const struct firmware *fw, struct himax_ts_data *ts)
{
- u16 chk_end;
- u16 chk_sum;
- u32 hid_table_addr;
- u32 i, j;
- u32 image_offset;
- u32 map_code;
- const u32 data_sz = 16;
- const u32 report_desc_offset = 24;
- union {
- u8 *buf;
- u32 *word;
- } map_data;
+ int ret;
+ union himax_dword_data data_system_reset = {
+ .dword = cpu_to_le32(HIMAX_REG_DATA_SYSTEM_RESET)
+ };
- /* looking for mapping in page, each mapping is 16 bytes */
- for (i = 0; i < HIMAX_HX83102J_PAGE_SIZE; i = i + data_sz) {
- chk_end = 0;
- chk_sum = 0;
- for (j = i; j < (i + data_sz); j++) {
- chk_end |= descript_buf[j];
- chk_sum += descript_buf[j];
- }
- if (!chk_end) { /* 1. Check all zero */
- return false;
- } else if (chk_sum % 0x100) { /* 2. Check sum */
- dev_warn(ts->dev, "%s: chk sum failed in %X\n", __func__, i + addr);
- } else { /* 3. get data */
- map_data.buf = &descript_buf[i];
- map_code = le32_to_cpup(map_data.word);
- map_data.buf = &descript_buf[i + 4];
- image_offset = le32_to_cpup(map_data.word);
- /* 4. load info from FW image by specified mapping offset */
- switch (map_code) {
- /* Config ID */
- case HIMAX_FW_CID:
- ts->fw_info_table.addr_cid_ver_major = image_offset;
- ts->fw_info_table.addr_cid_ver_minor = image_offset + 1;
- break;
- /* FW version */
- case HIMAX_FW_VER:
- ts->fw_info_table.addr_fw_ver_major = image_offset;
- ts->fw_info_table.addr_fw_ver_minor = image_offset + 1;
- break;
- /* Config version */
- case HIMAX_CFG_VER:
- ts->fw_info_table.addr_cfg_ver_major = image_offset;
- ts->fw_info_table.addr_cfg_ver_minor = image_offset + 1;
- break;
- /* Touch config table */
- case HIMAX_TP_CONFIG_TABLE:
- ts->fw_info_table.addr_cfg_table = image_offset;
- break;
- /* HID table */
- case HIMAX_HID_TABLE:
- ts->fw_info_table.addr_hid_table = image_offset;
- hid_table_addr = image_offset;
- ts->fw_info_table.addr_hid_desc = hid_table_addr;
- ts->fw_info_table.addr_hid_rd_desc =
- hid_table_addr + report_desc_offset;
- break;
- }
- }
+ dev_info(ts->dev, "%s: Updating FW - total FW size = %u\n", __func__, (u32)fw->size);
+ ret = himax_mcu_register_write(ts, HIMAX_REG_ADDR_SYSTEM_RESET, data_system_reset.byte, 4);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: system reset fail\n", __func__);
+ return ret;
}
- return true;
+ ret = hx83102j_sense_off(ts, false);
+ if (ret)
+ return ret;
+
+ ret = himax_zf_part_info(fw, ts);
+
+ return ret;
}
/**
- * himax_mcu_bin_desc_get() - Check and get the bin description from the data
- * @fw: Firmware data
+ * himax_zf_reload_from_file() - Complete firmware update sequence
+ * @file_name: File name of the firmware
* @ts: Himax touch screen data
- * @max_sz: Maximum size to check
*
- * This function is used to check and get the bin description from the firmware data.
- * It will check the given data to see if it match the bin description format, and
- * call himax_bin_desc_data_get() to get the related data.
+ * This function process the full sequence of updating the firmware to the touch chip.
+ * It will first check if the other thread is updating now, if not, it will request the
+ * firmware from user space and then call himax_mcu_firmware_update_zf() to update the
+ * firmware, and then tell firmware not to reload data from flash and initial the touch
+ * chip by calling himax_mcu_power_on_init().
*
- * Return: true on mapping_count > 0, false on otherwise
+ * return: 0 on success, negative error code on failure
*/
-static bool himax_mcu_bin_desc_get(unsigned char *fw, struct himax_ts_data *ts, u32 max_sz)
+static int himax_zf_reload_from_file(char *file_name, struct himax_ts_data *ts)
{
- bool keep_on_flag;
- u32 addr;
- u32 mapping_count;
- unsigned char *fw_buf;
- const u8 header_id = 0x87;
- const u8 header_id_loc = 0x0e;
- const u8 header_sz = 8;
- const u8 header[8] = {
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
- };
+ int ret;
+ const struct firmware *fw;
- /* Check bin is with description table or not */
- if (!(memcmp(fw, header, header_sz) == 0 && fw[header_id_loc] == header_id)) {
- dev_err(ts->dev, "%s: No description table\n", __func__);
- return false;
+ if (!mutex_trylock(&ts->zf_update_lock)) {
+ dev_warn(ts->dev, "%s: Other thread is updating now!\n", __func__);
+ return 0;
}
+ dev_info(ts->dev, "%s: Preparing to update %s!\n", __func__, file_name);
- for (addr = 0, mapping_count = 0; addr < max_sz; addr += HIMAX_HX83102J_PAGE_SIZE) {
- fw_buf = &fw[addr];
- /* Get related data */
- keep_on_flag = himax_bin_desc_data_get(ts, addr, fw_buf);
- if (keep_on_flag)
- mapping_count++;
- else
- break;
+ ret = request_firmware(&fw, file_name, ts->dev);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: request firmware fail, code[%d]!!\n", __func__, ret);
+ goto load_firmware_error;
}
- return mapping_count > 0;
+ ret = himax_mcu_firmware_update_zf(fw, ts);
+ release_firmware(fw);
+ if (ret < 0)
+ goto load_firmware_error;
+
+ ret = himax_disable_fw_reload(ts);
+ if (ret < 0)
+ goto load_firmware_error;
+ ret = himax_mcu_power_on_init(ts);
+
+load_firmware_error:
+ mutex_unlock(&ts->zf_update_lock);
+
+ return ret;
}
/**
@@ -1238,6 +2188,7 @@ static int himax_ts_operation(struct himax_ts_data *ts)
* This function is used to handle interrupt bottom half work. It will
* call the himax_ts_operation() to get the touch data, dispatch the data
* to HID core. If the touch data is not valid, it will reset the TPIC.
+ * It will also call the hx83102j_reload_to_active() after the reset action.
*
* Return: void
*/
@@ -1246,7 +2197,38 @@ static void himax_ts_work(struct himax_ts_data *ts)
if (himax_ts_operation(ts) == HIMAX_TS_GET_DATA_FAIL) {
dev_info(ts->dev, "%s: Now reset the Touch chip\n", __func__);
himax_mcu_ic_reset(ts, true);
+ if (hx83102j_reload_to_active(ts))
+ dev_warn(ts->dev, "%s: Reload to active failed\n", __func__);
+ }
+}
+
+/**
+ * himax_update_fw() - update firmware using firmware structure
+ * @ts: Himax touch screen data
+ *
+ * This function use already initialize firmware structure in ts to update
+ * firmware.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_update_fw(struct himax_ts_data *ts)
+{
+ int ret;
+ u32 retry_cnt;
+ const u32 retry_limit = 3;
+
+ for (retry_cnt = 0; retry_cnt < retry_limit; retry_cnt++) {
+ ret = himax_mcu_firmware_update_zf(ts->himax_fw, ts);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: TP upgrade error, upgrade_times = %d\n", __func__,
+ retry_cnt);
+ } else {
+ dev_info(ts->dev, "%s: TP FW upgrade OK\n", __func__);
+ return 0;
+ }
}
+
+ return -EIO;
}
/**
@@ -1279,9 +2261,8 @@ static int himax_hid_rd_init(struct himax_ts_data *ts)
if (!ts->hid_rd_data.rd_data)
return -ENOMEM;
}
- /* Copy the base RD from firmware table */
memcpy((void *)ts->hid_rd_data.rd_data,
- &ts->himax_fw_data[ts->fw_info_table.addr_hid_rd_desc],
+ &ts->himax_fw->data[ts->fw_info_table.addr_hid_rd_desc],
ts->hid_desc.report_desc_length);
ts->hid_rd_data.rd_length = ts->hid_desc.report_desc_length;
}
@@ -1343,81 +2324,105 @@ static int himax_hid_report_data_init(struct himax_ts_data *ts)
return 0;
}
-/* load firmware data from flash, parse HID info and register HID */
/**
- * himax_load_config() - Load the firmware from the flash
- * @ts: Himax touch screen data
+ * himax_initial_work() - Initial work for the touch screen
+ * @work: Work structure
+ *
+ * This function is used to do the initial work for the touch screen. It will
+ * call the request_firmware() to get the firmware from the file system, and parse the
+ * mapping table in 1k header. If the headers are parsed successfully, it will
+ * call the himax_update_fw() to update the firmware and power on the touch screen.
+ * If the power on action is successful, it will load the hid descriptor and
+ * check the touch panel information. If the touch panel information is correct,
+ * it will call the himax_hid_rd_init() to initialize the HID report descriptor,
+ * and call the himax_hid_register() to register the HID device. After all is done,
+ * it will release the firmware and enable the interrupt.
*
- * This function is used to load the firmware from the flash. It will read
- * the firmware from the flash and parse the HID info. If the HID info is
- * valid, it will initialize the HID report descriptor and register the HID
- * device. If the HID device is probed, it will initialize the report data
- * and enable the interrupt.
- *
- * Return: 0 on success, negative error code on failure
+ * Return: None
*/
-static int himax_load_config(struct himax_ts_data *ts)
+static void himax_initial_work(struct work_struct *work)
{
+ struct himax_ts_data *ts = container_of(work, struct himax_ts_data,
+ initial_work.work);
int ret;
- s32 i;
- s32 page_sz = (s32)HIMAX_HX83102J_PAGE_SIZE;
- s32 flash_sz = (s32)HIMAX_HX83102J_FLASH_SIZE;
- bool fw_load_status = false;
+ bool fw_load_status;
const u32 fw_bin_header_sz = 1024;
ts->ic_boot_done = false;
-
- ts->himax_fw_data = devm_kzalloc(ts->dev, HIMAX_HX83102J_FLASH_SIZE, GFP_KERNEL);
- if (!ts->himax_fw_data)
- return -ENOMEM;
-
- for (i = 0; i < flash_sz; i += page_sz) {
- ret = himax_mcu_register_read(ts, i, ts->himax_fw_data + i,
- (flash_sz - i) > page_sz ? page_sz : (flash_sz - i));
- if (ret < 0) {
- dev_err(ts->dev, "%s: read FW from flash fail!\n", __func__);
- return ret;
- }
+ dev_info(ts->dev, "%s: request file %s\n", __func__, ts->firmware_name);
+ ret = request_firmware(&ts->himax_fw, ts->firmware_name, ts->dev);
+ if (ret < 0) {
+ dev_err(ts->dev, "%s: request firmware failed, error code = %d\n", __func__, ret);
+ return;
}
- /* Search mapping table in 1k header */
- fw_load_status = himax_mcu_bin_desc_get((unsigned char *)ts->himax_fw_data,
+ /* Parse the mapping table in 1k header */
+ fw_load_status = himax_mcu_bin_desc_get((unsigned char *)ts->himax_fw->data,
ts, fw_bin_header_sz);
if (!fw_load_status) {
- dev_err(ts->dev, "%s: FW load status fail!\n", __func__);
- return -EINVAL;
+ dev_err(ts->dev, "%s: Failed to parse the mapping table!\n", __func__);
+ goto err_load_bin_descriptor;
}
- if (ts->fw_info_table.addr_hid_desc != 0) {
- memcpy(&ts->hid_desc,
- &ts->himax_fw_data[ts->fw_info_table.addr_hid_desc],
- sizeof(struct himax_hid_desc));
- ts->hid_desc.desc_length =
- le16_to_cpu(ts->hid_desc.desc_length);
- ts->hid_desc.bcd_version =
- le16_to_cpu(ts->hid_desc.bcd_version);
- ts->hid_desc.report_desc_length =
- le16_to_cpu(ts->hid_desc.report_desc_length);
- ts->hid_desc.max_input_length =
- le16_to_cpu(ts->hid_desc.max_input_length);
- ts->hid_desc.max_output_length =
- le16_to_cpu(ts->hid_desc.max_output_length);
- ts->hid_desc.max_fragment_length =
- le16_to_cpu(ts->hid_desc.max_fragment_length);
- ts->hid_desc.vendor_id =
- le16_to_cpu(ts->hid_desc.vendor_id);
- ts->hid_desc.product_id =
- le16_to_cpu(ts->hid_desc.product_id);
- ts->hid_desc.version_id =
- le16_to_cpu(ts->hid_desc.version_id);
- ts->hid_desc.flags =
- le16_to_cpu(ts->hid_desc.flags);
+ if (himax_update_fw(ts)) {
+ dev_err(ts->dev, "%s: Update FW fail\n", __func__);
+ goto err_update_fw_failed;
}
+ dev_info(ts->dev, "%s: Update FW success\n", __func__);
+ /* write flag to sram to stop fw reload again. */
+ if (himax_disable_fw_reload(ts))
+ goto err_disable_fw_reload;
+ if (himax_mcu_power_on_init(ts))
+ goto err_power_on_init;
+ /* get hid descriptors */
+ if (!ts->fw_info_table.addr_hid_desc) {
+ dev_err(ts->dev, "%s: No HID descriptor! Wrong FW!\n", __func__);
+ goto err_wrong_firmware;
+ }
+ memcpy(&ts->hid_desc,
+ &ts->himax_fw->data[ts->fw_info_table.addr_hid_desc],
+ sizeof(struct himax_hid_desc));
+ ts->hid_desc.desc_length =
+ le16_to_cpu(ts->hid_desc.desc_length);
+ ts->hid_desc.bcd_version =
+ le16_to_cpu(ts->hid_desc.bcd_version);
+ ts->hid_desc.report_desc_length =
+ le16_to_cpu(ts->hid_desc.report_desc_length);
+ ts->hid_desc.max_input_length =
+ le16_to_cpu(ts->hid_desc.max_input_length);
+ ts->hid_desc.max_output_length =
+ le16_to_cpu(ts->hid_desc.max_output_length);
+ ts->hid_desc.max_fragment_length =
+ le16_to_cpu(ts->hid_desc.max_fragment_length);
+ ts->hid_desc.vendor_id =
+ le16_to_cpu(ts->hid_desc.vendor_id);
+ ts->hid_desc.product_id =
+ le16_to_cpu(ts->hid_desc.product_id);
+ ts->hid_desc.version_id =
+ le16_to_cpu(ts->hid_desc.version_id);
+ ts->hid_desc.flags =
+ le16_to_cpu(ts->hid_desc.flags);
+
+ if (himax_mcu_tp_info_check(ts))
+ goto err_tp_info_failed;
+ if (himax_mcu_read_FW_ver(ts))
+ goto err_read_fw_ver;
+ if (ts->pdata.pid) {
+ if (ts->pdata.pid != ts->hid_desc.product_id) {
+ dev_err(ts->dev, "%s: PID mismatch, dtsi PID = 0x%x, fw PID = 0x%x\n",
+ __func__, ts->pdata.pid, ts->hid_desc.product_id);
+ goto err_pid_match_failed;
+ } else {
+ dev_info(ts->dev, "%s: PID match, dtsi PID = 0x%x, fw PID = 0x%x\n",
+ __func__, ts->pdata.pid, ts->hid_desc.product_id);
+ }
+ }
if (himax_hid_rd_init(ts)) {
dev_err(ts->dev, "%s: hid rd init fail\n", __func__);
goto err_hid_rd_init_failed;
}
+ usleep_range(1000000, 1000100);
himax_hid_register(ts);
if (!ts->hid_probed) {
goto err_hid_probe_failed;
@@ -1428,19 +2433,29 @@ static int himax_load_config(struct himax_ts_data *ts)
}
}
- ts->himax_fw_data = NULL;
+ release_firmware(ts->himax_fw);
+ ts->himax_fw = NULL;
+
ts->ic_boot_done = true;
himax_int_enable(ts, true);
- return 0;
+ return;
err_report_data_init_failed:
himax_hid_remove(ts);
ts->hid_probed = false;
err_hid_probe_failed:
err_hid_rd_init_failed:
-
- return -EINVAL;
+err_pid_match_failed:
+err_read_fw_ver:
+err_tp_info_failed:
+err_wrong_firmware:
+err_power_on_init:
+err_disable_fw_reload:
+err_update_fw_failed:
+err_load_bin_descriptor:
+ release_firmware(ts->himax_fw);
+ ts->himax_fw = NULL;
}
/**
@@ -1492,12 +2507,22 @@ static void himax_ap_notify_fw_suspend(struct himax_ts_data *ts, bool suspend)
* @ts: Himax touch screen data
*
* This function is used to resume the touch screen. It will call the
+ * himax_zf_reload_from_file() to reload the firmware. And call the
* himax_ap_notify_fw_suspend() to notify the FW of AP resume status.
*
* Return: None
*/
static void himax_resume_proc(struct himax_ts_data *ts)
{
+ int ret;
+
+ ret = himax_zf_reload_from_file(ts->firmware_name, ts);
+ if (ret) {
+ dev_err(ts->dev, "%s: update FW fail, code[%d]!!\n", __func__, ret);
+ return;
+ }
+ ts->resume_succeeded = true;
+
himax_ap_notify_fw_suspend(ts, false);
}
@@ -1527,15 +2552,23 @@ static int himax_chip_suspend(struct himax_ts_data *ts)
* This function is used to resume the touch screen. It will set the resume
* success flag to false, and disable reset pin. Then call the himax_resume_proc()
* to process detailed resume procedure.
+ * If the resume action is succeeded, it will call the himax_hid_probe() to restore
+ * the HID device and enable the interrupt.
*
* Return: 0 on success, negative error code on failure
*/
static int himax_chip_resume(struct himax_ts_data *ts)
{
+ ts->resume_succeeded = false;
gpiod_set_value(ts->pdata.gpiod_rst, 0);
himax_resume_proc(ts);
- himax_hid_probe(ts);
- himax_int_enable(ts, true);
+ if (ts->resume_succeeded) {
+ himax_hid_probe(ts);
+ himax_int_enable(ts, true);
+ } else {
+ dev_err(ts->dev, "%s: resume failed!\n", __func__);
+ return -ECANCELED;
+ }
return 0;
}
@@ -1596,8 +2629,7 @@ static int himax_resume(struct device *dev)
* initialize interrupt lock, register the interrupt, and disable the
* interrupt. If later part of initialization succeed, then interrupt will
* be enabled.
- * It will also load the firmware from the flash, parse the HID info, and
- * register the HID device by calling the himax_load_config().
+ * And initialize varies flags, workqueue and delayed work for later use.
*
* Return: 0 on success, negative error code on failure
*/
@@ -1605,18 +2637,96 @@ static int himax_chip_init(struct himax_ts_data *ts)
{
int ret;
+ hx83102j_chip_init_data(ts);
if (himax_ts_register_interrupt(ts)) {
dev_err(ts->dev, "%s: register interrupt failed\n", __func__);
return -EIO;
}
himax_int_enable(ts, false);
- ret = himax_load_config(ts);
- if (ret < 0)
- return ret;
+ ts->zf_update_cfg_buffer = devm_kzalloc(ts->dev, ts->chip_max_dsram_size, GFP_KERNEL);
+ if (!ts->zf_update_cfg_buffer) {
+ ret = -ENOMEM;
+ goto err_update_cfg_buf_alloc_failed;
+ }
+ INIT_DELAYED_WORK(&ts->initial_work, himax_initial_work);
+ schedule_delayed_work(&ts->initial_work, msecs_to_jiffies(HIMAX_DELAY_BOOT_UPDATE_MS));
ts->initialized = true;
+ return 0;
+ cancel_delayed_work_sync(&ts->initial_work);
+err_update_cfg_buf_alloc_failed:
+
+ return ret;
+}
+
+/**
+ * himax_chip_deinit() - Deinitialize the Himax touch screen
+ * @ts: Himax touch screen data
+ *
+ * This function is used to deinitialize the Himax touch screen.
+ *
+ * Return: None
+ */
+static void himax_chip_deinit(struct himax_ts_data *ts)
+{
+ cancel_delayed_work_sync(&ts->initial_work);
+}
+
+#if defined(CONFIG_OF)
+/**
+ * himax_parse_dt() - Parse the device tree
+ * @dt: Device node
+ * @pdata: Himax platform data
+ *
+ * This function is used to parse the device tree. If "himax,pid" is found,
+ * it will parse the PID value and set it to the platform data. The firmware
+ * name will set to himax_i2chid_$PID.bin if the PID is found, or
+ * himax_i2chid.bin if the PID is not found.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_parse_dt(struct device_node *dt, struct himax_platform_data *pdata)
+{
+ u32 data;
+ const char default_fw_name[] = HIMAX_BOOT_UPGRADE_FWNAME;
+ /*
+ * Maximum length of a firmware name size:
+ * (default_name) + _XXXX(PID) + .bin + null terminator
+ */
+ static char pid_fw_name[ARRAY_SIZE(default_fw_name) + 5 + 4 + 1] = {0};
+ struct himax_ts_data *ts;
+
+ if (!dt || !pdata)
+ return -EINVAL;
+
+ ts = container_of(pdata, struct himax_ts_data, pdata);
+ /* Set default firmware name, without PID */
+ strscpy(ts->firmware_name, HIMAX_BOOT_UPGRADE_FWNAME HIMAX_FW_EXT_NAME,
+ sizeof(HIMAX_BOOT_UPGRADE_FWNAME HIMAX_FW_EXT_NAME));
+
+ /*
+ * check himax,pid first, if exist then get the value.
+ * himax,pid = <0x1002>; 0x1002 is PID value
+ */
+ if (of_get_property(dt, "himax,pid", &data)) {
+ if (of_property_read_u32(dt, "himax,pid", &data)) {
+ pdata->pid = 0;
+ return -EINVAL;
+ }
+
+ pdata->pid = data;
+ snprintf(pid_fw_name, sizeof(pid_fw_name), "%s_%04X%s", HIMAX_BOOT_UPGRADE_FWNAME,
+ pdata->pid, HIMAX_FW_EXT_NAME);
+ dev_info(ts->dev, "%s: DT:himax,pid = %04X, fw_name = %s\n", __func__,
+ pdata->pid, pid_fw_name);
+ strscpy(ts->firmware_name, pid_fw_name, sizeof(pid_fw_name));
+ } else {
+ pdata->pid = 0;
+ }
+
return 0;
}
+#endif
/**
* __himax_initial_power_up() - Initial power up of the Himax touch screen
@@ -1820,6 +2930,13 @@ static int himax_spi_drv_probe(struct spi_device *spi)
dev_err(ts->dev, "%s: gpio-rst value is not valid\n", __func__);
return -EIO;
}
+#if defined(CONFIG_OF)
+ if (himax_parse_dt(spi->dev.of_node, pdata) < 0) {
+ dev_err(ts->dev, "%s: parse OF data failed!\n", __func__);
+ ts->dev = NULL;
+ return -ENODEV;
+ }
+#endif
spi->bits_per_word = 8;
spi->mode = SPI_MODE_3;
@@ -1850,6 +2967,7 @@ static int himax_spi_drv_probe(struct spi_device *spi)
spin_lock_init(&ts->irq_lock);
mutex_init(&ts->rw_lock);
mutex_init(&ts->reg_lock);
+ mutex_init(&ts->zf_update_lock);
dev_set_drvdata(&spi->dev, ts);
spi_set_drvdata(spi, ts);
@@ -1885,6 +3003,7 @@ static void himax_spi_drv_remove(struct spi_device *spi)
if (ts->hid_probed)
himax_hid_remove(ts);
}
+ himax_chip_deinit(ts);
himax_platform_deinit(ts);
}
}
diff --git a/drivers/hid/hid-himax-83102j.h b/drivers/hid/hid-himax-83102j.h
index eef55c45b1d4..6a41ff680478 100644
--- a/drivers/hid/hid-himax-83102j.h
+++ b/drivers/hid/hid-himax-83102j.h
@@ -10,7 +10,9 @@
// #define HX_PWR_CONFIG
#include <drm/drm_panel.h>
+#include <linux/crc32poly.h>
#include <linux/delay.h>
+#include <linux/firmware.h>
#include <linux/hid.h>
#include <linux/interrupt.h>
#include <linux/module.h>
@@ -41,6 +43,13 @@
HIMAX_BUS_W_HLEN + HIMAX_REG_SZ)
/* SPI CS setup time */
#define HIMAX_SPI_CS_SETUP_TIME 300
+/* Clear 4 bytes data */
+#define HIMAX_DATA_CLEAR 0x00000000
+/* boot update start delay */
+#define HIMAX_DELAY_BOOT_UPDATE_MS 2000
+#define HIMAX_TP_INFO_STR_LEN 12U
+#define HIMAX_ZF_PARTITION_AMOUNT_OFFSET 12
+#define HIMAX_ZF_PARTITION_DESC_SZ 16U
/* HIDRAW report header size */
#define HIMAX_HID_REPORT_HDR_SZ 2U
/* hx83102j IC parameters */
@@ -71,17 +80,48 @@
#define HIMAX_AHB_CMD_INCR4_ADD_4_BYTE 0x01
#define HIMAX_AHB_CMD_LEAVE_SAFE_MODE 0x0000
/* DSRAM flag addresses */
+#define HIMAX_DSRAM_ADDR_VENDOR 0x10007000
+#define HIMAX_DSRAM_ADDR_FW_VER 0x10007004
+#define HIMAX_DSRAM_ADDR_CUS_INFO 0x10007008
+#define HIMAX_DSRAM_ADDR_PROJ_INFO 0x10007014
+#define HIMAX_DSRAM_ADDR_CFG 0x10007084
+#define HIMAX_DSRAM_ADDR_INT_IS_EDGE 0x10007088
+#define HIMAX_DSRAM_ADDR_MKEY 0x100070e8
+#define HIMAX_DSRAM_ADDR_RXNUM_TXNUM 0x100070f4
+#define HIMAX_DSRAM_ADDR_MAXPT_XYRVS 0x100070f8
+#define HIMAX_DSRAM_ADDR_X_Y_RES 0x100070fc
+#define HIMAX_DSRAM_ADDR_STYLUS_FUNCTION 0x1000719c
+#define HIMAX_DSRAM_ADDR_STYLUS_VERSION 0x100071fc
+#define HIMAX_DSRAM_ADDR_SET_NFRAME 0x10007294
+#define HIMAX_DSRAM_ADDR_2ND_FLASH_RELOAD 0x100072c0
+#define HIMAX_DSRAM_ADDR_FLASH_RELOAD 0x10007f00
+#define HIMAX_DSRAM_ADDR_SORTING_MODE_EN 0x10007f04
+#define HIMAX_DSRAM_ADDR_DBG_MSG 0x10007f40
#define HIMAX_DSRAM_ADDR_AP_NOTIFY_FW_SUSPEND 0x10007fd0
/* dsram flag data */
#define HIMAX_DSRAM_DATA_AP_NOTIFY_FW_SUSPEND 0xa55aa55a
#define HIMAX_DSRAM_DATA_AP_NOTIFY_FW_RESUME 0x00000000
+#define HIMAX_DSRAM_DATA_DISABLE_FLASH_RELOAD 0x00009aa9
+#define HIMAX_DSRAM_DATA_FW_RELOAD_DONE 0x000072c0
/* hx83102j-specific register/dsram flags/data */
+#define HIMAX_HX83102J_DSRAM_ADDR_RAW_OUT_SEL 0x100072ec
+#define HIMAX_HX83102J_REG_ADDR_HW_CRC 0x80010000
#define HIMAX_HX83102J_REG_ADDR_TCON_RST 0x80020004
+#define HIMAX_HX83102J_REG_DATA_HW_CRC 0x0000ecce
+#define HIMAX_HX83102J_REG_DATA_HW_CRC_DISABLE 0x00000000
/* hardware register addresses */
#define HIMAX_REG_ADDR_SPI200_DATA 0x8000002c
+#define HIMAX_REG_ADDR_RELOAD_STATUS 0x80050000
+#define HIMAX_REG_ADDR_RELOAD_CRC32_RESULT 0x80050018
+#define HIMAX_REG_ADDR_RELOAD_ADDR_FROM 0x80050020
+#define HIMAX_REG_ADDR_RELOAD_ADDR_CMD_BEAT 0x80050028
+#define HIMAX_REG_ADDR_SYSTEM_RESET 0x90000018
+#define HIMAX_REG_ADDR_RELOAD_TO_ACTIVE 0x90000048
#define HIMAX_REG_ADDR_CTRL_FW 0x9000005c
#define HIMAX_REG_ADDR_FW_STATUS 0x900000a8
#define HIMAX_REG_ADDR_ICID 0x900000d0
+#define HIMAX_REG_ADDR_RESET_FLAG 0x900000e4
+#define HIMAX_REG_ADDR_DD_STATUS 0x900000e8
/* hardware reg data/flags */
#define HIMAX_REG_DATA_FW_STATE_RUNNING 0x05
#define HIMAX_REG_DATA_FW_STATE_SAFE_MODE 0x0c
@@ -89,6 +129,9 @@
#define HIMAX_REG_DATA_FW_GO_SAFEMODE 0xa5
#define HIMAX_REG_DATA_FW_IN_SAFEMODE 0x87
#define HIMAX_REG_DATA_ICID 0x83102900
+#define HIMAX_REG_DATA_RELOAD_DONE 0x01
+#define HIMAX_REG_DATA_RELOAD_PASSWORD 0x99
+#define HIMAX_REG_DATA_SYSTEM_RESET 0x00000055
#define HIMAX_REG_DATA_TCON_RST 0x00000000
/* HIMAX SPI function select, 1st byte of any SPI command sequence */
#define HIMAX_SPI_FUNCTION_READ 0xf3
@@ -100,6 +143,8 @@
#define HIMAX_CFG_VER 0x10000600
#define HIMAX_HID_TABLE 0x30000100
#define HIMAX_FW_BIN_DESC 0x10000000
+#define HIMAX_BOOT_UPGRADE_FWNAME "himax_i2chid"
+#define HIMAX_FW_EXT_NAME ".bin"
/**
* enum himax_hidraw_id_function - HIDRAW report IDs
@@ -119,6 +164,20 @@ enum himax_touch_report_status {
HIMAX_TS_SUCCESS = 0,
};
+/**
+ * struct himax_zf_info - Zero flash update information
+ * @sram_addr: SRAM address byte array buffer
+ * @write_size: Write size of each chunk
+ * @fw_addr: Offset in firmware file
+ * @cfg_addr: target sram address
+ */
+struct himax_zf_info {
+ u8 sram_addr[4];
+ int write_size;
+ u32 fw_addr;
+ u32 cfg_addr;
+};
+
/**
* struct himax_fw_address_table - address/offset in firmware image
* @addr_fw_ver_major: Address to Major version of firmware
@@ -170,9 +229,21 @@ union himax_dword_data {
/**
* struct himax_ic_data - IC information holder
* @stylus_ratio: Stylus ratio
+ * @vendor_cus_info: Vendor customer information
+ * @vendor_proj_info: Vendor project information
+ * @vendor_fw_ver: Vendor firmware version
+ * @vendor_config_ver: Vendor config version
+ * @vendor_touch_cfg_ver: Vendor touch config version
+ * @vendor_display_cfg_ver: Vendor display config version
+ * @vendor_cid_maj_ver: Vendor CID major version
+ * @vendor_cid_min_ver: Vendor CID minor version
+ * @vendor_panel_ver: Vendor panel version
+ * @vendor_sensor_id: Vendor sensor ID
* @rx_num: Number of RX
* @tx_num: Number of TX
* @button_num: Number of buttons
+ * @x_res: X resolution
+ * @y_res: Y resolution
* @max_point: Maximum touch point
* @icid: IC ID
* @interrupt_is_edge: Interrupt is edge otherwise level
@@ -181,9 +252,21 @@ union himax_dword_data {
*/
struct himax_ic_data {
u8 stylus_ratio;
+ u8 vendor_cus_info[12];
+ u8 vendor_proj_info[12];
+ int vendor_fw_ver;
+ int vendor_config_ver;
+ int vendor_touch_cfg_ver;
+ int vendor_display_cfg_ver;
+ int vendor_cid_maj_ver;
+ int vendor_cid_min_ver;
+ int vendor_panel_ver;
+ int vendor_sensor_id;
u32 rx_num;
u32 tx_num;
u32 button_num;
+ u32 x_res;
+ u32 y_res;
u32 max_point;
u32 icid;
bool interrupt_is_edge;
@@ -191,6 +274,38 @@ struct himax_ic_data {
bool stylus_v2;
};
+/**
+ * struct himax_bin_desc - Firmware binary descriptor
+ * @passwd: Password to indicate the binary is valid
+ * @cid: Customer ID
+ * @panel_ver: Panel version
+ * @fw_ver: Firmware version
+ * @ic_sign: IC signature
+ * @customer: Customer name
+ * @project: Project name
+ * @fw_major: Major version of firmware
+ * @fw_minor: Minor version of firmware
+ * @date: Generate date of firmware
+ * @ic_sign_2: IC signature 2
+ *
+ * This structure is used to hold the firmware binary descriptor.
+ * It directly maps to a sequence of bytes in firmware image,
+ * thus need to be packed.
+ */
+struct himax_bin_desc {
+ u16 passwd;
+ u16 cid;
+ u8 panel_ver;
+ u16 fw_ver;
+ u8 ic_sign;
+ char customer[12];
+ char project[12];
+ char fw_major[12];
+ char fw_minor[12];
+ char date[12];
+ char ic_sign_2[12];
+} __packed;
+
/**
* struct himax_hid_desc - HID descriptor
* @desc_length: Length of HID descriptor
@@ -223,8 +338,42 @@ struct himax_hid_desc {
u32 reserved;
} __packed;
+/**
+ * struct himax_hid_info - IC information holder for HIDRAW function
+ * @vid: Vendor ID
+ * @pid: Product ID
+ * @cfg_info: Configuration information
+ * @cfg_version: Configuration version
+ * @disp_version: Display version
+ * @rx: Number of RX
+ * @tx: Number of TX
+ * @y_res: Y resolution
+ * @x_res: X resolution
+ * @pt_num: Number of touch points
+ * @mkey_num: Number of mkey
+ * @debug_info: Debug information
+ *
+ * This structure is used to hold the IC config information for HIDRAW.
+ * The format is binary fixed, thus need to be packed.
+ */
+struct himax_hid_info {
+ u16 vid;
+ u16 pid;
+ u8 cfg_info[32];
+ u8 cfg_version;
+ u8 disp_version;
+ u8 rx;
+ u8 tx;
+ u16 y_res;
+ u16 x_res;
+ u8 pt_num;
+ u8 mkey_num;
+ u8 debug_info[78];
+} __packed;
+
/**
* struct himax_platform_data - Platform data holder
+ * @pid: Product ID
* @is_panel_follower: Is panel follower enabled
* @panel_follower: DRM panel follower
* @gpiod_rst: GPIO reset
@@ -232,6 +381,7 @@ struct himax_hid_desc {
* This structure is used to hold the platform related data.
*/
struct himax_platform_data {
+ u16 pid;
bool is_panel_follower;
struct drm_panel_follower panel_follower;
struct gpio_desc *gpiod_rst;
@@ -242,8 +392,9 @@ struct himax_platform_data {
* @xfer_buf: Interrupt data buffer
* @xfer_rx_data: SPI Transfer receive data buffer
* @xfer_tx_data: SPI Transfer transmit data buffer
- * @himax_fw_data: Firmware data holder from flash
+ * @zf_update_cfg_buffer: Zero flash update configuration buffer
* @himax_irq: IRQ number
+ * @chip_max_dsram_size: Maximum size of DSRAM
* @spi_xfer_max_sz: Size of SPI controller max transfer size
* @xfer_buf_sz: Size of interrupt data buffer
* @irq_state: IRQ state
@@ -252,24 +403,30 @@ struct himax_platform_data {
* @probe_finish: Indicate the driver probe is finished
* @ic_boot_done: Indicate the IC boot is done
* @hid_probed: Indicate the HID device is probed
+ * @resume_succeeded: Indicate the resume is succeeded
+ * @firmware_name: Firmware name
* @touch_data_sz: Size of each interrupt data from IC
+ * @himax_fw: Firmware data holder from user space
* @dev: Device pointer
* @spi: SPI device pointer
* @hid: HID device pointer
* @reg_lock: Mutex lock for reg access
* @rw_lock: Mutex lock for read/write action
+ * @zf_update_lock: Mutex lock for zero-flash FW update
* @ic_data: IC information holder
* @pdata: Platform data holder
* @fw_info_table: Firmware information address table of firmware image
* @hid_desc: HID descriptor
* @hid_rd_data: HID report descriptor data
+ * @initial_work: Delayed work for TP initialization
*/
struct himax_ts_data {
u8 *xfer_buf;
u8 *xfer_rx_data;
u8 *xfer_tx_data;
- u8 *himax_fw_data;
+ u8 *zf_update_cfg_buffer;
s32 himax_irq;
+ u32 chip_max_dsram_size;
u32 spi_xfer_max_sz;
u32 xfer_buf_sz;
atomic_t irq_state;
@@ -279,7 +436,11 @@ struct himax_ts_data {
bool probe_finish;
bool ic_boot_done;
bool hid_probed;
+ bool resume_succeeded;
+ bool zf_update_flag;
+ char firmware_name[64];
int touch_data_sz;
+ const struct firmware *himax_fw;
struct device *dev;
struct spi_device *spi;
struct hid_device *hid;
@@ -287,10 +448,13 @@ struct himax_ts_data {
struct mutex reg_lock;
/* lock for bus read/write action */
struct mutex rw_lock;
+ /* lock for zero-flash FW update */
+ struct mutex zf_update_lock;
struct himax_ic_data ic_data;
struct himax_platform_data pdata;
struct himax_fw_address_table fw_info_table;
struct himax_hid_desc hid_desc;
struct himax_hid_rd_data hid_rd_data;
+ struct delayed_work initial_work;
};
#endif
--
2.34.1
^ permalink raw reply related
* Re: [PATCH v7 2/2] dmaengine: Loongson1: Add Loongson-1 APB DMA driver
From: Keguang Zhang @ 2024-04-02 10:50 UTC (permalink / raw)
To: Huacai Chen
Cc: Vinod Koul, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
linux-mips, dmaengine, devicetree, linux-kernel
In-Reply-To: <CAAhV-H5uLcfaNYb7GAF17ruhJ02Wv71VZYEnxM_a642cuYaSBw@mail.gmail.com>
On Tue, Apr 2, 2024 at 5:04 PM Huacai Chen <chenhuacai@kernel.org> wrote:
>
> On Tue, Apr 2, 2024 at 9:56 AM Keguang Zhang <keguang.zhang@gmail.com> wrote:
> >
> > On Mon, Apr 1, 2024 at 9:24 PM Huacai Chen <chenhuacai@kernel.org> wrote:
> > >
> > > On Mon, Apr 1, 2024 at 7:10 PM Keguang Zhang <keguang.zhang@gmail.com> wrote:
> > > >
> > > > On Mon, Apr 1, 2024 at 5:06 PM Huacai Chen <chenhuacai@kernel.org> wrote:
> > > > >
> > > > > On Mon, Apr 1, 2024 at 10:45 AM Keguang Zhang <keguang.zhang@gmail.com> wrote:
> > > > > >
> > > > > > Hi Huacai,
> > > > > >
> > > > > > On Sat, Mar 30, 2024 at 9:59 PM Huacai Chen <chenhuacai@kernel.org> wrote:
> > > > > > >
> > > > > > > Hi, Keguang,
> > > > > > >
> > > > > > > On Fri, Mar 29, 2024 at 7:28 PM Keguang Zhang via B4 Relay
> > > > > > > <devnull+keguang.zhang.gmail.com@kernel.org> wrote:
> > > > > > > >
> > > > > > > > From: Keguang Zhang <keguang.zhang@gmail.com>
> > > > > > > >
> > > > > > > > This patch adds APB DMA driver for Loongson-1 SoCs.
> > > > > > > >
> > > > > > > > Signed-off-by: Keguang Zhang <keguang.zhang@gmail.com>
> > > > > > > > ---
> > > > > > > > Changes in v7:
> > > > > > > > - Change the comptible to 'loongson,ls1*-apbdma'
> > > > > > > > - Update Kconfig and Makefile accordingly
> > > > > > > > - Rename the file to loongson1-apb-dma.c to keep the consistency
> > > > > > > >
> > > > > > > > Changes in v6:
> > > > > > > > - Implement .device_prep_dma_cyclic for Loongson1 audio driver,
> > > > > > > > - as well as .device_pause and .device_resume.
> > > > > > > > - Set the limitation LS1X_DMA_MAX_DESC and put all descriptors
> > > > > > > > - into one page to save memory
> > > > > > > > - Move dma_pool_zalloc() into ls1x_dma_alloc_desc()
> > > > > > > > - Drop dma_slave_config structure
> > > > > > > > - Use .remove_new instead of .remove
> > > > > > > > - Use KBUILD_MODNAME for the driver name
> > > > > > > > - Improve the debug information
> > > > > > > >
> > > > > > > > Changes in v5:
> > > > > > > > - Add DT support
> > > > > > > > - Use DT data instead of platform data
> > > > > > > > - Use chan_id of struct dma_chan instead of own id
> > > > > > > > - Use of_dma_xlate_by_chan_id() instead of ls1x_dma_filter()
> > > > > > > > - Update the author information to my official name
> > > > > > > >
> > > > > > > > Changes in v4:
> > > > > > > > - Use dma_slave_map to find the proper channel.
> > > > > > > > - Explicitly call devm_request_irq() and tasklet_kill().
> > > > > > > > - Fix namespace issue.
> > > > > > > > - Some minor fixes and cleanups.
> > > > > > > >
> > > > > > > > Changes in v3:
> > > > > > > > - Rename ls1x_dma_filter_fn to ls1x_dma_filter.
> > > > > > > >
> > > > > > > > Changes in v2:
> > > > > > > > - Change the config from 'DMA_LOONGSON1' to 'LOONGSON1_DMA',
> > > > > > > > - and rearrange it in alphabetical order in Kconfig and Makefile.
> > > > > > > > - Fix comment style.
> > > > > > > > ---
> > > > > > > > drivers/dma/Kconfig | 9 +
> > > > > > > > drivers/dma/Makefile | 1 +
> > > > > > > > drivers/dma/loongson1-apb-dma.c | 665 ++++++++++++++++++++++++++++++++++++++++
> > > > > > > > 3 files changed, 675 insertions(+)
> > > > > > > >
> > > > > > > > diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
> > > > > > > > index 002a5ec80620..f7b06c4cdf3f 100644
> > > > > > > > --- a/drivers/dma/Kconfig
> > > > > > > > +++ b/drivers/dma/Kconfig
> > > > > > > > @@ -369,6 +369,15 @@ config K3_DMA
> > > > > > > > Support the DMA engine for Hisilicon K3 platform
> > > > > > > > devices.
> > > > > > > >
> > > > > > > > +config LOONGSON1_APB_DMA
> > > > > > > > + tristate "Loongson1 APB DMA support"
> > > > > > > > + depends on MACH_LOONGSON32 || COMPILE_TEST
> > > > > > > > + select DMA_ENGINE
> > > > > > > > + select DMA_VIRTUAL_CHANNELS
> > > > > > > > + help
> > > > > > > > + This selects support for the APB DMA controller in Loongson1 SoCs,
> > > > > > > > + which is required by Loongson1 NAND and audio support.
> > > > > > > Why not rename to LS1X_APB_DMA and put it just before LS2X_APB_DMA
> > > > > > > (and also the driver file name)?
> > > > > > >
> > > > > > So far all Kconfig entries of Loongson-1 drivers are named with the
> > > > > > keyword "LOONGSON1".
> > > > > > The same is true for these file names.
> > > > > > Therefore, I need to keep the consistency.
> > > > > But I see LS1X_IRQ in drivers/irqchip/Kconfig
> > > > >
> > > > Indeed, that's an exception, which was submitted by Jiaxun several years ago.
> > > > Actually, most drivers of Loongson family use the keyword "LOONGSON"
> > > > for Kconfig and "loongson" for filename.
> > > > Thus I take this keywork as the naming convention.
> > > But I think keeping consistency in a same subsystem is better than
> > > keeping consistency in a same SoC (but cross subsystems).
> > >
> > In my opinion, "LS*X" is too short and may be confused with other SoCs.
> > Meanwhile, there are only four drivers that use this keyword.
> > config I2C_LS2X
> > config LS2K_RESET
> > config LS2X_APB_DMA
> > config LS1X_IRQ
> > Then, my suggestion is to change these "LS*X" to "LOONGSON*" to get a
> > clear meaning.
> We have made a naming conversion some years before with Jiaxun.
> 1, Use "Loongson" for CPU in arch code;
> 2, Use "LS7A" or something like this for bridges and devices.
> 3, For drivers in SoC, if the driver is specific to Loongson-1, use
> LS1X, if it is to Loongson-2, use LS2X, if it is shared by both
> Loongson-1 and Loongson-2, use LOONGSON.
>
OK. But the doesn't the answer the question of confusion, such as
"Freescale LS1021A".
The same problem happens to the filenames.
./drivers/gpu/drm/nouveau/nvkm/nvfw/ls.c
./drivers/gpu/drm/nouveau/nvkm/subdev/acr/lsfw.c
./drivers/gpu/drm/amd/amdgpu/lsdma_v6_0.c
./drivers/gpu/drm/amd/amdgpu/lsdma_v7_0.c
./arch/powerpc/platforms/embedded6xx/ls_uart.c
Regarding "LS*X" itself, it contains the wildcard character "X" which
itself is confusing.
Therefore, I don't think "LS*X" is clear enough.
On the other hand, I see "LOONGSON2_*" strings are still there.
In addition, some of "LOONGSON_" definitions are not applicable for
Loongson-1 at all, which breaks your convention.
config SND_SOC_LOONGSON_I2S_PCI /* Loongson-1 doesn't support I2S */
config SND_SOC_LOONGSON_CARD
config DWMAC_LOONGSON1
config DWMAC_LOONGSON /* This glue layer doesn't support Loongson-1 */
config COMMON_CLK_LOONGSON2
config RTC_DRV_LOONGSON
config SPI_LOONGSON_CORE
config SPI_LOONGSON_PCI /* N/A for Loongson-1 */
config SPI_LOONGSON_PLATFORM
config LOONGSON2_CPUFREQ
config DRM_LOONGSON /* N/A for Loongson-1 */
config LOONGSON1_WDT
config CLKSRC_LOONGSON1_PWM
config LOONGSON_LIOINTC /* N/A for Loongson-1 */
config LOONGSON_EIOINTC /* N/A for Loongson-1 */
config LOONGSON_HTPIC /* N/A for Loongson-1 */
config LOONGSON_HTVEC /* N/A for Loongson-1 */
config LOONGSON_PCH_PIC /* N/A for Loongson-1 */
config LOONGSON_PCH_MSI /* N/A for Loongson-1 */
config LOONGSON_PCH_LPC /* N/A for Loongson-1 */
config PINCTRL_LOONGSON2
config LOONGSON2_THERMAL
config LOONGSON2_GUTS
config LOONGSON2_PM
config LOONGSON_LAPTOP /* N/A for Loongson-1 */
config GPIO_LOONGSON
config GPIO_LOONGSON_64BIT -> N/A for Loongson-1
config GPIO_LOONGSON1
config PCI_LOONGSON
What's your plan about the above Kconfig entries?
Why can't we use LOONGSON1/LOONGSON2 for drivers?
> Huacai
>
> >
> > > Huacai
> > >
> > > >
> > > > > Huacai
> > > > >
> > > > > >
> > > > > >
> > > > > > > Huacai
> > > > > > >
> > > > > > > > +
> > > > > > > > config LPC18XX_DMAMUX
> > > > > > > > bool "NXP LPC18xx/43xx DMA MUX for PL080"
> > > > > > > > depends on ARCH_LPC18XX || COMPILE_TEST
> > > > > > > > diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile
> > > > > > > > index dfd40d14e408..b26f6677978a 100644
> > > > > > > > --- a/drivers/dma/Makefile
> > > > > > > > +++ b/drivers/dma/Makefile
> > > > > > > > @@ -47,6 +47,7 @@ obj-$(CONFIG_INTEL_IDMA64) += idma64.o
> > > > > > > > obj-$(CONFIG_INTEL_IOATDMA) += ioat/
> > > > > > > > obj-y += idxd/
> > > > > > > > obj-$(CONFIG_K3_DMA) += k3dma.o
> > > > > > > > +obj-$(CONFIG_LOONGSON1_APB_DMA) += loongson1-apb-dma.o
> > > > > > > > obj-$(CONFIG_LPC18XX_DMAMUX) += lpc18xx-dmamux.o
> > > > > > > > obj-$(CONFIG_LS2X_APB_DMA) += ls2x-apb-dma.o
> > > > > > > > obj-$(CONFIG_MILBEAUT_HDMAC) += milbeaut-hdmac.o
> > > > > > > > diff --git a/drivers/dma/loongson1-apb-dma.c b/drivers/dma/loongson1-apb-dma.c
> > > > > > > > new file mode 100644
> > > > > > > > index 000000000000..d474a2601e6e
> > > > > > > > --- /dev/null
> > > > > > > > +++ b/drivers/dma/loongson1-apb-dma.c
> > > > > > > > @@ -0,0 +1,665 @@
> > > > > > > > +// SPDX-License-Identifier: GPL-2.0-or-later
> > > > > > > > +/*
> > > > > > > > + * Driver for Loongson-1 APB DMA Controller
> > > > > > > > + *
> > > > > > > > + * Copyright (C) 2015-2024 Keguang Zhang <keguang.zhang@gmail.com>
> > > > > > > > + */
> > > > > > > > +
> > > > > > > > +#include <linux/dmapool.h>
> > > > > > > > +#include <linux/dma-mapping.h>
> > > > > > > > +#include <linux/init.h>
> > > > > > > > +#include <linux/interrupt.h>
> > > > > > > > +#include <linux/iopoll.h>
> > > > > > > > +#include <linux/module.h>
> > > > > > > > +#include <linux/of.h>
> > > > > > > > +#include <linux/of_dma.h>
> > > > > > > > +#include <linux/platform_device.h>
> > > > > > > > +#include <linux/slab.h>
> > > > > > > > +
> > > > > > > > +#include "dmaengine.h"
> > > > > > > > +#include "virt-dma.h"
> > > > > > > > +
> > > > > > > > +/* Loongson-1 DMA Control Register */
> > > > > > > > +#define DMA_CTRL 0x0
> > > > > > > > +
> > > > > > > > +/* DMA Control Register Bits */
> > > > > > > > +#define DMA_STOP BIT(4)
> > > > > > > > +#define DMA_START BIT(3)
> > > > > > > > +#define DMA_ASK_VALID BIT(2)
> > > > > > > > +
> > > > > > > > +#define DMA_ADDR_MASK GENMASK(31, 6)
> > > > > > > > +
> > > > > > > > +/* DMA Next Field Bits */
> > > > > > > > +#define DMA_NEXT_VALID BIT(0)
> > > > > > > > +
> > > > > > > > +/* DMA Command Field Bits */
> > > > > > > > +#define DMA_RAM2DEV BIT(12)
> > > > > > > > +#define DMA_INT BIT(1)
> > > > > > > > +#define DMA_INT_MASK BIT(0)
> > > > > > > > +
> > > > > > > > +#define LS1X_DMA_MAX_CHANNELS 3
> > > > > > > > +
> > > > > > > > +/* Size of allocations for hardware descriptors */
> > > > > > > > +#define LS1X_DMA_DESCS_SIZE PAGE_SIZE
> > > > > > > > +#define LS1X_DMA_MAX_DESC \
> > > > > > > > + (LS1X_DMA_DESCS_SIZE / sizeof(struct ls1x_dma_hwdesc))
> > > > > > > > +
> > > > > > > > +struct ls1x_dma_hwdesc {
> > > > > > > > + u32 next; /* next descriptor address */
> > > > > > > > + u32 saddr; /* memory DMA address */
> > > > > > > > + u32 daddr; /* device DMA address */
> > > > > > > > + u32 length;
> > > > > > > > + u32 stride;
> > > > > > > > + u32 cycles;
> > > > > > > > + u32 cmd;
> > > > > > > > + u32 stats;
> > > > > > > > +};
> > > > > > > > +
> > > > > > > > +struct ls1x_dma_desc {
> > > > > > > > + struct virt_dma_desc vdesc;
> > > > > > > > + enum dma_transfer_direction dir;
> > > > > > > > + enum dma_transaction_type type;
> > > > > > > > + unsigned int bus_width;
> > > > > > > > +
> > > > > > > > + unsigned int nr_descs; /* number of descriptors */
> > > > > > > > +
> > > > > > > > + struct ls1x_dma_hwdesc *hwdesc;
> > > > > > > > + dma_addr_t hwdesc_phys;
> > > > > > > > +};
> > > > > > > > +
> > > > > > > > +struct ls1x_dma_chan {
> > > > > > > > + struct virt_dma_chan vchan;
> > > > > > > > + struct dma_pool *desc_pool;
> > > > > > > > + phys_addr_t src_addr;
> > > > > > > > + phys_addr_t dst_addr;
> > > > > > > > + enum dma_slave_buswidth src_addr_width;
> > > > > > > > + enum dma_slave_buswidth dst_addr_width;
> > > > > > > > +
> > > > > > > > + void __iomem *reg_base;
> > > > > > > > + int irq;
> > > > > > > > +
> > > > > > > > + struct ls1x_dma_desc *desc;
> > > > > > > > +
> > > > > > > > + struct ls1x_dma_hwdesc *curr_hwdesc;
> > > > > > > > + dma_addr_t curr_hwdesc_phys;
> > > > > > > > +};
> > > > > > > > +
> > > > > > > > +struct ls1x_dma {
> > > > > > > > + struct dma_device ddev;
> > > > > > > > + void __iomem *reg_base;
> > > > > > > > +
> > > > > > > > + unsigned int nr_chans;
> > > > > > > > + struct ls1x_dma_chan chan[];
> > > > > > > > +};
> > > > > > > > +
> > > > > > > > +#define to_ls1x_dma_chan(dchan) \
> > > > > > > > + container_of(dchan, struct ls1x_dma_chan, vchan.chan)
> > > > > > > > +
> > > > > > > > +#define to_ls1x_dma_desc(vd) \
> > > > > > > > + container_of(vd, struct ls1x_dma_desc, vdesc)
> > > > > > > > +
> > > > > > > > +/* macros for registers read/write */
> > > > > > > > +#define chan_readl(chan, off) \
> > > > > > > > + readl((chan)->reg_base + (off))
> > > > > > > > +
> > > > > > > > +#define chan_writel(chan, off, val) \
> > > > > > > > + writel((val), (chan)->reg_base + (off))
> > > > > > > > +
> > > > > > > > +static inline struct device *chan2dev(struct dma_chan *chan)
> > > > > > > > +{
> > > > > > > > + return &chan->dev->device;
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +static inline int ls1x_dma_query(struct ls1x_dma_chan *chan,
> > > > > > > > + dma_addr_t *hwdesc_phys)
> > > > > > > > +{
> > > > > > > > + struct dma_chan *dchan = &chan->vchan.chan;
> > > > > > > > + int val, ret;
> > > > > > > > +
> > > > > > > > + val = *hwdesc_phys & DMA_ADDR_MASK;
> > > > > > > > + val |= DMA_ASK_VALID;
> > > > > > > > + val |= dchan->chan_id;
> > > > > > > > + chan_writel(chan, DMA_CTRL, val);
> > > > > > > > + ret = readl_poll_timeout_atomic(chan->reg_base + DMA_CTRL, val,
> > > > > > > > + !(val & DMA_ASK_VALID), 0, 3000);
> > > > > > > > + if (ret)
> > > > > > > > + dev_err(chan2dev(dchan), "failed to query DMA\n");
> > > > > > > > +
> > > > > > > > + return ret;
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +static inline int ls1x_dma_start(struct ls1x_dma_chan *chan,
> > > > > > > > + dma_addr_t *hwdesc_phys)
> > > > > > > > +{
> > > > > > > > + struct dma_chan *dchan = &chan->vchan.chan;
> > > > > > > > + int val, ret;
> > > > > > > > +
> > > > > > > > + dev_dbg(chan2dev(dchan), "cookie=%d, starting hwdesc=%x\n",
> > > > > > > > + dchan->cookie, *hwdesc_phys);
> > > > > > > > +
> > > > > > > > + val = *hwdesc_phys & DMA_ADDR_MASK;
> > > > > > > > + val |= DMA_START;
> > > > > > > > + val |= dchan->chan_id;
> > > > > > > > + chan_writel(chan, DMA_CTRL, val);
> > > > > > > > + ret = readl_poll_timeout(chan->reg_base + DMA_CTRL, val,
> > > > > > > > + !(val & DMA_START), 0, 3000);
> > > > > > > > + if (ret)
> > > > > > > > + dev_err(chan2dev(dchan), "failed to start DMA\n");
> > > > > > > > +
> > > > > > > > + return ret;
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +static inline void ls1x_dma_stop(struct ls1x_dma_chan *chan)
> > > > > > > > +{
> > > > > > > > + chan_writel(chan, DMA_CTRL, chan_readl(chan, DMA_CTRL) | DMA_STOP);
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +static void ls1x_dma_free_chan_resources(struct dma_chan *dchan)
> > > > > > > > +{
> > > > > > > > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
> > > > > > > > +
> > > > > > > > + dma_free_coherent(chan2dev(dchan), sizeof(struct ls1x_dma_hwdesc),
> > > > > > > > + chan->curr_hwdesc, chan->curr_hwdesc_phys);
> > > > > > > > + vchan_free_chan_resources(&chan->vchan);
> > > > > > > > + dma_pool_destroy(chan->desc_pool);
> > > > > > > > + chan->desc_pool = NULL;
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +static int ls1x_dma_alloc_chan_resources(struct dma_chan *dchan)
> > > > > > > > +{
> > > > > > > > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
> > > > > > > > +
> > > > > > > > + chan->desc_pool = dma_pool_create(dma_chan_name(dchan),
> > > > > > > > + chan2dev(dchan),
> > > > > > > > + sizeof(struct ls1x_dma_hwdesc),
> > > > > > > > + __alignof__(struct ls1x_dma_hwdesc),
> > > > > > > > + 0);
> > > > > > > > + if (!chan->desc_pool)
> > > > > > > > + return -ENOMEM;
> > > > > > > > +
> > > > > > > > + /* allocate memory for querying current HW descriptor */
> > > > > > > > + dma_set_coherent_mask(chan2dev(dchan), DMA_BIT_MASK(32));
> > > > > > > > + chan->curr_hwdesc = dma_alloc_coherent(chan2dev(dchan),
> > > > > > > > + sizeof(struct ls1x_dma_hwdesc),
> > > > > > > > + &chan->curr_hwdesc_phys,
> > > > > > > > + GFP_KERNEL);
> > > > > > > > + if (!chan->curr_hwdesc)
> > > > > > > > + return -ENOMEM;
> > > > > > > > +
> > > > > > > > + return 0;
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +static void ls1x_dma_free_desc(struct virt_dma_desc *vdesc)
> > > > > > > > +{
> > > > > > > > + struct ls1x_dma_desc *desc = to_ls1x_dma_desc(vdesc);
> > > > > > > > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(vdesc->tx.chan);
> > > > > > > > +
> > > > > > > > + dma_pool_free(chan->desc_pool, desc->hwdesc, desc->hwdesc_phys);
> > > > > > > > + chan->desc = NULL;
> > > > > > > > + kfree(desc);
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +static struct ls1x_dma_desc *
> > > > > > > > +ls1x_dma_alloc_desc(struct dma_chan *dchan, int sg_len,
> > > > > > > > + enum dma_transfer_direction direction,
> > > > > > > > + enum dma_transaction_type type)
> > > > > > > > +{
> > > > > > > > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
> > > > > > > > + struct ls1x_dma_desc *desc;
> > > > > > > > +
> > > > > > > > + if (sg_len > LS1X_DMA_MAX_DESC) {
> > > > > > > > + dev_err(chan2dev(dchan), "sg_len %u exceeds limit %lu",
> > > > > > > > + sg_len, LS1X_DMA_MAX_DESC);
> > > > > > > > + return NULL;
> > > > > > > > + }
> > > > > > > > +
> > > > > > > > + desc = kzalloc(sizeof(*desc), GFP_NOWAIT);
> > > > > > > > + if (!desc)
> > > > > > > > + return NULL;
> > > > > > > > +
> > > > > > > > + /* allocate HW descriptors */
> > > > > > > > + desc->hwdesc = dma_pool_zalloc(chan->desc_pool, GFP_NOWAIT,
> > > > > > > > + &desc->hwdesc_phys);
> > > > > > > > + if (!desc->hwdesc) {
> > > > > > > > + dev_err(chan2dev(dchan), "failed to alloc HW descriptors\n");
> > > > > > > > + ls1x_dma_free_desc(&desc->vdesc);
> > > > > > > > + return NULL;
> > > > > > > > + }
> > > > > > > > +
> > > > > > > > + desc->dir = direction;
> > > > > > > > + desc->type = type;
> > > > > > > > + desc->nr_descs = sg_len;
> > > > > > > > +
> > > > > > > > + return desc;
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +static int ls1x_dma_setup_hwdescs(struct dma_chan *dchan,
> > > > > > > > + struct ls1x_dma_desc *desc,
> > > > > > > > + struct scatterlist *sgl, unsigned int sg_len)
> > > > > > > > +{
> > > > > > > > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
> > > > > > > > + dma_addr_t next_hwdesc_phys = desc->hwdesc_phys;
> > > > > > > > +
> > > > > > > > + struct scatterlist *sg;
> > > > > > > > + unsigned int dev_addr, cmd, i;
> > > > > > > > +
> > > > > > > > + switch (desc->dir) {
> > > > > > > > + case DMA_MEM_TO_DEV:
> > > > > > > > + dev_addr = chan->dst_addr;
> > > > > > > > + desc->bus_width = chan->dst_addr_width;
> > > > > > > > + cmd = DMA_RAM2DEV | DMA_INT;
> > > > > > > > + break;
> > > > > > > > + case DMA_DEV_TO_MEM:
> > > > > > > > + dev_addr = chan->src_addr;
> > > > > > > > + desc->bus_width = chan->src_addr_width;
> > > > > > > > + cmd = DMA_INT;
> > > > > > > > + break;
> > > > > > > > + default:
> > > > > > > > + dev_err(chan2dev(dchan), "unsupported DMA direction: %s\n",
> > > > > > > > + dmaengine_get_direction_text(desc->dir));
> > > > > > > > + return -EINVAL;
> > > > > > > > + }
> > > > > > > > +
> > > > > > > > + /* setup HW descriptors */
> > > > > > > > + for_each_sg(sgl, sg, sg_len, i) {
> > > > > > > > + dma_addr_t buf_addr = sg_dma_address(sg);
> > > > > > > > + size_t buf_len = sg_dma_len(sg);
> > > > > > > > + struct ls1x_dma_hwdesc *hwdesc = &desc->hwdesc[i];
> > > > > > > > +
> > > > > > > > + if (!is_dma_copy_aligned(dchan->device, buf_addr, 0, buf_len)) {
> > > > > > > > + dev_err(chan2dev(dchan), "buffer is not aligned!\n");
> > > > > > > > + return -EINVAL;
> > > > > > > > + }
> > > > > > > > +
> > > > > > > > + hwdesc->saddr = buf_addr;
> > > > > > > > + hwdesc->daddr = dev_addr;
> > > > > > > > + hwdesc->length = buf_len / desc->bus_width;
> > > > > > > > + hwdesc->stride = 0;
> > > > > > > > + hwdesc->cycles = 1;
> > > > > > > > + hwdesc->cmd = cmd;
> > > > > > > > +
> > > > > > > > + if (i) {
> > > > > > > > + next_hwdesc_phys += sizeof(*hwdesc);
> > > > > > > > + desc->hwdesc[i - 1].next = next_hwdesc_phys
> > > > > > > > + | DMA_NEXT_VALID;
> > > > > > > > + }
> > > > > > > > + }
> > > > > > > > +
> > > > > > > > + if (desc->type == DMA_CYCLIC)
> > > > > > > > + desc->hwdesc[i - 1].next = desc->hwdesc_phys | DMA_NEXT_VALID;
> > > > > > > > +
> > > > > > > > + for_each_sg(sgl, sg, sg_len, i) {
> > > > > > > > + struct ls1x_dma_hwdesc *hwdesc = &desc->hwdesc[i];
> > > > > > > > +
> > > > > > > > + print_hex_dump_debug("HW DESC: ", DUMP_PREFIX_OFFSET, 16, 4,
> > > > > > > > + hwdesc, sizeof(*hwdesc), false);
> > > > > > > > + }
> > > > > > > > +
> > > > > > > > + return 0;
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +static struct dma_async_tx_descriptor *
> > > > > > > > +ls1x_dma_prep_slave_sg(struct dma_chan *dchan,
> > > > > > > > + struct scatterlist *sgl, unsigned int sg_len,
> > > > > > > > + enum dma_transfer_direction direction,
> > > > > > > > + unsigned long flags, void *context)
> > > > > > > > +{
> > > > > > > > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
> > > > > > > > + struct ls1x_dma_desc *desc;
> > > > > > > > +
> > > > > > > > + dev_dbg(chan2dev(dchan), "sg_len=%u flags=0x%lx dir=%s\n",
> > > > > > > > + sg_len, flags, dmaengine_get_direction_text(direction));
> > > > > > > > +
> > > > > > > > + desc = ls1x_dma_alloc_desc(dchan, sg_len, direction, DMA_SLAVE);
> > > > > > > > + if (!desc)
> > > > > > > > + return NULL;
> > > > > > > > +
> > > > > > > > + if (ls1x_dma_setup_hwdescs(dchan, desc, sgl, sg_len)) {
> > > > > > > > + ls1x_dma_free_desc(&desc->vdesc);
> > > > > > > > + return NULL;
> > > > > > > > + }
> > > > > > > > +
> > > > > > > > + return vchan_tx_prep(&chan->vchan, &desc->vdesc, flags);
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +static struct dma_async_tx_descriptor *
> > > > > > > > +ls1x_dma_prep_dma_cyclic(struct dma_chan *dchan,
> > > > > > > > + dma_addr_t buf_addr, size_t buf_len, size_t period_len,
> > > > > > > > + enum dma_transfer_direction direction,
> > > > > > > > + unsigned long flags)
> > > > > > > > +{
> > > > > > > > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
> > > > > > > > + struct ls1x_dma_desc *desc;
> > > > > > > > + struct scatterlist *sgl;
> > > > > > > > + unsigned int sg_len;
> > > > > > > > + unsigned int i;
> > > > > > > > +
> > > > > > > > + dev_dbg(chan2dev(dchan),
> > > > > > > > + "buf_len=%d period_len=%zu flags=0x%lx dir=%s\n", buf_len,
> > > > > > > > + period_len, flags, dmaengine_get_direction_text(direction));
> > > > > > > > +
> > > > > > > > + sg_len = buf_len / period_len;
> > > > > > > > + desc = ls1x_dma_alloc_desc(dchan, sg_len, direction, DMA_CYCLIC);
> > > > > > > > + if (!desc)
> > > > > > > > + return NULL;
> > > > > > > > +
> > > > > > > > + /* allocate the scatterlist */
> > > > > > > > + sgl = kmalloc_array(sg_len, sizeof(*sgl), GFP_NOWAIT);
> > > > > > > > + if (!sgl)
> > > > > > > > + return NULL;
> > > > > > > > +
> > > > > > > > + sg_init_table(sgl, sg_len);
> > > > > > > > + for (i = 0; i < sg_len; ++i) {
> > > > > > > > + sg_set_page(&sgl[i], pfn_to_page(PFN_DOWN(buf_addr)),
> > > > > > > > + period_len, offset_in_page(buf_addr));
> > > > > > > > + sg_dma_address(&sgl[i]) = buf_addr;
> > > > > > > > + sg_dma_len(&sgl[i]) = period_len;
> > > > > > > > + buf_addr += period_len;
> > > > > > > > + }
> > > > > > > > +
> > > > > > > > + if (ls1x_dma_setup_hwdescs(dchan, desc, sgl, sg_len)) {
> > > > > > > > + ls1x_dma_free_desc(&desc->vdesc);
> > > > > > > > + return NULL;
> > > > > > > > + }
> > > > > > > > +
> > > > > > > > + kfree(sgl);
> > > > > > > > +
> > > > > > > > + return vchan_tx_prep(&chan->vchan, &desc->vdesc, flags);
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +static int ls1x_dma_slave_config(struct dma_chan *dchan,
> > > > > > > > + struct dma_slave_config *config)
> > > > > > > > +{
> > > > > > > > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
> > > > > > > > +
> > > > > > > > + chan->src_addr = config->src_addr;
> > > > > > > > + chan->src_addr_width = config->src_addr_width;
> > > > > > > > + chan->dst_addr = config->dst_addr;
> > > > > > > > + chan->dst_addr_width = config->dst_addr_width;
> > > > > > > > +
> > > > > > > > + return 0;
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +static int ls1x_dma_pause(struct dma_chan *dchan)
> > > > > > > > +{
> > > > > > > > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
> > > > > > > > + unsigned long flags;
> > > > > > > > + int ret;
> > > > > > > > +
> > > > > > > > + spin_lock_irqsave(&chan->vchan.lock, flags);
> > > > > > > > + ret = ls1x_dma_query(chan, &chan->curr_hwdesc_phys);
> > > > > > > > + if (!ret)
> > > > > > > > + ls1x_dma_stop(chan);
> > > > > > > > + spin_unlock_irqrestore(&chan->vchan.lock, flags);
> > > > > > > > +
> > > > > > > > + return ret;
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +static int ls1x_dma_resume(struct dma_chan *dchan)
> > > > > > > > +{
> > > > > > > > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
> > > > > > > > + unsigned long flags;
> > > > > > > > + int ret;
> > > > > > > > +
> > > > > > > > + spin_lock_irqsave(&chan->vchan.lock, flags);
> > > > > > > > + ret = ls1x_dma_start(chan, &chan->curr_hwdesc_phys);
> > > > > > > > + spin_unlock_irqrestore(&chan->vchan.lock, flags);
> > > > > > > > +
> > > > > > > > + return ret;
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +static int ls1x_dma_terminate_all(struct dma_chan *dchan)
> > > > > > > > +{
> > > > > > > > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
> > > > > > > > + unsigned long flags;
> > > > > > > > + LIST_HEAD(head);
> > > > > > > > +
> > > > > > > > + spin_lock_irqsave(&chan->vchan.lock, flags);
> > > > > > > > + ls1x_dma_stop(chan);
> > > > > > > > + vchan_get_all_descriptors(&chan->vchan, &head);
> > > > > > > > + spin_unlock_irqrestore(&chan->vchan.lock, flags);
> > > > > > > > +
> > > > > > > > + vchan_dma_desc_free_list(&chan->vchan, &head);
> > > > > > > > +
> > > > > > > > + return 0;
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +static enum dma_status ls1x_dma_tx_status(struct dma_chan *dchan,
> > > > > > > > + dma_cookie_t cookie,
> > > > > > > > + struct dma_tx_state *state)
> > > > > > > > +{
> > > > > > > > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
> > > > > > > > + struct virt_dma_desc *vdesc;
> > > > > > > > + enum dma_status status;
> > > > > > > > + size_t bytes = 0;
> > > > > > > > + unsigned long flags;
> > > > > > > > +
> > > > > > > > + status = dma_cookie_status(dchan, cookie, state);
> > > > > > > > + if (status == DMA_COMPLETE)
> > > > > > > > + return status;
> > > > > > > > +
> > > > > > > > + spin_lock_irqsave(&chan->vchan.lock, flags);
> > > > > > > > + vdesc = vchan_find_desc(&chan->vchan, cookie);
> > > > > > > > + if (chan->desc && cookie == chan->desc->vdesc.tx.cookie) {
> > > > > > > > + struct ls1x_dma_desc *desc = chan->desc;
> > > > > > > > + int i;
> > > > > > > > +
> > > > > > > > + if (ls1x_dma_query(chan, &chan->curr_hwdesc_phys))
> > > > > > > > + return status;
> > > > > > > > +
> > > > > > > > + /* locate the current HW descriptor */
> > > > > > > > + for (i = 0; i < desc->nr_descs; i++)
> > > > > > > > + if (desc->hwdesc[i].next == chan->curr_hwdesc->next)
> > > > > > > > + break;
> > > > > > > > +
> > > > > > > > + /* count the residues */
> > > > > > > > + for (; i < desc->nr_descs; i++)
> > > > > > > > + bytes += desc->hwdesc[i].length * desc->bus_width;
> > > > > > > > +
> > > > > > > > + dma_set_residue(state, bytes);
> > > > > > > > + }
> > > > > > > > + spin_unlock_irqrestore(&chan->vchan.lock, flags);
> > > > > > > > +
> > > > > > > > + return status;
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +static void ls1x_dma_issue_pending(struct dma_chan *dchan)
> > > > > > > > +{
> > > > > > > > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
> > > > > > > > + struct virt_dma_desc *vdesc;
> > > > > > > > + unsigned long flags;
> > > > > > > > +
> > > > > > > > + spin_lock_irqsave(&chan->vchan.lock, flags);
> > > > > > > > + if (vchan_issue_pending(&chan->vchan) && !chan->desc) {
> > > > > > > > + vdesc = vchan_next_desc(&chan->vchan);
> > > > > > > > + if (!vdesc) {
> > > > > > > > + chan->desc = NULL;
> > > > > > > > + return;
> > > > > > > > + }
> > > > > > > > + chan->desc = to_ls1x_dma_desc(vdesc);
> > > > > > > > + ls1x_dma_start(chan, &chan->desc->hwdesc_phys);
> > > > > > > > + }
> > > > > > > > + spin_unlock_irqrestore(&chan->vchan.lock, flags);
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +static irqreturn_t ls1x_dma_irq_handler(int irq, void *data)
> > > > > > > > +{
> > > > > > > > + struct ls1x_dma_chan *chan = data;
> > > > > > > > + struct ls1x_dma_desc *desc = chan->desc;
> > > > > > > > + struct dma_chan *dchan = &chan->vchan.chan;
> > > > > > > > +
> > > > > > > > + if (!desc) {
> > > > > > > > + dev_warn(chan2dev(dchan),
> > > > > > > > + "IRQ %d with no active descriptor on channel %d\n",
> > > > > > > > + irq, dchan->chan_id);
> > > > > > > > + return IRQ_NONE;
> > > > > > > > + }
> > > > > > > > +
> > > > > > > > + dev_dbg(chan2dev(dchan), "DMA IRQ %d on channel %d\n", irq,
> > > > > > > > + dchan->chan_id);
> > > > > > > > +
> > > > > > > > + spin_lock(&chan->vchan.lock);
> > > > > > > > +
> > > > > > > > + if (desc->type == DMA_CYCLIC) {
> > > > > > > > + vchan_cyclic_callback(&desc->vdesc);
> > > > > > > > + } else {
> > > > > > > > + list_del(&desc->vdesc.node);
> > > > > > > > + vchan_cookie_complete(&desc->vdesc);
> > > > > > > > + chan->desc = NULL;
> > > > > > > > + }
> > > > > > > > +
> > > > > > > > + spin_unlock(&chan->vchan.lock);
> > > > > > > > + return IRQ_HANDLED;
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +static int ls1x_dma_chan_probe(struct platform_device *pdev,
> > > > > > > > + struct ls1x_dma *dma, int chan_id)
> > > > > > > > +{
> > > > > > > > + struct device *dev = &pdev->dev;
> > > > > > > > + struct ls1x_dma_chan *chan = &dma->chan[chan_id];
> > > > > > > > + char pdev_irqname[4];
> > > > > > > > + char *irqname;
> > > > > > > > + int ret;
> > > > > > > > +
> > > > > > > > + sprintf(pdev_irqname, "ch%u", chan_id);
> > > > > > > > + chan->irq = platform_get_irq_byname(pdev, pdev_irqname);
> > > > > > > > + if (chan->irq < 0)
> > > > > > > > + return -ENODEV;
> > > > > > > > +
> > > > > > > > + irqname = devm_kasprintf(dev, GFP_KERNEL, "%s:%s",
> > > > > > > > + dev_name(dev), pdev_irqname);
> > > > > > > > + if (!irqname)
> > > > > > > > + return -ENOMEM;
> > > > > > > > +
> > > > > > > > + ret = devm_request_irq(dev, chan->irq, ls1x_dma_irq_handler,
> > > > > > > > + IRQF_SHARED, irqname, chan);
> > > > > > > > + if (ret)
> > > > > > > > + return dev_err_probe(dev, ret,
> > > > > > > > + "failed to request IRQ %u!\n", chan->irq);
> > > > > > > > +
> > > > > > > > + chan->reg_base = dma->reg_base;
> > > > > > > > + chan->vchan.desc_free = ls1x_dma_free_desc;
> > > > > > > > + vchan_init(&chan->vchan, &dma->ddev);
> > > > > > > > + dev_info(dev, "%s (irq %d) initialized\n", pdev_irqname, chan->irq);
> > > > > > > > +
> > > > > > > > + return 0;
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +static void ls1x_dma_chan_remove(struct ls1x_dma *dma, int chan_id)
> > > > > > > > +{
> > > > > > > > + struct device *dev = dma->ddev.dev;
> > > > > > > > + struct ls1x_dma_chan *chan = &dma->chan[chan_id];
> > > > > > > > +
> > > > > > > > + devm_free_irq(dev, chan->irq, chan);
> > > > > > > > + list_del(&chan->vchan.chan.device_node);
> > > > > > > > + tasklet_kill(&chan->vchan.task);
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +static int ls1x_dma_probe(struct platform_device *pdev)
> > > > > > > > +{
> > > > > > > > + struct device *dev = &pdev->dev;
> > > > > > > > + struct dma_device *ddev;
> > > > > > > > + struct ls1x_dma *dma;
> > > > > > > > + int nr_chans, ret, i;
> > > > > > > > +
> > > > > > > > + nr_chans = platform_irq_count(pdev);
> > > > > > > > + if (nr_chans <= 0)
> > > > > > > > + return nr_chans;
> > > > > > > > + if (nr_chans > LS1X_DMA_MAX_CHANNELS)
> > > > > > > > + return dev_err_probe(dev, -EINVAL,
> > > > > > > > + "nr_chans=%d exceeds the maximum\n",
> > > > > > > > + nr_chans);
> > > > > > > > +
> > > > > > > > + dma = devm_kzalloc(dev, struct_size(dma, chan, nr_chans), GFP_KERNEL);
> > > > > > > > + if (!dma)
> > > > > > > > + return -ENOMEM;
> > > > > > > > +
> > > > > > > > + /* initialize DMA device */
> > > > > > > > + dma->reg_base = devm_platform_ioremap_resource(pdev, 0);
> > > > > > > > + if (IS_ERR(dma->reg_base))
> > > > > > > > + return PTR_ERR(dma->reg_base);
> > > > > > > > +
> > > > > > > > + ddev = &dma->ddev;
> > > > > > > > + ddev->dev = dev;
> > > > > > > > + ddev->copy_align = DMAENGINE_ALIGN_4_BYTES;
> > > > > > > > + ddev->src_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) |
> > > > > > > > + BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | BIT(DMA_SLAVE_BUSWIDTH_4_BYTES);
> > > > > > > > + ddev->dst_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) |
> > > > > > > > + BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | BIT(DMA_SLAVE_BUSWIDTH_4_BYTES);
> > > > > > > > + ddev->directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV);
> > > > > > > > + ddev->max_sg_burst = LS1X_DMA_MAX_DESC;
> > > > > > > > + ddev->residue_granularity = DMA_RESIDUE_GRANULARITY_SEGMENT;
> > > > > > > > + ddev->device_alloc_chan_resources = ls1x_dma_alloc_chan_resources;
> > > > > > > > + ddev->device_free_chan_resources = ls1x_dma_free_chan_resources;
> > > > > > > > + ddev->device_prep_slave_sg = ls1x_dma_prep_slave_sg;
> > > > > > > > + ddev->device_prep_dma_cyclic = ls1x_dma_prep_dma_cyclic;
> > > > > > > > + ddev->device_config = ls1x_dma_slave_config;
> > > > > > > > + ddev->device_pause = ls1x_dma_pause;
> > > > > > > > + ddev->device_resume = ls1x_dma_resume;
> > > > > > > > + ddev->device_terminate_all = ls1x_dma_terminate_all;
> > > > > > > > + ddev->device_tx_status = ls1x_dma_tx_status;
> > > > > > > > + ddev->device_issue_pending = ls1x_dma_issue_pending;
> > > > > > > > +
> > > > > > > > + dma_cap_set(DMA_SLAVE, ddev->cap_mask);
> > > > > > > > + INIT_LIST_HEAD(&ddev->channels);
> > > > > > > > +
> > > > > > > > + /* initialize DMA channels */
> > > > > > > > + for (i = 0; i < nr_chans; i++) {
> > > > > > > > + ret = ls1x_dma_chan_probe(pdev, dma, i);
> > > > > > > > + if (ret)
> > > > > > > > + return ret;
> > > > > > > > + }
> > > > > > > > + dma->nr_chans = nr_chans;
> > > > > > > > +
> > > > > > > > + ret = dmaenginem_async_device_register(ddev);
> > > > > > > > + if (ret) {
> > > > > > > > + dev_err(dev, "failed to register DMA device! %d\n", ret);
> > > > > > > > + return ret;
> > > > > > > > + }
> > > > > > > > +
> > > > > > > > + ret =
> > > > > > > > + of_dma_controller_register(dev->of_node, of_dma_xlate_by_chan_id,
> > > > > > > > + ddev);
> > > > > > > > + if (ret) {
> > > > > > > > + dev_err(dev, "failed to register DMA controller! %d\n", ret);
> > > > > > > > + return ret;
> > > > > > > > + }
> > > > > > > > +
> > > > > > > > + platform_set_drvdata(pdev, dma);
> > > > > > > > + dev_info(dev, "Loongson1 DMA driver registered\n");
> > > > > > > > +
> > > > > > > > + return 0;
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +static void ls1x_dma_remove(struct platform_device *pdev)
> > > > > > > > +{
> > > > > > > > + struct ls1x_dma *dma = platform_get_drvdata(pdev);
> > > > > > > > + int i;
> > > > > > > > +
> > > > > > > > + of_dma_controller_free(pdev->dev.of_node);
> > > > > > > > +
> > > > > > > > + for (i = 0; i < dma->nr_chans; i++)
> > > > > > > > + ls1x_dma_chan_remove(dma, i);
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +static const struct of_device_id ls1x_dma_match[] = {
> > > > > > > > + { .compatible = "loongson,ls1b-apbdma" },
> > > > > > > > + { .compatible = "loongson,ls1c-apbdma" },
> > > > > > > > + { /* sentinel */ }
> > > > > > > > +};
> > > > > > > > +MODULE_DEVICE_TABLE(of, ls1x_dma_match);
> > > > > > > > +
> > > > > > > > +static struct platform_driver ls1x_dma_driver = {
> > > > > > > > + .probe = ls1x_dma_probe,
> > > > > > > > + .remove_new = ls1x_dma_remove,
> > > > > > > > + .driver = {
> > > > > > > > + .name = KBUILD_MODNAME,
> > > > > > > > + .of_match_table = ls1x_dma_match,
> > > > > > > > + },
> > > > > > > > +};
> > > > > > > > +
> > > > > > > > +module_platform_driver(ls1x_dma_driver);
> > > > > > > > +
> > > > > > > > +MODULE_AUTHOR("Keguang Zhang <keguang.zhang@gmail.com>");
> > > > > > > > +MODULE_DESCRIPTION("Loongson-1 APB DMA Controller driver");
> > > > > > > > +MODULE_LICENSE("GPL");
> > > > > > > >
> > > > > > > > --
> > > > > > > > 2.40.1
> > > > > > > >
> > > > > > > >
> > > > > >
> > > > > >
> > > > > >
> > > > > > --
> > > > > > Best regards,
> > > > > >
> > > > > > Keguang Zhang
> > > > > >
> > > >
> > > >
> > > >
> > > > --
> > > > Best regards,
> > > >
> > > > Keguang Zhang
> >
> >
> >
> > --
> > Best regards,
> >
> > Keguang Zhang
--
Best regards,
Keguang Zhang
^ permalink raw reply
* Re: [PATCH v5 2/2] backlight: Add new lm3509 backlight driver
From: Daniel Thompson @ 2024-04-02 10:52 UTC (permalink / raw)
To: Patrick Gansterer
Cc: dri-devel, linux-leds, devicetree, linux-kernel, linux-fbdev,
Lee Jones, Jingoo Han, Pavel Machek, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Helge Deller, Sam Ravnborg
In-Reply-To: <20240330145931.729116-3-paroga@paroga.com>
On Sat, Mar 30, 2024 at 03:59:25PM +0100, Patrick Gansterer wrote:
> This is a general driver for LM3509 backlight chip of TI.
> LM3509 is High Efficiency Boost for White LEDs and/or OLED Displays with
> Dual Current Sinks. This driver supports OLED/White LED select, brightness
> control and sub/main control.
> The datasheet can be found at http://www.ti.com/product/lm3509.
>
> Signed-off-by: Patrick Gansterer <paroga@paroga.com>
Reviewed-by: Daniel Thompson <daniel.thompson@linaro.org>
Daniel.
^ permalink raw reply
* Re: [PATCH 1/1] arm64: dts: imx8-ss-conn: fix usdhc wrong lpcg clock order
From: Shawn Guo @ 2024-04-02 10:56 UTC (permalink / raw)
To: Frank Li
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Shawn Guo,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
Dong Aisheng,
open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
open list:ARM/FREESCALE IMX / MXC ARM ARCHITECTURE,
moderated list:ARM/FREESCALE IMX / MXC ARM ARCHITECTURE,
open list
In-Reply-To: <20240322164706.2626088-1-Frank.Li@nxp.com>
On Fri, Mar 22, 2024 at 12:47:05PM -0400, Frank Li wrote:
> The actual clock show wrong frequency:
>
> echo on >/sys/devices/platform/bus\@5b000000/5b010000.mmc/power/control
> cat /sys/kernel/debug/mmc0/ios
>
> clock: 200000000 Hz
> actual clock: 166000000 Hz
> ^^^^^^^^^
> .....
>
> According to
>
> sdhc0_lpcg: clock-controller@5b200000 {
> compatible = "fsl,imx8qxp-lpcg";
> reg = <0x5b200000 0x10000>;
> #clock-cells = <1>;
> clocks = <&clk IMX_SC_R_SDHC_0 IMX_SC_PM_CLK_PER>,
> <&conn_ipg_clk>, <&conn_axi_clk>;
> clock-indices = <IMX_LPCG_CLK_0>, <IMX_LPCG_CLK_4>,
> <IMX_LPCG_CLK_5>;
> clock-output-names = "sdhc0_lpcg_per_clk",
> "sdhc0_lpcg_ipg_clk",
> "sdhc0_lpcg_ahb_clk";
> power-domains = <&pd IMX_SC_R_SDHC_0>;
> }
>
> "per_clk" should be IMX_LPCG_CLK_0 instead of IMX_LPCG_CLK_5.
>
> After correct clocks order:
>
> echo on >/sys/devices/platform/bus\@5b000000/5b010000.mmc/power/control
> cat /sys/kernel/debug/mmc0/ios
>
> clock: 200000000 Hz
> actual clock: 198000000 Hz
> ^^^^^^^^
> ...
>
> Fixes: 16c4ea7501b1 ("arm64: dts: imx8: switch to new lpcg clock binding")
> Signed-off-by: Frank Li <Frank.Li@nxp.com>
Applied, thanks!
^ permalink raw reply
* [PATCH] dt-bindings: mfd: syscon: Add ti,am62p-cpsw-mac-efuse compatible
From: Siddharth Vadapalli @ 2024-04-02 10:57 UTC (permalink / raw)
To: lee, robh, krzk+dt, conor+dt
Cc: devicetree, linux-kernel, linux-arm-kernel, srk, s-vadapalli
The CTRLMMR_MAC_IDx registers within the CTRL_MMR space of TI's AM62p SoC
contain the MAC Address programmed in the eFuse. Add compatible for
allowing the CPSW driver to obtain a regmap for the CTRLMMR_MAC_IDx
registers within the System Controller device-tree node. The default MAC
Address for the interface corresponding to the first MAC port will be set
to the value programmed in the eFuse.
Signed-off-by: Siddharth Vadapalli <s-vadapalli@ti.com>
---
This patch is based on linux-next tagged next-20240402.
Regards,
Siddharth.
Documentation/devicetree/bindings/mfd/syscon.yaml | 1 +
1 file changed, 1 insertion(+)
diff --git a/Documentation/devicetree/bindings/mfd/syscon.yaml b/Documentation/devicetree/bindings/mfd/syscon.yaml
index 9d55bee155ce..4936ac0b5936 100644
--- a/Documentation/devicetree/bindings/mfd/syscon.yaml
+++ b/Documentation/devicetree/bindings/mfd/syscon.yaml
@@ -73,6 +73,7 @@ properties:
- rockchip,rv1126-qos
- starfive,jh7100-sysmain
- ti,am62-usb-phy-ctrl
+ - ti,am62p-cpsw-mac-efuse
- ti,am654-dss-oldi-io-ctrl
- ti,am654-serdes-ctrl
- ti,j784s4-pcie-ctrl
--
2.40.1
^ permalink raw reply related
* RE: [EXT] Re: [PATCH v10 08/11] arm64: dts: imx93: add usb nodes
From: Xu Yang @ 2024-04-02 10:57 UTC (permalink / raw)
To: Shawn Guo
Cc: gregkh@linuxfoundation.org, robh+dt@kernel.org,
krzysztof.kozlowski+dt@linaro.org, shawnguo@kernel.org,
conor+dt@kernel.org, s.hauer@pengutronix.de,
kernel@pengutronix.de, festevam@gmail.com, dl-linux-imx,
peter.chen@kernel.org, Jun Li, linux-usb@vger.kernel.org,
devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
imx@lists.linux.dev, linux-kernel@vger.kernel.org
In-Reply-To: <ZgvEXZTOHUv+GGeH@dragon>
Hi Shawn,
>
> On Thu, Mar 21, 2024 at 04:14:36PM +0800, Xu Yang wrote:
> > There are 2 USB controllers on i.MX93. Add them.
> >
> > Acked-by: Alexander Stein <alexander.stein@ew.tq-group.com>
> > Tested-by: Alexander Stein <alexander.stein@ew.tq-group.com> # TQMa9352LA/CA
> > Signed-off-by: Xu Yang <xu.yang_2@nxp.com>
> >
> > ---
> > Changes in v2:
> > - fix format as suggested by Alexander
> > - change compatible from fsl,imx8mm-usb to fsl,imx93-usb
> > Changes in v3:
> > - replace deprecated fsl,usbphy with phys as suggested by Alexander
> > - reorder nodes
> > Changes in v4:
> > - fix the alignment
> > Changes in v5:
> > - rename usb_wakeup_clk to usb_wakeup
> > Changes in v6:
> > - rename usb_ctrl_root_clk to usb_ctrl_root
> > Changes in v7:
> > - no changes
> > Changes in v8:
> > - no changes
> > Changes in v9:
> > - no changes
> > Changes in v10:
> > - no changes
> > ---
> > arch/arm64/boot/dts/freescale/imx93.dtsi | 58 ++++++++++++++++++++++++
> > 1 file changed, 58 insertions(+)
> >
> > diff --git a/arch/arm64/boot/dts/freescale/imx93.dtsi b/arch/arm64/boot/dts/freescale/imx93.dtsi
> > index 8f2e7c42ad6e..4a7efccb4f67 100644
> > --- a/arch/arm64/boot/dts/freescale/imx93.dtsi
> > +++ b/arch/arm64/boot/dts/freescale/imx93.dtsi
> > @@ -183,6 +183,20 @@ mqs2: mqs2 {
> > status = "disabled";
> > };
> >
> > + usbphynop1: usbphynop1 {
> > + compatible = "usb-nop-xceiv";
> > + #phy-cells = <0>;
> > + clocks = <&clk IMX93_CLK_USB_PHY_BURUNIN>;
> > + clock-names = "main_clk";
> > + };
> > +
> > + usbphynop2: usbphynop2 {
> > + compatible = "usb-nop-xceiv";
> > + #phy-cells = <0>;
> > + clocks = <&clk IMX93_CLK_USB_PHY_BURUNIN>;
> > + clock-names = "main_clk";
> > + };
> > +
> > soc@0 {
> > compatible = "simple-bus";
> > #address-cells = <1>;
> > @@ -1167,6 +1181,50 @@ media_blk_ctrl: system-controller@4ac10000 {
> > status = "disabled";
> > };
> >
> > + usbotg1: usb@4c100000 {
> > + compatible = "fsl,imx93-usb", "fsl,imx7d-usb", "fsl,imx27-usb";
> > + reg = <0x4c100000 0x200>;
> > + interrupts = <GIC_SPI 187 IRQ_TYPE_LEVEL_HIGH>;
> > + clocks = <&clk IMX93_CLK_USB_CONTROLLER_GATE>,
> > + <&clk IMX93_CLK_HSIO_32K_GATE>;
> > + clock-names = "usb_ctrl_root", "usb_wakeup";
> > + assigned-clocks = <&clk IMX93_CLK_HSIO>;
> > + assigned-clock-parents = <&clk IMX93_CLK_SYS_PLL_PFD1_DIV2>;
> > + assigned-clock-rates = <133000000>;
> > + phys = <&usbphynop1>;
> > + fsl,usbmisc = <&usbmisc1 0>;
> > + status = "disabled";
> > + };
> > +
> > + usbmisc1: usbmisc@4c100200 {
> > + compatible = "fsl,imx8mm-usbmisc", "fsl,imx7d-usbmisc",
> > + "fsl,imx6q-usbmisc";
> > + reg = <0x4c100200 0x200>;
> > + #index-cells = <1>;
>
> Do we still need this '#index-cells' property? I see it's being marked
> as deprecated in bindings doc.
Sorry, the driver still needs fetch the value of this property so far. Otherwise,
the driver will probe failed. We still need some time to totally retire this property.
Thanks,
Xu Yang
>
> Shawn
>
> > + };
> > +
> > + usbotg2: usb@4c200000 {
> > + compatible = "fsl,imx93-usb", "fsl,imx7d-usb", "fsl,imx27-usb";
> > + reg = <0x4c200000 0x200>;
> > + interrupts = <GIC_SPI 188 IRQ_TYPE_LEVEL_HIGH>;
> > + clocks = <&clk IMX93_CLK_USB_CONTROLLER_GATE>,
> > + <&clk IMX93_CLK_HSIO_32K_GATE>;
> > + clock-names = "usb_ctrl_root", "usb_wakeup";
> > + assigned-clocks = <&clk IMX93_CLK_HSIO>;
> > + assigned-clock-parents = <&clk IMX93_CLK_SYS_PLL_PFD1_DIV2>;
> > + assigned-clock-rates = <133000000>;
> > + phys = <&usbphynop2>;
> > + fsl,usbmisc = <&usbmisc2 0>;
> > + status = "disabled";
> > + };
> > +
> > + usbmisc2: usbmisc@4c200200 {
> > + compatible = "fsl,imx8mm-usbmisc", "fsl,imx7d-usbmisc",
> > + "fsl,imx6q-usbmisc";
> > + reg = <0x4c200200 0x200>;
> > + #index-cells = <1>;
> > + };
> > +
> > ddr-pmu@4e300dc0 {
> > compatible = "fsl,imx93-ddr-pmu";
> > reg = <0x4e300dc0 0x200>;
> > --
> > 2.34.1
> >
^ permalink raw reply
* [PATCH 1/2] drm/bridge: lt8912b: add support for P/N pin swap
From: Alexandru Ardelean @ 2024-04-02 10:59 UTC (permalink / raw)
To: linux-kernel, dri-devel, devicetree
Cc: adrien.grassein, andrzej.hajda, neil.armstrong, rfoss,
Laurent.pinchart, jonas, jernej.skrabec, airlied, daniel,
maarten.lankhorst, mripard, tzimmermann, robh,
krzysztof.kozlowski+dt, conor+dt, stefan.eichenberger,
francesco.dolcini, marius.muresan, irina.muresan,
Alexandru Ardelean
On some HW designs, it's easier for the layout if the P/N pins are swapped.
In those cases, we need to adjust (for this) by configuring the MIPI analog
registers differently. Specifically, register 0x3e needs to be 0xf6
(instead of 0xd6).
This change adds a 'lontium,pn-swap' device-tree property to configure the
MIPI analog registers for P/N swap.
Signed-off-by: Alexandru Ardelean <alex@shruggie.ro>
---
drivers/gpu/drm/bridge/lontium-lt8912b.c | 25 +++++++++++++++++++++---
1 file changed, 22 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/bridge/lontium-lt8912b.c b/drivers/gpu/drm/bridge/lontium-lt8912b.c
index 4b2ae27f0a57f..154126bb922b4 100644
--- a/drivers/gpu/drm/bridge/lontium-lt8912b.c
+++ b/drivers/gpu/drm/bridge/lontium-lt8912b.c
@@ -47,6 +47,7 @@ struct lt8912 {
u8 data_lanes;
bool is_power_on;
+ bool do_pn_swap;
};
static int lt8912_write_init_config(struct lt8912 *lt)
@@ -78,15 +79,31 @@ static int lt8912_write_init_config(struct lt8912 *lt)
{0x55, 0x44},
{0x57, 0x01},
{0x5a, 0x02},
-
- /*MIPI Analog*/
+ };
+ const struct reg_sequence mipi_analog_seq[] = {
{0x3e, 0xd6},
{0x3f, 0xd4},
{0x41, 0x3c},
{0xB2, 0x00},
};
+ const struct reg_sequence mipi_analog_pn_swap_seq[] = {
+ {0x3e, 0xf6},
+ {0x3f, 0xd4},
+ {0x41, 0x3c},
+ {0xB2, 0x00},
+ };
+ int ret;
- return regmap_multi_reg_write(lt->regmap[I2C_MAIN], seq, ARRAY_SIZE(seq));
+ ret = regmap_multi_reg_write(lt->regmap[I2C_MAIN], seq, ARRAY_SIZE(seq));
+ if (ret < 0)
+ return ret;
+
+ if (!lt->do_pn_swap)
+ return regmap_multi_reg_write(lt->regmap[I2C_MAIN], mipi_analog_seq,
+ ARRAY_SIZE(mipi_analog_seq));
+
+ return regmap_multi_reg_write(lt->regmap[I2C_MAIN], mipi_analog_pn_swap_seq,
+ ARRAY_SIZE(mipi_analog_pn_swap_seq));
}
static int lt8912_write_mipi_basic_config(struct lt8912 *lt)
@@ -702,6 +719,8 @@ static int lt8912_parse_dt(struct lt8912 *lt)
}
lt->gp_reset = gp_reset;
+ lt->do_pn_swap = device_property_read_bool(dev, "lontium,pn-swap");
+
data_lanes = drm_of_get_data_lanes_count_ep(dev->of_node, 0, -1, 1, 4);
if (data_lanes < 0) {
dev_err(lt->dev, "%s: Bad data-lanes property\n", __func__);
--
2.44.0
^ permalink raw reply related
* [PATCH 2/2] dt-bindings: display: bridge: lt8912b: document 'lontium,pn-swap' property
From: Alexandru Ardelean @ 2024-04-02 10:59 UTC (permalink / raw)
To: linux-kernel, dri-devel, devicetree
Cc: adrien.grassein, andrzej.hajda, neil.armstrong, rfoss,
Laurent.pinchart, jonas, jernej.skrabec, airlied, daniel,
maarten.lankhorst, mripard, tzimmermann, robh,
krzysztof.kozlowski+dt, conor+dt, stefan.eichenberger,
francesco.dolcini, marius.muresan, irina.muresan,
Alexandru Ardelean
In-Reply-To: <20240402105925.905144-1-alex@shruggie.ro>
On some HW designs, it's easier for the layout if the P/N pins are swapped.
The driver currently has a DT property to do that.
This change documents the 'lontium,pn-swap' property.
Signed-off-by: Alexandru Ardelean <alex@shruggie.ro>
---
.../devicetree/bindings/display/bridge/lontium,lt8912b.yaml | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/Documentation/devicetree/bindings/display/bridge/lontium,lt8912b.yaml b/Documentation/devicetree/bindings/display/bridge/lontium,lt8912b.yaml
index 2cef252157985..3a804926b288a 100644
--- a/Documentation/devicetree/bindings/display/bridge/lontium,lt8912b.yaml
+++ b/Documentation/devicetree/bindings/display/bridge/lontium,lt8912b.yaml
@@ -24,6 +24,12 @@ properties:
maxItems: 1
description: GPIO connected to active high RESET pin.
+ lontium,pn-swap:
+ description: Swap the polarities of the P/N pins in software.
+ On some HW designs, the layout is simplified if the P/N pins
+ are inverted.
+ type: boolean
+
ports:
$ref: /schemas/graph.yaml#/properties/ports
--
2.44.0
^ permalink raw reply related
* Re: [PATCH v6 3/6] interconnect: icc-clk: Add devm_icc_clk_register
From: Varadarajan Narayanan @ 2024-04-02 11:02 UTC (permalink / raw)
To: Dmitry Baryshkov
Cc: andersson, konrad.dybcio, mturquette, sboyd, robh,
krzysztof.kozlowski+dt, conor+dt, djakov, quic_anusha,
linux-arm-msm, linux-clk, devicetree, linux-kernel, linux-pm
In-Reply-To: <CAA8EJpo=TMhu+Te+JE0cQzmjLOTDPi-Vv-h5Bch0Wfr_7iVi2w@mail.gmail.com>
On Tue, Apr 02, 2024 at 01:48:08PM +0300, Dmitry Baryshkov wrote:
> On Tue, 2 Apr 2024 at 13:40, Dmitry Baryshkov
> <dmitry.baryshkov@linaro.org> wrote:
> >
> > On Tue, 2 Apr 2024 at 13:34, Varadarajan Narayanan
> > <quic_varada@quicinc.com> wrote:
> > >
> > > Wrap icc_clk_register to create devm_icc_clk_register to be
> > > able to release the resources properly.
> > >
> > > Signed-off-by: Varadarajan Narayanan <quic_varada@quicinc.com>
> > > ---
> > > v5: Introduced devm_icc_clk_register
> > > ---
> > > drivers/interconnect/icc-clk.c | 29 +++++++++++++++++++++++++++++
> > > include/linux/interconnect-clk.h | 4 ++++
> > > 2 files changed, 33 insertions(+)
> >
> > Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
>
> Wait. Actually,
>
> Unreviewed-by: me
>
> Please return int from devm_icc_clk_register instead of returning the pointer.
Wouldn't returning int break the general assumption that
devm_foo(), returns the same type as foo(). For example
devm_clk_hw_get_clk and clk_hw_get_clk return struct clk *?
Thanks
Varada
^ permalink raw reply
* Re: [PATCH v1 6/6] clk: meson: a1: add Amlogic A1 CPU clock controller driver
From: Dmitry Rokosov @ 2024-04-02 11:05 UTC (permalink / raw)
To: Jerome Brunet
Cc: neil.armstrong, mturquette, sboyd, robh+dt,
krzysztof.kozlowski+dt, khilman, martin.blumenstingl, kernel,
rockosov, linux-amlogic, linux-clk, devicetree, linux-kernel,
linux-arm-kernel
In-Reply-To: <1jv850hyvm.fsf@starbuckisacylon.baylibre.com>
Hello Jerome,
On Tue, Apr 02, 2024 at 11:35:49AM +0200, Jerome Brunet wrote:
>
> On Fri 29 Mar 2024 at 23:58, Dmitry Rokosov <ddrokosov@salutedevices.com> wrote:
>
> > The CPU clock controller plays a general role in the Amlogic A1 SoC
> > family by generating CPU clocks. As an APB slave module, it offers the
> > capability to inherit the CPU clock from two sources: the internal fixed
> > clock known as 'cpu fixed clock' and the external input provided by the
> > A1 PLL clock controller, referred to as 'syspll'.
> >
> > It is important for the driver to handle cpu_clk rate switching
> > effectively by transitioning to the CPU fixed clock to avoid any
> > potential execution freezes.
> >
> > Signed-off-by: Dmitry Rokosov <ddrokosov@salutedevices.com>
> > ---
> > drivers/clk/meson/Kconfig | 10 ++
> > drivers/clk/meson/Makefile | 1 +
> > drivers/clk/meson/a1-cpu.c | 324 +++++++++++++++++++++++++++++++++++++
> > drivers/clk/meson/a1-cpu.h | 16 ++
> > 4 files changed, 351 insertions(+)
> > create mode 100644 drivers/clk/meson/a1-cpu.c
> > create mode 100644 drivers/clk/meson/a1-cpu.h
> >
> > diff --git a/drivers/clk/meson/Kconfig b/drivers/clk/meson/Kconfig
> > index 80c4a18c83d2..148d4495eee3 100644
> > --- a/drivers/clk/meson/Kconfig
> > +++ b/drivers/clk/meson/Kconfig
> > @@ -111,6 +111,16 @@ config COMMON_CLK_AXG_AUDIO
> > Support for the audio clock controller on AmLogic A113D devices,
> > aka axg, Say Y if you want audio subsystem to work.
> >
> > +config COMMON_CLK_A1_CPU
> > + tristate "Amlogic A1 SoC CPU controller support"
> > + depends on ARM64
> > + select COMMON_CLK_MESON_REGMAP
> > + select COMMON_CLK_MESON_CLKC_UTILS
> > + help
> > + Support for the CPU clock controller on Amlogic A113L based
> > + device, A1 SoC Family. Say Y if you want A1 CPU clock controller
> > + to work.
> > +
> > config COMMON_CLK_A1_PLL
> > tristate "Amlogic A1 SoC PLL controller support"
> > depends on ARM64
> > diff --git a/drivers/clk/meson/Makefile b/drivers/clk/meson/Makefile
> > index 4968fc7ad555..2a06eb0303d6 100644
> > --- a/drivers/clk/meson/Makefile
> > +++ b/drivers/clk/meson/Makefile
> > @@ -18,6 +18,7 @@ obj-$(CONFIG_COMMON_CLK_MESON_AUDIO_RSTC) += meson-audio-rstc.o
> >
> > obj-$(CONFIG_COMMON_CLK_AXG) += axg.o axg-aoclk.o
> > obj-$(CONFIG_COMMON_CLK_AXG_AUDIO) += axg-audio.o
> > +obj-$(CONFIG_COMMON_CLK_A1_CPU) += a1-cpu.o
> > obj-$(CONFIG_COMMON_CLK_A1_PLL) += a1-pll.o
> > obj-$(CONFIG_COMMON_CLK_A1_PERIPHERALS) += a1-peripherals.o
> > obj-$(CONFIG_COMMON_CLK_A1_AUDIO) += a1-audio.o
> > diff --git a/drivers/clk/meson/a1-cpu.c b/drivers/clk/meson/a1-cpu.c
> > new file mode 100644
> > index 000000000000..5f5d8ae112e5
> > --- /dev/null
> > +++ b/drivers/clk/meson/a1-cpu.c
> > @@ -0,0 +1,324 @@
> > +// SPDX-License-Identifier: GPL-2.0+
> > +/*
> > + * Amlogic A1 SoC family CPU Clock Controller driver.
> > + *
> > + * Copyright (c) 2024, SaluteDevices. All Rights Reserved.
> > + * Author: Dmitry Rokosov <ddrokosov@salutedevices.com>
> > + */
> > +
> > +#include <linux/clk.h>
> > +#include <linux/clk-provider.h>
> > +#include <linux/mod_devicetable.h>
> > +#include <linux/platform_device.h>
> > +#include "a1-cpu.h"
> > +#include "clk-regmap.h"
> > +#include "meson-clkc-utils.h"
> > +
> > +#include <dt-bindings/clock/amlogic,a1-cpu-clkc.h>
> > +
> > +static u32 cpu_fsource_sel_table[] = { 0, 1, 2 };
> > +static const struct clk_parent_data cpu_fsource_sel_parents[] = {
> > + { .fw_name = "xtal" },
> > + { .fw_name = "fclk_div2" },
> > + { .fw_name = "fclk_div3" },
> > +};
> > +
> > +static struct clk_regmap cpu_fsource_sel0 = {
> > + .data = &(struct clk_regmap_mux_data) {
> > + .offset = CPUCTRL_CLK_CTRL0,
> > + .mask = 0x3,
> > + .shift = 0,
> > + .table = cpu_fsource_sel_table,
> > + },
> > + .hw.init = &(struct clk_init_data) {
> > + .name = "cpu_fsource_sel0",
> > + .ops = &clk_regmap_mux_ops,
> > + .parent_data = cpu_fsource_sel_parents,
> > + .num_parents = ARRAY_SIZE(cpu_fsource_sel_parents),
> > + .flags = CLK_SET_RATE_PARENT,
> > + },
> > +};
> > +
> > +static struct clk_regmap cpu_fsource_div0 = {
> > + .data = &(struct clk_regmap_div_data) {
> > + .offset = CPUCTRL_CLK_CTRL0,
> > + .shift = 4,
> > + .width = 6,
> > + },
> > + .hw.init = &(struct clk_init_data) {
> > + .name = "cpu_fsource_div0",
> > + .ops = &clk_regmap_divider_ops,
> > + .parent_hws = (const struct clk_hw *[]) {
> > + &cpu_fsource_sel0.hw
> > + },
> > + .num_parents = 1,
> > + .flags = CLK_SET_RATE_PARENT,
> > + },
> > +};
> > +
> > +static struct clk_regmap cpu_fsel0 = {
> > + .data = &(struct clk_regmap_mux_data) {
> > + .offset = CPUCTRL_CLK_CTRL0,
> > + .mask = 0x1,
> > + .shift = 2,
> > + },
> > + .hw.init = &(struct clk_init_data) {
> > + .name = "cpu_fsel0",
> > + .ops = &clk_regmap_mux_ops,
> > + .parent_hws = (const struct clk_hw *[]) {
> > + &cpu_fsource_sel0.hw,
> > + &cpu_fsource_div0.hw,
> > + },
> > + .num_parents = 2,
> > + .flags = CLK_SET_RATE_PARENT,
> > + },
> > +};
> > +
> > +static struct clk_regmap cpu_fsource_sel1 = {
> > + .data = &(struct clk_regmap_mux_data) {
> > + .offset = CPUCTRL_CLK_CTRL0,
> > + .mask = 0x3,
> > + .shift = 16,
> > + .table = cpu_fsource_sel_table,
> > + },
> > + .hw.init = &(struct clk_init_data) {
> > + .name = "cpu_fsource_sel1",
> > + .ops = &clk_regmap_mux_ops,
> > + .parent_data = cpu_fsource_sel_parents,
> > + .num_parents = ARRAY_SIZE(cpu_fsource_sel_parents),
> > + .flags = CLK_SET_RATE_PARENT,
> > + },
> > +};
> > +
> > +static struct clk_regmap cpu_fsource_div1 = {
> > + .data = &(struct clk_regmap_div_data) {
> > + .offset = CPUCTRL_CLK_CTRL0,
> > + .shift = 20,
> > + .width = 6,
> > + },
> > + .hw.init = &(struct clk_init_data) {
> > + .name = "cpu_fsource_div1",
> > + .ops = &clk_regmap_divider_ops,
> > + .parent_hws = (const struct clk_hw *[]) {
> > + &cpu_fsource_sel1.hw
> > + },
> > + .num_parents = 1,
> > + .flags = CLK_SET_RATE_PARENT,
> > + },
> > +};
> > +
> > +static struct clk_regmap cpu_fsel1 = {
> > + .data = &(struct clk_regmap_mux_data) {
> > + .offset = CPUCTRL_CLK_CTRL0,
> > + .mask = 0x1,
> > + .shift = 18,
> > + },
> > + .hw.init = &(struct clk_init_data) {
> > + .name = "cpu_fsel1",
> > + .ops = &clk_regmap_mux_ops,
> > + .parent_hws = (const struct clk_hw *[]) {
> > + &cpu_fsource_sel1.hw,
> > + &cpu_fsource_div1.hw,
> > + },
> > + .num_parents = 2,
> > + .flags = CLK_SET_RATE_PARENT,
> > + },
> > +};
> > +
> > +static struct clk_regmap cpu_fclk = {
> > + .data = &(struct clk_regmap_mux_data) {
> > + .offset = CPUCTRL_CLK_CTRL0,
> > + .mask = 0x1,
> > + .shift = 10,
> > + },
> > + .hw.init = &(struct clk_init_data) {
> > + .name = "cpu_fclk",
> > + .ops = &clk_regmap_mux_ops,
> > + .parent_hws = (const struct clk_hw *[]) {
> > + &cpu_fsel0.hw,
> > + &cpu_fsel1.hw,
> > + },
> > + .num_parents = 2,
> > + .flags = CLK_SET_RATE_PARENT,
> > + },
> > +};
> > +
> > +static struct clk_regmap cpu_clk = {
> > + .data = &(struct clk_regmap_mux_data) {
> > + .offset = CPUCTRL_CLK_CTRL0,
> > + .mask = 0x1,
> > + .shift = 11,
> > + },
> > + .hw.init = &(struct clk_init_data) {
> > + .name = "cpu_clk",
> > + .ops = &clk_regmap_mux_ops,
> > + .parent_data = (const struct clk_parent_data []) {
> > + { .hw = &cpu_fclk.hw },
> > + { .fw_name = "sys_pll", },
> > + },
> > + .num_parents = 2,
> > + .flags = CLK_SET_RATE_PARENT | CLK_IS_CRITICAL,
> > + },
> > +};
> > +
> > +/* Array of all clocks registered by this provider */
> > +static struct clk_hw *a1_cpu_hw_clks[] = {
> > + [CLKID_CPU_FSOURCE_SEL0] = &cpu_fsource_sel0.hw,
> > + [CLKID_CPU_FSOURCE_DIV0] = &cpu_fsource_div0.hw,
> > + [CLKID_CPU_FSEL0] = &cpu_fsel0.hw,
> > + [CLKID_CPU_FSOURCE_SEL1] = &cpu_fsource_sel1.hw,
> > + [CLKID_CPU_FSOURCE_DIV1] = &cpu_fsource_div1.hw,
> > + [CLKID_CPU_FSEL1] = &cpu_fsel1.hw,
> > + [CLKID_CPU_FCLK] = &cpu_fclk.hw,
> > + [CLKID_CPU_CLK] = &cpu_clk.hw,
> > +};
> > +
> > +static struct clk_regmap *const a1_cpu_regmaps[] = {
> > + &cpu_fsource_sel0,
> > + &cpu_fsource_div0,
> > + &cpu_fsel0,
> > + &cpu_fsource_sel1,
> > + &cpu_fsource_div1,
> > + &cpu_fsel1,
> > + &cpu_fclk,
> > + &cpu_clk,
> > +};
> > +
> > +static struct regmap_config a1_cpu_regmap_cfg = {
> > + .reg_bits = 32,
> > + .val_bits = 32,
> > + .reg_stride = 4,
> > + .max_register = CPUCTRL_CLK_CTRL1,
> > +};
> > +
> > +static struct meson_clk_hw_data a1_cpu_clks = {
> > + .hws = a1_cpu_hw_clks,
> > + .num = ARRAY_SIZE(a1_cpu_hw_clks),
> > +};
> > +
> > +struct a1_cpu_clk_nb_data {
> > + const struct clk_ops *mux_ops;
>
> That's fishy ...
>
> > + struct clk_hw *cpu_clk;
> > + struct notifier_block nb;
> > + u8 parent;
> > +};
> > +
> > +#define MESON_A1_CPU_CLK_GET_PARENT(nbd) \
> > + ((nbd)->mux_ops->get_parent((nbd)->cpu_clk))
> > +#define MESON_A1_CPU_CLK_SET_PARENT(nbd, index) \
> > + ((nbd)->mux_ops->set_parent((nbd)->cpu_clk, index))
>
> ... Directly going for the mux ops ??!?? No way !
>
> We have a framework to handle the clocks, the whole point is to use it,
> not bypass it !
>
I suppose you understand my approach, which is quite similar to what is
happening in the Mediatek driver:
https://elixir.bootlin.com/linux/latest/source/drivers/clk/mediatek/clk-mux.c#L295
Initially, I attempted to set the parent using the clk_set_parent() API.
However, I encountered a problem with recursive calling of the
notifier_block. This issue arises because the parent triggers
notifications for its children, leading to repeated calls to the
notifier_block.
I find it puzzling why I cannot call an internal function or callback
within the internal driver context. After all, the notifier block is
just a part of the set_rate() flow. From a global Clock Control
Framework perspective, the context should not change.
> > +
> > +static int meson_a1_cpu_clk_notifier_cb(struct notifier_block *nb,
> > + unsigned long event, void *data)
> > +{
> > + struct a1_cpu_clk_nb_data *nbd;
> > + int ret = 0;
> > +
> > + nbd = container_of(nb, struct a1_cpu_clk_nb_data, nb);
> > +
> > + switch (event) {
> > + case PRE_RATE_CHANGE:
> > + nbd->parent = MESON_A1_CPU_CLK_GET_PARENT(nbd);
> > + /* Fallback to the CPU fixed clock */
> > + ret = MESON_A1_CPU_CLK_SET_PARENT(nbd, 0);
> > + /* Wait for clock propagation */
> > + udelay(100);
> > + break;
> > +
> > + case POST_RATE_CHANGE:
> > + case ABORT_RATE_CHANGE:
> > + /* Back to the original parent clock */
> > + ret = MESON_A1_CPU_CLK_SET_PARENT(nbd, nbd->parent);
> > + /* Wait for clock propagation */
> > + udelay(100);
> > + break;
> > +
> > + default:
> > + pr_warn("Unknown event %lu for %s notifier block\n",
> > + event, clk_hw_get_name(nbd->cpu_clk));
> > + break;
> > + }
> > +
> > + return notifier_from_errno(ret);
> > +}
> > +
> > +static struct a1_cpu_clk_nb_data a1_cpu_clk_nb_data = {
> > + .mux_ops = &clk_regmap_mux_ops,
> > + .cpu_clk = &cpu_clk.hw,
> > + .nb.notifier_call = meson_a1_cpu_clk_notifier_cb,
> > +};
> > +
> > +static int meson_a1_dvfs_setup(struct platform_device *pdev)
> > +{
> > + struct device *dev = &pdev->dev;
> > + struct clk *notifier_clk;
> > + int ret;
> > +
> > + /* Setup clock notifier for cpu_clk */
> > + notifier_clk = devm_clk_hw_get_clk(dev, &cpu_clk.hw, "dvfs");
> > + if (IS_ERR(notifier_clk))
> > + return dev_err_probe(dev, PTR_ERR(notifier_clk),
> > + "can't get cpu_clk as notifier clock\n");
> > +
> > + ret = devm_clk_notifier_register(dev, notifier_clk,
> > + &a1_cpu_clk_nb_data.nb);
> > + if (ret)
> > + return dev_err_probe(dev, ret,
> > + "can't register cpu_clk notifier\n");
> > +
> > + return ret;
> > +}
> > +
> > +static int meson_a1_cpu_probe(struct platform_device *pdev)
> > +{
> > + struct device *dev = &pdev->dev;
> > + void __iomem *base;
> > + struct regmap *map;
> > + int clkid, i, err;
> > +
> > + base = devm_platform_ioremap_resource(pdev, 0);
> > + if (IS_ERR(base))
> > + return dev_err_probe(dev, PTR_ERR(base),
> > + "can't ioremap resource\n");
> > +
> > + map = devm_regmap_init_mmio(dev, base, &a1_cpu_regmap_cfg);
> > + if (IS_ERR(map))
> > + return dev_err_probe(dev, PTR_ERR(map),
> > + "can't init regmap mmio region\n");
> > +
> > + /* Populate regmap for the regmap backed clocks */
> > + for (i = 0; i < ARRAY_SIZE(a1_cpu_regmaps); i++)
> > + a1_cpu_regmaps[i]->map = map;
> > +
> > + for (clkid = 0; clkid < a1_cpu_clks.num; clkid++) {
> > + err = devm_clk_hw_register(dev, a1_cpu_clks.hws[clkid]);
> > + if (err)
> > + return dev_err_probe(dev, err,
> > + "clock[%d] registration failed\n",
> > + clkid);
> > + }
> > +
> > + err = devm_of_clk_add_hw_provider(dev, meson_clk_hw_get, &a1_cpu_clks);
> > + if (err)
> > + return dev_err_probe(dev, err, "can't add clk hw provider\n");
>
> I wonder if there is a window of opportunity to poke the syspll without
> your notifier here. That being said, the situation would be similar on g12.
>
Yes, I have taken into account what you did in the G12A CPU clock
relations. My thoughts were that it might not be applicable for the A1
case. This is because the sys_pll should be located in a different
driver from a logical perspective. Consequently, we cannot configure the
sys_pll notifier block to manage the cpu_clk from a different driver.
However, if I were to move the sys_pll clock object to the A1 CPU clock
controller, I believe the g12a sys_pll notifier approach would work.
> > +
> > + return meson_a1_dvfs_setup(pdev);
>
>
>
> > +}
> > +
> > +static const struct of_device_id a1_cpu_clkc_match_table[] = {
> > + { .compatible = "amlogic,a1-cpu-clkc", },
> > + {}
> > +};
> > +MODULE_DEVICE_TABLE(of, a1_cpu_clkc_match_table);
> > +
> > +static struct platform_driver a1_cpu_clkc_driver = {
> > + .probe = meson_a1_cpu_probe,
> > + .driver = {
> > + .name = "a1-cpu-clkc",
> > + .of_match_table = a1_cpu_clkc_match_table,
> > + },
> > +};
> > +
> > +module_platform_driver(a1_cpu_clkc_driver);
> > +MODULE_AUTHOR("Dmitry Rokosov <ddrokosov@salutedevices.com>");
> > +MODULE_LICENSE("GPL");
> > diff --git a/drivers/clk/meson/a1-cpu.h b/drivers/clk/meson/a1-cpu.h
> > new file mode 100644
> > index 000000000000..e9af4117e26f
> > --- /dev/null
> > +++ b/drivers/clk/meson/a1-cpu.h
>
> There is not point putting the definition here in a header
> These are clearly not going to be shared with another driver.
>
> Please drop this file
>
The same approach was applied to the Peripherals and PLL A1 drivers.
Honestly, I am not a fan of having different file organization within a
single logical code folder.
Please refer to:
drivers/clk/meson/a1-peripherals.h
drivers/clk/meson/a1-pll.h
> > @@ -0,0 +1,16 @@
> > +/* SPDX-License-Identifier: GPL-2.0+ */
> > +/*
> > + * Amlogic A1 CPU Clock Controller internals
> > + *
> > + * Copyright (c) 2024, SaluteDevices. All Rights Reserved.
> > + * Author: Dmitry Rokosov <ddrokosov@salutedevices.com>
> > + */
> > +
> > +#ifndef __A1_CPU_H
> > +#define __A1_CPU_H
> > +
> > +/* cpu clock controller register offset */
> > +#define CPUCTRL_CLK_CTRL0 0x80
> > +#define CPUCTRL_CLK_CTRL1 0x84
>
> You are claiming the registers from 0x00 to 0x84 (included), but only
> using these 2 registers ? What is the rest ? Are you sure there is only
> clocks in there ?
>
Yes, unfortunately, the register map for this IP is not described in the
A1 Datasheet. The only available information about it can be found in
the vendor clock driver, which provides details for only two registers
used to configure the CPU clock.
From vendor kernel dtsi:
clkc: clock-controller {
compatible = "amlogic,a1-clkc";
#clock-cells = <1>;
reg = <0x0 0xfe000800 0x0 0x100>,
<0x0 0xfe007c00 0x0 0x21c>,
<0x0 0xfd000000 0x0 0x88>; <==== CPU clock regmap
reg-names = "basic", "pll",
"cpu_clk";
clocks = <&xtal>;
clock-names = "core";
status = "okay";
};
From vendor clkc driver:
/*
* CPU clok register offset
* APB_BASE: APB1_BASE_ADDR = 0xfd000000
*/
#define CPUCTRL_CLK_CTRL0 0x80
#define CPUCTRL_CLK_CTRL1 0x84
[...]
--
Thank you,
Dmitry
^ permalink raw reply
* Re: [PATCH v6 4/6] clk: qcom: common: Add interconnect clocks support
From: Varadarajan Narayanan @ 2024-04-02 11:05 UTC (permalink / raw)
To: Dmitry Baryshkov
Cc: andersson, konrad.dybcio, mturquette, sboyd, robh,
krzysztof.kozlowski+dt, conor+dt, djakov, quic_anusha,
linux-arm-msm, linux-clk, devicetree, linux-kernel, linux-pm
In-Reply-To: <CAA8EJprP0m53B=g7jafAkfcqAQP4kE2ZvtxPXEe4s7ALjFXGSQ@mail.gmail.com>
On Tue, Apr 02, 2024 at 01:48:14PM +0300, Dmitry Baryshkov wrote:
> On Tue, 2 Apr 2024 at 13:34, Varadarajan Narayanan
> <quic_varada@quicinc.com> wrote:
> >
> > Unlike MSM platforms that manage NoC related clocks and scaling
> > from RPM, IPQ SoCs dont involve RPM in managing NoC related
> > clocks and there is no NoC scaling.
> >
> > However, there is a requirement to enable some NoC interface
> > clocks for accessing the peripheral controllers present on
> > these NoCs. Though exposing these as normal clocks would work,
> > having a minimalistic interconnect driver to handle these clocks
> > would make it consistent with other Qualcomm platforms resulting
> > in common code paths. This is similar to msm8996-cbf's usage of
> > icc-clk framework.
> >
> > Signed-off-by: Varadarajan Narayanan <quic_varada@quicinc.com>
> > ---
> > v6: first_id -> icc_first_node_id
> > Remove clock get so that the peripheral that uses the clock
> > can do the clock get
> > v5: Split changes in common.c to separate patch
> > Fix error handling
> > Use devm_icc_clk_register instead of icc_clk_register
> > v4: Use clk_hw instead of indices
> > Do icc register in qcom_cc_probe() call stream
> > Add icc clock info to qcom_cc_desc structure
> > v3: Use indexed identifiers here to avoid confusion
> > Fix error messages and move to common.c
> > v2: Move DTS to separate patch
> > Update commit log
> > Auto select CONFIG_INTERCONNECT & CONFIG_INTERCONNECT_CLK to fix build error
> > ---
> > drivers/clk/qcom/common.c | 38 +++++++++++++++++++++++++++++++++++++-
> > drivers/clk/qcom/common.h | 3 +++
> > 2 files changed, 40 insertions(+), 1 deletion(-)
> >
> > diff --git a/drivers/clk/qcom/common.c b/drivers/clk/qcom/common.c
> > index 75f09e6e057e..d5c008048994 100644
> > --- a/drivers/clk/qcom/common.c
> > +++ b/drivers/clk/qcom/common.c
> > @@ -8,6 +8,7 @@
> > #include <linux/regmap.h>
> > #include <linux/platform_device.h>
> > #include <linux/clk-provider.h>
> > +#include <linux/interconnect-clk.h>
> > #include <linux/reset-controller.h>
> > #include <linux/of.h>
> >
> > @@ -234,6 +235,41 @@ static struct clk_hw *qcom_cc_clk_hw_get(struct of_phandle_args *clkspec,
> > return cc->rclks[idx] ? &cc->rclks[idx]->hw : NULL;
> > }
> >
> > +static int qcom_cc_icc_register(struct device *dev,
> > + const struct qcom_cc_desc *desc)
> > +{
> > + struct icc_clk_data *icd;
> > + int i;
> > +
> > + if (!IS_ENABLED(CONFIG_INTERCONNECT_CLK))
> > + return 0;
> > +
> > + if (!desc->icc_hws)
> > + return 0;
> > +
> > + icd = devm_kcalloc(dev, desc->num_icc_hws, sizeof(*icd), GFP_KERNEL);
> > + if (!icd)
> > + return -ENOMEM;
> > +
> > + for (i = 0; i < desc->num_icc_hws; i++) {
> > + /*
> > + * get_clk will be done by the peripheral device using this
> > + * clock with devm_clk_hw_get_clk() so that we can associate
> > + * the clk handle with the consumer device. It would also help
> > + * us make it so that drivers defer probe until their
> > + * clk isn't an orphan.
>
> How the clock instance returned to the peripheral driver is supposed
> to correspond to the clock instance used by the icc-clk?
> > + */
> > + icd[i].clk = desc->icc_hws[i]->clk;
>
> You again are abusing clk_hw->clk. Please don't do that.
Ok, will clk_get in both the places.
Thanks
Varada
> > + if (!icd[i].clk)
> > + return dev_err_probe(dev, -ENOENT,
> > + "(%d) clock entry is null\n", i);
> > + icd[i].name = clk_hw_get_name(desc->icc_hws[i]);
> > + }
> > +
> > + return PTR_ERR_OR_ZERO(devm_icc_clk_register(dev, desc->icc_first_node_id,
> > + desc->num_icc_hws, icd));
> > +}
> > +
> > int qcom_cc_really_probe(struct platform_device *pdev,
> > const struct qcom_cc_desc *desc, struct regmap *regmap)
> > {
> > @@ -303,7 +339,7 @@ int qcom_cc_really_probe(struct platform_device *pdev,
> > if (ret)
> > return ret;
> >
> > - return 0;
> > + return qcom_cc_icc_register(dev, desc);
> > }
> > EXPORT_SYMBOL_GPL(qcom_cc_really_probe);
> >
> > diff --git a/drivers/clk/qcom/common.h b/drivers/clk/qcom/common.h
> > index 9c8f7b798d9f..9058ffd46260 100644
> > --- a/drivers/clk/qcom/common.h
> > +++ b/drivers/clk/qcom/common.h
> > @@ -29,6 +29,9 @@ struct qcom_cc_desc {
> > size_t num_gdscs;
> > struct clk_hw **clk_hws;
> > size_t num_clk_hws;
> > + struct clk_hw **icc_hws;
> > + size_t num_icc_hws;
> > + unsigned int icc_first_node_id;
> > };
> >
> > /**
> > --
> > 2.34.1
> >
>
>
> --
> With best wishes
>
> Dmitry
^ permalink raw reply
* RE: [EXT] Re: [PATCH v10 03/11] arm64: dts: imx8ulp-evk: enable usb nodes and add ptn5150 nodes
From: Xu Yang @ 2024-04-02 11:06 UTC (permalink / raw)
To: Shawn Guo
Cc: gregkh@linuxfoundation.org, robh+dt@kernel.org,
krzysztof.kozlowski+dt@linaro.org, shawnguo@kernel.org,
conor+dt@kernel.org, s.hauer@pengutronix.de,
kernel@pengutronix.de, festevam@gmail.com, dl-linux-imx,
peter.chen@kernel.org, Jun Li, linux-usb@vger.kernel.org,
devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
imx@lists.linux.dev, linux-kernel@vger.kernel.org
In-Reply-To: <ZgvDTPiBM65l3F+U@dragon>
>
> On Thu, Mar 21, 2024 at 04:14:31PM +0800, Xu Yang wrote:
> > Enable 2 USB nodes and add 2 PTN5150 nodes on i.MX8ULP evk board.
> >
> > Signed-off-by: Xu Yang <xu.yang_2@nxp.com>
> >
> > ---
> > Changes in v2:
> > - fix format as suggusted by Fabio
> > - add PTN5150 nodes
> > Changes in v3:
> > - no changes
> > Changes in v4:
> > - no changes
> > Changes in v5:
> > - no changes
> > Changes in v6:
> > - no changes
> > Changes in v7:
> > - no changes
> > Changes in v8:
> > - no changes
> > Changes in v9:
> > - no changes
> > Changes in v10:
> > - no changes
> > ---
> > arch/arm64/boot/dts/freescale/imx8ulp-evk.dts | 84 +++++++++++++++++++
> > 1 file changed, 84 insertions(+)
> >
> > diff --git a/arch/arm64/boot/dts/freescale/imx8ulp-evk.dts b/arch/arm64/boot/dts/freescale/imx8ulp-evk.dts
> > index 69dd8e31027c..bf418af31039 100644
> > --- a/arch/arm64/boot/dts/freescale/imx8ulp-evk.dts
> > +++ b/arch/arm64/boot/dts/freescale/imx8ulp-evk.dts
> > @@ -133,6 +133,64 @@ pcal6408: gpio@21 {
> > gpio-controller;
> > #gpio-cells = <2>;
> > };
> > +
> > + ptn5150_1: typec@1d {
>
> Could you sort devices in unit-address?
Okay.
>
> > + compatible = "nxp,ptn5150";
> > + reg = <0x1d>;
> > + int-gpios = <&gpiof 3 IRQ_TYPE_EDGE_FALLING>;
> > + pinctrl-names = "default";
> > + pinctrl-0 = <&pinctrl_typec1>;
> > + status = "disabled";
> > + };
> > +
> > + ptn5150_2: typec@3d {
> > + compatible = "nxp,ptn5150";
> > + reg = <0x3d>;
> > + int-gpios = <&gpiof 5 IRQ_TYPE_EDGE_FALLING>;
> > + pinctrl-names = "default";
>
> Broken indent?
Yes, will fix it.
Thanks,
Xu Yang
>
> Shawn
>
> > + pinctrl-0 = <&pinctrl_typec2>;
> > + status = "disabled";
> > + };
> > +};
> > +
> > +&usbotg1 {
> > + pinctrl-names = "default";
> > + pinctrl-0 = <&pinctrl_usb1>;
> > + dr_mode = "otg";
> > + hnp-disable;
> > + srp-disable;
> > + adp-disable;
> > + over-current-active-low;
> > + status = "okay";
> > +};
> > +
> > +&usbphy1 {
> > + fsl,tx-d-cal = <110>;
> > + status = "okay";
> > +};
> > +
> > +&usbmisc1 {
> > + status = "okay";
> > +};
> > +
> > +&usbotg2 {
> > + pinctrl-names = "default";
> > + pinctrl-0 = <&pinctrl_usb2>;
> > + dr_mode = "otg";
> > + hnp-disable;
> > + srp-disable;
> > + adp-disable;
> > + over-current-active-low;
> > + status = "okay";
> > +};
> > +
> > +&usbphy2 {
> > + fsl,tx-d-cal = <110>;
> > + status = "okay";
> > +};
> > +
> > +&usbmisc2 {
> > + status = "okay";
> > };
> >
> > &usdhc0 {
> > @@ -224,6 +282,32 @@ MX8ULP_PAD_PTE13__LPI2C7_SDA 0x20
> > >;
> > };
> >
> > + pinctrl_typec1: typec1grp {
> > + fsl,pins = <
> > + MX8ULP_PAD_PTF3__PTF3 0x3
> > + >;
> > + };
> > +
> > + pinctrl_typec2: typec2grp {
> > + fsl,pins = <
> > + MX8ULP_PAD_PTF5__PTF5 0x3
> > + >;
> > + };
> > +
> > + pinctrl_usb1: usb1grp {
> > + fsl,pins = <
> > + MX8ULP_PAD_PTF2__USB0_ID 0x10003
> > + MX8ULP_PAD_PTF4__USB0_OC 0x10003
> > + >;
> > + };
> > +
> > + pinctrl_usb2: usb2grp {
> > + fsl,pins = <
> > + MX8ULP_PAD_PTD23__USB1_ID 0x10003
> > + MX8ULP_PAD_PTF6__USB1_OC 0x10003
> > + >;
> > + };
> > +
> > pinctrl_usdhc0: usdhc0grp {
> > fsl,pins = <
> > MX8ULP_PAD_PTD1__SDHC0_CMD 0x3
> > --
> > 2.34.1
> >
^ permalink raw reply
* Re: [PATCH 5/5] arm64: dts: qcom: x1e80100: Enable cpufreq
From: Sudeep Holla @ 2024-04-02 11:09 UTC (permalink / raw)
To: Sibi Sankar
Cc: cristian.marussi, andersson, konrad.dybcio, jassisinghbrar,
robh+dt, krzysztof.kozlowski+dt, linux-kernel, linux-arm-msm,
devicetree, quic_rgottimu, quic_kshivnan, conor+dt, quic_gkohli,
quic_nkela, Ulf Hansson, quic_psodagud
In-Reply-To: <20240328095044.2926125-6-quic_sibis@quicinc.com>
On Thu, Mar 28, 2024 at 03:20:44PM +0530, Sibi Sankar wrote:
> Enable cpufreq on X1E80100 SoCs through the SCMI perf protocol node.
>
> Signed-off-by: Sibi Sankar <quic_sibis@quicinc.com>
> ---
> arch/arm64/boot/dts/qcom/x1e80100.dtsi | 27 ++++++++++++++++++++++++++
> 1 file changed, 27 insertions(+)
>
> diff --git a/arch/arm64/boot/dts/qcom/x1e80100.dtsi b/arch/arm64/boot/dts/qcom/x1e80100.dtsi
> index 4e0ec859ed61..d1d232cd1f25 100644
> --- a/arch/arm64/boot/dts/qcom/x1e80100.dtsi
> +++ b/arch/arm64/boot/dts/qcom/x1e80100.dtsi
> @@ -68,6 +68,7 @@ CPU0: cpu@0 {
> compatible = "qcom,oryon";
> reg = <0x0 0x0>;
> enable-method = "psci";
> + clocks = <&scmi_dvfs 0>;
> next-level-cache = <&L2_0>;
> power-domains = <&CPU_PD0>;
> power-domain-names = "psci";
Any reason why you wouldn't want to use the new genpd based perf controls.
IIRC it was added based on mainly Qcom platform requirements.
- clocks = <&scmi_dvfs 0>;
next-level-cache = <&L2_0>;
- power-domains = <&CPU_PD0>;
- power-domain-names = "psci";
+ power-domains = <&CPU_PD0>, <&scmi_dvfs 0>;
+ power-domain-names = "psci", "perf";
And the associated changes in the scmi dvfs node for cells property.
This change is OK but just wanted to check the reasoning for the choice.
--
Regards,
Sudeep
^ permalink raw reply
* RE: [EXT] Re: [PATCH v10 09/11] arm64: dts: imx93-11x11-evk: enable usb and typec nodes
From: Xu Yang @ 2024-04-02 11:10 UTC (permalink / raw)
To: Shawn Guo
Cc: gregkh@linuxfoundation.org, robh+dt@kernel.org,
krzysztof.kozlowski+dt@linaro.org, shawnguo@kernel.org,
conor+dt@kernel.org, s.hauer@pengutronix.de,
kernel@pengutronix.de, festevam@gmail.com, dl-linux-imx,
peter.chen@kernel.org, Jun Li, linux-usb@vger.kernel.org,
devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
imx@lists.linux.dev, linux-kernel@vger.kernel.org
In-Reply-To: <ZgvKteCEZJxShA/j@dragon>
>
> On Thu, Mar 21, 2024 at 04:14:37PM +0800, Xu Yang wrote:
> > There are 2 Type-C ports and 2 USB controllers on i.MX93. Enable them.
> >
> > Signed-off-by: Xu Yang <xu.yang_2@nxp.com>
> >
> > ---
> > Changes in v2:
> > - remove status property in ptn5110 nodes
> > - fix dt-schema warnings
> > Changes in v3:
> > - no changes
> > Changes in v4:
> > - no changes
> > Changes in v5:
> > - no changes
> > Changes in v6:
> > - no changes
> > Changes in v7:
> > - no changes
> > Changes in v8:
> > - no changes
> > Changes in v9:
> > - use compatible "nxp,ptn5110", "tcpci"
> > Changes in v10:
> > - no changes
> > ---
> > .../boot/dts/freescale/imx93-11x11-evk.dts | 118 ++++++++++++++++++
> > 1 file changed, 118 insertions(+)
> >
> > diff --git a/arch/arm64/boot/dts/freescale/imx93-11x11-evk.dts b/arch/arm64/boot/dts/freescale/imx93-11x11-evk.dts
> > index 9921ea13ab48..ecc01d872e95 100644
> > --- a/arch/arm64/boot/dts/freescale/imx93-11x11-evk.dts
> > +++ b/arch/arm64/boot/dts/freescale/imx93-11x11-evk.dts
> > @@ -5,6 +5,7 @@
> >
> > /dts-v1/;
> >
> > +#include <dt-bindings/usb/pd.h>
> > #include "imx93.dtsi"
> >
> > / {
> > @@ -104,6 +105,80 @@ &mu2 {
> > status = "okay";
> > };
> >
> > +&lpi2c3 {
> > + #address-cells = <1>;
> > + #size-cells = <0>;
> > + clock-frequency = <400000>;
> > + pinctrl-names = "default", "sleep";
> > + pinctrl-0 = <&pinctrl_lpi2c3>;
> > + pinctrl-1 = <&pinctrl_lpi2c3>;
>
> Do you really need "sleep" pinctrl state?
"sleep" pinctrl state can be removed.
>
> > + status = "okay";
> > +
> > + ptn5110: tcpc@50 {
> > + compatible = "nxp,ptn5110", "tcpci";
> > + reg = <0x50>;
> > + interrupt-parent = <&gpio3>;
> > + interrupts = <27 IRQ_TYPE_LEVEL_LOW>;
> > +
> > + typec1_con: connector {
> > + compatible = "usb-c-connector";
> > + label = "USB-C";
> > + power-role = "dual";
> > + data-role = "dual";
> > + try-power-role = "sink";
> > + source-pdos = <PDO_FIXED(5000, 3000, PDO_FIXED_USB_COMM)>;
> > + sink-pdos = <PDO_FIXED(5000, 3000, PDO_FIXED_USB_COMM)
> > + PDO_VAR(5000, 20000, 3000)>;
> > + op-sink-microwatt = <15000000>;
> > + self-powered;
> > +
> > + ports {
> > + #address-cells = <1>;
> > + #size-cells = <0>;
> > +
> > + port@0 {
> > + reg = <0>;
>
> Have a newline between properties and child node.
Okay.
Thanks,
Xu Yang
>
> Shawn
>
> > + typec1_dr_sw: endpoint {
> > + remote-endpoint = <&usb1_drd_sw>;
> > + };
> > + };
> > + };
> > + };
> > + };
> > +
> > + ptn5110_2: tcpc@51 {
> > + compatible = "nxp,ptn5110", "tcpci";
> > + reg = <0x51>;
> > + interrupt-parent = <&gpio3>;
> > + interrupts = <27 IRQ_TYPE_LEVEL_LOW>;
> > +
> > + typec2_con: connector {
> > + compatible = "usb-c-connector";
> > + label = "USB-C";
> > + power-role = "dual";
> > + data-role = "dual";
> > + try-power-role = "sink";
> > + source-pdos = <PDO_FIXED(5000, 3000, PDO_FIXED_USB_COMM)>;
> > + sink-pdos = <PDO_FIXED(5000, 3000, PDO_FIXED_USB_COMM)
> > + PDO_VAR(5000, 20000, 3000)>;
> > + op-sink-microwatt = <15000000>;
> > + self-powered;
> > +
> > + ports {
> > + #address-cells = <1>;
> > + #size-cells = <0>;
> > +
> > + port@0 {
> > + reg = <0>;
> > + typec2_dr_sw: endpoint {
> > + remote-endpoint = <&usb2_drd_sw>;
> > + };
> > + };
> > + };
> > + };
> > + };
> > +};
> > +
> > &eqos {
> > pinctrl-names = "default";
> > pinctrl-0 = <&pinctrl_eqos>;
> > @@ -156,6 +231,42 @@ &lpuart5 {
> > status = "okay";
> > };
> >
> > +&usbotg1 {
> > + dr_mode = "otg";
> > + hnp-disable;
> > + srp-disable;
> > + adp-disable;
> > + usb-role-switch;
> > + disable-over-current;
> > + samsung,picophy-pre-emp-curr-control = <3>;
> > + samsung,picophy-dc-vol-level-adjust = <7>;
> > + status = "okay";
> > +
> > + port {
> > + usb1_drd_sw: endpoint {
> > + remote-endpoint = <&typec1_dr_sw>;
> > + };
> > + };
> > +};
> > +
> > +&usbotg2 {
> > + dr_mode = "otg";
> > + hnp-disable;
> > + srp-disable;
> > + adp-disable;
> > + usb-role-switch;
> > + disable-over-current;
> > + samsung,picophy-pre-emp-curr-control = <3>;
> > + samsung,picophy-dc-vol-level-adjust = <7>;
> > + status = "okay";
> > +
> > + port {
> > + usb2_drd_sw: endpoint {
> > + remote-endpoint = <&typec2_dr_sw>;
> > + };
> > + };
> > +};
> > +
> > &usdhc1 {
> > pinctrl-names = "default", "state_100mhz", "state_200mhz";
> > pinctrl-0 = <&pinctrl_usdhc1>;
> > @@ -222,6 +333,13 @@ MX93_PAD_ENET2_TX_CTL__ENET1_RGMII_TX_CTL 0x57e
> > >;
> > };
> >
> > + pinctrl_lpi2c3: lpi2c3grp {
> > + fsl,pins = <
> > + MX93_PAD_GPIO_IO28__LPI2C3_SDA 0x40000b9e
> > + MX93_PAD_GPIO_IO29__LPI2C3_SCL 0x40000b9e
> > + >;
> > + };
> > +
> > pinctrl_uart1: uart1grp {
> > fsl,pins = <
> > MX93_PAD_UART1_RXD__LPUART1_RX 0x31e
> > --
> > 2.34.1
> >
^ permalink raw reply
* Re: [PATCH v6 3/6] interconnect: icc-clk: Add devm_icc_clk_register
From: Dmitry Baryshkov @ 2024-04-02 11:16 UTC (permalink / raw)
To: Varadarajan Narayanan
Cc: andersson, konrad.dybcio, mturquette, sboyd, robh,
krzysztof.kozlowski+dt, conor+dt, djakov, quic_anusha,
linux-arm-msm, linux-clk, devicetree, linux-kernel, linux-pm
In-Reply-To: <ZgvlrbvvPNA6HRiL@hu-varada-blr.qualcomm.com>
On Tue, 2 Apr 2024 at 14:02, Varadarajan Narayanan
<quic_varada@quicinc.com> wrote:
>
> On Tue, Apr 02, 2024 at 01:48:08PM +0300, Dmitry Baryshkov wrote:
> > On Tue, 2 Apr 2024 at 13:40, Dmitry Baryshkov
> > <dmitry.baryshkov@linaro.org> wrote:
> > >
> > > On Tue, 2 Apr 2024 at 13:34, Varadarajan Narayanan
> > > <quic_varada@quicinc.com> wrote:
> > > >
> > > > Wrap icc_clk_register to create devm_icc_clk_register to be
> > > > able to release the resources properly.
> > > >
> > > > Signed-off-by: Varadarajan Narayanan <quic_varada@quicinc.com>
> > > > ---
> > > > v5: Introduced devm_icc_clk_register
> > > > ---
> > > > drivers/interconnect/icc-clk.c | 29 +++++++++++++++++++++++++++++
> > > > include/linux/interconnect-clk.h | 4 ++++
> > > > 2 files changed, 33 insertions(+)
> > >
> > > Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
> >
> > Wait. Actually,
> >
> > Unreviewed-by: me
> >
> > Please return int from devm_icc_clk_register instead of returning the pointer.
>
> Wouldn't returning int break the general assumption that
> devm_foo(), returns the same type as foo(). For example
> devm_clk_hw_get_clk and clk_hw_get_clk return struct clk *?
Not always. The only reason to return icc_provider was to make it
possible to destroy it. With devres-managed function you don't have to
do anything.
--
With best wishes
Dmitry
^ permalink raw reply
* [PATCH v3 0/2] mfd: rohm-bd71828: Add power off
From: Andreas Kemnade @ 2024-04-02 11:16 UTC (permalink / raw)
To: lee, robh+dt, krzysztof.kozlowski+dt, conor+dt, mazziesaccount,
devicetree, linux-kernel
Cc: Andreas Kemnade
Add power off functionality. Marked as RFC because of magic numbers
without a good source and strange delays. The only information source is
a vendor kernel.
Changes in v3:
- define for poweroff bit
- rmw operation to set only that bit
Changes in v2:
- style corrections
- remove unnecessary writes and delays
- correctly unregister handler
Andreas Kemnade (2):
dt-bindings: mfd: Add ROHM BD71828 system-power-controller property
mfd: rohm-bd71828: Add power off functionality
.../bindings/mfd/rohm,bd71828-pmic.yaml | 2 ++
drivers/mfd/rohm-bd71828.c | 36 ++++++++++++++++++-
include/linux/mfd/rohm-bd71828.h | 3 ++
3 files changed, 40 insertions(+), 1 deletion(-)
--
2.39.2
^ permalink raw reply
* [PATCH v3 1/2] dt-bindings: mfd: Add ROHM BD71828 system-power-controller property
From: Andreas Kemnade @ 2024-04-02 11:16 UTC (permalink / raw)
To: lee, robh+dt, krzysztof.kozlowski+dt, conor+dt, mazziesaccount,
devicetree, linux-kernel
Cc: Andreas Kemnade, Krzysztof Kozlowski
In-Reply-To: <20240402111700.494004-1-andreas@kemnade.info>
As the PMIC can power off the system, add the corresponding property.
Signed-off-by: Andreas Kemnade <andreas@kemnade.info>
Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
---
Documentation/devicetree/bindings/mfd/rohm,bd71828-pmic.yaml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Documentation/devicetree/bindings/mfd/rohm,bd71828-pmic.yaml b/Documentation/devicetree/bindings/mfd/rohm,bd71828-pmic.yaml
index 11089aa89ec6..0b62f854bf6b 100644
--- a/Documentation/devicetree/bindings/mfd/rohm,bd71828-pmic.yaml
+++ b/Documentation/devicetree/bindings/mfd/rohm,bd71828-pmic.yaml
@@ -73,6 +73,8 @@ properties:
used to mark the pins which should not be configured for GPIO. Please see
the ../gpio/gpio.txt for more information.
+ system-power-controller: true
+
required:
- compatible
- reg
--
2.39.2
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox