* [PATCH 0/5] platform: arm64: Huawei Matebook E Go embedded controller
@ 2024-12-27 17:13 Pengyu Luo
2024-12-27 17:13 ` [PATCH 1/5] dt-bindings: platform: Add Huawei Matebook E Go EC Pengyu Luo
` (4 more replies)
0 siblings, 5 replies; 51+ messages in thread
From: Pengyu Luo @ 2024-12-27 17:13 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson,
Konrad Dybcio, Hans de Goede, Ilpo Järvinen,
Bryan O'Donoghue, Sebastian Reichel, Heikki Krogerus,
Greg Kroah-Hartman
Cc: devicetree, linux-kernel, linux-arm-msm, platform-driver-x86,
linux-pm, linux-usb, Dmitry Baryshkov, Nikita Travkin, Pengyu Luo
This adds binding, drivers and the DT support for the Huawei Matebook E Go
(sc8280xp) Embedded Controller which is also found in Huawei Matebook E Go
LTE (sc8180x), but I don't have the sc8180x one to perferform test, so this
series enable support for sc8280xp variant only, this series provides the
following features:
- battery and charger information report
- charging thresholds control
- FN lock (An alternative method)
- LID switch detection
- Temperature sensors
- USB Type-C altmode
- USB Type-C PD(high power)
Thanks to the work of Bjorn and Dmitry([1]), the work of Nikita([2]), writing a
EC driver won't be suffering. This work refers a lot to their work, also, many
other works. I mentioned them in the source file.
Depends: https://lore.kernel.org/linux-arm-msm/20241220160530.444864-1-mitltlatltl@gmail.com
[1] https://lore.kernel.org/all/20240614-yoga-ec-driver-v7-0-9f0b9b40ae76@linaro.org/
[2] https://lore.kernel.org/all/20240315-aspire1-ec-v5-0-f93381deff39@trvn.ru/
Signed-off-by: Pengyu Luo <mitltlatltl@gmail.com>
---
Pengyu Luo (5):
dt-bindings: platform: Add Huawei Matebook E Go EC
platform: arm64: add Huawei Matebook E Go (sc8280xp) EC driver
usb: typec: ucsi: add Huawei Matebook E Go (sc8280xp) ucsi driver
power: supply: add Huawei Matebook E Go (sc8280xp) psy driver
arm64: dts: qcom: gaokun3: Add Embedded Controller node
.../bindings/platform/huawei,gaokun-ec.yaml | 116 ++++
.../boot/dts/qcom/sc8280xp-huawei-gaokun3.dts | 139 ++++
drivers/platform/arm64/Kconfig | 19 +
drivers/platform/arm64/Makefile | 2 +
drivers/platform/arm64/huawei-gaokun-ec.c | 598 ++++++++++++++++++
drivers/platform/arm64/huawei-gaokun-wmi.c | 283 +++++++++
drivers/power/supply/Kconfig | 9 +
drivers/power/supply/Makefile | 1 +
drivers/power/supply/huawei-gaokun-battery.c | 446 +++++++++++++
drivers/usb/typec/ucsi/Kconfig | 9 +
drivers/usb/typec/ucsi/Makefile | 1 +
drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c | 481 ++++++++++++++
.../linux/platform_data/huawei-gaokun-ec.h | 90 +++
13 files changed, 2194 insertions(+)
create mode 100644 Documentation/devicetree/bindings/platform/huawei,gaokun-ec.yaml
create mode 100644 drivers/platform/arm64/huawei-gaokun-ec.c
create mode 100644 drivers/platform/arm64/huawei-gaokun-wmi.c
create mode 100644 drivers/power/supply/huawei-gaokun-battery.c
create mode 100644 drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c
create mode 100644 include/linux/platform_data/huawei-gaokun-ec.h
--
2.47.1
^ permalink raw reply [flat|nested] 51+ messages in thread
* [PATCH 1/5] dt-bindings: platform: Add Huawei Matebook E Go EC
2024-12-27 17:13 [PATCH 0/5] platform: arm64: Huawei Matebook E Go embedded controller Pengyu Luo
@ 2024-12-27 17:13 ` Pengyu Luo
2024-12-27 18:18 ` Rob Herring (Arm)
2024-12-28 9:54 ` Krzysztof Kozlowski
2024-12-27 17:13 ` [PATCH 2/5] platform: arm64: add Huawei Matebook E Go (sc8280xp) EC driver Pengyu Luo
` (3 subsequent siblings)
4 siblings, 2 replies; 51+ messages in thread
From: Pengyu Luo @ 2024-12-27 17:13 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson,
Konrad Dybcio, Hans de Goede, Ilpo Järvinen,
Bryan O'Donoghue, Sebastian Reichel, Heikki Krogerus,
Greg Kroah-Hartman
Cc: devicetree, linux-kernel, linux-arm-msm, platform-driver-x86,
linux-pm, linux-usb, Dmitry Baryshkov, Nikita Travkin, Pengyu Luo
Add binding for the EC found in the Huawei Matebook E Go (sc8280xp) and
Huawei Matebook E Go LTE (sc8180x) 2in1 tablet.
Signed-off-by: Pengyu Luo <mitltlatltl@gmail.com>
---
.../bindings/platform/huawei,gaokun-ec.yaml | 116 ++++++++++++++++++
1 file changed, 116 insertions(+)
create mode 100644 Documentation/devicetree/bindings/platform/huawei,gaokun-ec.yaml
diff --git a/Documentation/devicetree/bindings/platform/huawei,gaokun-ec.yaml b/Documentation/devicetree/bindings/platform/huawei,gaokun-ec.yaml
new file mode 100644
index 000000000..f5488b57b
--- /dev/null
+++ b/Documentation/devicetree/bindings/platform/huawei,gaokun-ec.yaml
@@ -0,0 +1,116 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/platform/huawei,gaokun-ec.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Huawei Matebook E Go Embedded Controller
+
+maintainers:
+ - Pengyu Luo <mitltlatltl@gmail.com>
+
+description:
+ Different from other Qualcomm Snapdragon sc8180x sc8280xp based machines,
+ the Huawei Matebook E Go tablets use embedded controllers while others
+ use something called pmic glink which handles battery, UCSI, USB Type-C DP
+ alt mode. Huawei one handles even more, like charging thresholds, FN lock,
+ lid status, HPD events for the USB Type-C DP alt mode, etc.
+
+properties:
+ compatible:
+ items:
+ - enum:
+ - huawei,sc8180x-gaokun-ec
+ - huawei,sc8280xp-gaokun-ec
+ - const: huawei,gaokun-ec
+
+ reg:
+ const: 0x38
+
+ interrupts:
+ maxItems: 1
+
+ connector:
+ $ref: /schemas/connector/usb-connector.yaml#
+
+required:
+ - compatible
+ - reg
+ - interrupts
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+ i2c15 {
+ clock-frequency = <400000>;
+
+ pinctrl-names = "default";
+ pinctrl-0 = <&i2c15_default>;
+
+ embedded-controller@38 {
+ compatible = "huawei,sc8280xp-gaokun-ec", ""huawei,gaokun-ec";
+ reg = <0x38>;
+
+ interrupts-extended = <&tlmm 107 IRQ_TYPE_LEVEL_LOW>;
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ connector@0 {
+ compatible = "usb-c-connector";
+ reg = <0>;
+ power-role = "dual";
+ data-role = "dual";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+
+ ucsi0_ss_in: endpoint {
+ remote-endpoint = <&usb_0_qmpphy_out>;
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+
+ ucsi0_sbu: endpoint {
+ remote-endpoint = <&usb0_sbu_mux>;
+ };
+ };
+ };
+ };
+
+ connector@1 {
+ compatible = "usb-c-connector";
+ reg = <1>;
+ power-role = "dual";
+ data-role = "dual";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+
+ ucsi1_ss_in: endpoint {
+ remote-endpoint = <&usb_1_qmpphy_out>;
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+
+ ucsi1_sbu: endpoint {
+ remote-endpoint = <&usb1_sbu_mux>;
+ };
+ };
+ };
+ };
+ };
--
2.47.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 2/5] platform: arm64: add Huawei Matebook E Go (sc8280xp) EC driver
2024-12-27 17:13 [PATCH 0/5] platform: arm64: Huawei Matebook E Go embedded controller Pengyu Luo
2024-12-27 17:13 ` [PATCH 1/5] dt-bindings: platform: Add Huawei Matebook E Go EC Pengyu Luo
@ 2024-12-27 17:13 ` Pengyu Luo
2024-12-27 18:21 ` Maya Matuszczyk
` (4 more replies)
2024-12-27 17:13 ` [PATCH 3/5] usb: typec: ucsi: add Huawei Matebook E Go (sc8280xp) ucsi driver Pengyu Luo
` (2 subsequent siblings)
4 siblings, 5 replies; 51+ messages in thread
From: Pengyu Luo @ 2024-12-27 17:13 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson,
Konrad Dybcio, Hans de Goede, Ilpo Järvinen,
Bryan O'Donoghue, Sebastian Reichel, Heikki Krogerus,
Greg Kroah-Hartman
Cc: devicetree, linux-kernel, linux-arm-msm, platform-driver-x86,
linux-pm, linux-usb, Dmitry Baryshkov, Nikita Travkin, Pengyu Luo
There are 3 variants, Huawei released first 2 at the same time.
Huawei Matebook E Go LTE(sc8180x), codename should be gaokun2.
Huawei Matebook E Go(sc8280xp@3.0GHz), codename is gaokun3.
Huawei Matebook E Go 2023(sc8280xp@2.69GHz).
Adding support for the latter two variants for now, this driver should
also work for the sc8180x variant according to acpi table files, but I
don't have the device yet.
Different from other Qualcomm Snapdragon sc8280xp based machines, the
Huawei Matebook E Go uses an embedded controller while others use
something called pmic glink. This embedded controller can be used to
perform a set of various functions, including, but not limited:
- Battery and charger monitoring;
- Charge control and smart charge;
- Fn_lock settings;
- Tablet lid status;
- Temperature sensors;
- USB Type-C notifications (ports orientation, DP alt mode HPD);
- USB Type-C PD (according to observation, up to 48w).
Add the driver for the EC, that creates devices for UCSI, wmi and power
supply devices.
Signed-off-by: Pengyu Luo <mitltlatltl@gmail.com>
---
drivers/platform/arm64/Kconfig | 19 +
drivers/platform/arm64/Makefile | 2 +
drivers/platform/arm64/huawei-gaokun-ec.c | 598 ++++++++++++++++++
drivers/platform/arm64/huawei-gaokun-wmi.c | 283 +++++++++
.../linux/platform_data/huawei-gaokun-ec.h | 90 +++
5 files changed, 992 insertions(+)
create mode 100644 drivers/platform/arm64/huawei-gaokun-ec.c
create mode 100644 drivers/platform/arm64/huawei-gaokun-wmi.c
create mode 100644 include/linux/platform_data/huawei-gaokun-ec.h
diff --git a/drivers/platform/arm64/Kconfig b/drivers/platform/arm64/Kconfig
index f88395ea3..eb7fbacf0 100644
--- a/drivers/platform/arm64/Kconfig
+++ b/drivers/platform/arm64/Kconfig
@@ -33,6 +33,25 @@ config EC_ACER_ASPIRE1
laptop where this information is not properly exposed via the
standard ACPI devices.
+config EC_HUAWEI_GAOKUN
+ tristate "Huawei Matebook E Go (sc8280xp) Embedded Controller driver"
+ depends on ARCH_QCOM || COMPILE_TEST
+ depends on I2C
+ depends on DRM
+ depends on POWER_SUPPLY
+ depends on INPUT
+ help
+ Say Y here to enable the EC driver for Huawei Matebook E Go 2in1
+ tablet. The driver handles battery(information, charge control) and
+ USB Type-C DP HPD events as well as some misc functions like the lid
+ sensor and temperature sensors, etc.
+
+ This driver provides battery and AC status support for the mentioned
+ laptop where this information is not properly exposed via the
+ standard ACPI devices.
+
+ Say M or Y here to include this support.
+
config EC_LENOVO_YOGA_C630
tristate "Lenovo Yoga C630 Embedded Controller driver"
depends on ARCH_QCOM || COMPILE_TEST
diff --git a/drivers/platform/arm64/Makefile b/drivers/platform/arm64/Makefile
index b2ae9114f..ed32ad6c0 100644
--- a/drivers/platform/arm64/Makefile
+++ b/drivers/platform/arm64/Makefile
@@ -6,4 +6,6 @@
#
obj-$(CONFIG_EC_ACER_ASPIRE1) += acer-aspire1-ec.o
+obj-$(CONFIG_EC_HUAWEI_GAOKUN) += huawei-gaokun-ec.o
+obj-$(CONFIG_EC_HUAWEI_GAOKUN) += huawei-gaokun-wmi.o
obj-$(CONFIG_EC_LENOVO_YOGA_C630) += lenovo-yoga-c630.o
diff --git a/drivers/platform/arm64/huawei-gaokun-ec.c b/drivers/platform/arm64/huawei-gaokun-ec.c
new file mode 100644
index 000000000..c1c657f7b
--- /dev/null
+++ b/drivers/platform/arm64/huawei-gaokun-ec.c
@@ -0,0 +1,598 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * huawei-gaokun-ec - An EC driver for HUAWEI Matebook E Go (sc8280xp)
+ *
+ * reference: drivers/platform/arm64/acer-aspire1-ec.c
+ * drivers/platform/arm64/lenovo-yoga-c630.c
+ *
+ * Copyright (C) 2024 Pengyu Luo <mitltlatltl@gmail.com>
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/notifier.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/version.h>
+
+#include <linux/platform_data/huawei-gaokun-ec.h>
+
+#define EC_EVENT 0x06
+
+/* Also can be found in ACPI specification 12.3 */
+#define EC_READ 0x80
+#define EC_WRITE 0x81
+#define EC_BURST 0x82
+#define EC_QUERY 0x84
+
+
+#define EC_EVENT_LID 0x81
+
+#define EC_LID_STATE 0x80
+#define EC_LID_OPEN BIT(1)
+
+#define UCSI_REG_SIZE 7
+
+/* for tx, command sequences are arranged as
+ * {master_cmd, slave_cmd, data_len, data_seq}
+ */
+#define REQ_HDR_SIZE 3
+#define INPUT_SIZE_OFFSET 2
+#define INPUT_DATA_OFFSET 3
+
+/* for rx, data sequences are arranged as
+ * {status, data_len(unreliable), data_seq}
+ */
+#define RESP_HDR_SIZE 2
+#define DATA_OFFSET 2
+
+
+struct gaokun_ec {
+ struct i2c_client *client;
+ struct mutex lock;
+ struct blocking_notifier_head notifier_list;
+ struct input_dev *idev;
+ bool suspended;
+};
+
+static int gaokun_ec_request(struct gaokun_ec *ec, const u8 *req,
+ size_t resp_len, u8 *resp)
+{
+ struct i2c_client *client = ec->client;
+ struct i2c_msg msgs[2] = {
+ {
+ .addr = client->addr,
+ .flags = client->flags,
+ .len = req[INPUT_SIZE_OFFSET] + REQ_HDR_SIZE,
+ .buf = req,
+ }, {
+ .addr = client->addr,
+ .flags = client->flags | I2C_M_RD,
+ .len = resp_len,
+ .buf = resp,
+ },
+ };
+
+ mutex_lock(&ec->lock);
+
+ i2c_transfer(client->adapter, msgs, 2);
+ usleep_range(2000, 2500);
+
+ mutex_unlock(&ec->lock);
+
+ return *resp;
+}
+
+/* -------------------------------------------------------------------------- */
+/* Common API */
+
+/**
+ * gaokun_ec_read - read from EC
+ * @ec: The gaokun_ec
+ * @req: The sequence to request
+ * @resp_len: The size to read
+ * @resp: Where the data are read to
+ *
+ * This function is used to read data after writing a magic sequence to EC.
+ * All EC operations dependent on this functions.
+ *
+ * Huawei uses magic sequences everywhere to complete various functions, all
+ * these sequences are passed to ECCD(a ACPI method which is quiet similar
+ * to gaokun_ec_request), there is no good abstraction to generalize these
+ * sequences, so just wrap it for now. Almost all magic sequences are kept
+ * in this file.
+ */
+int gaokun_ec_read(struct gaokun_ec *ec, const u8 *req,
+ size_t resp_len, u8 *resp)
+{
+ return gaokun_ec_request(ec, req, resp_len, resp);
+}
+EXPORT_SYMBOL_GPL(gaokun_ec_read);
+
+/**
+ * gaokun_ec_write - write to EC
+ * @ec: The gaokun_ec
+ * @req: The sequence to request
+ *
+ * This function has no big difference from gaokun_ec_read. When caller care
+ * only write status and no actual data are returnd, then use it.
+ */
+int gaokun_ec_write(struct gaokun_ec *ec, u8 *req)
+{
+ u8 resp[RESP_HDR_SIZE];
+
+ return gaokun_ec_request(ec, req, sizeof(resp), resp);
+}
+EXPORT_SYMBOL_GPL(gaokun_ec_write);
+
+int gaokun_ec_read_byte(struct gaokun_ec *ec, u8 *req, u8 *byte)
+{
+ int ret;
+ u8 resp[RESP_HDR_SIZE + sizeof(*byte)];
+
+ ret = gaokun_ec_read(ec, req, sizeof(resp), resp);
+ *byte = resp[DATA_OFFSET];
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gaokun_ec_read_byte);
+
+int gaokun_ec_register_notify(struct gaokun_ec *ec, struct notifier_block *nb)
+{
+ return blocking_notifier_chain_register(&ec->notifier_list, nb);
+}
+EXPORT_SYMBOL_GPL(gaokun_ec_register_notify);
+
+void gaokun_ec_unregister_notify(struct gaokun_ec *ec, struct notifier_block *nb)
+{
+ blocking_notifier_chain_unregister(&ec->notifier_list, nb);
+}
+EXPORT_SYMBOL_GPL(gaokun_ec_unregister_notify);
+
+/* -------------------------------------------------------------------------- */
+/* API For PSY */
+
+int gaokun_ec_psy_multi_read(struct gaokun_ec *ec, u8 reg,
+ size_t resp_len, u8 *resp)
+{
+ int i, ret;
+ u8 _resp[RESP_HDR_SIZE + 1];
+ u8 req[REQ_HDR_SIZE + 1] = {0x02, EC_READ, 1, };
+
+ for (i = 0; i < resp_len; ++i) {
+ req[INPUT_DATA_OFFSET] = reg++;
+ ret = gaokun_ec_read(ec, req, sizeof(_resp), _resp);
+ if (ret)
+ return -EIO;
+ resp[i] = _resp[DATA_OFFSET];
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(gaokun_ec_psy_multi_read);
+
+/* -------------------------------------------------------------------------- */
+/* API For WMI */
+
+/* Battery charging threshold */
+int gaokun_ec_wmi_get_threshold(struct gaokun_ec *ec, u8 *value, int ind)
+{
+ /* GBTT */
+ return gaokun_ec_read_byte(ec, (u8 []){0x02, 0x69, 1, ind}, value);
+}
+EXPORT_SYMBOL_GPL(gaokun_ec_wmi_get_threshold);
+
+int gaokun_ec_wmi_set_threshold(struct gaokun_ec *ec, u8 start, u8 end)
+{
+ /* SBTT */
+ int ret;
+ u8 req[REQ_HDR_SIZE + 2] = {0x02, 0x68, 2, 3, 0x5a};
+
+ ret = gaokun_ec_write(ec, req);
+ if (ret)
+ return -EIO;
+
+ if (start == 0 && end == 0)
+ return -EINVAL;
+
+ if (start >= 0 && start <= end && end <= 100) {
+ req[INPUT_DATA_OFFSET] = 1;
+ req[INPUT_DATA_OFFSET + 1] = start;
+ ret = gaokun_ec_write(ec, req);
+ if (ret)
+ return -EIO;
+
+ req[INPUT_DATA_OFFSET] = 2;
+ req[INPUT_DATA_OFFSET + 1] = end;
+ ret = gaokun_ec_write(ec, req);
+ } else {
+ return -EINVAL;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gaokun_ec_wmi_set_threshold);
+
+/* Smart charge param */
+int gaokun_ec_wmi_get_smart_charge_param(struct gaokun_ec *ec, u8 *value)
+{
+ /* GBAC */
+ return gaokun_ec_read_byte(ec, (u8 []){0x02, 0xE6, 0}, value);
+}
+EXPORT_SYMBOL_GPL(gaokun_ec_wmi_get_smart_charge_param);
+
+int gaokun_ec_wmi_set_smart_charge_param(struct gaokun_ec *ec, u8 value)
+{
+ /* SBAC */
+ if (value < 0 || value > 2)
+ return -EINVAL;
+
+ return gaokun_ec_write(ec, (u8 []){0x02, 0xE5, 1, value});
+}
+EXPORT_SYMBOL_GPL(gaokun_ec_wmi_set_smart_charge_param);
+
+/* Smart charge */
+int gaokun_ec_wmi_get_smart_charge(struct gaokun_ec *ec,
+ u8 data[GAOKUN_SMART_CHARGE_DATA_SIZE])
+{
+ /* GBCM */
+ u8 req[REQ_HDR_SIZE] = {0x02, 0xE4, 0};
+ u8 resp[RESP_HDR_SIZE + 4];
+ int ret;
+
+ ret = gaokun_ec_read(ec, req, sizeof(resp), resp);
+ if (ret)
+ return -EIO;
+
+ data[0] = resp[DATA_OFFSET];
+ data[1] = resp[DATA_OFFSET + 1];
+ data[2] = resp[DATA_OFFSET + 2];
+ data[3] = resp[DATA_OFFSET + 3];
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(gaokun_ec_wmi_get_smart_charge);
+
+int gaokun_ec_wmi_set_smart_charge(struct gaokun_ec *ec,
+ u8 data[GAOKUN_SMART_CHARGE_DATA_SIZE])
+{
+ /* SBCM */
+ u8 req[REQ_HDR_SIZE + GAOKUN_SMART_CHARGE_DATA_SIZE] = {0x02, 0XE3, 4,};
+
+ if (!(data[2] >= 0 && data[2] <= data[3] && data[3] <= 100))
+ return -EINVAL;
+
+ memcpy(req + INPUT_DATA_OFFSET, data, GAOKUN_SMART_CHARGE_DATA_SIZE);
+
+ return gaokun_ec_write(ec, req);
+}
+EXPORT_SYMBOL_GPL(gaokun_ec_wmi_set_smart_charge);
+
+/* Fn lock */
+int gaokun_ec_wmi_get_fn_lock(struct gaokun_ec *ec, u8 *on)
+{
+ /* GFRS */
+ int ret;
+ u8 val;
+ u8 req[REQ_HDR_SIZE] = {0x02, 0x6B, 0};
+
+ ret = gaokun_ec_read_byte(ec, req, &val);
+ if (val == 0x55)
+ *on = 0;
+ else if (val == 0x5A)
+ *on = 1;
+ else
+ return -EIO;
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gaokun_ec_wmi_get_fn_lock);
+
+int gaokun_ec_wmi_set_fn_lock(struct gaokun_ec *ec, u8 on)
+{
+ /* SFRS */
+ u8 req[REQ_HDR_SIZE + 1] = {0x02, 0x6C, 1,};
+
+ if (on == 0)
+ req[INPUT_DATA_OFFSET] = 0x55;
+ else if (on == 1)
+ req[INPUT_DATA_OFFSET] = 0x5A;
+ else
+ return -EINVAL;
+
+ return gaokun_ec_write(ec, req);
+}
+EXPORT_SYMBOL_GPL(gaokun_ec_wmi_set_fn_lock);
+
+/* Thermal Zone */
+/* Range from 0 to 0x2C, partial valid */
+static const u8 temp_reg[] = {0x05, 0x07, 0x08, 0x0E, 0x0F, 0x12, 0x15, 0x1E,
+ 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
+ 0x27, 0x28, 0x29, 0x2A};
+
+int gaokun_ec_wmi_get_temp(struct gaokun_ec *ec, s16 temp[GAOKUN_TZ_REG_NUM])
+{
+ /* GTMP */
+ u8 req[REQ_HDR_SIZE] = {0x02, 0x61, 1,};
+ u8 resp[RESP_HDR_SIZE + sizeof(s16)];
+ int ret, i = 0;
+
+ while (i < GAOKUN_TZ_REG_NUM) {
+ req[INPUT_DATA_OFFSET] = temp_reg[i];
+ ret = gaokun_ec_read(ec, req, sizeof(resp), resp);
+ if (ret)
+ return -EIO;
+ temp[i++] = *(s16 *)(resp + DATA_OFFSET);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(gaokun_ec_wmi_get_temp);
+
+/* -------------------------------------------------------------------------- */
+/* API For UCSI */
+
+int gaokun_ec_ucsi_read(struct gaokun_ec *ec,
+ u8 resp[GAOKUN_UCSI_READ_SIZE])
+{
+ u8 req[REQ_HDR_SIZE] = {0x3, 0xD5, 0};
+ u8 _resp[RESP_HDR_SIZE + GAOKUN_UCSI_READ_SIZE];
+ int ret;
+
+ ret = gaokun_ec_read(ec, req, sizeof(_resp), _resp);
+ if (ret)
+ return ret;
+
+ memcpy(resp, _resp + DATA_OFFSET, GAOKUN_UCSI_READ_SIZE);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(gaokun_ec_ucsi_read);
+
+int gaokun_ec_ucsi_write(struct gaokun_ec *ec,
+ const u8 req[GAOKUN_UCSI_WRITE_SIZE])
+{
+ u8 _req[REQ_HDR_SIZE + GAOKUN_UCSI_WRITE_SIZE];
+
+ _req[0] = 0x03;
+ _req[1] = 0xD4;
+ _req[INPUT_SIZE_OFFSET] = GAOKUN_UCSI_WRITE_SIZE;
+ memcpy(_req + INPUT_DATA_OFFSET, req, GAOKUN_UCSI_WRITE_SIZE);
+
+ return gaokun_ec_write(ec, _req);
+}
+EXPORT_SYMBOL_GPL(gaokun_ec_ucsi_write);
+
+int gaokun_ec_ucsi_get_reg(struct gaokun_ec *ec, u8 *ureg)
+{
+ u8 req[REQ_HDR_SIZE] = {0x03, 0xD3, 0};
+ u8 _resp[RESP_HDR_SIZE + UCSI_REG_SIZE];
+ int ret;
+
+ ret = gaokun_ec_read(ec, req, sizeof(_resp), _resp);
+ if (ret)
+ return ret;
+
+ memcpy(ureg, _resp + DATA_OFFSET, UCSI_REG_SIZE);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(gaokun_ec_ucsi_get_reg);
+
+int gaokun_ec_ucsi_pan_ack(struct gaokun_ec *ec, int port_id)
+{
+ u8 req[REQ_HDR_SIZE + 1] = {0x03, 0xD2, 1, 0};
+
+ if (port_id >= 0)
+ req[INPUT_DATA_OFFSET] = 1 << port_id;
+
+ return gaokun_ec_write(ec, req);
+}
+EXPORT_SYMBOL_GPL(gaokun_ec_ucsi_pan_ack);
+
+/* -------------------------------------------------------------------------- */
+/* Modern Standby */
+
+static int gaokun_ec_suspend(struct device *dev)
+{
+ struct gaokun_ec *ec = dev_get_drvdata(dev);
+ u8 req[REQ_HDR_SIZE + 1] = {0x02, 0xB2, 1, 0xDB};
+ int ret;
+
+ if (ec->suspended)
+ return 0;
+
+ ret = gaokun_ec_write(ec, req);
+
+ if (ret)
+ return ret;
+
+ ec->suspended = true;
+
+ return 0;
+}
+
+static int gaokun_ec_resume(struct device *dev)
+{
+ struct gaokun_ec *ec = dev_get_drvdata(dev);
+ u8 req[REQ_HDR_SIZE + 1] = {0x02, 0xB2, 1, 0xEB};
+ int ret;
+ int i;
+
+ if (!ec->suspended)
+ return 0;
+
+ for (i = 0; i < 3; i++) {
+ ret = gaokun_ec_write(ec, req);
+ if (ret == 0)
+ break;
+
+ msleep(100);
+ };
+
+ ec->suspended = false;
+
+ return 0;
+}
+
+static void gaokun_aux_release(struct device *dev)
+{
+ struct auxiliary_device *adev = to_auxiliary_dev(dev);
+
+ kfree(adev);
+}
+
+static void gaokun_aux_remove(void *data)
+{
+ struct auxiliary_device *adev = data;
+
+ auxiliary_device_delete(adev);
+ auxiliary_device_uninit(adev);
+}
+
+static int gaokun_aux_init(struct device *parent, const char *name,
+ struct gaokun_ec *ec)
+{
+ struct auxiliary_device *adev;
+ int ret;
+
+ adev = kzalloc(sizeof(*adev), GFP_KERNEL);
+ if (!adev)
+ return -ENOMEM;
+
+ adev->name = name;
+ adev->id = 0;
+ adev->dev.parent = parent;
+ adev->dev.release = gaokun_aux_release;
+ adev->dev.platform_data = ec;
+ /* Allow aux devices to access parent's DT nodes directly */
+ device_set_of_node_from_dev(&adev->dev, parent);
+
+ ret = auxiliary_device_init(adev);
+ if (ret) {
+ kfree(adev);
+ return ret;
+ }
+
+ ret = auxiliary_device_add(adev);
+ if (ret) {
+ auxiliary_device_uninit(adev);
+ return ret;
+ }
+
+ return devm_add_action_or_reset(parent, gaokun_aux_remove, adev);
+}
+
+/* -------------------------------------------------------------------------- */
+/* EC */
+
+static irqreturn_t gaokun_ec_irq_handler(int irq, void *data)
+{
+ struct gaokun_ec *ec = data;
+ u8 req[REQ_HDR_SIZE] = {EC_EVENT, EC_QUERY, 0};
+ u8 status, id;
+ int ret;
+
+ ret = gaokun_ec_read_byte(ec, req, &id);
+ if (ret)
+ return IRQ_HANDLED;
+
+ switch (id) {
+ case 0x0: /* No event */
+ break;
+
+ case EC_EVENT_LID:
+ gaokun_ec_psy_read_byte(ec, EC_LID_STATE, &status);
+ status = EC_LID_OPEN & status;
+ input_report_switch(ec->idev, SW_LID, !status);
+ input_sync(ec->idev);
+ break;
+
+ default:
+ blocking_notifier_call_chain(&ec->notifier_list, id, ec);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int gaokun_ec_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct gaokun_ec *ec;
+ int ret;
+
+ ec = devm_kzalloc(dev, sizeof(*ec), GFP_KERNEL);
+ if (!ec)
+ return -ENOMEM;
+
+ mutex_init(&ec->lock);
+ ec->client = client;
+ i2c_set_clientdata(client, ec);
+ BLOCKING_INIT_NOTIFIER_HEAD(&ec->notifier_list);
+
+ /* Lid switch */
+ ec->idev = devm_input_allocate_device(dev);
+ if (!ec->idev)
+ return -ENOMEM;
+
+ ec->idev->name = "LID";
+ ec->idev->phys = "gaokun-ec/input0";
+ input_set_capability(ec->idev, EV_SW, SW_LID);
+
+ ret = input_register_device(ec->idev);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to register input device\n");
+
+ ret = gaokun_aux_init(dev, "psy", ec);
+ if (ret)
+ return ret;
+
+ ret = gaokun_aux_init(dev, "wmi", ec);
+ if (ret)
+ return ret;
+
+ ret = gaokun_aux_init(dev, "ucsi", ec);
+ if (ret)
+ return ret;
+
+ ret = devm_request_threaded_irq(dev, client->irq, NULL,
+ gaokun_ec_irq_handler, IRQF_ONESHOT,
+ dev_name(dev), ec);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to request irq\n");
+
+ return 0;
+}
+
+static const struct i2c_device_id gaokun_ec_id[] = {
+ { "gaokun-ec", },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, gaokun_ec_id);
+
+static const struct of_device_id gaokun_ec_of_match[] = {
+ { .compatible = "huawei,gaokun-ec", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, gaokun_ec_of_match);
+
+static const struct dev_pm_ops gaokun_ec_pm_ops = {
+ NOIRQ_SYSTEM_SLEEP_PM_OPS(gaokun_ec_suspend, gaokun_ec_resume)
+};
+
+static struct i2c_driver gaokun_ec_driver = {
+ .driver = {
+ .name = "gaokun-ec",
+ .of_match_table = gaokun_ec_of_match,
+ .pm = &gaokun_ec_pm_ops,
+ },
+ .probe = gaokun_ec_probe,
+ .id_table = gaokun_ec_id,
+};
+module_i2c_driver(gaokun_ec_driver);
+
+MODULE_DESCRIPTION("HUAWEI Matebook E Go EC driver");
+MODULE_AUTHOR("Pengyu Luo <mitltlatltl@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/arm64/huawei-gaokun-wmi.c b/drivers/platform/arm64/huawei-gaokun-wmi.c
new file mode 100644
index 000000000..793cb1659
--- /dev/null
+++ b/drivers/platform/arm64/huawei-gaokun-wmi.c
@@ -0,0 +1,283 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * huawei-gaokun-wmi - A WMI driver for HUAWEI Matebook E Go (sc8280xp)
+ *
+ * reference: drivers/platform/x86/huawei-wmi.c
+ *
+ * Copyright (C) 2024 Pengyu Luo <mitltlatltl@gmail.com>
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/sysfs.h>
+#include <linux/version.h>
+
+#include <linux/platform_data/huawei-gaokun-ec.h>
+
+struct gaokun_wmi {
+ struct gaokun_ec *ec;
+ struct device *dev;
+ struct platform_device *wmi;
+};
+
+/* -------------------------------------------------------------------------- */
+/* Battery charging threshold */
+
+enum gaokun_wmi_threshold_ind {
+ START = 1,
+ END = 2,
+};
+
+static ssize_t charge_control_thresholds_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ u8 start, end;
+ struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
+
+ ret = gaokun_ec_wmi_get_threshold(ecwmi->ec, &start, START)
+ || gaokun_ec_wmi_get_threshold(ecwmi->ec, &end, END);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%d %d\n", start, end);
+}
+
+static ssize_t charge_control_thresholds_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ int ret;
+ u8 start, end;
+ struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
+
+ if (sscanf(buf, "%hhd %hhd", &start, &end) != 2)
+ return -EINVAL;
+
+ ret = gaokun_ec_wmi_set_threshold(ecwmi->ec, start, end);
+ if (ret)
+ return ret;
+
+ return size;
+}
+
+static DEVICE_ATTR_RW(charge_control_thresholds);
+
+/* -------------------------------------------------------------------------- */
+/* Smart charge param */
+
+static ssize_t smart_charge_param_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ u8 value;
+ struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
+
+ ret = gaokun_ec_wmi_get_smart_charge_param(ecwmi->ec, &value);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%d\n", value);
+}
+
+static ssize_t smart_charge_param_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ int ret;
+ u8 value;
+ struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
+
+ if (kstrtou8(buf, 10, &value))
+ return -EINVAL;
+
+ ret = gaokun_ec_wmi_set_smart_charge_param(ecwmi->ec, value);
+ if (ret)
+ return ret;
+
+ return size;
+}
+
+static DEVICE_ATTR_RW(smart_charge_param);
+
+/* -------------------------------------------------------------------------- */
+/* Smart charge */
+
+static ssize_t smart_charge_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ u8 bf[GAOKUN_SMART_CHARGE_DATA_SIZE];
+ struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
+
+ ret = gaokun_ec_wmi_get_smart_charge(ecwmi->ec, bf);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%d %d %d %d\n",
+ bf[0], bf[1], bf[2], bf[3]);
+}
+
+static ssize_t smart_charge_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ int ret;
+ u8 bf[GAOKUN_SMART_CHARGE_DATA_SIZE];
+ struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
+
+ if (sscanf(buf, "%hhd %hhd %hhd %hhd", bf, bf + 1, bf + 2, bf + 3) != 4)
+ return -EINVAL;
+
+ ret = gaokun_ec_wmi_set_smart_charge(ecwmi->ec, bf);
+ if (ret)
+ return ret;
+
+ return size;
+}
+
+static DEVICE_ATTR_RW(smart_charge);
+
+/* -------------------------------------------------------------------------- */
+/* Fn lock */
+
+static ssize_t fn_lock_state_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ u8 on;
+ struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
+
+ ret = gaokun_ec_wmi_get_fn_lock(ecwmi->ec, &on);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%d\n", on);
+}
+
+static ssize_t fn_lock_state_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ int ret;
+ u8 on;
+ struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
+
+ if (kstrtou8(buf, 10, &on))
+ return -EINVAL;
+
+ ret = gaokun_ec_wmi_set_fn_lock(ecwmi->ec, on);
+ if (ret)
+ return ret;
+
+ return size;
+}
+
+static DEVICE_ATTR_RW(fn_lock_state);
+
+/* -------------------------------------------------------------------------- */
+/* Thermal Zone */
+
+static ssize_t temperature_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+
+ int ret, len, i;
+ char *ptr = buf;
+ s16 value;
+ s16 temp[GAOKUN_TZ_REG_NUM];
+ struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
+
+ ret = gaokun_ec_wmi_get_temp(ecwmi->ec, temp);
+ if (ret)
+ return ret;
+
+ i = 0;
+ len = 0;
+ while (i < GAOKUN_TZ_REG_NUM) {
+ value = temp[i++];
+ if (value < 0) {
+ len += sprintf(ptr + len, "-");
+ value = -value;
+ }
+ len += sprintf(ptr + len, "%d.%d ", value / 10, value % 10);
+ }
+ len += sprintf(ptr + len, "\n");
+
+ return len;
+}
+
+static DEVICE_ATTR_RO(temperature);
+
+static struct attribute *gaokun_wmi_features_attrs[] = {
+ &dev_attr_charge_control_thresholds.attr,
+ &dev_attr_smart_charge_param.attr,
+ &dev_attr_smart_charge.attr,
+ &dev_attr_fn_lock_state.attr,
+ &dev_attr_temperature.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(gaokun_wmi_features);
+
+static int gaokun_wmi_probe(struct auxiliary_device *adev,
+ const struct auxiliary_device_id *id)
+{
+ struct gaokun_ec *ec = adev->dev.platform_data;
+ struct device *dev = &adev->dev;
+ struct gaokun_wmi *ecwmi;
+
+ ecwmi = devm_kzalloc(&adev->dev, sizeof(*ecwmi), GFP_KERNEL);
+ if (!ecwmi)
+ return -ENOMEM;
+
+ ecwmi->ec = ec;
+ ecwmi->dev = dev;
+
+ auxiliary_set_drvdata(adev, ecwmi);
+
+ /* make it under /sys/devices/platform, convenient for sysfs I/O,
+ * while adev is under
+ * /sys/devices/platform/soc@0/ac0000.geniqup/a9c000.i2c/i2c-15/15-0038/
+ */
+ ecwmi->wmi = platform_device_register_simple("gaokun-wmi", -1, NULL, 0);
+ if (IS_ERR(ecwmi->wmi))
+ return dev_err_probe(dev, PTR_ERR(ecwmi->wmi),
+ "Failed to register wmi platform device\n");
+
+ platform_set_drvdata(ecwmi->wmi, ecwmi);
+
+ return device_add_groups(&ecwmi->wmi->dev, gaokun_wmi_features_groups);
+}
+
+static void gaokun_wmi_remove(struct auxiliary_device *adev)
+{
+ struct gaokun_wmi *ecwmi = auxiliary_get_drvdata(adev);
+ struct platform_device *wmi = ecwmi->wmi;
+
+ device_remove_groups(&wmi->dev, gaokun_wmi_features_groups);
+ platform_device_unregister(ecwmi->wmi);
+}
+
+static const struct auxiliary_device_id gaokun_wmi_id_table[] = {
+ { .name = GAOKUN_MOD_NAME "." GAOKUN_DEV_WMI, },
+ {}
+};
+MODULE_DEVICE_TABLE(auxiliary, gaokun_wmi_id_table);
+
+static struct auxiliary_driver gaokun_wmi_driver = {
+ .name = GAOKUN_DEV_WMI,
+ .id_table = gaokun_wmi_id_table,
+ .probe = gaokun_wmi_probe,
+ .remove = gaokun_wmi_remove,
+};
+
+module_auxiliary_driver(gaokun_wmi_driver);
+
+MODULE_DESCRIPTION("HUAWEI Matebook E Go WMI driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/platform_data/huawei-gaokun-ec.h b/include/linux/platform_data/huawei-gaokun-ec.h
new file mode 100644
index 000000000..a649e9ecf
--- /dev/null
+++ b/include/linux/platform_data/huawei-gaokun-ec.h
@@ -0,0 +1,90 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Huawei Matebook E Go (sc8280xp) Embedded Controller
+ *
+ * Copyright (C) 2024 Pengyu Luo <mitltlatltl@gmail.com>
+ *
+ */
+
+#ifndef __HUAWEI_GAOKUN_EC_H__
+#define __HUAWEI_GAOKUN_EC_H__
+
+#define GAOKUN_UCSI_CCI_SIZE 4
+#define GAOKUN_UCSI_DATA_SIZE 16
+#define GAOKUN_UCSI_READ_SIZE (GAOKUN_UCSI_CCI_SIZE + GAOKUN_UCSI_DATA_SIZE)
+#define GAOKUN_UCSI_WRITE_SIZE 0x18
+
+#define GAOKUN_TZ_REG_NUM 20
+#define GAOKUN_SMART_CHARGE_DATA_SIZE 4 /* mode, delay, start, end */
+
+/* -------------------------------------------------------------------------- */
+
+struct gaokun_ec;
+struct notifier_block;
+
+#define GAOKUN_MOD_NAME "huawei_gaokun_ec"
+#define GAOKUN_DEV_PSY "psy"
+#define GAOKUN_DEV_WMI "wmi"
+#define GAOKUN_DEV_UCSI "ucsi"
+
+/* -------------------------------------------------------------------------- */
+/* Common API */
+
+int gaokun_ec_register_notify(struct gaokun_ec *ec,
+ struct notifier_block *nb);
+void gaokun_ec_unregister_notify(struct gaokun_ec *ec,
+ struct notifier_block *nb);
+
+int gaokun_ec_read(struct gaokun_ec *ec, const u8 *req,
+ size_t resp_len, u8 *resp);
+int gaokun_ec_write(struct gaokun_ec *ec, u8 *req);
+int gaokun_ec_read_byte(struct gaokun_ec *ec, u8 *req, u8 *byte);
+
+/* -------------------------------------------------------------------------- */
+/* API For PSY */
+
+int gaokun_ec_psy_multi_read(struct gaokun_ec *ec, u8 reg,
+ size_t resp_len, u8 *resp);
+
+static inline int gaokun_ec_psy_read_byte(struct gaokun_ec *ec,
+ u8 reg, u8 *byte)
+{
+ return gaokun_ec_psy_multi_read(ec, reg, 1, byte);
+}
+
+static inline int gaokun_ec_psy_read_word(struct gaokun_ec *ec,
+ u8 reg, u16 *word)
+{
+ return gaokun_ec_psy_multi_read(ec, reg, 2, (u8 *)word);
+}
+
+/* -------------------------------------------------------------------------- */
+/* API For WMI */
+
+int gaokun_ec_wmi_get_threshold(struct gaokun_ec *ec, u8 *value, int ind);
+int gaokun_ec_wmi_set_threshold(struct gaokun_ec *ec, u8 start, u8 end);
+
+int gaokun_ec_wmi_get_smart_charge_param(struct gaokun_ec *ec, u8 *value);
+int gaokun_ec_wmi_set_smart_charge_param(struct gaokun_ec *ec, u8 value);
+
+int gaokun_ec_wmi_get_smart_charge(struct gaokun_ec *ec,
+ u8 data[GAOKUN_SMART_CHARGE_DATA_SIZE]);
+int gaokun_ec_wmi_set_smart_charge(struct gaokun_ec *ec,
+ u8 data[GAOKUN_SMART_CHARGE_DATA_SIZE]);
+
+int gaokun_ec_wmi_get_fn_lock(struct gaokun_ec *ec, u8 *on);
+int gaokun_ec_wmi_set_fn_lock(struct gaokun_ec *ec, u8 on);
+
+int gaokun_ec_wmi_get_temp(struct gaokun_ec *ec, s16 temp[GAOKUN_TZ_REG_NUM]);
+
+/* -------------------------------------------------------------------------- */
+/* API For UCSI */
+
+int gaokun_ec_ucsi_read(struct gaokun_ec *ec, u8 resp[GAOKUN_UCSI_READ_SIZE]);
+int gaokun_ec_ucsi_write(struct gaokun_ec *ec,
+ const u8 req[GAOKUN_UCSI_WRITE_SIZE]);
+
+int gaokun_ec_ucsi_get_reg(struct gaokun_ec *ec, u8 *ureg);
+int gaokun_ec_ucsi_pan_ack(struct gaokun_ec *ec, int port_id);
+
+
+#endif /* __HUAWEI_GAOKUN_EC_H__ */
--
2.47.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 3/5] usb: typec: ucsi: add Huawei Matebook E Go (sc8280xp) ucsi driver
2024-12-27 17:13 [PATCH 0/5] platform: arm64: Huawei Matebook E Go embedded controller Pengyu Luo
2024-12-27 17:13 ` [PATCH 1/5] dt-bindings: platform: Add Huawei Matebook E Go EC Pengyu Luo
2024-12-27 17:13 ` [PATCH 2/5] platform: arm64: add Huawei Matebook E Go (sc8280xp) EC driver Pengyu Luo
@ 2024-12-27 17:13 ` Pengyu Luo
2024-12-28 13:06 ` Bryan O'Donoghue
` (2 more replies)
2024-12-27 17:13 ` [PATCH 4/5] power: supply: add Huawei Matebook E Go (sc8280xp) psy driver Pengyu Luo
2024-12-27 17:13 ` [PATCH 5/5] arm64: dts: qcom: gaokun3: Add Embedded Controller node Pengyu Luo
4 siblings, 3 replies; 51+ messages in thread
From: Pengyu Luo @ 2024-12-27 17:13 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson,
Konrad Dybcio, Hans de Goede, Ilpo Järvinen,
Bryan O'Donoghue, Sebastian Reichel, Heikki Krogerus,
Greg Kroah-Hartman
Cc: devicetree, linux-kernel, linux-arm-msm, platform-driver-x86,
linux-pm, linux-usb, Dmitry Baryshkov, Nikita Travkin, Pengyu Luo
The Huawei Matebook E Go (sc8280xp) tablet provides implements UCSI
interface in the onboard EC. Add the glue driver to interface the
platform's UCSI implementation.
Signed-off-by: Pengyu Luo <mitltlatltl@gmail.com>
---
drivers/usb/typec/ucsi/Kconfig | 9 +
drivers/usb/typec/ucsi/Makefile | 1 +
drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c | 481 ++++++++++++++++++++
3 files changed, 491 insertions(+)
create mode 100644 drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c
diff --git a/drivers/usb/typec/ucsi/Kconfig b/drivers/usb/typec/ucsi/Kconfig
index 680e1b87b..0d0f07488 100644
--- a/drivers/usb/typec/ucsi/Kconfig
+++ b/drivers/usb/typec/ucsi/Kconfig
@@ -78,4 +78,13 @@ config UCSI_LENOVO_YOGA_C630
To compile the driver as a module, choose M here: the module will be
called ucsi_yoga_c630.
+config UCSI_HUAWEI_GAOKUN
+ tristate "UCSI Interface Driver for Huawei Matebook E Go (sc8280xp)"
+ depends on EC_HUAWEI_GAOKUN
+ help
+ This driver enables UCSI support on the Huawei Matebook E Go tablet.
+
+ To compile the driver as a module, choose M here: the module will be
+ called ucsi_huawei_gaokun.
+
endif
diff --git a/drivers/usb/typec/ucsi/Makefile b/drivers/usb/typec/ucsi/Makefile
index aed41d238..0b400122b 100644
--- a/drivers/usb/typec/ucsi/Makefile
+++ b/drivers/usb/typec/ucsi/Makefile
@@ -22,3 +22,4 @@ obj-$(CONFIG_UCSI_CCG) += ucsi_ccg.o
obj-$(CONFIG_UCSI_STM32G0) += ucsi_stm32g0.o
obj-$(CONFIG_UCSI_PMIC_GLINK) += ucsi_glink.o
obj-$(CONFIG_UCSI_LENOVO_YOGA_C630) += ucsi_yoga_c630.o
+obj-$(CONFIG_UCSI_HUAWEI_GAOKUN) += ucsi_huawei_gaokun.o
diff --git a/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c
new file mode 100644
index 000000000..84ed0407d
--- /dev/null
+++ b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c
@@ -0,0 +1,481 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ucsi-huawei-gaokun - A UCSI driver for HUAWEI Matebook E Go (sc8280xp)
+ *
+ * reference: drivers/usb/typec/ucsi/ucsi_yoga_c630.c
+ * drivers/usb/typec/ucsi/ucsi_glink.c
+ * drivers/soc/qcom/pmic_glink_altmode.c
+ *
+ * Copyright (C) 2024 Pengyu Luo <mitltlatltl@gmail.com>
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/bitops.h>
+#include <linux/completion.h>
+#include <linux/container_of.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/of.h>
+#include <linux/string.h>
+#include <linux/workqueue_types.h>
+
+#include <linux/usb/pd_vdo.h>
+#include <drm/bridge/aux-bridge.h>
+
+#include "ucsi.h"
+#include <linux/platform_data/huawei-gaokun-ec.h>
+
+
+#define EC_EVENT_UCSI 0x21
+#define EC_EVENT_USB 0x22
+
+#define GAOKUN_CCX_MASK GENMASK(1, 0)
+#define GAOKUN_MUX_MASK GENMASK(3, 2)
+
+#define GAOKUN_DPAM_MASK GENMASK(3, 0)
+#define GAOKUN_HPD_STATE_MASK BIT(4)
+#define GAOKUN_HPD_IRQ_MASK BIT(5)
+
+#define CCX_TO_ORI(ccx) (++ccx % 3)
+
+#define GET_IDX(updt) (ffs(updt) - 1)
+
+/* Configuration Channel Extension */
+enum gaokun_ucsi_ccx {
+ USBC_CCX_NORMAL,
+ USBC_CCX_REVERSE,
+ USBC_CCX_NONE,
+};
+
+enum gaokun_ucsi_mux {
+ USBC_MUX_NONE,
+ USBC_MUX_USB_2L,
+ USBC_MUX_DP_4L,
+ USBC_MUX_USB_DP,
+};
+
+struct gaokun_ucsi_reg {
+ u8 port_num;
+ u8 port_updt;
+ u8 port_data[4];
+ u8 checksum;
+ u8 reserved;
+} __packed;
+
+struct gaokun_ucsi_port {
+ struct completion usb_ack;
+ spinlock_t lock;
+
+ struct gaokun_ucsi *ucsi;
+ struct auxiliary_device *bridge;
+
+ int idx;
+ enum gaokun_ucsi_ccx ccx;
+ enum gaokun_ucsi_mux mux;
+ u8 mode;
+ u16 svid;
+ u8 hpd_state;
+ u8 hpd_irq;
+};
+
+struct gaokun_ucsi {
+ struct gaokun_ec *ec;
+ struct ucsi *ucsi;
+ struct gaokun_ucsi_port *ports;
+ struct device *dev;
+ struct work_struct work;
+ struct notifier_block nb;
+ u16 version;
+ u8 port_num;
+};
+
+/* -------------------------------------------------------------------------- */
+/* For UCSI */
+
+static int gaokun_ucsi_read_version(struct ucsi *ucsi, u16 *version)
+{
+ struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi);
+
+ *version = uec->version;
+
+ return 0;
+}
+
+static int gaokun_ucsi_read_cci(struct ucsi *ucsi, u32 *cci)
+{
+ struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi);
+ u8 buf[GAOKUN_UCSI_READ_SIZE];
+ int ret;
+
+ ret = gaokun_ec_ucsi_read(uec->ec, buf);
+ if (ret)
+ return ret;
+
+ memcpy(cci, buf, sizeof(*cci));
+
+ return 0;
+}
+
+static int gaokun_ucsi_read_message_in(struct ucsi *ucsi,
+ void *val, size_t val_len)
+{
+ struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi);
+ u8 buf[GAOKUN_UCSI_READ_SIZE];
+ int ret;
+
+ ret = gaokun_ec_ucsi_read(uec->ec, buf);
+ if (ret)
+ return ret;
+
+ memcpy(val, buf + GAOKUN_UCSI_CCI_SIZE,
+ min(val_len, GAOKUN_UCSI_DATA_SIZE));
+
+ return 0;
+}
+
+static int gaokun_ucsi_async_control(struct ucsi *ucsi, u64 command)
+{
+ struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi);
+ u8 buf[GAOKUN_UCSI_WRITE_SIZE] = {};
+
+ memcpy(buf, &command, sizeof(command));
+
+ return gaokun_ec_ucsi_write(uec->ec, buf);
+}
+
+static void gaokun_ucsi_update_connector(struct ucsi_connector *con)
+{
+ struct gaokun_ucsi *uec = ucsi_get_drvdata(con->ucsi);
+
+ if (con->num > uec->port_num)
+ return;
+
+ con->typec_cap.orientation_aware = true;
+}
+
+static void gaokun_set_orientation(struct ucsi_connector *con,
+ struct gaokun_ucsi_port *port)
+{
+ enum gaokun_ucsi_ccx ccx;
+ unsigned long flags;
+
+ spin_lock_irqsave(&port->lock, flags);
+ ccx = port->ccx;
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ typec_set_orientation(con->port, CCX_TO_ORI(ccx));
+}
+
+static void gaokun_ucsi_connector_status(struct ucsi_connector *con)
+{
+ struct gaokun_ucsi *uec = ucsi_get_drvdata(con->ucsi);
+ int idx;
+
+ idx = con->num - 1;
+ if (con->num > uec->port_num) {
+ dev_warn(uec->ucsi->dev, "set orientation out of range: con%d\n", idx);
+ return;
+ }
+
+ gaokun_set_orientation(con, &uec->ports[idx]);
+}
+
+const struct ucsi_operations gaokun_ucsi_ops = {
+ .read_version = gaokun_ucsi_read_version,
+ .read_cci = gaokun_ucsi_read_cci,
+ .read_message_in = gaokun_ucsi_read_message_in,
+ .sync_control = ucsi_sync_control_common,
+ .async_control = gaokun_ucsi_async_control,
+ .update_connector = gaokun_ucsi_update_connector,
+ .connector_status = gaokun_ucsi_connector_status,
+};
+
+/* -------------------------------------------------------------------------- */
+/* For Altmode */
+
+static void gaokun_ucsi_port_update(struct gaokun_ucsi_port *port,
+ const u8 *port_data)
+{
+ unsigned long flags;
+ u8 dcc, ddi;
+ int offset = port->idx * 2; /* every port has 2 Bytes data */
+
+ dcc = port_data[offset];
+ ddi = port_data[offset + 1];
+
+ spin_lock_irqsave(&port->lock, flags);
+
+ port->ccx = FIELD_GET(GAOKUN_CCX_MASK, dcc);
+ port->mux = FIELD_GET(GAOKUN_MUX_MASK, dcc);
+ port->mode = FIELD_GET(GAOKUN_DPAM_MASK, ddi);
+ port->hpd_state = FIELD_GET(GAOKUN_HPD_STATE_MASK, ddi);
+ port->hpd_irq = FIELD_GET(GAOKUN_HPD_IRQ_MASK, ddi);
+
+ switch (port->mux) {
+ case USBC_MUX_NONE:
+ port->svid = 0;
+ break;
+ case USBC_MUX_USB_2L:
+ port->svid = USB_SID_PD;
+ break;
+ case USBC_MUX_DP_4L:
+ case USBC_MUX_USB_DP:
+ port->svid = USB_SID_DISPLAYPORT;
+ if (port->ccx == USBC_CCX_REVERSE)
+ port->mode -= 6;
+ break;
+ default:
+ break;
+ }
+
+ spin_unlock_irqrestore(&port->lock, flags);
+}
+
+static int gaokun_ucsi_refresh(struct gaokun_ucsi *uec)
+{
+ struct gaokun_ucsi_reg ureg;
+ int ret, idx;
+
+ ret = gaokun_ec_ucsi_get_reg(uec->ec, (u8 *)&ureg);
+ if (ret)
+ return -EIO;
+
+ uec->port_num = ureg.port_num;
+ idx = GET_IDX(ureg.port_updt);
+
+ if (idx >= 0 && idx < ureg.port_num)
+ gaokun_ucsi_port_update(&uec->ports[idx], ureg.port_data);
+
+ return idx;
+}
+
+static void gaokun_ucsi_handle_altmode(struct gaokun_ucsi_port *port)
+{
+ struct gaokun_ucsi *uec = port->ucsi;
+ int idx = port->idx;
+
+ if (idx >= uec->ucsi->cap.num_connectors || !uec->ucsi->connector) {
+ dev_warn(uec->ucsi->dev, "altmode port out of range: %d\n", idx);
+ return;
+ }
+
+ /* UCSI callback .connector_status() have set orientation */
+ if (port->bridge)
+ drm_aux_hpd_bridge_notify(&port->bridge->dev,
+ port->hpd_state ?
+ connector_status_connected :
+ connector_status_disconnected);
+
+ gaokun_ec_ucsi_pan_ack(uec->ec, port->idx);
+}
+
+static void gaokun_ucsi_altmode_notify_ind(struct gaokun_ucsi *uec)
+{
+ int idx;
+
+ idx = gaokun_ucsi_refresh(uec);
+ if (idx < 0)
+ gaokun_ec_ucsi_pan_ack(uec->ec, idx);
+ else
+ gaokun_ucsi_handle_altmode(&uec->ports[idx]);
+}
+
+/*
+ * USB event is necessary for enabling altmode, the event should follow
+ * UCSI event, if not after timeout(this notify may be disabled somehow),
+ * then force to enable altmode.
+ */
+static void gaokun_ucsi_handle_no_usb_event(struct gaokun_ucsi *uec, int idx)
+{
+ struct gaokun_ucsi_port *port;
+
+ port = &uec->ports[idx];
+ if (!wait_for_completion_timeout(&port->usb_ack, 2 * HZ)) {
+ dev_warn(uec->dev, "No USB EVENT, triggered by UCSI EVENT");
+ gaokun_ucsi_altmode_notify_ind(uec);
+ }
+}
+
+static int gaokun_ucsi_notify(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ u32 cci;
+ struct gaokun_ucsi *uec = container_of(nb, struct gaokun_ucsi, nb);
+
+ switch (action) {
+ case EC_EVENT_USB:
+ gaokun_ucsi_altmode_notify_ind(uec);
+ return NOTIFY_OK;
+
+ case EC_EVENT_UCSI:
+ uec->ucsi->ops->read_cci(uec->ucsi, &cci);
+ ucsi_notify_common(uec->ucsi, cci);
+ if (UCSI_CCI_CONNECTOR(cci))
+ gaokun_ucsi_handle_no_usb_event(uec, UCSI_CCI_CONNECTOR(cci) - 1);
+
+ return NOTIFY_OK;
+
+ default:
+ return NOTIFY_DONE;
+ }
+}
+
+static int gaokun_ucsi_get_port_num(struct gaokun_ucsi *uec)
+{
+ struct gaokun_ucsi_reg ureg;
+ int ret;
+
+ ret = gaokun_ec_ucsi_get_reg(uec->ec, (u8 *)&ureg);
+
+ return ret ? 0 : ureg.port_num;
+}
+
+static int gaokun_ucsi_ports_init(struct gaokun_ucsi *uec)
+{
+ u32 port;
+ int i, ret, port_num;
+ struct device *dev = uec->dev;
+ struct gaokun_ucsi_port *ucsi_port;
+ struct fwnode_handle *fwnode;
+
+ port_num = gaokun_ucsi_get_port_num(uec);
+ uec->port_num = port_num;
+
+ uec->ports = devm_kzalloc(dev, port_num * sizeof(*(uec->ports)),
+ GFP_KERNEL);
+ if (!uec->ports)
+ return -ENOMEM;
+
+ for (i = 0; i < port_num; ++i) {
+ ucsi_port = &uec->ports[i];
+ ucsi_port->ccx = USBC_CCX_NONE;
+ ucsi_port->idx = i;
+ ucsi_port->ucsi = uec;
+ init_completion(&ucsi_port->usb_ack);
+ spin_lock_init(&ucsi_port->lock);
+ }
+
+ device_for_each_child_node(dev, fwnode) {
+ ret = fwnode_property_read_u32(fwnode, "reg", &port);
+ if (ret < 0) {
+ dev_err(dev, "missing reg property of %pOFn\n", fwnode);
+ fwnode_handle_put(fwnode);
+ return ret;
+ }
+
+ if (port >= port_num) {
+ dev_warn(dev, "invalid connector number %d, ignoring\n", port);
+ continue;
+ }
+
+ ucsi_port = &uec->ports[port];
+ ucsi_port->bridge = devm_drm_dp_hpd_bridge_alloc(dev, to_of_node(fwnode));
+ if (IS_ERR(ucsi_port->bridge)) {
+ fwnode_handle_put(fwnode);
+ return PTR_ERR(ucsi_port->bridge);
+ }
+ }
+
+ for (i = 0; i < port_num; i++) {
+ if (!uec->ports[i].bridge)
+ continue;
+
+ ret = devm_drm_dp_hpd_bridge_add(dev, uec->ports[i].bridge);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static void gaokun_ucsi_register_worker(struct work_struct *work)
+{
+ struct gaokun_ucsi *uec;
+ struct ucsi *ucsi;
+ int ret;
+
+ uec = container_of(work, struct gaokun_ucsi, work);
+ ucsi = uec->ucsi;
+
+ ucsi->quirks = UCSI_NO_PARTNER_PDOS | UCSI_DELAY_DEVICE_PDOS;
+
+ ssleep(3); /* EC can't handle UCSI properly in the early stage */
+
+ ret = gaokun_ec_register_notify(uec->ec, &uec->nb);
+ if (ret) {
+ dev_err_probe(ucsi->dev, ret, "notifier register failed\n");
+ return;
+ }
+
+ ret = ucsi_register(ucsi);
+ if (ret)
+ dev_err_probe(ucsi->dev, ret, "ucsi register failed\n");
+}
+
+static int gaokun_ucsi_register(struct gaokun_ucsi *uec)
+{
+ schedule_work(&uec->work);
+
+ return 0;
+}
+
+static int gaokun_ucsi_probe(struct auxiliary_device *adev,
+ const struct auxiliary_device_id *id)
+{
+ struct gaokun_ec *ec = adev->dev.platform_data;
+ struct device *dev = &adev->dev;
+ struct gaokun_ucsi *uec;
+ int ret;
+
+ uec = devm_kzalloc(dev, sizeof(*uec), GFP_KERNEL);
+ if (!uec)
+ return -ENOMEM;
+
+ uec->ec = ec;
+ uec->dev = dev;
+ uec->version = 0x0100;
+ uec->nb.notifier_call = gaokun_ucsi_notify;
+
+ INIT_WORK(&uec->work, gaokun_ucsi_register_worker);
+
+ ret = gaokun_ucsi_ports_init(uec);
+ if (ret)
+ return ret;
+
+ uec->ucsi = ucsi_create(dev, &gaokun_ucsi_ops);
+ if (IS_ERR(uec->ucsi))
+ return PTR_ERR(uec->ucsi);
+
+ ucsi_set_drvdata(uec->ucsi, uec);
+ auxiliary_set_drvdata(adev, uec);
+
+ return gaokun_ucsi_register(uec);
+}
+
+static void gaokun_ucsi_remove(struct auxiliary_device *adev)
+{
+ struct gaokun_ucsi *uec = auxiliary_get_drvdata(adev);
+
+ gaokun_ec_unregister_notify(uec->ec, &uec->nb);
+ ucsi_unregister(uec->ucsi);
+ ucsi_destroy(uec->ucsi);
+}
+
+static const struct auxiliary_device_id gaokun_ucsi_id_table[] = {
+ { .name = GAOKUN_MOD_NAME "." GAOKUN_DEV_UCSI, },
+ {}
+};
+MODULE_DEVICE_TABLE(auxiliary, gaokun_ucsi_id_table);
+
+static struct auxiliary_driver gaokun_ucsi_driver = {
+ .name = GAOKUN_DEV_UCSI,
+ .id_table = gaokun_ucsi_id_table,
+ .probe = gaokun_ucsi_probe,
+ .remove = gaokun_ucsi_remove,
+};
+
+module_auxiliary_driver(gaokun_ucsi_driver);
+
+MODULE_DESCRIPTION("HUAWEI Matebook E Go UCSI driver");
+MODULE_LICENSE("GPL");
--
2.47.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 4/5] power: supply: add Huawei Matebook E Go (sc8280xp) psy driver
2024-12-27 17:13 [PATCH 0/5] platform: arm64: Huawei Matebook E Go embedded controller Pengyu Luo
` (2 preceding siblings ...)
2024-12-27 17:13 ` [PATCH 3/5] usb: typec: ucsi: add Huawei Matebook E Go (sc8280xp) ucsi driver Pengyu Luo
@ 2024-12-27 17:13 ` Pengyu Luo
2024-12-27 17:13 ` [PATCH 5/5] arm64: dts: qcom: gaokun3: Add Embedded Controller node Pengyu Luo
4 siblings, 0 replies; 51+ messages in thread
From: Pengyu Luo @ 2024-12-27 17:13 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson,
Konrad Dybcio, Hans de Goede, Ilpo Järvinen,
Bryan O'Donoghue, Sebastian Reichel, Heikki Krogerus,
Greg Kroah-Hartman
Cc: devicetree, linux-kernel, linux-arm-msm, platform-driver-x86,
linux-pm, linux-usb, Dmitry Baryshkov, Nikita Travkin, Pengyu Luo
On the Huawei Matebook E Go (sc8280xp) tablet the EC provides access
to the adapter and battery status. Add the driver to read power supply
status on the tablet.
Signed-off-by: Pengyu Luo <mitltlatltl@gmail.com>
---
drivers/power/supply/Kconfig | 9 +
drivers/power/supply/Makefile | 1 +
drivers/power/supply/huawei-gaokun-battery.c | 446 +++++++++++++++++++
3 files changed, 456 insertions(+)
create mode 100644 drivers/power/supply/huawei-gaokun-battery.c
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index 9f2eef678..e385d0a1f 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -161,6 +161,15 @@ config BATTERY_DS2782
Say Y here to enable support for the DS2782/DS2786 standalone battery
gas-gauge.
+config BATTERY_HUAWEI_GAOKUN
+ tristate "Huawei Matebook E Go (sc8280xp) battery"
+ depends on EC_HUAWEI_GAOKUN
+ help
+ This driver enables battery support on the Huawei Matebook E Go.
+
+ To compile the driver as a module, choose M here: the module will be
+ called huawei-gaokun-battery.
+
config BATTERY_LEGO_EV3
tristate "LEGO MINDSTORMS EV3 battery"
depends on OF && IIO && GPIOLIB && (ARCH_DAVINCI_DA850 || COMPILE_TEST)
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index 59c4a9f40..c0941a832 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -31,6 +31,7 @@ obj-$(CONFIG_BATTERY_DS2781) += ds2781_battery.o
obj-$(CONFIG_BATTERY_DS2782) += ds2782_battery.o
obj-$(CONFIG_BATTERY_GAUGE_LTC2941) += ltc2941-battery-gauge.o
obj-$(CONFIG_BATTERY_GOLDFISH) += goldfish_battery.o
+obj-$(CONFIG_BATTERY_HUAWEI_GAOKUN) += huawei-gaokun-battery.o
obj-$(CONFIG_BATTERY_LEGO_EV3) += lego_ev3_battery.o
obj-$(CONFIG_BATTERY_LENOVO_YOGA_C630) += lenovo_yoga_c630_battery.o
obj-$(CONFIG_BATTERY_PMU) += pmu_battery.o
diff --git a/drivers/power/supply/huawei-gaokun-battery.c b/drivers/power/supply/huawei-gaokun-battery.c
new file mode 100644
index 000000000..40141b59e
--- /dev/null
+++ b/drivers/power/supply/huawei-gaokun-battery.c
@@ -0,0 +1,446 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * gaokun-battery - A power supply driver for HUAWEI Matebook E Go (sc8280xp)
+ *
+ * reference: drivers/power/supply/lenovo_yoga_c630_battery.c
+ * drivers/platform/arm64/acer-aspire1-ec.c
+ * drivers/acpi/battery.c
+ * drivers/acpi/ac.c
+ *
+ * Copyright (C) 2024 Pengyu Luo <mitltlatltl@gmail.com>
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/bits.h>
+#include <linux/delay.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/power_supply.h>
+#include <linux/sprintf.h>
+
+#include <linux/platform_data/huawei-gaokun-ec.h>
+
+
+/* -------------------------------------------------------------------------- */
+/* String Data Reg */
+
+#define EC_BAT_VENDOR 0x01 /* from 0x01 to 0x0F, SUNWODA */
+#define EC_BAT_MODEL 0x11 /* from 0x11 to 0x1F, HB30A8P9ECW-22T */
+
+#define EC_ADP_STATUS 0x81
+#define EC_AC_STATUS BIT(0)
+#define EC_BAT_PRESENT BIT(1) /* BATC._STA */
+
+#define EC_BAT_STATUS 0x82 /* _BST */
+#define EC_BAT_DISCHARGING BIT(0)
+#define EC_BAT_CHARGING BIT(1)
+#define EC_BAT_CRITICAL BIT(2) /* Low Battery Level */
+#define EC_BAT_FULL BIT(3)
+
+/* -------------------------------------------------------------------------- */
+/* Word Data Reg */
+
+/* 0x5A: ?
+ * 0x5C: ?
+ * 0x5E: ?
+ * 0X60: ?
+ * 0x84: ?
+ */
+
+#define EC_BAT_STATUS_START 0x90
+#define EC_BAT_PERCENTAGE 0x90
+#define EC_BAT_VOLTAGE 0x92
+#define EC_BAT_CAPACITY 0x94
+#define EC_BAT_FULL_CAPACITY 0x96
+/* 0x98: ? */
+#define EC_BAT_CURRENT 0x9A
+/* 0x9C: ? */
+
+#define EC_BAT_INFO_START 0xA0
+/* 0xA0: POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT? */
+#define EC_BAT_DESIGN_CAPACITY 0xA2
+#define EC_BAT_DESIGN_VOLTAGE 0xA4
+#define EC_BAT_SERIAL_NUMBER 0xA6
+#define EC_BAT_CYCLE_COUNT 0xAA
+
+/* -------------------------------------------------------------------------- */
+/* Battery Event ID */
+
+#define EC_EVENT_BAT_A0 0xA0
+#define EC_EVENT_BAT_A1 0xA1
+#define EC_EVENT_BAT_A2 0xA2
+#define EC_EVENT_BAT_A3 0xA3
+#define EC_EVENT_BAT_B1 0xB1
+/* EVENT B1 A0 A1 repeat about every 1s 2s 3s respectively */
+
+/* ACPI _BIX field, Min sampling time, the duration between two _BST */
+#define CACHE_TIME 5000 /* cache time in milliseconds */
+
+#define MILLI_TO_MICRO 1000
+
+struct gaokun_psy_bat_status {
+ __le16 percentage_now; /* 0x90 */
+ __le16 voltage_now;
+ __le16 capacity_now;
+ __le16 full_capacity;
+ __le16 unknown1;
+ __le16 rate_now;
+ __le16 unknown2; /* 0x9C */
+} __packed;
+
+struct gaokun_psy_bat_info {
+ __le16 unknown3; /* 0xA0 */
+ __le16 design_capacity;
+ __le16 design_voltage;
+ __le16 serial_number;
+ __le16 padding2;
+ __le16 cycle_count; /* 0xAA */
+} __packed;
+
+struct gaokun_psy {
+ struct gaokun_ec *ec;
+ struct device *dev;
+ struct notifier_block nb;
+
+ struct power_supply *bat_psy;
+ struct power_supply *adp_psy;
+
+ unsigned long update_time;
+ struct gaokun_psy_bat_status status;
+ struct gaokun_psy_bat_info info;
+
+ char battery_model[0x10]; /* HB30A8P9ECW-22T, the real one is XXX-22A */
+ char battery_serial[0x10];
+ char battery_vendor[0x10];
+
+ int charge_now;
+ int online;
+ int bat_present;
+};
+
+/* -------------------------------------------------------------------------- */
+/* Adapter */
+
+static int gaokun_psy_get_adp_status(struct gaokun_psy *ecbat)
+{
+ /* _PSR */
+ int ret;
+ u8 online = 0;
+
+ ret = gaokun_ec_psy_read_byte(ecbat->ec, EC_ADP_STATUS, &online);
+ ecbat->online = !!(online & EC_AC_STATUS);
+
+ return ret;
+}
+
+static int gaokun_psy_get_adp_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct gaokun_psy *ecbat = power_supply_get_drvdata(psy);
+
+ if (gaokun_psy_get_adp_status(ecbat))
+ return -EIO;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = ecbat->online;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static enum power_supply_property gaokun_psy_adp_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static const struct power_supply_desc gaokun_psy_adp_desc = {
+ .name = "gaokun-ec-adapter",
+ .type = POWER_SUPPLY_TYPE_USB_TYPE_C,
+ .get_property = gaokun_psy_get_adp_property,
+ .properties = gaokun_psy_adp_props,
+ .num_properties = ARRAY_SIZE(gaokun_psy_adp_props),
+};
+
+/* -------------------------------------------------------------------------- */
+/* Battery */
+
+static inline void gaokun_psy_get_bat_present(struct gaokun_psy *ecbat)
+{
+ int ret;
+ u8 present;
+
+ /* Some kind of initialization */
+ gaokun_ec_write(ecbat->ec, (u8 []){0x02, 0xB2, 1, 0x90});
+
+ ret = gaokun_ec_psy_read_byte(ecbat->ec, EC_ADP_STATUS, &present);
+
+ ecbat->bat_present = ret ? false : !!(present & EC_BAT_PRESENT);
+}
+
+static inline int gaokun_psy_bat_present(struct gaokun_psy *ecbat)
+{
+ return ecbat->bat_present;
+}
+
+static int gaokun_psy_get_bat_info(struct gaokun_psy *ecbat)
+{
+ /* _BIX */
+ if (!gaokun_psy_bat_present(ecbat))
+ return 0;
+
+ return gaokun_ec_psy_multi_read(ecbat->ec, EC_BAT_INFO_START,
+ sizeof(ecbat->info), (u8 *)&ecbat->info);
+}
+
+static inline void gaokun_psy_update_bat_charge(struct gaokun_psy *ecbat)
+{
+ u8 charge = 0;
+
+ gaokun_ec_psy_read_byte(ecbat->ec, EC_BAT_STATUS, &charge);
+
+ charge = (charge == EC_BAT_CHARGING) * POWER_SUPPLY_STATUS_CHARGING
+ + (charge == EC_BAT_DISCHARGING) * POWER_SUPPLY_STATUS_DISCHARGING
+ + (charge == EC_BAT_FULL) * POWER_SUPPLY_STATUS_FULL;
+
+ ecbat->charge_now = charge;
+}
+
+static int gaokun_psy_get_bat_status(struct gaokun_psy *ecbat)
+{
+ /* _BST */
+ int ret;
+
+ if (time_before(jiffies, ecbat->update_time +
+ msecs_to_jiffies(CACHE_TIME)))
+ return 0;
+
+ gaokun_psy_update_bat_charge(ecbat);
+ ret = gaokun_ec_psy_multi_read(ecbat->ec, EC_BAT_STATUS_START,
+ sizeof(ecbat->status), (u8 *)&ecbat->status);
+
+ ecbat->update_time = jiffies;
+
+ return ret;
+}
+
+static inline void gaokun_psy_init(struct gaokun_psy *ecbat)
+{
+ gaokun_psy_get_bat_present(ecbat);
+ if (!gaokun_psy_bat_present(ecbat))
+ return;
+
+ gaokun_psy_get_bat_info(ecbat);
+
+ snprintf(ecbat->battery_serial, sizeof(ecbat->battery_serial),
+ "%d", le16_to_cpu(ecbat->info.serial_number));
+
+ gaokun_ec_psy_multi_read(ecbat->ec, EC_BAT_VENDOR,
+ sizeof(ecbat->battery_vendor) - 1,
+ ecbat->battery_vendor);
+
+ gaokun_ec_psy_multi_read(ecbat->ec, EC_BAT_MODEL,
+ sizeof(ecbat->battery_model) - 1,
+ ecbat->battery_model);
+
+ ecbat->battery_model[14] = 'A'; /* FIX UP */
+}
+
+static int gaokun_psy_get_bat_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct gaokun_psy *ecbat = power_supply_get_drvdata(psy);
+
+ if (gaokun_psy_bat_present(ecbat))
+ gaokun_psy_get_bat_status(ecbat);
+ else if (psp != POWER_SUPPLY_PROP_PRESENT)
+ return -ENODEV;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = ecbat->charge_now;
+ break;
+
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = ecbat->bat_present;
+ break;
+
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+
+ case POWER_SUPPLY_PROP_CYCLE_COUNT:
+ val->intval = le16_to_cpu(ecbat->info.cycle_count);
+ break;
+
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ val->intval = le16_to_cpu(ecbat->info.design_voltage) * MILLI_TO_MICRO;
+ break;
+
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = le16_to_cpu(ecbat->status.voltage_now) * MILLI_TO_MICRO;
+ break;
+
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ val->intval = (s16)le16_to_cpu(ecbat->status.rate_now) * MILLI_TO_MICRO;
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ val->intval = le16_to_cpu(ecbat->info.design_capacity) * MILLI_TO_MICRO;
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ val->intval = le16_to_cpu(ecbat->status.full_capacity) * MILLI_TO_MICRO;
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ val->intval = le16_to_cpu(ecbat->status.capacity_now) * MILLI_TO_MICRO;
+ break;
+
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = le16_to_cpu(ecbat->status.percentage_now);
+ break;
+
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = ecbat->battery_model;
+ break;
+
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = ecbat->battery_vendor;
+ break;
+
+ case POWER_SUPPLY_PROP_SERIAL_NUMBER:
+ val->strval = ecbat->battery_serial;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static enum power_supply_property gaokun_psy_bat_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_CYCLE_COUNT,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+ POWER_SUPPLY_PROP_SERIAL_NUMBER,
+};
+
+static const struct power_supply_desc gaokun_psy_bat_desc = {
+ .name = "gaokun-ec-battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .get_property = gaokun_psy_get_bat_property,
+ .properties = gaokun_psy_bat_props,
+ .num_properties = ARRAY_SIZE(gaokun_psy_bat_props),
+};
+
+static int gaokun_psy_notify(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct gaokun_psy *ecbat = container_of(nb, struct gaokun_psy, nb);
+
+ switch (action) {
+ case EC_EVENT_BAT_A2:
+ case EC_EVENT_BAT_B1:
+ gaokun_psy_get_bat_info(ecbat);
+ break;
+
+ case EC_EVENT_BAT_A0:
+ gaokun_psy_get_adp_status(ecbat);
+ power_supply_changed(ecbat->adp_psy);
+ msleep(10);
+ fallthrough;
+
+ case EC_EVENT_BAT_A1:
+ case EC_EVENT_BAT_A3:
+ if (action == EC_EVENT_BAT_A3) {
+ gaokun_psy_get_bat_info(ecbat);
+ msleep(100);
+ }
+ gaokun_psy_get_bat_status(ecbat);
+ power_supply_changed(ecbat->bat_psy);
+ break;
+ }
+
+ return NOTIFY_OK;
+}
+
+static int gaokun_psy_probe(struct auxiliary_device *adev,
+ const struct auxiliary_device_id *id)
+{
+ struct gaokun_ec *ec = adev->dev.platform_data;
+ struct power_supply_config psy_cfg = {};
+ struct device *dev = &adev->dev;
+ struct gaokun_psy *ecbat;
+
+ ecbat = devm_kzalloc(&adev->dev, sizeof(*ecbat), GFP_KERNEL);
+ if (!ecbat)
+ return -ENOMEM;
+
+ ecbat->ec = ec;
+ ecbat->dev = dev;
+ ecbat->nb.notifier_call = gaokun_psy_notify;
+
+ auxiliary_set_drvdata(adev, ecbat);
+
+ psy_cfg.drv_data = ecbat;
+ ecbat->adp_psy = devm_power_supply_register(dev, &gaokun_psy_adp_desc,
+ &psy_cfg);
+ if (IS_ERR(ecbat->adp_psy))
+ return dev_err_probe(dev, PTR_ERR(ecbat->adp_psy),
+ "Failed to register AC power supply\n");
+
+ psy_cfg.supplied_to = (char **)&gaokun_psy_bat_desc.name;
+ psy_cfg.num_supplicants = 1;
+ psy_cfg.no_wakeup_source = true;
+ ecbat->bat_psy = devm_power_supply_register(dev, &gaokun_psy_bat_desc,
+ &psy_cfg);
+ if (IS_ERR(ecbat->bat_psy))
+ return dev_err_probe(dev, PTR_ERR(ecbat->bat_psy),
+ "Failed to register battery power supply\n");
+
+ gaokun_psy_init(ecbat);
+
+ return gaokun_ec_register_notify(ec, &ecbat->nb);
+}
+
+static void gaokun_psy_remove(struct auxiliary_device *adev)
+{
+ struct gaokun_psy *ecbat = auxiliary_get_drvdata(adev);
+
+ gaokun_ec_unregister_notify(ecbat->ec, &ecbat->nb);
+}
+
+static const struct auxiliary_device_id gaokun_psy_id_table[] = {
+ { .name = GAOKUN_MOD_NAME "." GAOKUN_DEV_PSY, },
+ {}
+};
+MODULE_DEVICE_TABLE(auxiliary, gaokun_psy_id_table);
+
+static struct auxiliary_driver gaokun_psy_driver = {
+ .name = GAOKUN_DEV_PSY,
+ .id_table = gaokun_psy_id_table,
+ .probe = gaokun_psy_probe,
+ .remove = gaokun_psy_remove,
+};
+
+module_auxiliary_driver(gaokun_psy_driver);
+
+MODULE_DESCRIPTION("HUAWEI Matebook E Go psy driver");
+MODULE_LICENSE("GPL");
--
2.47.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 5/5] arm64: dts: qcom: gaokun3: Add Embedded Controller node
2024-12-27 17:13 [PATCH 0/5] platform: arm64: Huawei Matebook E Go embedded controller Pengyu Luo
` (3 preceding siblings ...)
2024-12-27 17:13 ` [PATCH 4/5] power: supply: add Huawei Matebook E Go (sc8280xp) psy driver Pengyu Luo
@ 2024-12-27 17:13 ` Pengyu Luo
2024-12-30 14:53 ` Konrad Dybcio
4 siblings, 1 reply; 51+ messages in thread
From: Pengyu Luo @ 2024-12-27 17:13 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson,
Konrad Dybcio, Hans de Goede, Ilpo Järvinen,
Bryan O'Donoghue, Sebastian Reichel, Heikki Krogerus,
Greg Kroah-Hartman
Cc: devicetree, linux-kernel, linux-arm-msm, platform-driver-x86,
linux-pm, linux-usb, Dmitry Baryshkov, Nikita Travkin, Pengyu Luo
The Embedded Controller in the Huawei Matebook E Go (s8280xp)
is accessible on &i2c15 and provides battery and adapter status,
port orientation status, as well as HPD event notifications for
two USB Type-C port, etc.
Add the EC to the device tree and describe the relationship among
the type-c ports, orientation switches and the QMP combo PHY.
Signed-off-by: Pengyu Luo <mitltlatltl@gmail.com>
---
.../boot/dts/qcom/sc8280xp-huawei-gaokun3.dts | 139 ++++++++++++++++++
1 file changed, 139 insertions(+)
diff --git a/arch/arm64/boot/dts/qcom/sc8280xp-huawei-gaokun3.dts b/arch/arm64/boot/dts/qcom/sc8280xp-huawei-gaokun3.dts
index 09b95f89e..09ca9a560 100644
--- a/arch/arm64/boot/dts/qcom/sc8280xp-huawei-gaokun3.dts
+++ b/arch/arm64/boot/dts/qcom/sc8280xp-huawei-gaokun3.dts
@@ -28,6 +28,7 @@ / {
aliases {
i2c4 = &i2c4;
+ i2c15 = &i2c15;
serial1 = &uart2;
};
@@ -216,6 +217,40 @@ map1 {
};
};
+ usb0-sbu-mux {
+ compatible = "pericom,pi3usb102", "gpio-sbu-mux";
+
+ select-gpios = <&tlmm 164 GPIO_ACTIVE_HIGH>;
+
+ pinctrl-names = "default";
+ pinctrl-0 = <&usb0_sbu_default>;
+
+ orientation-switch;
+
+ port {
+ usb0_sbu_mux: endpoint {
+ remote-endpoint = <&ucsi0_sbu>;
+ };
+ };
+ };
+
+ usb1-sbu-mux {
+ compatible = "pericom,pi3usb102", "gpio-sbu-mux";
+
+ select-gpios = <&tlmm 47 GPIO_ACTIVE_HIGH>;
+
+ pinctrl-names = "default";
+ pinctrl-0 = <&usb1_sbu_default>;
+
+ orientation-switch;
+
+ port {
+ usb1_sbu_mux: endpoint {
+ remote-endpoint = <&ucsi1_sbu>;
+ };
+ };
+ };
+
wcn6855-pmu {
compatible = "qcom,wcn6855-pmu";
@@ -584,6 +619,81 @@ touchscreen@4f {
};
+&i2c15 {
+ clock-frequency = <400000>;
+
+ pinctrl-0 = <&i2c15_default>;
+ pinctrl-names = "default";
+
+ status = "okay";
+
+ embedded-controller@38 {
+ compatible = "huawei,sc8280xp-gaokun-ec", "huawei,gaokun-ec";
+ reg = <0x38>;
+
+ interrupts-extended = <&tlmm 107 IRQ_TYPE_LEVEL_LOW>;
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ connector@0 {
+ compatible = "usb-c-connector";
+ reg = <0>;
+ power-role = "dual";
+ data-role = "dual";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+
+ ucsi0_ss_in: endpoint {
+ remote-endpoint = <&usb_0_qmpphy_out>;
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+
+ ucsi0_sbu: endpoint {
+ remote-endpoint = <&usb0_sbu_mux>;
+ };
+ };
+ };
+ };
+
+ connector@1 {
+ compatible = "usb-c-connector";
+ reg = <1>;
+ power-role = "dual";
+ data-role = "dual";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+
+ ucsi1_ss_in: endpoint {
+ remote-endpoint = <&usb_1_qmpphy_out>;
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+
+ ucsi1_sbu: endpoint {
+ remote-endpoint = <&usb1_sbu_mux>;
+ };
+ };
+ };
+ };
+ };
+};
+
&mdss0 {
status = "okay";
};
@@ -1025,6 +1135,10 @@ &usb_0_qmpphy_dp_in {
remote-endpoint = <&mdss0_dp0_out>;
};
+&usb_0_qmpphy_out {
+ remote-endpoint = <&ucsi0_ss_in>;
+};
+
&usb_1 {
status = "okay";
};
@@ -1054,6 +1168,10 @@ &usb_1_qmpphy_dp_in {
remote-endpoint = <&mdss0_dp1_out>;
};
+&usb_1_qmpphy_out {
+ remote-endpoint = <&ucsi1_ss_in>;
+};
+
&usb_2 {
status = "okay";
};
@@ -1177,6 +1295,13 @@ i2c4_default: i2c4-default-state {
bias-disable;
};
+ i2c15_default: i2c15-default-state {
+ pins = "gpio36", "gpio37";
+ function = "qup15";
+ drive-strength = <2>;
+ bias-pull-up;
+ };
+
mode_pin_active: mode-pin-state {
pins = "gpio26";
function = "gpio";
@@ -1301,6 +1426,20 @@ tx-pins {
};
};
+ usb0_sbu_default: usb0-sbu-state {
+ pins = "gpio164";
+ function = "gpio";
+ bias-disable;
+ drive-strength = <16>;
+ };
+
+ usb1_sbu_default: usb1-sbu-state {
+ pins = "gpio47";
+ function = "gpio";
+ bias-disable;
+ drive-strength = <16>;
+ };
+
wcd_default: wcd-default-state {
reset-pins {
pins = "gpio106";
--
2.47.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* Re: [PATCH 1/5] dt-bindings: platform: Add Huawei Matebook E Go EC
2024-12-27 17:13 ` [PATCH 1/5] dt-bindings: platform: Add Huawei Matebook E Go EC Pengyu Luo
@ 2024-12-27 18:18 ` Rob Herring (Arm)
2024-12-28 9:54 ` Krzysztof Kozlowski
1 sibling, 0 replies; 51+ messages in thread
From: Rob Herring (Arm) @ 2024-12-27 18:18 UTC (permalink / raw)
To: Pengyu Luo
Cc: devicetree, linux-kernel, linux-pm, Ilpo Järvinen,
Sebastian Reichel, linux-usb, Heikki Krogerus, linux-arm-msm,
Bjorn Andersson, Dmitry Baryshkov, Nikita Travkin,
Krzysztof Kozlowski, Greg Kroah-Hartman, Hans de Goede,
platform-driver-x86, Conor Dooley, Bryan O'Donoghue,
Konrad Dybcio
On Sat, 28 Dec 2024 01:13:49 +0800, Pengyu Luo wrote:
> Add binding for the EC found in the Huawei Matebook E Go (sc8280xp) and
> Huawei Matebook E Go LTE (sc8180x) 2in1 tablet.
>
> Signed-off-by: Pengyu Luo <mitltlatltl@gmail.com>
> ---
> .../bindings/platform/huawei,gaokun-ec.yaml | 116 ++++++++++++++++++
> 1 file changed, 116 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/platform/huawei,gaokun-ec.yaml
>
My bot found errors running 'make dt_binding_check' on your patch:
yamllint warnings/errors:
dtschema/dtc warnings/errors:
Error: Documentation/devicetree/bindings/platform/huawei,gaokun-ec.example.dts:26.61-62 syntax error
FATAL ERROR: Unable to parse input tree
make[2]: *** [scripts/Makefile.dtbs:131: Documentation/devicetree/bindings/platform/huawei,gaokun-ec.example.dtb] Error 1
make[2]: *** Waiting for unfinished jobs....
make[1]: *** [/builds/robherring/dt-review-ci/linux/Makefile:1506: dt_binding_check] Error 2
make: *** [Makefile:251: __sub-make] Error 2
doc reference errors (make refcheckdocs):
See https://patchwork.ozlabs.org/project/devicetree-bindings/patch/20241227171353.404432-2-mitltlatltl@gmail.com
The base for the series is generally the latest rc1. A different dependency
should be noted in *this* patch.
If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:
pip3 install dtschema --upgrade
Please check and re-submit after running the above command yourself. Note
that DT_SCHEMA_FILES can be set to your schema file to speed up checking
your schema. However, it must be unset to test all examples with your schema.
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 2/5] platform: arm64: add Huawei Matebook E Go (sc8280xp) EC driver
2024-12-27 17:13 ` [PATCH 2/5] platform: arm64: add Huawei Matebook E Go (sc8280xp) EC driver Pengyu Luo
@ 2024-12-27 18:21 ` Maya Matuszczyk
2024-12-28 5:42 ` Pengyu Luo
2024-12-28 9:58 ` Krzysztof Kozlowski
` (3 subsequent siblings)
4 siblings, 1 reply; 51+ messages in thread
From: Maya Matuszczyk @ 2024-12-27 18:21 UTC (permalink / raw)
To: Pengyu Luo
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson,
Konrad Dybcio, Hans de Goede, Ilpo Järvinen,
Bryan O'Donoghue, Sebastian Reichel, Heikki Krogerus,
Greg Kroah-Hartman, devicetree, linux-kernel, linux-arm-msm,
platform-driver-x86, linux-pm, linux-usb, Dmitry Baryshkov,
Nikita Travkin
Good to see someone else doing EC drivers for arm64 laptops!
pt., 27 gru 2024 o 18:16 Pengyu Luo <mitltlatltl@gmail.com> napisał(a):
>
> There are 3 variants, Huawei released first 2 at the same time.
> Huawei Matebook E Go LTE(sc8180x), codename should be gaokun2.
> Huawei Matebook E Go(sc8280xp@3.0GHz), codename is gaokun3.
> Huawei Matebook E Go 2023(sc8280xp@2.69GHz).
>
> Adding support for the latter two variants for now, this driver should
> also work for the sc8180x variant according to acpi table files, but I
> don't have the device yet.
>
> Different from other Qualcomm Snapdragon sc8280xp based machines, the
> Huawei Matebook E Go uses an embedded controller while others use
> something called pmic glink. This embedded controller can be used to
> perform a set of various functions, including, but not limited:
>
> - Battery and charger monitoring;
> - Charge control and smart charge;
> - Fn_lock settings;
> - Tablet lid status;
> - Temperature sensors;
> - USB Type-C notifications (ports orientation, DP alt mode HPD);
> - USB Type-C PD (according to observation, up to 48w).
>
> Add the driver for the EC, that creates devices for UCSI, wmi and power
> supply devices.
>
> Signed-off-by: Pengyu Luo <mitltlatltl@gmail.com>
> ---
> drivers/platform/arm64/Kconfig | 19 +
> drivers/platform/arm64/Makefile | 2 +
> drivers/platform/arm64/huawei-gaokun-ec.c | 598 ++++++++++++++++++
> drivers/platform/arm64/huawei-gaokun-wmi.c | 283 +++++++++
> .../linux/platform_data/huawei-gaokun-ec.h | 90 +++
> 5 files changed, 992 insertions(+)
> create mode 100644 drivers/platform/arm64/huawei-gaokun-ec.c
> create mode 100644 drivers/platform/arm64/huawei-gaokun-wmi.c
> create mode 100644 include/linux/platform_data/huawei-gaokun-ec.h
>
> diff --git a/drivers/platform/arm64/Kconfig b/drivers/platform/arm64/Kconfig
> index f88395ea3..eb7fbacf0 100644
> --- a/drivers/platform/arm64/Kconfig
> +++ b/drivers/platform/arm64/Kconfig
> @@ -33,6 +33,25 @@ config EC_ACER_ASPIRE1
> laptop where this information is not properly exposed via the
> standard ACPI devices.
>
> +config EC_HUAWEI_GAOKUN
> + tristate "Huawei Matebook E Go (sc8280xp) Embedded Controller driver"
> + depends on ARCH_QCOM || COMPILE_TEST
> + depends on I2C
> + depends on DRM
> + depends on POWER_SUPPLY
> + depends on INPUT
> + help
> + Say Y here to enable the EC driver for Huawei Matebook E Go 2in1
> + tablet. The driver handles battery(information, charge control) and
> + USB Type-C DP HPD events as well as some misc functions like the lid
> + sensor and temperature sensors, etc.
> +
> + This driver provides battery and AC status support for the mentioned
> + laptop where this information is not properly exposed via the
> + standard ACPI devices.
> +
> + Say M or Y here to include this support.
> +
> config EC_LENOVO_YOGA_C630
> tristate "Lenovo Yoga C630 Embedded Controller driver"
> depends on ARCH_QCOM || COMPILE_TEST
> diff --git a/drivers/platform/arm64/Makefile b/drivers/platform/arm64/Makefile
> index b2ae9114f..ed32ad6c0 100644
> --- a/drivers/platform/arm64/Makefile
> +++ b/drivers/platform/arm64/Makefile
> @@ -6,4 +6,6 @@
> #
>
> obj-$(CONFIG_EC_ACER_ASPIRE1) += acer-aspire1-ec.o
> +obj-$(CONFIG_EC_HUAWEI_GAOKUN) += huawei-gaokun-ec.o
> +obj-$(CONFIG_EC_HUAWEI_GAOKUN) += huawei-gaokun-wmi.o
> obj-$(CONFIG_EC_LENOVO_YOGA_C630) += lenovo-yoga-c630.o
> diff --git a/drivers/platform/arm64/huawei-gaokun-ec.c b/drivers/platform/arm64/huawei-gaokun-ec.c
> new file mode 100644
> index 000000000..c1c657f7b
> --- /dev/null
> +++ b/drivers/platform/arm64/huawei-gaokun-ec.c
> @@ -0,0 +1,598 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * huawei-gaokun-ec - An EC driver for HUAWEI Matebook E Go (sc8280xp)
> + *
> + * reference: drivers/platform/arm64/acer-aspire1-ec.c
> + * drivers/platform/arm64/lenovo-yoga-c630.c
> + *
> + * Copyright (C) 2024 Pengyu Luo <mitltlatltl@gmail.com>
> + */
> +
> +#include <linux/auxiliary_bus.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/i2c.h>
> +#include <linux/input.h>
> +#include <linux/notifier.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/version.h>
> +
> +#include <linux/platform_data/huawei-gaokun-ec.h>
> +
> +#define EC_EVENT 0x06
> +
> +/* Also can be found in ACPI specification 12.3 */
> +#define EC_READ 0x80
> +#define EC_WRITE 0x81
> +#define EC_BURST 0x82
> +#define EC_QUERY 0x84
> +
> +
> +#define EC_EVENT_LID 0x81
> +
> +#define EC_LID_STATE 0x80
> +#define EC_LID_OPEN BIT(1)
> +
> +#define UCSI_REG_SIZE 7
> +
> +/* for tx, command sequences are arranged as
> + * {master_cmd, slave_cmd, data_len, data_seq}
> + */
> +#define REQ_HDR_SIZE 3
> +#define INPUT_SIZE_OFFSET 2
> +#define INPUT_DATA_OFFSET 3
> +
> +/* for rx, data sequences are arranged as
> + * {status, data_len(unreliable), data_seq}
> + */
> +#define RESP_HDR_SIZE 2
> +#define DATA_OFFSET 2
> +
> +
> +struct gaokun_ec {
> + struct i2c_client *client;
> + struct mutex lock;
> + struct blocking_notifier_head notifier_list;
> + struct input_dev *idev;
> + bool suspended;
> +};
> +
> +static int gaokun_ec_request(struct gaokun_ec *ec, const u8 *req,
> + size_t resp_len, u8 *resp)
> +{
> + struct i2c_client *client = ec->client;
> + struct i2c_msg msgs[2] = {
> + {
> + .addr = client->addr,
> + .flags = client->flags,
> + .len = req[INPUT_SIZE_OFFSET] + REQ_HDR_SIZE,
> + .buf = req,
> + }, {
> + .addr = client->addr,
> + .flags = client->flags | I2C_M_RD,
> + .len = resp_len,
> + .buf = resp,
> + },
> + };
> +
> + mutex_lock(&ec->lock);
> +
> + i2c_transfer(client->adapter, msgs, 2);
> + usleep_range(2000, 2500);
> +
> + mutex_unlock(&ec->lock);
> +
> + return *resp;
> +}
> +
> +/* -------------------------------------------------------------------------- */
> +/* Common API */
> +
> +/**
> + * gaokun_ec_read - read from EC
> + * @ec: The gaokun_ec
> + * @req: The sequence to request
> + * @resp_len: The size to read
> + * @resp: Where the data are read to
> + *
> + * This function is used to read data after writing a magic sequence to EC.
> + * All EC operations dependent on this functions.
> + *
> + * Huawei uses magic sequences everywhere to complete various functions, all
> + * these sequences are passed to ECCD(a ACPI method which is quiet similar
> + * to gaokun_ec_request), there is no good abstraction to generalize these
> + * sequences, so just wrap it for now. Almost all magic sequences are kept
> + * in this file.
> + */
> +int gaokun_ec_read(struct gaokun_ec *ec, const u8 *req,
> + size_t resp_len, u8 *resp)
> +{
> + return gaokun_ec_request(ec, req, resp_len, resp);
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_read);
> +
> +/**
> + * gaokun_ec_write - write to EC
> + * @ec: The gaokun_ec
> + * @req: The sequence to request
> + *
> + * This function has no big difference from gaokun_ec_read. When caller care
> + * only write status and no actual data are returnd, then use it.
> + */
> +int gaokun_ec_write(struct gaokun_ec *ec, u8 *req)
> +{
> + u8 resp[RESP_HDR_SIZE];
> +
> + return gaokun_ec_request(ec, req, sizeof(resp), resp);
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_write);
> +
> +int gaokun_ec_read_byte(struct gaokun_ec *ec, u8 *req, u8 *byte)
> +{
> + int ret;
> + u8 resp[RESP_HDR_SIZE + sizeof(*byte)];
> +
> + ret = gaokun_ec_read(ec, req, sizeof(resp), resp);
> + *byte = resp[DATA_OFFSET];
> +
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_read_byte);
> +
> +int gaokun_ec_register_notify(struct gaokun_ec *ec, struct notifier_block *nb)
> +{
> + return blocking_notifier_chain_register(&ec->notifier_list, nb);
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_register_notify);
> +
> +void gaokun_ec_unregister_notify(struct gaokun_ec *ec, struct notifier_block *nb)
> +{
> + blocking_notifier_chain_unregister(&ec->notifier_list, nb);
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_unregister_notify);
> +
> +/* -------------------------------------------------------------------------- */
> +/* API For PSY */
> +
> +int gaokun_ec_psy_multi_read(struct gaokun_ec *ec, u8 reg,
> + size_t resp_len, u8 *resp)
> +{
> + int i, ret;
> + u8 _resp[RESP_HDR_SIZE + 1];
> + u8 req[REQ_HDR_SIZE + 1] = {0x02, EC_READ, 1, };
> +
> + for (i = 0; i < resp_len; ++i) {
> + req[INPUT_DATA_OFFSET] = reg++;
> + ret = gaokun_ec_read(ec, req, sizeof(_resp), _resp);
> + if (ret)
> + return -EIO;
> + resp[i] = _resp[DATA_OFFSET];
> + }
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_psy_multi_read);
> +
> +/* -------------------------------------------------------------------------- */
> +/* API For WMI */
WMI is in ACPI, this laptop doesn't boot with ACPI so why are things
handled in WMI separate from rest of the driver?
> +
> +/* Battery charging threshold */
> +int gaokun_ec_wmi_get_threshold(struct gaokun_ec *ec, u8 *value, int ind)
> +{
> + /* GBTT */
> + return gaokun_ec_read_byte(ec, (u8 []){0x02, 0x69, 1, ind}, value);
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_get_threshold);
> +
> +int gaokun_ec_wmi_set_threshold(struct gaokun_ec *ec, u8 start, u8 end)
> +{
> + /* SBTT */
> + int ret;
> + u8 req[REQ_HDR_SIZE + 2] = {0x02, 0x68, 2, 3, 0x5a};
> +
> + ret = gaokun_ec_write(ec, req);
> + if (ret)
> + return -EIO;
> +
> + if (start == 0 && end == 0)
> + return -EINVAL;
> +
> + if (start >= 0 && start <= end && end <= 100) {
> + req[INPUT_DATA_OFFSET] = 1;
> + req[INPUT_DATA_OFFSET + 1] = start;
> + ret = gaokun_ec_write(ec, req);
> + if (ret)
> + return -EIO;
> +
> + req[INPUT_DATA_OFFSET] = 2;
> + req[INPUT_DATA_OFFSET + 1] = end;
> + ret = gaokun_ec_write(ec, req);
> + } else {
> + return -EINVAL;
> + }
> +
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_set_threshold);
> +
> +/* Smart charge param */
> +int gaokun_ec_wmi_get_smart_charge_param(struct gaokun_ec *ec, u8 *value)
> +{
> + /* GBAC */
> + return gaokun_ec_read_byte(ec, (u8 []){0x02, 0xE6, 0}, value);
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_get_smart_charge_param);
> +
> +int gaokun_ec_wmi_set_smart_charge_param(struct gaokun_ec *ec, u8 value)
> +{
> + /* SBAC */
> + if (value < 0 || value > 2)
> + return -EINVAL;
> +
> + return gaokun_ec_write(ec, (u8 []){0x02, 0xE5, 1, value});
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_set_smart_charge_param);
> +
> +/* Smart charge */
> +int gaokun_ec_wmi_get_smart_charge(struct gaokun_ec *ec,
> + u8 data[GAOKUN_SMART_CHARGE_DATA_SIZE])
> +{
> + /* GBCM */
> + u8 req[REQ_HDR_SIZE] = {0x02, 0xE4, 0};
> + u8 resp[RESP_HDR_SIZE + 4];
> + int ret;
> +
> + ret = gaokun_ec_read(ec, req, sizeof(resp), resp);
> + if (ret)
> + return -EIO;
> +
> + data[0] = resp[DATA_OFFSET];
> + data[1] = resp[DATA_OFFSET + 1];
> + data[2] = resp[DATA_OFFSET + 2];
> + data[3] = resp[DATA_OFFSET + 3];
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_get_smart_charge);
> +
> +int gaokun_ec_wmi_set_smart_charge(struct gaokun_ec *ec,
> + u8 data[GAOKUN_SMART_CHARGE_DATA_SIZE])
> +{
> + /* SBCM */
> + u8 req[REQ_HDR_SIZE + GAOKUN_SMART_CHARGE_DATA_SIZE] = {0x02, 0XE3, 4,};
> +
> + if (!(data[2] >= 0 && data[2] <= data[3] && data[3] <= 100))
> + return -EINVAL;
> +
> + memcpy(req + INPUT_DATA_OFFSET, data, GAOKUN_SMART_CHARGE_DATA_SIZE);
> +
> + return gaokun_ec_write(ec, req);
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_set_smart_charge);
> +
> +/* Fn lock */
> +int gaokun_ec_wmi_get_fn_lock(struct gaokun_ec *ec, u8 *on)
> +{
> + /* GFRS */
> + int ret;
> + u8 val;
> + u8 req[REQ_HDR_SIZE] = {0x02, 0x6B, 0};
> +
> + ret = gaokun_ec_read_byte(ec, req, &val);
> + if (val == 0x55)
> + *on = 0;
> + else if (val == 0x5A)
> + *on = 1;
> + else
> + return -EIO;
> +
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_get_fn_lock);
> +
> +int gaokun_ec_wmi_set_fn_lock(struct gaokun_ec *ec, u8 on)
> +{
> + /* SFRS */
> + u8 req[REQ_HDR_SIZE + 1] = {0x02, 0x6C, 1,};
> +
> + if (on == 0)
> + req[INPUT_DATA_OFFSET] = 0x55;
> + else if (on == 1)
> + req[INPUT_DATA_OFFSET] = 0x5A;
> + else
> + return -EINVAL;
> +
> + return gaokun_ec_write(ec, req);
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_set_fn_lock);
> +
> +/* Thermal Zone */
> +/* Range from 0 to 0x2C, partial valid */
> +static const u8 temp_reg[] = {0x05, 0x07, 0x08, 0x0E, 0x0F, 0x12, 0x15, 0x1E,
> + 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
> + 0x27, 0x28, 0x29, 0x2A};
> +
> +int gaokun_ec_wmi_get_temp(struct gaokun_ec *ec, s16 temp[GAOKUN_TZ_REG_NUM])
> +{
> + /* GTMP */
> + u8 req[REQ_HDR_SIZE] = {0x02, 0x61, 1,};
> + u8 resp[RESP_HDR_SIZE + sizeof(s16)];
> + int ret, i = 0;
> +
> + while (i < GAOKUN_TZ_REG_NUM) {
> + req[INPUT_DATA_OFFSET] = temp_reg[i];
> + ret = gaokun_ec_read(ec, req, sizeof(resp), resp);
> + if (ret)
> + return -EIO;
> + temp[i++] = *(s16 *)(resp + DATA_OFFSET);
> + }
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_get_temp);
> +
> +/* -------------------------------------------------------------------------- */
> +/* API For UCSI */
> +
> +int gaokun_ec_ucsi_read(struct gaokun_ec *ec,
> + u8 resp[GAOKUN_UCSI_READ_SIZE])
> +{
> + u8 req[REQ_HDR_SIZE] = {0x3, 0xD5, 0};
> + u8 _resp[RESP_HDR_SIZE + GAOKUN_UCSI_READ_SIZE];
> + int ret;
> +
> + ret = gaokun_ec_read(ec, req, sizeof(_resp), _resp);
> + if (ret)
> + return ret;
> +
> + memcpy(resp, _resp + DATA_OFFSET, GAOKUN_UCSI_READ_SIZE);
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_ucsi_read);
> +
> +int gaokun_ec_ucsi_write(struct gaokun_ec *ec,
> + const u8 req[GAOKUN_UCSI_WRITE_SIZE])
> +{
> + u8 _req[REQ_HDR_SIZE + GAOKUN_UCSI_WRITE_SIZE];
> +
> + _req[0] = 0x03;
> + _req[1] = 0xD4;
> + _req[INPUT_SIZE_OFFSET] = GAOKUN_UCSI_WRITE_SIZE;
> + memcpy(_req + INPUT_DATA_OFFSET, req, GAOKUN_UCSI_WRITE_SIZE);
> +
> + return gaokun_ec_write(ec, _req);
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_ucsi_write);
> +
> +int gaokun_ec_ucsi_get_reg(struct gaokun_ec *ec, u8 *ureg)
> +{
> + u8 req[REQ_HDR_SIZE] = {0x03, 0xD3, 0};
> + u8 _resp[RESP_HDR_SIZE + UCSI_REG_SIZE];
> + int ret;
> +
> + ret = gaokun_ec_read(ec, req, sizeof(_resp), _resp);
> + if (ret)
> + return ret;
> +
> + memcpy(ureg, _resp + DATA_OFFSET, UCSI_REG_SIZE);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_ucsi_get_reg);
> +
> +int gaokun_ec_ucsi_pan_ack(struct gaokun_ec *ec, int port_id)
> +{
> + u8 req[REQ_HDR_SIZE + 1] = {0x03, 0xD2, 1, 0};
> +
> + if (port_id >= 0)
> + req[INPUT_DATA_OFFSET] = 1 << port_id;
> +
> + return gaokun_ec_write(ec, req);
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_ucsi_pan_ack);
> +
> +/* -------------------------------------------------------------------------- */
> +/* Modern Standby */
> +
> +static int gaokun_ec_suspend(struct device *dev)
> +{
> + struct gaokun_ec *ec = dev_get_drvdata(dev);
> + u8 req[REQ_HDR_SIZE + 1] = {0x02, 0xB2, 1, 0xDB};
> + int ret;
> +
> + if (ec->suspended)
> + return 0;
> +
> + ret = gaokun_ec_write(ec, req);
> +
> + if (ret)
> + return ret;
> +
> + ec->suspended = true;
> +
> + return 0;
> +}
> +
> +static int gaokun_ec_resume(struct device *dev)
> +{
> + struct gaokun_ec *ec = dev_get_drvdata(dev);
> + u8 req[REQ_HDR_SIZE + 1] = {0x02, 0xB2, 1, 0xEB};
> + int ret;
> + int i;
> +
> + if (!ec->suspended)
> + return 0;
> +
> + for (i = 0; i < 3; i++) {
> + ret = gaokun_ec_write(ec, req);
> + if (ret == 0)
> + break;
> +
> + msleep(100);
> + };
> +
> + ec->suspended = false;
> +
> + return 0;
> +}
> +
> +static void gaokun_aux_release(struct device *dev)
> +{
> + struct auxiliary_device *adev = to_auxiliary_dev(dev);
> +
> + kfree(adev);
> +}
> +
> +static void gaokun_aux_remove(void *data)
> +{
> + struct auxiliary_device *adev = data;
> +
> + auxiliary_device_delete(adev);
> + auxiliary_device_uninit(adev);
> +}
> +
> +static int gaokun_aux_init(struct device *parent, const char *name,
> + struct gaokun_ec *ec)
> +{
> + struct auxiliary_device *adev;
> + int ret;
> +
> + adev = kzalloc(sizeof(*adev), GFP_KERNEL);
> + if (!adev)
> + return -ENOMEM;
> +
> + adev->name = name;
> + adev->id = 0;
> + adev->dev.parent = parent;
> + adev->dev.release = gaokun_aux_release;
> + adev->dev.platform_data = ec;
> + /* Allow aux devices to access parent's DT nodes directly */
> + device_set_of_node_from_dev(&adev->dev, parent);
> +
> + ret = auxiliary_device_init(adev);
> + if (ret) {
> + kfree(adev);
> + return ret;
> + }
> +
> + ret = auxiliary_device_add(adev);
> + if (ret) {
> + auxiliary_device_uninit(adev);
> + return ret;
> + }
> +
> + return devm_add_action_or_reset(parent, gaokun_aux_remove, adev);
> +}
> +
> +/* -------------------------------------------------------------------------- */
> +/* EC */
> +
> +static irqreturn_t gaokun_ec_irq_handler(int irq, void *data)
> +{
> + struct gaokun_ec *ec = data;
> + u8 req[REQ_HDR_SIZE] = {EC_EVENT, EC_QUERY, 0};
> + u8 status, id;
> + int ret;
> +
> + ret = gaokun_ec_read_byte(ec, req, &id);
> + if (ret)
> + return IRQ_HANDLED;
The error should probably be logged instead of silently ignored
> +
> + switch (id) {
> + case 0x0: /* No event */
> + break;
> +
> + case EC_EVENT_LID:
> + gaokun_ec_psy_read_byte(ec, EC_LID_STATE, &status);
> + status = EC_LID_OPEN & status;
> + input_report_switch(ec->idev, SW_LID, !status);
> + input_sync(ec->idev);
> + break;
> +
> + default:
> + blocking_notifier_call_chain(&ec->notifier_list, id, ec);
> + }
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int gaokun_ec_probe(struct i2c_client *client)
> +{
> + struct device *dev = &client->dev;
> + struct gaokun_ec *ec;
> + int ret;
> +
> + ec = devm_kzalloc(dev, sizeof(*ec), GFP_KERNEL);
> + if (!ec)
> + return -ENOMEM;
> +
> + mutex_init(&ec->lock);
> + ec->client = client;
> + i2c_set_clientdata(client, ec);
> + BLOCKING_INIT_NOTIFIER_HEAD(&ec->notifier_list);
> +
> + /* Lid switch */
> + ec->idev = devm_input_allocate_device(dev);
> + if (!ec->idev)
> + return -ENOMEM;
> +
> + ec->idev->name = "LID";
> + ec->idev->phys = "gaokun-ec/input0";
> + input_set_capability(ec->idev, EV_SW, SW_LID);
> +
> + ret = input_register_device(ec->idev);
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to register input device\n");
> +
> + ret = gaokun_aux_init(dev, "psy", ec);
> + if (ret)
> + return ret;
> +
> + ret = gaokun_aux_init(dev, "wmi", ec);
> + if (ret)
> + return ret;
> +
> + ret = gaokun_aux_init(dev, "ucsi", ec);
> + if (ret)
> + return ret;
> +
> + ret = devm_request_threaded_irq(dev, client->irq, NULL,
> + gaokun_ec_irq_handler, IRQF_ONESHOT,
> + dev_name(dev), ec);
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to request irq\n");
> +
> + return 0;
> +}
> +
> +static const struct i2c_device_id gaokun_ec_id[] = {
> + { "gaokun-ec", },
> + { }
> +};
> +MODULE_DEVICE_TABLE(i2c, gaokun_ec_id);
> +
> +static const struct of_device_id gaokun_ec_of_match[] = {
> + { .compatible = "huawei,gaokun-ec", },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, gaokun_ec_of_match);
> +
> +static const struct dev_pm_ops gaokun_ec_pm_ops = {
> + NOIRQ_SYSTEM_SLEEP_PM_OPS(gaokun_ec_suspend, gaokun_ec_resume)
> +};
> +
> +static struct i2c_driver gaokun_ec_driver = {
> + .driver = {
> + .name = "gaokun-ec",
> + .of_match_table = gaokun_ec_of_match,
> + .pm = &gaokun_ec_pm_ops,
> + },
> + .probe = gaokun_ec_probe,
> + .id_table = gaokun_ec_id,
> +};
> +module_i2c_driver(gaokun_ec_driver);
> +
> +MODULE_DESCRIPTION("HUAWEI Matebook E Go EC driver");
> +MODULE_AUTHOR("Pengyu Luo <mitltlatltl@gmail.com>");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/platform/arm64/huawei-gaokun-wmi.c b/drivers/platform/arm64/huawei-gaokun-wmi.c
> new file mode 100644
> index 000000000..793cb1659
> --- /dev/null
> +++ b/drivers/platform/arm64/huawei-gaokun-wmi.c
> @@ -0,0 +1,283 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * huawei-gaokun-wmi - A WMI driver for HUAWEI Matebook E Go (sc8280xp)
Just because in ACPI it's done by WMI stuff doesn't mean the non-ACPI
driver has to reflect this
> + *
> + * reference: drivers/platform/x86/huawei-wmi.c
> + *
> + * Copyright (C) 2024 Pengyu Luo <mitltlatltl@gmail.com>
> + */
> +
> +#include <linux/auxiliary_bus.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/sysfs.h>
> +#include <linux/version.h>
> +
> +#include <linux/platform_data/huawei-gaokun-ec.h>
> +
> +struct gaokun_wmi {
> + struct gaokun_ec *ec;
> + struct device *dev;
> + struct platform_device *wmi;
> +};
> +
> +/* -------------------------------------------------------------------------- */
> +/* Battery charging threshold */
> +
> +enum gaokun_wmi_threshold_ind {
> + START = 1,
> + END = 2,
> +};
> +
> +static ssize_t charge_control_thresholds_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + int ret;
> + u8 start, end;
> + struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
> +
> + ret = gaokun_ec_wmi_get_threshold(ecwmi->ec, &start, START)
> + || gaokun_ec_wmi_get_threshold(ecwmi->ec, &end, END);
> + if (ret)
> + return ret;
> +
> + return sysfs_emit(buf, "%d %d\n", start, end);
> +}
> +
> +static ssize_t charge_control_thresholds_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t size)
> +{
> + int ret;
> + u8 start, end;
> + struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
> +
> + if (sscanf(buf, "%hhd %hhd", &start, &end) != 2)
> + return -EINVAL;
> +
> + ret = gaokun_ec_wmi_set_threshold(ecwmi->ec, start, end);
> + if (ret)
> + return ret;
> +
> + return size;
> +}
> +
> +static DEVICE_ATTR_RW(charge_control_thresholds);
> +
> +/* -------------------------------------------------------------------------- */
> +/* Smart charge param */
> +
> +static ssize_t smart_charge_param_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + int ret;
> + u8 value;
> + struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
> +
> + ret = gaokun_ec_wmi_get_smart_charge_param(ecwmi->ec, &value);
> + if (ret)
> + return ret;
> +
> + return sysfs_emit(buf, "%d\n", value);
> +}
> +
> +static ssize_t smart_charge_param_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t size)
> +{
> + int ret;
> + u8 value;
> + struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
> +
> + if (kstrtou8(buf, 10, &value))
> + return -EINVAL;
> +
> + ret = gaokun_ec_wmi_set_smart_charge_param(ecwmi->ec, value);
> + if (ret)
> + return ret;
> +
> + return size;
> +}
> +
> +static DEVICE_ATTR_RW(smart_charge_param);
> +
> +/* -------------------------------------------------------------------------- */
> +/* Smart charge */
> +
> +static ssize_t smart_charge_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + int ret;
> + u8 bf[GAOKUN_SMART_CHARGE_DATA_SIZE];
> + struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
> +
> + ret = gaokun_ec_wmi_get_smart_charge(ecwmi->ec, bf);
> + if (ret)
> + return ret;
> +
> + return sysfs_emit(buf, "%d %d %d %d\n",
> + bf[0], bf[1], bf[2], bf[3]);
> +}
> +
> +static ssize_t smart_charge_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t size)
> +{
> + int ret;
> + u8 bf[GAOKUN_SMART_CHARGE_DATA_SIZE];
> + struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
> +
> + if (sscanf(buf, "%hhd %hhd %hhd %hhd", bf, bf + 1, bf + 2, bf + 3) != 4)
> + return -EINVAL;
> +
> + ret = gaokun_ec_wmi_set_smart_charge(ecwmi->ec, bf);
> + if (ret)
> + return ret;
> +
> + return size;
> +}
> +
> +static DEVICE_ATTR_RW(smart_charge);
> +
> +/* -------------------------------------------------------------------------- */
> +/* Fn lock */
> +
> +static ssize_t fn_lock_state_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + int ret;
> + u8 on;
> + struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
> +
> + ret = gaokun_ec_wmi_get_fn_lock(ecwmi->ec, &on);
> + if (ret)
> + return ret;
> +
> + return sysfs_emit(buf, "%d\n", on);
> +}
> +
> +static ssize_t fn_lock_state_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t size)
> +{
> + int ret;
> + u8 on;
> + struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
> +
> + if (kstrtou8(buf, 10, &on))
> + return -EINVAL;
> +
> + ret = gaokun_ec_wmi_set_fn_lock(ecwmi->ec, on);
> + if (ret)
> + return ret;
> +
> + return size;
> +}
> +
> +static DEVICE_ATTR_RW(fn_lock_state);
> +
> +/* -------------------------------------------------------------------------- */
> +/* Thermal Zone */
> +
> +static ssize_t temperature_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> +
> + int ret, len, i;
> + char *ptr = buf;
> + s16 value;
> + s16 temp[GAOKUN_TZ_REG_NUM];
> + struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
> +
> + ret = gaokun_ec_wmi_get_temp(ecwmi->ec, temp);
> + if (ret)
> + return ret;
> +
> + i = 0;
> + len = 0;
> + while (i < GAOKUN_TZ_REG_NUM) {
> + value = temp[i++];
> + if (value < 0) {
> + len += sprintf(ptr + len, "-");
> + value = -value;
> + }
> + len += sprintf(ptr + len, "%d.%d ", value / 10, value % 10);
> + }
> + len += sprintf(ptr + len, "\n");
> +
> + return len;
> +}
> +
> +static DEVICE_ATTR_RO(temperature);
> +
> +static struct attribute *gaokun_wmi_features_attrs[] = {
> + &dev_attr_charge_control_thresholds.attr,
> + &dev_attr_smart_charge_param.attr,
> + &dev_attr_smart_charge.attr,
> + &dev_attr_fn_lock_state.attr,
> + &dev_attr_temperature.attr,
> + NULL,
> +};
> +ATTRIBUTE_GROUPS(gaokun_wmi_features);
> +
> +static int gaokun_wmi_probe(struct auxiliary_device *adev,
> + const struct auxiliary_device_id *id)
> +{
> + struct gaokun_ec *ec = adev->dev.platform_data;
> + struct device *dev = &adev->dev;
> + struct gaokun_wmi *ecwmi;
> +
> + ecwmi = devm_kzalloc(&adev->dev, sizeof(*ecwmi), GFP_KERNEL);
> + if (!ecwmi)
> + return -ENOMEM;
> +
> + ecwmi->ec = ec;
> + ecwmi->dev = dev;
> +
> + auxiliary_set_drvdata(adev, ecwmi);
> +
> + /* make it under /sys/devices/platform, convenient for sysfs I/O,
> + * while adev is under
> + * /sys/devices/platform/soc@0/ac0000.geniqup/a9c000.i2c/i2c-15/15-0038/
> + */
> + ecwmi->wmi = platform_device_register_simple("gaokun-wmi", -1, NULL, 0);
> + if (IS_ERR(ecwmi->wmi))
> + return dev_err_probe(dev, PTR_ERR(ecwmi->wmi),
> + "Failed to register wmi platform device\n");
> +
> + platform_set_drvdata(ecwmi->wmi, ecwmi);
> +
> + return device_add_groups(&ecwmi->wmi->dev, gaokun_wmi_features_groups);
> +}
> +
> +static void gaokun_wmi_remove(struct auxiliary_device *adev)
> +{
> + struct gaokun_wmi *ecwmi = auxiliary_get_drvdata(adev);
> + struct platform_device *wmi = ecwmi->wmi;
> +
> + device_remove_groups(&wmi->dev, gaokun_wmi_features_groups);
> + platform_device_unregister(ecwmi->wmi);
> +}
> +
> +static const struct auxiliary_device_id gaokun_wmi_id_table[] = {
> + { .name = GAOKUN_MOD_NAME "." GAOKUN_DEV_WMI, },
> + {}
> +};
> +MODULE_DEVICE_TABLE(auxiliary, gaokun_wmi_id_table);
> +
> +static struct auxiliary_driver gaokun_wmi_driver = {
> + .name = GAOKUN_DEV_WMI,
> + .id_table = gaokun_wmi_id_table,
> + .probe = gaokun_wmi_probe,
> + .remove = gaokun_wmi_remove,
> +};
> +
> +module_auxiliary_driver(gaokun_wmi_driver);
> +
> +MODULE_DESCRIPTION("HUAWEI Matebook E Go WMI driver");
> +MODULE_LICENSE("GPL");
> diff --git a/include/linux/platform_data/huawei-gaokun-ec.h b/include/linux/platform_data/huawei-gaokun-ec.h
> new file mode 100644
> index 000000000..a649e9ecf
> --- /dev/null
> +++ b/include/linux/platform_data/huawei-gaokun-ec.h
> @@ -0,0 +1,90 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/* Huawei Matebook E Go (sc8280xp) Embedded Controller
> + *
> + * Copyright (C) 2024 Pengyu Luo <mitltlatltl@gmail.com>
> + *
> + */
> +
> +#ifndef __HUAWEI_GAOKUN_EC_H__
> +#define __HUAWEI_GAOKUN_EC_H__
> +
> +#define GAOKUN_UCSI_CCI_SIZE 4
> +#define GAOKUN_UCSI_DATA_SIZE 16
> +#define GAOKUN_UCSI_READ_SIZE (GAOKUN_UCSI_CCI_SIZE + GAOKUN_UCSI_DATA_SIZE)
> +#define GAOKUN_UCSI_WRITE_SIZE 0x18
> +
> +#define GAOKUN_TZ_REG_NUM 20
> +#define GAOKUN_SMART_CHARGE_DATA_SIZE 4 /* mode, delay, start, end */
> +
> +/* -------------------------------------------------------------------------- */
> +
> +struct gaokun_ec;
> +struct notifier_block;
> +
> +#define GAOKUN_MOD_NAME "huawei_gaokun_ec"
> +#define GAOKUN_DEV_PSY "psy"
> +#define GAOKUN_DEV_WMI "wmi"
> +#define GAOKUN_DEV_UCSI "ucsi"
> +
> +/* -------------------------------------------------------------------------- */
> +/* Common API */
> +
> +int gaokun_ec_register_notify(struct gaokun_ec *ec,
> + struct notifier_block *nb);
> +void gaokun_ec_unregister_notify(struct gaokun_ec *ec,
> + struct notifier_block *nb);
> +
> +int gaokun_ec_read(struct gaokun_ec *ec, const u8 *req,
> + size_t resp_len, u8 *resp);
> +int gaokun_ec_write(struct gaokun_ec *ec, u8 *req);
> +int gaokun_ec_read_byte(struct gaokun_ec *ec, u8 *req, u8 *byte);
> +
> +/* -------------------------------------------------------------------------- */
> +/* API For PSY */
> +
> +int gaokun_ec_psy_multi_read(struct gaokun_ec *ec, u8 reg,
> + size_t resp_len, u8 *resp);
> +
> +static inline int gaokun_ec_psy_read_byte(struct gaokun_ec *ec,
> + u8 reg, u8 *byte)
> +{
> + return gaokun_ec_psy_multi_read(ec, reg, 1, byte);
> +}
> +
> +static inline int gaokun_ec_psy_read_word(struct gaokun_ec *ec,
> + u8 reg, u16 *word)
> +{
> + return gaokun_ec_psy_multi_read(ec, reg, 2, (u8 *)word);
> +}
> +
> +/* -------------------------------------------------------------------------- */
> +/* API For WMI */
> +
> +int gaokun_ec_wmi_get_threshold(struct gaokun_ec *ec, u8 *value, int ind);
> +int gaokun_ec_wmi_set_threshold(struct gaokun_ec *ec, u8 start, u8 end);
> +
> +int gaokun_ec_wmi_get_smart_charge_param(struct gaokun_ec *ec, u8 *value);
> +int gaokun_ec_wmi_set_smart_charge_param(struct gaokun_ec *ec, u8 value);
> +
> +int gaokun_ec_wmi_get_smart_charge(struct gaokun_ec *ec,
> + u8 data[GAOKUN_SMART_CHARGE_DATA_SIZE]);
> +int gaokun_ec_wmi_set_smart_charge(struct gaokun_ec *ec,
> + u8 data[GAOKUN_SMART_CHARGE_DATA_SIZE]);
> +
> +int gaokun_ec_wmi_get_fn_lock(struct gaokun_ec *ec, u8 *on);
> +int gaokun_ec_wmi_set_fn_lock(struct gaokun_ec *ec, u8 on);
> +
> +int gaokun_ec_wmi_get_temp(struct gaokun_ec *ec, s16 temp[GAOKUN_TZ_REG_NUM]);
> +
> +/* -------------------------------------------------------------------------- */
> +/* API For UCSI */
> +
> +int gaokun_ec_ucsi_read(struct gaokun_ec *ec, u8 resp[GAOKUN_UCSI_READ_SIZE]);
> +int gaokun_ec_ucsi_write(struct gaokun_ec *ec,
> + const u8 req[GAOKUN_UCSI_WRITE_SIZE]);
> +
> +int gaokun_ec_ucsi_get_reg(struct gaokun_ec *ec, u8 *ureg);
> +int gaokun_ec_ucsi_pan_ack(struct gaokun_ec *ec, int port_id);
> +
> +
> +#endif /* __HUAWEI_GAOKUN_EC_H__ */
> --
> 2.47.1
>
>
Best Regards,
Maya Matuszczyk
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 2/5] platform: arm64: add Huawei Matebook E Go (sc8280xp) EC driver
2024-12-27 18:21 ` Maya Matuszczyk
@ 2024-12-28 5:42 ` Pengyu Luo
0 siblings, 0 replies; 51+ messages in thread
From: Pengyu Luo @ 2024-12-28 5:42 UTC (permalink / raw)
To: maccraft123mc
Cc: andersson, bryan.odonoghue, conor+dt, devicetree,
dmitry.baryshkov, gregkh, hdegoede, heikki.krogerus,
ilpo.jarvinen, konradybcio, krzk+dt, linux-arm-msm, linux-kernel,
linux-pm, linux-usb, mitltlatltl, nikita, platform-driver-x86,
robh, sre
On Sat, Dec 28, 2024 at 2:21 AM Maya Matuszczyk <maccraft123mc@gmail.com> wrote:
> Good to see someone else doing EC drivers for arm64 laptops!
>
Yeah, I have worked on it for a while. I really don't know why don't some
mechines use a pmic glink. AFAIK, there are some WOA devices without EC.
Mechines can definitely work without it in a way.
I am also glad that you are reviewing my code.
> pt., 27 gru 2024 o 18:16 Pengyu Luo <mitltlatltl@gmail.com> napisał(a):
> >
> > There are 3 variants, Huawei released first 2 at the same time.
> > Huawei Matebook E Go LTE(sc8180x), codename should be gaokun2.
> > Huawei Matebook E Go(sc8280xp@3.0GHz), codename is gaokun3.
> > Huawei Matebook E Go 2023(sc8280xp@2.69GHz).
> >
> > Adding support for the latter two variants for now, this driver should
> > also work for the sc8180x variant according to acpi table files, but I
> > don't have the device yet.
> >
> > Different from other Qualcomm Snapdragon sc8280xp based machines, the
> > Huawei Matebook E Go uses an embedded controller while others use
> > something called pmic glink. This embedded controller can be used to
> > perform a set of various functions, including, but not limited:
> >
> > - Battery and charger monitoring;
> > - Charge control and smart charge;
> > - Fn_lock settings;
> > - Tablet lid status;
> > - Temperature sensors;
> > - USB Type-C notifications (ports orientation, DP alt mode HPD);
> > - USB Type-C PD (according to observation, up to 48w).
> >
> > Add the driver for the EC, that creates devices for UCSI, wmi and power
> > supply devices.
> >
> > Signed-off-by: Pengyu Luo <mitltlatltl@gmail.com>
> > ---
> > drivers/platform/arm64/Kconfig | 19 +
> > drivers/platform/arm64/Makefile | 2 +
> > drivers/platform/arm64/huawei-gaokun-ec.c | 598 ++++++++++++++++++
> > drivers/platform/arm64/huawei-gaokun-wmi.c | 283 +++++++++
> > .../linux/platform_data/huawei-gaokun-ec.h | 90 +++
> > 5 files changed, 992 insertions(+)
> > create mode 100644 drivers/platform/arm64/huawei-gaokun-ec.c
> > create mode 100644 drivers/platform/arm64/huawei-gaokun-wmi.c
> > create mode 100644 include/linux/platform_data/huawei-gaokun-ec.h
[...]
> > +/* -------------------------------------------------------------------------- */
> > +/* API For PSY */
> > +
> > +int gaokun_ec_psy_multi_read(struct gaokun_ec *ec, u8 reg,
> > + size_t resp_len, u8 *resp)
> > +{
> > + int i, ret;
> > + u8 _resp[RESP_HDR_SIZE + 1];
> > + u8 req[REQ_HDR_SIZE + 1] = {0x02, EC_READ, 1, };
> > +
> > + for (i = 0; i < resp_len; ++i) {
> > + req[INPUT_DATA_OFFSET] = reg++;
> > + ret = gaokun_ec_read(ec, req, sizeof(_resp), _resp);
> > + if (ret)
> > + return -EIO;
> > + resp[i] = _resp[DATA_OFFSET];
> > + }
> > +
> > + return 0;
> > +}
> > +EXPORT_SYMBOL_GPL(gaokun_ec_psy_multi_read);
> > +
> > +/* -------------------------------------------------------------------------- */
> > +/* API For WMI */
> WMI is in ACPI, this laptop doesn't boot with ACPI so why are things
> handled in WMI separate from rest of the driver?
>
This driver reimplemented WMI functoins, and it perform a lot of
operations, to avoid naming it, just call it WMI, make life easier.
Once I considered keeping WMI things in this file, but it makes this file
bloated. Then I splitted every part into a module.
> > +
> > +/* Battery charging threshold */
> > +int gaokun_ec_wmi_get_threshold(struct gaokun_ec *ec, u8 *value, int ind)
> > +{
> > + /* GBTT */
> > + return gaokun_ec_read_byte(ec, (u8 []){0x02, 0x69, 1, ind}, value);
> > +}
> > +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_get_threshold);
> > +
> > +int gaokun_ec_wmi_set_threshold(struct gaokun_ec *ec, u8 start, u8 end)
> > +{
> > + /* SBTT */
> > + int ret;
> > + u8 req[REQ_HDR_SIZE + 2] = {0x02, 0x68, 2, 3, 0x5a};
> > +
> > + ret = gaokun_ec_write(ec, req);
> > + if (ret)
> > + return -EIO;
> > +
> > + if (start == 0 && end == 0)
> > + return -EINVAL;
> > +
> > + if (start >= 0 && start <= end && end <= 100) {
> > + req[INPUT_DATA_OFFSET] = 1;
> > + req[INPUT_DATA_OFFSET + 1] = start;
> > + ret = gaokun_ec_write(ec, req);
> > + if (ret)
> > + return -EIO;
> > +
> > + req[INPUT_DATA_OFFSET] = 2;
> > + req[INPUT_DATA_OFFSET + 1] = end;
> > + ret = gaokun_ec_write(ec, req);
> > + } else {
> > + return -EINVAL;
> > + }
> > +
> > + return ret;
> > +}
> > +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_set_threshold);
[...]
> > +/* -------------------------------------------------------------------------- */
> > +/* EC */
> > +
> > +static irqreturn_t gaokun_ec_irq_handler(int irq, void *data)
> > +{
> > + struct gaokun_ec *ec = data;
> > + u8 req[REQ_HDR_SIZE] = {EC_EVENT, EC_QUERY, 0};
> > + u8 status, id;
> > + int ret;
> > +
> > + ret = gaokun_ec_read_byte(ec, req, &id);
> > + if (ret)
> > + return IRQ_HANDLED;
> The error should probably be logged instead of silently ignored
>
Generally, one or two I/O errors don't crash anything, but actually if we
just do as ACPI methoda did, there should not be any error. It may be
necessary for debugging at the early stage of developemnt. If you suggest,
then we can add a report to the lower function (gaokun_ec_request) to check
every transaction. BTW, lenovo c630 also ignored them.
> > +
> > + switch (id) {
> > + case 0x0: /* No event */
> > + break;
> > +
> > + case EC_EVENT_LID:
> > + gaokun_ec_psy_read_byte(ec, EC_LID_STATE, &status);
> > + status = EC_LID_OPEN & status;
> > + input_report_switch(ec->idev, SW_LID, !status);
> > + input_sync(ec->idev);
> > + break;
> > +
> > + default:
> > + blocking_notifier_call_chain(&ec->notifier_list, id, ec);
> > + }
> > +
> > + return IRQ_HANDLED;
> > +}
> > +
[...]
> > diff --git a/drivers/platform/arm64/huawei-gaokun-wmi.c b/drivers/platform/arm64/huawei-gaokun-wmi.c
> > new file mode 100644
> > index 000000000..793cb1659
> > --- /dev/null
> > +++ b/drivers/platform/arm64/huawei-gaokun-wmi.c
> > @@ -0,0 +1,283 @@
> > +// SPDX-License-Identifier: GPL-2.0-only
> > +/*
> > + * huawei-gaokun-wmi - A WMI driver for HUAWEI Matebook E Go (sc8280xp)
> Just because in ACPI it's done by WMI stuff doesn't mean the non-ACPI
> driver has to reflect this
>
As I just said, and the WMI stuffs are all implemented by wrapping a EC
transaction in ACPI, at least in this machine.
> > + *
> > + * reference: drivers/platform/x86/huawei-wmi.c
> > + *
> > + * Copyright (C) 2024 Pengyu Luo <mitltlatltl@gmail.com>
> > + */
> > +
> > +#include <linux/auxiliary_bus.h>
> > +#include <linux/module.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/sysfs.h>
> > +#include <linux/version.h>
> > +
> > +#include <linux/platform_data/huawei-gaokun-ec.h>
> > +
> >
[...]
> >
>
> Best Regards,
> Maya Matuszczyk
Best Wishes,
Pengyu
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 1/5] dt-bindings: platform: Add Huawei Matebook E Go EC
2024-12-27 17:13 ` [PATCH 1/5] dt-bindings: platform: Add Huawei Matebook E Go EC Pengyu Luo
2024-12-27 18:18 ` Rob Herring (Arm)
@ 2024-12-28 9:54 ` Krzysztof Kozlowski
2024-12-28 10:50 ` Pengyu Luo
1 sibling, 1 reply; 51+ messages in thread
From: Krzysztof Kozlowski @ 2024-12-28 9:54 UTC (permalink / raw)
To: Pengyu Luo, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Bjorn Andersson, Konrad Dybcio, Hans de Goede, Ilpo Järvinen,
Bryan O'Donoghue, Sebastian Reichel, Heikki Krogerus,
Greg Kroah-Hartman
Cc: devicetree, linux-kernel, linux-arm-msm, platform-driver-x86,
linux-pm, linux-usb, Dmitry Baryshkov, Nikita Travkin
On 27/12/2024 18:13, Pengyu Luo wrote:
> +
> +description:
> + Different from other Qualcomm Snapdragon sc8180x sc8280xp based machines,
> + the Huawei Matebook E Go tablets use embedded controllers while others
> + use something called pmic glink which handles battery, UCSI, USB Type-C DP
> + alt mode. Huawei one handles even more, like charging thresholds, FN lock,
> + lid status, HPD events for the USB Type-C DP alt mode, etc.
> +
> +properties:
> + compatible:
> + items:
> + - enum:
> + - huawei,sc8180x-gaokun-ec
> + - huawei,sc8280xp-gaokun-ec
sc8180x and sc8280xp are not products of Huawei, so you cannot combine
them. Use compatibles matching exactly your device, because I doubt any
of us has actual schematics or datasheet of that device.
> + - const: huawei,gaokun-ec
How did you get the name?
> +
> + reg:
> + const: 0x38
> +
> + interrupts:
> + maxItems: 1
> +
> + connector:
> + $ref: /schemas/connector/usb-connector.yaml#
> +
> +required:
> + - compatible
> + - reg
> + - interrupts
> +
> +additionalProperties: false
> +
> +examples:
> + - |
> + #include <dt-bindings/interrupt-controller/irq.h>
> + i2c15 {
i2c
> + clock-frequency = <400000>;
> +
> + pinctrl-names = "default";
> + pinctrl-0 = <&i2c15_default>;
Drop all three above and test your bindings. This cannot work and test
will tell you what is missing.
> +
> + embedded-controller@38 {
> + compatible = "huawei,sc8280xp-gaokun-ec", ""huawei,gaokun-ec";
> + reg = <0x38>;
> +
> + interrupts-extended = <&tlmm 107 IRQ_TYPE_LEVEL_LOW>;
> +
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + connector@0 {
Test your bindings - you do not have node connector@0.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 2/5] platform: arm64: add Huawei Matebook E Go (sc8280xp) EC driver
2024-12-27 17:13 ` [PATCH 2/5] platform: arm64: add Huawei Matebook E Go (sc8280xp) EC driver Pengyu Luo
2024-12-27 18:21 ` Maya Matuszczyk
@ 2024-12-28 9:58 ` Krzysztof Kozlowski
2024-12-28 11:34 ` [PATCH 1/5] dt-bindings: platform: Add Huawei Matebook E Go EC Pengyu Luo
2024-12-28 12:33 ` [PATCH 2/5] platform: arm64: add Huawei Matebook E Go (sc8280xp) EC driver Bryan O'Donoghue
` (2 subsequent siblings)
4 siblings, 1 reply; 51+ messages in thread
From: Krzysztof Kozlowski @ 2024-12-28 9:58 UTC (permalink / raw)
To: Pengyu Luo, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Bjorn Andersson, Konrad Dybcio, Hans de Goede, Ilpo Järvinen,
Bryan O'Donoghue, Sebastian Reichel, Heikki Krogerus,
Greg Kroah-Hartman
Cc: devicetree, linux-kernel, linux-arm-msm, platform-driver-x86,
linux-pm, linux-usb, Dmitry Baryshkov, Nikita Travkin
On 27/12/2024 18:13, Pengyu Luo wrote:
> +
> +#include <linux/platform_data/huawei-gaokun-ec.h>
> +
> +#define EC_EVENT 0x06
> +
> +/* Also can be found in ACPI specification 12.3 */
> +#define EC_READ 0x80
> +#define EC_WRITE 0x81
> +#define EC_BURST 0x82
> +#define EC_QUERY 0x84
> +
> +
> +#define EC_EVENT_LID 0x81
> +
> +#define EC_LID_STATE 0x80
> +#define EC_LID_OPEN BIT(1)
> +
> +#define UCSI_REG_SIZE 7
> +
> +/* for tx, command sequences are arranged as
Use Linux style comments, see coding style.
> + * {master_cmd, slave_cmd, data_len, data_seq}
> + */
> +#define REQ_HDR_SIZE 3
> +#define INPUT_SIZE_OFFSET 2
> +#define INPUT_DATA_OFFSET 3
> +
> +/* for rx, data sequences are arranged as
> + * {status, data_len(unreliable), data_seq}
> + */
> +#define RESP_HDR_SIZE 2
> +#define DATA_OFFSET 2
> +
> +
> +struct gaokun_ec {
> + struct i2c_client *client;
> + struct mutex lock;
Missing doc. Run Checkpatch --strict, so you will know what is missing here.
> + struct blocking_notifier_head notifier_list;
> + struct input_dev *idev;
> + bool suspended;
> +};
> +
...
> +
> +static DEVICE_ATTR_RO(temperature);
> +
> +static struct attribute *gaokun_wmi_features_attrs[] = {
> + &dev_attr_charge_control_thresholds.attr,
> + &dev_attr_smart_charge_param.attr,
> + &dev_attr_smart_charge.attr,
> + &dev_attr_fn_lock_state.attr,
> + &dev_attr_temperature.attr,
> + NULL,
> +};
No, don't expose your own interface. Charging is already exposed by
power supply framework. Temperature by hwmon sensors. Drop all these and
never re-implement existing kernel user-space interfaces.
> diff --git a/include/linux/platform_data/huawei-gaokun-ec.h b/include/linux/platform_data/huawei-gaokun-ec.h
> new file mode 100644
> index 000000000..a649e9ecf
> --- /dev/null
> +++ b/include/linux/platform_data/huawei-gaokun-ec.h
> @@ -0,0 +1,90 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/* Huawei Matebook E Go (sc8280xp) Embedded Controller
> + *
> + * Copyright (C) 2024 Pengyu Luo <mitltlatltl@gmail.com>
> + *
> + */
> +
> +#ifndef __HUAWEI_GAOKUN_EC_H__
> +#define __HUAWEI_GAOKUN_EC_H__
> +
> +#define GAOKUN_UCSI_CCI_SIZE 4
> +#define GAOKUN_UCSI_DATA_SIZE 16
> +#define GAOKUN_UCSI_READ_SIZE (GAOKUN_UCSI_CCI_SIZE + GAOKUN_UCSI_DATA_SIZE)
> +#define GAOKUN_UCSI_WRITE_SIZE 0x18
> +
> +#define GAOKUN_TZ_REG_NUM 20
> +#define GAOKUN_SMART_CHARGE_DATA_SIZE 4 /* mode, delay, start, end */
> +
> +/* -------------------------------------------------------------------------- */
> +
> +struct gaokun_ec;
> +struct notifier_block;
Drop, include proper header instead.
> +
> +#define GAOKUN_MOD_NAME "huawei_gaokun_ec"
> +#define GAOKUN_DEV_PSY "psy"
> +#define GAOKUN_DEV_WMI "wmi"
> +#define GAOKUN_DEV_UCSI "ucsi"
> +
> +/* -------------------------------------------------------------------------- */
> +/* Common API */
> +
> +int gaokun_ec_register_notify(struct gaokun_ec *ec,
> + struct notifier_block *nb);
> +void gaokun_ec_unregister_notify(struct gaokun_ec *ec,
> + struct notifier_block *nb);
> +
> +int gaokun_ec_read(struct gaokun_ec *ec, const u8 *req,
> + size_t resp_len, u8 *resp);
> +int gaokun_ec_write(struct gaokun_ec *ec, u8 *req);
> +int gaokun_ec_read_byte(struct gaokun_ec *ec, u8 *req, u8 *byte);
You need kerneldoc, in the C file, for all exported functions.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 1/5] dt-bindings: platform: Add Huawei Matebook E Go EC
2024-12-28 9:54 ` Krzysztof Kozlowski
@ 2024-12-28 10:50 ` Pengyu Luo
2024-12-29 9:50 ` Krzysztof Kozlowski
0 siblings, 1 reply; 51+ messages in thread
From: Pengyu Luo @ 2024-12-28 10:50 UTC (permalink / raw)
To: krzk
Cc: andersson, bryan.odonoghue, conor+dt, devicetree,
dmitry.baryshkov, gregkh, hdegoede, heikki.krogerus,
ilpo.jarvinen, konradybcio, krzk+dt, linux-arm-msm, linux-kernel,
linux-pm, linux-usb, mitltlatltl, nikita, platform-driver-x86,
robh, sre
On Sat, Dec 28, 2024 at 5:54 PM Krzysztof Kozlowski <krzk@kernel.org> wrote:
> On 27/12/2024 18:13, Pengyu Luo wrote:
> > +
> > +description:
> > + Different from other Qualcomm Snapdragon sc8180x sc8280xp based machines,
> > + the Huawei Matebook E Go tablets use embedded controllers while others
> > + use something called pmic glink which handles battery, UCSI, USB Type-C DP
> > + alt mode. Huawei one handles even more, like charging thresholds, FN lock,
> > + lid status, HPD events for the USB Type-C DP alt mode, etc.
> > +
> > +properties:
> > + compatible:
> > + items:
> > + - enum:
> > + - huawei,sc8180x-gaokun-ec
> > + - huawei,sc8280xp-gaokun-ec
>
> sc8180x and sc8280xp are not products of Huawei, so you cannot combine
> them. Use compatibles matching exactly your device, because I doubt any
> of us has actual schematics or datasheet of that device.
>
> > + - const: huawei,gaokun-ec
>
> How did you get the name?
>
From website of Huawei([1]), please search for 'gaokun' here, we can know
this series is called gaokun. Many files from windows indicate more,
someone fetch drivers from microsoft server([2]), in one of driver archive
'OemXAudioExt_HWVE.cab', there are two files, "algorithm_GaoKunGen2.xml"
"algorithm_GaoKunGen3.xml". And `Gaokun Gen3` print can be found on
motherboard(someone have the motherboard, I can ask for it later).
So can I use?
- enum:
- huawei,gaokun-gen2
- huawei,gaokun-gen3
Some backgroud:
There are 3 variants, Huawei released first 2 at the same time.
Huawei Matebook E Go LTE(sc8180x), codename should be gaokun2.
Huawei Matebook E Go(sc8280xp@3.0GHz), codename is gaokun3.
Huawei Matebook E Go 2023(sc8280xp@2.69GHz).
> > +
> > + reg:
> > + const: 0x38
> > +
> > + interrupts:
> > + maxItems: 1
> > +
> > + connector:
> > + $ref: /schemas/connector/usb-connector.yaml#
> > +
> > +required:
> > + - compatible
> > + - reg
> > + - interrupts
> > +
> > +additionalProperties: false
> > +
> > +examples:
> > + - |
> > + #include <dt-bindings/interrupt-controller/irq.h>
> > + i2c15 {
>
> i2c
>
Agree
> > + clock-frequency = <400000>;
> > +
> > + pinctrl-names = "default";
> > + pinctrl-0 = <&i2c15_default>;
>
> Drop all three above and test your bindings. This cannot work and test
> will tell you what is missing.
>
Agree
> > +
> > + embedded-controller@38 {
> > + compatible = "huawei,sc8280xp-gaokun-ec", ""huawei,gaokun-ec";
> > + reg = <0x38>;
> > +
> > + interrupts-extended = <&tlmm 107 IRQ_TYPE_LEVEL_LOW>;
> > +
> > + #address-cells = <1>;
> > + #size-cells = <0>;
> > +
> > + connector@0 {
>
> Test your bindings - you do not have node connector@0.
>
I have rewritten it locally. So I will add the following and do some fixes
in v2.
patternProperties:
'^connector@[01]$':
$ref: /schemas/connector/usb-connector.yaml#
properties:
reg:
maxItems: 1
>
>
> Best regards,
> Krzysztof
Thanks,
Pengyu
[1] https://consumer.huawei.com/en/support/content/en-us15945089
[2] https://github.com/matebook-e-go/uup-drivers-sc8280xp/releases
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 1/5] dt-bindings: platform: Add Huawei Matebook E Go EC
2024-12-28 9:58 ` Krzysztof Kozlowski
@ 2024-12-28 11:34 ` Pengyu Luo
2024-12-29 4:08 ` Dmitry Baryshkov
2024-12-29 9:43 ` [PATCH 1/5] dt-bindings: platform: Add Huawei Matebook E Go EC Krzysztof Kozlowski
0 siblings, 2 replies; 51+ messages in thread
From: Pengyu Luo @ 2024-12-28 11:34 UTC (permalink / raw)
To: krzk
Cc: andersson, bryan.odonoghue, conor+dt, devicetree,
dmitry.baryshkov, gregkh, hdegoede, heikki.krogerus,
ilpo.jarvinen, konradybcio, krzk+dt, linux-arm-msm, linux-kernel,
linux-pm, linux-usb, mitltlatltl, nikita, platform-driver-x86,
robh, sre
> On Sat, Dec 28, 2024 at 5:58 PM Krzysztof Kozlowski <krzk@kernel.org> wrote:
> On 27/12/2024 18:13, Pengyu Luo wrote:
> > +
> > +#include <linux/platform_data/huawei-gaokun-ec.h>
> > +
> > +#define EC_EVENT 0x06
> > +
> > +/* Also can be found in ACPI specification 12.3 */
> > +#define EC_READ 0x80
> > +#define EC_WRITE 0x81
> > +#define EC_BURST 0x82
> > +#define EC_QUERY 0x84
> > +
> > +
> > +#define EC_EVENT_LID 0x81
> > +
> > +#define EC_LID_STATE 0x80
> > +#define EC_LID_OPEN BIT(1)
> > +
> > +#define UCSI_REG_SIZE 7
> > +
> > +/* for tx, command sequences are arranged as
>
> Use Linux style comments, see coding style.
>
Agree
> > + * {master_cmd, slave_cmd, data_len, data_seq}
> > + */
> > +#define REQ_HDR_SIZE 3
> > +#define INPUT_SIZE_OFFSET 2
> > +#define INPUT_DATA_OFFSET 3
> > +
> > +/* for rx, data sequences are arranged as
> > + * {status, data_len(unreliable), data_seq}
> > + */
> > +#define RESP_HDR_SIZE 2
> > +#define DATA_OFFSET 2
> > +
> > +
> > +struct gaokun_ec {
> > + struct i2c_client *client;
> > + struct mutex lock;
>
> Missing doc. Run Checkpatch --strict, so you will know what is missing here.
>
I see. A comment for mutex lock.
> > + struct blocking_notifier_head notifier_list;
> > + struct input_dev *idev;
> > + bool suspended;
> > +};
> > +
>
>
>
> ...
>
> > +
> > +static DEVICE_ATTR_RO(temperature);
> > +
> > +static struct attribute *gaokun_wmi_features_attrs[] = {
> > + &dev_attr_charge_control_thresholds.attr,
> > + &dev_attr_smart_charge_param.attr,
> > + &dev_attr_smart_charge.attr,
> > + &dev_attr_fn_lock_state.attr,
> > + &dev_attr_temperature.attr,
> > + NULL,
> > +};
>
>
> No, don't expose your own interface. Charging is already exposed by
> power supply framework. Temperature by hwmon sensors. Drop all these and
> never re-implement existing kernel user-space interfaces.
>
I don't quite understand what you mean. You mean I should use hwmon
interface like hwmon_device_register_with_groups to register it, right?
As for battery, get/set_propery allow us to handle charging thresholds
things, but there are smart_charge_param, smart_charge and fn_lock to handle.
>
> > diff --git a/include/linux/platform_data/huawei-gaokun-ec.h b/include/linux/platform_data/huawei-gaokun-ec.h
> > new file mode 100644
> > index 000000000..a649e9ecf
> > --- /dev/null
> > +++ b/include/linux/platform_data/huawei-gaokun-ec.h
> > @@ -0,0 +1,90 @@
> > +// SPDX-License-Identifier: GPL-2.0-only
> > +/* Huawei Matebook E Go (sc8280xp) Embedded Controller
> > + *
> > + * Copyright (C) 2024 Pengyu Luo <mitltlatltl@gmail.com>
> > + *
> > + */
> > +
> > +#ifndef __HUAWEI_GAOKUN_EC_H__
> > +#define __HUAWEI_GAOKUN_EC_H__
> > +
> > +#define GAOKUN_UCSI_CCI_SIZE 4
> > +#define GAOKUN_UCSI_DATA_SIZE 16
> > +#define GAOKUN_UCSI_READ_SIZE (GAOKUN_UCSI_CCI_SIZE + GAOKUN_UCSI_DATA_SIZE)
> > +#define GAOKUN_UCSI_WRITE_SIZE 0x18
> > +
> > +#define GAOKUN_TZ_REG_NUM 20
> > +#define GAOKUN_SMART_CHARGE_DATA_SIZE 4 /* mode, delay, start, end */
> > +
> > +/* -------------------------------------------------------------------------- */
> > +
> > +struct gaokun_ec;
> > +struct notifier_block;
>
> Drop, include proper header instead.
>
I agree, I copy 'struct notifier_block;' from
include/linux/platform_data/lenovo-yoga-c630.h
> > +
> > +#define GAOKUN_MOD_NAME "huawei_gaokun_ec"
> > +#define GAOKUN_DEV_PSY "psy"
> > +#define GAOKUN_DEV_WMI "wmi"
> > +#define GAOKUN_DEV_UCSI "ucsi"
> > +
> > +/* -------------------------------------------------------------------------- */
> > +/* Common API */
> > +
> > +int gaokun_ec_register_notify(struct gaokun_ec *ec,
> > + struct notifier_block *nb);
> > +void gaokun_ec_unregister_notify(struct gaokun_ec *ec,
> > + struct notifier_block *nb);
> > +
> > +int gaokun_ec_read(struct gaokun_ec *ec, const u8 *req,
> > + size_t resp_len, u8 *resp);
> > +int gaokun_ec_write(struct gaokun_ec *ec, u8 *req);
> > +int gaokun_ec_read_byte(struct gaokun_ec *ec, u8 *req, u8 *byte);
>
>
> You need kerneldoc, in the C file, for all exported functions.
>
I got it.
>
>
> Best regards,
> Krzysztof
Best Wishes,
Pengyu
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 2/5] platform: arm64: add Huawei Matebook E Go (sc8280xp) EC driver
2024-12-27 17:13 ` [PATCH 2/5] platform: arm64: add Huawei Matebook E Go (sc8280xp) EC driver Pengyu Luo
2024-12-27 18:21 ` Maya Matuszczyk
2024-12-28 9:58 ` Krzysztof Kozlowski
@ 2024-12-28 12:33 ` Bryan O'Donoghue
2024-12-28 13:51 ` Pengyu Luo
2024-12-29 14:49 ` Markus Elfring
2024-12-30 9:04 ` Aiqun(Maria) Yu
4 siblings, 1 reply; 51+ messages in thread
From: Bryan O'Donoghue @ 2024-12-28 12:33 UTC (permalink / raw)
To: Pengyu Luo, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Bjorn Andersson, Konrad Dybcio, Hans de Goede, Ilpo Järvinen,
Sebastian Reichel, Heikki Krogerus, Greg Kroah-Hartman
Cc: devicetree, linux-kernel, linux-arm-msm, platform-driver-x86,
linux-pm, linux-usb, Dmitry Baryshkov, Nikita Travkin
On 27/12/2024 17:13, Pengyu Luo wrote:
> There are 3 variants, Huawei released first 2 at the same time.
There are three variants of which Huawei released the first two
simultaneously.
> Huawei Matebook E Go LTE(sc8180x), codename should be gaokun2.
> Huawei Matebook E Go(sc8280xp@3.0GHz), codename is gaokun3.
Choose either "codename should be" or "codename is"
> Huawei Matebook E Go 2023(sc8280xp@2.69GHz).
Maybe say here "codename unknown"
> Adding support for the latter two variants for now, this driver should
> also work for the sc8180x variant according to acpi table files, but I
> don't have the device yet.
>
> Different from other Qualcomm Snapdragon sc8280xp based machines, the
> Huawei Matebook E Go uses an embedded controller while others use
> something called pmic glink. This embedded controller can be used to
> perform a set of various functions, including, but not limited:
"but not limited to":
> - Battery and charger monitoring;
> - Charge control and smart charge;
> - Fn_lock settings;
> - Tablet lid status;
> - Temperature sensors;
> - USB Type-C notifications (ports orientation, DP alt mode HPD);
> - USB Type-C PD (according to observation, up to 48w).
>
> Add the driver for the EC, that creates devices for UCSI, wmi and power
> supply devices.
I'm a terrible man for the "," dropped all about the place but you're
going too mad with the commans there ->
"Add a driver for the EC which creates devices for UCSI, WMI and power
supply devices"
>
> Signed-off-by: Pengyu Luo <mitltlatltl@gmail.com>
> ---
> drivers/platform/arm64/Kconfig | 19 +
> drivers/platform/arm64/Makefile | 2 +
> drivers/platform/arm64/huawei-gaokun-ec.c | 598 ++++++++++++++++++
> drivers/platform/arm64/huawei-gaokun-wmi.c | 283 +++++++++
> .../linux/platform_data/huawei-gaokun-ec.h | 90 +++
> 5 files changed, 992 insertions(+)
> create mode 100644 drivers/platform/arm64/huawei-gaokun-ec.c
> create mode 100644 drivers/platform/arm64/huawei-gaokun-wmi.c
> create mode 100644 include/linux/platform_data/huawei-gaokun-ec.h
>
> diff --git a/drivers/platform/arm64/Kconfig b/drivers/platform/arm64/Kconfig
> index f88395ea3..eb7fbacf0 100644
> --- a/drivers/platform/arm64/Kconfig
> +++ b/drivers/platform/arm64/Kconfig
> @@ -33,6 +33,25 @@ config EC_ACER_ASPIRE1
> laptop where this information is not properly exposed via the
> standard ACPI devices.
>
> +config EC_HUAWEI_GAOKUN
> + tristate "Huawei Matebook E Go (sc8280xp) Embedded Controller driver"
Krzysztof already mentioned this but the "sc8280xp" is questionable, you
should probably drop mention of qcom and sc8280xp from your compat and
tristate here.
> + depends on ARCH_QCOM || COMPILE_TEST
> + depends on I2C
> + depends on DRM
> + depends on POWER_SUPPLY
> + depends on INPUT
> + help
> + Say Y here to enable the EC driver for Huawei Matebook E Go 2in1
> + tablet. The driver handles battery(information, charge control) and
> + USB Type-C DP HPD events as well as some misc functions like the lid
> + sensor and temperature sensors, etc.
> +
> + This driver provides battery and AC status support for the mentioned
> + laptop where this information is not properly exposed via the
> + standard ACPI devices.
> +
> + Say M or Y here to include this support.
OTOH the help text is where you could mention the sc8280xp class of
laptops/tablets.
> +
> config EC_LENOVO_YOGA_C630
> tristate "Lenovo Yoga C630 Embedded Controller driver"
> depends on ARCH_QCOM || COMPILE_TEST
> diff --git a/drivers/platform/arm64/Makefile b/drivers/platform/arm64/Makefile
> index b2ae9114f..ed32ad6c0 100644
> --- a/drivers/platform/arm64/Makefile
> +++ b/drivers/platform/arm64/Makefile
> @@ -6,4 +6,6 @@
> #
>
> obj-$(CONFIG_EC_ACER_ASPIRE1) += acer-aspire1-ec.o
> +obj-$(CONFIG_EC_HUAWEI_GAOKUN) += huawei-gaokun-ec.o
> +obj-$(CONFIG_EC_HUAWEI_GAOKUN) += huawei-gaokun-wmi.o
> obj-$(CONFIG_EC_LENOVO_YOGA_C630) += lenovo-yoga-c630.o
> diff --git a/drivers/platform/arm64/huawei-gaokun-ec.c b/drivers/platform/arm64/huawei-gaokun-ec.c
> new file mode 100644
> index 000000000..c1c657f7b
> --- /dev/null
> +++ b/drivers/platform/arm64/huawei-gaokun-ec.c
> @@ -0,0 +1,598 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * huawei-gaokun-ec - An EC driver for HUAWEI Matebook E Go (sc8280xp)
> + *
> + * reference: drivers/platform/arm64/acer-aspire1-ec.c
> + * drivers/platform/arm64/lenovo-yoga-c630.c
> + *
> + * Copyright (C) 2024 Pengyu Luo <mitltlatltl@gmail.com>
> + */
> +
> +#include <linux/auxiliary_bus.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/i2c.h>
> +#include <linux/input.h>
> +#include <linux/notifier.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/version.h>
> +
> +#include <linux/platform_data/huawei-gaokun-ec.h>
> +
> +#define EC_EVENT 0x06
> +
> +/* Also can be found in ACPI specification 12.3 */
> +#define EC_READ 0x80
> +#define EC_WRITE 0x81
Is this odd indentation ?
> +#define EC_BURST 0x82
> +#define EC_QUERY 0x84
> +
> +
> +#define EC_EVENT_LID 0x81
> +
> +#define EC_LID_STATE 0x80
> +#define EC_LID_OPEN BIT(1)
> +
> +#define UCSI_REG_SIZE 7
> +
> +/* for tx, command sequences are arranged as
> + * {master_cmd, slave_cmd, data_len, data_seq}
> + */
> +#define REQ_HDR_SIZE 3
> +#define INPUT_SIZE_OFFSET 2
> +#define INPUT_DATA_OFFSET 3
> +
> +/* for rx, data sequences are arranged as
> + * {status, data_len(unreliable), data_seq}
> + */
> +#define RESP_HDR_SIZE 2
> +#define DATA_OFFSET 2
> +
> +
> +struct gaokun_ec {
> + struct i2c_client *client;
> + struct mutex lock;
> + struct blocking_notifier_head notifier_list;
> + struct input_dev *idev;
> + bool suspended;
> +};
> +
> +static int gaokun_ec_request(struct gaokun_ec *ec, const u8 *req,
> + size_t resp_len, u8 *resp)
> +{
> + struct i2c_client *client = ec->client;
> + struct i2c_msg msgs[2] = {
> + {
> + .addr = client->addr,
> + .flags = client->flags,
> + .len = req[INPUT_SIZE_OFFSET] + REQ_HDR_SIZE,
> + .buf = req,
> + }, {
> + .addr = client->addr,
> + .flags = client->flags | I2C_M_RD,
> + .len = resp_len,
> + .buf = resp,
> + },
> + };
> +
> + mutex_lock(&ec->lock);
> +
> + i2c_transfer(client->adapter, msgs, 2);
> + usleep_range(2000, 2500);
What is this sleep about and why are you doing it inside of a critical
section ?
> +
> + mutex_unlock(&ec->lock);
> +
> + return *resp;
> +}
> +
> +/* -------------------------------------------------------------------------- */
> +/* Common API */
> +
> +/**
> + * gaokun_ec_read - read from EC
> + * @ec: The gaokun_ec
> + * @req: The sequence to request
> + * @resp_len: The size to read
> + * @resp: Where the data are read to
> + *
> + * This function is used to read data after writing a magic sequence to EC.
> + * All EC operations dependent on this functions.
depend on this
> + *
> + * Huawei uses magic sequences everywhere to complete various functions, all
> + * these sequences are passed to ECCD(a ACPI method which is quiet similar
> + * to gaokun_ec_request), there is no good abstraction to generalize these
> + * sequences, so just wrap it for now. Almost all magic sequences are kept
> + * in this file.
> + */
> +int gaokun_ec_read(struct gaokun_ec *ec, const u8 *req,
> + size_t resp_len, u8 *resp)
> +{
> + return gaokun_ec_request(ec, req, resp_len, resp);
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_read);
> +
> +/**
> + * gaokun_ec_write - write to EC
> + * @ec: The gaokun_ec
> + * @req: The sequence to request
> + *
> + * This function has no big difference from gaokun_ec_read. When caller care
> + * only write status and no actual data are returnd, then use it.
> + */
> +int gaokun_ec_write(struct gaokun_ec *ec, u8 *req)
> +{
> + u8 resp[RESP_HDR_SIZE];
> +
> + return gaokun_ec_request(ec, req, sizeof(resp), resp);
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_write);
> +
> +int gaokun_ec_read_byte(struct gaokun_ec *ec, u8 *req, u8 *byte)
> +{
> + int ret;
> + u8 resp[RESP_HDR_SIZE + sizeof(*byte)];
> +
> + ret = gaokun_ec_read(ec, req, sizeof(resp), resp);
> + *byte = resp[DATA_OFFSET];
> +
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_read_byte);
> +
> +int gaokun_ec_register_notify(struct gaokun_ec *ec, struct notifier_block *nb)
> +{
> + return blocking_notifier_chain_register(&ec->notifier_list, nb);
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_register_notify);
> +
> +void gaokun_ec_unregister_notify(struct gaokun_ec *ec, struct notifier_block *nb)
> +{
> + blocking_notifier_chain_unregister(&ec->notifier_list, nb);
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_unregister_notify);
> +
> +/* -------------------------------------------------------------------------- */
> +/* API For PSY */
> +
> +int gaokun_ec_psy_multi_read(struct gaokun_ec *ec, u8 reg,
> + size_t resp_len, u8 *resp)
> +{
> + int i, ret;
> + u8 _resp[RESP_HDR_SIZE + 1];
> + u8 req[REQ_HDR_SIZE + 1] = {0x02, EC_READ, 1, };
Instead of constructing your packet inline like this, suggest a
dedicated function to construct a request packet.
For example 1 @ INPUT_SIZE_OFFSET => the size of data a dedicated
function will make the "stuffing" of the request frame more obvious to
readers and make the construction of packets less error prone.
> +
> + for (i = 0; i < resp_len; ++i) {
> + req[INPUT_DATA_OFFSET] = reg++;
> + ret = gaokun_ec_read(ec, req, sizeof(_resp), _resp);
> + if (ret)
> + return -EIO;
> + resp[i] = _resp[DATA_OFFSET];
> + }
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_psy_multi_read);
> +
> +/* -------------------------------------------------------------------------- */
> +/* API For WMI */
> +
> +/* Battery charging threshold */
> +int gaokun_ec_wmi_get_threshold(struct gaokun_ec *ec, u8 *value, int ind)
> +{
> + /* GBTT */
> + return gaokun_ec_read_byte(ec, (u8 []){0x02, 0x69, 1, ind}, value);
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_get_threshold);
> +
> +int gaokun_ec_wmi_set_threshold(struct gaokun_ec *ec, u8 start, u8 end)
> +{
> + /* SBTT */
> + int ret;
> + u8 req[REQ_HDR_SIZE + 2] = {0x02, 0x68, 2, 3, 0x5a};
> +
> + ret = gaokun_ec_write(ec, req);
> + if (ret)
> + return -EIO;
> +
> + if (start == 0 && end == 0)
> + return -EINVAL;
> +
> + if (start >= 0 && start <= end && end <= 100) {
if start >= 0
is redundant no ? start is a u8 it can _only_ be >= 0 ..
> + req[INPUT_DATA_OFFSET] = 1;
> + req[INPUT_DATA_OFFSET + 1] = start;
> + ret = gaokun_ec_write(ec, req);
> + if (ret)
> + return -EIO;
> +
> + req[INPUT_DATA_OFFSET] = 2;
> + req[INPUT_DATA_OFFSET + 1] = end;
again a function to construct a packet gets you out of the business of
inlining and "just knowing" which offset is which within any give
function which indexes an array.
> + ret = gaokun_ec_write(ec, req);
> + } else {
> + return -EINVAL;
> + }
> +
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_set_threshold);
> +
> +/* Smart charge param */
> +int gaokun_ec_wmi_get_smart_charge_param(struct gaokun_ec *ec, u8 *value)
> +{
> + /* GBAC */
> + return gaokun_ec_read_byte(ec, (u8 []){0x02, 0xE6, 0}, value);
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_get_smart_charge_param);
> +
> +int gaokun_ec_wmi_set_smart_charge_param(struct gaokun_ec *ec, u8 value)
> +{
> + /* SBAC */
> + if (value < 0 || value > 2)
value < 0 can never be true
> + return -EINVAL;
> +
> + return gaokun_ec_write(ec, (u8 []){0x02, 0xE5, 1, value});
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_set_smart_charge_param);
> +
> +/* Smart charge */
> +int gaokun_ec_wmi_get_smart_charge(struct gaokun_ec *ec,
> + u8 data[GAOKUN_SMART_CHARGE_DATA_SIZE])
> +{
> + /* GBCM */
> + u8 req[REQ_HDR_SIZE] = {0x02, 0xE4, 0};
> + u8 resp[RESP_HDR_SIZE + 4];
> + int ret;
> +
> + ret = gaokun_ec_read(ec, req, sizeof(resp), resp);
> + if (ret)
> + return -EIO;
> +
> + data[0] = resp[DATA_OFFSET];
> + data[1] = resp[DATA_OFFSET + 1];
> + data[2] = resp[DATA_OFFSET + 2];
> + data[3] = resp[DATA_OFFSET + 3];
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_get_smart_charge);
> +
> +int gaokun_ec_wmi_set_smart_charge(struct gaokun_ec *ec,
> + u8 data[GAOKUN_SMART_CHARGE_DATA_SIZE])
> +{
> + /* SBCM */
> + u8 req[REQ_HDR_SIZE + GAOKUN_SMART_CHARGE_DATA_SIZE] = {0x02, 0XE3, 4,};
> +
> + if (!(data[2] >= 0 && data[2] <= data[3] && data[3] <= 100))
> + return -EINVAL;
Repeat of the clause above which was checking u8 >= 0 for the same
values in the rest of the clause - including checking <= 100.
Certainly a candidate for functional decomposition, inline function or a
define.
> +
> + memcpy(req + INPUT_DATA_OFFSET, data, GAOKUN_SMART_CHARGE_DATA_SIZE);
> +
> + return gaokun_ec_write(ec, req);
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_set_smart_charge);
> +
> +/* Fn lock */
> +int gaokun_ec_wmi_get_fn_lock(struct gaokun_ec *ec, u8 *on)
> +{
> + /* GFRS */
> + int ret;
> + u8 val;
> + u8 req[REQ_HDR_SIZE] = {0x02, 0x6B, 0};
Reverse Christmas tree
u8 req[REQ_HDR_SIZE];
int ret;
u8 val;
Not required but nice to look at.
> +
> + ret = gaokun_ec_read_byte(ec, req, &val);
> + if (val == 0x55)
> + *on = 0;
> + else if (val == 0x5A)
> + *on = 1;
> + else
> + return -EIO;
> +
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_get_fn_lock);
> +
> +int gaokun_ec_wmi_set_fn_lock(struct gaokun_ec *ec, u8 on)
> +{
> + /* SFRS */
> + u8 req[REQ_HDR_SIZE + 1] = {0x02, 0x6C, 1,};
> +
> + if (on == 0)
> + req[INPUT_DATA_OFFSET] = 0x55;
> + else if (on == 1)
> + req[INPUT_DATA_OFFSET] = 0x5A;
> + else
> + return -EINVAL;
Why not use a bool for on ?
> +
> + return gaokun_ec_write(ec, req);
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_set_fn_lock);
> +
> +/* Thermal Zone */
> +/* Range from 0 to 0x2C, partial valid */
> +static const u8 temp_reg[] = {0x05, 0x07, 0x08, 0x0E, 0x0F, 0x12, 0x15, 0x1E,
> + 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
> + 0x27, 0x28, 0x29, 0x2A};
> +
> +int gaokun_ec_wmi_get_temp(struct gaokun_ec *ec, s16 temp[GAOKUN_TZ_REG_NUM])
int gaokun_ec_wmi_get_temp(struct gaokun_ec *ec, s16 *temp, size_t
temp_reg_num)
> +{
> + /* GTMP */
> + u8 req[REQ_HDR_SIZE] = {0x02, 0x61, 1,};
> + u8 resp[RESP_HDR_SIZE + sizeof(s16)];
> + int ret, i = 0;
> +
> + while (i < GAOKUN_TZ_REG_NUM) {
while (i < temp_reg_num)
> + req[INPUT_DATA_OFFSET] = temp_reg[i];
> + ret = gaokun_ec_read(ec, req, sizeof(resp), resp);
> + if (ret)
> + return -EIO;
> + temp[i++] = *(s16 *)(resp + DATA_OFFSET);
What's the point of the casting here ?
memcpy(temp, resp, sizeof(s16));
temp++;
> + }
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_get_temp);
> +
> +/* -------------------------------------------------------------------------- */
> +/* API For UCSI */
> +
> +int gaokun_ec_ucsi_read(struct gaokun_ec *ec,
> + u8 resp[GAOKUN_UCSI_READ_SIZE])
> +{
> + u8 req[REQ_HDR_SIZE] = {0x3, 0xD5, 0};
> + u8 _resp[RESP_HDR_SIZE + GAOKUN_UCSI_READ_SIZE];
> + int ret;
> +
> + ret = gaokun_ec_read(ec, req, sizeof(_resp), _resp);
> + if (ret)
> + return ret;
> +
> + memcpy(resp, _resp + DATA_OFFSET, GAOKUN_UCSI_READ_SIZE);
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_ucsi_read);
> +
> +int gaokun_ec_ucsi_write(struct gaokun_ec *ec,
> + const u8 req[GAOKUN_UCSI_WRITE_SIZE])
> +{
> + u8 _req[REQ_HDR_SIZE + GAOKUN_UCSI_WRITE_SIZE];
> +
> + _req[0] = 0x03;
> + _req[1] = 0xD4;
> + _req[INPUT_SIZE_OFFSET] = GAOKUN_UCSI_WRITE_SIZE;
> + memcpy(_req + INPUT_DATA_OFFSET, req, GAOKUN_UCSI_WRITE_SIZE);
> +
> + return gaokun_ec_write(ec, _req);
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_ucsi_write);
> +
> +int gaokun_ec_ucsi_get_reg(struct gaokun_ec *ec, u8 *ureg)
> +{
> + u8 req[REQ_HDR_SIZE] = {0x03, 0xD3, 0};
> + u8 _resp[RESP_HDR_SIZE + UCSI_REG_SIZE];
> + int ret;
> +
> + ret = gaokun_ec_read(ec, req, sizeof(_resp), _resp);
> + if (ret)
> + return ret;
> +
> + memcpy(ureg, _resp + DATA_OFFSET, UCSI_REG_SIZE);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_ucsi_get_reg);
> +
> +int gaokun_ec_ucsi_pan_ack(struct gaokun_ec *ec, int port_id)
> +{
> + u8 req[REQ_HDR_SIZE + 1] = {0x03, 0xD2, 1, 0};
> +
> + if (port_id >= 0)
> + req[INPUT_DATA_OFFSET] = 1 << port_id;
> +
> + return gaokun_ec_write(ec, req);
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_ucsi_pan_ack);
> +
> +/* -------------------------------------------------------------------------- */
> +/* Modern Standby */
> +
> +static int gaokun_ec_suspend(struct device *dev)
> +{
> + struct gaokun_ec *ec = dev_get_drvdata(dev);
> + u8 req[REQ_HDR_SIZE + 1] = {0x02, 0xB2, 1, 0xDB};
> + int ret;
> +
> + if (ec->suspended)
> + return 0;
> +
> + ret = gaokun_ec_write(ec, req);
> +
> + if (ret)
> + return ret;
> +
> + ec->suspended = true;
> +
> + return 0;
> +}
> +
> +static int gaokun_ec_resume(struct device *dev)
> +{
> + struct gaokun_ec *ec = dev_get_drvdata(dev);
> + u8 req[REQ_HDR_SIZE + 1] = {0x02, 0xB2, 1, 0xEB};
> + int ret;
> + int i;
> +
> + if (!ec->suspended)
> + return 0;
> +
> + for (i = 0; i < 3; i++) {
> + ret = gaokun_ec_write(ec, req);
> + if (ret == 0)
> + break;
> +
> + msleep(100);
> + };
Write three times with a 100 millisecond sleep ?
Deserves a comment at least.
> +
> + ec->suspended = false;
> +
> + return 0;
> +}
> +
> +static void gaokun_aux_release(struct device *dev)
> +{
> + struct auxiliary_device *adev = to_auxiliary_dev(dev);
> +
> + kfree(adev);
> +}
> +
> +static void gaokun_aux_remove(void *data)
> +{
> + struct auxiliary_device *adev = data;
> +
> + auxiliary_device_delete(adev);
> + auxiliary_device_uninit(adev);
> +}
> +
> +static int gaokun_aux_init(struct device *parent, const char *name,
> + struct gaokun_ec *ec)
> +{
> + struct auxiliary_device *adev;
> + int ret;
> +
> + adev = kzalloc(sizeof(*adev), GFP_KERNEL);
> + if (!adev)
> + return -ENOMEM;
> +
> + adev->name = name;
> + adev->id = 0;
> + adev->dev.parent = parent;
> + adev->dev.release = gaokun_aux_release;
> + adev->dev.platform_data = ec;
> + /* Allow aux devices to access parent's DT nodes directly */
> + device_set_of_node_from_dev(&adev->dev, parent);
> +
> + ret = auxiliary_device_init(adev);
> + if (ret) {
> + kfree(adev);
> + return ret;
> + }
> +
> + ret = auxiliary_device_add(adev);
> + if (ret) {
> + auxiliary_device_uninit(adev);
> + return ret;
> + }
> +
> + return devm_add_action_or_reset(parent, gaokun_aux_remove, adev);
> +}
> +
> +/* -------------------------------------------------------------------------- */
> +/* EC */
> +
> +static irqreturn_t gaokun_ec_irq_handler(int irq, void *data)
> +{
> + struct gaokun_ec *ec = data;
> + u8 req[REQ_HDR_SIZE] = {EC_EVENT, EC_QUERY, 0};
> + u8 status, id;
> + int ret;
> +
> + ret = gaokun_ec_read_byte(ec, req, &id);
> + if (ret)
> + return IRQ_HANDLED;
> +
> + switch (id) {
> + case 0x0: /* No event */
> + break;
> +
> + case EC_EVENT_LID:
> + gaokun_ec_psy_read_byte(ec, EC_LID_STATE, &status);
> + status = EC_LID_OPEN & status;
> + input_report_switch(ec->idev, SW_LID, !status);
> + input_sync(ec->idev);
> + break;
> +
> + default:
> + blocking_notifier_call_chain(&ec->notifier_list, id, ec);
> + }
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int gaokun_ec_probe(struct i2c_client *client)
> +{
> + struct device *dev = &client->dev;
> + struct gaokun_ec *ec;
> + int ret;
> +
> + ec = devm_kzalloc(dev, sizeof(*ec), GFP_KERNEL);
> + if (!ec)
> + return -ENOMEM;
> +
> + mutex_init(&ec->lock);
> + ec->client = client;
> + i2c_set_clientdata(client, ec);
> + BLOCKING_INIT_NOTIFIER_HEAD(&ec->notifier_list);
> +
> + /* Lid switch */
> + ec->idev = devm_input_allocate_device(dev);
> + if (!ec->idev)
> + return -ENOMEM;
> +
> + ec->idev->name = "LID";
> + ec->idev->phys = "gaokun-ec/input0";
> + input_set_capability(ec->idev, EV_SW, SW_LID);
> +
> + ret = input_register_device(ec->idev);
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to register input device\n");
> +
> + ret = gaokun_aux_init(dev, "psy", ec);
> + if (ret)
> + return ret;
> +
> + ret = gaokun_aux_init(dev, "wmi", ec);
> + if (ret)
> + return ret;
> +
> + ret = gaokun_aux_init(dev, "ucsi", ec);
> + if (ret)
> + return ret;
> +
> + ret = devm_request_threaded_irq(dev, client->irq, NULL,
> + gaokun_ec_irq_handler, IRQF_ONESHOT,
> + dev_name(dev), ec);
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to request irq\n");
> +
> + return 0;
> +}
> +
> +static const struct i2c_device_id gaokun_ec_id[] = {
> + { "gaokun-ec", },
> + { }
> +};
> +MODULE_DEVICE_TABLE(i2c, gaokun_ec_id);
> +
> +static const struct of_device_id gaokun_ec_of_match[] = {
> + { .compatible = "huawei,gaokun-ec", },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, gaokun_ec_of_match);
> +
> +static const struct dev_pm_ops gaokun_ec_pm_ops = {
> + NOIRQ_SYSTEM_SLEEP_PM_OPS(gaokun_ec_suspend, gaokun_ec_resume)
> +};
> +
> +static struct i2c_driver gaokun_ec_driver = {
> + .driver = {
> + .name = "gaokun-ec",
> + .of_match_table = gaokun_ec_of_match,
> + .pm = &gaokun_ec_pm_ops,
> + },
> + .probe = gaokun_ec_probe,
> + .id_table = gaokun_ec_id,
> +};
> +module_i2c_driver(gaokun_ec_driver);
> +
> +MODULE_DESCRIPTION("HUAWEI Matebook E Go EC driver");
> +MODULE_AUTHOR("Pengyu Luo <mitltlatltl@gmail.com>");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/platform/arm64/huawei-gaokun-wmi.c b/drivers/platform/arm64/huawei-gaokun-wmi.c
> new file mode 100644
> index 000000000..793cb1659
> --- /dev/null
> +++ b/drivers/platform/arm64/huawei-gaokun-wmi.c
> @@ -0,0 +1,283 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * huawei-gaokun-wmi - A WMI driver for HUAWEI Matebook E Go (sc8280xp)
> + *
> + * reference: drivers/platform/x86/huawei-wmi.c
> + *
> + * Copyright (C) 2024 Pengyu Luo <mitltlatltl@gmail.com>
> + */
> +
> +#include <linux/auxiliary_bus.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/sysfs.h>
> +#include <linux/version.h>
> +
> +#include <linux/platform_data/huawei-gaokun-ec.h>
> +
> +struct gaokun_wmi {
> + struct gaokun_ec *ec;
> + struct device *dev;
> + struct platform_device *wmi;
> +};
> +
> +/* -------------------------------------------------------------------------- */
> +/* Battery charging threshold */
> +
> +enum gaokun_wmi_threshold_ind {
> + START = 1,
> + END = 2,
> +};
> +
> +static ssize_t charge_control_thresholds_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + int ret;
> + u8 start, end;
> + struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
> +
> + ret = gaokun_ec_wmi_get_threshold(ecwmi->ec, &start, START)
> + || gaokun_ec_wmi_get_threshold(ecwmi->ec, &end, END);
> + if (ret)
> + return ret;
ick ouch.
Please call these two functions and evaluate their result codes
individually.
> +
> + return sysfs_emit(buf, "%d %d\n", start, end);
> +}
> +
> +static ssize_t charge_control_thresholds_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t size)
> +{
> + int ret;
> + u8 start, end;
> + struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
> +
> + if (sscanf(buf, "%hhd %hhd", &start, &end) != 2)
> + return -EINVAL;
> +
> + ret = gaokun_ec_wmi_set_threshold(ecwmi->ec, start, end);
> + if (ret)
> + return ret;
> +
> + return size;
> +}
> +
> +static DEVICE_ATTR_RW(charge_control_thresholds);
> +
> +/* -------------------------------------------------------------------------- */
> +/* Smart charge param */
> +
> +static ssize_t smart_charge_param_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + int ret;
> + u8 value;
> + struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
> +
> + ret = gaokun_ec_wmi_get_smart_charge_param(ecwmi->ec, &value);
> + if (ret)
> + return ret;
> +
> + return sysfs_emit(buf, "%d\n", value);
> +}
> +
> +static ssize_t smart_charge_param_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t size)
> +{
> + int ret;
> + u8 value;
> + struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
> +
> + if (kstrtou8(buf, 10, &value))
> + return -EINVAL;
> +
> + ret = gaokun_ec_wmi_set_smart_charge_param(ecwmi->ec, value);
> + if (ret)
> + return ret;
> +
> + return size;
> +}
> +
> +static DEVICE_ATTR_RW(smart_charge_param);
> +
> +/* -------------------------------------------------------------------------- */
> +/* Smart charge */
> +
> +static ssize_t smart_charge_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + int ret;
> + u8 bf[GAOKUN_SMART_CHARGE_DATA_SIZE];
> + struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
> +
> + ret = gaokun_ec_wmi_get_smart_charge(ecwmi->ec, bf);
> + if (ret)
> + return ret;
> +
> + return sysfs_emit(buf, "%d %d %d %d\n",
> + bf[0], bf[1], bf[2], bf[3]);
> +}
> +
> +static ssize_t smart_charge_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t size)
> +{
> + int ret;
> + u8 bf[GAOKUN_SMART_CHARGE_DATA_SIZE];
> + struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
> +
> + if (sscanf(buf, "%hhd %hhd %hhd %hhd", bf, bf + 1, bf + 2, bf + 3) != 4)
> + return -EINVAL;
> +
> + ret = gaokun_ec_wmi_set_smart_charge(ecwmi->ec, bf);
> + if (ret)
> + return ret;
> +
> + return size;
> +}
> +
> +static DEVICE_ATTR_RW(smart_charge);
> +
> +/* -------------------------------------------------------------------------- */
> +/* Fn lock */
> +
> +static ssize_t fn_lock_state_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + int ret;
> + u8 on;
> + struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
> +
> + ret = gaokun_ec_wmi_get_fn_lock(ecwmi->ec, &on);
> + if (ret)
> + return ret;
> +
> + return sysfs_emit(buf, "%d\n", on);
> +}
> +
> +static ssize_t fn_lock_state_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t size)
> +{
> + int ret;
> + u8 on;
> + struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
> +
> + if (kstrtou8(buf, 10, &on))
> + return -EINVAL;
> +
> + ret = gaokun_ec_wmi_set_fn_lock(ecwmi->ec, on);
I mentioned already you should pass on as a bool and then decide here in
the input function if "on" as passed is a reasonable boolean state.
> + if (ret)
> + return ret;
> +
> + return size;
> +}
> +
> +static DEVICE_ATTR_RW(fn_lock_state);
> +
> +/* -------------------------------------------------------------------------- */
> +/* Thermal Zone */
> +
> +static ssize_t temperature_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> +
> + int ret, len, i;
> + char *ptr = buf;
> + s16 value;
> + s16 temp[GAOKUN_TZ_REG_NUM];
> + struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
> +
> + ret = gaokun_ec_wmi_get_temp(ecwmi->ec, temp);
> + if (ret)
> + return ret;
> +
> + i = 0;
> + len = 0;
> + while (i < GAOKUN_TZ_REG_NUM) {
> + value = temp[i++];
> + if (value < 0) {
> + len += sprintf(ptr + len, "-");
Have you ever seen a negative temperature value in running silicon ?
Looks suspicious to me.
> + value = -value;
> + }
> + len += sprintf(ptr + len, "%d.%d ", value / 10, value % 10);
> + }
> + len += sprintf(ptr + len, "\n");
> +
> + return len;
> +}
> +
> +static DEVICE_ATTR_RO(temperature);
> +
> +static struct attribute *gaokun_wmi_features_attrs[] = {
> + &dev_attr_charge_control_thresholds.attr,
> + &dev_attr_smart_charge_param.attr,
> + &dev_attr_smart_charge.attr,
> + &dev_attr_fn_lock_state.attr,
> + &dev_attr_temperature.attr,
> + NULL,
> +};
> +ATTRIBUTE_GROUPS(gaokun_wmi_features);
> +
> +static int gaokun_wmi_probe(struct auxiliary_device *adev,
> + const struct auxiliary_device_id *id)
> +{
> + struct gaokun_ec *ec = adev->dev.platform_data;
> + struct device *dev = &adev->dev;
> + struct gaokun_wmi *ecwmi;
> +
> + ecwmi = devm_kzalloc(&adev->dev, sizeof(*ecwmi), GFP_KERNEL);
> + if (!ecwmi)
> + return -ENOMEM;
> +
> + ecwmi->ec = ec;
> + ecwmi->dev = dev;
> +
> + auxiliary_set_drvdata(adev, ecwmi);
> +
> + /* make it under /sys/devices/platform, convenient for sysfs I/O,
> + * while adev is under
> + * /sys/devices/platform/soc@0/ac0000.geniqup/a9c000.i2c/i2c-15/15-0038/
> + */
> + ecwmi->wmi = platform_device_register_simple("gaokun-wmi", -1, NULL, 0);
> + if (IS_ERR(ecwmi->wmi))
> + return dev_err_probe(dev, PTR_ERR(ecwmi->wmi),
> + "Failed to register wmi platform device\n");
> +
> + platform_set_drvdata(ecwmi->wmi, ecwmi);
> +
> + return device_add_groups(&ecwmi->wmi->dev, gaokun_wmi_features_groups);
> +}
> +
> +static void gaokun_wmi_remove(struct auxiliary_device *adev)
> +{
> + struct gaokun_wmi *ecwmi = auxiliary_get_drvdata(adev);
> + struct platform_device *wmi = ecwmi->wmi;
> +
> + device_remove_groups(&wmi->dev, gaokun_wmi_features_groups);
> + platform_device_unregister(ecwmi->wmi);
> +}
> +
> +static const struct auxiliary_device_id gaokun_wmi_id_table[] = {
> + { .name = GAOKUN_MOD_NAME "." GAOKUN_DEV_WMI, },
> + {}
> +};
> +MODULE_DEVICE_TABLE(auxiliary, gaokun_wmi_id_table);
> +
> +static struct auxiliary_driver gaokun_wmi_driver = {
> + .name = GAOKUN_DEV_WMI,
> + .id_table = gaokun_wmi_id_table,
> + .probe = gaokun_wmi_probe,
> + .remove = gaokun_wmi_remove,
> +};
> +
> +module_auxiliary_driver(gaokun_wmi_driver);
> +
> +MODULE_DESCRIPTION("HUAWEI Matebook E Go WMI driver");
> +MODULE_LICENSE("GPL");
> diff --git a/include/linux/platform_data/huawei-gaokun-ec.h b/include/linux/platform_data/huawei-gaokun-ec.h
> new file mode 100644
> index 000000000..a649e9ecf
> --- /dev/null
> +++ b/include/linux/platform_data/huawei-gaokun-ec.h
> @@ -0,0 +1,90 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/* Huawei Matebook E Go (sc8280xp) Embedded Controller
> + *
> + * Copyright (C) 2024 Pengyu Luo <mitltlatltl@gmail.com>
> + *
> + */
> +
> +#ifndef __HUAWEI_GAOKUN_EC_H__
> +#define __HUAWEI_GAOKUN_EC_H__
> +
> +#define GAOKUN_UCSI_CCI_SIZE 4
> +#define GAOKUN_UCSI_DATA_SIZE 16
> +#define GAOKUN_UCSI_READ_SIZE (GAOKUN_UCSI_CCI_SIZE + GAOKUN_UCSI_DATA_SIZE)
> +#define GAOKUN_UCSI_WRITE_SIZE 0x18
> +
> +#define GAOKUN_TZ_REG_NUM 20
> +#define GAOKUN_SMART_CHARGE_DATA_SIZE 4 /* mode, delay, start, end */
> +
> +/* -------------------------------------------------------------------------- */
> +
> +struct gaokun_ec;
> +struct notifier_block;
> +
> +#define GAOKUN_MOD_NAME "huawei_gaokun_ec"
> +#define GAOKUN_DEV_PSY "psy"
> +#define GAOKUN_DEV_WMI "wmi"
> +#define GAOKUN_DEV_UCSI "ucsi"
> +
> +/* -------------------------------------------------------------------------- */
> +/* Common API */
> +
> +int gaokun_ec_register_notify(struct gaokun_ec *ec,
> + struct notifier_block *nb);
> +void gaokun_ec_unregister_notify(struct gaokun_ec *ec,
> + struct notifier_block *nb);
> +
> +int gaokun_ec_read(struct gaokun_ec *ec, const u8 *req,
> + size_t resp_len, u8 *resp);
> +int gaokun_ec_write(struct gaokun_ec *ec, u8 *req);
> +int gaokun_ec_read_byte(struct gaokun_ec *ec, u8 *req, u8 *byte);
> +
> +/* -------------------------------------------------------------------------- */
> +/* API For PSY */
> +
> +int gaokun_ec_psy_multi_read(struct gaokun_ec *ec, u8 reg,
> + size_t resp_len, u8 *resp);
> +
> +static inline int gaokun_ec_psy_read_byte(struct gaokun_ec *ec,
> + u8 reg, u8 *byte)
> +{
> + return gaokun_ec_psy_multi_read(ec, reg, 1, byte);
> +}
> +
> +static inline int gaokun_ec_psy_read_word(struct gaokun_ec *ec,
> + u8 reg, u16 *word)
> +{
> + return gaokun_ec_psy_multi_read(ec, reg, 2, (u8 *)word);
> +}
> +
> +/* -------------------------------------------------------------------------- */
> +/* API For WMI */
> +
> +int gaokun_ec_wmi_get_threshold(struct gaokun_ec *ec, u8 *value, int ind);
> +int gaokun_ec_wmi_set_threshold(struct gaokun_ec *ec, u8 start, u8 end);
> +
> +int gaokun_ec_wmi_get_smart_charge_param(struct gaokun_ec *ec, u8 *value);
> +int gaokun_ec_wmi_set_smart_charge_param(struct gaokun_ec *ec, u8 value);
> +
> +int gaokun_ec_wmi_get_smart_charge(struct gaokun_ec *ec,
> + u8 data[GAOKUN_SMART_CHARGE_DATA_SIZE]);
> +int gaokun_ec_wmi_set_smart_charge(struct gaokun_ec *ec,
> + u8 data[GAOKUN_SMART_CHARGE_DATA_SIZE]);
> +
> +int gaokun_ec_wmi_get_fn_lock(struct gaokun_ec *ec, u8 *on);
> +int gaokun_ec_wmi_set_fn_lock(struct gaokun_ec *ec, u8 on);
> +
> +int gaokun_ec_wmi_get_temp(struct gaokun_ec *ec, s16 temp[GAOKUN_TZ_REG_NUM]);
> +
> +/* -------------------------------------------------------------------------- */
> +/* API For UCSI */
> +
> +int gaokun_ec_ucsi_read(struct gaokun_ec *ec, u8 resp[GAOKUN_UCSI_READ_SIZE]);
> +int gaokun_ec_ucsi_write(struct gaokun_ec *ec,
> + const u8 req[GAOKUN_UCSI_WRITE_SIZE]);
> +
> +int gaokun_ec_ucsi_get_reg(struct gaokun_ec *ec, u8 *ureg);
> +int gaokun_ec_ucsi_pan_ack(struct gaokun_ec *ec, int port_id);
> +
> +
> +#endif /* __HUAWEI_GAOKUN_EC_H__ */
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 3/5] usb: typec: ucsi: add Huawei Matebook E Go (sc8280xp) ucsi driver
2024-12-27 17:13 ` [PATCH 3/5] usb: typec: ucsi: add Huawei Matebook E Go (sc8280xp) ucsi driver Pengyu Luo
@ 2024-12-28 13:06 ` Bryan O'Donoghue
2024-12-28 14:38 ` Pengyu Luo
2024-12-29 4:40 ` Dmitry Baryshkov
2024-12-29 16:15 ` Markus Elfring
2 siblings, 1 reply; 51+ messages in thread
From: Bryan O'Donoghue @ 2024-12-28 13:06 UTC (permalink / raw)
To: Pengyu Luo, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Bjorn Andersson, Konrad Dybcio, Hans de Goede, Ilpo Järvinen,
Sebastian Reichel, Heikki Krogerus, Greg Kroah-Hartman
Cc: devicetree, linux-kernel, linux-arm-msm, platform-driver-x86,
linux-pm, linux-usb, Dmitry Baryshkov, Nikita Travkin
On 27/12/2024 17:13, Pengyu Luo wrote:
> The Huawei Matebook E Go (sc8280xp) tablet provides implements UCSI
> interface in the onboard EC. Add the glue driver to interface the
> platform's UCSI implementation.
>
> Signed-off-by: Pengyu Luo <mitltlatltl@gmail.com>
> ---
> drivers/usb/typec/ucsi/Kconfig | 9 +
> drivers/usb/typec/ucsi/Makefile | 1 +
> drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c | 481 ++++++++++++++++++++
> 3 files changed, 491 insertions(+)
> create mode 100644 drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c
>
> diff --git a/drivers/usb/typec/ucsi/Kconfig b/drivers/usb/typec/ucsi/Kconfig
> index 680e1b87b..0d0f07488 100644
> --- a/drivers/usb/typec/ucsi/Kconfig
> +++ b/drivers/usb/typec/ucsi/Kconfig
> @@ -78,4 +78,13 @@ config UCSI_LENOVO_YOGA_C630
> To compile the driver as a module, choose M here: the module will be
> called ucsi_yoga_c630.
>
> +config UCSI_HUAWEI_GAOKUN
> + tristate "UCSI Interface Driver for Huawei Matebook E Go (sc8280xp)"
> + depends on EC_HUAWEI_GAOKUN
> + help
> + This driver enables UCSI support on the Huawei Matebook E Go tablet.
> +
> + To compile the driver as a module, choose M here: the module will be
> + called ucsi_huawei_gaokun.
> +
> endif
> diff --git a/drivers/usb/typec/ucsi/Makefile b/drivers/usb/typec/ucsi/Makefile
> index aed41d238..0b400122b 100644
> --- a/drivers/usb/typec/ucsi/Makefile
> +++ b/drivers/usb/typec/ucsi/Makefile
> @@ -22,3 +22,4 @@ obj-$(CONFIG_UCSI_CCG) += ucsi_ccg.o
> obj-$(CONFIG_UCSI_STM32G0) += ucsi_stm32g0.o
> obj-$(CONFIG_UCSI_PMIC_GLINK) += ucsi_glink.o
> obj-$(CONFIG_UCSI_LENOVO_YOGA_C630) += ucsi_yoga_c630.o
> +obj-$(CONFIG_UCSI_HUAWEI_GAOKUN) += ucsi_huawei_gaokun.o
> diff --git a/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c
> new file mode 100644
> index 000000000..84ed0407d
> --- /dev/null
> +++ b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c
> @@ -0,0 +1,481 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * ucsi-huawei-gaokun - A UCSI driver for HUAWEI Matebook E Go (sc8280xp)
> + *
> + * reference: drivers/usb/typec/ucsi/ucsi_yoga_c630.c
> + * drivers/usb/typec/ucsi/ucsi_glink.c
> + * drivers/soc/qcom/pmic_glink_altmode.c
> + *
> + * Copyright (C) 2024 Pengyu Luo <mitltlatltl@gmail.com>
> + */
> +
> +#include <linux/auxiliary_bus.h>
> +#include <linux/bitops.h>
> +#include <linux/completion.h>
> +#include <linux/container_of.h>
> +#include <linux/delay.h>
> +#include <linux/module.h>
> +#include <linux/notifier.h>
> +#include <linux/of.h>
> +#include <linux/string.h>
> +#include <linux/workqueue_types.h>
> +
> +#include <linux/usb/pd_vdo.h>
> +#include <drm/bridge/aux-bridge.h
Is there a reason you don't have strict include alphanumeric ordering here ?
>
> +
> +#include "ucsi.h"
> +#include <linux/platform_data/huawei-gaokun-ec.h>
> +
> +
> +#define EC_EVENT_UCSI 0x21
> +#define EC_EVENT_USB 0x22
> +
> +#define GAOKUN_CCX_MASK GENMASK(1, 0)
> +#define GAOKUN_MUX_MASK GENMASK(3, 2)
> +
> +#define GAOKUN_DPAM_MASK GENMASK(3, 0)
> +#define GAOKUN_HPD_STATE_MASK BIT(4)
> +#define GAOKUN_HPD_IRQ_MASK BIT(5)
> +
> +#define CCX_TO_ORI(ccx) (++ccx % 3)
Why do you increment the value of the enum ?
Seems strange.
> +
> +#define GET_IDX(updt) (ffs(updt) - 1)
> +
> +/* Configuration Channel Extension */
> +enum gaokun_ucsi_ccx {
> + USBC_CCX_NORMAL,
> + USBC_CCX_REVERSE,
> + USBC_CCX_NONE,
> +};
> +
> +enum gaokun_ucsi_mux {
> + USBC_MUX_NONE,
> + USBC_MUX_USB_2L,
> + USBC_MUX_DP_4L,
> + USBC_MUX_USB_DP,
> +};
> +
> +struct gaokun_ucsi_reg {
> + u8 port_num;
> + u8 port_updt;
> + u8 port_data[4];
> + u8 checksum;
> + u8 reserved;
> +} __packed;
> +
> +struct gaokun_ucsi_port {
> + struct completion usb_ack;
> + spinlock_t lock;
> +
> + struct gaokun_ucsi *ucsi;
> + struct auxiliary_device *bridge;
> +
> + int idx;
> + enum gaokun_ucsi_ccx ccx;
> + enum gaokun_ucsi_mux mux;
> + u8 mode;
> + u16 svid;
> + u8 hpd_state;
> + u8 hpd_irq;
> +};
> +
> +struct gaokun_ucsi {
> + struct gaokun_ec *ec;
> + struct ucsi *ucsi;
> + struct gaokun_ucsi_port *ports;
> + struct device *dev;
> + struct work_struct work;
> + struct notifier_block nb;
> + u16 version;
> + u8 port_num;
> +};
> +
> +/* -------------------------------------------------------------------------- */
> +/* For UCSI */
> +
> +static int gaokun_ucsi_read_version(struct ucsi *ucsi, u16 *version)
> +{
> + struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi);
> +
> + *version = uec->version;
> +
> + return 0;
> +}
> +
> +static int gaokun_ucsi_read_cci(struct ucsi *ucsi, u32 *cci)
> +{
> + struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi);
> + u8 buf[GAOKUN_UCSI_READ_SIZE];
> + int ret;
> +
> + ret = gaokun_ec_ucsi_read(uec->ec, buf);
> + if (ret)
> + return ret;
> +
> + memcpy(cci, buf, sizeof(*cci));
> +
> + return 0;
> +}
> +
> +static int gaokun_ucsi_read_message_in(struct ucsi *ucsi,
> + void *val, size_t val_len)
> +{
> + struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi);
> + u8 buf[GAOKUN_UCSI_READ_SIZE];
> + int ret;
> +
> + ret = gaokun_ec_ucsi_read(uec->ec, buf);
> + if (ret)
> + return ret;
> +
> + memcpy(val, buf + GAOKUN_UCSI_CCI_SIZE,
> + min(val_len, GAOKUN_UCSI_DATA_SIZE));
> +
> + return 0;
> +}
> +
> +static int gaokun_ucsi_async_control(struct ucsi *ucsi, u64 command)
> +{
> + struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi);
> + u8 buf[GAOKUN_UCSI_WRITE_SIZE] = {};
> +
> + memcpy(buf, &command, sizeof(command));
> +
> + return gaokun_ec_ucsi_write(uec->ec, buf);
> +}
> +
> +static void gaokun_ucsi_update_connector(struct ucsi_connector *con)
> +{
> + struct gaokun_ucsi *uec = ucsi_get_drvdata(con->ucsi);
> +
> + if (con->num > uec->port_num)
> + return;
> +
> + con->typec_cap.orientation_aware = true;
> +}
> +
> +static void gaokun_set_orientation(struct ucsi_connector *con,
> + struct gaokun_ucsi_port *port)
> +{
> + enum gaokun_ucsi_ccx ccx;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&port->lock, flags);
> + ccx = port->ccx;
> + spin_unlock_irqrestore(&port->lock, flags);
> +
> + typec_set_orientation(con->port, CCX_TO_ORI(ccx));
> +}
> +
> +static void gaokun_ucsi_connector_status(struct ucsi_connector *con)
> +{
> + struct gaokun_ucsi *uec = ucsi_get_drvdata(con->ucsi);
> + int idx;
> +
> + idx = con->num - 1;
> + if (con->num > uec->port_num) {
> + dev_warn(uec->ucsi->dev, "set orientation out of range: con%d\n", idx);
> + return;
> + }
> +
> + gaokun_set_orientation(con, &uec->ports[idx]);
> +}
> +
> +const struct ucsi_operations gaokun_ucsi_ops = {
> + .read_version = gaokun_ucsi_read_version,
> + .read_cci = gaokun_ucsi_read_cci,
> + .read_message_in = gaokun_ucsi_read_message_in,
> + .sync_control = ucsi_sync_control_common,
> + .async_control = gaokun_ucsi_async_control,
> + .update_connector = gaokun_ucsi_update_connector,
> + .connector_status = gaokun_ucsi_connector_status,
> +};
> +
> +/* -------------------------------------------------------------------------- */
> +/* For Altmode */
> +
> +static void gaokun_ucsi_port_update(struct gaokun_ucsi_port *port,
> + const u8 *port_data)
> +{
> + unsigned long flags;
> + u8 dcc, ddi;
> + int offset = port->idx * 2; /* every port has 2 Bytes data */
> +
> + dcc = port_data[offset];
> + ddi = port_data[offset + 1];
> +
> + spin_lock_irqsave(&port->lock, flags);
> +
> + port->ccx = FIELD_GET(GAOKUN_CCX_MASK, dcc);
> + port->mux = FIELD_GET(GAOKUN_MUX_MASK, dcc);
> + port->mode = FIELD_GET(GAOKUN_DPAM_MASK, ddi);
> + port->hpd_state = FIELD_GET(GAOKUN_HPD_STATE_MASK, ddi);
> + port->hpd_irq = FIELD_GET(GAOKUN_HPD_IRQ_MASK, ddi);
> +
> + switch (port->mux) {
> + case USBC_MUX_NONE:
> + port->svid = 0;
> + break;
> + case USBC_MUX_USB_2L:
> + port->svid = USB_SID_PD;
> + break;
> + case USBC_MUX_DP_4L:
> + case USBC_MUX_USB_DP:
> + port->svid = USB_SID_DISPLAYPORT;
> + if (port->ccx == USBC_CCX_REVERSE)
> + port->mode -= 6;
why minus six ?
needs a comment.
> + break;
> + default:
> + break;
> + }
> +
> + spin_unlock_irqrestore(&port->lock, flags);
> +}
> +
> +static int gaokun_ucsi_refresh(struct gaokun_ucsi *uec)
> +{
> + struct gaokun_ucsi_reg ureg;
> + int ret, idx;
> +
> + ret = gaokun_ec_ucsi_get_reg(uec->ec, (u8 *)&ureg);
> + if (ret)
> + return -EIO;
> +
> + uec->port_num = ureg.port_num;
> + idx = GET_IDX(ureg.port_updt);
> +
> + if (idx >= 0 && idx < ureg.port_num)
> + gaokun_ucsi_port_update(&uec->ports[idx], ureg.port_data);
Since you are checking the validity of the index, you should -EINVAL if
the index is out of range.
> +
> + return idx;
> +}
> +
> +static void gaokun_ucsi_handle_altmode(struct gaokun_ucsi_port *port)
> +{
> + struct gaokun_ucsi *uec = port->ucsi;
> + int idx = port->idx;
> +
> + if (idx >= uec->ucsi->cap.num_connectors || !uec->ucsi->connector) {
> + dev_warn(uec->ucsi->dev, "altmode port out of range: %d\n", idx);
> + return;
> + }
> +
> + /* UCSI callback .connector_status() have set orientation */
> + if (port->bridge)
> + drm_aux_hpd_bridge_notify(&port->bridge->dev,
> + port->hpd_state ?
> + connector_status_connected :
> + connector_status_disconnected);
> +
> + gaokun_ec_ucsi_pan_ack(uec->ec, port->idx);
> +}
> +
> +static void gaokun_ucsi_altmode_notify_ind(struct gaokun_ucsi *uec)
> +{
> + int idx;
> +
> + idx = gaokun_ucsi_refresh(uec);
> + if (idx < 0)
> + gaokun_ec_ucsi_pan_ack(uec->ec, idx);
> + else
> + gaokun_ucsi_handle_altmode(&uec->ports[idx]);
> +}
> +
> +/*
> + * USB event is necessary for enabling altmode, the event should follow
> + * UCSI event, if not after timeout(this notify may be disabled somehow),
> + * then force to enable altmode.
> + */
> +static void gaokun_ucsi_handle_no_usb_event(struct gaokun_ucsi *uec, int idx)
> +{
> + struct gaokun_ucsi_port *port;
> +
> + port = &uec->ports[idx];
> + if (!wait_for_completion_timeout(&port->usb_ack, 2 * HZ)) {
> + dev_warn(uec->dev, "No USB EVENT, triggered by UCSI EVENT");
> + gaokun_ucsi_altmode_notify_ind(uec);
> + }
> +}
> +
> +static int gaokun_ucsi_notify(struct notifier_block *nb,
> + unsigned long action, void *data)
> +{
> + u32 cci;
> + struct gaokun_ucsi *uec = container_of(nb, struct gaokun_ucsi, nb);
> +
> + switch (action) {
> + case EC_EVENT_USB:
> + gaokun_ucsi_altmode_notify_ind(uec);
> + return NOTIFY_OK;
> +
> + case EC_EVENT_UCSI:
> + uec->ucsi->ops->read_cci(uec->ucsi, &cci);
> + ucsi_notify_common(uec->ucsi, cci);
> + if (UCSI_CCI_CONNECTOR(cci))
> + gaokun_ucsi_handle_no_usb_event(uec, UCSI_CCI_CONNECTOR(cci) - 1);
> +
> + return NOTIFY_OK;
> +
> + default:
> + return NOTIFY_DONE;
> + }
> +}
> +
> +static int gaokun_ucsi_get_port_num(struct gaokun_ucsi *uec)
> +{
> + struct gaokun_ucsi_reg ureg;
> + int ret;
> +
> + ret = gaokun_ec_ucsi_get_reg(uec->ec, (u8 *)&ureg);
> +
> + return ret ? 0 : ureg.port_num;
> +}
> +
> +static int gaokun_ucsi_ports_init(struct gaokun_ucsi *uec)
> +{
> + u32 port;
> + int i, ret, port_num;
> + struct device *dev = uec->dev;
> + struct gaokun_ucsi_port *ucsi_port;
> + struct fwnode_handle *fwnode;
> +
> + port_num = gaokun_ucsi_get_port_num(uec);
> + uec->port_num = port_num;
> +
> + uec->ports = devm_kzalloc(dev, port_num * sizeof(*(uec->ports)),
> + GFP_KERNEL);
> + if (!uec->ports)
> + return -ENOMEM;
> +
> + for (i = 0; i < port_num; ++i) {
> + ucsi_port = &uec->ports[i];
> + ucsi_port->ccx = USBC_CCX_NONE;
> + ucsi_port->idx = i;
> + ucsi_port->ucsi = uec;
> + init_completion(&ucsi_port->usb_ack);
> + spin_lock_init(&ucsi_port->lock);
> + }
> +
> + device_for_each_child_node(dev, fwnode) {
> + ret = fwnode_property_read_u32(fwnode, "reg", &port);
> + if (ret < 0) {
> + dev_err(dev, "missing reg property of %pOFn\n", fwnode);
> + fwnode_handle_put(fwnode);
> + return ret;
> + }
> +
> + if (port >= port_num) {
> + dev_warn(dev, "invalid connector number %d, ignoring\n", port);
> + continue;
> + }
> +
> + ucsi_port = &uec->ports[port];
> + ucsi_port->bridge = devm_drm_dp_hpd_bridge_alloc(dev, to_of_node(fwnode));
> + if (IS_ERR(ucsi_port->bridge)) {
> + fwnode_handle_put(fwnode);
> + return PTR_ERR(ucsi_port->bridge);
> + }
> + }
> +
> + for (i = 0; i < port_num; i++) {
> + if (!uec->ports[i].bridge)
> + continue;
> +
> + ret = devm_drm_dp_hpd_bridge_add(dev, uec->ports[i].bridge);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static void gaokun_ucsi_register_worker(struct work_struct *work)
> +{
> + struct gaokun_ucsi *uec;
> + struct ucsi *ucsi;
> + int ret;
> +
> + uec = container_of(work, struct gaokun_ucsi, work);
> + ucsi = uec->ucsi;
> +
> + ucsi->quirks = UCSI_NO_PARTNER_PDOS | UCSI_DELAY_DEVICE_PDOS;
> +
> + ssleep(3); /* EC can't handle UCSI properly in the early stage */
Could you not schedule work for + 3 seconds instead of sleeping here -
representing the required stall time in some sort of state machine ?
3 seconds is an incredibly long time for a computer to sleep.
> +
> + ret = gaokun_ec_register_notify(uec->ec, &uec->nb);
> + if (ret) {
> + dev_err_probe(ucsi->dev, ret, "notifier register failed\n");
> + return;
> + }
> +
> + ret = ucsi_register(ucsi);
> + if (ret)
> + dev_err_probe(ucsi->dev, ret, "ucsi register failed\n");
> +}
> +
> +static int gaokun_ucsi_register(struct gaokun_ucsi *uec)
> +{
> + schedule_work(&uec->work);
> +
> + return 0;
> +}
> +
> +static int gaokun_ucsi_probe(struct auxiliary_device *adev,
> + const struct auxiliary_device_id *id)
> +{
> + struct gaokun_ec *ec = adev->dev.platform_data;
> + struct device *dev = &adev->dev;
> + struct gaokun_ucsi *uec;
> + int ret;
> +
> + uec = devm_kzalloc(dev, sizeof(*uec), GFP_KERNEL);
> + if (!uec)
> + return -ENOMEM;
> +
> + uec->ec = ec;
> + uec->dev = dev;
> + uec->version = 0x0100;
> + uec->nb.notifier_call = gaokun_ucsi_notify;
> +
> + INIT_WORK(&uec->work, gaokun_ucsi_register_worker);
> +
> + ret = gaokun_ucsi_ports_init(uec);
> + if (ret)
> + return ret;
> +
> + uec->ucsi = ucsi_create(dev, &gaokun_ucsi_ops);
> + if (IS_ERR(uec->ucsi))
> + return PTR_ERR(uec->ucsi);
> +
> + ucsi_set_drvdata(uec->ucsi, uec);
> + auxiliary_set_drvdata(adev, uec);
> +
> + return gaokun_ucsi_register(uec);
> +}
> +
> +static void gaokun_ucsi_remove(struct auxiliary_device *adev)
> +{
> + struct gaokun_ucsi *uec = auxiliary_get_drvdata(adev);
> +
> + gaokun_ec_unregister_notify(uec->ec, &uec->nb);
> + ucsi_unregister(uec->ucsi);
> + ucsi_destroy(uec->ucsi);
> +}
> +
> +static const struct auxiliary_device_id gaokun_ucsi_id_table[] = {
> + { .name = GAOKUN_MOD_NAME "." GAOKUN_DEV_UCSI, },
> + {}
> +};
> +MODULE_DEVICE_TABLE(auxiliary, gaokun_ucsi_id_table);
> +
> +static struct auxiliary_driver gaokun_ucsi_driver = {
> + .name = GAOKUN_DEV_UCSI,
> + .id_table = gaokun_ucsi_id_table,
> + .probe = gaokun_ucsi_probe,
> + .remove = gaokun_ucsi_remove,
> +};
> +
> +module_auxiliary_driver(gaokun_ucsi_driver);
> +
> +MODULE_DESCRIPTION("HUAWEI Matebook E Go UCSI driver");
> +MODULE_LICENSE("GPL");
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 2/5] platform: arm64: add Huawei Matebook E Go (sc8280xp) EC driver
2024-12-28 12:33 ` [PATCH 2/5] platform: arm64: add Huawei Matebook E Go (sc8280xp) EC driver Bryan O'Donoghue
@ 2024-12-28 13:51 ` Pengyu Luo
2024-12-29 15:32 ` Ilpo Järvinen
0 siblings, 1 reply; 51+ messages in thread
From: Pengyu Luo @ 2024-12-28 13:51 UTC (permalink / raw)
To: bryan.odonoghue
Cc: andersson, conor+dt, devicetree, dmitry.baryshkov, gregkh,
hdegoede, heikki.krogerus, ilpo.jarvinen, konradybcio, krzk+dt,
linux-arm-msm, linux-kernel, linux-pm, linux-usb, mitltlatltl,
nikita, platform-driver-x86, robh, sre
On Sat, Dec 28, 2024 at 8:33 PM Bryan O'Donoghue <bryan.odonoghue@linaro.org> wrote:
> On 27/12/2024 17:13, Pengyu Luo wrote:
> > There are 3 variants, Huawei released first 2 at the same time.
>
> There are three variants of which Huawei released the first two
> simultaneously.
>
You have mentioned many grammar and code issues.
So I explain something, I taught myself C, writing code is just my hobby.
I am also not a native speaker, I have never lived in an English
environment. Sometime I may use inappropriate words to express. I try my
best to express my idea clearly. I hope you can understand.
> > Huawei Matebook E Go LTE(sc8180x), codename should be gaokun2.
> > Huawei Matebook E Go(sc8280xp@3.0GHz), codename is gaokun3.
>
> Choose either "codename should be" or "codename is"
>
> > Huawei Matebook E Go 2023(sc8280xp@2.69GHz).
>
> Maybe say here "codename unknown"
>
should be, I want to express I am guessing.
I guess the last one is also gaokun3. But anyways, this driver works
for the latter two.
> > Adding support for the latter two variants for now, this driver should
> > also work for the sc8180x variant according to acpi table files, but I
> > don't have the device yet.
> >
> > Different from other Qualcomm Snapdragon sc8280xp based machines, the
> > Huawei Matebook E Go uses an embedded controller while others use
> > something called pmic glink. This embedded controller can be used to
> > perform a set of various functions, including, but not limited:
>
> "but not limited to":
>
> > - Battery and charger monitoring;
> > - Charge control and smart charge;
> > - Fn_lock settings;
> > - Tablet lid status;
> > - Temperature sensors;
> > - USB Type-C notifications (ports orientation, DP alt mode HPD);
> > - USB Type-C PD (according to observation, up to 48w).
> >
> > Add the driver for the EC, that creates devices for UCSI, wmi and power
> > supply devices.
>
> I'm a terrible man for the "," dropped all about the place but you're
> going too mad with the commans there ->
>
> "Add a driver for the EC which creates devices for UCSI, WMI and power
> supply devices"
>
This was copied from C630 patches, replaced with my devices.
> >
> > Signed-off-by: Pengyu Luo <mitltlatltl@gmail.com>
> > ---
> > drivers/platform/arm64/Kconfig | 19 +
> > drivers/platform/arm64/Makefile | 2 +
> > drivers/platform/arm64/huawei-gaokun-ec.c | 598 ++++++++++++++++++
> > drivers/platform/arm64/huawei-gaokun-wmi.c | 283 +++++++++
> > .../linux/platform_data/huawei-gaokun-ec.h | 90 +++
> > 5 files changed, 992 insertions(+)
> > create mode 100644 drivers/platform/arm64/huawei-gaokun-ec.c
> > create mode 100644 drivers/platform/arm64/huawei-gaokun-wmi.c
> > create mode 100644 include/linux/platform_data/huawei-gaokun-ec.h
> >
> > diff --git a/drivers/platform/arm64/Kconfig b/drivers/platform/arm64/Kconfig
> > index f88395ea3..eb7fbacf0 100644
> > --- a/drivers/platform/arm64/Kconfig
> > +++ b/drivers/platform/arm64/Kconfig
> > @@ -33,6 +33,25 @@ config EC_ACER_ASPIRE1
> > laptop where this information is not properly exposed via the
> > standard ACPI devices.
> >
> > +config EC_HUAWEI_GAOKUN
> > + tristate "Huawei Matebook E Go (sc8280xp) Embedded Controller driver"
>
> Krzysztof already mentioned this but the "sc8280xp" is questionable, you
> should probably drop mention of qcom and sc8280xp from your compat and
> tristate here.
>
Agree, how about using sc8280xp-based for tristate?
> > + depends on ARCH_QCOM || COMPILE_TEST
> > + depends on I2C
> > + depends on DRM
> > + depends on POWER_SUPPLY
> > + depends on INPUT
> > + help
> > + Say Y here to enable the EC driver for Huawei Matebook E Go 2in1
> > + tablet. The driver handles battery(information, charge control) and
> > + USB Type-C DP HPD events as well as some misc functions like the lid
> > + sensor and temperature sensors, etc.
> > +
> > + This driver provides battery and AC status support for the mentioned
> > + laptop where this information is not properly exposed via the
> > + standard ACPI devices.
> > +
> > + Say M or Y here to include this support.
>
> OTOH the help text is where you could mention the sc8280xp class of
> laptops/tablets.
>
I see
> > +
> > config EC_LENOVO_YOGA_C630
> > tristate "Lenovo Yoga C630 Embedded Controller driver"
> > depends on ARCH_QCOM || COMPILE_TEST
> > diff --git a/drivers/platform/arm64/Makefile b/drivers/platform/arm64/Makefile
> > index b2ae9114f..ed32ad6c0 100644
> > --- a/drivers/platform/arm64/Makefile
> > +++ b/drivers/platform/arm64/Makefile
> > @@ -6,4 +6,6 @@
> > #
> >
> > obj-$(CONFIG_EC_ACER_ASPIRE1) += acer-aspire1-ec.o
> > +obj-$(CONFIG_EC_HUAWEI_GAOKUN) += huawei-gaokun-ec.o
> > +obj-$(CONFIG_EC_HUAWEI_GAOKUN) += huawei-gaokun-wmi.o
> > obj-$(CONFIG_EC_LENOVO_YOGA_C630) += lenovo-yoga-c630.o
> > diff --git a/drivers/platform/arm64/huawei-gaokun-ec.c b/drivers/platform/arm64/huawei-gaokun-ec.c
> > new file mode 100644
> > index 000000000..c1c657f7b
> > --- /dev/null
> > +++ b/drivers/platform/arm64/huawei-gaokun-ec.c
> > @@ -0,0 +1,598 @@
> > +// SPDX-License-Identifier: GPL-2.0-only
> > +/*
> > + * huawei-gaokun-ec - An EC driver for HUAWEI Matebook E Go (sc8280xp)
> > + *
> > + * reference: drivers/platform/arm64/acer-aspire1-ec.c
> > + * drivers/platform/arm64/lenovo-yoga-c630.c
> > + *
> > + * Copyright (C) 2024 Pengyu Luo <mitltlatltl@gmail.com>
> > + */
> > +
> > +#include <linux/auxiliary_bus.h>
> > +#include <linux/delay.h>
> > +#include <linux/device.h>
> > +#include <linux/i2c.h>
> > +#include <linux/input.h>
> > +#include <linux/notifier.h>
> > +#include <linux/module.h>
> > +#include <linux/mutex.h>
> > +#include <linux/version.h>
> > +
> > +#include <linux/platform_data/huawei-gaokun-ec.h>
> > +
> > +#define EC_EVENT 0x06
> > +
> > +/* Also can be found in ACPI specification 12.3 */
> > +#define EC_READ 0x80
> > +#define EC_WRITE 0x81
>
> Is this odd indentation ?
>
No, apply it then check the source, it is normal.
> > +#define EC_BURST 0x82
> > +#define EC_QUERY 0x84
> > +
> > +
> > +#define EC_EVENT_LID 0x81
> > +
> > +#define EC_LID_STATE 0x80
> > +#define EC_LID_OPEN BIT(1)
> > +
> > +#define UCSI_REG_SIZE 7
> > +
> > +/* for tx, command sequences are arranged as
> > + * {master_cmd, slave_cmd, data_len, data_seq}
> > + */
> > +#define REQ_HDR_SIZE 3
> > +#define INPUT_SIZE_OFFSET 2
> > +#define INPUT_DATA_OFFSET 3
> > +
> > +/* for rx, data sequences are arranged as
> > + * {status, data_len(unreliable), data_seq}
> > + */
> > +#define RESP_HDR_SIZE 2
> > +#define DATA_OFFSET 2
> > +
> > +
> > +struct gaokun_ec {
> > + struct i2c_client *client;
> > + struct mutex lock;
> > + struct blocking_notifier_head notifier_list;
> > + struct input_dev *idev;
> > + bool suspended;
> > +};
> > +
> > +static int gaokun_ec_request(struct gaokun_ec *ec, const u8 *req,
> > + size_t resp_len, u8 *resp)
> > +{
> > + struct i2c_client *client = ec->client;
> > + struct i2c_msg msgs[2] = {
> > + {
> > + .addr = client->addr,
> > + .flags = client->flags,
> > + .len = req[INPUT_SIZE_OFFSET] + REQ_HDR_SIZE,
> > + .buf = req,
> > + }, {
> > + .addr = client->addr,
> > + .flags = client->flags | I2C_M_RD,
> > + .len = resp_len,
> > + .buf = resp,
> > + },
> > + };
> > +
> > + mutex_lock(&ec->lock);
> > +
> > + i2c_transfer(client->adapter, msgs, 2);
> > + usleep_range(2000, 2500);
>
> What is this sleep about and why are you doing it inside of a critical
> section ?
>
Have a break between 2 transaction. This sleep happens in acpi code, also
inside a critical region. I rearrange it. We can sleep when using mutex
lock instead of spinlock.
Local7 = Acquire (\_SB.IC16.MUEC, 0x03E8)
...
read ops
...
Sleep (0x02)
...
write ops
...
Release (\_SB.IC16.MUEC)
> > +
> > + mutex_unlock(&ec->lock);
> > +
> > + return *resp;
> > +}
> > +
> > +/* -------------------------------------------------------------------------- */
> > +/* Common API */
> > +
> > +/**
> > + * gaokun_ec_read - read from EC
> > + * @ec: The gaokun_ec
> > + * @req: The sequence to request
> > + * @resp_len: The size to read
> > + * @resp: Where the data are read to
> > + *
> > + * This function is used to read data after writing a magic sequence to EC.
> > + * All EC operations dependent on this functions.
>
> depend on this
>
mistyping
> > + *
> > + * Huawei uses magic sequences everywhere to complete various functions, all
> > + * these sequences are passed to ECCD(a ACPI method which is quiet similar
> > + * to gaokun_ec_request), there is no good abstraction to generalize these
> > + * sequences, so just wrap it for now. Almost all magic sequences are kept
> > + * in this file.
> > + */
> > +int gaokun_ec_read(struct gaokun_ec *ec, const u8 *req,
> > + size_t resp_len, u8 *resp)
> > +{
> > + return gaokun_ec_request(ec, req, resp_len, resp);
> > +}
> > +EXPORT_SYMBOL_GPL(gaokun_ec_read);
> > +
> > +/**
> > + * gaokun_ec_write - write to EC
> > + * @ec: The gaokun_ec
> > + * @req: The sequence to request
> > + *
> > + * This function has no big difference from gaokun_ec_read. When caller care
> > + * only write status and no actual data are returnd, then use it.
> > + */
> > +int gaokun_ec_write(struct gaokun_ec *ec, u8 *req)
> > +{
> > + u8 resp[RESP_HDR_SIZE];
> > +
> > + return gaokun_ec_request(ec, req, sizeof(resp), resp);
> > +}
> > +EXPORT_SYMBOL_GPL(gaokun_ec_write);
> > +
> > +int gaokun_ec_read_byte(struct gaokun_ec *ec, u8 *req, u8 *byte)
> > +{
> > + int ret;
> > + u8 resp[RESP_HDR_SIZE + sizeof(*byte)];
> > +
> > + ret = gaokun_ec_read(ec, req, sizeof(resp), resp);
> > + *byte = resp[DATA_OFFSET];
> > +
> > + return ret;
> > +}
> > +EXPORT_SYMBOL_GPL(gaokun_ec_read_byte);
> > +
> > +int gaokun_ec_register_notify(struct gaokun_ec *ec, struct notifier_block *nb)
> > +{
> > + return blocking_notifier_chain_register(&ec->notifier_list, nb);
> > +}
> > +EXPORT_SYMBOL_GPL(gaokun_ec_register_notify);
> > +
> > +void gaokun_ec_unregister_notify(struct gaokun_ec *ec, struct notifier_block *nb)
> > +{
> > + blocking_notifier_chain_unregister(&ec->notifier_list, nb);
> > +}
> > +EXPORT_SYMBOL_GPL(gaokun_ec_unregister_notify);
> > +
> > +/* -------------------------------------------------------------------------- */
> > +/* API For PSY */
> > +
> > +int gaokun_ec_psy_multi_read(struct gaokun_ec *ec, u8 reg,
> > + size_t resp_len, u8 *resp)
> > +{
> > + int i, ret;
> > + u8 _resp[RESP_HDR_SIZE + 1];
> > + u8 req[REQ_HDR_SIZE + 1] = {0x02, EC_READ, 1, };
>
> Instead of constructing your packet inline like this, suggest a
> dedicated function to construct a request packet.
>
> For example 1 @ INPUT_SIZE_OFFSET => the size of data a dedicated
> function will make the "stuffing" of the request frame more obvious to
> readers and make the construction of packets less error prone.
>
I will try to do it, but there are many magic sequences in any size, I
need pass them anyways.
> > +
> > + for (i = 0; i < resp_len; ++i) {
> > + req[INPUT_DATA_OFFSET] = reg++;
> > + ret = gaokun_ec_read(ec, req, sizeof(_resp), _resp);
> > + if (ret)
> > + return -EIO;
> > + resp[i] = _resp[DATA_OFFSET];
> > + }
> > +
> > + return 0;
> > +}
> > +EXPORT_SYMBOL_GPL(gaokun_ec_psy_multi_read);
> > +
> > +/* -------------------------------------------------------------------------- */
> > +/* API For WMI */
> > +
> > +/* Battery charging threshold */
> > +int gaokun_ec_wmi_get_threshold(struct gaokun_ec *ec, u8 *value, int ind)
> > +{
> > + /* GBTT */
> > + return gaokun_ec_read_byte(ec, (u8 []){0x02, 0x69, 1, ind}, value);
> > +}
> > +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_get_threshold);
> > +
> > +int gaokun_ec_wmi_set_threshold(struct gaokun_ec *ec, u8 start, u8 end)
> > +{
> > + /* SBTT */
> > + int ret;
> > + u8 req[REQ_HDR_SIZE + 2] = {0x02, 0x68, 2, 3, 0x5a};
> > +
> > + ret = gaokun_ec_write(ec, req);
> > + if (ret)
> > + return -EIO;
> > +
> > + if (start == 0 && end == 0)
> > + return -EINVAL;
> > +
> > + if (start >= 0 && start <= end && end <= 100) {
>
> if start >= 0
>
> is redundant no ? start is a u8 it can _only_ be >= 0 ..
>
Yeah, this code was written with int. Later, I used u8 since one byte is
convenient for EC transaction(we don't need clear other 3 Bytes). After
chaging, I forgot to revaluate it.
>
> > + req[INPUT_DATA_OFFSET] = 1;
> > + req[INPUT_DATA_OFFSET + 1] = start;
> > + ret = gaokun_ec_write(ec, req);
> > + if (ret)
> > + return -EIO;
> > +
> > + req[INPUT_DATA_OFFSET] = 2;
> > + req[INPUT_DATA_OFFSET + 1] = end;
>
> again a function to construct a packet gets you out of the business of
> inlining and "just knowing" which offset is which within any give
> function which indexes an array.
>
> > + ret = gaokun_ec_write(ec, req);
> > + } else {
> > + return -EINVAL;
> > + }
> > +
> > + return ret;
> > +}
> > +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_set_threshold);
> > +
> > +/* Smart charge param */
> > +int gaokun_ec_wmi_get_smart_charge_param(struct gaokun_ec *ec, u8 *value)
> > +{
> > + /* GBAC */
> > + return gaokun_ec_read_byte(ec, (u8 []){0x02, 0xE6, 0}, value);
> > +}
> > +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_get_smart_charge_param);
> > +
> > +int gaokun_ec_wmi_set_smart_charge_param(struct gaokun_ec *ec, u8 value)
> > +{
> > + /* SBAC */
> > + if (value < 0 || value > 2)
>
> value < 0 can never be true
>
> > + return -EINVAL;
> > +
> > + return gaokun_ec_write(ec, (u8 []){0x02, 0xE5, 1, value});
> > +}
> > +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_set_smart_charge_param);
> > +
> > +/* Smart charge */
> > +int gaokun_ec_wmi_get_smart_charge(struct gaokun_ec *ec,
> > + u8 data[GAOKUN_SMART_CHARGE_DATA_SIZE])
> > +{
> > + /* GBCM */
> > + u8 req[REQ_HDR_SIZE] = {0x02, 0xE4, 0};
> > + u8 resp[RESP_HDR_SIZE + 4];
> > + int ret;
> > +
> > + ret = gaokun_ec_read(ec, req, sizeof(resp), resp);
> > + if (ret)
> > + return -EIO;
> > +
> > + data[0] = resp[DATA_OFFSET];
> > + data[1] = resp[DATA_OFFSET + 1];
> > + data[2] = resp[DATA_OFFSET + 2];
> > + data[3] = resp[DATA_OFFSET + 3];
> > +
> > + return 0;
> > +}
> > +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_get_smart_charge);
> > +
> > +int gaokun_ec_wmi_set_smart_charge(struct gaokun_ec *ec,
> > + u8 data[GAOKUN_SMART_CHARGE_DATA_SIZE])
> > +{
> > + /* SBCM */
> > + u8 req[REQ_HDR_SIZE + GAOKUN_SMART_CHARGE_DATA_SIZE] = {0x02, 0XE3, 4,};
> > +
> > + if (!(data[2] >= 0 && data[2] <= data[3] && data[3] <= 100))
> > + return -EINVAL;
>
> Repeat of the clause above which was checking u8 >= 0 for the same
> values in the rest of the clause - including checking <= 100.
>
> Certainly a candidate for functional decomposition, inline function or a
> define.
>
> > +
> > + memcpy(req + INPUT_DATA_OFFSET, data, GAOKUN_SMART_CHARGE_DATA_SIZE);
> > +
> > + return gaokun_ec_write(ec, req);
> > +}
> > +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_set_smart_charge);
> > +
> > +/* Fn lock */
> > +int gaokun_ec_wmi_get_fn_lock(struct gaokun_ec *ec, u8 *on)
> > +{
> > + /* GFRS */
> > + int ret;
> > + u8 val;
> > + u8 req[REQ_HDR_SIZE] = {0x02, 0x6B, 0};
>
> Reverse Christmas tree
>
> u8 req[REQ_HDR_SIZE];
> int ret;
> u8 val;
>
> Not required but nice to look at.
>
Agree
> > +
> > + ret = gaokun_ec_read_byte(ec, req, &val);
> > + if (val == 0x55)
> > + *on = 0;
> > + else if (val == 0x5A)
> > + *on = 1;
> > + else
> > + return -EIO;
> > +
> > + return ret;
> > +}
> > +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_get_fn_lock);
> > +
> > +int gaokun_ec_wmi_set_fn_lock(struct gaokun_ec *ec, u8 on)
> > +{
> > + /* SFRS */
> > + u8 req[REQ_HDR_SIZE + 1] = {0x02, 0x6C, 1,};
> > +
> > + if (on == 0)
> > + req[INPUT_DATA_OFFSET] = 0x55;
> > + else if (on == 1)
> > + req[INPUT_DATA_OFFSET] = 0x5A;
> > + else
> > + return -EINVAL;
>
> Why not use a bool for on ?
>
Yes, we can.
> > +
> > + return gaokun_ec_write(ec, req);
> > +}
> > +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_set_fn_lock);
> > +
> > +/* Thermal Zone */
> > +/* Range from 0 to 0x2C, partial valid */
> > +static const u8 temp_reg[] = {0x05, 0x07, 0x08, 0x0E, 0x0F, 0x12, 0x15, 0x1E,
> > + 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
> > + 0x27, 0x28, 0x29, 0x2A};
> > +
> > +int gaokun_ec_wmi_get_temp(struct gaokun_ec *ec, s16 temp[GAOKUN_TZ_REG_NUM])
>
> int gaokun_ec_wmi_get_temp(struct gaokun_ec *ec, s16 *temp, size_t
> temp_reg_num)
>
>
> > +{
> > + /* GTMP */
> > + u8 req[REQ_HDR_SIZE] = {0x02, 0x61, 1,};
> > + u8 resp[RESP_HDR_SIZE + sizeof(s16)];
> > + int ret, i = 0;
> > +
> > + while (i < GAOKUN_TZ_REG_NUM) {
> while (i < temp_reg_num)
>
It is a constant. But later, as Krzysztof suggested, I will use interfaces
from hwmon, then reading one at a time.
> > + req[INPUT_DATA_OFFSET] = temp_reg[i];
> > + ret = gaokun_ec_read(ec, req, sizeof(resp), resp);
> > + if (ret)
> > + return -EIO;
> > + temp[i++] = *(s16 *)(resp + DATA_OFFSET);
>
> What's the point of the casting here ?
>
> memcpy(temp, resp, sizeof(s16));
> temp++;
>
A 2Bytes symbol number in little endian, ec return it like this, so
casting.
> > + }
> > +
> > + return 0;
> > +}
> > +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_get_temp);
> > +
> > +/* -------------------------------------------------------------------------- */
> > +/* API For UCSI */
> > +
> > +int gaokun_ec_ucsi_read(struct gaokun_ec *ec,
> > + u8 resp[GAOKUN_UCSI_READ_SIZE])
> > +{
> > + u8 req[REQ_HDR_SIZE] = {0x3, 0xD5, 0};
> > + u8 _resp[RESP_HDR_SIZE + GAOKUN_UCSI_READ_SIZE];
> > + int ret;
> > +
> > + ret = gaokun_ec_read(ec, req, sizeof(_resp), _resp);
> > + if (ret)
> > + return ret;
> > +
> > + memcpy(resp, _resp + DATA_OFFSET, GAOKUN_UCSI_READ_SIZE);
> > + return 0;
> > +}
> > +EXPORT_SYMBOL_GPL(gaokun_ec_ucsi_read);
> > +
> > +int gaokun_ec_ucsi_write(struct gaokun_ec *ec,
> > + const u8 req[GAOKUN_UCSI_WRITE_SIZE])
> > +{
> > + u8 _req[REQ_HDR_SIZE + GAOKUN_UCSI_WRITE_SIZE];
> > +
> > + _req[0] = 0x03;
> > + _req[1] = 0xD4;
> > + _req[INPUT_SIZE_OFFSET] = GAOKUN_UCSI_WRITE_SIZE;
> > + memcpy(_req + INPUT_DATA_OFFSET, req, GAOKUN_UCSI_WRITE_SIZE);
> > +
> > + return gaokun_ec_write(ec, _req);
> > +}
> > +EXPORT_SYMBOL_GPL(gaokun_ec_ucsi_write);
> > +
> > +int gaokun_ec_ucsi_get_reg(struct gaokun_ec *ec, u8 *ureg)
> > +{
> > + u8 req[REQ_HDR_SIZE] = {0x03, 0xD3, 0};
> > + u8 _resp[RESP_HDR_SIZE + UCSI_REG_SIZE];
> > + int ret;
> > +
> > + ret = gaokun_ec_read(ec, req, sizeof(_resp), _resp);
> > + if (ret)
> > + return ret;
> > +
> > + memcpy(ureg, _resp + DATA_OFFSET, UCSI_REG_SIZE);
> > +
> > + return 0;
> > +}
> > +EXPORT_SYMBOL_GPL(gaokun_ec_ucsi_get_reg);
> > +
> > +int gaokun_ec_ucsi_pan_ack(struct gaokun_ec *ec, int port_id)
> > +{
> > + u8 req[REQ_HDR_SIZE + 1] = {0x03, 0xD2, 1, 0};
> > +
> > + if (port_id >= 0)
> > + req[INPUT_DATA_OFFSET] = 1 << port_id;
> > +
> > + return gaokun_ec_write(ec, req);
> > +}
> > +EXPORT_SYMBOL_GPL(gaokun_ec_ucsi_pan_ack);
> > +
> > +/* -------------------------------------------------------------------------- */
> > +/* Modern Standby */
> > +
> > +static int gaokun_ec_suspend(struct device *dev)
> > +{
> > + struct gaokun_ec *ec = dev_get_drvdata(dev);
> > + u8 req[REQ_HDR_SIZE + 1] = {0x02, 0xB2, 1, 0xDB};
> > + int ret;
> > +
> > + if (ec->suspended)
> > + return 0;
> > +
> > + ret = gaokun_ec_write(ec, req);
> > +
> > + if (ret)
> > + return ret;
> > +
> > + ec->suspended = true;
> > +
> > + return 0;
> > +}
> > +
> > +static int gaokun_ec_resume(struct device *dev)
> > +{
> > + struct gaokun_ec *ec = dev_get_drvdata(dev);
> > + u8 req[REQ_HDR_SIZE + 1] = {0x02, 0xB2, 1, 0xEB};
> > + int ret;
> > + int i;
> > +
> > + if (!ec->suspended)
> > + return 0;
> > +
> > + for (i = 0; i < 3; i++) {
> > + ret = gaokun_ec_write(ec, req);
> > + if (ret == 0)
> > + break;
> > +
> > + msleep(100);
> > + };
>
> Write three times with a 100 millisecond sleep ?
>
After resueming from suspend, it should write somting to EC, but EC may
not resume, if not, then retry after a break.
> Deserves a comment at least.
>
I see.
> > +
> > + ec->suspended = false;
> > +
> > + return 0;
> > +}
> > +
> > +static void gaokun_aux_release(struct device *dev)
> > +{
> > + struct auxiliary_device *adev = to_auxiliary_dev(dev);
> > +
> > + kfree(adev);
> > +}
> > +
> > +static void gaokun_aux_remove(void *data)
> > +{
> > + struct auxiliary_device *adev = data;
> > +
> > + auxiliary_device_delete(adev);
> > + auxiliary_device_uninit(adev);
> > +}
> > +
> > +static int gaokun_aux_init(struct device *parent, const char *name,
> > + struct gaokun_ec *ec)
> > +{
> > + struct auxiliary_device *adev;
> > + int ret;
> > +
> > + adev = kzalloc(sizeof(*adev), GFP_KERNEL);
> > + if (!adev)
> > + return -ENOMEM;
> > +
> > + adev->name = name;
> > + adev->id = 0;
> > + adev->dev.parent = parent;
> > + adev->dev.release = gaokun_aux_release;
> > + adev->dev.platform_data = ec;
> > + /* Allow aux devices to access parent's DT nodes directly */
> > + device_set_of_node_from_dev(&adev->dev, parent);
> > +
> > + ret = auxiliary_device_init(adev);
> > + if (ret) {
> > + kfree(adev);
> > + return ret;
> > + }
> > +
> > + ret = auxiliary_device_add(adev);
> > + if (ret) {
> > + auxiliary_device_uninit(adev);
> > + return ret;
> > + }
> > +
> > + return devm_add_action_or_reset(parent, gaokun_aux_remove, adev);
> > +}
> > +
> > +/* -------------------------------------------------------------------------- */
> > +/* EC */
> > +
> > +static irqreturn_t gaokun_ec_irq_handler(int irq, void *data)
> > +{
> > + struct gaokun_ec *ec = data;
> > + u8 req[REQ_HDR_SIZE] = {EC_EVENT, EC_QUERY, 0};
> > + u8 status, id;
> > + int ret;
> > +
> > + ret = gaokun_ec_read_byte(ec, req, &id);
> > + if (ret)
> > + return IRQ_HANDLED;
> > +
> > + switch (id) {
> > + case 0x0: /* No event */
> > + break;
> > +
> > + case EC_EVENT_LID:
> > + gaokun_ec_psy_read_byte(ec, EC_LID_STATE, &status);
> > + status = EC_LID_OPEN & status;
> > + input_report_switch(ec->idev, SW_LID, !status);
> > + input_sync(ec->idev);
> > + break;
> > +
> > + default:
> > + blocking_notifier_call_chain(&ec->notifier_list, id, ec);
> > + }
> > +
> > + return IRQ_HANDLED;
> > +}
> > +
> > +static int gaokun_ec_probe(struct i2c_client *client)
> > +{
> > + struct device *dev = &client->dev;
> > + struct gaokun_ec *ec;
> > + int ret;
> > +
> > + ec = devm_kzalloc(dev, sizeof(*ec), GFP_KERNEL);
> > + if (!ec)
> > + return -ENOMEM;
> > +
> > + mutex_init(&ec->lock);
> > + ec->client = client;
> > + i2c_set_clientdata(client, ec);
> > + BLOCKING_INIT_NOTIFIER_HEAD(&ec->notifier_list);
> > +
> > + /* Lid switch */
> > + ec->idev = devm_input_allocate_device(dev);
> > + if (!ec->idev)
> > + return -ENOMEM;
> > +
> > + ec->idev->name = "LID";
> > + ec->idev->phys = "gaokun-ec/input0";
> > + input_set_capability(ec->idev, EV_SW, SW_LID);
> > +
> > + ret = input_register_device(ec->idev);
> > + if (ret)
> > + return dev_err_probe(dev, ret, "Failed to register input device\n");
> > +
> > + ret = gaokun_aux_init(dev, "psy", ec);
> > + if (ret)
> > + return ret;
> > +
> > + ret = gaokun_aux_init(dev, "wmi", ec);
> > + if (ret)
> > + return ret;
> > +
> > + ret = gaokun_aux_init(dev, "ucsi", ec);
> > + if (ret)
> > + return ret;
> > +
> > + ret = devm_request_threaded_irq(dev, client->irq, NULL,
> > + gaokun_ec_irq_handler, IRQF_ONESHOT,
> > + dev_name(dev), ec);
> > + if (ret)
> > + return dev_err_probe(dev, ret, "Failed to request irq\n");
> > +
> > + return 0;
> > +}
> > +
> > +static const struct i2c_device_id gaokun_ec_id[] = {
> > + { "gaokun-ec", },
> > + { }
> > +};
> > +MODULE_DEVICE_TABLE(i2c, gaokun_ec_id);
> > +
> > +static const struct of_device_id gaokun_ec_of_match[] = {
> > + { .compatible = "huawei,gaokun-ec", },
> > + { }
> > +};
> > +MODULE_DEVICE_TABLE(of, gaokun_ec_of_match);
> > +
> > +static const struct dev_pm_ops gaokun_ec_pm_ops = {
> > + NOIRQ_SYSTEM_SLEEP_PM_OPS(gaokun_ec_suspend, gaokun_ec_resume)
> > +};
> > +
> > +static struct i2c_driver gaokun_ec_driver = {
> > + .driver = {
> > + .name = "gaokun-ec",
> > + .of_match_table = gaokun_ec_of_match,
> > + .pm = &gaokun_ec_pm_ops,
> > + },
> > + .probe = gaokun_ec_probe,
> > + .id_table = gaokun_ec_id,
> > +};
> > +module_i2c_driver(gaokun_ec_driver);
> > +
> > +MODULE_DESCRIPTION("HUAWEI Matebook E Go EC driver");
> > +MODULE_AUTHOR("Pengyu Luo <mitltlatltl@gmail.com>");
> > +MODULE_LICENSE("GPL");
> > diff --git a/drivers/platform/arm64/huawei-gaokun-wmi.c b/drivers/platform/arm64/huawei-gaokun-wmi.c
> > new file mode 100644
> > index 000000000..793cb1659
> > --- /dev/null
> > +++ b/drivers/platform/arm64/huawei-gaokun-wmi.c
> > @@ -0,0 +1,283 @@
> > +// SPDX-License-Identifier: GPL-2.0-only
> > +/*
> > + * huawei-gaokun-wmi - A WMI driver for HUAWEI Matebook E Go (sc8280xp)
> > + *
> > + * reference: drivers/platform/x86/huawei-wmi.c
> > + *
> > + * Copyright (C) 2024 Pengyu Luo <mitltlatltl@gmail.com>
> > + */
> > +
> > +#include <linux/auxiliary_bus.h>
> > +#include <linux/module.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/sysfs.h>
> > +#include <linux/version.h>
> > +
> > +#include <linux/platform_data/huawei-gaokun-ec.h>
> > +
> > +struct gaokun_wmi {
> > + struct gaokun_ec *ec;
> > + struct device *dev;
> > + struct platform_device *wmi;
> > +};
> > +
> > +/* -------------------------------------------------------------------------- */
> > +/* Battery charging threshold */
> > +
> > +enum gaokun_wmi_threshold_ind {
> > + START = 1,
> > + END = 2,
> > +};
> > +
> > +static ssize_t charge_control_thresholds_show(struct device *dev,
> > + struct device_attribute *attr,
> > + char *buf)
> > +{
> > + int ret;
> > + u8 start, end;
> > + struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
> > +
> > + ret = gaokun_ec_wmi_get_threshold(ecwmi->ec, &start, START)
> > + || gaokun_ec_wmi_get_threshold(ecwmi->ec, &end, END);
> > + if (ret)
> > + return ret;
>
> ick ouch.
>
> Please call these two functions and evaluate their result codes
> individually.
>
Agree
> > +
> > + return sysfs_emit(buf, "%d %d\n", start, end);
> > +}
> > +
> > +static ssize_t charge_control_thresholds_store(struct device *dev,
> > + struct device_attribute *attr,
> > + const char *buf, size_t size)
> > +{
> > + int ret;
> > + u8 start, end;
> > + struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
> > +
> > + if (sscanf(buf, "%hhd %hhd", &start, &end) != 2)
> > + return -EINVAL;
> > +
> > + ret = gaokun_ec_wmi_set_threshold(ecwmi->ec, start, end);
> > + if (ret)
> > + return ret;
> > +
> > + return size;
> > +}
> > +
> > +static DEVICE_ATTR_RW(charge_control_thresholds);
> > +
> > +/* -------------------------------------------------------------------------- */
> > +/* Smart charge param */
> > +
> > +static ssize_t smart_charge_param_show(struct device *dev,
> > + struct device_attribute *attr,
> > + char *buf)
> > +{
> > + int ret;
> > + u8 value;
> > + struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
> > +
> > + ret = gaokun_ec_wmi_get_smart_charge_param(ecwmi->ec, &value);
> > + if (ret)
> > + return ret;
> > +
> > + return sysfs_emit(buf, "%d\n", value);
> > +}
> > +
> > +static ssize_t smart_charge_param_store(struct device *dev,
> > + struct device_attribute *attr,
> > + const char *buf, size_t size)
> > +{
> > + int ret;
> > + u8 value;
> > + struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
> > +
> > + if (kstrtou8(buf, 10, &value))
> > + return -EINVAL;
> > +
> > + ret = gaokun_ec_wmi_set_smart_charge_param(ecwmi->ec, value);
> > + if (ret)
> > + return ret;
> > +
> > + return size;
> > +}
> > +
> > +static DEVICE_ATTR_RW(smart_charge_param);
> > +
> > +/* -------------------------------------------------------------------------- */
> > +/* Smart charge */
> > +
> > +static ssize_t smart_charge_show(struct device *dev,
> > + struct device_attribute *attr,
> > + char *buf)
> > +{
> > + int ret;
> > + u8 bf[GAOKUN_SMART_CHARGE_DATA_SIZE];
> > + struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
> > +
> > + ret = gaokun_ec_wmi_get_smart_charge(ecwmi->ec, bf);
> > + if (ret)
> > + return ret;
> > +
> > + return sysfs_emit(buf, "%d %d %d %d\n",
> > + bf[0], bf[1], bf[2], bf[3]);
> > +}
> > +
> > +static ssize_t smart_charge_store(struct device *dev,
> > + struct device_attribute *attr,
> > + const char *buf, size_t size)
> > +{
> > + int ret;
> > + u8 bf[GAOKUN_SMART_CHARGE_DATA_SIZE];
> > + struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
> > +
> > + if (sscanf(buf, "%hhd %hhd %hhd %hhd", bf, bf + 1, bf + 2, bf + 3) != 4)
> > + return -EINVAL;
> > +
> > + ret = gaokun_ec_wmi_set_smart_charge(ecwmi->ec, bf);
> > + if (ret)
> > + return ret;
> > +
> > + return size;
> > +}
> > +
> > +static DEVICE_ATTR_RW(smart_charge);
> > +
> > +/* -------------------------------------------------------------------------- */
> > +/* Fn lock */
> > +
> > +static ssize_t fn_lock_state_show(struct device *dev,
> > + struct device_attribute *attr,
> > + char *buf)
> > +{
> > + int ret;
> > + u8 on;
> > + struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
> > +
> > + ret = gaokun_ec_wmi_get_fn_lock(ecwmi->ec, &on);
> > + if (ret)
> > + return ret;
> > +
> > + return sysfs_emit(buf, "%d\n", on);
> > +}
> > +
> > +static ssize_t fn_lock_state_store(struct device *dev,
> > + struct device_attribute *attr,
> > + const char *buf, size_t size)
> > +{
> > + int ret;
> > + u8 on;
> > + struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
> > +
> > + if (kstrtou8(buf, 10, &on))
> > + return -EINVAL;
> > +
> > + ret = gaokun_ec_wmi_set_fn_lock(ecwmi->ec, on);
>
> I mentioned already you should pass on as a bool and then decide here in
> the input function if "on" as passed is a reasonable boolean state.
>
I see
> > + if (ret)
> > + return ret;
> > +
> > + return size;
> > +}
> > +
> > +static DEVICE_ATTR_RW(fn_lock_state);
> > +
> > +/* -------------------------------------------------------------------------- */
> > +/* Thermal Zone */
> > +
> > +static ssize_t temperature_show(struct device *dev,
> > + struct device_attribute *attr,
> > + char *buf)
> > +{
> > +
> > + int ret, len, i;
> > + char *ptr = buf;
> > + s16 value;
> > + s16 temp[GAOKUN_TZ_REG_NUM];
> > + struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
> > +
> > + ret = gaokun_ec_wmi_get_temp(ecwmi->ec, temp);
> > + if (ret)
> > + return ret;
> > +
> > + i = 0;
> > + len = 0;
> > + while (i < GAOKUN_TZ_REG_NUM) {
> > + value = temp[i++];
> > + if (value < 0) {
> > + len += sprintf(ptr + len, "-");
>
> Have you ever seen a negative temperature value in running silicon ?
> Looks suspicious to me.
>
EC's logic, it handles them as symbol numbers, and dealing negative with
bit ops. And I think not all sensors are for silicon.
{
BUF1 = \_SB.IC16.ECCD (0x02, 0x61, One, ISNB, 0x04)
TEMP [Zero] = DerefOf (BUF1 [0x02])
TEMP [One] = DerefOf (BUF1 [0x03])
Local1 = TEMP /* \GTMP.TEMP */
Local2 = DerefOf (TEMP [Zero])
Local3 = DerefOf (TEMP [One])
If ((Local2 == 0xFF))
{
TEMP = Zero
STAT = One
}
Else
{
Local4 = (Local3 << 0x08)
Local1 = (Local4 | Local2)
Local4 = (Local3 & 0x80)
If (Local4)
{
TFLG = One
Local5 = (Local1 - One)
Local1 = ~Local5
}
Divide (Local1, 0x0A, Local4, Local5)
TMPD = Local4
TMPI = Local5
STAT = Zero
}
}
> > + value = -value;
> > + }
> > + len += sprintf(ptr + len, "%d.%d ", value / 10, value % 10);
> > + }
> > + len += sprintf(ptr + len, "\n");
> > +
> > + return len;
> > +}
> > +
> > +static DEVICE_ATTR_RO(temperature);
> > +
> > +static struct attribute *gaokun_wmi_features_attrs[] = {
> > + &dev_attr_charge_control_thresholds.attr,
> > + &dev_attr_smart_charge_param.attr,
> > + &dev_attr_smart_charge.attr,
> > + &dev_attr_fn_lock_state.attr,
> > + &dev_attr_temperature.attr,
> > + NULL,
> > +};
> > +ATTRIBUTE_GROUPS(gaokun_wmi_features);
> > +
> > +static int gaokun_wmi_probe(struct auxiliary_device *adev,
> > + const struct auxiliary_device_id *id)
> > +{
> > + struct gaokun_ec *ec = adev->dev.platform_data;
> > + struct device *dev = &adev->dev;
> > + struct gaokun_wmi *ecwmi;
> > +
> > + ecwmi = devm_kzalloc(&adev->dev, sizeof(*ecwmi), GFP_KERNEL);
> > + if (!ecwmi)
> > + return -ENOMEM;
> > +
> > + ecwmi->ec = ec;
> > + ecwmi->dev = dev;
> > +
> > + auxiliary_set_drvdata(adev, ecwmi);
> > +
> > + /* make it under /sys/devices/platform, convenient for sysfs I/O,
> > + * while adev is under
> > + * /sys/devices/platform/soc@0/ac0000.geniqup/a9c000.i2c/i2c-15/15-0038/
> > + */
> > + ecwmi->wmi = platform_device_register_simple("gaokun-wmi", -1, NULL, 0);
> > + if (IS_ERR(ecwmi->wmi))
> > + return dev_err_probe(dev, PTR_ERR(ecwmi->wmi),
> > + "Failed to register wmi platform device\n");
> > +
> > + platform_set_drvdata(ecwmi->wmi, ecwmi);
> > +
> > + return device_add_groups(&ecwmi->wmi->dev, gaokun_wmi_features_groups);
> > +}
> > +
> > +static void gaokun_wmi_remove(struct auxiliary_device *adev)
> > +{
> > + struct gaokun_wmi *ecwmi = auxiliary_get_drvdata(adev);
> > + struct platform_device *wmi = ecwmi->wmi;
> > +
> > + device_remove_groups(&wmi->dev, gaokun_wmi_features_groups);
> > + platform_device_unregister(ecwmi->wmi);
> > +}
> > +
> > +static const struct auxiliary_device_id gaokun_wmi_id_table[] = {
> > + { .name = GAOKUN_MOD_NAME "." GAOKUN_DEV_WMI, },
> > + {}
> > +};
> > +MODULE_DEVICE_TABLE(auxiliary, gaokun_wmi_id_table);
> > +
> > +static struct auxiliary_driver gaokun_wmi_driver = {
> > + .name = GAOKUN_DEV_WMI,
> > + .id_table = gaokun_wmi_id_table,
> > + .probe = gaokun_wmi_probe,
> > + .remove = gaokun_wmi_remove,
> > +};
> > +
> > +module_auxiliary_driver(gaokun_wmi_driver);
> > +
> > +MODULE_DESCRIPTION("HUAWEI Matebook E Go WMI driver");
> > +MODULE_LICENSE("GPL");
> > diff --git a/include/linux/platform_data/huawei-gaokun-ec.h b/include/linux/platform_data/huawei-gaokun-ec.h
> > new file mode 100644
> > index 000000000..a649e9ecf
> > --- /dev/null
> > +++ b/include/linux/platform_data/huawei-gaokun-ec.h
> > @@ -0,0 +1,90 @@
> > +// SPDX-License-Identifier: GPL-2.0-only
> > +/* Huawei Matebook E Go (sc8280xp) Embedded Controller
> > + *
> > + * Copyright (C) 2024 Pengyu Luo <mitltlatltl@gmail.com>
> > + *
> > + */
> > +
> > +#ifndef __HUAWEI_GAOKUN_EC_H__
> > +#define __HUAWEI_GAOKUN_EC_H__
> > +
> > +#define GAOKUN_UCSI_CCI_SIZE 4
> > +#define GAOKUN_UCSI_DATA_SIZE 16
> > +#define GAOKUN_UCSI_READ_SIZE (GAOKUN_UCSI_CCI_SIZE + GAOKUN_UCSI_DATA_SIZE)
> > +#define GAOKUN_UCSI_WRITE_SIZE 0x18
> > +
> > +#define GAOKUN_TZ_REG_NUM 20
> > +#define GAOKUN_SMART_CHARGE_DATA_SIZE 4 /* mode, delay, start, end */
> > +
> > +/* -------------------------------------------------------------------------- */
> > +
> > +struct gaokun_ec;
> > +struct notifier_block;
> > +
> > +#define GAOKUN_MOD_NAME "huawei_gaokun_ec"
> > +#define GAOKUN_DEV_PSY "psy"
> > +#define GAOKUN_DEV_WMI "wmi"
> > +#define GAOKUN_DEV_UCSI "ucsi"
> > +
> > +/* -------------------------------------------------------------------------- */
> > +/* Common API */
> > +
> > +int gaokun_ec_register_notify(struct gaokun_ec *ec,
> > + struct notifier_block *nb);
> > +void gaokun_ec_unregister_notify(struct gaokun_ec *ec,
> > + struct notifier_block *nb);
> > +
> > +int gaokun_ec_read(struct gaokun_ec *ec, const u8 *req,
> > + size_t resp_len, u8 *resp);
> > +int gaokun_ec_write(struct gaokun_ec *ec, u8 *req);
> > +int gaokun_ec_read_byte(struct gaokun_ec *ec, u8 *req, u8 *byte);
> > +
> > +/* -------------------------------------------------------------------------- */
> > +/* API For PSY */
> > +
> > +int gaokun_ec_psy_multi_read(struct gaokun_ec *ec, u8 reg,
> > + size_t resp_len, u8 *resp);
> > +
> > +static inline int gaokun_ec_psy_read_byte(struct gaokun_ec *ec,
> > + u8 reg, u8 *byte)
> > +{
> > + return gaokun_ec_psy_multi_read(ec, reg, 1, byte);
> > +}
> > +
> > +static inline int gaokun_ec_psy_read_word(struct gaokun_ec *ec,
> > + u8 reg, u16 *word)
> > +{
> > + return gaokun_ec_psy_multi_read(ec, reg, 2, (u8 *)word);
> > +}
> > +
> > +/* -------------------------------------------------------------------------- */
> > +/* API For WMI */
> > +
> > +int gaokun_ec_wmi_get_threshold(struct gaokun_ec *ec, u8 *value, int ind);
> > +int gaokun_ec_wmi_set_threshold(struct gaokun_ec *ec, u8 start, u8 end);
> > +
> > +int gaokun_ec_wmi_get_smart_charge_param(struct gaokun_ec *ec, u8 *value);
> > +int gaokun_ec_wmi_set_smart_charge_param(struct gaokun_ec *ec, u8 value);
> > +
> > +int gaokun_ec_wmi_get_smart_charge(struct gaokun_ec *ec,
> > + u8 data[GAOKUN_SMART_CHARGE_DATA_SIZE]);
> > +int gaokun_ec_wmi_set_smart_charge(struct gaokun_ec *ec,
> > + u8 data[GAOKUN_SMART_CHARGE_DATA_SIZE]);
> > +
> > +int gaokun_ec_wmi_get_fn_lock(struct gaokun_ec *ec, u8 *on);
> > +int gaokun_ec_wmi_set_fn_lock(struct gaokun_ec *ec, u8 on);
> > +
> > +int gaokun_ec_wmi_get_temp(struct gaokun_ec *ec, s16 temp[GAOKUN_TZ_REG_NUM]);
> > +
> > +/* -------------------------------------------------------------------------- */
> > +/* API For UCSI */
> > +
> > +int gaokun_ec_ucsi_read(struct gaokun_ec *ec, u8 resp[GAOKUN_UCSI_READ_SIZE]);
> > +int gaokun_ec_ucsi_write(struct gaokun_ec *ec,
> > + const u8 req[GAOKUN_UCSI_WRITE_SIZE]);
> > +
> > +int gaokun_ec_ucsi_get_reg(struct gaokun_ec *ec, u8 *ureg);
> > +int gaokun_ec_ucsi_pan_ack(struct gaokun_ec *ec, int port_id);
> > +
> > +
> > +#endif /* __HUAWEI_GAOKUN_EC_H__ */
>
Best Wishes,
Pengyu
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 3/5] usb: typec: ucsi: add Huawei Matebook E Go (sc8280xp) ucsi driver
2024-12-28 13:06 ` Bryan O'Donoghue
@ 2024-12-28 14:38 ` Pengyu Luo
2024-12-29 14:51 ` Bryan O'Donoghue
0 siblings, 1 reply; 51+ messages in thread
From: Pengyu Luo @ 2024-12-28 14:38 UTC (permalink / raw)
To: bryan.odonoghue
Cc: andersson, conor+dt, devicetree, dmitry.baryshkov, gregkh,
hdegoede, heikki.krogerus, ilpo.jarvinen, konradybcio, krzk+dt,
linux-arm-msm, linux-kernel, linux-pm, linux-usb, mitltlatltl,
nikita, platform-driver-x86, robh, sre
On Sat, Dec 28, 2024 at 9:06 PM Bryan O'Donoghue <bryan.odonoghue@linaro.org> wrote:
> On 27/12/2024 17:13, Pengyu Luo wrote:
> > The Huawei Matebook E Go (sc8280xp) tablet provides implements UCSI
> > interface in the onboard EC. Add the glue driver to interface the
> > platform's UCSI implementation.
> >
> > Signed-off-by: Pengyu Luo <mitltlatltl@gmail.com>
> > ---
> > drivers/usb/typec/ucsi/Kconfig | 9 +
> > drivers/usb/typec/ucsi/Makefile | 1 +
> > drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c | 481 ++++++++++++++++++++
> > 3 files changed, 491 insertions(+)
> > create mode 100644 drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c
> >
> > diff --git a/drivers/usb/typec/ucsi/Kconfig b/drivers/usb/typec/ucsi/Kconfig
> > index 680e1b87b..0d0f07488 100644
> > --- a/drivers/usb/typec/ucsi/Kconfig
> > +++ b/drivers/usb/typec/ucsi/Kconfig
> > @@ -78,4 +78,13 @@ config UCSI_LENOVO_YOGA_C630
> > To compile the driver as a module, choose M here: the module will be
> > called ucsi_yoga_c630.
> >
> > +config UCSI_HUAWEI_GAOKUN
> > + tristate "UCSI Interface Driver for Huawei Matebook E Go (sc8280xp)"
> > + depends on EC_HUAWEI_GAOKUN
> > + help
> > + This driver enables UCSI support on the Huawei Matebook E Go tablet.
> > +
> > + To compile the driver as a module, choose M here: the module will be
> > + called ucsi_huawei_gaokun.
> > +
> > endif
> > diff --git a/drivers/usb/typec/ucsi/Makefile b/drivers/usb/typec/ucsi/Makefile
> > index aed41d238..0b400122b 100644
> > --- a/drivers/usb/typec/ucsi/Makefile
> > +++ b/drivers/usb/typec/ucsi/Makefile
> > @@ -22,3 +22,4 @@ obj-$(CONFIG_UCSI_CCG) += ucsi_ccg.o
> > obj-$(CONFIG_UCSI_STM32G0) += ucsi_stm32g0.o
> > obj-$(CONFIG_UCSI_PMIC_GLINK) += ucsi_glink.o
> > obj-$(CONFIG_UCSI_LENOVO_YOGA_C630) += ucsi_yoga_c630.o
> > +obj-$(CONFIG_UCSI_HUAWEI_GAOKUN) += ucsi_huawei_gaokun.o
> > diff --git a/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c
> > new file mode 100644
> > index 000000000..84ed0407d
> > --- /dev/null
> > +++ b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c
> > @@ -0,0 +1,481 @@
> > +// SPDX-License-Identifier: GPL-2.0-only
> > +/*
> > + * ucsi-huawei-gaokun - A UCSI driver for HUAWEI Matebook E Go (sc8280xp)
> > + *
> > + * reference: drivers/usb/typec/ucsi/ucsi_yoga_c630.c
> > + * drivers/usb/typec/ucsi/ucsi_glink.c
> > + * drivers/soc/qcom/pmic_glink_altmode.c
> > + *
> > + * Copyright (C) 2024 Pengyu Luo <mitltlatltl@gmail.com>
> > + */
> > +
> > +#include <linux/auxiliary_bus.h>
> > +#include <linux/bitops.h>
> > +#include <linux/completion.h>
> > +#include <linux/container_of.h>
> > +#include <linux/delay.h>
> > +#include <linux/module.h>
> > +#include <linux/notifier.h>
> > +#include <linux/of.h>
> > +#include <linux/string.h>
> > +#include <linux/workqueue_types.h>
> > +
> > +#include <linux/usb/pd_vdo.h>
> > +#include <drm/bridge/aux-bridge.h
>
> Is there a reason you don't have strict include alphanumeric ordering here ?
>
These two is dp/alt mode related, so listing them out. Above of them are
general things.
> >
> > +
> > +#include "ucsi.h"
> > +#include <linux/platform_data/huawei-gaokun-ec.h>
> > +
> > +
> > +#define EC_EVENT_UCSI 0x21
> > +#define EC_EVENT_USB 0x22
> > +
> > +#define GAOKUN_CCX_MASK GENMASK(1, 0)
> > +#define GAOKUN_MUX_MASK GENMASK(3, 2)
> > +
> > +#define GAOKUN_DPAM_MASK GENMASK(3, 0)
> > +#define GAOKUN_HPD_STATE_MASK BIT(4)
> > +#define GAOKUN_HPD_IRQ_MASK BIT(5)
> > +
> > +#define CCX_TO_ORI(ccx) (++ccx % 3)
>
> Why do you increment the value of the enum ?
> Seems strange.
>
EC's logic, it is just a trick. Qualcomm maps
0 1 2 to normal, reverse, none(no device insert)
typec lib maps 1 2 0 to that.
> > +
> > +#define GET_IDX(updt) (ffs(updt) - 1)
> > +
> > +/* Configuration Channel Extension */
> > +enum gaokun_ucsi_ccx {
> > + USBC_CCX_NORMAL,
> > + USBC_CCX_REVERSE,
> > + USBC_CCX_NONE,
> > +};
> > +
> > +enum gaokun_ucsi_mux {
> > + USBC_MUX_NONE,
> > + USBC_MUX_USB_2L,
> > + USBC_MUX_DP_4L,
> > + USBC_MUX_USB_DP,
> > +};
> > +
> > +struct gaokun_ucsi_reg {
> > + u8 port_num;
> > + u8 port_updt;
> > + u8 port_data[4];
> > + u8 checksum;
> > + u8 reserved;
> > +} __packed;
> > +
> > +struct gaokun_ucsi_port {
> > + struct completion usb_ack;
> > + spinlock_t lock;
> > +
> > + struct gaokun_ucsi *ucsi;
> > + struct auxiliary_device *bridge;
> > +
> > + int idx;
> > + enum gaokun_ucsi_ccx ccx;
> > + enum gaokun_ucsi_mux mux;
> > + u8 mode;
> > + u16 svid;
> > + u8 hpd_state;
> > + u8 hpd_irq;
> > +};
> > +
> > +struct gaokun_ucsi {
> > + struct gaokun_ec *ec;
> > + struct ucsi *ucsi;
> > + struct gaokun_ucsi_port *ports;
> > + struct device *dev;
> > + struct work_struct work;
> > + struct notifier_block nb;
> > + u16 version;
> > + u8 port_num;
> > +};
> > +
> > +/* -------------------------------------------------------------------------- */
> > +/* For UCSI */
> > +
> > +static int gaokun_ucsi_read_version(struct ucsi *ucsi, u16 *version)
> > +{
> > + struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi);
> > +
> > + *version = uec->version;
> > +
> > + return 0;
> > +}
> > +
> > +static int gaokun_ucsi_read_cci(struct ucsi *ucsi, u32 *cci)
> > +{
> > + struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi);
> > + u8 buf[GAOKUN_UCSI_READ_SIZE];
> > + int ret;
> > +
> > + ret = gaokun_ec_ucsi_read(uec->ec, buf);
> > + if (ret)
> > + return ret;
> > +
> > + memcpy(cci, buf, sizeof(*cci));
> > +
> > + return 0;
> > +}
> > +
> > +static int gaokun_ucsi_read_message_in(struct ucsi *ucsi,
> > + void *val, size_t val_len)
> > +{
> > + struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi);
> > + u8 buf[GAOKUN_UCSI_READ_SIZE];
> > + int ret;
> > +
> > + ret = gaokun_ec_ucsi_read(uec->ec, buf);
> > + if (ret)
> > + return ret;
> > +
> > + memcpy(val, buf + GAOKUN_UCSI_CCI_SIZE,
> > + min(val_len, GAOKUN_UCSI_DATA_SIZE));
> > +
> > + return 0;
> > +}
> > +
> > +static int gaokun_ucsi_async_control(struct ucsi *ucsi, u64 command)
> > +{
> > + struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi);
> > + u8 buf[GAOKUN_UCSI_WRITE_SIZE] = {};
> > +
> > + memcpy(buf, &command, sizeof(command));
> > +
> > + return gaokun_ec_ucsi_write(uec->ec, buf);
> > +}
> > +
> > +static void gaokun_ucsi_update_connector(struct ucsi_connector *con)
> > +{
> > + struct gaokun_ucsi *uec = ucsi_get_drvdata(con->ucsi);
> > +
> > + if (con->num > uec->port_num)
> > + return;
> > +
> > + con->typec_cap.orientation_aware = true;
> > +}
> > +
> > +static void gaokun_set_orientation(struct ucsi_connector *con,
> > + struct gaokun_ucsi_port *port)
> > +{
> > + enum gaokun_ucsi_ccx ccx;
> > + unsigned long flags;
> > +
> > + spin_lock_irqsave(&port->lock, flags);
> > + ccx = port->ccx;
> > + spin_unlock_irqrestore(&port->lock, flags);
> > +
> > + typec_set_orientation(con->port, CCX_TO_ORI(ccx));
> > +}
> > +
> > +static void gaokun_ucsi_connector_status(struct ucsi_connector *con)
> > +{
> > + struct gaokun_ucsi *uec = ucsi_get_drvdata(con->ucsi);
> > + int idx;
> > +
> > + idx = con->num - 1;
> > + if (con->num > uec->port_num) {
> > + dev_warn(uec->ucsi->dev, "set orientation out of range: con%d\n", idx);
> > + return;
> > + }
> > +
> > + gaokun_set_orientation(con, &uec->ports[idx]);
> > +}
> > +
> > +const struct ucsi_operations gaokun_ucsi_ops = {
> > + .read_version = gaokun_ucsi_read_version,
> > + .read_cci = gaokun_ucsi_read_cci,
> > + .read_message_in = gaokun_ucsi_read_message_in,
> > + .sync_control = ucsi_sync_control_common,
> > + .async_control = gaokun_ucsi_async_control,
> > + .update_connector = gaokun_ucsi_update_connector,
> > + .connector_status = gaokun_ucsi_connector_status,
> > +};
> > +
> > +/* -------------------------------------------------------------------------- */
> > +/* For Altmode */
> > +
> > +static void gaokun_ucsi_port_update(struct gaokun_ucsi_port *port,
> > + const u8 *port_data)
> > +{
> > + unsigned long flags;
> > + u8 dcc, ddi;
> > + int offset = port->idx * 2; /* every port has 2 Bytes data */
> > +
> > + dcc = port_data[offset];
> > + ddi = port_data[offset + 1];
> > +
> > + spin_lock_irqsave(&port->lock, flags);
> > +
> > + port->ccx = FIELD_GET(GAOKUN_CCX_MASK, dcc);
> > + port->mux = FIELD_GET(GAOKUN_MUX_MASK, dcc);
> > + port->mode = FIELD_GET(GAOKUN_DPAM_MASK, ddi);
> > + port->hpd_state = FIELD_GET(GAOKUN_HPD_STATE_MASK, ddi);
> > + port->hpd_irq = FIELD_GET(GAOKUN_HPD_IRQ_MASK, ddi);
> > +
> > + switch (port->mux) {
> > + case USBC_MUX_NONE:
> > + port->svid = 0;
> > + break;
> > + case USBC_MUX_USB_2L:
> > + port->svid = USB_SID_PD;
> > + break;
> > + case USBC_MUX_DP_4L:
> > + case USBC_MUX_USB_DP:
> > + port->svid = USB_SID_DISPLAYPORT;
> > + if (port->ccx == USBC_CCX_REVERSE)
> > + port->mode -= 6;
>
> why minus six ?
> needs a comment.
>
EC's logic. I don't know why, it is a quirk from Qualcomm or Huawei.
I will mention this.
> > + break;
> > + default:
> > + break;
> > + }
> > +
> > + spin_unlock_irqrestore(&port->lock, flags);
> > +}
> > +
> > +static int gaokun_ucsi_refresh(struct gaokun_ucsi *uec)
> > +{
> > + struct gaokun_ucsi_reg ureg;
> > + int ret, idx;
> > +
> > + ret = gaokun_ec_ucsi_get_reg(uec->ec, (u8 *)&ureg);
> > + if (ret)
> > + return -EIO;
> > +
> > + uec->port_num = ureg.port_num;
> > + idx = GET_IDX(ureg.port_updt);
> > +
> > + if (idx >= 0 && idx < ureg.port_num)
> > + gaokun_ucsi_port_update(&uec->ports[idx], ureg.port_data);
>
> Since you are checking the validity of the index, you should -EINVAL if
> the index is out of range.
>
EC / pmic glink encode every port in a bit
0/1/2/4/... => ???/left/right/some port
I remap it to -1/0/1/2, to access specific port exceptions(-1) are not
harmful, later in gaokun_ucsi_altmode_notify_ind
if (idx < 0)
gaokun_ec_ucsi_pan_ack(uec->ec, idx);
else
gaokun_ucsi_handle_altmode(&uec->ports[idx]);
gaokun_ec_ucsi_pan_ack can handle exceptions.
> > +
> > + return idx;
> > +}
> > +
> > +static void gaokun_ucsi_handle_altmode(struct gaokun_ucsi_port *port)
> > +{
> > + struct gaokun_ucsi *uec = port->ucsi;
> > + int idx = port->idx;
> > +
> > + if (idx >= uec->ucsi->cap.num_connectors || !uec->ucsi->connector) {
> > + dev_warn(uec->ucsi->dev, "altmode port out of range: %d\n", idx);
> > + return;
> > + }
> > +
> > + /* UCSI callback .connector_status() have set orientation */
> > + if (port->bridge)
> > + drm_aux_hpd_bridge_notify(&port->bridge->dev,
> > + port->hpd_state ?
> > + connector_status_connected :
> > + connector_status_disconnected);
> > +
> > + gaokun_ec_ucsi_pan_ack(uec->ec, port->idx);
> > +}
> > +
> > +static void gaokun_ucsi_altmode_notify_ind(struct gaokun_ucsi *uec)
> > +{
> > + int idx;
> > +
> > + idx = gaokun_ucsi_refresh(uec);
> > + if (idx < 0)
> > + gaokun_ec_ucsi_pan_ack(uec->ec, idx);
> > + else
> > + gaokun_ucsi_handle_altmode(&uec->ports[idx]);
> > +}
> > +
> > +/*
> > + * USB event is necessary for enabling altmode, the event should follow
> > + * UCSI event, if not after timeout(this notify may be disabled somehow),
> > + * then force to enable altmode.
> > + */
> > +static void gaokun_ucsi_handle_no_usb_event(struct gaokun_ucsi *uec, int idx)
> > +{
> > + struct gaokun_ucsi_port *port;
> > +
> > + port = &uec->ports[idx];
> > + if (!wait_for_completion_timeout(&port->usb_ack, 2 * HZ)) {
> > + dev_warn(uec->dev, "No USB EVENT, triggered by UCSI EVENT");
> > + gaokun_ucsi_altmode_notify_ind(uec);
> > + }
> > +}
> > +
> > +static int gaokun_ucsi_notify(struct notifier_block *nb,
> > + unsigned long action, void *data)
> > +{
> > + u32 cci;
> > + struct gaokun_ucsi *uec = container_of(nb, struct gaokun_ucsi, nb);
> > +
> > + switch (action) {
> > + case EC_EVENT_USB:
> > + gaokun_ucsi_altmode_notify_ind(uec);
> > + return NOTIFY_OK;
> > +
> > + case EC_EVENT_UCSI:
> > + uec->ucsi->ops->read_cci(uec->ucsi, &cci);
> > + ucsi_notify_common(uec->ucsi, cci);
> > + if (UCSI_CCI_CONNECTOR(cci))
> > + gaokun_ucsi_handle_no_usb_event(uec, UCSI_CCI_CONNECTOR(cci) - 1);
> > +
> > + return NOTIFY_OK;
> > +
> > + default:
> > + return NOTIFY_DONE;
> > + }
> > +}
> > +
> > +static int gaokun_ucsi_get_port_num(struct gaokun_ucsi *uec)
> > +{
> > + struct gaokun_ucsi_reg ureg;
> > + int ret;
> > +
> > + ret = gaokun_ec_ucsi_get_reg(uec->ec, (u8 *)&ureg);
> > +
> > + return ret ? 0 : ureg.port_num;
> > +}
> > +
> > +static int gaokun_ucsi_ports_init(struct gaokun_ucsi *uec)
> > +{
> > + u32 port;
> > + int i, ret, port_num;
> > + struct device *dev = uec->dev;
> > + struct gaokun_ucsi_port *ucsi_port;
> > + struct fwnode_handle *fwnode;
> > +
> > + port_num = gaokun_ucsi_get_port_num(uec);
> > + uec->port_num = port_num;
> > +
> > + uec->ports = devm_kzalloc(dev, port_num * sizeof(*(uec->ports)),
> > + GFP_KERNEL);
> > + if (!uec->ports)
> > + return -ENOMEM;
> > +
> > + for (i = 0; i < port_num; ++i) {
> > + ucsi_port = &uec->ports[i];
> > + ucsi_port->ccx = USBC_CCX_NONE;
> > + ucsi_port->idx = i;
> > + ucsi_port->ucsi = uec;
> > + init_completion(&ucsi_port->usb_ack);
> > + spin_lock_init(&ucsi_port->lock);
> > + }
> > +
> > + device_for_each_child_node(dev, fwnode) {
> > + ret = fwnode_property_read_u32(fwnode, "reg", &port);
> > + if (ret < 0) {
> > + dev_err(dev, "missing reg property of %pOFn\n", fwnode);
> > + fwnode_handle_put(fwnode);
> > + return ret;
> > + }
> > +
> > + if (port >= port_num) {
> > + dev_warn(dev, "invalid connector number %d, ignoring\n", port);
> > + continue;
> > + }
> > +
> > + ucsi_port = &uec->ports[port];
> > + ucsi_port->bridge = devm_drm_dp_hpd_bridge_alloc(dev, to_of_node(fwnode));
> > + if (IS_ERR(ucsi_port->bridge)) {
> > + fwnode_handle_put(fwnode);
> > + return PTR_ERR(ucsi_port->bridge);
> > + }
> > + }
> > +
> > + for (i = 0; i < port_num; i++) {
> > + if (!uec->ports[i].bridge)
> > + continue;
> > +
> > + ret = devm_drm_dp_hpd_bridge_add(dev, uec->ports[i].bridge);
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static void gaokun_ucsi_register_worker(struct work_struct *work)
> > +{
> > + struct gaokun_ucsi *uec;
> > + struct ucsi *ucsi;
> > + int ret;
> > +
> > + uec = container_of(work, struct gaokun_ucsi, work);
> > + ucsi = uec->ucsi;
> > +
> > + ucsi->quirks = UCSI_NO_PARTNER_PDOS | UCSI_DELAY_DEVICE_PDOS;
> > +
> > + ssleep(3); /* EC can't handle UCSI properly in the early stage */
>
> Could you not schedule work for + 3 seconds instead of sleeping here -
> representing the required stall time in some sort of state machine ?
>
I see, I will check work schedule interface.
> 3 seconds is an incredibly long time for a computer to sleep.
>
This module will be loaded at about 5th second after power up, if not
sleep, we will receive something disharmonious, sleeping for 3 seconds is
a hack.
> > +
> > + ret = gaokun_ec_register_notify(uec->ec, &uec->nb);
> > + if (ret) {
> > + dev_err_probe(ucsi->dev, ret, "notifier register failed\n");
> > + return;
> > + }
> > +
> > + ret = ucsi_register(ucsi);
> > + if (ret)
> > + dev_err_probe(ucsi->dev, ret, "ucsi register failed\n");
> > +}
> > +
> > +static int gaokun_ucsi_register(struct gaokun_ucsi *uec)
> > +{
> > + schedule_work(&uec->work);
> > +
> > + return 0;
> > +}
> > +
> > +static int gaokun_ucsi_probe(struct auxiliary_device *adev,
> > + const struct auxiliary_device_id *id)
> > +{
> > + struct gaokun_ec *ec = adev->dev.platform_data;
> > + struct device *dev = &adev->dev;
> > + struct gaokun_ucsi *uec;
> > + int ret;
> > +
> > + uec = devm_kzalloc(dev, sizeof(*uec), GFP_KERNEL);
> > + if (!uec)
> > + return -ENOMEM;
> > +
> > + uec->ec = ec;
> > + uec->dev = dev;
> > + uec->version = 0x0100;
> > + uec->nb.notifier_call = gaokun_ucsi_notify;
> > +
> > + INIT_WORK(&uec->work, gaokun_ucsi_register_worker);
> > +
> > + ret = gaokun_ucsi_ports_init(uec);
> > + if (ret)
> > + return ret;
> > +
> > + uec->ucsi = ucsi_create(dev, &gaokun_ucsi_ops);
> > + if (IS_ERR(uec->ucsi))
> > + return PTR_ERR(uec->ucsi);
> > +
> > + ucsi_set_drvdata(uec->ucsi, uec);
> > + auxiliary_set_drvdata(adev, uec);
> > +
> > + return gaokun_ucsi_register(uec);
> > +}
> > +
> > +static void gaokun_ucsi_remove(struct auxiliary_device *adev)
> > +{
> > + struct gaokun_ucsi *uec = auxiliary_get_drvdata(adev);
> > +
> > + gaokun_ec_unregister_notify(uec->ec, &uec->nb);
> > + ucsi_unregister(uec->ucsi);
> > + ucsi_destroy(uec->ucsi);
> > +}
> > +
> > +static const struct auxiliary_device_id gaokun_ucsi_id_table[] = {
> > + { .name = GAOKUN_MOD_NAME "." GAOKUN_DEV_UCSI, },
> > + {}
> > +};
> > +MODULE_DEVICE_TABLE(auxiliary, gaokun_ucsi_id_table);
> > +
> > +static struct auxiliary_driver gaokun_ucsi_driver = {
> > + .name = GAOKUN_DEV_UCSI,
> > + .id_table = gaokun_ucsi_id_table,
> > + .probe = gaokun_ucsi_probe,
> > + .remove = gaokun_ucsi_remove,
> > +};
> > +
> > +module_auxiliary_driver(gaokun_ucsi_driver);
> > +
> > +MODULE_DESCRIPTION("HUAWEI Matebook E Go UCSI driver");
> > +MODULE_LICENSE("GPL");
>
Best Wishes,
Pengyu
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 1/5] dt-bindings: platform: Add Huawei Matebook E Go EC
2024-12-28 11:34 ` [PATCH 1/5] dt-bindings: platform: Add Huawei Matebook E Go EC Pengyu Luo
@ 2024-12-29 4:08 ` Dmitry Baryshkov
2024-12-29 9:04 ` [PATCH 2/5] platform: arm64: add Huawei Matebook E Go (sc8280xp) EC driver Pengyu Luo
2024-12-29 9:43 ` [PATCH 1/5] dt-bindings: platform: Add Huawei Matebook E Go EC Krzysztof Kozlowski
1 sibling, 1 reply; 51+ messages in thread
From: Dmitry Baryshkov @ 2024-12-29 4:08 UTC (permalink / raw)
To: Pengyu Luo
Cc: krzk, andersson, bryan.odonoghue, conor+dt, devicetree, gregkh,
hdegoede, heikki.krogerus, ilpo.jarvinen, konradybcio, krzk+dt,
linux-arm-msm, linux-kernel, linux-pm, linux-usb, nikita,
platform-driver-x86, robh, sre
On Sat, Dec 28, 2024 at 07:34:37PM +0800, Pengyu Luo wrote:
> > On Sat, Dec 28, 2024 at 5:58 PM Krzysztof Kozlowski <krzk@kernel.org> wrote:
> > On 27/12/2024 18:13, Pengyu Luo wrote:
> > > +
> > > +#include <linux/platform_data/huawei-gaokun-ec.h>
> > > +
> > > +#define EC_EVENT 0x06
> > > +
> > > +/* Also can be found in ACPI specification 12.3 */
> > > +#define EC_READ 0x80
> > > +#define EC_WRITE 0x81
> > > +#define EC_BURST 0x82
> > > +#define EC_QUERY 0x84
> > > +
> > > +
> > > +#define EC_EVENT_LID 0x81
> > > +
> > > +#define EC_LID_STATE 0x80
> > > +#define EC_LID_OPEN BIT(1)
> > > +
> > > +#define UCSI_REG_SIZE 7
> > > +
> > > +/* for tx, command sequences are arranged as
> >
> > Use Linux style comments, see coding style.
> >
>
> Agree
>
> > > + * {master_cmd, slave_cmd, data_len, data_seq}
> > > + */
> > > +#define REQ_HDR_SIZE 3
> > > +#define INPUT_SIZE_OFFSET 2
> > > +#define INPUT_DATA_OFFSET 3
> > > +
> > > +/* for rx, data sequences are arranged as
> > > + * {status, data_len(unreliable), data_seq}
> > > + */
> > > +#define RESP_HDR_SIZE 2
> > > +#define DATA_OFFSET 2
> > > +
> > > +
> > > +struct gaokun_ec {
> > > + struct i2c_client *client;
> > > + struct mutex lock;
> >
> > Missing doc. Run Checkpatch --strict, so you will know what is missing here.
> >
>
> I see. A comment for mutex lock.
>
> > > + struct blocking_notifier_head notifier_list;
> > > + struct input_dev *idev;
> > > + bool suspended;
> > > +};
> > > +
> >
> >
> >
> > ...
> >
> > > +
> > > +static DEVICE_ATTR_RO(temperature);
> > > +
> > > +static struct attribute *gaokun_wmi_features_attrs[] = {
> > > + &dev_attr_charge_control_thresholds.attr,
> > > + &dev_attr_smart_charge_param.attr,
> > > + &dev_attr_smart_charge.attr,
> > > + &dev_attr_fn_lock_state.attr,
> > > + &dev_attr_temperature.attr,
> > > + NULL,
> > > +};
> >
> >
> > No, don't expose your own interface. Charging is already exposed by
> > power supply framework. Temperature by hwmon sensors. Drop all these and
> > never re-implement existing kernel user-space interfaces.
> >
>
> I don't quite understand what you mean. You mean I should use hwmon
> interface like hwmon_device_register_with_groups to register it, right?
> As for battery, get/set_propery allow us to handle charging thresholds
> things, but there are smart_charge_param, smart_charge and fn_lock to handle.
Please push the smart_* to the PSY driver. At least it makes sense to
move those. I'm not sure about the fn_lock one. If you have a separate
EC-based input device, it should go to it. If not, let's keep it in the
base device.
>
> >
> > > diff --git a/include/linux/platform_data/huawei-gaokun-ec.h b/include/linux/platform_data/huawei-gaokun-ec.h
> > > new file mode 100644
> > > index 000000000..a649e9ecf
> > > --- /dev/null
> > > +++ b/include/linux/platform_data/huawei-gaokun-ec.h
> > > @@ -0,0 +1,90 @@
> > > +// SPDX-License-Identifier: GPL-2.0-only
> > > +/* Huawei Matebook E Go (sc8280xp) Embedded Controller
> > > + *
> > > + * Copyright (C) 2024 Pengyu Luo <mitltlatltl@gmail.com>
> > > + *
> > > + */
> > > +
> > > +#ifndef __HUAWEI_GAOKUN_EC_H__
> > > +#define __HUAWEI_GAOKUN_EC_H__
> > > +
> > > +#define GAOKUN_UCSI_CCI_SIZE 4
> > > +#define GAOKUN_UCSI_DATA_SIZE 16
> > > +#define GAOKUN_UCSI_READ_SIZE (GAOKUN_UCSI_CCI_SIZE + GAOKUN_UCSI_DATA_SIZE)
> > > +#define GAOKUN_UCSI_WRITE_SIZE 0x18
> > > +
> > > +#define GAOKUN_TZ_REG_NUM 20
> > > +#define GAOKUN_SMART_CHARGE_DATA_SIZE 4 /* mode, delay, start, end */
> > > +
> > > +/* -------------------------------------------------------------------------- */
> > > +
> > > +struct gaokun_ec;
> > > +struct notifier_block;
> >
> > Drop, include proper header instead.
> >
>
> I agree, I copy 'struct notifier_block;' from
> include/linux/platform_data/lenovo-yoga-c630.h
Please don't pollute header files with extra dependencies. It's usually
better to just forware-declare the struct instead of adding unnecessary
include.
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 3/5] usb: typec: ucsi: add Huawei Matebook E Go (sc8280xp) ucsi driver
2024-12-27 17:13 ` [PATCH 3/5] usb: typec: ucsi: add Huawei Matebook E Go (sc8280xp) ucsi driver Pengyu Luo
2024-12-28 13:06 ` Bryan O'Donoghue
@ 2024-12-29 4:40 ` Dmitry Baryshkov
2024-12-29 9:05 ` Pengyu Luo
2024-12-29 16:15 ` Markus Elfring
2 siblings, 1 reply; 51+ messages in thread
From: Dmitry Baryshkov @ 2024-12-29 4:40 UTC (permalink / raw)
To: Pengyu Luo
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson,
Konrad Dybcio, Hans de Goede, Ilpo Järvinen,
Bryan O'Donoghue, Sebastian Reichel, Heikki Krogerus,
Greg Kroah-Hartman, devicetree, linux-kernel, linux-arm-msm,
platform-driver-x86, linux-pm, linux-usb, Nikita Travkin
On Sat, Dec 28, 2024 at 01:13:51AM +0800, Pengyu Luo wrote:
> The Huawei Matebook E Go (sc8280xp) tablet provides implements UCSI
> interface in the onboard EC. Add the glue driver to interface the
> platform's UCSI implementation.
>
> Signed-off-by: Pengyu Luo <mitltlatltl@gmail.com>
> ---
> drivers/usb/typec/ucsi/Kconfig | 9 +
> drivers/usb/typec/ucsi/Makefile | 1 +
> drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c | 481 ++++++++++++++++++++
> 3 files changed, 491 insertions(+)
> create mode 100644 drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c
>
> diff --git a/drivers/usb/typec/ucsi/Kconfig b/drivers/usb/typec/ucsi/Kconfig
> index 680e1b87b..0d0f07488 100644
> --- a/drivers/usb/typec/ucsi/Kconfig
> +++ b/drivers/usb/typec/ucsi/Kconfig
> @@ -78,4 +78,13 @@ config UCSI_LENOVO_YOGA_C630
> To compile the driver as a module, choose M here: the module will be
> called ucsi_yoga_c630.
>
> +config UCSI_HUAWEI_GAOKUN
> + tristate "UCSI Interface Driver for Huawei Matebook E Go (sc8280xp)"
> + depends on EC_HUAWEI_GAOKUN
> + help
> + This driver enables UCSI support on the Huawei Matebook E Go tablet.
> +
> + To compile the driver as a module, choose M here: the module will be
> + called ucsi_huawei_gaokun.
> +
> endif
> diff --git a/drivers/usb/typec/ucsi/Makefile b/drivers/usb/typec/ucsi/Makefile
> index aed41d238..0b400122b 100644
> --- a/drivers/usb/typec/ucsi/Makefile
> +++ b/drivers/usb/typec/ucsi/Makefile
> @@ -22,3 +22,4 @@ obj-$(CONFIG_UCSI_CCG) += ucsi_ccg.o
> obj-$(CONFIG_UCSI_STM32G0) += ucsi_stm32g0.o
> obj-$(CONFIG_UCSI_PMIC_GLINK) += ucsi_glink.o
> obj-$(CONFIG_UCSI_LENOVO_YOGA_C630) += ucsi_yoga_c630.o
> +obj-$(CONFIG_UCSI_HUAWEI_GAOKUN) += ucsi_huawei_gaokun.o
> diff --git a/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c
> new file mode 100644
> index 000000000..84ed0407d
> --- /dev/null
> +++ b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c
> @@ -0,0 +1,481 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * ucsi-huawei-gaokun - A UCSI driver for HUAWEI Matebook E Go (sc8280xp)
> + *
> + * reference: drivers/usb/typec/ucsi/ucsi_yoga_c630.c
> + * drivers/usb/typec/ucsi/ucsi_glink.c
> + * drivers/soc/qcom/pmic_glink_altmode.c
> + *
> + * Copyright (C) 2024 Pengyu Luo <mitltlatltl@gmail.com>
> + */
> +
> +#include <linux/auxiliary_bus.h>
> +#include <linux/bitops.h>
> +#include <linux/completion.h>
> +#include <linux/container_of.h>
> +#include <linux/delay.h>
> +#include <linux/module.h>
> +#include <linux/notifier.h>
> +#include <linux/of.h>
> +#include <linux/string.h>
> +#include <linux/workqueue_types.h>
> +
> +#include <linux/usb/pd_vdo.h>
> +#include <drm/bridge/aux-bridge.h>
> +
> +#include "ucsi.h"
> +#include <linux/platform_data/huawei-gaokun-ec.h>
> +
> +
> +#define EC_EVENT_UCSI 0x21
> +#define EC_EVENT_USB 0x22
> +
> +#define GAOKUN_CCX_MASK GENMASK(1, 0)
> +#define GAOKUN_MUX_MASK GENMASK(3, 2)
> +
> +#define GAOKUN_DPAM_MASK GENMASK(3, 0)
> +#define GAOKUN_HPD_STATE_MASK BIT(4)
> +#define GAOKUN_HPD_IRQ_MASK BIT(5)
> +
> +#define CCX_TO_ORI(ccx) (++ccx % 3)
> +
> +#define GET_IDX(updt) (ffs(updt) - 1)
> +
> +/* Configuration Channel Extension */
> +enum gaokun_ucsi_ccx {
> + USBC_CCX_NORMAL,
> + USBC_CCX_REVERSE,
> + USBC_CCX_NONE,
> +};
> +
> +enum gaokun_ucsi_mux {
> + USBC_MUX_NONE,
> + USBC_MUX_USB_2L,
> + USBC_MUX_DP_4L,
> + USBC_MUX_USB_DP,
> +};
> +
> +struct gaokun_ucsi_reg {
> + u8 port_num;
> + u8 port_updt;
> + u8 port_data[4];
> + u8 checksum;
> + u8 reserved;
> +} __packed;
> +
> +struct gaokun_ucsi_port {
> + struct completion usb_ack;
> + spinlock_t lock;
> +
> + struct gaokun_ucsi *ucsi;
> + struct auxiliary_device *bridge;
> +
> + int idx;
> + enum gaokun_ucsi_ccx ccx;
> + enum gaokun_ucsi_mux mux;
> + u8 mode;
> + u16 svid;
> + u8 hpd_state;
> + u8 hpd_irq;
> +};
> +
> +struct gaokun_ucsi {
> + struct gaokun_ec *ec;
> + struct ucsi *ucsi;
> + struct gaokun_ucsi_port *ports;
> + struct device *dev;
> + struct work_struct work;
> + struct notifier_block nb;
> + u16 version;
> + u8 port_num;
> +};
> +
> +/* -------------------------------------------------------------------------- */
> +/* For UCSI */
> +
> +static int gaokun_ucsi_read_version(struct ucsi *ucsi, u16 *version)
> +{
> + struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi);
> +
> + *version = uec->version;
> +
> + return 0;
> +}
> +
> +static int gaokun_ucsi_read_cci(struct ucsi *ucsi, u32 *cci)
> +{
> + struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi);
> + u8 buf[GAOKUN_UCSI_READ_SIZE];
> + int ret;
> +
> + ret = gaokun_ec_ucsi_read(uec->ec, buf);
> + if (ret)
> + return ret;
> +
> + memcpy(cci, buf, sizeof(*cci));
> +
> + return 0;
> +}
> +
> +static int gaokun_ucsi_read_message_in(struct ucsi *ucsi,
> + void *val, size_t val_len)
> +{
> + struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi);
> + u8 buf[GAOKUN_UCSI_READ_SIZE];
> + int ret;
> +
> + ret = gaokun_ec_ucsi_read(uec->ec, buf);
> + if (ret)
> + return ret;
> +
> + memcpy(val, buf + GAOKUN_UCSI_CCI_SIZE,
> + min(val_len, GAOKUN_UCSI_DATA_SIZE));
> +
> + return 0;
> +}
> +
> +static int gaokun_ucsi_async_control(struct ucsi *ucsi, u64 command)
> +{
> + struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi);
> + u8 buf[GAOKUN_UCSI_WRITE_SIZE] = {};
> +
> + memcpy(buf, &command, sizeof(command));
> +
> + return gaokun_ec_ucsi_write(uec->ec, buf);
> +}
> +
> +static void gaokun_ucsi_update_connector(struct ucsi_connector *con)
> +{
> + struct gaokun_ucsi *uec = ucsi_get_drvdata(con->ucsi);
> +
> + if (con->num > uec->port_num)
> + return;
> +
> + con->typec_cap.orientation_aware = true;
> +}
> +
> +static void gaokun_set_orientation(struct ucsi_connector *con,
> + struct gaokun_ucsi_port *port)
> +{
> + enum gaokun_ucsi_ccx ccx;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&port->lock, flags);
> + ccx = port->ccx;
> + spin_unlock_irqrestore(&port->lock, flags);
> +
> + typec_set_orientation(con->port, CCX_TO_ORI(ccx));
> +}
> +
> +static void gaokun_ucsi_connector_status(struct ucsi_connector *con)
> +{
> + struct gaokun_ucsi *uec = ucsi_get_drvdata(con->ucsi);
> + int idx;
> +
> + idx = con->num - 1;
> + if (con->num > uec->port_num) {
> + dev_warn(uec->ucsi->dev, "set orientation out of range: con%d\n", idx);
> + return;
> + }
> +
> + gaokun_set_orientation(con, &uec->ports[idx]);
> +}
> +
> +const struct ucsi_operations gaokun_ucsi_ops = {
> + .read_version = gaokun_ucsi_read_version,
> + .read_cci = gaokun_ucsi_read_cci,
> + .read_message_in = gaokun_ucsi_read_message_in,
> + .sync_control = ucsi_sync_control_common,
> + .async_control = gaokun_ucsi_async_control,
> + .update_connector = gaokun_ucsi_update_connector,
> + .connector_status = gaokun_ucsi_connector_status,
> +};
> +
> +/* -------------------------------------------------------------------------- */
> +/* For Altmode */
> +
> +static void gaokun_ucsi_port_update(struct gaokun_ucsi_port *port,
> + const u8 *port_data)
> +{
> + unsigned long flags;
> + u8 dcc, ddi;
> + int offset = port->idx * 2; /* every port has 2 Bytes data */
> +
> + dcc = port_data[offset];
> + ddi = port_data[offset + 1];
What is dcc and ddi? Are those just names from the DSDT?
> +
> + spin_lock_irqsave(&port->lock, flags);
> +
> + port->ccx = FIELD_GET(GAOKUN_CCX_MASK, dcc);
> + port->mux = FIELD_GET(GAOKUN_MUX_MASK, dcc);
> + port->mode = FIELD_GET(GAOKUN_DPAM_MASK, ddi);
> + port->hpd_state = FIELD_GET(GAOKUN_HPD_STATE_MASK, ddi);
> + port->hpd_irq = FIELD_GET(GAOKUN_HPD_IRQ_MASK, ddi);
> +
> + switch (port->mux) {
> + case USBC_MUX_NONE:
> + port->svid = 0;
> + break;
> + case USBC_MUX_USB_2L:
> + port->svid = USB_SID_PD;
> + break;
> + case USBC_MUX_DP_4L:
> + case USBC_MUX_USB_DP:
> + port->svid = USB_SID_DISPLAYPORT;
> + if (port->ccx == USBC_CCX_REVERSE)
> + port->mode -= 6;
I'd prefer it this were more explicit about what is happening.
> + break;
> + default:
> + break;
> + }
> +
> + spin_unlock_irqrestore(&port->lock, flags);
> +}
> +
> +static int gaokun_ucsi_refresh(struct gaokun_ucsi *uec)
> +{
> + struct gaokun_ucsi_reg ureg;
> + int ret, idx;
> +
> + ret = gaokun_ec_ucsi_get_reg(uec->ec, (u8 *)&ureg);
> + if (ret)
> + return -EIO;
> +
> + uec->port_num = ureg.port_num;
> + idx = GET_IDX(ureg.port_updt);
> +
> + if (idx >= 0 && idx < ureg.port_num)
> + gaokun_ucsi_port_update(&uec->ports[idx], ureg.port_data);
> +
> + return idx;
> +}
> +
> +static void gaokun_ucsi_handle_altmode(struct gaokun_ucsi_port *port)
> +{
> + struct gaokun_ucsi *uec = port->ucsi;
> + int idx = port->idx;
> +
> + if (idx >= uec->ucsi->cap.num_connectors || !uec->ucsi->connector) {
> + dev_warn(uec->ucsi->dev, "altmode port out of range: %d\n", idx);
> + return;
> + }
> +
> + /* UCSI callback .connector_status() have set orientation */
> + if (port->bridge)
> + drm_aux_hpd_bridge_notify(&port->bridge->dev,
> + port->hpd_state ?
> + connector_status_connected :
> + connector_status_disconnected);
Does your platform report any altmodes? What do you see in
/sys/class/typec/port0/port0.*/ ?
> +
> + gaokun_ec_ucsi_pan_ack(uec->ec, port->idx);
> +}
> +
> +static void gaokun_ucsi_altmode_notify_ind(struct gaokun_ucsi *uec)
> +{
> + int idx;
> +
> + idx = gaokun_ucsi_refresh(uec);
> + if (idx < 0)
> + gaokun_ec_ucsi_pan_ack(uec->ec, idx);
> + else
> + gaokun_ucsi_handle_altmode(&uec->ports[idx]);
> +}
> +
> +/*
> + * USB event is necessary for enabling altmode, the event should follow
> + * UCSI event, if not after timeout(this notify may be disabled somehow),
> + * then force to enable altmode.
> + */
> +static void gaokun_ucsi_handle_no_usb_event(struct gaokun_ucsi *uec, int idx)
> +{
> + struct gaokun_ucsi_port *port;
> +
> + port = &uec->ports[idx];
> + if (!wait_for_completion_timeout(&port->usb_ack, 2 * HZ)) {
> + dev_warn(uec->dev, "No USB EVENT, triggered by UCSI EVENT");
> + gaokun_ucsi_altmode_notify_ind(uec);
> + }
> +}
> +
> +static int gaokun_ucsi_notify(struct notifier_block *nb,
> + unsigned long action, void *data)
> +{
> + u32 cci;
> + struct gaokun_ucsi *uec = container_of(nb, struct gaokun_ucsi, nb);
> +
> + switch (action) {
> + case EC_EVENT_USB:
> + gaokun_ucsi_altmode_notify_ind(uec);
> + return NOTIFY_OK;
> +
> + case EC_EVENT_UCSI:
> + uec->ucsi->ops->read_cci(uec->ucsi, &cci);
> + ucsi_notify_common(uec->ucsi, cci);
> + if (UCSI_CCI_CONNECTOR(cci))
> + gaokun_ucsi_handle_no_usb_event(uec, UCSI_CCI_CONNECTOR(cci) - 1);
> +
> + return NOTIFY_OK;
> +
> + default:
> + return NOTIFY_DONE;
> + }
> +}
> +
> +static int gaokun_ucsi_get_port_num(struct gaokun_ucsi *uec)
> +{
> + struct gaokun_ucsi_reg ureg;
> + int ret;
> +
> + ret = gaokun_ec_ucsi_get_reg(uec->ec, (u8 *)&ureg);
> +
> + return ret ? 0 : ureg.port_num;
> +}
> +
> +static int gaokun_ucsi_ports_init(struct gaokun_ucsi *uec)
> +{
> + u32 port;
> + int i, ret, port_num;
> + struct device *dev = uec->dev;
> + struct gaokun_ucsi_port *ucsi_port;
> + struct fwnode_handle *fwnode;
> +
> + port_num = gaokun_ucsi_get_port_num(uec);
> + uec->port_num = port_num;
> +
> + uec->ports = devm_kzalloc(dev, port_num * sizeof(*(uec->ports)),
> + GFP_KERNEL);
> + if (!uec->ports)
> + return -ENOMEM;
> +
> + for (i = 0; i < port_num; ++i) {
> + ucsi_port = &uec->ports[i];
> + ucsi_port->ccx = USBC_CCX_NONE;
> + ucsi_port->idx = i;
> + ucsi_port->ucsi = uec;
> + init_completion(&ucsi_port->usb_ack);
> + spin_lock_init(&ucsi_port->lock);
> + }
> +
> + device_for_each_child_node(dev, fwnode) {
> + ret = fwnode_property_read_u32(fwnode, "reg", &port);
> + if (ret < 0) {
> + dev_err(dev, "missing reg property of %pOFn\n", fwnode);
> + fwnode_handle_put(fwnode);
> + return ret;
> + }
> +
> + if (port >= port_num) {
> + dev_warn(dev, "invalid connector number %d, ignoring\n", port);
> + continue;
> + }
> +
> + ucsi_port = &uec->ports[port];
> + ucsi_port->bridge = devm_drm_dp_hpd_bridge_alloc(dev, to_of_node(fwnode));
> + if (IS_ERR(ucsi_port->bridge)) {
> + fwnode_handle_put(fwnode);
> + return PTR_ERR(ucsi_port->bridge);
> + }
> + }
> +
> + for (i = 0; i < port_num; i++) {
> + if (!uec->ports[i].bridge)
> + continue;
> +
> + ret = devm_drm_dp_hpd_bridge_add(dev, uec->ports[i].bridge);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static void gaokun_ucsi_register_worker(struct work_struct *work)
> +{
> + struct gaokun_ucsi *uec;
> + struct ucsi *ucsi;
> + int ret;
> +
> + uec = container_of(work, struct gaokun_ucsi, work);
> + ucsi = uec->ucsi;
> +
> + ucsi->quirks = UCSI_NO_PARTNER_PDOS | UCSI_DELAY_DEVICE_PDOS;
Does it crash in the same way as GLINK crashes (as you've set
UCSI_NO_PARTNER_PDOS)?
> +
> + ssleep(3); /* EC can't handle UCSI properly in the early stage */
> +
> + ret = gaokun_ec_register_notify(uec->ec, &uec->nb);
> + if (ret) {
> + dev_err_probe(ucsi->dev, ret, "notifier register failed\n");
> + return;
> + }
> +
> + ret = ucsi_register(ucsi);
> + if (ret)
> + dev_err_probe(ucsi->dev, ret, "ucsi register failed\n");
> +}
> +
> +static int gaokun_ucsi_register(struct gaokun_ucsi *uec)
Please inline
> +{
> + schedule_work(&uec->work);
> +
> + return 0;
> +}
> +
> +static int gaokun_ucsi_probe(struct auxiliary_device *adev,
> + const struct auxiliary_device_id *id)
> +{
> + struct gaokun_ec *ec = adev->dev.platform_data;
> + struct device *dev = &adev->dev;
> + struct gaokun_ucsi *uec;
> + int ret;
> +
> + uec = devm_kzalloc(dev, sizeof(*uec), GFP_KERNEL);
> + if (!uec)
> + return -ENOMEM;
> +
> + uec->ec = ec;
> + uec->dev = dev;
> + uec->version = 0x0100;
> + uec->nb.notifier_call = gaokun_ucsi_notify;
> +
> + INIT_WORK(&uec->work, gaokun_ucsi_register_worker);
> +
> + ret = gaokun_ucsi_ports_init(uec);
> + if (ret)
> + return ret;
> +
> + uec->ucsi = ucsi_create(dev, &gaokun_ucsi_ops);
> + if (IS_ERR(uec->ucsi))
> + return PTR_ERR(uec->ucsi);
> +
> + ucsi_set_drvdata(uec->ucsi, uec);
> + auxiliary_set_drvdata(adev, uec);
> +
> + return gaokun_ucsi_register(uec);
> +}
> +
> +static void gaokun_ucsi_remove(struct auxiliary_device *adev)
> +{
> + struct gaokun_ucsi *uec = auxiliary_get_drvdata(adev);
> +
> + gaokun_ec_unregister_notify(uec->ec, &uec->nb);
> + ucsi_unregister(uec->ucsi);
> + ucsi_destroy(uec->ucsi);
> +}
> +
> +static const struct auxiliary_device_id gaokun_ucsi_id_table[] = {
> + { .name = GAOKUN_MOD_NAME "." GAOKUN_DEV_UCSI, },
> + {}
> +};
> +MODULE_DEVICE_TABLE(auxiliary, gaokun_ucsi_id_table);
> +
> +static struct auxiliary_driver gaokun_ucsi_driver = {
> + .name = GAOKUN_DEV_UCSI,
> + .id_table = gaokun_ucsi_id_table,
> + .probe = gaokun_ucsi_probe,
> + .remove = gaokun_ucsi_remove,
> +};
> +
> +module_auxiliary_driver(gaokun_ucsi_driver);
> +
> +MODULE_DESCRIPTION("HUAWEI Matebook E Go UCSI driver");
> +MODULE_LICENSE("GPL");
> --
> 2.47.1
>
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 2/5] platform: arm64: add Huawei Matebook E Go (sc8280xp) EC driver
2024-12-29 4:08 ` Dmitry Baryshkov
@ 2024-12-29 9:04 ` Pengyu Luo
2024-12-29 9:44 ` Krzysztof Kozlowski
0 siblings, 1 reply; 51+ messages in thread
From: Pengyu Luo @ 2024-12-29 9:04 UTC (permalink / raw)
To: dmitry.baryshkov
Cc: andersson, bryan.odonoghue, conor+dt, devicetree, gregkh,
hdegoede, heikki.krogerus, ilpo.jarvinen, konradybcio, krzk+dt,
krzk, linux-arm-msm, linux-kernel, linux-pm, linux-usb,
mitltlatltl, nikita, platform-driver-x86, robh, sre
On Sun, Dec 29, 2024 at 12:08 PM Dmitry Baryshkov <dmitry.baryshkov@linaro.org> wrote:
> On Sat, Dec 28, 2024 at 07:34:37PM +0800, Pengyu Luo wrote:
> > > On Sat, Dec 28, 2024 at 5:58 PM Krzysztof Kozlowski <krzk@kernel.org> wrote:
> > > On 27/12/2024 18:13, Pengyu Luo wrote:
> > > > +
> > > > +#include <linux/platform_data/huawei-gaokun-ec.h>
> > > > +
> > > > +#define EC_EVENT 0x06
> > > > +
> > > > +/* Also can be found in ACPI specification 12.3 */
> > > > +#define EC_READ 0x80
> > > > +#define EC_WRITE 0x81
> > > > +#define EC_BURST 0x82
> > > > +#define EC_QUERY 0x84
> > > > +
> > > > +
> > > > +#define EC_EVENT_LID 0x81
> > > > +
> > > > +#define EC_LID_STATE 0x80
> > > > +#define EC_LID_OPEN BIT(1)
> > > > +
> > > > +#define UCSI_REG_SIZE 7
> > > > +
> > > > +/* for tx, command sequences are arranged as
> > >
> > > Use Linux style comments, see coding style.
> > >
> >
> > Agree
> >
> > > > + * {master_cmd, slave_cmd, data_len, data_seq}
> > > > + */
> > > > +#define REQ_HDR_SIZE 3
> > > > +#define INPUT_SIZE_OFFSET 2
> > > > +#define INPUT_DATA_OFFSET 3
> > > > +
> > > > +/* for rx, data sequences are arranged as
> > > > + * {status, data_len(unreliable), data_seq}
> > > > + */
> > > > +#define RESP_HDR_SIZE 2
> > > > +#define DATA_OFFSET 2
> > > > +
> > > > +
> > > > +struct gaokun_ec {
> > > > + struct i2c_client *client;
> > > > + struct mutex lock;
> > >
> > > Missing doc. Run Checkpatch --strict, so you will know what is missing here.
> > >
> >
> > I see. A comment for mutex lock.
> >
> > > > + struct blocking_notifier_head notifier_list;
> > > > + struct input_dev *idev;
> > > > + bool suspended;
> > > > +};
> > > > +
> > >
> > >
> > >
> > > ...
> > >
> > > > +
> > > > +static DEVICE_ATTR_RO(temperature);
> > > > +
> > > > +static struct attribute *gaokun_wmi_features_attrs[] = {
> > > > + &dev_attr_charge_control_thresholds.attr,
> > > > + &dev_attr_smart_charge_param.attr,
> > > > + &dev_attr_smart_charge.attr,
> > > > + &dev_attr_fn_lock_state.attr,
> > > > + &dev_attr_temperature.attr,
> > > > + NULL,
> > > > +};
> > >
> > >
> > > No, don't expose your own interface. Charging is already exposed by
> > > power supply framework. Temperature by hwmon sensors. Drop all these and
> > > never re-implement existing kernel user-space interfaces.
> > >
> >
> > I don't quite understand what you mean. You mean I should use hwmon
> > interface like hwmon_device_register_with_groups to register it, right?
> > As for battery, get/set_propery allow us to handle charging thresholds
> > things, but there are smart_charge_param, smart_charge and fn_lock to handle.
>
> Please push the smart_* to the PSY driver. At least it makes sense to
> move those. I'm not sure about the fn_lock one. If you have a separate
> EC-based input device, it should go to it. If not, let's keep it in the
> base device.
>
I see, so can I fix it in v2 like this?
- Using device_add_groups to register smart_* sysfs in PSY
- Using hwmon_device_register_with_groups to register thermal related sysfs in base driver
> >
> > >
> > > > diff --git a/include/linux/platform_data/huawei-gaokun-ec.h b/include/linux/platform_data/huawei-gaokun-ec.h
> > > > new file mode 100644
> > > > index 000000000..a649e9ecf
> > > > --- /dev/null
> > > > +++ b/include/linux/platform_data/huawei-gaokun-ec.h
> > > > @@ -0,0 +1,90 @@
> > > > +// SPDX-License-Identifier: GPL-2.0-only
> > > > +/* Huawei Matebook E Go (sc8280xp) Embedded Controller
> > > > + *
> > > > + * Copyright (C) 2024 Pengyu Luo <mitltlatltl@gmail.com>
> > > > + *
> > > > + */
> > > > +
> > > > +#ifndef __HUAWEI_GAOKUN_EC_H__
> > > > +#define __HUAWEI_GAOKUN_EC_H__
> > > > +
> > > > +#define GAOKUN_UCSI_CCI_SIZE 4
> > > > +#define GAOKUN_UCSI_DATA_SIZE 16
> > > > +#define GAOKUN_UCSI_READ_SIZE (GAOKUN_UCSI_CCI_SIZE + GAOKUN_UCSI_DATA_SIZE)
> > > > +#define GAOKUN_UCSI_WRITE_SIZE 0x18
> > > > +
> > > > +#define GAOKUN_TZ_REG_NUM 20
> > > > +#define GAOKUN_SMART_CHARGE_DATA_SIZE 4 /* mode, delay, start, end */
> > > > +
> > > > +/* -------------------------------------------------------------------------- */
> > > > +
> > > > +struct gaokun_ec;
> > > > +struct notifier_block;
> > >
> > > Drop, include proper header instead.
> > >
> >
> > I agree, I copy 'struct notifier_block;' from
> > include/linux/platform_data/lenovo-yoga-c630.h
>
> Please don't pollute header files with extra dependencies. It's usually
> better to just forware-declare the struct instead of adding unnecessary
> include.
>
Both of you are resonable. So how?
BTW, Krzysztof said
> > You need kerneldoc, in the C file, for all exported functions.
So Dmitry is here, I want to check again, should I add kerneldoc for all
exported functions? C630 one never added all kerneldocs. In my driver,
some function names have already indicated many things, some complex
functions have been doced.
Best wishes,
Pengyu
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 3/5] usb: typec: ucsi: add Huawei Matebook E Go (sc8280xp) ucsi driver
2024-12-29 4:40 ` Dmitry Baryshkov
@ 2024-12-29 9:05 ` Pengyu Luo
2025-01-06 3:33 ` Dmitry Baryshkov
0 siblings, 1 reply; 51+ messages in thread
From: Pengyu Luo @ 2024-12-29 9:05 UTC (permalink / raw)
To: dmitry.baryshkov
Cc: andersson, bryan.odonoghue, conor+dt, devicetree, gregkh,
hdegoede, heikki.krogerus, ilpo.jarvinen, konradybcio, krzk+dt,
linux-arm-msm, linux-kernel, linux-pm, linux-usb, mitltlatltl,
nikita, platform-driver-x86, robh, sre
On Sun, Dec 29, 2024 at 12:40 PM Dmitry Baryshkov <dmitry.baryshkov@linaro.org> wrote:
> On Sat, Dec 28, 2024 at 01:13:51AM +0800, Pengyu Luo wrote:
> > The Huawei Matebook E Go (sc8280xp) tablet provides implements UCSI
> > interface in the onboard EC. Add the glue driver to interface the
> > platform's UCSI implementation.
> >
> > Signed-off-by: Pengyu Luo <mitltlatltl@gmail.com>
> > ---
> > drivers/usb/typec/ucsi/Kconfig | 9 +
> > drivers/usb/typec/ucsi/Makefile | 1 +
> > drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c | 481 ++++++++++++++++++++
> > 3 files changed, 491 insertions(+)
> > create mode 100644 drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c
> >
> > diff --git a/drivers/usb/typec/ucsi/Kconfig b/drivers/usb/typec/ucsi/Kconfig
> > index 680e1b87b..0d0f07488 100644
> > --- a/drivers/usb/typec/ucsi/Kconfig
> > +++ b/drivers/usb/typec/ucsi/Kconfig
> > @@ -78,4 +78,13 @@ config UCSI_LENOVO_YOGA_C630
> > To compile the driver as a module, choose M here: the module will be
> > called ucsi_yoga_c630.
> >
> > +config UCSI_HUAWEI_GAOKUN
> > + tristate "UCSI Interface Driver for Huawei Matebook E Go (sc8280xp)"
> > + depends on EC_HUAWEI_GAOKUN
> > + help
> > + This driver enables UCSI support on the Huawei Matebook E Go tablet.
> > +
> > + To compile the driver as a module, choose M here: the module will be
> > + called ucsi_huawei_gaokun.
> > +
> > endif
> > diff --git a/drivers/usb/typec/ucsi/Makefile b/drivers/usb/typec/ucsi/Makefile
> > index aed41d238..0b400122b 100644
> > --- a/drivers/usb/typec/ucsi/Makefile
> > +++ b/drivers/usb/typec/ucsi/Makefile
> > @@ -22,3 +22,4 @@ obj-$(CONFIG_UCSI_CCG) += ucsi_ccg.o
> > obj-$(CONFIG_UCSI_STM32G0) += ucsi_stm32g0.o
> > obj-$(CONFIG_UCSI_PMIC_GLINK) += ucsi_glink.o
> > obj-$(CONFIG_UCSI_LENOVO_YOGA_C630) += ucsi_yoga_c630.o
> > +obj-$(CONFIG_UCSI_HUAWEI_GAOKUN) += ucsi_huawei_gaokun.o
> > diff --git a/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c
> > new file mode 100644
> > index 000000000..84ed0407d
> > --- /dev/null
> > +++ b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c
> > @@ -0,0 +1,481 @@
> > +// SPDX-License-Identifier: GPL-2.0-only
> > +/*
> > + * ucsi-huawei-gaokun - A UCSI driver for HUAWEI Matebook E Go (sc8280xp)
> > + *
> > + * reference: drivers/usb/typec/ucsi/ucsi_yoga_c630.c
> > + * drivers/usb/typec/ucsi/ucsi_glink.c
> > + * drivers/soc/qcom/pmic_glink_altmode.c
> > + *
> > + * Copyright (C) 2024 Pengyu Luo <mitltlatltl@gmail.com>
> > + */
> > +
> > +#include <linux/auxiliary_bus.h>
> > +#include <linux/bitops.h>
> > +#include <linux/completion.h>
> > +#include <linux/container_of.h>
> > +#include <linux/delay.h>
> > +#include <linux/module.h>
> > +#include <linux/notifier.h>
> > +#include <linux/of.h>
> > +#include <linux/string.h>
> > +#include <linux/workqueue_types.h>
> > +
> > +#include <linux/usb/pd_vdo.h>
> > +#include <drm/bridge/aux-bridge.h>
> > +
> > +#include "ucsi.h"
> > +#include <linux/platform_data/huawei-gaokun-ec.h>
> > +
> > +
> > +#define EC_EVENT_UCSI 0x21
> > +#define EC_EVENT_USB 0x22
> > +
> > +#define GAOKUN_CCX_MASK GENMASK(1, 0)
> > +#define GAOKUN_MUX_MASK GENMASK(3, 2)
> > +
> > +#define GAOKUN_DPAM_MASK GENMASK(3, 0)
> > +#define GAOKUN_HPD_STATE_MASK BIT(4)
> > +#define GAOKUN_HPD_IRQ_MASK BIT(5)
> > +
> > +#define CCX_TO_ORI(ccx) (++ccx % 3)
> > +
> > +#define GET_IDX(updt) (ffs(updt) - 1)
> > +
> > +/* Configuration Channel Extension */
> > +enum gaokun_ucsi_ccx {
> > + USBC_CCX_NORMAL,
> > + USBC_CCX_REVERSE,
> > + USBC_CCX_NONE,
> > +};
> > +
> > +enum gaokun_ucsi_mux {
> > + USBC_MUX_NONE,
> > + USBC_MUX_USB_2L,
> > + USBC_MUX_DP_4L,
> > + USBC_MUX_USB_DP,
> > +};
> > +
> > +struct gaokun_ucsi_reg {
> > + u8 port_num;
> > + u8 port_updt;
> > + u8 port_data[4];
> > + u8 checksum;
> > + u8 reserved;
> > +} __packed;
> > +
> > +struct gaokun_ucsi_port {
> > + struct completion usb_ack;
> > + spinlock_t lock;
> > +
> > + struct gaokun_ucsi *ucsi;
> > + struct auxiliary_device *bridge;
> > +
> > + int idx;
> > + enum gaokun_ucsi_ccx ccx;
> > + enum gaokun_ucsi_mux mux;
> > + u8 mode;
> > + u16 svid;
> > + u8 hpd_state;
> > + u8 hpd_irq;
> > +};
> > +
> > +struct gaokun_ucsi {
> > + struct gaokun_ec *ec;
> > + struct ucsi *ucsi;
> > + struct gaokun_ucsi_port *ports;
> > + struct device *dev;
> > + struct work_struct work;
> > + struct notifier_block nb;
> > + u16 version;
> > + u8 port_num;
> > +};
> > +
> > +/* -------------------------------------------------------------------------- */
> > +/* For UCSI */
> > +
> > +static int gaokun_ucsi_read_version(struct ucsi *ucsi, u16 *version)
> > +{
> > + struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi);
> > +
> > + *version = uec->version;
> > +
> > + return 0;
> > +}
> > +
> > +static int gaokun_ucsi_read_cci(struct ucsi *ucsi, u32 *cci)
> > +{
> > + struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi);
> > + u8 buf[GAOKUN_UCSI_READ_SIZE];
> > + int ret;
> > +
> > + ret = gaokun_ec_ucsi_read(uec->ec, buf);
> > + if (ret)
> > + return ret;
> > +
> > + memcpy(cci, buf, sizeof(*cci));
> > +
> > + return 0;
> > +}
> > +
> > +static int gaokun_ucsi_read_message_in(struct ucsi *ucsi,
> > + void *val, size_t val_len)
> > +{
> > + struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi);
> > + u8 buf[GAOKUN_UCSI_READ_SIZE];
> > + int ret;
> > +
> > + ret = gaokun_ec_ucsi_read(uec->ec, buf);
> > + if (ret)
> > + return ret;
> > +
> > + memcpy(val, buf + GAOKUN_UCSI_CCI_SIZE,
> > + min(val_len, GAOKUN_UCSI_DATA_SIZE));
> > +
> > + return 0;
> > +}
> > +
> > +static int gaokun_ucsi_async_control(struct ucsi *ucsi, u64 command)
> > +{
> > + struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi);
> > + u8 buf[GAOKUN_UCSI_WRITE_SIZE] = {};
> > +
> > + memcpy(buf, &command, sizeof(command));
> > +
> > + return gaokun_ec_ucsi_write(uec->ec, buf);
> > +}
> > +
> > +static void gaokun_ucsi_update_connector(struct ucsi_connector *con)
> > +{
> > + struct gaokun_ucsi *uec = ucsi_get_drvdata(con->ucsi);
> > +
> > + if (con->num > uec->port_num)
> > + return;
> > +
> > + con->typec_cap.orientation_aware = true;
> > +}
> > +
> > +static void gaokun_set_orientation(struct ucsi_connector *con,
> > + struct gaokun_ucsi_port *port)
> > +{
> > + enum gaokun_ucsi_ccx ccx;
> > + unsigned long flags;
> > +
> > + spin_lock_irqsave(&port->lock, flags);
> > + ccx = port->ccx;
> > + spin_unlock_irqrestore(&port->lock, flags);
> > +
> > + typec_set_orientation(con->port, CCX_TO_ORI(ccx));
> > +}
> > +
> > +static void gaokun_ucsi_connector_status(struct ucsi_connector *con)
> > +{
> > + struct gaokun_ucsi *uec = ucsi_get_drvdata(con->ucsi);
> > + int idx;
> > +
> > + idx = con->num - 1;
> > + if (con->num > uec->port_num) {
> > + dev_warn(uec->ucsi->dev, "set orientation out of range: con%d\n", idx);
> > + return;
> > + }
> > +
> > + gaokun_set_orientation(con, &uec->ports[idx]);
> > +}
> > +
> > +const struct ucsi_operations gaokun_ucsi_ops = {
> > + .read_version = gaokun_ucsi_read_version,
> > + .read_cci = gaokun_ucsi_read_cci,
> > + .read_message_in = gaokun_ucsi_read_message_in,
> > + .sync_control = ucsi_sync_control_common,
> > + .async_control = gaokun_ucsi_async_control,
> > + .update_connector = gaokun_ucsi_update_connector,
> > + .connector_status = gaokun_ucsi_connector_status,
> > +};
> > +
> > +/* -------------------------------------------------------------------------- */
> > +/* For Altmode */
> > +
> > +static void gaokun_ucsi_port_update(struct gaokun_ucsi_port *port,
> > + const u8 *port_data)
> > +{
> > + unsigned long flags;
> > + u8 dcc, ddi;
> > + int offset = port->idx * 2; /* every port has 2 Bytes data */
> > +
> > + dcc = port_data[offset];
> > + ddi = port_data[offset + 1];
>
> What is dcc and ddi? Are those just names from the DSDT?
>
Yes, DSDT's inventions. Huawei one uses that.
Some additional information, you can check the following in sc8280xp or
xelite based dsdt.
In UPAN(usbc pinassignment notification), PBUF carries a pan info, which
is a 8B data, {BPID, BORI, BMUX, BVID(2B), BSID(2B), BSSD} which stands for
port_id, orientation of port, mux state, USB-IF vendor id, USB-IF standard id,
I don't know the BSSD, (if linaro know something?)
but according to drivers/soc/qcom/pmic_glink_altmode.c
BSSD is related to pin assignment(mode field), hpd_state, hpd_irq, ddi is
something equivalent to BSSD. dcc is something equivalent to BORI and BMUX.
> > +
> > + spin_lock_irqsave(&port->lock, flags);
> > +
> > + port->ccx = FIELD_GET(GAOKUN_CCX_MASK, dcc);
> > + port->mux = FIELD_GET(GAOKUN_MUX_MASK, dcc);
> > + port->mode = FIELD_GET(GAOKUN_DPAM_MASK, ddi);
> > + port->hpd_state = FIELD_GET(GAOKUN_HPD_STATE_MASK, ddi);
> > + port->hpd_irq = FIELD_GET(GAOKUN_HPD_IRQ_MASK, ddi);
> > +
> > + switch (port->mux) {
> > + case USBC_MUX_NONE:
> > + port->svid = 0;
> > + break;
> > + case USBC_MUX_USB_2L:
> > + port->svid = USB_SID_PD;
> > + break;
> > + case USBC_MUX_DP_4L:
> > + case USBC_MUX_USB_DP:
> > + port->svid = USB_SID_DISPLAYPORT;
> > + if (port->ccx == USBC_CCX_REVERSE)
> > + port->mode -= 6;
>
> I'd prefer it this were more explicit about what is happening.
>
If orientation is reverse, then we should minus 6, EC's logic.
I will add a comment for it. Actually, this field is unused, I don't
find the mux yet, so I cannot set it with this field. But I don't want
to make things imcomplete, so keep it.
Let me go off the topic, on my device, I can just use drm_aux_hpd_bridge_notify
to enable altmode, usb functions well after I pluged out, I don't need set mode
switch(orientation switch is required if orientation is reverse), which is quiet
similar to Acer aspire 1. Is mux controlled also by QMP combo phy(see [1])?
> > + break;
> > + default:
> > + break;
> > + }
> > +
> > + spin_unlock_irqrestore(&port->lock, flags);
> > +}
> > +
> > +static int gaokun_ucsi_refresh(struct gaokun_ucsi *uec)
> > +{
> > + struct gaokun_ucsi_reg ureg;
> > + int ret, idx;
> > +
> > + ret = gaokun_ec_ucsi_get_reg(uec->ec, (u8 *)&ureg);
> > + if (ret)
> > + return -EIO;
> > +
> > + uec->port_num = ureg.port_num;
> > + idx = GET_IDX(ureg.port_updt);
> > +
> > + if (idx >= 0 && idx < ureg.port_num)
> > + gaokun_ucsi_port_update(&uec->ports[idx], ureg.port_data);
> > +
> > + return idx;
> > +}
> > +
> > +static void gaokun_ucsi_handle_altmode(struct gaokun_ucsi_port *port)
> > +{
> > + struct gaokun_ucsi *uec = port->ucsi;
> > + int idx = port->idx;
> > +
> > + if (idx >= uec->ucsi->cap.num_connectors || !uec->ucsi->connector) {
> > + dev_warn(uec->ucsi->dev, "altmode port out of range: %d\n", idx);
> > + return;
> > + }
> > +
> > + /* UCSI callback .connector_status() have set orientation */
> > + if (port->bridge)
> > + drm_aux_hpd_bridge_notify(&port->bridge->dev,
> > + port->hpd_state ?
> > + connector_status_connected :
> > + connector_status_disconnected);
>
> Does your platform report any altmodes? What do you see in
> /sys/class/typec/port0/port0.*/ ?
>
/sys/class/typec/port0/port0.0:
active mode mode1 power svid uevent vdo
/sys/class/typec/port0/port0.1:
active mode mode1 power svid uevent vdo
/sys/class/typec/port0/port0.2:
active mode mode1 power svid uevent vdo
/sys/class/typec/port0/port0.3:
active mode mode2 power svid uevent vdo
/sys/class/typec/port0/port0.4:
active mode mode3 power svid uevent vdo
> > +
> > + gaokun_ec_ucsi_pan_ack(uec->ec, port->idx);
> > +}
> > +
> > +static void gaokun_ucsi_altmode_notify_ind(struct gaokun_ucsi *uec)
> > +{
> > + int idx;
> > +
> > + idx = gaokun_ucsi_refresh(uec);
> > + if (idx < 0)
> > + gaokun_ec_ucsi_pan_ack(uec->ec, idx);
> > + else
> > + gaokun_ucsi_handle_altmode(&uec->ports[idx]);
> > +}
> > +
> > +/*
> > + * USB event is necessary for enabling altmode, the event should follow
> > + * UCSI event, if not after timeout(this notify may be disabled somehow),
> > + * then force to enable altmode.
> > + */
> > +static void gaokun_ucsi_handle_no_usb_event(struct gaokun_ucsi *uec, int idx)
> > +{
> > + struct gaokun_ucsi_port *port;
> > +
> > + port = &uec->ports[idx];
> > + if (!wait_for_completion_timeout(&port->usb_ack, 2 * HZ)) {
> > + dev_warn(uec->dev, "No USB EVENT, triggered by UCSI EVENT");
> > + gaokun_ucsi_altmode_notify_ind(uec);
> > + }
> > +}
> > +
> > +static int gaokun_ucsi_notify(struct notifier_block *nb,
> > + unsigned long action, void *data)
> > +{
> > + u32 cci;
> > + struct gaokun_ucsi *uec = container_of(nb, struct gaokun_ucsi, nb);
> > +
> > + switch (action) {
> > + case EC_EVENT_USB:
> > + gaokun_ucsi_altmode_notify_ind(uec);
> > + return NOTIFY_OK;
> > +
> > + case EC_EVENT_UCSI:
> > + uec->ucsi->ops->read_cci(uec->ucsi, &cci);
> > + ucsi_notify_common(uec->ucsi, cci);
> > + if (UCSI_CCI_CONNECTOR(cci))
> > + gaokun_ucsi_handle_no_usb_event(uec, UCSI_CCI_CONNECTOR(cci) - 1);
> > +
> > + return NOTIFY_OK;
> > +
> > + default:
> > + return NOTIFY_DONE;
> > + }
> > +}
> > +
> > +static int gaokun_ucsi_get_port_num(struct gaokun_ucsi *uec)
> > +{
> > + struct gaokun_ucsi_reg ureg;
> > + int ret;
> > +
> > + ret = gaokun_ec_ucsi_get_reg(uec->ec, (u8 *)&ureg);
> > +
> > + return ret ? 0 : ureg.port_num;
> > +}
> > +
> > +static int gaokun_ucsi_ports_init(struct gaokun_ucsi *uec)
> > +{
> > + u32 port;
> > + int i, ret, port_num;
> > + struct device *dev = uec->dev;
> > + struct gaokun_ucsi_port *ucsi_port;
> > + struct fwnode_handle *fwnode;
> > +
> > + port_num = gaokun_ucsi_get_port_num(uec);
> > + uec->port_num = port_num;
> > +
> > + uec->ports = devm_kzalloc(dev, port_num * sizeof(*(uec->ports)),
> > + GFP_KERNEL);
> > + if (!uec->ports)
> > + return -ENOMEM;
> > +
> > + for (i = 0; i < port_num; ++i) {
> > + ucsi_port = &uec->ports[i];
> > + ucsi_port->ccx = USBC_CCX_NONE;
> > + ucsi_port->idx = i;
> > + ucsi_port->ucsi = uec;
> > + init_completion(&ucsi_port->usb_ack);
> > + spin_lock_init(&ucsi_port->lock);
> > + }
> > +
> > + device_for_each_child_node(dev, fwnode) {
> > + ret = fwnode_property_read_u32(fwnode, "reg", &port);
> > + if (ret < 0) {
> > + dev_err(dev, "missing reg property of %pOFn\n", fwnode);
> > + fwnode_handle_put(fwnode);
> > + return ret;
> > + }
> > +
> > + if (port >= port_num) {
> > + dev_warn(dev, "invalid connector number %d, ignoring\n", port);
> > + continue;
> > + }
> > +
> > + ucsi_port = &uec->ports[port];
> > + ucsi_port->bridge = devm_drm_dp_hpd_bridge_alloc(dev, to_of_node(fwnode));
> > + if (IS_ERR(ucsi_port->bridge)) {
> > + fwnode_handle_put(fwnode);
> > + return PTR_ERR(ucsi_port->bridge);
> > + }
> > + }
> > +
> > + for (i = 0; i < port_num; i++) {
> > + if (!uec->ports[i].bridge)
> > + continue;
> > +
> > + ret = devm_drm_dp_hpd_bridge_add(dev, uec->ports[i].bridge);
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static void gaokun_ucsi_register_worker(struct work_struct *work)
> > +{
> > + struct gaokun_ucsi *uec;
> > + struct ucsi *ucsi;
> > + int ret;
> > +
> > + uec = container_of(work, struct gaokun_ucsi, work);
> > + ucsi = uec->ucsi;
> > +
> > + ucsi->quirks = UCSI_NO_PARTNER_PDOS | UCSI_DELAY_DEVICE_PDOS;
>
> Does it crash in the same way as GLINK crashes (as you've set
> UCSI_NO_PARTNER_PDOS)?
>
Yes, no partner can be detected, I checked. I think it is also handled by
the firmware As you said in [2]
> In some obscure cases (Qualcomm PMIC Glink) altmode is completely
> handled by the firmware. Linux does not get proper partner altmode info.
> > +
> > + ssleep(3); /* EC can't handle UCSI properly in the early stage */
> > +
> > + ret = gaokun_ec_register_notify(uec->ec, &uec->nb);
> > + if (ret) {
> > + dev_err_probe(ucsi->dev, ret, "notifier register failed\n");
> > + return;
> > + }
> > +
> > + ret = ucsi_register(ucsi);
> > + if (ret)
> > + dev_err_probe(ucsi->dev, ret, "ucsi register failed\n");
> > +}
> > +
> > +static int gaokun_ucsi_register(struct gaokun_ucsi *uec)
>
> Please inline
>
I see.
Best wishes
Pengyu
[1] https://elixir.bootlin.com/linux/v6.12.5/source/drivers/phy/qualcomm/phy-qcom-qmp-combo.c#L2679
[2] https://lore.kernel.org/lkml/20240416-ucsi-glink-altmode-v1-0-890db00877ac@linaro.org
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 1/5] dt-bindings: platform: Add Huawei Matebook E Go EC
2024-12-28 11:34 ` [PATCH 1/5] dt-bindings: platform: Add Huawei Matebook E Go EC Pengyu Luo
2024-12-29 4:08 ` Dmitry Baryshkov
@ 2024-12-29 9:43 ` Krzysztof Kozlowski
2024-12-29 10:28 ` Pengyu Luo
1 sibling, 1 reply; 51+ messages in thread
From: Krzysztof Kozlowski @ 2024-12-29 9:43 UTC (permalink / raw)
To: Pengyu Luo
Cc: andersson, bryan.odonoghue, conor+dt, devicetree,
dmitry.baryshkov, gregkh, hdegoede, heikki.krogerus,
ilpo.jarvinen, konradybcio, krzk+dt, linux-arm-msm, linux-kernel,
linux-pm, linux-usb, nikita, platform-driver-x86, robh, sre
On 28/12/2024 12:34, Pengyu Luo wrote:
>> On Sat, Dec 28, 2024 at 5:58 PM Krzysztof Kozlowski <krzk@kernel.org> wrote:
>> On 27/12/2024 18:13, Pengyu Luo wrote:
>>> +
>>> +#include <linux/platform_data/huawei-gaokun-ec.h>
>>> +
>>> +#define EC_EVENT 0x06
>>> +
>>> +/* Also can be found in ACPI specification 12.3 */
>>> +#define EC_READ 0x80
>>> +#define EC_WRITE 0x81
>>> +#define EC_BURST 0x82
>>> +#define EC_QUERY 0x84
>>> +
>>> +
>>> +#define EC_EVENT_LID 0x81
>>> +
>>> +#define EC_LID_STATE 0x80
>>> +#define EC_LID_OPEN BIT(1)
>>> +
>>> +#define UCSI_REG_SIZE 7
>>> +
>>> +/* for tx, command sequences are arranged as
>>
>> Use Linux style comments, see coding style.
>>
>
> Agree
>
>>> + * {master_cmd, slave_cmd, data_len, data_seq}
>>> + */
>>> +#define REQ_HDR_SIZE 3
>>> +#define INPUT_SIZE_OFFSET 2
>>> +#define INPUT_DATA_OFFSET 3
>>> +
>>> +/* for rx, data sequences are arranged as
>>> + * {status, data_len(unreliable), data_seq}
>>> + */
>>> +#define RESP_HDR_SIZE 2
>>> +#define DATA_OFFSET 2
>>> +
>>> +
>>> +struct gaokun_ec {
>>> + struct i2c_client *client;
>>> + struct mutex lock;
>>
>> Missing doc. Run Checkpatch --strict, so you will know what is missing here.
>>
>
> I see. A comment for mutex lock.
>
>>> + struct blocking_notifier_head notifier_list;
>>> + struct input_dev *idev;
>>> + bool suspended;
>>> +};
>>> +
>>
>>
>>
>> ...
>>
>>> +
>>> +static DEVICE_ATTR_RO(temperature);
>>> +
>>> +static struct attribute *gaokun_wmi_features_attrs[] = {
>>> + &dev_attr_charge_control_thresholds.attr,
>>> + &dev_attr_smart_charge_param.attr,
>>> + &dev_attr_smart_charge.attr,
>>> + &dev_attr_fn_lock_state.attr,
>>> + &dev_attr_temperature.attr,
>>> + NULL,
>>> +};
>>
>>
>> No, don't expose your own interface. Charging is already exposed by
>> power supply framework. Temperature by hwmon sensors. Drop all these and
>> never re-implement existing kernel user-space interfaces.
>>
>
> I don't quite understand what you mean. You mean I should use hwmon
> interface like hwmon_device_register_with_groups to register it, right?
You added sysfs interface, I think. My comment is: do not. We have
existing interfaces.
> As for battery, get/set_propery allow us to handle charging thresholds
> things, but there are smart_charge_param, smart_charge and fn_lock to handle.
So where is the ABI documentation? Where is any explanation why existing
interfaces are not enough?
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 2/5] platform: arm64: add Huawei Matebook E Go (sc8280xp) EC driver
2024-12-29 9:04 ` [PATCH 2/5] platform: arm64: add Huawei Matebook E Go (sc8280xp) EC driver Pengyu Luo
@ 2024-12-29 9:44 ` Krzysztof Kozlowski
0 siblings, 0 replies; 51+ messages in thread
From: Krzysztof Kozlowski @ 2024-12-29 9:44 UTC (permalink / raw)
To: Pengyu Luo, dmitry.baryshkov
Cc: andersson, bryan.odonoghue, conor+dt, devicetree, gregkh,
hdegoede, heikki.krogerus, ilpo.jarvinen, konradybcio, krzk+dt,
linux-arm-msm, linux-kernel, linux-pm, linux-usb, nikita,
platform-driver-x86, robh, sre
On 29/12/2024 10:04, Pengyu Luo wrote:
>>>>> +
>>>>> +struct gaokun_ec;
>>>>> +struct notifier_block;
>>>>
>>>> Drop, include proper header instead.
>>>>
>>>
>>> I agree, I copy 'struct notifier_block;' from
>>> include/linux/platform_data/lenovo-yoga-c630.h
>>
>> Please don't pollute header files with extra dependencies. It's usually
>> better to just forware-declare the struct instead of adding unnecessary
>> include.
>>
>
> Both of you are resonable. So how?
>
> BTW, Krzysztof said
I would recommend differently, but I am fine with other approach if
other reviewers have their preference.
>
>>> You need kerneldoc, in the C file, for all exported functions.
>
> So Dmitry is here, I want to check again, should I add kerneldoc for all
> exported functions? C630 one never added all kerneldocs. In my driver,
> some function names have already indicated many things, some complex
> functions have been doced.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 1/5] dt-bindings: platform: Add Huawei Matebook E Go EC
2024-12-28 10:50 ` Pengyu Luo
@ 2024-12-29 9:50 ` Krzysztof Kozlowski
2024-12-29 10:12 ` Pengyu Luo
0 siblings, 1 reply; 51+ messages in thread
From: Krzysztof Kozlowski @ 2024-12-29 9:50 UTC (permalink / raw)
To: Pengyu Luo
Cc: andersson, bryan.odonoghue, conor+dt, devicetree,
dmitry.baryshkov, gregkh, hdegoede, heikki.krogerus,
ilpo.jarvinen, konradybcio, krzk+dt, linux-arm-msm, linux-kernel,
linux-pm, linux-usb, nikita, platform-driver-x86, robh, sre
On 28/12/2024 11:50, Pengyu Luo wrote:
> On Sat, Dec 28, 2024 at 5:54 PM Krzysztof Kozlowski <krzk@kernel.org> wrote:
>> On 27/12/2024 18:13, Pengyu Luo wrote:
>>> +
>>> +description:
>>> + Different from other Qualcomm Snapdragon sc8180x sc8280xp based machines,
>>> + the Huawei Matebook E Go tablets use embedded controllers while others
>>> + use something called pmic glink which handles battery, UCSI, USB Type-C DP
>>> + alt mode. Huawei one handles even more, like charging thresholds, FN lock,
>>> + lid status, HPD events for the USB Type-C DP alt mode, etc.
>>> +
>>> +properties:
>>> + compatible:
>>> + items:
>>> + - enum:
>>> + - huawei,sc8180x-gaokun-ec
>>> + - huawei,sc8280xp-gaokun-ec
>>
>> sc8180x and sc8280xp are not products of Huawei, so you cannot combine
>> them. Use compatibles matching exactly your device, because I doubt any
>> of us has actual schematics or datasheet of that device.
>>
>>> + - const: huawei,gaokun-ec
>>
>> How did you get the name?
>>
>
> From website of Huawei([1]), please search for 'gaokun' here, we can know
Then please explain this in commit msg or bindings description (what is
gaokun).
> this series is called gaokun. Many files from windows indicate more,
> someone fetch drivers from microsoft server([2]), in one of driver archive
> 'OemXAudioExt_HWVE.cab', there are two files, "algorithm_GaoKunGen2.xml"
> "algorithm_GaoKunGen3.xml". And `Gaokun Gen3` print can be found on
> motherboard(someone have the motherboard, I can ask for it later).
>
> So can I use?
> - enum:
> - huawei,gaokun-gen2
> - huawei,gaokun-gen3
The internal name?
>
> Some backgroud:
> There are 3 variants, Huawei released first 2 at the same time.
> Huawei Matebook E Go LTE(sc8180x), codename should be gaokun2.
> Huawei Matebook E Go(sc8280xp@3.0GHz), codename is gaokun3.
> Huawei Matebook E Go 2023(sc8280xp@2.69GHz).
Well, I believe it is still not good choice because we have absolutely
zero insights what is actually there, what else is called gaokun etc.
Especially "gen2" and "gen3" - how can anyone, outside of Huawei, figure
out which is gen3?
Why do people try to decode some vendor naming scheme instead of using
well recognized, public and available naming: the device name?
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 1/5] dt-bindings: platform: Add Huawei Matebook E Go EC
2024-12-29 9:50 ` Krzysztof Kozlowski
@ 2024-12-29 10:12 ` Pengyu Luo
2024-12-30 7:28 ` Aiqun(Maria) Yu
0 siblings, 1 reply; 51+ messages in thread
From: Pengyu Luo @ 2024-12-29 10:12 UTC (permalink / raw)
To: krzk
Cc: andersson, bryan.odonoghue, conor+dt, devicetree,
dmitry.baryshkov, gregkh, hdegoede, heikki.krogerus,
ilpo.jarvinen, konradybcio, krzk+dt, linux-arm-msm, linux-kernel,
linux-pm, linux-usb, mitltlatltl, nikita, platform-driver-x86,
robh, sre
On Sun, Dec 29, 2024 at 5:50 PM Krzysztof Kozlowski <krzk@kernel.org> wrote:
> On 28/12/2024 11:50, Pengyu Luo wrote:
> > On Sat, Dec 28, 2024 at 5:54 PM Krzysztof Kozlowski <krzk@kernel.org> wrote:
> >> On 27/12/2024 18:13, Pengyu Luo wrote:
> >>> +
> >>> +description:
> >>> + Different from other Qualcomm Snapdragon sc8180x sc8280xp based machines,
> >>> + the Huawei Matebook E Go tablets use embedded controllers while others
> >>> + use something called pmic glink which handles battery, UCSI, USB Type-C DP
> >>> + alt mode. Huawei one handles even more, like charging thresholds, FN lock,
> >>> + lid status, HPD events for the USB Type-C DP alt mode, etc.
> >>> +
> >>> +properties:
> >>> + compatible:
> >>> + items:
> >>> + - enum:
> >>> + - huawei,sc8180x-gaokun-ec
> >>> + - huawei,sc8280xp-gaokun-ec
> >>
> >> sc8180x and sc8280xp are not products of Huawei, so you cannot combine
> >> them. Use compatibles matching exactly your device, because I doubt any
> >> of us has actual schematics or datasheet of that device.
> >>
> >>> + - const: huawei,gaokun-ec
> >>
> >> How did you get the name?
> >>
> >
> > From website of Huawei([1]), please search for 'gaokun' here, we can know
>
> Then please explain this in commit msg or bindings description (what is
> gaokun).
>
I will add it in v2.
> > this series is called gaokun. Many files from windows indicate more,
> > someone fetch drivers from microsoft server([2]), in one of driver archive
> > 'OemXAudioExt_HWVE.cab', there are two files, "algorithm_GaoKunGen2.xml"
> > "algorithm_GaoKunGen3.xml". And `Gaokun Gen3` print can be found on
> > motherboard(someone have the motherboard, I can ask for it later).
> >
> > So can I use?
> > - enum:
> > - huawei,gaokun-gen2
> > - huawei,gaokun-gen3
>
> The internal name?
>
> >
> > Some backgroud:
> > There are 3 variants, Huawei released first 2 at the same time.
> > Huawei Matebook E Go LTE(sc8180x), codename should be gaokun2.
> > Huawei Matebook E Go(sc8280xp@3.0GHz), codename is gaokun3.
> > Huawei Matebook E Go 2023(sc8280xp@2.69GHz).
>
> Well, I believe it is still not good choice because we have absolutely
> zero insights what is actually there, what else is called gaokun etc.
> Especially "gen2" and "gen3" - how can anyone, outside of Huawei, figure
> out which is gen3?
>
> Why do people try to decode some vendor naming scheme instead of using
> well recognized, public and available naming: the device name?
>
Check the motherboard, https://postimg.cc/V5r4KCgx (Credit to Tianyu Gao <gty0622@gmail.com>)
Gen3 must be sc8280xp based variants. There are many clues showing that
sc8180x based variant is Gen2. I don't want to decode anything, but as you
also review Documentation/devicetree/bindings/arm/qcom.yaml, most of them
are suggest to use a codename, the retailer name is so long and confused.
Best wishes,
Pengyu
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 1/5] dt-bindings: platform: Add Huawei Matebook E Go EC
2024-12-29 9:43 ` [PATCH 1/5] dt-bindings: platform: Add Huawei Matebook E Go EC Krzysztof Kozlowski
@ 2024-12-29 10:28 ` Pengyu Luo
2024-12-29 21:45 ` Krzysztof Kozlowski
0 siblings, 1 reply; 51+ messages in thread
From: Pengyu Luo @ 2024-12-29 10:28 UTC (permalink / raw)
To: krzk
Cc: andersson, bryan.odonoghue, conor+dt, devicetree,
dmitry.baryshkov, gregkh, hdegoede, heikki.krogerus,
ilpo.jarvinen, konradybcio, krzk+dt, linux-arm-msm, linux-kernel,
linux-pm, linux-usb, mitltlatltl, nikita, platform-driver-x86,
robh, sre
On Sun, Dec 29, 2024 at 5:43 PM Krzysztof Kozlowski <krzk@kernel.org> wrote:
> On 28/12/2024 12:34, Pengyu Luo wrote:
> >> On Sat, Dec 28, 2024 at 5:58 PM Krzysztof Kozlowski <krzk@kernel.org> wrote:
> >> On 27/12/2024 18:13, Pengyu Luo wrote:
> >>> +
> >>> +#include <linux/platform_data/huawei-gaokun-ec.h>
> >>> +
> >>> +#define EC_EVENT 0x06
> >>> +
> >>> +/* Also can be found in ACPI specification 12.3 */
> >>> +#define EC_READ 0x80
> >>> +#define EC_WRITE 0x81
> >>> +#define EC_BURST 0x82
> >>> +#define EC_QUERY 0x84
> >>> +
> >>> +
> >>> +#define EC_EVENT_LID 0x81
> >>> +
> >>> +#define EC_LID_STATE 0x80
> >>> +#define EC_LID_OPEN BIT(1)
> >>> +
> >>> +#define UCSI_REG_SIZE 7
> >>> +
> >>> +/* for tx, command sequences are arranged as
> >>
> >> Use Linux style comments, see coding style.
> >>
> >
> > Agree
> >
> >>> + * {master_cmd, slave_cmd, data_len, data_seq}
> >>> + */
> >>> +#define REQ_HDR_SIZE 3
> >>> +#define INPUT_SIZE_OFFSET 2
> >>> +#define INPUT_DATA_OFFSET 3
> >>> +
> >>> +/* for rx, data sequences are arranged as
> >>> + * {status, data_len(unreliable), data_seq}
> >>> + */
> >>> +#define RESP_HDR_SIZE 2
> >>> +#define DATA_OFFSET 2
> >>> +
> >>> +
> >>> +struct gaokun_ec {
> >>> + struct i2c_client *client;
> >>> + struct mutex lock;
> >>
> >> Missing doc. Run Checkpatch --strict, so you will know what is missing here.
> >>
> >
> > I see. A comment for mutex lock.
> >
> >>> + struct blocking_notifier_head notifier_list;
> >>> + struct input_dev *idev;
> >>> + bool suspended;
> >>> +};
> >>> +
> >>
> >>
> >>
> >> ...
> >>
> >>> +
> >>> +static DEVICE_ATTR_RO(temperature);
> >>> +
> >>> +static struct attribute *gaokun_wmi_features_attrs[] = {
> >>> + &dev_attr_charge_control_thresholds.attr,
> >>> + &dev_attr_smart_charge_param.attr,
> >>> + &dev_attr_smart_charge.attr,
> >>> + &dev_attr_fn_lock_state.attr,
> >>> + &dev_attr_temperature.attr,
> >>> + NULL,
> >>> +};
> >>
> >>
> >> No, don't expose your own interface. Charging is already exposed by
> >> power supply framework. Temperature by hwmon sensors. Drop all these and
> >> never re-implement existing kernel user-space interfaces.
> >>
> >
> > I don't quite understand what you mean. You mean I should use hwmon
> > interface like hwmon_device_register_with_groups to register it, right?
>
> You added sysfs interface, I think. My comment is: do not. We have
> existing interfaces.
>
I agree with you, but device_add_groups is used to add sysfs interface
everywhere, device_add_groups are wrapped in acpi_battery_hook, they
handle charge_control_thresholds like this, since qcom arm64 do not
support acpi on linux, we do not use acpi_battery_hook to implement it,
so it is reasonable to implement it in PSY drivers.
some examples:
drivers/platform/x86/thinkpad_acpi.c
> static struct attribute *tpacpi_battery_attrs[] = {
> &dev_attr_charge_control_start_threshold.attr,
> &dev_attr_charge_control_end_threshold.attr,
> &dev_attr_charge_start_threshold.attr,
> &dev_attr_charge_stop_threshold.attr,
> &dev_attr_charge_behaviour.attr,
> NULL,
> };
>
> ATTRIBUTE_GROUPS(tpacpi_battery);
>
> /* ACPI battery hooking */
>
> static int tpacpi_battery_add(struct power_supply *battery, struct acpi_battery_hook *hook)
> {
> int batteryid = tpacpi_battery_get_id(battery->desc->name);
>
> if (tpacpi_battery_probe(batteryid))
> return -ENODEV;
> if (device_add_groups(&battery->dev, tpacpi_battery_groups))
> return -ENODEV;
> return 0;
> }
drivers/platform/x86/dell/dell-laptop.c
> static struct attribute *dell_battery_attrs[] = {
> &dev_attr_charge_control_start_threshold.attr,
> &dev_attr_charge_control_end_threshold.attr,
> &dev_attr_charge_types.attr,
> NULL,
> };
> ATTRIBUTE_GROUPS(dell_battery);
>
> static bool dell_battery_supported(struct power_supply *battery)
> {
> /* We currently only support the primary battery */
> return strcmp(battery->desc->name, "BAT0") == 0;
> }
>
> static int dell_battery_add(struct power_supply *battery,
> struct acpi_battery_hook *hook)
> {
> /* Return 0 instead of an error to avoid being unloaded */
> if (!dell_battery_supported(battery))
> return 0;
>
> return device_add_groups(&battery->dev, dell_battery_groups);
> }
> > As for battery, get/set_propery allow us to handle charging thresholds
> > things, but there are smart_charge_param, smart_charge and fn_lock to handle.
>
> So where is the ABI documentation? Where is any explanation why existing
> interfaces are not enough?
>
OK, if you insist, I will explain it in v2.
Best wishes,
Pengyu
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 2/5] platform: arm64: add Huawei Matebook E Go (sc8280xp) EC driver
2024-12-27 17:13 ` [PATCH 2/5] platform: arm64: add Huawei Matebook E Go (sc8280xp) EC driver Pengyu Luo
` (2 preceding siblings ...)
2024-12-28 12:33 ` [PATCH 2/5] platform: arm64: add Huawei Matebook E Go (sc8280xp) EC driver Bryan O'Donoghue
@ 2024-12-29 14:49 ` Markus Elfring
2024-12-30 9:04 ` Aiqun(Maria) Yu
4 siblings, 0 replies; 51+ messages in thread
From: Markus Elfring @ 2024-12-29 14:49 UTC (permalink / raw)
To: Pengyu Luo, platform-driver-x86, devicetree, linux-arm-msm,
linux-usb, linux-pm, Bjorn Andersson, Bryan O'Donoghue,
Conor Dooley, Greg Kroah-Hartman, Hans de Goede, Heikki Krogerus,
Ilpo Järvinen, Konrad Dybcio, Krzysztof Kozlowski,
Rob Herring, Sebastian Reichel
Cc: LKML, Dmitry Baryshkov, Nikita Travkin
…
> +++ b/drivers/platform/arm64/huawei-gaokun-ec.c
> @@ -0,0 +1,598 @@
…
> +static int gaokun_ec_request(struct gaokun_ec *ec, const u8 *req,
> + size_t resp_len, u8 *resp)
> +{
…
> + mutex_lock(&ec->lock);
> +
> + i2c_transfer(client->adapter, msgs, 2);
> + usleep_range(2000, 2500);
> +
> + mutex_unlock(&ec->lock);
> +
> + return *resp;
> +}
…
Under which circumstances would you become interested to apply a statement
like “guard(mutex)(&ec->lock);”?
https://elixir.bootlin.com/linux/v6.13-rc3/source/include/linux/mutex.h#L201
Regards,
Markus
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 3/5] usb: typec: ucsi: add Huawei Matebook E Go (sc8280xp) ucsi driver
2024-12-28 14:38 ` Pengyu Luo
@ 2024-12-29 14:51 ` Bryan O'Donoghue
2024-12-29 16:25 ` Pengyu Luo
0 siblings, 1 reply; 51+ messages in thread
From: Bryan O'Donoghue @ 2024-12-29 14:51 UTC (permalink / raw)
To: Pengyu Luo
Cc: andersson, conor+dt, devicetree, dmitry.baryshkov, gregkh,
hdegoede, heikki.krogerus, ilpo.jarvinen, konradybcio, krzk+dt,
linux-arm-msm, linux-kernel, linux-pm, linux-usb, nikita,
platform-driver-x86, robh, sre
On 28/12/2024 14:38, Pengyu Luo wrote:
> On Sat, Dec 28, 2024 at 9:06 PM Bryan O'Donoghue <bryan.odonoghue@linaro.org> wrote:
>> On 27/12/2024 17:13, Pengyu Luo wrote:
>>> The Huawei Matebook E Go (sc8280xp) tablet provides implements UCSI
>>> interface in the onboard EC. Add the glue driver to interface the
>>> platform's UCSI implementation.
>>>
>>> Signed-off-by: Pengyu Luo <mitltlatltl@gmail.com>
>>> ---
>>> drivers/usb/typec/ucsi/Kconfig | 9 +
>>> drivers/usb/typec/ucsi/Makefile | 1 +
>>> drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c | 481 ++++++++++++++++++++
>>> 3 files changed, 491 insertions(+)
>>> create mode 100644 drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c
>>>
>>> diff --git a/drivers/usb/typec/ucsi/Kconfig b/drivers/usb/typec/ucsi/Kconfig
>>> index 680e1b87b..0d0f07488 100644
>>> --- a/drivers/usb/typec/ucsi/Kconfig
>>> +++ b/drivers/usb/typec/ucsi/Kconfig
>>> @@ -78,4 +78,13 @@ config UCSI_LENOVO_YOGA_C630
>>> To compile the driver as a module, choose M here: the module will be
>>> called ucsi_yoga_c630.
>>>
>>> +config UCSI_HUAWEI_GAOKUN
>>> + tristate "UCSI Interface Driver for Huawei Matebook E Go (sc8280xp)"
>>> + depends on EC_HUAWEI_GAOKUN
>>> + help
>>> + This driver enables UCSI support on the Huawei Matebook E Go tablet.
>>> +
>>> + To compile the driver as a module, choose M here: the module will be
>>> + called ucsi_huawei_gaokun.
>>> +
>>> endif
>>> diff --git a/drivers/usb/typec/ucsi/Makefile b/drivers/usb/typec/ucsi/Makefile
>>> index aed41d238..0b400122b 100644
>>> --- a/drivers/usb/typec/ucsi/Makefile
>>> +++ b/drivers/usb/typec/ucsi/Makefile
>>> @@ -22,3 +22,4 @@ obj-$(CONFIG_UCSI_CCG) += ucsi_ccg.o
>>> obj-$(CONFIG_UCSI_STM32G0) += ucsi_stm32g0.o
>>> obj-$(CONFIG_UCSI_PMIC_GLINK) += ucsi_glink.o
>>> obj-$(CONFIG_UCSI_LENOVO_YOGA_C630) += ucsi_yoga_c630.o
>>> +obj-$(CONFIG_UCSI_HUAWEI_GAOKUN) += ucsi_huawei_gaokun.o
>>> diff --git a/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c
>>> new file mode 100644
>>> index 000000000..84ed0407d
>>> --- /dev/null
>>> +++ b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c
>>> @@ -0,0 +1,481 @@
>>> +// SPDX-License-Identifier: GPL-2.0-only
>>> +/*
>>> + * ucsi-huawei-gaokun - A UCSI driver for HUAWEI Matebook E Go (sc8280xp)
>>> + *
>>> + * reference: drivers/usb/typec/ucsi/ucsi_yoga_c630.c
>>> + * drivers/usb/typec/ucsi/ucsi_glink.c
>>> + * drivers/soc/qcom/pmic_glink_altmode.c
>>> + *
>>> + * Copyright (C) 2024 Pengyu Luo <mitltlatltl@gmail.com>
>>> + */
>>> +
>>> +#include <linux/auxiliary_bus.h>
>>> +#include <linux/bitops.h>
>>> +#include <linux/completion.h>
>>> +#include <linux/container_of.h>
>>> +#include <linux/delay.h>
>>> +#include <linux/module.h>
>>> +#include <linux/notifier.h>
>>> +#include <linux/of.h>
>>> +#include <linux/string.h>
>>> +#include <linux/workqueue_types.h>
>>> +
>>> +#include <linux/usb/pd_vdo.h>
>>> +#include <drm/bridge/aux-bridge.h
>>
>> Is there a reason you don't have strict include alphanumeric ordering here ?
>>
>
> These two is dp/alt mode related, so listing them out. Above of them are
> general things.
OK. Unless there's an include dependency reason you need to, please just
sort the headers alphanumerically in order
#include <globals_first>
#include <globals_first_alpha>
#include "locals_next"
#include "locals_next_alpha_also"
>>>
>>> +
>>> +#include "ucsi.h"
>>> +#include <linux/platform_data/huawei-gaokun-ec.h>
>>> +
>>> +
>>> +#define EC_EVENT_UCSI 0x21
>>> +#define EC_EVENT_USB 0x22
>>> +
>>> +#define GAOKUN_CCX_MASK GENMASK(1, 0)
>>> +#define GAOKUN_MUX_MASK GENMASK(3, 2)
>>> +
>>> +#define GAOKUN_DPAM_MASK GENMASK(3, 0)
>>> +#define GAOKUN_HPD_STATE_MASK BIT(4)
>>> +#define GAOKUN_HPD_IRQ_MASK BIT(5)
>>> +
>>> +#define CCX_TO_ORI(ccx) (++ccx % 3)
>>
>> Why do you increment the value of the enum ?
>> Seems strange.
>>
>
> EC's logic, it is just a trick. Qualcomm maps
> 0 1 2 to normal, reverse, none(no device insert)
> typec lib maps 1 2 0 to that.
I'd suggest making the trick much more obvious.
Either with a comment or just mapping 1:1 between EC and Linux' view of
this data.
The main reason for that is to make it easier to
read/understand/maintain/debug.
>>> + port->svid = USB_SID_DISPLAYPORT;
>>> + if (port->ccx == USBC_CCX_REVERSE)
>>> + port->mode -= 6;
>>
>> why minus six ?
>> needs a comment.
>>
>
> EC's logic. I don't know why, it is a quirk from Qualcomm or Huawei.
> I will mention this.
Instead of hard-coding a mapping between the EC's mode and Linux' UCSI
enum - just make a define or an inline, ideally something with
switch(port->mode)
case GOAKUN_MODE_0:
val = LINUX_UCSI_MODE_X;
case GOAKUN_MODE_1:
val = LINUX_UCSI_MODE_Y;
}
That will make the mapping obvious and also ensure both to yourself and
to your reviewers that you have accounted for _all_ of the potential
mappings and if those mappings don't exist then the default: of your
switch statement should make some noise about it
dev_err(dev, "GOKUN UCSI mode %d unmapped\n"); or something like that.
>
>>> + break;
>>> + default:
>>> + break;
>>> + }
>>> +
>>> + spin_unlock_irqrestore(&port->lock, flags);
>>> +}
>>> +
>>> +static int gaokun_ucsi_refresh(struct gaokun_ucsi *uec)
>>> +{
>>> + struct gaokun_ucsi_reg ureg;
>>> + int ret, idx;
>>> +
>>> + ret = gaokun_ec_ucsi_get_reg(uec->ec, (u8 *)&ureg);
>>> + if (ret)
>>> + return -EIO;
>>> +
>>> + uec->port_num = ureg.port_num;
>>> + idx = GET_IDX(ureg.port_updt);
>>> +
>>> + if (idx >= 0 && idx < ureg.port_num)
>>> + gaokun_ucsi_port_update(&uec->ports[idx], ureg.port_data);
>>
>> Since you are checking the validity of the index, you should -EINVAL if
>> the index is out of range.
>>
>
> EC / pmic glink encode every port in a bit
> 0/1/2/4/... => ???/left/right/some port
>
> I remap it to -1/0/1/2, to access specific port exceptions(-1) are not
> harmful, later in gaokun_ucsi_altmode_notify_ind
>
> if (idx < 0)
> gaokun_ec_ucsi_pan_ack(uec->ec, idx);
> else
> gaokun_ucsi_handle_altmode(&uec->ports[idx]);
>
> gaokun_ec_ucsi_pan_ack can handle exceptions.
>
gaokun_ucsi_refresh() can return
-EIO
-1
>=0
Where -EIO and -1 both trigger gaokun_ec_ucsi_pan_ack() in
gaokun_ucsi_altmode_notify_ind()
So if the index doesn't matter and < 0 => pan_ack() is OK or -EIO is not
returning meaningful error.
Either way strongly advise against mixing a negative index as having a
valid meaning with actual -E error codes...
As a reviewer doing a fist-pass this looks suspicous and implies more
thought/refinement should be done to the flow.
>>> +
>>> + ucsi->quirks = UCSI_NO_PARTNER_PDOS | UCSI_DELAY_DEVICE_PDOS;
>>> +
>>> + ssleep(3); /* EC can't handle UCSI properly in the early stage */
>>
>> Could you not schedule work for + 3 seconds instead of sleeping here -
>> representing the required stall time in some sort of state machine ?
>>
>
> I see, I will check work schedule interface.
>
>> 3 seconds is an incredibly long time for a computer to sleep.
>>
>
> This module will be loaded at about 5th second after power up, if not
> sleep, we will receive something disharmonious, sleeping for 3 seconds is
> a hack.
Yes it is. You could schedule some work to complete three seconds from
here instead of doing this long sleep here.
In fact you are registering a worker here right ?
In which case its pretty trivial to schedule some work on that worker
for three seconds hence ..
Please investigate.
---
bod
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 2/5] platform: arm64: add Huawei Matebook E Go (sc8280xp) EC driver
2024-12-28 13:51 ` Pengyu Luo
@ 2024-12-29 15:32 ` Ilpo Järvinen
2024-12-29 15:55 ` Pengyu Luo
0 siblings, 1 reply; 51+ messages in thread
From: Ilpo Järvinen @ 2024-12-29 15:32 UTC (permalink / raw)
To: Pengyu Luo
Cc: bryan.odonoghue, andersson, conor+dt, devicetree,
dmitry.baryshkov, Greg Kroah-Hartman, Hans de Goede,
heikki.krogerus, konradybcio, krzk+dt, linux-arm-msm, LKML,
linux-pm, linux-usb, nikita, platform-driver-x86, robh, sre
[-- Attachment #1: Type: text/plain, Size: 20307 bytes --]
On Sat, 28 Dec 2024, Pengyu Luo wrote:
> On Sat, Dec 28, 2024 at 8:33 PM Bryan O'Donoghue <bryan.odonoghue@linaro.org> wrote:
> > On 27/12/2024 17:13, Pengyu Luo wrote:
> > > There are 3 variants, Huawei released first 2 at the same time.
> >
> > There are three variants of which Huawei released the first two
> > simultaneously.
> >
>
> You have mentioned many grammar and code issues.
> So I explain something, I taught myself C, writing code is just my hobby.
> I am also not a native speaker, I have never lived in an English
> environment. Sometime I may use inappropriate words to express. I try my
> best to express my idea clearly. I hope you can understand.
>
> > > Huawei Matebook E Go LTE(sc8180x), codename should be gaokun2.
> > > Huawei Matebook E Go(sc8280xp@3.0GHz), codename is gaokun3.
> >
> > Choose either "codename should be" or "codename is"
> >
> > > Huawei Matebook E Go 2023(sc8280xp@2.69GHz).
> >
> > Maybe say here "codename unknown"
> >
>
> should be, I want to express I am guessing.
> I guess the last one is also gaokun3. But anyways, this driver works
> for the latter two.
>
> > > Adding support for the latter two variants for now, this driver should
> > > also work for the sc8180x variant according to acpi table files, but I
> > > don't have the device yet.
> > >
> > > Different from other Qualcomm Snapdragon sc8280xp based machines, the
> > > Huawei Matebook E Go uses an embedded controller while others use
> > > something called pmic glink. This embedded controller can be used to
> > > perform a set of various functions, including, but not limited:
> >
> > "but not limited to":
> >
> > > - Battery and charger monitoring;
> > > - Charge control and smart charge;
> > > - Fn_lock settings;
> > > - Tablet lid status;
> > > - Temperature sensors;
> > > - USB Type-C notifications (ports orientation, DP alt mode HPD);
> > > - USB Type-C PD (according to observation, up to 48w).
> > >
> > > Add the driver for the EC, that creates devices for UCSI, wmi and power
> > > supply devices.
> >
> > I'm a terrible man for the "," dropped all about the place but you're
> > going too mad with the commans there ->
> >
> > "Add a driver for the EC which creates devices for UCSI, WMI and power
> > supply devices"
> >
>
> This was copied from C630 patches, replaced with my devices.
>
> > >
> > > Signed-off-by: Pengyu Luo <mitltlatltl@gmail.com>
> > > ---
> > > drivers/platform/arm64/Kconfig | 19 +
> > > drivers/platform/arm64/Makefile | 2 +
> > > drivers/platform/arm64/huawei-gaokun-ec.c | 598 ++++++++++++++++++
> > > drivers/platform/arm64/huawei-gaokun-wmi.c | 283 +++++++++
> > > .../linux/platform_data/huawei-gaokun-ec.h | 90 +++
> > > 5 files changed, 992 insertions(+)
> > > create mode 100644 drivers/platform/arm64/huawei-gaokun-ec.c
> > > create mode 100644 drivers/platform/arm64/huawei-gaokun-wmi.c
> > > create mode 100644 include/linux/platform_data/huawei-gaokun-ec.h
> > >
> > > diff --git a/drivers/platform/arm64/Kconfig b/drivers/platform/arm64/Kconfig
> > > index f88395ea3..eb7fbacf0 100644
> > > --- a/drivers/platform/arm64/Kconfig
> > > +++ b/drivers/platform/arm64/Kconfig
> > > @@ -33,6 +33,25 @@ config EC_ACER_ASPIRE1
> > > laptop where this information is not properly exposed via the
> > > standard ACPI devices.
> > >
> > > +config EC_HUAWEI_GAOKUN
> > > + tristate "Huawei Matebook E Go (sc8280xp) Embedded Controller driver"
> >
> > Krzysztof already mentioned this but the "sc8280xp" is questionable, you
> > should probably drop mention of qcom and sc8280xp from your compat and
> > tristate here.
> >
>
> Agree, how about using sc8280xp-based for tristate?
>
> > > + depends on ARCH_QCOM || COMPILE_TEST
> > > + depends on I2C
> > > + depends on DRM
> > > + depends on POWER_SUPPLY
> > > + depends on INPUT
> > > + help
> > > + Say Y here to enable the EC driver for Huawei Matebook E Go 2in1
> > > + tablet. The driver handles battery(information, charge control) and
> > > + USB Type-C DP HPD events as well as some misc functions like the lid
> > > + sensor and temperature sensors, etc.
> > > +
> > > + This driver provides battery and AC status support for the mentioned
> > > + laptop where this information is not properly exposed via the
> > > + standard ACPI devices.
> > > +
> > > + Say M or Y here to include this support.
> >
> > OTOH the help text is where you could mention the sc8280xp class of
> > laptops/tablets.
> >
>
> I see
>
> > > +
> > > config EC_LENOVO_YOGA_C630
> > > tristate "Lenovo Yoga C630 Embedded Controller driver"
> > > depends on ARCH_QCOM || COMPILE_TEST
> > > diff --git a/drivers/platform/arm64/Makefile b/drivers/platform/arm64/Makefile
> > > index b2ae9114f..ed32ad6c0 100644
> > > --- a/drivers/platform/arm64/Makefile
> > > +++ b/drivers/platform/arm64/Makefile
> > > @@ -6,4 +6,6 @@
> > > #
> > >
> > > obj-$(CONFIG_EC_ACER_ASPIRE1) += acer-aspire1-ec.o
> > > +obj-$(CONFIG_EC_HUAWEI_GAOKUN) += huawei-gaokun-ec.o
> > > +obj-$(CONFIG_EC_HUAWEI_GAOKUN) += huawei-gaokun-wmi.o
> > > obj-$(CONFIG_EC_LENOVO_YOGA_C630) += lenovo-yoga-c630.o
> > > diff --git a/drivers/platform/arm64/huawei-gaokun-ec.c b/drivers/platform/arm64/huawei-gaokun-ec.c
> > > new file mode 100644
> > > index 000000000..c1c657f7b
> > > --- /dev/null
> > > +++ b/drivers/platform/arm64/huawei-gaokun-ec.c
> > > @@ -0,0 +1,598 @@
> > > +// SPDX-License-Identifier: GPL-2.0-only
> > > +/*
> > > + * huawei-gaokun-ec - An EC driver for HUAWEI Matebook E Go (sc8280xp)
> > > + *
> > > + * reference: drivers/platform/arm64/acer-aspire1-ec.c
> > > + * drivers/platform/arm64/lenovo-yoga-c630.c
> > > + *
> > > + * Copyright (C) 2024 Pengyu Luo <mitltlatltl@gmail.com>
> > > + */
> > > +
> > > +#include <linux/auxiliary_bus.h>
> > > +#include <linux/delay.h>
> > > +#include <linux/device.h>
> > > +#include <linux/i2c.h>
> > > +#include <linux/input.h>
> > > +#include <linux/notifier.h>
> > > +#include <linux/module.h>
> > > +#include <linux/mutex.h>
> > > +#include <linux/version.h>
> > > +
> > > +#include <linux/platform_data/huawei-gaokun-ec.h>
> > > +
> > > +#define EC_EVENT 0x06
> > > +
> > > +/* Also can be found in ACPI specification 12.3 */
> > > +#define EC_READ 0x80
> > > +#define EC_WRITE 0x81
> >
> > Is this odd indentation ?
> >
>
> No, apply it then check the source, it is normal.
>
> > > +#define EC_BURST 0x82
> > > +#define EC_QUERY 0x84
> > > +
> > > +
> > > +#define EC_EVENT_LID 0x81
> > > +
> > > +#define EC_LID_STATE 0x80
> > > +#define EC_LID_OPEN BIT(1)
> > > +
> > > +#define UCSI_REG_SIZE 7
> > > +
> > > +/* for tx, command sequences are arranged as
> > > + * {master_cmd, slave_cmd, data_len, data_seq}
> > > + */
> > > +#define REQ_HDR_SIZE 3
> > > +#define INPUT_SIZE_OFFSET 2
> > > +#define INPUT_DATA_OFFSET 3
> > > +
> > > +/* for rx, data sequences are arranged as
> > > + * {status, data_len(unreliable), data_seq}
> > > + */
> > > +#define RESP_HDR_SIZE 2
> > > +#define DATA_OFFSET 2
> > > +
> > > +
> > > +struct gaokun_ec {
> > > + struct i2c_client *client;
> > > + struct mutex lock;
> > > + struct blocking_notifier_head notifier_list;
> > > + struct input_dev *idev;
> > > + bool suspended;
> > > +};
> > > +
> > > +static int gaokun_ec_request(struct gaokun_ec *ec, const u8 *req,
> > > + size_t resp_len, u8 *resp)
> > > +{
> > > + struct i2c_client *client = ec->client;
> > > + struct i2c_msg msgs[2] = {
> > > + {
> > > + .addr = client->addr,
> > > + .flags = client->flags,
> > > + .len = req[INPUT_SIZE_OFFSET] + REQ_HDR_SIZE,
> > > + .buf = req,
> > > + }, {
> > > + .addr = client->addr,
> > > + .flags = client->flags | I2C_M_RD,
> > > + .len = resp_len,
> > > + .buf = resp,
> > > + },
> > > + };
> > > +
> > > + mutex_lock(&ec->lock);
> > > +
> > > + i2c_transfer(client->adapter, msgs, 2);
> > > + usleep_range(2000, 2500);
> >
> > What is this sleep about and why are you doing it inside of a critical
> > section ?
> >
>
> Have a break between 2 transaction. This sleep happens in acpi code, also
> inside a critical region. I rearrange it. We can sleep when using mutex
> lock instead of spinlock.
>
> Local7 = Acquire (\_SB.IC16.MUEC, 0x03E8)
> ...
> read ops
> ...
> Sleep (0x02)
> ...
> write ops
> ...
> Release (\_SB.IC16.MUEC)
>
> > > +
> > > + mutex_unlock(&ec->lock);
> > > +
> > > + return *resp;
> > > +}
> > > +
> > > +/* -------------------------------------------------------------------------- */
> > > +/* Common API */
> > > +
> > > +/**
> > > + * gaokun_ec_read - read from EC
> > > + * @ec: The gaokun_ec
> > > + * @req: The sequence to request
> > > + * @resp_len: The size to read
> > > + * @resp: Where the data are read to
> > > + *
> > > + * This function is used to read data after writing a magic sequence to EC.
> > > + * All EC operations dependent on this functions.
> >
> > depend on this
> >
>
> mistyping
>
> > > + *
> > > + * Huawei uses magic sequences everywhere to complete various functions, all
> > > + * these sequences are passed to ECCD(a ACPI method which is quiet similar
> > > + * to gaokun_ec_request), there is no good abstraction to generalize these
> > > + * sequences, so just wrap it for now. Almost all magic sequences are kept
> > > + * in this file.
> > > + */
> > > +int gaokun_ec_read(struct gaokun_ec *ec, const u8 *req,
> > > + size_t resp_len, u8 *resp)
> > > +{
> > > + return gaokun_ec_request(ec, req, resp_len, resp);
> > > +}
> > > +EXPORT_SYMBOL_GPL(gaokun_ec_read);
> > > +
> > > +/**
> > > + * gaokun_ec_write - write to EC
> > > + * @ec: The gaokun_ec
> > > + * @req: The sequence to request
> > > + *
> > > + * This function has no big difference from gaokun_ec_read. When caller care
> > > + * only write status and no actual data are returnd, then use it.
> > > + */
> > > +int gaokun_ec_write(struct gaokun_ec *ec, u8 *req)
> > > +{
> > > + u8 resp[RESP_HDR_SIZE];
> > > +
> > > + return gaokun_ec_request(ec, req, sizeof(resp), resp);
> > > +}
> > > +EXPORT_SYMBOL_GPL(gaokun_ec_write);
> > > +
> > > +int gaokun_ec_read_byte(struct gaokun_ec *ec, u8 *req, u8 *byte)
> > > +{
> > > + int ret;
> > > + u8 resp[RESP_HDR_SIZE + sizeof(*byte)];
> > > +
> > > + ret = gaokun_ec_read(ec, req, sizeof(resp), resp);
> > > + *byte = resp[DATA_OFFSET];
> > > +
> > > + return ret;
> > > +}
> > > +EXPORT_SYMBOL_GPL(gaokun_ec_read_byte);
> > > +
> > > +int gaokun_ec_register_notify(struct gaokun_ec *ec, struct notifier_block *nb)
> > > +{
> > > + return blocking_notifier_chain_register(&ec->notifier_list, nb);
> > > +}
> > > +EXPORT_SYMBOL_GPL(gaokun_ec_register_notify);
> > > +
> > > +void gaokun_ec_unregister_notify(struct gaokun_ec *ec, struct notifier_block *nb)
> > > +{
> > > + blocking_notifier_chain_unregister(&ec->notifier_list, nb);
> > > +}
> > > +EXPORT_SYMBOL_GPL(gaokun_ec_unregister_notify);
> > > +
> > > +/* -------------------------------------------------------------------------- */
> > > +/* API For PSY */
> > > +
> > > +int gaokun_ec_psy_multi_read(struct gaokun_ec *ec, u8 reg,
> > > + size_t resp_len, u8 *resp)
> > > +{
> > > + int i, ret;
> > > + u8 _resp[RESP_HDR_SIZE + 1];
> > > + u8 req[REQ_HDR_SIZE + 1] = {0x02, EC_READ, 1, };
> >
> > Instead of constructing your packet inline like this, suggest a
> > dedicated function to construct a request packet.
> >
> > For example 1 @ INPUT_SIZE_OFFSET => the size of data a dedicated
> > function will make the "stuffing" of the request frame more obvious to
> > readers and make the construction of packets less error prone.
> >
>
> I will try to do it, but there are many magic sequences in any size, I
> need pass them anyways.
>
> > > +
> > > + for (i = 0; i < resp_len; ++i) {
> > > + req[INPUT_DATA_OFFSET] = reg++;
> > > + ret = gaokun_ec_read(ec, req, sizeof(_resp), _resp);
> > > + if (ret)
> > > + return -EIO;
> > > + resp[i] = _resp[DATA_OFFSET];
> > > + }
> > > +
> > > + return 0;
> > > +}
> > > +EXPORT_SYMBOL_GPL(gaokun_ec_psy_multi_read);
> > > +
> > > +/* -------------------------------------------------------------------------- */
> > > +/* API For WMI */
> > > +
> > > +/* Battery charging threshold */
> > > +int gaokun_ec_wmi_get_threshold(struct gaokun_ec *ec, u8 *value, int ind)
> > > +{
> > > + /* GBTT */
> > > + return gaokun_ec_read_byte(ec, (u8 []){0x02, 0x69, 1, ind}, value);
> > > +}
> > > +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_get_threshold);
> > > +
> > > +int gaokun_ec_wmi_set_threshold(struct gaokun_ec *ec, u8 start, u8 end)
> > > +{
> > > + /* SBTT */
> > > + int ret;
> > > + u8 req[REQ_HDR_SIZE + 2] = {0x02, 0x68, 2, 3, 0x5a};
> > > +
> > > + ret = gaokun_ec_write(ec, req);
> > > + if (ret)
> > > + return -EIO;
> > > +
> > > + if (start == 0 && end == 0)
> > > + return -EINVAL;
> > > +
> > > + if (start >= 0 && start <= end && end <= 100) {
> >
> > if start >= 0
> >
> > is redundant no ? start is a u8 it can _only_ be >= 0 ..
> >
>
> Yeah, this code was written with int. Later, I used u8 since one byte is
> convenient for EC transaction(we don't need clear other 3 Bytes). After
> chaging, I forgot to revaluate it.
>
> >
> > > + req[INPUT_DATA_OFFSET] = 1;
> > > + req[INPUT_DATA_OFFSET + 1] = start;
> > > + ret = gaokun_ec_write(ec, req);
> > > + if (ret)
> > > + return -EIO;
> > > +
> > > + req[INPUT_DATA_OFFSET] = 2;
> > > + req[INPUT_DATA_OFFSET + 1] = end;
> >
> > again a function to construct a packet gets you out of the business of
> > inlining and "just knowing" which offset is which within any give
> > function which indexes an array.
> >
> > > + ret = gaokun_ec_write(ec, req);
> > > + } else {
> > > + return -EINVAL;
> > > + }
> > > +
> > > + return ret;
> > > +}
> > > +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_set_threshold);
> > > +
> > > +/* Smart charge param */
> > > +int gaokun_ec_wmi_get_smart_charge_param(struct gaokun_ec *ec, u8 *value)
> > > +{
> > > + /* GBAC */
> > > + return gaokun_ec_read_byte(ec, (u8 []){0x02, 0xE6, 0}, value);
> > > +}
> > > +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_get_smart_charge_param);
> > > +
> > > +int gaokun_ec_wmi_set_smart_charge_param(struct gaokun_ec *ec, u8 value)
> > > +{
> > > + /* SBAC */
> > > + if (value < 0 || value > 2)
> >
> > value < 0 can never be true
> >
> > > + return -EINVAL;
> > > +
> > > + return gaokun_ec_write(ec, (u8 []){0x02, 0xE5, 1, value});
> > > +}
> > > +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_set_smart_charge_param);
> > > +
> > > +/* Smart charge */
> > > +int gaokun_ec_wmi_get_smart_charge(struct gaokun_ec *ec,
> > > + u8 data[GAOKUN_SMART_CHARGE_DATA_SIZE])
> > > +{
> > > + /* GBCM */
> > > + u8 req[REQ_HDR_SIZE] = {0x02, 0xE4, 0};
> > > + u8 resp[RESP_HDR_SIZE + 4];
> > > + int ret;
> > > +
> > > + ret = gaokun_ec_read(ec, req, sizeof(resp), resp);
> > > + if (ret)
> > > + return -EIO;
> > > +
> > > + data[0] = resp[DATA_OFFSET];
> > > + data[1] = resp[DATA_OFFSET + 1];
> > > + data[2] = resp[DATA_OFFSET + 2];
> > > + data[3] = resp[DATA_OFFSET + 3];
> > > +
> > > + return 0;
> > > +}
> > > +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_get_smart_charge);
> > > +
> > > +int gaokun_ec_wmi_set_smart_charge(struct gaokun_ec *ec,
> > > + u8 data[GAOKUN_SMART_CHARGE_DATA_SIZE])
> > > +{
> > > + /* SBCM */
> > > + u8 req[REQ_HDR_SIZE + GAOKUN_SMART_CHARGE_DATA_SIZE] = {0x02, 0XE3, 4,};
> > > +
> > > + if (!(data[2] >= 0 && data[2] <= data[3] && data[3] <= 100))
> > > + return -EINVAL;
> >
> > Repeat of the clause above which was checking u8 >= 0 for the same
> > values in the rest of the clause - including checking <= 100.
> >
> > Certainly a candidate for functional decomposition, inline function or a
> > define.
> >
> > > +
> > > + memcpy(req + INPUT_DATA_OFFSET, data, GAOKUN_SMART_CHARGE_DATA_SIZE);
> > > +
> > > + return gaokun_ec_write(ec, req);
> > > +}
> > > +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_set_smart_charge);
> > > +
> > > +/* Fn lock */
> > > +int gaokun_ec_wmi_get_fn_lock(struct gaokun_ec *ec, u8 *on)
> > > +{
> > > + /* GFRS */
> > > + int ret;
> > > + u8 val;
> > > + u8 req[REQ_HDR_SIZE] = {0x02, 0x6B, 0};
> >
> > Reverse Christmas tree
> >
> > u8 req[REQ_HDR_SIZE];
> > int ret;
> > u8 val;
> >
> > Not required but nice to look at.
> >
>
> Agree
>
> > > +
> > > + ret = gaokun_ec_read_byte(ec, req, &val);
> > > + if (val == 0x55)
> > > + *on = 0;
> > > + else if (val == 0x5A)
> > > + *on = 1;
> > > + else
> > > + return -EIO;
> > > +
> > > + return ret;
> > > +}
> > > +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_get_fn_lock);
> > > +
> > > +int gaokun_ec_wmi_set_fn_lock(struct gaokun_ec *ec, u8 on)
> > > +{
> > > + /* SFRS */
> > > + u8 req[REQ_HDR_SIZE + 1] = {0x02, 0x6C, 1,};
> > > +
> > > + if (on == 0)
> > > + req[INPUT_DATA_OFFSET] = 0x55;
> > > + else if (on == 1)
> > > + req[INPUT_DATA_OFFSET] = 0x5A;
> > > + else
> > > + return -EINVAL;
> >
> > Why not use a bool for on ?
> >
>
> Yes, we can.
>
> > > +
> > > + return gaokun_ec_write(ec, req);
> > > +}
> > > +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_set_fn_lock);
> > > +
> > > +/* Thermal Zone */
> > > +/* Range from 0 to 0x2C, partial valid */
> > > +static const u8 temp_reg[] = {0x05, 0x07, 0x08, 0x0E, 0x0F, 0x12, 0x15, 0x1E,
> > > + 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
> > > + 0x27, 0x28, 0x29, 0x2A};
> > > +
> > > +int gaokun_ec_wmi_get_temp(struct gaokun_ec *ec, s16 temp[GAOKUN_TZ_REG_NUM])
> >
> > int gaokun_ec_wmi_get_temp(struct gaokun_ec *ec, s16 *temp, size_t
> > temp_reg_num)
> >
> >
> > > +{
> > > + /* GTMP */
> > > + u8 req[REQ_HDR_SIZE] = {0x02, 0x61, 1,};
> > > + u8 resp[RESP_HDR_SIZE + sizeof(s16)];
> > > + int ret, i = 0;
> > > +
> > > + while (i < GAOKUN_TZ_REG_NUM) {
> > while (i < temp_reg_num)
> >
>
> It is a constant. But later, as Krzysztof suggested, I will use interfaces
> from hwmon, then reading one at a time.
>
> > > + req[INPUT_DATA_OFFSET] = temp_reg[i];
> > > + ret = gaokun_ec_read(ec, req, sizeof(resp), resp);
> > > + if (ret)
> > > + return -EIO;
> > > + temp[i++] = *(s16 *)(resp + DATA_OFFSET);
> >
> > What's the point of the casting here ?
> >
> > memcpy(temp, resp, sizeof(s16));
> > temp++;
>
> A 2Bytes symbol number in little endian, ec return it like this, so
> casting.
You should use __le16 and proper endianess conversion function then.
It's bit confusing that in the declaration you used RESP_HDR_SIZE and here
you do it with DATA_OFFSET instead. It feels DATA_OFFSET is unnecessary
duplicate of RESP_HDR_SIZE and will easily lead confusing variation such
as above.
--
i.
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 2/5] platform: arm64: add Huawei Matebook E Go (sc8280xp) EC driver
2024-12-29 15:32 ` Ilpo Järvinen
@ 2024-12-29 15:55 ` Pengyu Luo
0 siblings, 0 replies; 51+ messages in thread
From: Pengyu Luo @ 2024-12-29 15:55 UTC (permalink / raw)
To: ilpo.jarvinen
Cc: andersson, bryan.odonoghue, conor+dt, devicetree,
dmitry.baryshkov, gregkh, hdegoede, heikki.krogerus, konradybcio,
krzk+dt, linux-arm-msm, linux-kernel, linux-pm, linux-usb,
mitltlatltl, nikita, platform-driver-x86, robh, sre
On Sun, Dec 29, 2024 at 11:33 PM Ilpo Järvinen <ilpo.jarvinen@linux.intel.com> wrote:
> On Sat, 28 Dec 2024, Pengyu Luo wrote:
> > On Sat, Dec 28, 2024 at 8:33 PM Bryan O'Donoghue <bryan.odonoghue@linaro.org> wrote:
> > > On 27/12/2024 17:13, Pengyu Luo wrote:
> > > > There are 3 variants, Huawei released first 2 at the same time.
> > >
> > > There are three variants of which Huawei released the first two
> > > simultaneously.
[skipped]
> > > > +/* Thermal Zone */
> > > > +/* Range from 0 to 0x2C, partial valid */
> > > > +static const u8 temp_reg[] = {0x05, 0x07, 0x08, 0x0E, 0x0F, 0x12, 0x15, 0x1E,
> > > > + 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
> > > > + 0x27, 0x28, 0x29, 0x2A};
> > > > +
> > > > +int gaokun_ec_wmi_get_temp(struct gaokun_ec *ec, s16 temp[GAOKUN_TZ_REG_NUM])
> > >
> > > int gaokun_ec_wmi_get_temp(struct gaokun_ec *ec, s16 *temp, size_t
> > > temp_reg_num)
> > >
> > >
> > > > +{
> > > > + /* GTMP */
> > > > + u8 req[REQ_HDR_SIZE] = {0x02, 0x61, 1,};
> > > > + u8 resp[RESP_HDR_SIZE + sizeof(s16)];
> > > > + int ret, i = 0;
> > > > +
> > > > + while (i < GAOKUN_TZ_REG_NUM) {
> > > while (i < temp_reg_num)
> > >
> >
> > It is a constant. But later, as Krzysztof suggested, I will use interfaces
> > from hwmon, then reading one at a time.
> >
> > > > + req[INPUT_DATA_OFFSET] = temp_reg[i];
> > > > + ret = gaokun_ec_read(ec, req, sizeof(resp), resp);
> > > > + if (ret)
> > > > + return -EIO;
> > > > + temp[i++] = *(s16 *)(resp + DATA_OFFSET);
> > >
> > > What's the point of the casting here ?
> > >
> > > memcpy(temp, resp, sizeof(s16));
> > > temp++;
> >
> > A 2Bytes symbol number in little endian, ec return it like this, so
> > casting.
>
> You should use __le16 and proper endianess conversion function then.
>
Agree
> It's bit confusing that in the declaration you used RESP_HDR_SIZE and here
> you do it with DATA_OFFSET instead. It feels DATA_OFFSET is unnecessary
> duplicate of RESP_HDR_SIZE and will easily lead confusing variation such
> as above.
>
I totally agree with you, it is duplicated.
In declaration, u8 resp[RESP_HDR_SIZE]; RESP_HDR_SIZE indicates the size.
When assigning, val = resp[DATA_OFFSET]; let us know we are extracting a
data from a response without thinking, so I added an alias. Removing it
is also fine for me.
Best wishes,
Pengyu
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 3/5] usb: typec: ucsi: add Huawei Matebook E Go (sc8280xp) ucsi driver
2024-12-27 17:13 ` [PATCH 3/5] usb: typec: ucsi: add Huawei Matebook E Go (sc8280xp) ucsi driver Pengyu Luo
2024-12-28 13:06 ` Bryan O'Donoghue
2024-12-29 4:40 ` Dmitry Baryshkov
@ 2024-12-29 16:15 ` Markus Elfring
2 siblings, 0 replies; 51+ messages in thread
From: Markus Elfring @ 2024-12-29 16:15 UTC (permalink / raw)
To: Pengyu Luo, platform-driver-x86, devicetree, linux-arm-msm,
linux-usb, linux-pm, Bjorn Andersson, Bryan O'Donoghue,
Conor Dooley, Greg Kroah-Hartman, Hans de Goede, Heikki Krogerus,
Ilpo Järvinen, Konrad Dybcio, Krzysztof Kozlowski,
Rob Herring, Sebastian Reichel
Cc: LKML, Dmitry Baryshkov, Nikita Travkin
> The Huawei Matebook E Go (sc8280xp) tablet provides implements UCSI
Omit this verb here?
> interface in the onboard EC. …
> +++ b/drivers/platform/arm64/huawei-gaokun-ec.c
> @@ -0,0 +1,598 @@
…
> +static void gaokun_set_orientation(struct ucsi_connector *con,
> + struct gaokun_ucsi_port *port)
> +{
…
> + spin_lock_irqsave(&port->lock, flags);
> + ccx = port->ccx;
> + spin_unlock_irqrestore(&port->lock, flags);
> +
> + typec_set_orientation(con->port, CCX_TO_ORI(ccx));
> +}
…
Under which circumstances would you become interested to apply a statement
like “guard(spinlock_irqsave)(&port->lock);”?
https://elixir.bootlin.com/linux/v6.13-rc3/source/include/linux/spinlock.h#L572
Regards,
Markus
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 3/5] usb: typec: ucsi: add Huawei Matebook E Go (sc8280xp) ucsi driver
2024-12-29 14:51 ` Bryan O'Donoghue
@ 2024-12-29 16:25 ` Pengyu Luo
0 siblings, 0 replies; 51+ messages in thread
From: Pengyu Luo @ 2024-12-29 16:25 UTC (permalink / raw)
To: bryan.odonoghue
Cc: andersson, conor+dt, devicetree, dmitry.baryshkov, gregkh,
hdegoede, heikki.krogerus, ilpo.jarvinen, konradybcio, krzk+dt,
linux-arm-msm, linux-kernel, linux-pm, linux-usb, mitltlatltl,
nikita, platform-driver-x86, robh, sre
On Sun, Dec 29, 2024 at 10:52 PM Bryan O'Donoghue <bryan.odonoghue@linaro.org> wrote:
> On 28/12/2024 14:38, Pengyu Luo wrote:
> > On Sat, Dec 28, 2024 at 9:06 PM Bryan O'Donoghue <bryan.odonoghue@linaro.org> wrote:
> >> On 27/12/2024 17:13, Pengyu Luo wrote:
> >>> The Huawei Matebook E Go (sc8280xp) tablet provides implements UCSI
> >>> interface in the onboard EC. Add the glue driver to interface the
> >>> platform's UCSI implementation.
> >>>
> >>> Signed-off-by: Pengyu Luo <mitltlatltl@gmail.com>
> >>> ---
> >>> drivers/usb/typec/ucsi/Kconfig | 9 +
> >>> drivers/usb/typec/ucsi/Makefile | 1 +
> >>> drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c | 481 ++++++++++++++++++++
> >>> 3 files changed, 491 insertions(+)
> >>> create mode 100644 drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c
> >>>
> >>> diff --git a/drivers/usb/typec/ucsi/Kconfig b/drivers/usb/typec/ucsi/Kconfig
> >>> index 680e1b87b..0d0f07488 100644
> >>> --- a/drivers/usb/typec/ucsi/Kconfig
> >>> +++ b/drivers/usb/typec/ucsi/Kconfig
> >>> @@ -78,4 +78,13 @@ config UCSI_LENOVO_YOGA_C630
> >>> To compile the driver as a module, choose M here: the module will be
> >>> called ucsi_yoga_c630.
> >>>
> >>> +config UCSI_HUAWEI_GAOKUN
> >>> + tristate "UCSI Interface Driver for Huawei Matebook E Go (sc8280xp)"
> >>> + depends on EC_HUAWEI_GAOKUN
> >>> + help
> >>> + This driver enables UCSI support on the Huawei Matebook E Go tablet.
> >>> +
> >>> + To compile the driver as a module, choose M here: the module will be
> >>> + called ucsi_huawei_gaokun.
> >>> +
> >>> endif
> >>> diff --git a/drivers/usb/typec/ucsi/Makefile b/drivers/usb/typec/ucsi/Makefile
> >>> index aed41d238..0b400122b 100644
> >>> --- a/drivers/usb/typec/ucsi/Makefile
> >>> +++ b/drivers/usb/typec/ucsi/Makefile
> >>> @@ -22,3 +22,4 @@ obj-$(CONFIG_UCSI_CCG) += ucsi_ccg.o
> >>> obj-$(CONFIG_UCSI_STM32G0) += ucsi_stm32g0.o
> >>> obj-$(CONFIG_UCSI_PMIC_GLINK) += ucsi_glink.o
> >>> obj-$(CONFIG_UCSI_LENOVO_YOGA_C630) += ucsi_yoga_c630.o
> >>> +obj-$(CONFIG_UCSI_HUAWEI_GAOKUN) += ucsi_huawei_gaokun.o
> >>> diff --git a/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c
> >>> new file mode 100644
> >>> index 000000000..84ed0407d
> >>> --- /dev/null
> >>> +++ b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c
> >>> @@ -0,0 +1,481 @@
> >>> +// SPDX-License-Identifier: GPL-2.0-only
> >>> +/*
> >>> + * ucsi-huawei-gaokun - A UCSI driver for HUAWEI Matebook E Go (sc8280xp)
> >>> + *
> >>> + * reference: drivers/usb/typec/ucsi/ucsi_yoga_c630.c
> >>> + * drivers/usb/typec/ucsi/ucsi_glink.c
> >>> + * drivers/soc/qcom/pmic_glink_altmode.c
> >>> + *
> >>> + * Copyright (C) 2024 Pengyu Luo <mitltlatltl@gmail.com>
> >>> + */
> >>> +
> >>> +#include <linux/auxiliary_bus.h>
> >>> +#include <linux/bitops.h>
> >>> +#include <linux/completion.h>
> >>> +#include <linux/container_of.h>
> >>> +#include <linux/delay.h>
> >>> +#include <linux/module.h>
> >>> +#include <linux/notifier.h>
> >>> +#include <linux/of.h>
> >>> +#include <linux/string.h>
> >>> +#include <linux/workqueue_types.h>
> >>> +
> >>> +#include <linux/usb/pd_vdo.h>
> >>> +#include <drm/bridge/aux-bridge.h
> >>
> >> Is there a reason you don't have strict include alphanumeric ordering here ?
> >>
> >
> > These two is dp/alt mode related, so listing them out. Above of them are
> > general things.
>
> OK. Unless there's an include dependency reason you need to, please just
> sort the headers alphanumerically in order
>
> #include <globals_first>
> #include <globals_first_alpha>
>
> #include "locals_next"
> #include "locals_next_alpha_also"
>
I will follow this in v2.
> >>>
> >>> +
> >>> +#include "ucsi.h"
> >>> +#include <linux/platform_data/huawei-gaokun-ec.h>
> >>> +
> >>> +
> >>> +#define EC_EVENT_UCSI 0x21
> >>> +#define EC_EVENT_USB 0x22
> >>> +
> >>> +#define GAOKUN_CCX_MASK GENMASK(1, 0)
> >>> +#define GAOKUN_MUX_MASK GENMASK(3, 2)
> >>> +
> >>> +#define GAOKUN_DPAM_MASK GENMASK(3, 0)
> >>> +#define GAOKUN_HPD_STATE_MASK BIT(4)
> >>> +#define GAOKUN_HPD_IRQ_MASK BIT(5)
> >>> +
> >>> +#define CCX_TO_ORI(ccx) (++ccx % 3)
> >>
> >> Why do you increment the value of the enum ?
> >> Seems strange.
> >>
> >
> > EC's logic, it is just a trick. Qualcomm maps
> > 0 1 2 to normal, reverse, none(no device insert)
> > typec lib maps 1 2 0 to that.
>
> I'd suggest making the trick much more obvious.
>
> Either with a comment or just mapping 1:1 between EC and Linux' view of
> this data.
>
> The main reason for that is to make it easier to
> read/understand/maintain/debug.
>
I got it
>
>
> >>> + port->svid = USB_SID_DISPLAYPORT;
> >>> + if (port->ccx == USBC_CCX_REVERSE)
> >>> + port->mode -= 6;
> >>
> >> why minus six ?
> >> needs a comment.
> >>
> >
> > EC's logic. I don't know why, it is a quirk from Qualcomm or Huawei.
> > I will mention this.
>
> Instead of hard-coding a mapping between the EC's mode and Linux' UCSI
> enum - just make a define or an inline, ideally something with
>
> switch(port->mode)
> case GOAKUN_MODE_0:
> val = LINUX_UCSI_MODE_X;
> case GOAKUN_MODE_1:
> val = LINUX_UCSI_MODE_Y;
> }
>
> That will make the mapping obvious and also ensure both to yourself and
> to your reviewers that you have accounted for _all_ of the potential
> mappings and if those mappings don't exist then the default: of your
> switch statement should make some noise about it
>
> dev_err(dev, "GOKUN UCSI mode %d unmapped\n"); or something like that.
>
Makes sense
>
> >
> >>> + break;
> >>> + default:
> >>> + break;
> >>> + }
> >>> +
> >>> + spin_unlock_irqrestore(&port->lock, flags);
> >>> +}
> >>> +
> >>> +static int gaokun_ucsi_refresh(struct gaokun_ucsi *uec)
> >>> +{
> >>> + struct gaokun_ucsi_reg ureg;
> >>> + int ret, idx;
> >>> +
> >>> + ret = gaokun_ec_ucsi_get_reg(uec->ec, (u8 *)&ureg);
> >>> + if (ret)
> >>> + return -EIO;
> >>> +
> >>> + uec->port_num = ureg.port_num;
> >>> + idx = GET_IDX(ureg.port_updt);
> >>> +
> >>> + if (idx >= 0 && idx < ureg.port_num)
> >>> + gaokun_ucsi_port_update(&uec->ports[idx], ureg.port_data);
> >>
> >> Since you are checking the validity of the index, you should -EINVAL if
> >> the index is out of range.
> >>
> >
> > EC / pmic glink encode every port in a bit
> > 0/1/2/4/... => ???/left/right/some port
> >
> > I remap it to -1/0/1/2, to access specific port exceptions(-1) are not
> > harmful, later in gaokun_ucsi_altmode_notify_ind
> >
> > if (idx < 0)
> > gaokun_ec_ucsi_pan_ack(uec->ec, idx);
> > else
> > gaokun_ucsi_handle_altmode(&uec->ports[idx]);
> >
> > gaokun_ec_ucsi_pan_ack can handle exceptions.
> >
>
> gaokun_ucsi_refresh() can return
>
> -EIO
> -1
> >=0
>
> Where -EIO and -1 both trigger gaokun_ec_ucsi_pan_ack() in
> gaokun_ucsi_altmode_notify_ind()
>
> So if the index doesn't matter and < 0 => pan_ack() is OK or -EIO is not
> returning meaningful error.
>
> Either way strongly advise against mixing a negative index as having a
> valid meaning with actual -E error codes...
>
> As a reviewer doing a fist-pass this looks suspicous and implies more
> thought/refinement should be done to the flow.
>
Agree, I will drop -EIO to use -1 instead, and define -1 as NO_UPDATE.
It is also resonable when there is a EC transaction error.
Best wishes,
Pengyu
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 1/5] dt-bindings: platform: Add Huawei Matebook E Go EC
2024-12-29 10:28 ` Pengyu Luo
@ 2024-12-29 21:45 ` Krzysztof Kozlowski
0 siblings, 0 replies; 51+ messages in thread
From: Krzysztof Kozlowski @ 2024-12-29 21:45 UTC (permalink / raw)
To: Pengyu Luo
Cc: andersson, bryan.odonoghue, conor+dt, devicetree,
dmitry.baryshkov, gregkh, hdegoede, heikki.krogerus,
ilpo.jarvinen, konradybcio, krzk+dt, linux-arm-msm, linux-kernel,
linux-pm, linux-usb, nikita, platform-driver-x86, robh, sre
On 29/12/2024 11:28, Pengyu Luo wrote:
> On Sun, Dec 29, 2024 at 5:43 PM Krzysztof Kozlowski <krzk@kernel.org> wrote:
>> On 28/12/2024 12:34, Pengyu Luo wrote:
>>>> On Sat, Dec 28, 2024 at 5:58 PM Krzysztof Kozlowski <krzk@kernel.org> wrote:
>>>> On 27/12/2024 18:13, Pengyu Luo wrote:
>>>>> +
>>>>> +#include <linux/platform_data/huawei-gaokun-ec.h>
>>>>> +
>>>>> +#define EC_EVENT 0x06
>>>>> +
>>>>> +/* Also can be found in ACPI specification 12.3 */
>>>>> +#define EC_READ 0x80
>>>>> +#define EC_WRITE 0x81
>>>>> +#define EC_BURST 0x82
>>>>> +#define EC_QUERY 0x84
>>>>> +
>>>>> +
>>>>> +#define EC_EVENT_LID 0x81
>>>>> +
>>>>> +#define EC_LID_STATE 0x80
>>>>> +#define EC_LID_OPEN BIT(1)
>>>>> +
>>>>> +#define UCSI_REG_SIZE 7
>>>>> +
>>>>> +/* for tx, command sequences are arranged as
>>>>
>>>> Use Linux style comments, see coding style.
>>>>
>>>
>>> Agree
>>>
>>>>> + * {master_cmd, slave_cmd, data_len, data_seq}
>>>>> + */
>>>>> +#define REQ_HDR_SIZE 3
>>>>> +#define INPUT_SIZE_OFFSET 2
>>>>> +#define INPUT_DATA_OFFSET 3
>>>>> +
>>>>> +/* for rx, data sequences are arranged as
>>>>> + * {status, data_len(unreliable), data_seq}
>>>>> + */
>>>>> +#define RESP_HDR_SIZE 2
>>>>> +#define DATA_OFFSET 2
>>>>> +
>>>>> +
>>>>> +struct gaokun_ec {
>>>>> + struct i2c_client *client;
>>>>> + struct mutex lock;
>>>>
>>>> Missing doc. Run Checkpatch --strict, so you will know what is missing here.
>>>>
>>>
>>> I see. A comment for mutex lock.
>>>
>>>>> + struct blocking_notifier_head notifier_list;
>>>>> + struct input_dev *idev;
>>>>> + bool suspended;
>>>>> +};
>>>>> +
>>>>
>>>>
>>>>
>>>> ...
>>>>
>>>>> +
>>>>> +static DEVICE_ATTR_RO(temperature);
>>>>> +
>>>>> +static struct attribute *gaokun_wmi_features_attrs[] = {
>>>>> + &dev_attr_charge_control_thresholds.attr,
>>>>> + &dev_attr_smart_charge_param.attr,
>>>>> + &dev_attr_smart_charge.attr,
>>>>> + &dev_attr_fn_lock_state.attr,
>>>>> + &dev_attr_temperature.attr,
>>>>> + NULL,
>>>>> +};
>>>>
>>>>
>>>> No, don't expose your own interface. Charging is already exposed by
>>>> power supply framework. Temperature by hwmon sensors. Drop all these and
>>>> never re-implement existing kernel user-space interfaces.
>>>>
>>>
>>> I don't quite understand what you mean. You mean I should use hwmon
>>> interface like hwmon_device_register_with_groups to register it, right?
>>
>> You added sysfs interface, I think. My comment is: do not. We have
>> existing interfaces.
>>
>
> I agree with you, but device_add_groups is used to add sysfs interface
> everywhere, device_add_groups are wrapped in acpi_battery_hook, they
> handle charge_control_thresholds like this, since qcom arm64 do not
> support acpi on linux, we do not use acpi_battery_hook to implement it,
> so it is reasonable to implement it in PSY drivers.
OK, then do not make it a platform driver sysfs ABI but power supply one
and document the ABI (see Documentation/ABI/)
>
> some examples:
>
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 1/5] dt-bindings: platform: Add Huawei Matebook E Go EC
2024-12-29 10:12 ` Pengyu Luo
@ 2024-12-30 7:28 ` Aiqun(Maria) Yu
2024-12-30 7:35 ` Krzysztof Kozlowski
` (2 more replies)
0 siblings, 3 replies; 51+ messages in thread
From: Aiqun(Maria) Yu @ 2024-12-30 7:28 UTC (permalink / raw)
To: Pengyu Luo, krzk
Cc: andersson, bryan.odonoghue, conor+dt, devicetree,
dmitry.baryshkov, gregkh, hdegoede, heikki.krogerus,
ilpo.jarvinen, konradybcio, krzk+dt, linux-arm-msm, linux-kernel,
linux-pm, linux-usb, nikita, platform-driver-x86, robh, sre
On 12/29/2024 6:12 PM, Pengyu Luo wrote:
[...]
>>>>> + - const: huawei,gaokun-ec
>>>>
>>>> How did you get the name?
>>>>
>>>
>>> From website of Huawei([1]), please search for 'gaokun' here, we can know
Gaokun appears to be a code name from Huawei for the HUAWEI MateBook E
Go devices.
Could you please specify which EC functions are customized specifically
for Gaokun and which EC functions are common features used in
qcom,sc8180x and qcom,sc8280xp boards? For example, the upstreamed ones
like sc8180x (Lenovo Flex 5G/Primus) and sc8280xp (CRD/Lenovo ThinkPad
X13s/Microsoft Arcata).
>>
[...]
>>
>
> Check the motherboard, https://postimg.cc/V5r4KCgx (Credit to Tianyu Gao <gty0622@gmail.com>)
The link is not accessible from my end. Could you please help follow the
document tips referenced by [1] if this content is important for the
overall naming design?
Here are some snippets for reference:
"for 'volatile' documents, please create an entry in the kernel
bugzilla https://bugzilla.kernel.org and attach a copy of these documents
to the bugzilla entry. Finally, provide the URL of the bugzilla entry in
the changelog."
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/process/maintainer-tip.rst
[1]
--
Thx and BRs,
Aiqun(Maria) Yu
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 1/5] dt-bindings: platform: Add Huawei Matebook E Go EC
2024-12-30 7:28 ` Aiqun(Maria) Yu
@ 2024-12-30 7:35 ` Krzysztof Kozlowski
2024-12-30 9:10 ` Aiqun(Maria) Yu
2024-12-30 8:00 ` Pengyu Luo
2025-01-01 5:57 ` Pengyu Luo
2 siblings, 1 reply; 51+ messages in thread
From: Krzysztof Kozlowski @ 2024-12-30 7:35 UTC (permalink / raw)
To: Aiqun(Maria) Yu, Pengyu Luo
Cc: andersson, bryan.odonoghue, conor+dt, devicetree,
dmitry.baryshkov, gregkh, hdegoede, heikki.krogerus,
ilpo.jarvinen, konradybcio, krzk+dt, linux-arm-msm, linux-kernel,
linux-pm, linux-usb, nikita, platform-driver-x86, robh, sre
On 30/12/2024 08:28, Aiqun(Maria) Yu wrote:
>>>
> [...]
>>>
>>
>> Check the motherboard, https://postimg.cc/V5r4KCgx (Credit to Tianyu Gao <gty0622@gmail.com>)
>
> The link is not accessible from my end. Could you please help follow the
Link is accessible. Maybe you are using corporate network with some
firewalls/content filtering?
> document tips referenced by [1] if this content is important for the
> overall naming design?
>
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 1/5] dt-bindings: platform: Add Huawei Matebook E Go EC
2024-12-30 7:28 ` Aiqun(Maria) Yu
2024-12-30 7:35 ` Krzysztof Kozlowski
@ 2024-12-30 8:00 ` Pengyu Luo
2025-01-01 5:57 ` Pengyu Luo
2 siblings, 0 replies; 51+ messages in thread
From: Pengyu Luo @ 2024-12-30 8:00 UTC (permalink / raw)
To: quic_aiquny
Cc: andersson, bryan.odonoghue, conor+dt, devicetree,
dmitry.baryshkov, gregkh, hdegoede, heikki.krogerus,
ilpo.jarvinen, konradybcio, krzk+dt, krzk, linux-arm-msm,
linux-kernel, linux-pm, linux-usb, mitltlatltl, nikita,
platform-driver-x86, robh, sre
On Mon, Dec 30, 2024 at 3:28 PM Aiqun(Maria) Yu <quic_aiquny@quicinc.com> wrote:
> On 12/29/2024 6:12 PM, Pengyu Luo wrote:
> [...]
> >>>>> + - const: huawei,gaokun-ec
> >>>>
> >>>> How did you get the name?
> >>>>
> >>>
> >>> From website of Huawei([1]), please search for 'gaokun' here, we can know
>
> Gaokun appears to be a code name from Huawei for the HUAWEI MateBook E
> Go devices.
>
> Could you please specify which EC functions are customized specifically
> for Gaokun and which EC functions are common features used in
> qcom,sc8180x and qcom,sc8280xp boards? For example, the upstreamed ones
> like sc8180x (Lenovo Flex 5G/Primus) and sc8280xp (CRD/Lenovo ThinkPad
> X13s/Microsoft Arcata).
>
Generally, pmic glink is a subset to this EC,
common functions(slightlg different in implementations):
- Battery and charger monitoring; (drivers/power/supply/qcom_battmgr.c)
- UCSI (drivers/usb/typec/ucsi/ucsi_glink.c)
- Altmodes (drivers/soc/qcom/pmic_glink_altmode.c)
EC extended:
- Charge control and smart charge;
- Fn_lock settings;
- Tablet lid status;
- Temperature sensors;
- many other thngs (watchdog, more WMI functions, it is hard to reverse for me)
If necessary, I will add them to dt-binding, it is a bit bloated.
> >>
> [...]
> >>
> >
> > Check the motherboard, https://postimg.cc/V5r4KCgx (Credit to Tianyu Gao <gty0622@gmail.com>)
>
> The link is not accessible from my end. Could you please help follow the
> document tips referenced by [1] if this content is important for the
> overall naming design?
>
> Here are some snippets for reference:
> "for 'volatile' documents, please create an entry in the kernel
> bugzilla https://bugzilla.kernel.org and attach a copy of these documents
> to the bugzilla entry. Finally, provide the URL of the bugzilla entry in
> the changelog."
>
> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/process/maintainer-tip.rst
> [1]
>
I am an amateur, I have only read a small part of the documentation so
far. I will have a try in v2 after reading the doc.
Best wishes,
Pengyu
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 2/5] platform: arm64: add Huawei Matebook E Go (sc8280xp) EC driver
2024-12-27 17:13 ` [PATCH 2/5] platform: arm64: add Huawei Matebook E Go (sc8280xp) EC driver Pengyu Luo
` (3 preceding siblings ...)
2024-12-29 14:49 ` Markus Elfring
@ 2024-12-30 9:04 ` Aiqun(Maria) Yu
2024-12-30 10:44 ` Pengyu Luo
4 siblings, 1 reply; 51+ messages in thread
From: Aiqun(Maria) Yu @ 2024-12-30 9:04 UTC (permalink / raw)
To: Pengyu Luo, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Bjorn Andersson, Konrad Dybcio, Hans de Goede, Ilpo Järvinen,
Bryan O'Donoghue, Sebastian Reichel, Heikki Krogerus,
Greg Kroah-Hartman
Cc: devicetree, linux-kernel, linux-arm-msm, platform-driver-x86,
linux-pm, linux-usb, Dmitry Baryshkov, Nikita Travkin
On 12/28/2024 1:13 AM, Pengyu Luo wrote:
> There are 3 variants, Huawei released first 2 at the same time.
> Huawei Matebook E Go LTE(sc8180x), codename should be gaokun2.
> Huawei Matebook E Go(sc8280xp@3.0GHz), codename is gaokun3.
> Huawei Matebook E Go 2023(sc8280xp@2.69GHz).
>
> Adding support for the latter two variants for now, this driver should
> also work for the sc8180x variant according to acpi table files, but I
> don't have the device yet.
>
> Different from other Qualcomm Snapdragon sc8280xp based machines, the
> Huawei Matebook E Go uses an embedded controller while others use
> something called pmic glink. This embedded controller can be used to
> perform a set of various functions, including, but not limited:
>
> - Battery and charger monitoring;
> - Charge control and smart charge;
> - Fn_lock settings;
> - Tablet lid status;
> - Temperature sensors;
> - USB Type-C notifications (ports orientation, DP alt mode HPD);
> - USB Type-C PD (according to observation, up to 48w).
>
> Add the driver for the EC, that creates devices for UCSI, wmi and power
> supply devices.
>
> Signed-off-by: Pengyu Luo <mitltlatltl@gmail.com>
> ---
> drivers/platform/arm64/Kconfig | 19 +
> drivers/platform/arm64/Makefile | 2 +
> drivers/platform/arm64/huawei-gaokun-ec.c | 598 ++++++++++++++++++
> drivers/platform/arm64/huawei-gaokun-wmi.c | 283 +++++++++
> .../linux/platform_data/huawei-gaokun-ec.h | 90 +++
> 5 files changed, 992 insertions(+)
> create mode 100644 drivers/platform/arm64/huawei-gaokun-ec.c
> create mode 100644 drivers/platform/arm64/huawei-gaokun-wmi.c
> create mode 100644 include/linux/platform_data/huawei-gaokun-ec.h
>
> diff --git a/drivers/platform/arm64/Kconfig b/drivers/platform/arm64/Kconfig
> index f88395ea3..eb7fbacf0 100644
> --- a/drivers/platform/arm64/Kconfig
> +++ b/drivers/platform/arm64/Kconfig
> @@ -33,6 +33,25 @@ config EC_ACER_ASPIRE1
> laptop where this information is not properly exposed via the
> standard ACPI devices.
>
> +config EC_HUAWEI_GAOKUN
> + tristate "Huawei Matebook E Go (sc8280xp) Embedded Controller driver"
> + depends on ARCH_QCOM || COMPILE_TEST
> + depends on I2C
> + depends on DRM
> + depends on POWER_SUPPLY
> + depends on INPUT
> + help
> + Say Y here to enable the EC driver for Huawei Matebook E Go 2in1
> + tablet. The driver handles battery(information, charge control) and
> + USB Type-C DP HPD events as well as some misc functions like the lid
> + sensor and temperature sensors, etc.
> +
> + This driver provides battery and AC status support for the mentioned
> + laptop where this information is not properly exposed via the
> + standard ACPI devices.
> +
> + Say M or Y here to include this support.
> +
> config EC_LENOVO_YOGA_C630
> tristate "Lenovo Yoga C630 Embedded Controller driver"
> depends on ARCH_QCOM || COMPILE_TEST
> diff --git a/drivers/platform/arm64/Makefile b/drivers/platform/arm64/Makefile
> index b2ae9114f..ed32ad6c0 100644
> --- a/drivers/platform/arm64/Makefile
> +++ b/drivers/platform/arm64/Makefile
> @@ -6,4 +6,6 @@
> #
>
> obj-$(CONFIG_EC_ACER_ASPIRE1) += acer-aspire1-ec.o
> +obj-$(CONFIG_EC_HUAWEI_GAOKUN) += huawei-gaokun-ec.o
> +obj-$(CONFIG_EC_HUAWEI_GAOKUN) += huawei-gaokun-wmi.o
> obj-$(CONFIG_EC_LENOVO_YOGA_C630) += lenovo-yoga-c630.o
> diff --git a/drivers/platform/arm64/huawei-gaokun-ec.c b/drivers/platform/arm64/huawei-gaokun-ec.c
> new file mode 100644
> index 000000000..c1c657f7b
> --- /dev/null
> +++ b/drivers/platform/arm64/huawei-gaokun-ec.c
> @@ -0,0 +1,598 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * huawei-gaokun-ec - An EC driver for HUAWEI Matebook E Go (sc8280xp)
> + *
> + * reference: drivers/platform/arm64/acer-aspire1-ec.c
> + * drivers/platform/arm64/lenovo-yoga-c630.c
> + *
> + * Copyright (C) 2024 Pengyu Luo <mitltlatltl@gmail.com>
> + */
> +
> +#include <linux/auxiliary_bus.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/i2c.h>
> +#include <linux/input.h>
> +#include <linux/notifier.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/version.h>
> +
> +#include <linux/platform_data/huawei-gaokun-ec.h>
> +
> +#define EC_EVENT 0x06
> +
> +/* Also can be found in ACPI specification 12.3 */
It appears that the following EC commands are common to all ACPI-applied
embedded controllers. Is it possible to standardize these commands and API?
> +#define EC_READ 0x80
> +#define EC_WRITE 0x81
> +#define EC_BURST 0x82
> +#define EC_QUERY 0x84
> +
> +
> +#define EC_EVENT_LID 0x81
> +
> +#define EC_LID_STATE 0x80
> +#define EC_LID_OPEN BIT(1)
> +
> +#define UCSI_REG_SIZE 7
> +
> +/* for tx, command sequences are arranged as
> + * {master_cmd, slave_cmd, data_len, data_seq}
> + */
> +#define REQ_HDR_SIZE 3
> +#define INPUT_SIZE_OFFSET 2
> +#define INPUT_DATA_OFFSET 3
> +
> +/* for rx, data sequences are arranged as
> + * {status, data_len(unreliable), data_seq}
> + */
> +#define RESP_HDR_SIZE 2
> +#define DATA_OFFSET 2
> +
> +
> +struct gaokun_ec {
> + struct i2c_client *client;
> + struct mutex lock;
> + struct blocking_notifier_head notifier_list;
> + struct input_dev *idev;
> + bool suspended;
> +};
> +
> +static int gaokun_ec_request(struct gaokun_ec *ec, const u8 *req,
> + size_t resp_len, u8 *resp)
> +{
> + struct i2c_client *client = ec->client;
> + struct i2c_msg msgs[2] = {
> + {
> + .addr = client->addr,
> + .flags = client->flags,
> + .len = req[INPUT_SIZE_OFFSET] + REQ_HDR_SIZE,
> + .buf = req,
> + }, {
> + .addr = client->addr,
> + .flags = client->flags | I2C_M_RD,
> + .len = resp_len,
> + .buf = resp,
> + },
> + };
> +
> + mutex_lock(&ec->lock);
> +
> + i2c_transfer(client->adapter, msgs, 2);
ARRAY_SIZE(msgs) is suggested instead of pure 2.
> + usleep_range(2000, 2500);
Why is a sleep needed here? Is this information specified in any datasheet?
> +
> + mutex_unlock(&ec->lock);
> +
> + return *resp;
> +}
> +
> +/* -------------------------------------------------------------------------- */
> +/* Common API */
> +
> +/**
> + * gaokun_ec_read - read from EC
> + * @ec: The gaokun_ec
> + * @req: The sequence to request
> + * @resp_len: The size to read
> + * @resp: Where the data are read to
> + *
> + * This function is used to read data after writing a magic sequence to EC.
> + * All EC operations dependent on this functions.
> + *
> + * Huawei uses magic sequences everywhere to complete various functions, all
> + * these sequences are passed to ECCD(a ACPI method which is quiet similar
> + * to gaokun_ec_request), there is no good abstraction to generalize these
> + * sequences, so just wrap it for now. Almost all magic sequences are kept
> + * in this file.
> + */
> +int gaokun_ec_read(struct gaokun_ec *ec, const u8 *req,
> + size_t resp_len, u8 *resp)
> +{
> + return gaokun_ec_request(ec, req, resp_len, resp);
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_read);
> +
> +/**
> + * gaokun_ec_write - write to EC
> + * @ec: The gaokun_ec
> + * @req: The sequence to request
> + *
> + * This function has no big difference from gaokun_ec_read. When caller care
> + * only write status and no actual data are returnd, then use it.
> + */
> +int gaokun_ec_write(struct gaokun_ec *ec, u8 *req)
> +{
> + u8 resp[RESP_HDR_SIZE];
> +
> + return gaokun_ec_request(ec, req, sizeof(resp), resp);
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_write);
> +
> +int gaokun_ec_read_byte(struct gaokun_ec *ec, u8 *req, u8 *byte)
> +{
> + int ret;
> + u8 resp[RESP_HDR_SIZE + sizeof(*byte)];
> +
> + ret = gaokun_ec_read(ec, req, sizeof(resp), resp);
> + *byte = resp[DATA_OFFSET];
> +
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_read_byte);
> +
> +int gaokun_ec_register_notify(struct gaokun_ec *ec, struct notifier_block *nb)
> +{
> + return blocking_notifier_chain_register(&ec->notifier_list, nb);
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_register_notify);
> +
> +void gaokun_ec_unregister_notify(struct gaokun_ec *ec, struct notifier_block *nb)
> +{
> + blocking_notifier_chain_unregister(&ec->notifier_list, nb);
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_unregister_notify);
> +
> +/* -------------------------------------------------------------------------- */
> +/* API For PSY */
> +
> +int gaokun_ec_psy_multi_read(struct gaokun_ec *ec, u8 reg,
> + size_t resp_len, u8 *resp)
> +{
> + int i, ret;
> + u8 _resp[RESP_HDR_SIZE + 1];
> + u8 req[REQ_HDR_SIZE + 1] = {0x02, EC_READ, 1, };
Could it be made more readable by specifying the macro names for 0x02
and 1? This would help in understanding the meaning of these numbers.
Also, please ensure the actual size of the request buffer is handled
properly. In gaokun_ec_request(), the req is passed down directly, and
the i2c_msg.len is used dynamically with req[INPUT_SIZE_OFFSET] +
REQ_HDR_SIZE. This requires the caller to carefully manage the contents
to avoid memory over-read, making the code difficult to read.
Creating a defined macro can help you avoid manually defining the size.
For example:
#define REQ(size, data_0, data_1, args...) \
u8 req[REQ_HDR_SIZE + size] = {data_0, data_1, size, args};
> +
> + for (i = 0; i < resp_len; ++i) {
> + req[INPUT_DATA_OFFSET] = reg++;
> + ret = gaokun_ec_read(ec, req, sizeof(_resp), _resp);
> + if (ret)
> + return -EIO;
> + resp[i] = _resp[DATA_OFFSET];
> + }
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_psy_multi_read);
> +
> +/* -------------------------------------------------------------------------- */
> +/* API For WMI */
> +
> +/* Battery charging threshold */
> +int gaokun_ec_wmi_get_threshold(struct gaokun_ec *ec, u8 *value, int ind)
> +{
> + /* GBTT */
> + return gaokun_ec_read_byte(ec, (u8 []){0x02, 0x69, 1, ind}, value);
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_get_threshold);
> +
> +int gaokun_ec_wmi_set_threshold(struct gaokun_ec *ec, u8 start, u8 end)
> +{
> + /* SBTT */
> + int ret;
> + u8 req[REQ_HDR_SIZE + 2] = {0x02, 0x68, 2, 3, 0x5a};
> +
> + ret = gaokun_ec_write(ec, req);
> + if (ret)
> + return -EIO;
> +
> + if (start == 0 && end == 0)
> + return -EINVAL;
> +
> + if (start >= 0 && start <= end && end <= 100) {
> + req[INPUT_DATA_OFFSET] = 1;
> + req[INPUT_DATA_OFFSET + 1] = start;
> + ret = gaokun_ec_write(ec, req);
> + if (ret)
> + return -EIO;
> +
> + req[INPUT_DATA_OFFSET] = 2;
> + req[INPUT_DATA_OFFSET + 1] = end;
> + ret = gaokun_ec_write(ec, req);
> + } else {
> + return -EINVAL;
> + }
> +
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_set_threshold);
> +
> +/* Smart charge param */
> +int gaokun_ec_wmi_get_smart_charge_param(struct gaokun_ec *ec, u8 *value)
> +{
> + /* GBAC */
> + return gaokun_ec_read_byte(ec, (u8 []){0x02, 0xE6, 0}, value);
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_get_smart_charge_param);
> +
> +int gaokun_ec_wmi_set_smart_charge_param(struct gaokun_ec *ec, u8 value)
> +{
> + /* SBAC */
> + if (value < 0 || value > 2)
> + return -EINVAL;
> +
> + return gaokun_ec_write(ec, (u8 []){0x02, 0xE5, 1, value});
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_set_smart_charge_param);
> +
> +/* Smart charge */
> +int gaokun_ec_wmi_get_smart_charge(struct gaokun_ec *ec,
> + u8 data[GAOKUN_SMART_CHARGE_DATA_SIZE])
> +{
> + /* GBCM */
> + u8 req[REQ_HDR_SIZE] = {0x02, 0xE4, 0};
> + u8 resp[RESP_HDR_SIZE + 4];
> + int ret;
> +
> + ret = gaokun_ec_read(ec, req, sizeof(resp), resp);
> + if (ret)
> + return -EIO;
> +
> + data[0] = resp[DATA_OFFSET];
> + data[1] = resp[DATA_OFFSET + 1];
> + data[2] = resp[DATA_OFFSET + 2];
> + data[3] = resp[DATA_OFFSET + 3];
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_get_smart_charge);
> +
> +int gaokun_ec_wmi_set_smart_charge(struct gaokun_ec *ec,
> + u8 data[GAOKUN_SMART_CHARGE_DATA_SIZE])
> +{
> + /* SBCM */
> + u8 req[REQ_HDR_SIZE + GAOKUN_SMART_CHARGE_DATA_SIZE] = {0x02, 0XE3, 4,};
> +
> + if (!(data[2] >= 0 && data[2] <= data[3] && data[3] <= 100))
> + return -EINVAL;
> +
> + memcpy(req + INPUT_DATA_OFFSET, data, GAOKUN_SMART_CHARGE_DATA_SIZE);
> +
> + return gaokun_ec_write(ec, req);
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_set_smart_charge);
> +
> +/* Fn lock */
> +int gaokun_ec_wmi_get_fn_lock(struct gaokun_ec *ec, u8 *on)
> +{
> + /* GFRS */
> + int ret;
> + u8 val;
> + u8 req[REQ_HDR_SIZE] = {0x02, 0x6B, 0};
> +
> + ret = gaokun_ec_read_byte(ec, req, &val);
> + if (val == 0x55)
> + *on = 0;
> + else if (val == 0x5A)
> + *on = 1;
> + else
> + return -EIO;
> +
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_get_fn_lock);
> +
> +int gaokun_ec_wmi_set_fn_lock(struct gaokun_ec *ec, u8 on)
> +{
> + /* SFRS */
> + u8 req[REQ_HDR_SIZE + 1] = {0x02, 0x6C, 1,};
> +
> + if (on == 0)
> + req[INPUT_DATA_OFFSET] = 0x55;
> + else if (on == 1)
> + req[INPUT_DATA_OFFSET] = 0x5A;
> + else
> + return -EINVAL;
> +
> + return gaokun_ec_write(ec, req);
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_set_fn_lock);
> +
> +/* Thermal Zone */
> +/* Range from 0 to 0x2C, partial valid */
> +static const u8 temp_reg[] = {0x05, 0x07, 0x08, 0x0E, 0x0F, 0x12, 0x15, 0x1E,
> + 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
> + 0x27, 0x28, 0x29, 0x2A};
> +
> +int gaokun_ec_wmi_get_temp(struct gaokun_ec *ec, s16 temp[GAOKUN_TZ_REG_NUM])
> +{
> + /* GTMP */
> + u8 req[REQ_HDR_SIZE] = {0x02, 0x61, 1,};
> + u8 resp[RESP_HDR_SIZE + sizeof(s16)];
> + int ret, i = 0;
> +
> + while (i < GAOKUN_TZ_REG_NUM) {
> + req[INPUT_DATA_OFFSET] = temp_reg[i];
> + ret = gaokun_ec_read(ec, req, sizeof(resp), resp);
> + if (ret)
> + return -EIO;
> + temp[i++] = *(s16 *)(resp + DATA_OFFSET);
> + }
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_wmi_get_temp);
> +
> +/* -------------------------------------------------------------------------- */
> +/* API For UCSI */
> +
> +int gaokun_ec_ucsi_read(struct gaokun_ec *ec,
> + u8 resp[GAOKUN_UCSI_READ_SIZE])
> +{
> + u8 req[REQ_HDR_SIZE] = {0x3, 0xD5, 0};
> + u8 _resp[RESP_HDR_SIZE + GAOKUN_UCSI_READ_SIZE];
> + int ret;
> +
> + ret = gaokun_ec_read(ec, req, sizeof(_resp), _resp);
> + if (ret)
> + return ret;
> +
> + memcpy(resp, _resp + DATA_OFFSET, GAOKUN_UCSI_READ_SIZE);
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_ucsi_read);
> +
> +int gaokun_ec_ucsi_write(struct gaokun_ec *ec,
> + const u8 req[GAOKUN_UCSI_WRITE_SIZE])
> +{
> + u8 _req[REQ_HDR_SIZE + GAOKUN_UCSI_WRITE_SIZE];
> +
> + _req[0] = 0x03;
> + _req[1] = 0xD4;
> + _req[INPUT_SIZE_OFFSET] = GAOKUN_UCSI_WRITE_SIZE;
> + memcpy(_req + INPUT_DATA_OFFSET, req, GAOKUN_UCSI_WRITE_SIZE);
> +
> + return gaokun_ec_write(ec, _req);
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_ucsi_write);
> +
> +int gaokun_ec_ucsi_get_reg(struct gaokun_ec *ec, u8 *ureg)
> +{
> + u8 req[REQ_HDR_SIZE] = {0x03, 0xD3, 0};
> + u8 _resp[RESP_HDR_SIZE + UCSI_REG_SIZE];
> + int ret;
> +
> + ret = gaokun_ec_read(ec, req, sizeof(_resp), _resp);
> + if (ret)
> + return ret;
> +
> + memcpy(ureg, _resp + DATA_OFFSET, UCSI_REG_SIZE);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_ucsi_get_reg);
> +
> +int gaokun_ec_ucsi_pan_ack(struct gaokun_ec *ec, int port_id)
> +{
> + u8 req[REQ_HDR_SIZE + 1] = {0x03, 0xD2, 1, 0};
> +
> + if (port_id >= 0)
> + req[INPUT_DATA_OFFSET] = 1 << port_id;
> +
> + return gaokun_ec_write(ec, req);
> +}
> +EXPORT_SYMBOL_GPL(gaokun_ec_ucsi_pan_ack);
> +
> +/* -------------------------------------------------------------------------- */
> +/* Modern Standby */
> +
> +static int gaokun_ec_suspend(struct device *dev)
> +{
> + struct gaokun_ec *ec = dev_get_drvdata(dev);
> + u8 req[REQ_HDR_SIZE + 1] = {0x02, 0xB2, 1, 0xDB};
> + int ret;
> +
> + if (ec->suspended)
> + return 0;
> +
> + ret = gaokun_ec_write(ec, req);
> +
> + if (ret)
> + return ret;
> +
> + ec->suspended = true;
> +
> + return 0;
> +}
> +
> +static int gaokun_ec_resume(struct device *dev)
> +{
> + struct gaokun_ec *ec = dev_get_drvdata(dev);
> + u8 req[REQ_HDR_SIZE + 1] = {0x02, 0xB2, 1, 0xEB};
> + int ret;
> + int i;
> +
> + if (!ec->suspended)
> + return 0;
> +
> + for (i = 0; i < 3; i++) {
> + ret = gaokun_ec_write(ec, req);
> + if (ret == 0)
> + break;
> +
> + msleep(100);
> + };
> +
> + ec->suspended = false;
> +
> + return 0;
> +}
> +
> +static void gaokun_aux_release(struct device *dev)
> +{
> + struct auxiliary_device *adev = to_auxiliary_dev(dev);
> +
> + kfree(adev);
> +}
> +
> +static void gaokun_aux_remove(void *data)
> +{
> + struct auxiliary_device *adev = data;
> +
> + auxiliary_device_delete(adev);
> + auxiliary_device_uninit(adev);
> +}
> +
> +static int gaokun_aux_init(struct device *parent, const char *name,
> + struct gaokun_ec *ec)
> +{
> + struct auxiliary_device *adev;
> + int ret;
> +
> + adev = kzalloc(sizeof(*adev), GFP_KERNEL);
> + if (!adev)
> + return -ENOMEM;
> +
> + adev->name = name;
> + adev->id = 0;
> + adev->dev.parent = parent;
> + adev->dev.release = gaokun_aux_release;
> + adev->dev.platform_data = ec;
> + /* Allow aux devices to access parent's DT nodes directly */
> + device_set_of_node_from_dev(&adev->dev, parent);
> +
> + ret = auxiliary_device_init(adev);
> + if (ret) {
> + kfree(adev);
> + return ret;
> + }
> +
> + ret = auxiliary_device_add(adev);
> + if (ret) {
> + auxiliary_device_uninit(adev);
> + return ret;
> + }
> +
> + return devm_add_action_or_reset(parent, gaokun_aux_remove, adev);
> +}
> +
> +/* -------------------------------------------------------------------------- */
> +/* EC */
> +
> +static irqreturn_t gaokun_ec_irq_handler(int irq, void *data)
> +{
> + struct gaokun_ec *ec = data;
> + u8 req[REQ_HDR_SIZE] = {EC_EVENT, EC_QUERY, 0};
> + u8 status, id;
> + int ret;
> +
> + ret = gaokun_ec_read_byte(ec, req, &id);
> + if (ret)
> + return IRQ_HANDLED;
> +
> + switch (id) {
> + case 0x0: /* No event */
> + break;
> +
> + case EC_EVENT_LID:
> + gaokun_ec_psy_read_byte(ec, EC_LID_STATE, &status);
> + status = EC_LID_OPEN & status;
> + input_report_switch(ec->idev, SW_LID, !status);
> + input_sync(ec->idev);
> + break;
> +
> + default:
> + blocking_notifier_call_chain(&ec->notifier_list, id, ec);
> + }
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int gaokun_ec_probe(struct i2c_client *client)
> +{
> + struct device *dev = &client->dev;
> + struct gaokun_ec *ec;
> + int ret;
> +
> + ec = devm_kzalloc(dev, sizeof(*ec), GFP_KERNEL);
> + if (!ec)
> + return -ENOMEM;
> +
> + mutex_init(&ec->lock);
> + ec->client = client;
> + i2c_set_clientdata(client, ec);
> + BLOCKING_INIT_NOTIFIER_HEAD(&ec->notifier_list);
> +
> + /* Lid switch */
> + ec->idev = devm_input_allocate_device(dev);
> + if (!ec->idev)
> + return -ENOMEM;
> +
> + ec->idev->name = "LID";
> + ec->idev->phys = "gaokun-ec/input0";
> + input_set_capability(ec->idev, EV_SW, SW_LID);
> +
> + ret = input_register_device(ec->idev);
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to register input device\n");
> +
> + ret = gaokun_aux_init(dev, "psy", ec);
> + if (ret)
> + return ret;
> +
> + ret = gaokun_aux_init(dev, "wmi", ec);
> + if (ret)
> + return ret;
> +
> + ret = gaokun_aux_init(dev, "ucsi", ec);
> + if (ret)
> + return ret;
> +
> + ret = devm_request_threaded_irq(dev, client->irq, NULL,
> + gaokun_ec_irq_handler, IRQF_ONESHOT,
> + dev_name(dev), ec);
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to request irq\n");
> +
> + return 0;
> +}
> +
> +static const struct i2c_device_id gaokun_ec_id[] = {
> + { "gaokun-ec", },
> + { }
> +};
> +MODULE_DEVICE_TABLE(i2c, gaokun_ec_id);
> +
> +static const struct of_device_id gaokun_ec_of_match[] = {
> + { .compatible = "huawei,gaokun-ec", },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, gaokun_ec_of_match);
> +
> +static const struct dev_pm_ops gaokun_ec_pm_ops = {
> + NOIRQ_SYSTEM_SLEEP_PM_OPS(gaokun_ec_suspend, gaokun_ec_resume)
> +};
> +
> +static struct i2c_driver gaokun_ec_driver = {
> + .driver = {
> + .name = "gaokun-ec",
> + .of_match_table = gaokun_ec_of_match,
> + .pm = &gaokun_ec_pm_ops,
> + },
> + .probe = gaokun_ec_probe,
> + .id_table = gaokun_ec_id,
> +};
> +module_i2c_driver(gaokun_ec_driver);
> +
> +MODULE_DESCRIPTION("HUAWEI Matebook E Go EC driver");
> +MODULE_AUTHOR("Pengyu Luo <mitltlatltl@gmail.com>");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/platform/arm64/huawei-gaokun-wmi.c b/drivers/platform/arm64/huawei-gaokun-wmi.c
> new file mode 100644
> index 000000000..793cb1659
> --- /dev/null
> +++ b/drivers/platform/arm64/huawei-gaokun-wmi.c
> @@ -0,0 +1,283 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * huawei-gaokun-wmi - A WMI driver for HUAWEI Matebook E Go (sc8280xp)
> + *
> + * reference: drivers/platform/x86/huawei-wmi.c
> + *
> + * Copyright (C) 2024 Pengyu Luo <mitltlatltl@gmail.com>
> + */
> +
> +#include <linux/auxiliary_bus.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/sysfs.h>
> +#include <linux/version.h>
> +
> +#include <linux/platform_data/huawei-gaokun-ec.h>
> +
> +struct gaokun_wmi {
> + struct gaokun_ec *ec;
> + struct device *dev;
> + struct platform_device *wmi;
> +};
> +
> +/* -------------------------------------------------------------------------- */
> +/* Battery charging threshold */
> +
> +enum gaokun_wmi_threshold_ind {
> + START = 1,
> + END = 2,
> +};
> +
> +static ssize_t charge_control_thresholds_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + int ret;
> + u8 start, end;
> + struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
> +
> + ret = gaokun_ec_wmi_get_threshold(ecwmi->ec, &start, START)
> + || gaokun_ec_wmi_get_threshold(ecwmi->ec, &end, END);
> + if (ret)
> + return ret;
> +
> + return sysfs_emit(buf, "%d %d\n", start, end);
> +}
> +
> +static ssize_t charge_control_thresholds_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t size)
> +{
> + int ret;
> + u8 start, end;
> + struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
> +
> + if (sscanf(buf, "%hhd %hhd", &start, &end) != 2)
> + return -EINVAL;
> +
> + ret = gaokun_ec_wmi_set_threshold(ecwmi->ec, start, end);
> + if (ret)
> + return ret;
> +
> + return size;
> +}
> +
> +static DEVICE_ATTR_RW(charge_control_thresholds);
> +
> +/* -------------------------------------------------------------------------- */
> +/* Smart charge param */
> +
> +static ssize_t smart_charge_param_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + int ret;
> + u8 value;
> + struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
> +
> + ret = gaokun_ec_wmi_get_smart_charge_param(ecwmi->ec, &value);
> + if (ret)
> + return ret;
> +
> + return sysfs_emit(buf, "%d\n", value);
> +}
> +
> +static ssize_t smart_charge_param_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t size)
> +{
> + int ret;
> + u8 value;
> + struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
> +
> + if (kstrtou8(buf, 10, &value))
> + return -EINVAL;
> +
> + ret = gaokun_ec_wmi_set_smart_charge_param(ecwmi->ec, value);
> + if (ret)
> + return ret;
> +
> + return size;
> +}
> +
> +static DEVICE_ATTR_RW(smart_charge_param);
> +
> +/* -------------------------------------------------------------------------- */
> +/* Smart charge */
> +
> +static ssize_t smart_charge_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + int ret;
> + u8 bf[GAOKUN_SMART_CHARGE_DATA_SIZE];
> + struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
> +
> + ret = gaokun_ec_wmi_get_smart_charge(ecwmi->ec, bf);
> + if (ret)
> + return ret;
> +
> + return sysfs_emit(buf, "%d %d %d %d\n",
> + bf[0], bf[1], bf[2], bf[3]);
> +}
> +
> +static ssize_t smart_charge_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t size)
> +{
> + int ret;
> + u8 bf[GAOKUN_SMART_CHARGE_DATA_SIZE];
> + struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
> +
> + if (sscanf(buf, "%hhd %hhd %hhd %hhd", bf, bf + 1, bf + 2, bf + 3) != 4)
> + return -EINVAL;
> +
> + ret = gaokun_ec_wmi_set_smart_charge(ecwmi->ec, bf);
> + if (ret)
> + return ret;
> +
> + return size;
> +}
> +
> +static DEVICE_ATTR_RW(smart_charge);
> +
> +/* -------------------------------------------------------------------------- */
> +/* Fn lock */
> +
> +static ssize_t fn_lock_state_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + int ret;
> + u8 on;
> + struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
> +
> + ret = gaokun_ec_wmi_get_fn_lock(ecwmi->ec, &on);
> + if (ret)
> + return ret;
> +
> + return sysfs_emit(buf, "%d\n", on);
> +}
> +
> +static ssize_t fn_lock_state_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t size)
> +{
> + int ret;
> + u8 on;
> + struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
> +
> + if (kstrtou8(buf, 10, &on))
> + return -EINVAL;
> +
> + ret = gaokun_ec_wmi_set_fn_lock(ecwmi->ec, on);
> + if (ret)
> + return ret;
> +
> + return size;
> +}
> +
> +static DEVICE_ATTR_RW(fn_lock_state);
> +
> +/* -------------------------------------------------------------------------- */
> +/* Thermal Zone */
> +
> +static ssize_t temperature_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> +
> + int ret, len, i;
> + char *ptr = buf;
> + s16 value;
> + s16 temp[GAOKUN_TZ_REG_NUM];
> + struct gaokun_wmi *ecwmi = dev_get_drvdata(dev);
> +
> + ret = gaokun_ec_wmi_get_temp(ecwmi->ec, temp);
> + if (ret)
> + return ret;
> +
> + i = 0;
> + len = 0;
> + while (i < GAOKUN_TZ_REG_NUM) {
> + value = temp[i++];
> + if (value < 0) {
> + len += sprintf(ptr + len, "-");
> + value = -value;
> + }
> + len += sprintf(ptr + len, "%d.%d ", value / 10, value % 10);
> + }
> + len += sprintf(ptr + len, "\n");
> +
> + return len;
> +}
> +
> +static DEVICE_ATTR_RO(temperature);
> +
> +static struct attribute *gaokun_wmi_features_attrs[] = {
> + &dev_attr_charge_control_thresholds.attr,
> + &dev_attr_smart_charge_param.attr,
> + &dev_attr_smart_charge.attr,
> + &dev_attr_fn_lock_state.attr,
> + &dev_attr_temperature.attr,
> + NULL,
> +};
> +ATTRIBUTE_GROUPS(gaokun_wmi_features);
> +
> +static int gaokun_wmi_probe(struct auxiliary_device *adev,
> + const struct auxiliary_device_id *id)
> +{
> + struct gaokun_ec *ec = adev->dev.platform_data;
> + struct device *dev = &adev->dev;
> + struct gaokun_wmi *ecwmi;
> +
> + ecwmi = devm_kzalloc(&adev->dev, sizeof(*ecwmi), GFP_KERNEL);
> + if (!ecwmi)
> + return -ENOMEM;
> +
> + ecwmi->ec = ec;
> + ecwmi->dev = dev;
> +
> + auxiliary_set_drvdata(adev, ecwmi);
> +
> + /* make it under /sys/devices/platform, convenient for sysfs I/O,
> + * while adev is under
> + * /sys/devices/platform/soc@0/ac0000.geniqup/a9c000.i2c/i2c-15/15-0038/
> + */
> + ecwmi->wmi = platform_device_register_simple("gaokun-wmi", -1, NULL, 0);
> + if (IS_ERR(ecwmi->wmi))
> + return dev_err_probe(dev, PTR_ERR(ecwmi->wmi),
> + "Failed to register wmi platform device\n");
> +
> + platform_set_drvdata(ecwmi->wmi, ecwmi);
> +
> + return device_add_groups(&ecwmi->wmi->dev, gaokun_wmi_features_groups);
> +}
> +
> +static void gaokun_wmi_remove(struct auxiliary_device *adev)
> +{
> + struct gaokun_wmi *ecwmi = auxiliary_get_drvdata(adev);
> + struct platform_device *wmi = ecwmi->wmi;
> +
> + device_remove_groups(&wmi->dev, gaokun_wmi_features_groups);
> + platform_device_unregister(ecwmi->wmi);
> +}
> +
> +static const struct auxiliary_device_id gaokun_wmi_id_table[] = {
> + { .name = GAOKUN_MOD_NAME "." GAOKUN_DEV_WMI, },
> + {}
> +};
> +MODULE_DEVICE_TABLE(auxiliary, gaokun_wmi_id_table);
> +
> +static struct auxiliary_driver gaokun_wmi_driver = {
> + .name = GAOKUN_DEV_WMI,
> + .id_table = gaokun_wmi_id_table,
> + .probe = gaokun_wmi_probe,
> + .remove = gaokun_wmi_remove,
> +};
> +
> +module_auxiliary_driver(gaokun_wmi_driver);
> +
> +MODULE_DESCRIPTION("HUAWEI Matebook E Go WMI driver");
> +MODULE_LICENSE("GPL");
> diff --git a/include/linux/platform_data/huawei-gaokun-ec.h b/include/linux/platform_data/huawei-gaokun-ec.h
> new file mode 100644
> index 000000000..a649e9ecf
> --- /dev/null
> +++ b/include/linux/platform_data/huawei-gaokun-ec.h
> @@ -0,0 +1,90 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/* Huawei Matebook E Go (sc8280xp) Embedded Controller
> + *
> + * Copyright (C) 2024 Pengyu Luo <mitltlatltl@gmail.com>
> + *
> + */
> +
> +#ifndef __HUAWEI_GAOKUN_EC_H__
> +#define __HUAWEI_GAOKUN_EC_H__
> +
> +#define GAOKUN_UCSI_CCI_SIZE 4
> +#define GAOKUN_UCSI_DATA_SIZE 16
> +#define GAOKUN_UCSI_READ_SIZE (GAOKUN_UCSI_CCI_SIZE + GAOKUN_UCSI_DATA_SIZE)
> +#define GAOKUN_UCSI_WRITE_SIZE 0x18
> +
> +#define GAOKUN_TZ_REG_NUM 20
> +#define GAOKUN_SMART_CHARGE_DATA_SIZE 4 /* mode, delay, start, end */
> +
> +/* -------------------------------------------------------------------------- */
> +
> +struct gaokun_ec;
> +struct notifier_block;
> +
> +#define GAOKUN_MOD_NAME "huawei_gaokun_ec"
> +#define GAOKUN_DEV_PSY "psy"
> +#define GAOKUN_DEV_WMI "wmi"
> +#define GAOKUN_DEV_UCSI "ucsi"
> +
> +/* -------------------------------------------------------------------------- */
> +/* Common API */
> +
> +int gaokun_ec_register_notify(struct gaokun_ec *ec,
> + struct notifier_block *nb);
> +void gaokun_ec_unregister_notify(struct gaokun_ec *ec,
> + struct notifier_block *nb);
> +
> +int gaokun_ec_read(struct gaokun_ec *ec, const u8 *req,
> + size_t resp_len, u8 *resp);
> +int gaokun_ec_write(struct gaokun_ec *ec, u8 *req);
> +int gaokun_ec_read_byte(struct gaokun_ec *ec, u8 *req, u8 *byte);
> +
> +/* -------------------------------------------------------------------------- */
> +/* API For PSY */
> +
> +int gaokun_ec_psy_multi_read(struct gaokun_ec *ec, u8 reg,
> + size_t resp_len, u8 *resp);
> +
> +static inline int gaokun_ec_psy_read_byte(struct gaokun_ec *ec,
> + u8 reg, u8 *byte)
> +{
> + return gaokun_ec_psy_multi_read(ec, reg, 1, byte);
> +}
> +
> +static inline int gaokun_ec_psy_read_word(struct gaokun_ec *ec,
> + u8 reg, u16 *word)
> +{
> + return gaokun_ec_psy_multi_read(ec, reg, 2, (u8 *)word);
> +}
> +
> +/* -------------------------------------------------------------------------- */
> +/* API For WMI */
> +
> +int gaokun_ec_wmi_get_threshold(struct gaokun_ec *ec, u8 *value, int ind);
> +int gaokun_ec_wmi_set_threshold(struct gaokun_ec *ec, u8 start, u8 end);
> +
> +int gaokun_ec_wmi_get_smart_charge_param(struct gaokun_ec *ec, u8 *value);
> +int gaokun_ec_wmi_set_smart_charge_param(struct gaokun_ec *ec, u8 value);
> +
> +int gaokun_ec_wmi_get_smart_charge(struct gaokun_ec *ec,
> + u8 data[GAOKUN_SMART_CHARGE_DATA_SIZE]);
> +int gaokun_ec_wmi_set_smart_charge(struct gaokun_ec *ec,
> + u8 data[GAOKUN_SMART_CHARGE_DATA_SIZE]);
> +
> +int gaokun_ec_wmi_get_fn_lock(struct gaokun_ec *ec, u8 *on);
> +int gaokun_ec_wmi_set_fn_lock(struct gaokun_ec *ec, u8 on);
> +
> +int gaokun_ec_wmi_get_temp(struct gaokun_ec *ec, s16 temp[GAOKUN_TZ_REG_NUM]);
> +
> +/* -------------------------------------------------------------------------- */
> +/* API For UCSI */
> +
> +int gaokun_ec_ucsi_read(struct gaokun_ec *ec, u8 resp[GAOKUN_UCSI_READ_SIZE]);
> +int gaokun_ec_ucsi_write(struct gaokun_ec *ec,
> + const u8 req[GAOKUN_UCSI_WRITE_SIZE]);
> +
> +int gaokun_ec_ucsi_get_reg(struct gaokun_ec *ec, u8 *ureg);
> +int gaokun_ec_ucsi_pan_ack(struct gaokun_ec *ec, int port_id);
> +
> +
> +#endif /* __HUAWEI_GAOKUN_EC_H__ */
--
Thx and BRs,
Aiqun(Maria) Yu
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 1/5] dt-bindings: platform: Add Huawei Matebook E Go EC
2024-12-30 7:35 ` Krzysztof Kozlowski
@ 2024-12-30 9:10 ` Aiqun(Maria) Yu
0 siblings, 0 replies; 51+ messages in thread
From: Aiqun(Maria) Yu @ 2024-12-30 9:10 UTC (permalink / raw)
To: Krzysztof Kozlowski, Pengyu Luo
Cc: andersson, bryan.odonoghue, conor+dt, devicetree,
dmitry.baryshkov, gregkh, hdegoede, heikki.krogerus,
ilpo.jarvinen, konradybcio, krzk+dt, linux-arm-msm, linux-kernel,
linux-pm, linux-usb, nikita, platform-driver-x86, robh, sre
On 12/30/2024 3:35 PM, Krzysztof Kozlowski wrote:
> On 30/12/2024 08:28, Aiqun(Maria) Yu wrote:
>>>>
>> [...]
>>>>
>>>
>>> Check the motherboard, https://postimg.cc/V5r4KCgx (Credit to Tianyu Gao <gty0622@gmail.com>)
>>
>> The link is not accessible from my end. Could you please help follow the
>
> Link is accessible. Maybe you are using corporate network with some
> firewalls/content filtering?
It's highly likely that my corporate network has blocked this.
--
Thx and BRs,
Aiqun(Maria) Yu
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 2/5] platform: arm64: add Huawei Matebook E Go (sc8280xp) EC driver
2024-12-30 9:04 ` Aiqun(Maria) Yu
@ 2024-12-30 10:44 ` Pengyu Luo
2024-12-31 5:00 ` Aiqun(Maria) Yu
0 siblings, 1 reply; 51+ messages in thread
From: Pengyu Luo @ 2024-12-30 10:44 UTC (permalink / raw)
To: quic_aiquny
Cc: andersson, bryan.odonoghue, conor+dt, devicetree,
dmitry.baryshkov, gregkh, hdegoede, heikki.krogerus,
ilpo.jarvinen, konradybcio, krzk+dt, linux-arm-msm, linux-kernel,
linux-pm, linux-usb, mitltlatltl, nikita, platform-driver-x86,
robh, sre
On Mon, Dec 30, 2024 at 5:04 PM Aiqun(Maria) Yu <quic_aiquny@quicinc.com> wrote:
> On 12/28/2024 1:13 AM, Pengyu Luo wrote:
> > There are 3 variants, Huawei released first 2 at the same time.
> > Huawei Matebook E Go LTE(sc8180x), codename should be gaokun2.
> > Huawei Matebook E Go(sc8280xp@3.0GHz), codename is gaokun3.
> > Huawei Matebook E Go 2023(sc8280xp@2.69GHz).
[...]
> > +#include <linux/mutex.h>
> > +#include <linux/version.h>
> > +
> > +#include <linux/platform_data/huawei-gaokun-ec.h>
> > +
> > +#define EC_EVENT 0x06
> > +
> > +/* Also can be found in ACPI specification 12.3 */
>
> It appears that the following EC commands are common to all ACPI-applied
> embedded controllers. Is it possible to standardize these commands and API?
>
No, I mentioned a little in kerneldoc, EC_READ only works for psy
related things.
> > +#define EC_READ 0x80
> > +#define EC_WRITE 0x81
> > +#define EC_BURST 0x82
> > +#define EC_QUERY 0x84
> > +
> > +
> > +#define EC_EVENT_LID 0x81
> > +
> > +#define EC_LID_STATE 0x80
> > +#define EC_LID_OPEN BIT(1)
> > +
> > +#define UCSI_REG_SIZE 7
> > +
> > +/* for tx, command sequences are arranged as
> > + * {master_cmd, slave_cmd, data_len, data_seq}
> > + */
> > +#define REQ_HDR_SIZE 3
> > +#define INPUT_SIZE_OFFSET 2
> > +#define INPUT_DATA_OFFSET 3
> > +
> > +/* for rx, data sequences are arranged as
> > + * {status, data_len(unreliable), data_seq}
> > + */
> > +#define RESP_HDR_SIZE 2
> > +#define DATA_OFFSET 2
> > +
> > +
> > +struct gaokun_ec {
> > + struct i2c_client *client;
> > + struct mutex lock;
> > + struct blocking_notifier_head notifier_list;
> > + struct input_dev *idev;
> > + bool suspended;
> > +};
> > +
> > +static int gaokun_ec_request(struct gaokun_ec *ec, const u8 *req,
> > + size_t resp_len, u8 *resp)
> > +{
> > + struct i2c_client *client = ec->client;
> > + struct i2c_msg msgs[2] = {
> > + {
> > + .addr = client->addr,
> > + .flags = client->flags,
> > + .len = req[INPUT_SIZE_OFFSET] + REQ_HDR_SIZE,
> > + .buf = req,
> > + }, {
> > + .addr = client->addr,
> > + .flags = client->flags | I2C_M_RD,
> > + .len = resp_len,
> > + .buf = resp,
> > + },
> > + };
> > +
> > + mutex_lock(&ec->lock);
> > +
> > + i2c_transfer(client->adapter, msgs, 2);
>
> ARRAY_SIZE(msgs) is suggested instead of pure 2.
>
Agree
> > + usleep_range(2000, 2500);
>
> Why is a sleep needed here? Is this information specified in any datasheet?
>
Have a break between 2 transaction. This sleep happens in acpi code, also
inside a critical region. I rearranged it.
Local7 = Acquire (\_SB.IC16.MUEC, 0x03E8)
...
write ops
...
Sleep (0x02)
...
read ops
...
Release (\_SB.IC16.MUEC)
> > +
> > + mutex_unlock(&ec->lock);
> > +
> > + return *resp;
> > +}
> > +
> > +/* -------------------------------------------------------------------------- */
> > +/* Common API */
> > +
> > +/**
> > + * gaokun_ec_read - read from EC
> > + * @ec: The gaokun_ec
> > + * @req: The sequence to request
> > + * @resp_len: The size to read
> > + * @resp: Where the data are read to
> > + *
> > + * This function is used to read data after writing a magic sequence to EC.
> > + * All EC operations dependent on this functions.
> > + *
> > + * Huawei uses magic sequences everywhere to complete various functions, all
> > + * these sequences are passed to ECCD(a ACPI method which is quiet similar
> > + * to gaokun_ec_request), there is no good abstraction to generalize these
> > + * sequences, so just wrap it for now. Almost all magic sequences are kept
> > + * in this file.
> > + */
> > +int gaokun_ec_read(struct gaokun_ec *ec, const u8 *req,
> > + size_t resp_len, u8 *resp)
> > +{
> > + return gaokun_ec_request(ec, req, resp_len, resp);
> > +}
> > +EXPORT_SYMBOL_GPL(gaokun_ec_read);
> > +
> > +/**
> > + * gaokun_ec_write - write to EC
> > + * @ec: The gaokun_ec
> > + * @req: The sequence to request
> > + *
> > + * This function has no big difference from gaokun_ec_read. When caller care
> > + * only write status and no actual data are returnd, then use it.
> > + */
> > +int gaokun_ec_write(struct gaokun_ec *ec, u8 *req)
> > +{
> > + u8 resp[RESP_HDR_SIZE];
> > +
> > + return gaokun_ec_request(ec, req, sizeof(resp), resp);
> > +}
> > +EXPORT_SYMBOL_GPL(gaokun_ec_write);
> > +
> > +int gaokun_ec_read_byte(struct gaokun_ec *ec, u8 *req, u8 *byte)
> > +{
> > + int ret;
> > + u8 resp[RESP_HDR_SIZE + sizeof(*byte)];
> > +
> > + ret = gaokun_ec_read(ec, req, sizeof(resp), resp);
> > + *byte = resp[DATA_OFFSET];
> > +
> > + return ret;
> > +}
> > +EXPORT_SYMBOL_GPL(gaokun_ec_read_byte);
> > +
> > +int gaokun_ec_register_notify(struct gaokun_ec *ec, struct notifier_block *nb)
> > +{
> > + return blocking_notifier_chain_register(&ec->notifier_list, nb);
> > +}
> > +EXPORT_SYMBOL_GPL(gaokun_ec_register_notify);
> > +
> > +void gaokun_ec_unregister_notify(struct gaokun_ec *ec, struct notifier_block *nb)
> > +{
> > + blocking_notifier_chain_unregister(&ec->notifier_list, nb);
> > +}
> > +EXPORT_SYMBOL_GPL(gaokun_ec_unregister_notify);
> > +
> > +/* -------------------------------------------------------------------------- */
> > +/* API For PSY */
> > +
> > +int gaokun_ec_psy_multi_read(struct gaokun_ec *ec, u8 reg,
> > + size_t resp_len, u8 *resp)
> > +{
> > + int i, ret;
> > + u8 _resp[RESP_HDR_SIZE + 1];
> > + u8 req[REQ_HDR_SIZE + 1] = {0x02, EC_READ, 1, };
>
> Could it be made more readable by specifying the macro names for 0x02
> and 1? This would help in understanding the meaning of these numbers.
>
I really don't know the meaning of master command 0x02, 1 is the size for
the data_seq behind of it. There are many possible sizes. It is not a good
idea to define a macro name for everyone.
> Also, please ensure the actual size of the request buffer is handled
> properly. In gaokun_ec_request(), the req is passed down directly, and
> the i2c_msg.len is used dynamically with req[INPUT_SIZE_OFFSET] +
> REQ_HDR_SIZE. This requires the caller to carefully manage the contents
> to avoid memory over-read, making the code difficult to read.
>
> Creating a defined macro can help you avoid manually defining the size.
> For example:
> #define REQ(size, data_0, data_1, args...) \
> u8 req[REQ_HDR_SIZE + size] = {data_0, data_1, size, args};
>
I think wrapping like this is not recommended, see '5)' in [1]
Best wishes,
Pengyu
[1] https://www.kernel.org/doc/html/v4.10/process/coding-style.html#macros-enums-and-rtl
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 5/5] arm64: dts: qcom: gaokun3: Add Embedded Controller node
2024-12-27 17:13 ` [PATCH 5/5] arm64: dts: qcom: gaokun3: Add Embedded Controller node Pengyu Luo
@ 2024-12-30 14:53 ` Konrad Dybcio
2024-12-30 16:22 ` Pengyu Luo
0 siblings, 1 reply; 51+ messages in thread
From: Konrad Dybcio @ 2024-12-30 14:53 UTC (permalink / raw)
To: Pengyu Luo, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Bjorn Andersson, Konrad Dybcio, Hans de Goede, Ilpo Järvinen,
Bryan O'Donoghue, Sebastian Reichel, Heikki Krogerus,
Greg Kroah-Hartman
Cc: devicetree, linux-kernel, linux-arm-msm, platform-driver-x86,
linux-pm, linux-usb, Dmitry Baryshkov, Nikita Travkin
On 27.12.2024 6:13 PM, Pengyu Luo wrote:
> The Embedded Controller in the Huawei Matebook E Go (s8280xp)
> is accessible on &i2c15 and provides battery and adapter status,
> port orientation status, as well as HPD event notifications for
> two USB Type-C port, etc.
>
> Add the EC to the device tree and describe the relationship among
> the type-c ports, orientation switches and the QMP combo PHY.
>
> Signed-off-by: Pengyu Luo <mitltlatltl@gmail.com>
> ---
> .../boot/dts/qcom/sc8280xp-huawei-gaokun3.dts | 139 ++++++++++++++++++
> 1 file changed, 139 insertions(+)
>
> diff --git a/arch/arm64/boot/dts/qcom/sc8280xp-huawei-gaokun3.dts b/arch/arm64/boot/dts/qcom/sc8280xp-huawei-gaokun3.dts
> index 09b95f89e..09ca9a560 100644
> --- a/arch/arm64/boot/dts/qcom/sc8280xp-huawei-gaokun3.dts
> +++ b/arch/arm64/boot/dts/qcom/sc8280xp-huawei-gaokun3.dts
> @@ -28,6 +28,7 @@ / {
>
> aliases {
> i2c4 = &i2c4;
> + i2c15 = &i2c15;
> serial1 = &uart2;
> };
>
> @@ -216,6 +217,40 @@ map1 {
> };
> };
>
> + usb0-sbu-mux {
> + compatible = "pericom,pi3usb102", "gpio-sbu-mux";
> +
> + select-gpios = <&tlmm 164 GPIO_ACTIVE_HIGH>;
> +
> + pinctrl-names = "default";
> + pinctrl-0 = <&usb0_sbu_default>;
Please preserve this order:
property-n
property-names
> +
> + orientation-switch;
This
> +
> + port {
> + usb0_sbu_mux: endpoint {
> + remote-endpoint = <&ucsi0_sbu>;
And this section have incorrect whitespacing (one tab too many, make
sure you set your tab width to 8 spaces)
Same for usb1-sbu-mux
[...]
> + i2c15_default: i2c15-default-state {
> + pins = "gpio36", "gpio37";
> + function = "qup15";
> + drive-strength = <2>;
> + bias-pull-up;
> + };
> +
> mode_pin_active: mode-pin-state {
> pins = "gpio26";
> function = "gpio";
> @@ -1301,6 +1426,20 @@ tx-pins {
> };
> };
>
> + usb0_sbu_default: usb0-sbu-state {
> + pins = "gpio164";
> + function = "gpio";
> + bias-disable;
> + drive-strength = <16>;
> + };
> +
> + usb1_sbu_default: usb1-sbu-state {
> + pins = "gpio47";
> + function = "gpio";
> + bias-disable;
> + drive-strength = <16>;
> + };
Similarly, please keep drive-strength above bias for consistency
lgtm otherwise
Konrad
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 5/5] arm64: dts: qcom: gaokun3: Add Embedded Controller node
2024-12-30 14:53 ` Konrad Dybcio
@ 2024-12-30 16:22 ` Pengyu Luo
0 siblings, 0 replies; 51+ messages in thread
From: Pengyu Luo @ 2024-12-30 16:22 UTC (permalink / raw)
To: konrad.dybcio
Cc: andersson, bryan.odonoghue, conor+dt, devicetree,
dmitry.baryshkov, gregkh, hdegoede, heikki.krogerus,
ilpo.jarvinen, konradybcio, krzk+dt, linux-arm-msm, linux-kernel,
linux-pm, linux-usb, mitltlatltl, nikita, platform-driver-x86,
robh, sre
On Mon, Dec 30, 2024 at 10:53 PM Konrad Dybcio <konrad.dybcio@oss.qualcomm.com> wrote:
> On 27.12.2024 6:13 PM, Pengyu Luo wrote:
> > The Embedded Controller in the Huawei Matebook E Go (s8280xp)
> > is accessible on &i2c15 and provides battery and adapter status,
> > port orientation status, as well as HPD event notifications for
> > two USB Type-C port, etc.
> >
> > Add the EC to the device tree and describe the relationship among
> > the type-c ports, orientation switches and the QMP combo PHY.
> >
> > Signed-off-by: Pengyu Luo <mitltlatltl@gmail.com>
> > ---
> > .../boot/dts/qcom/sc8280xp-huawei-gaokun3.dts | 139 ++++++++++++++++++
> > 1 file changed, 139 insertions(+)
> >
> > diff --git a/arch/arm64/boot/dts/qcom/sc8280xp-huawei-gaokun3.dts b/arch/arm64/boot/dts/qcom/sc8280xp-huawei-gaokun3.dts
> > index 09b95f89e..09ca9a560 100644
> > --- a/arch/arm64/boot/dts/qcom/sc8280xp-huawei-gaokun3.dts
> > +++ b/arch/arm64/boot/dts/qcom/sc8280xp-huawei-gaokun3.dts
> > @@ -28,6 +28,7 @@ / {
> >
> > aliases {
> > i2c4 = &i2c4;
> > + i2c15 = &i2c15;
> > serial1 = &uart2;
> > };
> >
> > @@ -216,6 +217,40 @@ map1 {
> > };
> > };
> >
> > + usb0-sbu-mux {
> > + compatible = "pericom,pi3usb102", "gpio-sbu-mux";
> > +
> > + select-gpios = <&tlmm 164 GPIO_ACTIVE_HIGH>;
> > +
> > + pinctrl-names = "default";
> > + pinctrl-0 = <&usb0_sbu_default>;
>
> Please preserve this order:
>
> property-n
> property-names
>
> > +
> > + orientation-switch;
>
> This
>
> > +
> > + port {
> > + usb0_sbu_mux: endpoint {
> > + remote-endpoint = <&ucsi0_sbu>;
>
> And this section have incorrect whitespacing (one tab too many, make
> sure you set your tab width to 8 spaces)
>
> Same for usb1-sbu-mux
>
> [...]
>
> > + i2c15_default: i2c15-default-state {
> > + pins = "gpio36", "gpio37";
> > + function = "qup15";
> > + drive-strength = <2>;
> > + bias-pull-up;
> > + };
> > +
> > mode_pin_active: mode-pin-state {
> > pins = "gpio26";
> > function = "gpio";
> > @@ -1301,6 +1426,20 @@ tx-pins {
> > };
> > };
> >
> > + usb0_sbu_default: usb0-sbu-state {
> > + pins = "gpio164";
> > + function = "gpio";
> > + bias-disable;
> > + drive-strength = <16>;
> > + };
> > +
> > + usb1_sbu_default: usb1-sbu-state {
> > + pins = "gpio47";
> > + function = "gpio";
> > + bias-disable;
> > + drive-strength = <16>;
> > + };
>
> Similarly, please keep drive-strength above bias for consistency
>
> lgtm otherwise
>
Totaly agree, I was in a hurry, I will fix it in v2.
Best wishes,
Pengyu
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 2/5] platform: arm64: add Huawei Matebook E Go (sc8280xp) EC driver
2024-12-30 10:44 ` Pengyu Luo
@ 2024-12-31 5:00 ` Aiqun(Maria) Yu
2024-12-31 7:44 ` Pengyu Luo
2025-01-01 11:27 ` Pengyu Luo
0 siblings, 2 replies; 51+ messages in thread
From: Aiqun(Maria) Yu @ 2024-12-31 5:00 UTC (permalink / raw)
To: Pengyu Luo
Cc: andersson, bryan.odonoghue, conor+dt, devicetree,
dmitry.baryshkov, gregkh, hdegoede, heikki.krogerus,
ilpo.jarvinen, konradybcio, krzk+dt, linux-arm-msm, linux-kernel,
linux-pm, linux-usb, nikita, platform-driver-x86, robh, sre
On 12/30/2024 6:44 PM, Pengyu Luo wrote:
> On Mon, Dec 30, 2024 at 5:04 PM Aiqun(Maria) Yu <quic_aiquny@quicinc.com> wrote:
>> On 12/28/2024 1:13 AM, Pengyu Luo wrote:
[...]
>>> + i2c_transfer(client->adapter, msgs, 2);
>>
>> ARRAY_SIZE(msgs) is suggested instead of pure 2.
>>
>
> Agree
>
>>> + usleep_range(2000, 2500);
>>
>> Why is a sleep needed here? Is this information specified in any datasheet?
>>
>
> Have a break between 2 transaction. This sleep happens in acpi code, also
> inside a critical region. I rearranged it.
>
> Local7 = Acquire (\_SB.IC16.MUEC, 0x03E8)
> ...
> write ops
> ...
> Sleep (0x02)
> ...
> read ops
> ...
> Release (\_SB.IC16.MUEC)
Could you please share the exact code snippet that is being referenced?
I'm a bit confused because it doesn't seem to align with the current
logic, which doesn't have read operations within the same mutex lock. I
also want to understand the background and necessity of the sleep function.
>
>>> +
>>> + mutex_unlock(&ec->lock);
>>> +
>>> + return *resp;
>>> +}
>>> +
>>> +/* -------------------------------------------------------------------------- */
>>> +/* Common API */
[...]
>>> + int i, ret;
>>> + u8 _resp[RESP_HDR_SIZE + 1];
>>> + u8 req[REQ_HDR_SIZE + 1] = {0x02, EC_READ, 1, };
>>
>> Could it be made more readable by specifying the macro names for 0x02
>> and 1? This would help in understanding the meaning of these numbers.
>>
>
> I really don't know the meaning of master command 0x02, 1 is the size for
> the data_seq behind of it. There are many possible sizes. It is not a good
> idea to define a macro name for everyone.
>
Perhaps you didn't get the "arg..." magic here. A single definition is
sufficient for all sizes.
>> Also, please ensure the actual size of the request buffer is handled
>> properly. In gaokun_ec_request(), the req is passed down directly, and
>> the i2c_msg.len is used dynamically with req[INPUT_SIZE_OFFSET] +
>> REQ_HDR_SIZE. This requires the caller to carefully manage the contents
>> to avoid memory over-read, making the code difficult to read.
>>
>> Creating a defined macro can help you avoid manually defining the size.
>> For example:
>> #define REQ(size, data_0, data_1, args...) \
>> u8 req[REQ_HDR_SIZE + size] = {data_0, data_1, size, args};
>>
>
> I think wrapping like this is not recommended, see '5)' in [1]
>
> Best wishes,
> Pengyu
>
> [1] https://www.kernel.org/doc/html/v4.10/process/coding-style.html#macros-enums-and-rtl
I believe that the consideration of namespace collisions is a valid concern.
Some examples can be like have a naming pattern as well:
/*To have a name pattern to reflect the size like reg0/reg1/reg2*/
#define REQ(variable_name, size, data_0, data_1, args...) \
u8 ##variable_name[REQ_HDR_SIZE + size] = {data_0, data_1, size, args};
/*u8 req1[REQ_HDR_SIZE + 1] = {0x02, EC_READ, 1, };*/
REQ(req, 1, 0x02, EC_READ);
/*u8 req2[REQ_HDR_SIZE + 2] = {0x02, 0x68, 2, 3, 0x5a}; */
REQ(req, 2, 0x02, 0x68, 3, 0x5a);
Please note that this is just an example and a suggestion to avoid the
current manual variable pattern setting. The final decision still
requires the current maintainers' agreement.
--
Thx and BRs,
Aiqun(Maria) Yu
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 2/5] platform: arm64: add Huawei Matebook E Go (sc8280xp) EC driver
2024-12-31 5:00 ` Aiqun(Maria) Yu
@ 2024-12-31 7:44 ` Pengyu Luo
2024-12-31 11:09 ` Bryan O'Donoghue
2025-01-03 5:38 ` Dmitry Baryshkov
2025-01-01 11:27 ` Pengyu Luo
1 sibling, 2 replies; 51+ messages in thread
From: Pengyu Luo @ 2024-12-31 7:44 UTC (permalink / raw)
To: quic_aiquny
Cc: andersson, bryan.odonoghue, conor+dt, devicetree,
dmitry.baryshkov, gregkh, hdegoede, heikki.krogerus,
ilpo.jarvinen, konradybcio, krzk+dt, linux-arm-msm, linux-kernel,
linux-pm, linux-usb, mitltlatltl, nikita, platform-driver-x86,
robh, sre
On Tue, Dec 31, 2024 at 1:00 PM Aiqun(Maria) Yu <quic_aiquny@quicinc.com> wrote:
> On 12/30/2024 6:44 PM, Pengyu Luo wrote:
> > On Mon, Dec 30, 2024 at 5:04 PM Aiqun(Maria) Yu <quic_aiquny@quicinc.com> wrote:
> >> On 12/28/2024 1:13 AM, Pengyu Luo wrote:
> [...]
> >>> + i2c_transfer(client->adapter, msgs, 2);
> >>
> >> ARRAY_SIZE(msgs) is suggested instead of pure 2.
> >>
> >
> > Agree
> >
> >>> + usleep_range(2000, 2500);
> >>
> >> Why is a sleep needed here? Is this information specified in any datasheet?
> >>
> >
> > Have a break between 2 transaction. This sleep happens in acpi code, also
> > inside a critical region. I rearranged it.
> >
> > Local7 = Acquire (\_SB.IC16.MUEC, 0x03E8)
> > ...
> > write ops
> > ...
> > Sleep (0x02)
> > ...
> > read ops
> > ...
> > Release (\_SB.IC16.MUEC)
>
> Could you please share the exact code snippet that is being referenced?
> I'm a bit confused because it doesn't seem to align with the current
> logic, which doesn't have read operations within the same mutex lock. I
> also want to understand the background and necessity of the sleep function.
>
I mentioned I rearranged it to optimize it. In a EC transaction,
write sleep read => write read sleep, in this way, we sleep once a
transaction.
Please search
'device name + acpi table' on the internet, someone dumped it and uploaded
it, in SSDT, check ECCD. I am not sure if huawei allows users to dump it.
So I don't provide it here.
> >
> >>> +
> >>> + mutex_unlock(&ec->lock);
> >>> +
> >>> + return *resp;
> >>> +}
> >>> +
> >>> +/* -------------------------------------------------------------------------- */
> >>> +/* Common API */
> [...]
> >>> + int i, ret;
> >>> + u8 _resp[RESP_HDR_SIZE + 1];
> >>> + u8 req[REQ_HDR_SIZE + 1] = {0x02, EC_READ, 1, };
> >>
> >> Could it be made more readable by specifying the macro names for 0x02
> >> and 1? This would help in understanding the meaning of these numbers.
> >>
> >
> > I really don't know the meaning of master command 0x02, 1 is the size for
> > the data_seq behind of it. There are many possible sizes. It is not a good
> > idea to define a macro name for everyone.
> >
>
> Perhaps you didn't get the "arg..." magic here. A single definition is
> sufficient for all sizes.
>
You were talking using a macro to inline the varadic magic sequences, I was
talking defining macro for every constant number. If so, I got you now.
> >> Also, please ensure the actual size of the request buffer is handled
> >> properly. In gaokun_ec_request(), the req is passed down directly, and
> >> the i2c_msg.len is used dynamically with req[INPUT_SIZE_OFFSET] +
> >> REQ_HDR_SIZE. This requires the caller to carefully manage the contents
> >> to avoid memory over-read, making the code difficult to read.
> >>
> >> Creating a defined macro can help you avoid manually defining the size.
> >> For example:
> >> #define REQ(size, data_0, data_1, args...) \
> >> u8 req[REQ_HDR_SIZE + size] = {data_0, data_1, size, args};
> >>
> >
> > I think wrapping like this is not recommended, see '5)' in [1]
> >
> > Best wishes,
> > Pengyu
> >
> > [1] https://www.kernel.org/doc/html/v4.10/process/coding-style.html#macros-enums-and-rtl
>
> I believe that the consideration of namespace collisions is a valid concern.
>
> Some examples can be like have a naming pattern as well:
> /*To have a name pattern to reflect the size like reg0/reg1/reg2*/
> #define REQ(variable_name, size, data_0, data_1, args...) \
> u8 ##variable_name[REQ_HDR_SIZE + size] = {data_0, data_1, size, args};
>
> /*u8 req1[REQ_HDR_SIZE + 1] = {0x02, EC_READ, 1, };*/
> REQ(req, 1, 0x02, EC_READ);
>
> /*u8 req2[REQ_HDR_SIZE + 2] = {0x02, 0x68, 2, 3, 0x5a}; */
> REQ(req, 2, 0x02, 0x68, 3, 0x5a);
>
> Please note that this is just an example and a suggestion to avoid the
> current manual variable pattern setting. The final decision still
> requires the current maintainers' agreement.
>
The main point I am against is hiding the data type, in some functions,
later we assign req[some_offset] = val; That makes things really weird.
I prefer to define all magic sequences, like
#define MAGIC_SEQ_1 {0x02, EC_READ, 1, 0} /* padding with 0 */
#define MAGIC_SEQ_2 {0x02, 0x68, 2, 3, 0x5a}
Gathering them makes things easy to manage, but I doubt if any source
file in Linux kernel doing it like this.
Another one alternative,
#define INLINE(REG0, REG1, SIZE) \
{ REG0, REG1, SIZE, [3 ... 2 + SIZE] = 0} /* GCC extension */
/* or just */
{ REG0, REG1, SIZE, [2 + SIZE] = 0}
u8 req[] = INLINE(0x02, 0x68, 2); /* Creating it */
/* not sure if we can make tricks to initial this in macro */
initial(req, 3, 0x5a); /* initial it */
Best wishes,
Pengyu
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 2/5] platform: arm64: add Huawei Matebook E Go (sc8280xp) EC driver
2024-12-31 7:44 ` Pengyu Luo
@ 2024-12-31 11:09 ` Bryan O'Donoghue
2025-01-03 5:38 ` Dmitry Baryshkov
1 sibling, 0 replies; 51+ messages in thread
From: Bryan O'Donoghue @ 2024-12-31 11:09 UTC (permalink / raw)
To: Pengyu Luo, quic_aiquny
Cc: andersson, conor+dt, devicetree, dmitry.baryshkov, gregkh,
hdegoede, heikki.krogerus, ilpo.jarvinen, konradybcio, krzk+dt,
linux-arm-msm, linux-kernel, linux-pm, linux-usb, nikita,
platform-driver-x86, robh, sre
On 31/12/2024 07:44, Pengyu Luo wrote:
> Please search
> 'device name + acpi table' on the internet, someone dumped it and uploaded
> it, in SSDT, check ECCD. I am not sure if huawei allows users to dump it.
> So I don't provide it here.
There's a repository of ACPI dumps here:
https://github.com/aarch64-laptops/build/tree/master/misc
including the Huawei Matebook E - not sure if that should include the
"Matebook E Go"
https://github.com/aarch64-laptops/build/tree/master/misc/huawei-matebooke-2019
You could provide it there.
---
bod
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 1/5] dt-bindings: platform: Add Huawei Matebook E Go EC
2024-12-30 7:28 ` Aiqun(Maria) Yu
2024-12-30 7:35 ` Krzysztof Kozlowski
2024-12-30 8:00 ` Pengyu Luo
@ 2025-01-01 5:57 ` Pengyu Luo
2 siblings, 0 replies; 51+ messages in thread
From: Pengyu Luo @ 2025-01-01 5:57 UTC (permalink / raw)
To: quic_aiquny
Cc: andersson, bryan.odonoghue, conor+dt, devicetree,
dmitry.baryshkov, gregkh, hdegoede, heikki.krogerus,
ilpo.jarvinen, konradybcio, krzk+dt, krzk, linux-arm-msm,
linux-kernel, linux-pm, linux-usb, mitltlatltl, nikita,
platform-driver-x86, robh, sre
On Mon, Dec 30, 2024 at 3:28 PM Aiqun(Maria) Yu <quic_aiquny@quicinc.com> wrote:
> On 12/29/2024 6:12 PM, Pengyu Luo wrote:
>
[...]
> >>
> >
> > Check the motherboard, https://postimg.cc/V5r4KCgx (Credit to Tianyu Gao <gty0622@gmail.com>)
>
> The link is not accessible from my end. Could you please help follow the
> document tips referenced by [1] if this content is important for the
> overall naming design?
>
> Here are some snippets for reference:
> "for 'volatile' documents, please create an entry in the kernel
> bugzilla https://bugzilla.kernel.org and attach a copy of these documents
> to the bugzilla entry. Finally, provide the URL of the bugzilla entry in
> the changelog."
>
> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/process/maintainer-tip.rst
> [1]
I created one entry, then I got myself and the entry banned, :(
I had written to them to explain this yesterday. No response.
Title 'Huawei Matebook E Go, whose codename is Gaokun',
In this entry, I explained why is it called gaokun, and why gen3.
> https://bugzilla.kernel.org/show_bug.cgi?id=219645
>
> Artem S. Tashkinov (aros@gmx.com) changed:
>
> What |Removed |Added
> ----------------------------------------------------------------------------
> Status|NEW |RESOLVED
> Group| |Junk
> Component|man-pages |Spam
> Version|unspecified |2.5
> Resolution|--- |INVALID
> Assignee|documentation_man-pages@ker |other_spam@kernel-bugs.kern
> |nel-bugs.osdl.org |el.org
> Product|Documentation |Other
Best wishes,
Pengyu
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 2/5] platform: arm64: add Huawei Matebook E Go (sc8280xp) EC driver
2024-12-31 5:00 ` Aiqun(Maria) Yu
2024-12-31 7:44 ` Pengyu Luo
@ 2025-01-01 11:27 ` Pengyu Luo
1 sibling, 0 replies; 51+ messages in thread
From: Pengyu Luo @ 2025-01-01 11:27 UTC (permalink / raw)
To: quic_aiquny, bryan.odonoghue
Cc: andersson, conor+dt, devicetree, dmitry.baryshkov, gregkh,
hdegoede, heikki.krogerus, ilpo.jarvinen, konradybcio, krzk+dt,
linux-arm-msm, linux-kernel, linux-pm, linux-usb, mitltlatltl,
nikita, platform-driver-x86, robh, sre
On Tue, Dec 31, 2024 at 1:00 PM Aiqun(Maria) Yu <quic_aiquny@quicinc.com> wrote:
> On 12/30/2024 6:44 PM, Pengyu Luo wrote:
> > On Mon, Dec 30, 2024 at 5:04 PM Aiqun(Maria) Yu <quic_aiquny@quicinc.com> wrote:
> >> On 12/28/2024 1:13 AM, Pengyu Luo wrote:
> [...]
> >>> + i2c_transfer(client->adapter, msgs, 2);
> >>
> >> ARRAY_SIZE(msgs) is suggested instead of pure 2.
> >>
> >
> > Agree
> >
> >>> + usleep_range(2000, 2500);
> >>
> >> Why is a sleep needed here? Is this information specified in any datasheet?
> >>
> >
> > Have a break between 2 transaction. This sleep happens in acpi code, also
> > inside a critical region. I rearranged it.
> >
> > Local7 = Acquire (\_SB.IC16.MUEC, 0x03E8)
> > ...
> > write ops
> > ...
> > Sleep (0x02)
> > ...
> > read ops
> > ...
> > Release (\_SB.IC16.MUEC)
>
> Could you please share the exact code snippet that is being referenced?
> I'm a bit confused because it doesn't seem to align with the current
> logic, which doesn't have read operations within the same mutex lock. I
> also want to understand the background and necessity of the sleep function.
>
> >
> >>> +
> >>> + mutex_unlock(&ec->lock);
> >>> +
> >>> + return *resp;
> >>> +}
> >>> +
> >>> +/* -------------------------------------------------------------------------- */
> >>> +/* Common API */
> [...]
> >>> + int i, ret;
> >>> + u8 _resp[RESP_HDR_SIZE + 1];
> >>> + u8 req[REQ_HDR_SIZE + 1] = {0x02, EC_READ, 1, };
> >>
> >> Could it be made more readable by specifying the macro names for 0x02
> >> and 1? This would help in understanding the meaning of these numbers.
> >>
> >
> > I really don't know the meaning of master command 0x02, 1 is the size for
> > the data_seq behind of it. There are many possible sizes. It is not a good
> > idea to define a macro name for everyone.
> >
>
> Perhaps you didn't get the "arg..." magic here. A single definition is
> sufficient for all sizes.
>
> >> Also, please ensure the actual size of the request buffer is handled
> >> properly. In gaokun_ec_request(), the req is passed down directly, and
> >> the i2c_msg.len is used dynamically with req[INPUT_SIZE_OFFSET] +
> >> REQ_HDR_SIZE. This requires the caller to carefully manage the contents
> >> to avoid memory over-read, making the code difficult to read.
> >>
> >> Creating a defined macro can help you avoid manually defining the size.
> >> For example:
> >> #define REQ(size, data_0, data_1, args...) \
> >> u8 req[REQ_HDR_SIZE + size] = {data_0, data_1, size, args};
> >>
> >
> > I think wrapping like this is not recommended, see '5)' in [1]
> >
> > Best wishes,
> > Pengyu
> >
> > [1] https://www.kernel.org/doc/html/v4.10/process/coding-style.html#macros-enums-and-rtl
>
> I believe that the consideration of namespace collisions is a valid concern.
>
> Some examples can be like have a naming pattern as well:
> /*To have a name pattern to reflect the size like reg0/reg1/reg2*/
> #define REQ(variable_name, size, data_0, data_1, args...) \
> u8 ##variable_name[REQ_HDR_SIZE + size] = {data_0, data_1, size, args};
>
> /*u8 req1[REQ_HDR_SIZE + 1] = {0x02, EC_READ, 1, };*/
> REQ(req, 1, 0x02, EC_READ);
>
> /*u8 req2[REQ_HDR_SIZE + 2] = {0x02, 0x68, 2, 3, 0x5a}; */
> REQ(req, 2, 0x02, 0x68, 3, 0x5a);
>
> Please note that this is just an example and a suggestion to avoid the
> current manual variable pattern setting. The final decision still
> requires the current maintainers' agreement.
>
I am gonna do this, Aiqun(Maria), Bryan, any suggestions?
/*
* for tx, command sequences are arranged as
* {master_cmd, slave_cmd, data_len, data_seq}
*/
#define REQ_HDR_SIZE 3
#define INPUT_SIZE_OFFSET 2
/*
* for rx, data sequences are arranged as
* {status, data_len(unreliable), data_seq}
*/
#define RESP_HDR_SIZE 2
#define MKREQ(REG0, REG1, SIZE, ...) \
{ \
/* ## will remove comma when no __VA_ARGS__ */ \
REG0, REG1, SIZE, ## __VA_ARGS__, \
/* make sure len(pkt[3:]) >= SIZE */ \
[3 + SIZE] = 0, \
}
#define MKRESP(SIZE) \
{ \
[RESP_HDR_SIZE + SIZE - 1] = 0, \
}
static inline void refill_req(u8 *dest, const u8 *src, size_t size)
{
int i;
for (i = 0; i < size; ++i)
dest[REQ_HDR_SIZE + i] = src[i];
}
static inline void extr_resp(u8 *dest, const u8 *src, size_t size)
{
int i;
for (i = 0; i < size; ++i)
dest[i] = src[RESP_HDR_SIZE + i];
}
[...]
example:
int gaokun_ec_psy_multi_read(struct gaokun_ec *ec, u8 reg,
size_t resp_len, u8 *resp)
{
int i, ret;
u8 _resp[] = MKRESP(1);
u8 req[] = MKREQ(0x02, EC_READ, 1);
for (i = 0; i < resp_len; ++i, ++reg) {
refill_req(req, ®, 1);
ret = gaokun_ec_read(ec, req, sizeof(_resp), _resp);
if (ret)
return ret;
extr_resp(&resp[i], _resp, 1);
}
return 0;
}
Best wishes,
Pengyu
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 2/5] platform: arm64: add Huawei Matebook E Go (sc8280xp) EC driver
2024-12-31 7:44 ` Pengyu Luo
2024-12-31 11:09 ` Bryan O'Donoghue
@ 2025-01-03 5:38 ` Dmitry Baryshkov
2025-01-03 7:19 ` Pengyu Luo
1 sibling, 1 reply; 51+ messages in thread
From: Dmitry Baryshkov @ 2025-01-03 5:38 UTC (permalink / raw)
To: Pengyu Luo
Cc: quic_aiquny, andersson, bryan.odonoghue, conor+dt, devicetree,
gregkh, hdegoede, heikki.krogerus, ilpo.jarvinen, konradybcio,
krzk+dt, linux-arm-msm, linux-kernel, linux-pm, linux-usb, nikita,
platform-driver-x86, robh, sre
On Tue, Dec 31, 2024 at 03:44:36PM +0800, Pengyu Luo wrote:
> On Tue, Dec 31, 2024 at 1:00 PM Aiqun(Maria) Yu <quic_aiquny@quicinc.com> wrote:
> > On 12/30/2024 6:44 PM, Pengyu Luo wrote:
> > > On Mon, Dec 30, 2024 at 5:04 PM Aiqun(Maria) Yu <quic_aiquny@quicinc.com> wrote:
> > >> On 12/28/2024 1:13 AM, Pengyu Luo wrote:
> > [...]
> > >>> + i2c_transfer(client->adapter, msgs, 2);
> > >>
> > >> ARRAY_SIZE(msgs) is suggested instead of pure 2.
> > >>
> > >
> > > Agree
> > >
> > >>> + usleep_range(2000, 2500);
> > >>
> > >> Why is a sleep needed here? Is this information specified in any datasheet?
> > >>
> > >
> > > Have a break between 2 transaction. This sleep happens in acpi code, also
> > > inside a critical region. I rearranged it.
> > >
> > > Local7 = Acquire (\_SB.IC16.MUEC, 0x03E8)
> > > ...
> > > write ops
> > > ...
> > > Sleep (0x02)
> > > ...
> > > read ops
> > > ...
> > > Release (\_SB.IC16.MUEC)
> >
> > Could you please share the exact code snippet that is being referenced?
> > I'm a bit confused because it doesn't seem to align with the current
> > logic, which doesn't have read operations within the same mutex lock. I
> > also want to understand the background and necessity of the sleep function.
> >
>
> I mentioned I rearranged it to optimize it. In a EC transaction,
> write sleep read => write read sleep, in this way, we sleep once a
> transaction.
Sleeping between write and read is logical: it provides EC some time to
respond. Sleeping after read is complete doesn't seem to have any
reason.
>
> Please search
> 'device name + acpi table' on the internet, someone dumped it and uploaded
> it, in SSDT, check ECCD. I am not sure if huawei allows users to dump it.
> So I don't provide it here.
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 2/5] platform: arm64: add Huawei Matebook E Go (sc8280xp) EC driver
2025-01-03 5:38 ` Dmitry Baryshkov
@ 2025-01-03 7:19 ` Pengyu Luo
0 siblings, 0 replies; 51+ messages in thread
From: Pengyu Luo @ 2025-01-03 7:19 UTC (permalink / raw)
To: dmitry.baryshkov
Cc: andersson, bryan.odonoghue, conor+dt, devicetree, gregkh,
hdegoede, heikki.krogerus, ilpo.jarvinen, konradybcio, krzk+dt,
linux-arm-msm, linux-kernel, linux-pm, linux-usb, mitltlatltl,
nikita, platform-driver-x86, quic_aiquny, robh, sre
On Fri, Jan 3, 2025 at 1:38 PM Dmitry Baryshkov <dmitry.baryshkov@linaro.org> wrote:
> On Tue, Dec 31, 2024 at 03:44:36PM +0800, Pengyu Luo wrote:
> > On Tue, Dec 31, 2024 at 1:00 PM Aiqun(Maria) Yu <quic_aiquny@quicinc.com> wrote:
> > > On 12/30/2024 6:44 PM, Pengyu Luo wrote:
> > > > On Mon, Dec 30, 2024 at 5:04 PM Aiqun(Maria) Yu <quic_aiquny@quicinc.com> wrote:
> > > >> On 12/28/2024 1:13 AM, Pengyu Luo wrote:
> > > [...]
> > > >>> + i2c_transfer(client->adapter, msgs, 2);
> > > >>
> > > >> ARRAY_SIZE(msgs) is suggested instead of pure 2.
> > > >>
> > > >
> > > > Agree
> > > >
> > > >>> + usleep_range(2000, 2500);
> > > >>
> > > >> Why is a sleep needed here? Is this information specified in any datasheet?
> > > >>
> > > >
> > > > Have a break between 2 transaction. This sleep happens in acpi code, also
> > > > inside a critical region. I rearranged it.
> > > >
> > > > Local7 = Acquire (\_SB.IC16.MUEC, 0x03E8)
> > > > ...
> > > > write ops
> > > > ...
> > > > Sleep (0x02)
> > > > ...
> > > > read ops
> > > > ...
> > > > Release (\_SB.IC16.MUEC)
> > >
> > > Could you please share the exact code snippet that is being referenced?
> > > I'm a bit confused because it doesn't seem to align with the current
> > > logic, which doesn't have read operations within the same mutex lock. I
> > > also want to understand the background and necessity of the sleep function.
> > >
> >
> > I mentioned I rearranged it to optimize it. In a EC transaction,
> > write sleep read => write read sleep, in this way, we sleep once a
> > transaction.
>
> Sleeping between write and read is logical: it provides EC some time to
> respond. Sleeping after read is complete doesn't seem to have any
> reason.
>
OK, if you are interested, I explain this in details
First, EC transaction in acpi on this device is doing like
======== this transaction =========
lock
...
write
...
sleep
...
read
...
release
======== this transaction =========
When there are intensive transactions, another sleep is added in
======== this transaction =========
...
======== this transaction =========
...
sleep
...
======== next transaction =========
...
======== next transaction =========
Can we eliminate this? I am not sure, I have not tested it.
Generally, the code in acpi is terrible, it can just do the jobs, so I did
some changes and tested.
The process(reading after writing) and data structure(cmd, count, data...)
are very similar to I2C_FUNC_SMBUS_BLOCK_PROC_CALL(see [1]), see also ACPI
Specification 13.3.7. (It like this in acpi, BUFF = VREG = BUFF)
So I tried to send two messages in one shot without a break. Why not using
a smbus API? Qualcomm I2C driver in kernel does not support it
(Fall back to i2c_smbus_xfer_emulated).
Why not using a I2C Block Read/Write API?
One transaction with this api would send 3 messages, and return the wrong
status in return buffer.
Write:
i2c_smbus_write_i2c_block_data(mcmd, ilen+2, {scmd, ilen, ibuf})
i2c_msg = {
.len = ilen + 3,
.buf = {mcmd, scmd, ilen, ibuf}
}
Read:
i2c_smbus_read_i2c_block_data(mcmd, olen)
i2c_msg[0] = {
.len = 1,
.buf = {mcmd},
};
i2c_msg[1] = {
.flags = I2C_M_RD,
.len = olen,
.buf = {}, /* the first byte return is wrong */
};
Best wishes,
Pengyu
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 3/5] usb: typec: ucsi: add Huawei Matebook E Go (sc8280xp) ucsi driver
2024-12-29 9:05 ` Pengyu Luo
@ 2025-01-06 3:33 ` Dmitry Baryshkov
2025-01-06 9:20 ` [PATCH 1/5] dt-bindings: platform: Add Huawei Matebook E Go EC Pengyu Luo
2025-01-06 9:22 ` [PATCH 3/5] usb: typec: ucsi: add Huawei Matebook E Go (sc8280xp) ucsi driver Pengyu Luo
0 siblings, 2 replies; 51+ messages in thread
From: Dmitry Baryshkov @ 2025-01-06 3:33 UTC (permalink / raw)
To: Pengyu Luo
Cc: andersson, bryan.odonoghue, conor+dt, devicetree, gregkh,
hdegoede, heikki.krogerus, ilpo.jarvinen, konradybcio, krzk+dt,
linux-arm-msm, linux-kernel, linux-pm, linux-usb, nikita,
platform-driver-x86, robh, sre
On Sun, Dec 29, 2024 at 05:05:47PM +0800, Pengyu Luo wrote:
> On Sun, Dec 29, 2024 at 12:40 PM Dmitry Baryshkov <dmitry.baryshkov@linaro.org> wrote:
> > On Sat, Dec 28, 2024 at 01:13:51AM +0800, Pengyu Luo wrote:
> > > The Huawei Matebook E Go (sc8280xp) tablet provides implements UCSI
> > > interface in the onboard EC. Add the glue driver to interface the
> > > platform's UCSI implementation.
> > >
> > > Signed-off-by: Pengyu Luo <mitltlatltl@gmail.com>
> > > ---
> > > drivers/usb/typec/ucsi/Kconfig | 9 +
> > > drivers/usb/typec/ucsi/Makefile | 1 +
> > > drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c | 481 ++++++++++++++++++++
> > > 3 files changed, 491 insertions(+)
> > > create mode 100644 drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c
> > >
> > > diff --git a/drivers/usb/typec/ucsi/Kconfig b/drivers/usb/typec/ucsi/Kconfig
> > > index 680e1b87b..0d0f07488 100644
> > > --- a/drivers/usb/typec/ucsi/Kconfig
> > > +++ b/drivers/usb/typec/ucsi/Kconfig
> > > @@ -78,4 +78,13 @@ config UCSI_LENOVO_YOGA_C630
> > > To compile the driver as a module, choose M here: the module will be
> > > called ucsi_yoga_c630.
> > >
> > > +config UCSI_HUAWEI_GAOKUN
> > > + tristate "UCSI Interface Driver for Huawei Matebook E Go (sc8280xp)"
> > > + depends on EC_HUAWEI_GAOKUN
> > > + help
> > > + This driver enables UCSI support on the Huawei Matebook E Go tablet.
> > > +
> > > + To compile the driver as a module, choose M here: the module will be
> > > + called ucsi_huawei_gaokun.
> > > +
> > > endif
> > > diff --git a/drivers/usb/typec/ucsi/Makefile b/drivers/usb/typec/ucsi/Makefile
> > > index aed41d238..0b400122b 100644
> > > --- a/drivers/usb/typec/ucsi/Makefile
> > > +++ b/drivers/usb/typec/ucsi/Makefile
> > > @@ -22,3 +22,4 @@ obj-$(CONFIG_UCSI_CCG) += ucsi_ccg.o
> > > obj-$(CONFIG_UCSI_STM32G0) += ucsi_stm32g0.o
> > > obj-$(CONFIG_UCSI_PMIC_GLINK) += ucsi_glink.o
> > > obj-$(CONFIG_UCSI_LENOVO_YOGA_C630) += ucsi_yoga_c630.o
> > > +obj-$(CONFIG_UCSI_HUAWEI_GAOKUN) += ucsi_huawei_gaokun.o
> > > diff --git a/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c
> > > new file mode 100644
> > > index 000000000..84ed0407d
> > > --- /dev/null
> > > +++ b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c
> > > @@ -0,0 +1,481 @@
> > > +// SPDX-License-Identifier: GPL-2.0-only
> > > +/*
> > > + * ucsi-huawei-gaokun - A UCSI driver for HUAWEI Matebook E Go (sc8280xp)
> > > + *
> > > + * reference: drivers/usb/typec/ucsi/ucsi_yoga_c630.c
> > > + * drivers/usb/typec/ucsi/ucsi_glink.c
> > > + * drivers/soc/qcom/pmic_glink_altmode.c
> > > + *
> > > + * Copyright (C) 2024 Pengyu Luo <mitltlatltl@gmail.com>
> > > + */
> > > +
> > > +#include <linux/auxiliary_bus.h>
> > > +#include <linux/bitops.h>
> > > +#include <linux/completion.h>
> > > +#include <linux/container_of.h>
> > > +#include <linux/delay.h>
> > > +#include <linux/module.h>
> > > +#include <linux/notifier.h>
> > > +#include <linux/of.h>
> > > +#include <linux/string.h>
> > > +#include <linux/workqueue_types.h>
> > > +
> > > +#include <linux/usb/pd_vdo.h>
> > > +#include <drm/bridge/aux-bridge.h>
> > > +
> > > +#include "ucsi.h"
> > > +#include <linux/platform_data/huawei-gaokun-ec.h>
> > > +
> > > +
> > > +#define EC_EVENT_UCSI 0x21
> > > +#define EC_EVENT_USB 0x22
> > > +
> > > +#define GAOKUN_CCX_MASK GENMASK(1, 0)
> > > +#define GAOKUN_MUX_MASK GENMASK(3, 2)
> > > +
> > > +#define GAOKUN_DPAM_MASK GENMASK(3, 0)
> > > +#define GAOKUN_HPD_STATE_MASK BIT(4)
> > > +#define GAOKUN_HPD_IRQ_MASK BIT(5)
> > > +
> > > +#define CCX_TO_ORI(ccx) (++ccx % 3)
> > > +
> > > +#define GET_IDX(updt) (ffs(updt) - 1)
> > > +
> > > +/* Configuration Channel Extension */
> > > +enum gaokun_ucsi_ccx {
> > > + USBC_CCX_NORMAL,
> > > + USBC_CCX_REVERSE,
> > > + USBC_CCX_NONE,
> > > +};
> > > +
> > > +enum gaokun_ucsi_mux {
> > > + USBC_MUX_NONE,
> > > + USBC_MUX_USB_2L,
> > > + USBC_MUX_DP_4L,
> > > + USBC_MUX_USB_DP,
> > > +};
> > > +
> > > +struct gaokun_ucsi_reg {
> > > + u8 port_num;
> > > + u8 port_updt;
> > > + u8 port_data[4];
> > > + u8 checksum;
> > > + u8 reserved;
> > > +} __packed;
> > > +
> > > +struct gaokun_ucsi_port {
> > > + struct completion usb_ack;
> > > + spinlock_t lock;
> > > +
> > > + struct gaokun_ucsi *ucsi;
> > > + struct auxiliary_device *bridge;
> > > +
> > > + int idx;
> > > + enum gaokun_ucsi_ccx ccx;
> > > + enum gaokun_ucsi_mux mux;
> > > + u8 mode;
> > > + u16 svid;
> > > + u8 hpd_state;
> > > + u8 hpd_irq;
> > > +};
> > > +
> > > +struct gaokun_ucsi {
> > > + struct gaokun_ec *ec;
> > > + struct ucsi *ucsi;
> > > + struct gaokun_ucsi_port *ports;
> > > + struct device *dev;
> > > + struct work_struct work;
> > > + struct notifier_block nb;
> > > + u16 version;
> > > + u8 port_num;
> > > +};
> > > +
> > > +/* -------------------------------------------------------------------------- */
> > > +/* For UCSI */
> > > +
> > > +static int gaokun_ucsi_read_version(struct ucsi *ucsi, u16 *version)
> > > +{
> > > + struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi);
> > > +
> > > + *version = uec->version;
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static int gaokun_ucsi_read_cci(struct ucsi *ucsi, u32 *cci)
> > > +{
> > > + struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi);
> > > + u8 buf[GAOKUN_UCSI_READ_SIZE];
> > > + int ret;
> > > +
> > > + ret = gaokun_ec_ucsi_read(uec->ec, buf);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + memcpy(cci, buf, sizeof(*cci));
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static int gaokun_ucsi_read_message_in(struct ucsi *ucsi,
> > > + void *val, size_t val_len)
> > > +{
> > > + struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi);
> > > + u8 buf[GAOKUN_UCSI_READ_SIZE];
> > > + int ret;
> > > +
> > > + ret = gaokun_ec_ucsi_read(uec->ec, buf);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + memcpy(val, buf + GAOKUN_UCSI_CCI_SIZE,
> > > + min(val_len, GAOKUN_UCSI_DATA_SIZE));
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static int gaokun_ucsi_async_control(struct ucsi *ucsi, u64 command)
> > > +{
> > > + struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi);
> > > + u8 buf[GAOKUN_UCSI_WRITE_SIZE] = {};
> > > +
> > > + memcpy(buf, &command, sizeof(command));
> > > +
> > > + return gaokun_ec_ucsi_write(uec->ec, buf);
> > > +}
> > > +
> > > +static void gaokun_ucsi_update_connector(struct ucsi_connector *con)
> > > +{
> > > + struct gaokun_ucsi *uec = ucsi_get_drvdata(con->ucsi);
> > > +
> > > + if (con->num > uec->port_num)
> > > + return;
> > > +
> > > + con->typec_cap.orientation_aware = true;
> > > +}
> > > +
> > > +static void gaokun_set_orientation(struct ucsi_connector *con,
> > > + struct gaokun_ucsi_port *port)
> > > +{
> > > + enum gaokun_ucsi_ccx ccx;
> > > + unsigned long flags;
> > > +
> > > + spin_lock_irqsave(&port->lock, flags);
> > > + ccx = port->ccx;
> > > + spin_unlock_irqrestore(&port->lock, flags);
> > > +
> > > + typec_set_orientation(con->port, CCX_TO_ORI(ccx));
> > > +}
> > > +
> > > +static void gaokun_ucsi_connector_status(struct ucsi_connector *con)
> > > +{
> > > + struct gaokun_ucsi *uec = ucsi_get_drvdata(con->ucsi);
> > > + int idx;
> > > +
> > > + idx = con->num - 1;
> > > + if (con->num > uec->port_num) {
> > > + dev_warn(uec->ucsi->dev, "set orientation out of range: con%d\n", idx);
> > > + return;
> > > + }
> > > +
> > > + gaokun_set_orientation(con, &uec->ports[idx]);
> > > +}
> > > +
> > > +const struct ucsi_operations gaokun_ucsi_ops = {
> > > + .read_version = gaokun_ucsi_read_version,
> > > + .read_cci = gaokun_ucsi_read_cci,
> > > + .read_message_in = gaokun_ucsi_read_message_in,
> > > + .sync_control = ucsi_sync_control_common,
> > > + .async_control = gaokun_ucsi_async_control,
> > > + .update_connector = gaokun_ucsi_update_connector,
> > > + .connector_status = gaokun_ucsi_connector_status,
> > > +};
> > > +
> > > +/* -------------------------------------------------------------------------- */
> > > +/* For Altmode */
> > > +
> > > +static void gaokun_ucsi_port_update(struct gaokun_ucsi_port *port,
> > > + const u8 *port_data)
> > > +{
> > > + unsigned long flags;
> > > + u8 dcc, ddi;
> > > + int offset = port->idx * 2; /* every port has 2 Bytes data */
> > > +
> > > + dcc = port_data[offset];
> > > + ddi = port_data[offset + 1];
> >
> > What is dcc and ddi? Are those just names from the DSDT?
> >
>
> Yes, DSDT's inventions. Huawei one uses that.
>
> Some additional information, you can check the following in sc8280xp or
> xelite based dsdt.
>
> In UPAN(usbc pinassignment notification), PBUF carries a pan info, which
> is a 8B data, {BPID, BORI, BMUX, BVID(2B), BSID(2B), BSSD} which stands for
> port_id, orientation of port, mux state, USB-IF vendor id, USB-IF standard id,
> I don't know the BSSD, (if linaro know something?)
> but according to drivers/soc/qcom/pmic_glink_altmode.c
> BSSD is related to pin assignment(mode field), hpd_state, hpd_irq, ddi is
> something equivalent to BSSD. dcc is something equivalent to BORI and BMUX.
Ack
>
>
> > > +
> > > + spin_lock_irqsave(&port->lock, flags);
> > > +
> > > + port->ccx = FIELD_GET(GAOKUN_CCX_MASK, dcc);
> > > + port->mux = FIELD_GET(GAOKUN_MUX_MASK, dcc);
> > > + port->mode = FIELD_GET(GAOKUN_DPAM_MASK, ddi);
> > > + port->hpd_state = FIELD_GET(GAOKUN_HPD_STATE_MASK, ddi);
> > > + port->hpd_irq = FIELD_GET(GAOKUN_HPD_IRQ_MASK, ddi);
> > > +
> > > + switch (port->mux) {
> > > + case USBC_MUX_NONE:
> > > + port->svid = 0;
> > > + break;
> > > + case USBC_MUX_USB_2L:
> > > + port->svid = USB_SID_PD;
> > > + break;
> > > + case USBC_MUX_DP_4L:
> > > + case USBC_MUX_USB_DP:
> > > + port->svid = USB_SID_DISPLAYPORT;
> > > + if (port->ccx == USBC_CCX_REVERSE)
> > > + port->mode -= 6;
> >
> > I'd prefer it this were more explicit about what is happening.
> >
>
> If orientation is reverse, then we should minus 6, EC's logic.
> I will add a comment for it. Actually, this field is unused, I don't
> find the mux yet, so I cannot set it with this field. But I don't want
> to make things imcomplete, so keep it.
Which values are you expecting / getting there? The -6 is a pure magic.
Please replace this with a switch-case or something more obvious.
> Let me go off the topic, on my device, I can just use drm_aux_hpd_bridge_notify
> to enable altmode, usb functions well after I pluged out, I don't need set mode
> switch(orientation switch is required if orientation is reverse), which is quiet
> similar to Acer aspire 1. Is mux controlled also by QMP combo phy(see [1])?
>
> > > + break;
> > > + default:
> > > + break;
> > > + }
> > > +
> > > + spin_unlock_irqrestore(&port->lock, flags);
> > > +}
> > > +
> > > +static int gaokun_ucsi_refresh(struct gaokun_ucsi *uec)
> > > +{
> > > + struct gaokun_ucsi_reg ureg;
> > > + int ret, idx;
> > > +
> > > + ret = gaokun_ec_ucsi_get_reg(uec->ec, (u8 *)&ureg);
> > > + if (ret)
> > > + return -EIO;
> > > +
> > > + uec->port_num = ureg.port_num;
> > > + idx = GET_IDX(ureg.port_updt);
> > > +
> > > + if (idx >= 0 && idx < ureg.port_num)
> > > + gaokun_ucsi_port_update(&uec->ports[idx], ureg.port_data);
> > > +
> > > + return idx;
> > > +}
> > > +
> > > +static void gaokun_ucsi_handle_altmode(struct gaokun_ucsi_port *port)
> > > +{
> > > + struct gaokun_ucsi *uec = port->ucsi;
> > > + int idx = port->idx;
> > > +
> > > + if (idx >= uec->ucsi->cap.num_connectors || !uec->ucsi->connector) {
> > > + dev_warn(uec->ucsi->dev, "altmode port out of range: %d\n", idx);
> > > + return;
> > > + }
> > > +
> > > + /* UCSI callback .connector_status() have set orientation */
> > > + if (port->bridge)
> > > + drm_aux_hpd_bridge_notify(&port->bridge->dev,
> > > + port->hpd_state ?
> > > + connector_status_connected :
> > > + connector_status_disconnected);
> >
> > Does your platform report any altmodes? What do you see in
> > /sys/class/typec/port0/port0.*/ ?
> >
>
> /sys/class/typec/port0/port0.0:
> active mode mode1 power svid uevent vdo
>
> /sys/class/typec/port0/port0.1:
> active mode mode1 power svid uevent vdo
>
> /sys/class/typec/port0/port0.2:
> active mode mode1 power svid uevent vdo
>
> /sys/class/typec/port0/port0.3:
> active mode mode2 power svid uevent vdo
>
> /sys/class/typec/port0/port0.4:
> active mode mode3 power svid uevent vdo
please:
cat /sys/class/typec/port0/port0*/svid
cat /sys/class/typec/port0/port0*/vdo
If DP is reported as one the altmodes, then it should be using the
DisplayPort AltMode driver, as suggested by Heikki.
> > > +
> > > + gaokun_ec_ucsi_pan_ack(uec->ec, port->idx);
> > > +}
> > > +
> > > +static void gaokun_ucsi_altmode_notify_ind(struct gaokun_ucsi *uec)
> > > +{
> > > + int idx;
> > > +
> > > + idx = gaokun_ucsi_refresh(uec);
> > > + if (idx < 0)
> > > + gaokun_ec_ucsi_pan_ack(uec->ec, idx);
> > > + else
> > > + gaokun_ucsi_handle_altmode(&uec->ports[idx]);
> > > +}
> > > +
> > > +/*
> > > + * USB event is necessary for enabling altmode, the event should follow
> > > + * UCSI event, if not after timeout(this notify may be disabled somehow),
> > > + * then force to enable altmode.
> > > + */
> > > +static void gaokun_ucsi_handle_no_usb_event(struct gaokun_ucsi *uec, int idx)
> > > +{
> > > + struct gaokun_ucsi_port *port;
> > > +
> > > + port = &uec->ports[idx];
> > > + if (!wait_for_completion_timeout(&port->usb_ack, 2 * HZ)) {
> > > + dev_warn(uec->dev, "No USB EVENT, triggered by UCSI EVENT");
> > > + gaokun_ucsi_altmode_notify_ind(uec);
> > > + }
> > > +}
> > > +
> > > +static int gaokun_ucsi_notify(struct notifier_block *nb,
> > > + unsigned long action, void *data)
> > > +{
> > > + u32 cci;
> > > + struct gaokun_ucsi *uec = container_of(nb, struct gaokun_ucsi, nb);
> > > +
> > > + switch (action) {
> > > + case EC_EVENT_USB:
> > > + gaokun_ucsi_altmode_notify_ind(uec);
> > > + return NOTIFY_OK;
> > > +
> > > + case EC_EVENT_UCSI:
> > > + uec->ucsi->ops->read_cci(uec->ucsi, &cci);
> > > + ucsi_notify_common(uec->ucsi, cci);
> > > + if (UCSI_CCI_CONNECTOR(cci))
> > > + gaokun_ucsi_handle_no_usb_event(uec, UCSI_CCI_CONNECTOR(cci) - 1);
> > > +
> > > + return NOTIFY_OK;
> > > +
> > > + default:
> > > + return NOTIFY_DONE;
> > > + }
> > > +}
> > > +
> > > +static int gaokun_ucsi_get_port_num(struct gaokun_ucsi *uec)
> > > +{
> > > + struct gaokun_ucsi_reg ureg;
> > > + int ret;
> > > +
> > > + ret = gaokun_ec_ucsi_get_reg(uec->ec, (u8 *)&ureg);
> > > +
> > > + return ret ? 0 : ureg.port_num;
> > > +}
> > > +
> > > +static int gaokun_ucsi_ports_init(struct gaokun_ucsi *uec)
> > > +{
> > > + u32 port;
> > > + int i, ret, port_num;
> > > + struct device *dev = uec->dev;
> > > + struct gaokun_ucsi_port *ucsi_port;
> > > + struct fwnode_handle *fwnode;
> > > +
> > > + port_num = gaokun_ucsi_get_port_num(uec);
> > > + uec->port_num = port_num;
> > > +
> > > + uec->ports = devm_kzalloc(dev, port_num * sizeof(*(uec->ports)),
> > > + GFP_KERNEL);
> > > + if (!uec->ports)
> > > + return -ENOMEM;
> > > +
> > > + for (i = 0; i < port_num; ++i) {
> > > + ucsi_port = &uec->ports[i];
> > > + ucsi_port->ccx = USBC_CCX_NONE;
> > > + ucsi_port->idx = i;
> > > + ucsi_port->ucsi = uec;
> > > + init_completion(&ucsi_port->usb_ack);
> > > + spin_lock_init(&ucsi_port->lock);
> > > + }
> > > +
> > > + device_for_each_child_node(dev, fwnode) {
> > > + ret = fwnode_property_read_u32(fwnode, "reg", &port);
> > > + if (ret < 0) {
> > > + dev_err(dev, "missing reg property of %pOFn\n", fwnode);
> > > + fwnode_handle_put(fwnode);
> > > + return ret;
> > > + }
> > > +
> > > + if (port >= port_num) {
> > > + dev_warn(dev, "invalid connector number %d, ignoring\n", port);
> > > + continue;
> > > + }
> > > +
> > > + ucsi_port = &uec->ports[port];
> > > + ucsi_port->bridge = devm_drm_dp_hpd_bridge_alloc(dev, to_of_node(fwnode));
> > > + if (IS_ERR(ucsi_port->bridge)) {
> > > + fwnode_handle_put(fwnode);
> > > + return PTR_ERR(ucsi_port->bridge);
> > > + }
> > > + }
> > > +
> > > + for (i = 0; i < port_num; i++) {
> > > + if (!uec->ports[i].bridge)
> > > + continue;
> > > +
> > > + ret = devm_drm_dp_hpd_bridge_add(dev, uec->ports[i].bridge);
> > > + if (ret)
> > > + return ret;
> > > + }
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static void gaokun_ucsi_register_worker(struct work_struct *work)
> > > +{
> > > + struct gaokun_ucsi *uec;
> > > + struct ucsi *ucsi;
> > > + int ret;
> > > +
> > > + uec = container_of(work, struct gaokun_ucsi, work);
> > > + ucsi = uec->ucsi;
> > > +
> > > + ucsi->quirks = UCSI_NO_PARTNER_PDOS | UCSI_DELAY_DEVICE_PDOS;
> >
> > Does it crash in the same way as GLINK crashes (as you've set
> > UCSI_NO_PARTNER_PDOS)?
> >
>
> Yes, no partner can be detected, I checked. I think it is also handled by
> the firmware As you said in [2]
> > In some obscure cases (Qualcomm PMIC Glink) altmode is completely
> > handled by the firmware. Linux does not get proper partner altmode info.
This is a separate topic. Those two flags were added for a very
particular reason:
- To workaround firmware crash on requesting PDOs for a partner
- To delay requeting PDOs for the device because in the unconnected
state the GET_PDOS returns incorrect information
Are you sure that those two flags are necessary for your platform?
>
> > > +
> > > + ssleep(3); /* EC can't handle UCSI properly in the early stage */
> > > +
> > > + ret = gaokun_ec_register_notify(uec->ec, &uec->nb);
> > > + if (ret) {
> > > + dev_err_probe(ucsi->dev, ret, "notifier register failed\n");
> > > + return;
> > > + }
> > > +
> > > + ret = ucsi_register(ucsi);
> > > + if (ret)
> > > + dev_err_probe(ucsi->dev, ret, "ucsi register failed\n");
> > > +}
> > > +
> > > +static int gaokun_ucsi_register(struct gaokun_ucsi *uec)
> >
> > Please inline
> >
>
> I see.
>
> Best wishes
> Pengyu
>
> [1] https://elixir.bootlin.com/linux/v6.12.5/source/drivers/phy/qualcomm/phy-qcom-qmp-combo.c#L2679
> [2] https://lore.kernel.org/lkml/20240416-ucsi-glink-altmode-v1-0-890db00877ac@linaro.org
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 1/5] dt-bindings: platform: Add Huawei Matebook E Go EC
2025-01-06 3:33 ` Dmitry Baryshkov
@ 2025-01-06 9:20 ` Pengyu Luo
2025-01-06 9:22 ` [PATCH 3/5] usb: typec: ucsi: add Huawei Matebook E Go (sc8280xp) ucsi driver Pengyu Luo
1 sibling, 0 replies; 51+ messages in thread
From: Pengyu Luo @ 2025-01-06 9:20 UTC (permalink / raw)
To: dmitry.baryshkov
Cc: andersson, bryan.odonoghue, conor+dt, devicetree, gregkh,
hdegoede, heikki.krogerus, ilpo.jarvinen, konradybcio, krzk+dt,
linux-arm-msm, linux-kernel, linux-pm, linux-usb, mitltlatltl,
nikita, platform-driver-x86, robh, sre
On Mon, Dec 30, 2024 at 3:28 PM Aiqun(Maria) Yu <quic_aiquny@quicinc.com> wrote:
> On 12/29/2024 6:12 PM, Pengyu Luo wrote:
>
[...]
> >>
> >
> > Check the motherboard, https://postimg.cc/V5r4KCgx (Credit to Tianyu Gao <gty0622@gmail.com>)
>
> The link is not accessible from my end. Could you please help follow the
> document tips referenced by [1] if this content is important for the
> overall naming design?
>
> Here are some snippets for reference:
> "for 'volatile' documents, please create an entry in the kernel
> bugzilla https://bugzilla.kernel.org and attach a copy of these documents
> to the bugzilla entry. Finally, provide the URL of the bugzilla entry in
> the changelog."
>
> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/process/maintainer-tip.rst
> [1]
I created one entry, then I got myself and the entry banned, :(
I had written to them to explain this yesterday. No response.
Title 'Huawei Matebook E Go, whose codename is Gaokun',
In this entry, I explained why is it called gaokun, and why gen3.
> https://bugzilla.kernel.org/show_bug.cgi?id=219645
>
> Artem S. Tashkinov (aros@gmx.com) changed:
>
> What |Removed |Added
> ----------------------------------------------------------------------------
> Status|NEW |RESOLVED
> Group| |Junk
> Component|man-pages |Spam
> Version|unspecified |2.5
> Resolution|--- |INVALID
> Assignee|documentation_man-pages@ker |other_spam@kernel-bugs.kern
> |nel-bugs.osdl.org |el.org
> Product|Documentation |Other
Best wishes,
Pengyu
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 3/5] usb: typec: ucsi: add Huawei Matebook E Go (sc8280xp) ucsi driver
2025-01-06 3:33 ` Dmitry Baryshkov
2025-01-06 9:20 ` [PATCH 1/5] dt-bindings: platform: Add Huawei Matebook E Go EC Pengyu Luo
@ 2025-01-06 9:22 ` Pengyu Luo
1 sibling, 0 replies; 51+ messages in thread
From: Pengyu Luo @ 2025-01-06 9:22 UTC (permalink / raw)
To: dmitry.baryshkov
Cc: andersson, bryan.odonoghue, conor+dt, devicetree, gregkh,
hdegoede, heikki.krogerus, ilpo.jarvinen, konradybcio, krzk+dt,
linux-arm-msm, linux-kernel, linux-pm, linux-usb, mitltlatltl,
nikita, platform-driver-x86, robh, sre
Please ignore the last email, I sent the wrong archive.
On Mon, Jan 6, 2025 at 11:33 AM Dmitry Baryshkov <dmitry.baryshkov@linaro.org> wrote:
> On Sun, Dec 29, 2024 at 05:05:47PM +0800, Pengyu Luo wrote:
> > On Sun, Dec 29, 2024 at 12:40 PM Dmitry Baryshkov <dmitry.baryshkov@linaro.org> wrote:
> > > On Sat, Dec 28, 2024 at 01:13:51AM +0800, Pengyu Luo wrote:
> > > > The Huawei Matebook E Go (sc8280xp) tablet provides implements UCSI
> > > > interface in the onboard EC. Add the glue driver to interface the
> > > > platform's UCSI implementation.
> > > >
> > > > Signed-off-by: Pengyu Luo <mitltlatltl@gmail.com>
> > > > ---
> > > > drivers/usb/typec/ucsi/Kconfig | 9 +
> > > > drivers/usb/typec/ucsi/Makefile | 1 +
> > > > drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c | 481 ++++++++++++++++++++
> > > > 3 files changed, 491 insertions(+)
> > > > create mode 100644 drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c
> > > >
> > > > diff --git a/drivers/usb/typec/ucsi/Kconfig b/drivers/usb/typec/ucsi/Kconfig
> > > > index 680e1b87b..0d0f07488 100644
> > > > --- a/drivers/usb/typec/ucsi/Kconfig
> > > > +++ b/drivers/usb/typec/ucsi/Kconfig
> > > > @@ -78,4 +78,13 @@ config UCSI_LENOVO_YOGA_C630
> > > > To compile the driver as a module, choose M here: the module will be
> > > > called ucsi_yoga_c630.
[...]
> > > > +
> > > > + spin_lock_irqsave(&port->lock, flags);
> > > > +
> > > > + port->ccx = FIELD_GET(GAOKUN_CCX_MASK, dcc);
> > > > + port->mux = FIELD_GET(GAOKUN_MUX_MASK, dcc);
> > > > + port->mode = FIELD_GET(GAOKUN_DPAM_MASK, ddi);
> > > > + port->hpd_state = FIELD_GET(GAOKUN_HPD_STATE_MASK, ddi);
> > > > + port->hpd_irq = FIELD_GET(GAOKUN_HPD_IRQ_MASK, ddi);
> > > > +
> > > > + switch (port->mux) {
> > > > + case USBC_MUX_NONE:
> > > > + port->svid = 0;
> > > > + break;
> > > > + case USBC_MUX_USB_2L:
> > > > + port->svid = USB_SID_PD;
> > > > + break;
> > > > + case USBC_MUX_DP_4L:
> > > > + case USBC_MUX_USB_DP:
> > > > + port->svid = USB_SID_DISPLAYPORT;
> > > > + if (port->ccx == USBC_CCX_REVERSE)
> > > > + port->mode -= 6;
> > >
> > > I'd prefer it this were more explicit about what is happening.
> > >
> >
> > If orientation is reverse, then we should minus 6, EC's logic.
> > I will add a comment for it. Actually, this field is unused, I don't
> > find the mux yet, so I cannot set it with this field. But I don't want
> > to make things imcomplete, so keep it.
>
> Which values are you expecting / getting there? The -6 is a pure magic.
> Please replace this with a switch-case or something more obvious.
>
In v2, I have deduced their meaning, with a switch to map them.
> > Let me go off the topic, on my device, I can just use drm_aux_hpd_bridge_notify
> > to enable altmode, usb functions well after I pluged out, I don't need set mode
> > switch(orientation switch is required if orientation is reverse), which is quiet
> > similar to Acer aspire 1. Is mux controlled also by QMP combo phy(see [1])?
> >
> > > > + break;
> > > > + default:
> > > > + break;
> > > > + }
> > > > +
> > > > + spin_unlock_irqrestore(&port->lock, flags);
> > > > +}
> > > > +
> > > > +static int gaokun_ucsi_refresh(struct gaokun_ucsi *uec)
> > > > +{
> > > > + struct gaokun_ucsi_reg ureg;
> > > > + int ret, idx;
> > > > +
> > > > + ret = gaokun_ec_ucsi_get_reg(uec->ec, (u8 *)&ureg);
> > > > + if (ret)
> > > > + return -EIO;
> > > > +
> > > > + uec->port_num = ureg.port_num;
> > > > + idx = GET_IDX(ureg.port_updt);
> > > > +
> > > > + if (idx >= 0 && idx < ureg.port_num)
> > > > + gaokun_ucsi_port_update(&uec->ports[idx], ureg.port_data);
> > > > +
> > > > + return idx;
> > > > +}
> > > > +
> > > > +static void gaokun_ucsi_handle_altmode(struct gaokun_ucsi_port *port)
> > > > +{
> > > > + struct gaokun_ucsi *uec = port->ucsi;
> > > > + int idx = port->idx;
> > > > +
> > > > + if (idx >= uec->ucsi->cap.num_connectors || !uec->ucsi->connector) {
> > > > + dev_warn(uec->ucsi->dev, "altmode port out of range: %d\n", idx);
> > > > + return;
> > > > + }
> > > > +
> > > > + /* UCSI callback .connector_status() have set orientation */
> > > > + if (port->bridge)
> > > > + drm_aux_hpd_bridge_notify(&port->bridge->dev,
> > > > + port->hpd_state ?
> > > > + connector_status_connected :
> > > > + connector_status_disconnected);
> > >
> > > Does your platform report any altmodes? What do you see in
> > > /sys/class/typec/port0/port0.*/ ?
> > >
> >
> > /sys/class/typec/port0/port0.0:
> > active mode mode1 power svid uevent vdo
> >
> > /sys/class/typec/port0/port0.1:
> > active mode mode1 power svid uevent vdo
> >
> > /sys/class/typec/port0/port0.2:
> > active mode mode1 power svid uevent vdo
> >
> > /sys/class/typec/port0/port0.3:
> > active mode mode2 power svid uevent vdo
> >
> > /sys/class/typec/port0/port0.4:
> > active mode mode3 power svid uevent vdo
>
> please:
>
> cat /sys/class/typec/port0/port0*/svid
> cat /sys/class/typec/port0/port0*/vdo
>
svid:
8087
ff01
12d1
12d1
12d1
vdo:
0xff000001
0xff1c1c46
0xff000001
0xff000002
0xff000003
> If DP is reported as one the altmodes, then it should be using the
> DisplayPort AltMode driver, as suggested by Heikki.
>
But this paltform cannot access to the partner device, related API
requires a partner.
BTW, it is unnecessary that implementing/call a DP Altmode driver for
this platform. Currently, we can enter altmode with a HPD event notify.
This point is quiet similar to Acer aspire 1. I mentioned this when we
last talked about minus 6.
> > > > +
> > > > + gaokun_ec_ucsi_pan_ack(uec->ec, port->idx);
> > > > +}
> > > > +
> > > > +static void gaokun_ucsi_altmode_notify_ind(struct gaokun_ucsi *uec)
> > > > +{
> > > > + int idx;
> > > > +
> > > > + idx = gaokun_ucsi_refresh(uec);
> > > > + if (idx < 0)
> > > > + gaokun_ec_ucsi_pan_ack(uec->ec, idx);
> > > > + else
> > > > + gaokun_ucsi_handle_altmode(&uec->ports[idx]);
> > > > +}
> > > > +
> > > > +/*
> > > > + * USB event is necessary for enabling altmode, the event should follow
> > > > + * UCSI event, if not after timeout(this notify may be disabled somehow),
> > > > + * then force to enable altmode.
> > > > + */
> > > > +static void gaokun_ucsi_handle_no_usb_event(struct gaokun_ucsi *uec, int idx)
> > > > +{
> > > > + struct gaokun_ucsi_port *port;
> > > > +
> > > > + port = &uec->ports[idx];
> > > > + if (!wait_for_completion_timeout(&port->usb_ack, 2 * HZ)) {
> > > > + dev_warn(uec->dev, "No USB EVENT, triggered by UCSI EVENT");
> > > > + gaokun_ucsi_altmode_notify_ind(uec);
> > > > + }
> > > > +}
> > > > +
[...]
> > > > +
> > > > +static void gaokun_ucsi_register_worker(struct work_struct *work)
> > > > +{
> > > > + struct gaokun_ucsi *uec;
> > > > + struct ucsi *ucsi;
> > > > + int ret;
> > > > +
> > > > + uec = container_of(work, struct gaokun_ucsi, work);
> > > > + ucsi = uec->ucsi;
> > > > +
> > > > + ucsi->quirks = UCSI_NO_PARTNER_PDOS | UCSI_DELAY_DEVICE_PDOS;
> > >
> > > Does it crash in the same way as GLINK crashes (as you've set
> > > UCSI_NO_PARTNER_PDOS)?
> > >
> >
> > Yes, no partner can be detected, I checked. I think it is also handled by
> > the firmware As you said in [2]
> > > In some obscure cases (Qualcomm PMIC Glink) altmode is completely
> > > handled by the firmware. Linux does not get proper partner altmode info.
>
> This is a separate topic. Those two flags were added for a very
> particular reason:
>
> - To workaround firmware crash on requesting PDOs for a partner
> - To delay requeting PDOs for the device because in the unconnected
> state the GET_PDOS returns incorrect information
>
> Are you sure that those two flags are necessary for your platform?
>
Alright, I think I got things mixed up. Actually PDO requires UCSI only,
not a partner device.
I think I will remove it in v3 if it works well during the time. Rencetly,
this platform works well without it. Thanks for pointing out.
> >
> > > > +
> > > > + ssleep(3); /* EC can't handle UCSI properly in the early stage */
> > > > +
> > > > + ret = gaokun_ec_register_notify(uec->ec, &uec->nb);
> > > > + if (ret) {
> > > > + dev_err_probe(ucsi->dev, ret, "notifier register failed\n");
> > > > + return;
> > > > + }
> > > > +
> > > > + ret = ucsi_register(ucsi);
> > > > + if (ret)
> > > > + dev_err_probe(ucsi->dev, ret, "ucsi register failed\n");
> > > > +}
> > > > +
> > > > +static int gaokun_ucsi_register(struct gaokun_ucsi *uec)
> > >
> > > Please inline
> > >
> >
> > I see.
> >
> > Best wishes
> > Pengyu
> >
> > [1] https://elixir.bootlin.com/linux/v6.12.5/source/drivers/phy/qualcomm/phy-qcom-qmp-combo.c#L2679
> > [2] https://lore.kernel.org/lkml/20240416-ucsi-glink-altmode-v1-0-890db00877ac@linaro.org
Best Wishes,
Pengyu
^ permalink raw reply [flat|nested] 51+ messages in thread
end of thread, other threads:[~2025-01-06 9:23 UTC | newest]
Thread overview: 51+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-12-27 17:13 [PATCH 0/5] platform: arm64: Huawei Matebook E Go embedded controller Pengyu Luo
2024-12-27 17:13 ` [PATCH 1/5] dt-bindings: platform: Add Huawei Matebook E Go EC Pengyu Luo
2024-12-27 18:18 ` Rob Herring (Arm)
2024-12-28 9:54 ` Krzysztof Kozlowski
2024-12-28 10:50 ` Pengyu Luo
2024-12-29 9:50 ` Krzysztof Kozlowski
2024-12-29 10:12 ` Pengyu Luo
2024-12-30 7:28 ` Aiqun(Maria) Yu
2024-12-30 7:35 ` Krzysztof Kozlowski
2024-12-30 9:10 ` Aiqun(Maria) Yu
2024-12-30 8:00 ` Pengyu Luo
2025-01-01 5:57 ` Pengyu Luo
2024-12-27 17:13 ` [PATCH 2/5] platform: arm64: add Huawei Matebook E Go (sc8280xp) EC driver Pengyu Luo
2024-12-27 18:21 ` Maya Matuszczyk
2024-12-28 5:42 ` Pengyu Luo
2024-12-28 9:58 ` Krzysztof Kozlowski
2024-12-28 11:34 ` [PATCH 1/5] dt-bindings: platform: Add Huawei Matebook E Go EC Pengyu Luo
2024-12-29 4:08 ` Dmitry Baryshkov
2024-12-29 9:04 ` [PATCH 2/5] platform: arm64: add Huawei Matebook E Go (sc8280xp) EC driver Pengyu Luo
2024-12-29 9:44 ` Krzysztof Kozlowski
2024-12-29 9:43 ` [PATCH 1/5] dt-bindings: platform: Add Huawei Matebook E Go EC Krzysztof Kozlowski
2024-12-29 10:28 ` Pengyu Luo
2024-12-29 21:45 ` Krzysztof Kozlowski
2024-12-28 12:33 ` [PATCH 2/5] platform: arm64: add Huawei Matebook E Go (sc8280xp) EC driver Bryan O'Donoghue
2024-12-28 13:51 ` Pengyu Luo
2024-12-29 15:32 ` Ilpo Järvinen
2024-12-29 15:55 ` Pengyu Luo
2024-12-29 14:49 ` Markus Elfring
2024-12-30 9:04 ` Aiqun(Maria) Yu
2024-12-30 10:44 ` Pengyu Luo
2024-12-31 5:00 ` Aiqun(Maria) Yu
2024-12-31 7:44 ` Pengyu Luo
2024-12-31 11:09 ` Bryan O'Donoghue
2025-01-03 5:38 ` Dmitry Baryshkov
2025-01-03 7:19 ` Pengyu Luo
2025-01-01 11:27 ` Pengyu Luo
2024-12-27 17:13 ` [PATCH 3/5] usb: typec: ucsi: add Huawei Matebook E Go (sc8280xp) ucsi driver Pengyu Luo
2024-12-28 13:06 ` Bryan O'Donoghue
2024-12-28 14:38 ` Pengyu Luo
2024-12-29 14:51 ` Bryan O'Donoghue
2024-12-29 16:25 ` Pengyu Luo
2024-12-29 4:40 ` Dmitry Baryshkov
2024-12-29 9:05 ` Pengyu Luo
2025-01-06 3:33 ` Dmitry Baryshkov
2025-01-06 9:20 ` [PATCH 1/5] dt-bindings: platform: Add Huawei Matebook E Go EC Pengyu Luo
2025-01-06 9:22 ` [PATCH 3/5] usb: typec: ucsi: add Huawei Matebook E Go (sc8280xp) ucsi driver Pengyu Luo
2024-12-29 16:15 ` Markus Elfring
2024-12-27 17:13 ` [PATCH 4/5] power: supply: add Huawei Matebook E Go (sc8280xp) psy driver Pengyu Luo
2024-12-27 17:13 ` [PATCH 5/5] arm64: dts: qcom: gaokun3: Add Embedded Controller node Pengyu Luo
2024-12-30 14:53 ` Konrad Dybcio
2024-12-30 16:22 ` Pengyu Luo
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).