2024-06-12 09:59:55

by Dmitry Baryshkov

[permalink] [raw]
Subject: [PATCH v6 0/6] power: supply: Lenovo Yoga C630 EC

This adds binding, driver and the DT support for the Lenovo Yoga C630
Embedded Controller, to provide battery information.

Support for this EC was implemented by Bjorn, who later could not work
on this driver. I've picked this patchset up and updated it following
the pending review comments.

DisplayPort support is still not a part of this patchset. It uses EC
messages to provide AltMode information rather than implementing
corresponding UCSI commands. However to have a cleaner uAPI story, the
AltMode should be handled via the same Type-C port.

Merge strategy: the driver bits depend on the platform/arm64 patch,
which adds interface for the subdrivers. I'd either ask to get that
patch merged to the immutable branch, which then can be picked up by
power/supply and USB trees or, to make life simpler, ack merging all
driver bits e.g. through USB subsystem (I'm biased here since I plan to
send more cleanups for the UCSI subsystem, which would otherwise result
in cross-subsystem conflicts).

---
Changes in v6:
- Use guard() instead of scoped_guard() (Ilpo)
- Add a define for UCSI version register (Ilpo)
- Added a check to prevent overflowing the address in reg16 read (Ilpo)
- Link to v5: https://lore.kernel.org/r/[email protected]

Changes in v5:
- Added missing article in the commit message (Bryan)
- Changed yoga_c630_ec_ucsi_get_version() to explicitly set the register
instead of just incrementing it (Bryan)
- Dropped spurious debugging pr_info (Bryan)
- Added missing includes all over the place (Ilpo)
- Switched to scoped_guard() where it's suitable (Ilpo)
- Defined register bits (Ilpo, Bryan)
- Whitespace cleanup (Ilpo, Bryan)
- Reworked yoga_c630_ucsi_notify() to use switch-case (Bryan)
- Use ternary operators instead of if()s (Ilpo)
- Switched power supply driver to use fwnode (Sebastian)
- Fixed handling of the adapter's type vs usb_type (Sebastian)
- Added SCOPE property to the battery (Sebastian)
- Link to v4: https://lore.kernel.org/r/[email protected]

Changes in v4:
- Moved bindings to platform/ to follow example of other Acer Aspire1 EC
(Nikita Travkin)
- Fixed dt validation for EC interrupt pin (Rob Herring)
- Dropped separate 'scale' property (Oliver Neukum)
- Link to v3: https://lore.kernel.org/r/[email protected]

Changes in v3:
- Split the driver into core and power supply drivers,
- Added UCSI driver part, handling USB connections,
- Fixed Bjorn's address in DT bindings (Brian Masney)
- Changed power-role for both ports to be "dual" per UCSI
- Link to v2: https://lore.kernel.org/linux-arm-msm/[email protected]/

Changes in v2:
- Dropped DP support for now, as the bindings are in process of being
discussed separately,
- Merged dt patch into the same patchseries,
- Removed the fixed serial number battery property,
- Fixed indentation of dt bindings example,
- Added property: reg and unevaluatedProperties to the connector
bindings.
- Link to v1: https://lore.kernel.org/linux-arm-msm/[email protected]/

---
Bjorn Andersson (2):
dt-bindings: platform: Add Lenovo Yoga C630 EC
arm64: dts: qcom: c630: Add Embedded Controller node

Dmitry Baryshkov (4):
platform: arm64: add Lenovo Yoga C630 WOS EC driver
usb: typec: ucsi: add Lenovo Yoga C630 glue driver
power: supply: lenovo_yoga_c630_battery: add Lenovo C630 driver
arm64: dts: qcom: sdm845: describe connections of USB/DP port

.../bindings/platform/lenovo,yoga-c630-ec.yaml | 83 ++++
arch/arm64/boot/dts/qcom/sdm845.dtsi | 53 ++-
.../boot/dts/qcom/sdm850-lenovo-yoga-c630.dts | 75 ++++
drivers/platform/arm64/Kconfig | 14 +
drivers/platform/arm64/Makefile | 1 +
drivers/platform/arm64/lenovo-yoga-c630.c | 290 ++++++++++++
drivers/power/supply/Kconfig | 9 +
drivers/power/supply/Makefile | 1 +
drivers/power/supply/lenovo_yoga_c630_battery.c | 500 +++++++++++++++++++++
drivers/usb/typec/ucsi/Kconfig | 9 +
drivers/usb/typec/ucsi/Makefile | 1 +
drivers/usb/typec/ucsi/ucsi_yoga_c630.c | 202 +++++++++
include/linux/platform_data/lenovo-yoga-c630.h | 44 ++
13 files changed, 1281 insertions(+), 1 deletion(-)
---
base-commit: 03d44168cbd7fc57d5de56a3730427db758fc7f6
change-id: 20240527-yoga-ec-driver-76fd7f5ddae8

Best regards,
--
Dmitry Baryshkov <[email protected]>



2024-06-12 10:00:59

by Dmitry Baryshkov

[permalink] [raw]
Subject: [PATCH v6 1/6] dt-bindings: platform: Add Lenovo Yoga C630 EC

From: Bjorn Andersson <[email protected]>

Add binding for the Embedded Controller found in the Qualcomm
Snapdragon-based Lenovo Yoga C630.

Signed-off-by: Bjorn Andersson <[email protected]>
Reviewed-by: Krzysztof Kozlowski <[email protected]>
Signed-off-by: Dmitry Baryshkov <[email protected]>
---
.../bindings/platform/lenovo,yoga-c630-ec.yaml | 83 ++++++++++++++++++++++
1 file changed, 83 insertions(+)

diff --git a/Documentation/devicetree/bindings/platform/lenovo,yoga-c630-ec.yaml b/Documentation/devicetree/bindings/platform/lenovo,yoga-c630-ec.yaml
new file mode 100644
index 000000000000..3180ce1a22d4
--- /dev/null
+++ b/Documentation/devicetree/bindings/platform/lenovo,yoga-c630-ec.yaml
@@ -0,0 +1,83 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/platform/lenovo,yoga-c630-ec.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Lenovo Yoga C630 Embedded Controller.
+
+maintainers:
+ - Bjorn Andersson <[email protected]>
+
+description:
+ The Qualcomm Snapdragon-based Lenovo Yoga C630 has an Embedded Controller
+ (EC) which handles things such as battery and USB Type-C. This binding
+ describes the interface, on an I2C bus, to this EC.
+
+properties:
+ compatible:
+ const: lenovo,yoga-c630-ec
+
+ reg:
+ const: 0x70
+
+ '#address-cells':
+ const: 1
+
+ '#size-cells':
+ const: 0
+
+ interrupts:
+ maxItems: 1
+
+patternProperties:
+ '^connector@[01]$':
+ $ref: /schemas/connector/usb-connector.yaml#
+
+ properties:
+ reg:
+ maxItems: 1
+
+ unevaluatedProperties: false
+
+required:
+ - compatible
+ - reg
+ - interrupts
+
+additionalProperties: false
+
+examples:
+ - |+
+ #include <dt-bindings/interrupt-controller/irq.h>
+ i2c1 {
+ clock-frequency = <400000>;
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ embedded-controller@70 {
+ compatible = "lenovo,yoga-c630-ec";
+ reg = <0x70>;
+
+ interrupts-extended = <&tlmm 20 IRQ_TYPE_LEVEL_HIGH>;
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ connector@0 {
+ compatible = "usb-c-connector";
+ reg = <0>;
+ power-role = "source";
+ data-role = "host";
+ };
+
+ connector@1 {
+ compatible = "usb-c-connector";
+ reg = <1>;
+ power-role = "source";
+ data-role = "host";
+ };
+ };
+ };
+...

--
2.39.2


2024-06-12 10:01:16

by Dmitry Baryshkov

[permalink] [raw]
Subject: [PATCH v6 2/6] platform: arm64: add Lenovo Yoga C630 WOS EC driver

Lenovo Yoga C630 WOS is a laptop using Snapdragon 850 SoC. Like many
laptops it uses an embedded controller (EC) to perform various platform
operations, including, but not limited, to Type-C port control or power
supply handlng.

Add the driver for the EC, that creates devices for UCSI and power
supply devices.

Reviewed-by: Bryan O'Donoghue <[email protected]>
Signed-off-by: Dmitry Baryshkov <[email protected]>
---
drivers/platform/arm64/Kconfig | 14 ++
drivers/platform/arm64/Makefile | 1 +
drivers/platform/arm64/lenovo-yoga-c630.c | 290 +++++++++++++++++++++++++
include/linux/platform_data/lenovo-yoga-c630.h | 44 ++++
4 files changed, 349 insertions(+)

diff --git a/drivers/platform/arm64/Kconfig b/drivers/platform/arm64/Kconfig
index 8fdca0f8e909..8c103b3150d1 100644
--- a/drivers/platform/arm64/Kconfig
+++ b/drivers/platform/arm64/Kconfig
@@ -32,4 +32,18 @@ config EC_ACER_ASPIRE1
laptop where this information is not properly exposed via the
standard ACPI devices.

+config EC_LENOVO_YOGA_C630
+ tristate "Lenovo Yoga C630 Embedded Controller driver"
+ depends on I2C
+ help
+ Driver for the Embedded Controller in the Qualcomm Snapdragon-based
+ Lenovo Yoga C630, which provides battery and power adapter
+ information.
+
+ 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.
+
endif # ARM64_PLATFORM_DEVICES
diff --git a/drivers/platform/arm64/Makefile b/drivers/platform/arm64/Makefile
index 4fcc9855579b..b2ae9114fdd8 100644
--- a/drivers/platform/arm64/Makefile
+++ b/drivers/platform/arm64/Makefile
@@ -6,3 +6,4 @@
#

obj-$(CONFIG_EC_ACER_ASPIRE1) += acer-aspire1-ec.o
+obj-$(CONFIG_EC_LENOVO_YOGA_C630) += lenovo-yoga-c630.o
diff --git a/drivers/platform/arm64/lenovo-yoga-c630.c b/drivers/platform/arm64/lenovo-yoga-c630.c
new file mode 100644
index 000000000000..edd2ad91292f
--- /dev/null
+++ b/drivers/platform/arm64/lenovo-yoga-c630.c
@@ -0,0 +1,290 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022-2024, Linaro Ltd
+ * Authors:
+ * Bjorn Andersson
+ * Dmitry Baryshkov
+ */
+#include <linux/auxiliary_bus.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/irqreturn.h>
+#include <linux/lockdep.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/notifier.h>
+#include <linux/slab.h>
+#include <linux/platform_data/lenovo-yoga-c630.h>
+
+#define LENOVO_EC_RESPONSE_REG 0x01
+#define LENOVO_EC_REQUEST_REG 0x02
+
+#define LENOVO_EC_UCSI_WRITE 0x20
+#define LENOVO_EC_UCSI_READ 0x21
+
+#define LENOVO_EC_READ_REG 0xb0
+#define LENOVO_EC_REQUEST_NEXT_EVENT 0x84
+
+#define LENOVO_EC_UCSI_VERSION 0x20
+
+struct yoga_c630_ec {
+ struct i2c_client *client;
+ struct mutex lock;
+ struct blocking_notifier_head notifier_list;
+};
+
+static int yoga_c630_ec_request(struct yoga_c630_ec *ec, u8 *req, size_t req_len,
+ u8 *resp, size_t resp_len)
+{
+ int ret;
+
+ lockdep_assert_held(&ec->lock);
+
+ ret = i2c_smbus_write_i2c_block_data(ec->client, LENOVO_EC_REQUEST_REG,
+ req_len, req);
+ if (ret < 0)
+ return ret;
+
+ return i2c_smbus_read_i2c_block_data(ec->client, LENOVO_EC_RESPONSE_REG,
+ resp_len, resp);
+}
+
+int yoga_c630_ec_read8(struct yoga_c630_ec *ec, u8 addr)
+{
+ u8 req[2] = { LENOVO_EC_READ_REG, };
+ int ret;
+ u8 val;
+
+ guard(mutex)(&ec->lock);
+
+ req[1] = addr;
+ ret = yoga_c630_ec_request(ec, req, sizeof(req), &val, 1);
+ if (ret < 0)
+ return ret;
+
+ return val;
+}
+EXPORT_SYMBOL_GPL(yoga_c630_ec_read8);
+
+int yoga_c630_ec_read16(struct yoga_c630_ec *ec, u8 addr)
+{
+ u8 req[2] = { LENOVO_EC_READ_REG, };
+ int ret;
+ u8 msb;
+ u8 lsb;
+
+ /* don't overflow the address */
+ if (addr == 0xff)
+ return -EINVAL;
+
+ guard(mutex)(&ec->lock);
+
+ req[1] = addr;
+ ret = yoga_c630_ec_request(ec, req, sizeof(req), &lsb, 1);
+ if (ret < 0)
+ return ret;
+
+ req[1] = addr + 1;
+ ret = yoga_c630_ec_request(ec, req, sizeof(req), &msb, 1);
+ if (ret < 0)
+ return ret;
+
+ return msb << 8 | lsb;
+}
+EXPORT_SYMBOL_GPL(yoga_c630_ec_read16);
+
+u16 yoga_c630_ec_ucsi_get_version(struct yoga_c630_ec *ec)
+{
+ u8 req[3] = { 0xb3, 0xf2, };
+ int ret;
+ u8 msb;
+ u8 lsb;
+
+ guard(mutex)(&ec->lock);
+
+ req[2] = LENOVO_EC_UCSI_VERSION;
+ ret = yoga_c630_ec_request(ec, req, sizeof(req), &lsb, 1);
+ if (ret < 0)
+ return ret;
+
+ req[2] = LENOVO_EC_UCSI_VERSION + 1;
+ ret = yoga_c630_ec_request(ec, req, sizeof(req), &msb, 1);
+ if (ret < 0)
+ return ret;
+
+ return msb << 8 | lsb;
+}
+EXPORT_SYMBOL_GPL(yoga_c630_ec_ucsi_get_version);
+
+int yoga_c630_ec_ucsi_write(struct yoga_c630_ec *ec,
+ const u8 req[YOGA_C630_UCSI_WRITE_SIZE])
+{
+ int ret;
+
+ mutex_lock(&ec->lock);
+ ret = i2c_smbus_write_i2c_block_data(ec->client, LENOVO_EC_UCSI_WRITE,
+ YOGA_C630_UCSI_WRITE_SIZE, req);
+ mutex_unlock(&ec->lock);
+
+ return ret < 0 ? ret : 0;
+}
+EXPORT_SYMBOL_GPL(yoga_c630_ec_ucsi_write);
+
+int yoga_c630_ec_ucsi_read(struct yoga_c630_ec *ec,
+ u8 resp[YOGA_C630_UCSI_READ_SIZE])
+{
+ int ret;
+
+ mutex_lock(&ec->lock);
+ ret = i2c_smbus_read_i2c_block_data(ec->client, LENOVO_EC_UCSI_READ,
+ YOGA_C630_UCSI_READ_SIZE, resp);
+ mutex_unlock(&ec->lock);
+
+ return ret < 0 ? ret : 0;
+}
+EXPORT_SYMBOL_GPL(yoga_c630_ec_ucsi_read);
+
+static irqreturn_t yoga_c630_ec_thread_intr(int irq, void *data)
+{
+ u8 req[] = { LENOVO_EC_REQUEST_NEXT_EVENT };
+ struct yoga_c630_ec *ec = data;
+ u8 event;
+ int ret;
+
+ mutex_lock(&ec->lock);
+ ret = yoga_c630_ec_request(ec, req, sizeof(req), &event, 1);
+ mutex_unlock(&ec->lock);
+ if (ret < 0)
+ return IRQ_HANDLED;
+
+ blocking_notifier_call_chain(&ec->notifier_list, event, ec);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * yoga_c630_ec_register_notify - Register a notifier callback for EC events.
+ * @ec: Yoga C630 EC
+ * @nb: Notifier block pointer to register
+ *
+ * Return: 0 on success or negative error code.
+ */
+int yoga_c630_ec_register_notify(struct yoga_c630_ec *ec, struct notifier_block *nb)
+{
+ return blocking_notifier_chain_register(&ec->notifier_list, nb);
+}
+EXPORT_SYMBOL_GPL(yoga_c630_ec_register_notify);
+
+/**
+ * yoga_c630_ec_unregister_notify - Unregister notifier callback for EC events.
+ * @ec: Yoga C630 EC
+ * @nb: Notifier block pointer to unregister
+ *
+ * Unregister a notifier callback that was previously registered with
+ * yoga_c630_ec_register_notify().
+ */
+void yoga_c630_ec_unregister_notify(struct yoga_c630_ec *ec, struct notifier_block *nb)
+{
+ blocking_notifier_chain_unregister(&ec->notifier_list, nb);
+}
+EXPORT_SYMBOL_GPL(yoga_c630_ec_unregister_notify);
+
+static void yoga_c630_aux_release(struct device *dev)
+{
+ struct auxiliary_device *adev = to_auxiliary_dev(dev);
+
+ kfree(adev);
+}
+
+static void yoga_c630_aux_remove(void *data)
+{
+ struct auxiliary_device *adev = data;
+
+ auxiliary_device_delete(adev);
+ auxiliary_device_uninit(adev);
+}
+
+static int yoga_c630_aux_init(struct device *parent, const char *name,
+ struct yoga_c630_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 = yoga_c630_aux_release;
+ adev->dev.platform_data = ec;
+
+ 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, yoga_c630_aux_remove, adev);
+}
+
+static int yoga_c630_ec_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct yoga_c630_ec *ec;
+ int ret;
+
+ ec = devm_kzalloc(dev, sizeof(*ec), GFP_KERNEL);
+ if (!ec)
+ return -ENOMEM;
+
+ mutex_init(&ec->lock);
+ ec->client = client;
+ BLOCKING_INIT_NOTIFIER_HEAD(&ec->notifier_list);
+
+ ret = devm_request_threaded_irq(dev, client->irq,
+ NULL, yoga_c630_ec_thread_intr,
+ IRQF_ONESHOT, "yoga_c630_ec", ec);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "unable to request irq\n");
+
+ ret = yoga_c630_aux_init(dev, YOGA_C630_DEV_PSY, ec);
+ if (ret)
+ return ret;
+
+ return yoga_c630_aux_init(dev, YOGA_C630_DEV_UCSI, ec);
+}
+
+
+static const struct of_device_id yoga_c630_ec_of_match[] = {
+ { .compatible = "lenovo,yoga-c630-ec" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, yoga_c630_ec_of_match);
+
+static const struct i2c_device_id yoga_c630_ec_i2c_id_table[] = {
+ { "yoga-c630-ec", },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, yoga_c630_ec_i2c_id_table);
+
+static struct i2c_driver yoga_c630_ec_i2c_driver = {
+ .driver = {
+ .name = "yoga-c630-ec",
+ .of_match_table = yoga_c630_ec_of_match
+ },
+ .probe = yoga_c630_ec_probe,
+ .id_table = yoga_c630_ec_i2c_id_table,
+};
+module_i2c_driver(yoga_c630_ec_i2c_driver);
+
+MODULE_DESCRIPTION("Lenovo Yoga C630 Embedded Controller");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/platform_data/lenovo-yoga-c630.h b/include/linux/platform_data/lenovo-yoga-c630.h
new file mode 100644
index 000000000000..5d1f9fb33cfc
--- /dev/null
+++ b/include/linux/platform_data/lenovo-yoga-c630.h
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022-2024, Linaro Ltd
+ * Authors:
+ * Bjorn Andersson
+ * Dmitry Baryshkov
+ */
+
+#ifndef _LENOVO_YOGA_C630_DATA_H
+#define _LENOVO_YOGA_C630_DATA_H
+
+struct yoga_c630_ec;
+struct notifier_block;
+
+#define YOGA_C630_MOD_NAME "lenovo_yoga_c630"
+
+#define YOGA_C630_DEV_UCSI "ucsi"
+#define YOGA_C630_DEV_PSY "psy"
+
+int yoga_c630_ec_read8(struct yoga_c630_ec *ec, u8 addr);
+int yoga_c630_ec_read16(struct yoga_c630_ec *ec, u8 addr);
+
+int yoga_c630_ec_register_notify(struct yoga_c630_ec *ec, struct notifier_block *nb);
+void yoga_c630_ec_unregister_notify(struct yoga_c630_ec *ec, struct notifier_block *nb);
+
+#define YOGA_C630_UCSI_WRITE_SIZE 8
+#define YOGA_C630_UCSI_CCI_SIZE 4
+#define YOGA_C630_UCSI_DATA_SIZE 16
+#define YOGA_C630_UCSI_READ_SIZE (YOGA_C630_UCSI_CCI_SIZE + YOGA_C630_UCSI_DATA_SIZE)
+
+u16 yoga_c630_ec_ucsi_get_version(struct yoga_c630_ec *ec);
+int yoga_c630_ec_ucsi_write(struct yoga_c630_ec *ec,
+ const u8 req[YOGA_C630_UCSI_WRITE_SIZE]);
+int yoga_c630_ec_ucsi_read(struct yoga_c630_ec *ec,
+ u8 resp[YOGA_C630_UCSI_READ_SIZE]);
+
+#define LENOVO_EC_EVENT_USB 0x20
+#define LENOVO_EC_EVENT_UCSI 0x21
+#define LENOVO_EC_EVENT_HPD 0x22
+#define LENOVO_EC_EVENT_BAT_STATUS 0x24
+#define LENOVO_EC_EVENT_BAT_INFO 0x25
+#define LENOVO_EC_EVENT_BAT_ADPT_STATUS 0x37
+
+#endif

--
2.39.2


2024-06-12 10:01:18

by Dmitry Baryshkov

[permalink] [raw]
Subject: [PATCH v6 3/6] usb: typec: ucsi: add Lenovo Yoga C630 glue driver

The Lenovo Yoga C630 WOS laptop provides implements UCSI interface in
the onboard EC. Add glue driver to interface the platform's UCSI
implementation.

Reviewed-by: Bryan O'Donoghue <[email protected]>
Reviewed-by: Heikki Krogerus <[email protected]>
Signed-off-by: Dmitry Baryshkov <[email protected]>
---
drivers/usb/typec/ucsi/Kconfig | 9 ++
drivers/usb/typec/ucsi/Makefile | 1 +
drivers/usb/typec/ucsi/ucsi_yoga_c630.c | 202 ++++++++++++++++++++++++++++++++
3 files changed, 212 insertions(+)

diff --git a/drivers/usb/typec/ucsi/Kconfig b/drivers/usb/typec/ucsi/Kconfig
index bdcb1764cfae..680e1b87b152 100644
--- a/drivers/usb/typec/ucsi/Kconfig
+++ b/drivers/usb/typec/ucsi/Kconfig
@@ -69,4 +69,13 @@ config UCSI_PMIC_GLINK
To compile the driver as a module, choose M here: the module will be
called ucsi_glink.

+config UCSI_LENOVO_YOGA_C630
+ tristate "UCSI Interface Driver for Lenovo Yoga C630"
+ depends on EC_LENOVO_YOGA_C630
+ help
+ This driver enables UCSI support on the Lenovo Yoga C630 laptop.
+
+ To compile the driver as a module, choose M here: the module will be
+ called ucsi_yoga_c630.
+
endif
diff --git a/drivers/usb/typec/ucsi/Makefile b/drivers/usb/typec/ucsi/Makefile
index b4679f94696b..aed41d23887b 100644
--- a/drivers/usb/typec/ucsi/Makefile
+++ b/drivers/usb/typec/ucsi/Makefile
@@ -21,3 +21,4 @@ obj-$(CONFIG_UCSI_ACPI) += ucsi_acpi.o
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
diff --git a/drivers/usb/typec/ucsi/ucsi_yoga_c630.c b/drivers/usb/typec/ucsi/ucsi_yoga_c630.c
new file mode 100644
index 000000000000..194b49291f28
--- /dev/null
+++ b/drivers/usb/typec/ucsi/ucsi_yoga_c630.c
@@ -0,0 +1,202 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022-2024, Linaro Ltd
+ * Authors:
+ * Bjorn Andersson
+ * Dmitry Baryshkov
+ */
+#include <linux/auxiliary_bus.h>
+#include <linux/bitops.h>
+#include <linux/completion.h>
+#include <linux/container_of.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/string.h>
+#include <linux/platform_data/lenovo-yoga-c630.h>
+
+#include "ucsi.h"
+
+struct yoga_c630_ucsi {
+ struct yoga_c630_ec *ec;
+ struct ucsi *ucsi;
+ struct notifier_block nb;
+ struct completion complete;
+ unsigned long flags;
+#define UCSI_C630_COMMAND_PENDING 0
+#define UCSI_C630_ACK_PENDING 1
+ u16 version;
+};
+
+static int yoga_c630_ucsi_read(struct ucsi *ucsi, unsigned int offset,
+ void *val, size_t val_len)
+{
+ struct yoga_c630_ucsi *uec = ucsi_get_drvdata(ucsi);
+ u8 buf[YOGA_C630_UCSI_READ_SIZE];
+ int ret;
+
+ ret = yoga_c630_ec_ucsi_read(uec->ec, buf);
+ if (ret)
+ return ret;
+
+ if (offset == UCSI_VERSION) {
+ memcpy(val, &uec->version, min(val_len, sizeof(uec->version)));
+ return 0;
+ }
+
+ if (offset == UCSI_CCI)
+ memcpy(val, buf, min(val_len, YOGA_C630_UCSI_CCI_SIZE));
+ else if (offset == UCSI_MESSAGE_IN)
+ memcpy(val, buf + YOGA_C630_UCSI_CCI_SIZE,
+ min(val_len, YOGA_C630_UCSI_DATA_SIZE));
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+static int yoga_c630_ucsi_async_write(struct ucsi *ucsi, unsigned int offset,
+ const void *val, size_t val_len)
+{
+ struct yoga_c630_ucsi *uec = ucsi_get_drvdata(ucsi);
+
+ if (offset != UCSI_CONTROL ||
+ val_len != YOGA_C630_UCSI_WRITE_SIZE)
+ return -EINVAL;
+
+ return yoga_c630_ec_ucsi_write(uec->ec, val);
+}
+
+static int yoga_c630_ucsi_sync_write(struct ucsi *ucsi, unsigned int offset,
+ const void *val, size_t val_len)
+{
+ struct yoga_c630_ucsi *uec = ucsi_get_drvdata(ucsi);
+ bool ack = UCSI_COMMAND(*(u64 *)val) == UCSI_ACK_CC_CI;
+ int ret;
+
+ if (ack)
+ set_bit(UCSI_C630_ACK_PENDING, &uec->flags);
+ else
+ set_bit(UCSI_C630_COMMAND_PENDING, &uec->flags);
+
+ reinit_completion(&uec->complete);
+
+ ret = yoga_c630_ucsi_async_write(ucsi, offset, val, val_len);
+ if (ret)
+ goto out_clear_bit;
+
+ if (!wait_for_completion_timeout(&uec->complete, 5 * HZ))
+ ret = -ETIMEDOUT;
+
+out_clear_bit:
+ if (ack)
+ clear_bit(UCSI_C630_ACK_PENDING, &uec->flags);
+ else
+ clear_bit(UCSI_C630_COMMAND_PENDING, &uec->flags);
+
+ return ret;
+}
+
+const struct ucsi_operations yoga_c630_ucsi_ops = {
+ .read = yoga_c630_ucsi_read,
+ .sync_write = yoga_c630_ucsi_sync_write,
+ .async_write = yoga_c630_ucsi_async_write,
+};
+
+static void yoga_c630_ucsi_notify_ucsi(struct yoga_c630_ucsi *uec, u32 cci)
+{
+ if (UCSI_CCI_CONNECTOR(cci))
+ ucsi_connector_change(uec->ucsi, UCSI_CCI_CONNECTOR(cci));
+
+ if (cci & UCSI_CCI_ACK_COMPLETE &&
+ test_bit(UCSI_C630_ACK_PENDING, &uec->flags))
+ complete(&uec->complete);
+
+ if (cci & UCSI_CCI_COMMAND_COMPLETE &&
+ test_bit(UCSI_C630_COMMAND_PENDING, &uec->flags))
+ complete(&uec->complete);
+}
+
+static int yoga_c630_ucsi_notify(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct yoga_c630_ucsi *uec = container_of(nb, struct yoga_c630_ucsi, nb);
+ u32 cci;
+ int ret;
+
+ switch (action) {
+ case LENOVO_EC_EVENT_USB:
+ case LENOVO_EC_EVENT_HPD:
+ ucsi_connector_change(uec->ucsi, 1);
+ return NOTIFY_OK;
+
+ case LENOVO_EC_EVENT_UCSI:
+ ret = uec->ucsi->ops->read(uec->ucsi, UCSI_CCI, &cci, sizeof(cci));
+ if (ret)
+ return NOTIFY_DONE;
+
+ yoga_c630_ucsi_notify_ucsi(uec, cci);
+
+ return NOTIFY_OK;
+
+ default:
+ return NOTIFY_DONE;
+ }
+}
+
+static int yoga_c630_ucsi_probe(struct auxiliary_device *adev,
+ const struct auxiliary_device_id *id)
+{
+ struct yoga_c630_ec *ec = adev->dev.platform_data;
+ struct yoga_c630_ucsi *uec;
+ int ret;
+
+ uec = devm_kzalloc(&adev->dev, sizeof(*uec), GFP_KERNEL);
+ if (!uec)
+ return -ENOMEM;
+
+ uec->ec = ec;
+ init_completion(&uec->complete);
+ uec->nb.notifier_call = yoga_c630_ucsi_notify;
+
+ uec->ucsi = ucsi_create(&adev->dev, &yoga_c630_ucsi_ops);
+ if (IS_ERR(uec->ucsi))
+ return PTR_ERR(uec->ucsi);
+
+ ucsi_set_drvdata(uec->ucsi, uec);
+
+ uec->version = yoga_c630_ec_ucsi_get_version(uec->ec);
+
+ auxiliary_set_drvdata(adev, uec);
+
+ ret = yoga_c630_ec_register_notify(ec, &uec->nb);
+ if (ret)
+ return ret;
+
+ return ucsi_register(uec->ucsi);
+}
+
+static void yoga_c630_ucsi_remove(struct auxiliary_device *adev)
+{
+ struct yoga_c630_ucsi *uec = auxiliary_get_drvdata(adev);
+
+ yoga_c630_ec_unregister_notify(uec->ec, &uec->nb);
+ ucsi_unregister(uec->ucsi);
+}
+
+static const struct auxiliary_device_id yoga_c630_ucsi_id_table[] = {
+ { .name = YOGA_C630_MOD_NAME "." YOGA_C630_DEV_UCSI, },
+ {}
+};
+MODULE_DEVICE_TABLE(auxiliary, yoga_c630_ucsi_id_table);
+
+static struct auxiliary_driver yoga_c630_ucsi_driver = {
+ .name = YOGA_C630_DEV_UCSI,
+ .id_table = yoga_c630_ucsi_id_table,
+ .probe = yoga_c630_ucsi_probe,
+ .remove = yoga_c630_ucsi_remove,
+};
+
+module_auxiliary_driver(yoga_c630_ucsi_driver);
+
+MODULE_DESCRIPTION("Lenovo Yoga C630 UCSI");
+MODULE_LICENSE("GPL");

--
2.39.2


2024-06-12 10:01:26

by Dmitry Baryshkov

[permalink] [raw]
Subject: [PATCH v6 4/6] power: supply: lenovo_yoga_c630_battery: add Lenovo C630 driver

On the Lenovo Yoga C630 WOS laptop the EC provides access to the adapter
and battery status. Add the driver to read power supply status on the
laptop.

Signed-off-by: Dmitry Baryshkov <[email protected]>
---
drivers/power/supply/Kconfig | 9 +
drivers/power/supply/Makefile | 1 +
drivers/power/supply/lenovo_yoga_c630_battery.c | 500 ++++++++++++++++++++++++
3 files changed, 510 insertions(+)

diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index 3e31375491d5..55ab8e90747d 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -167,6 +167,15 @@ config BATTERY_LEGO_EV3
help
Say Y here to enable support for the LEGO MINDSTORMS EV3 battery.

+config BATTERY_LENOVO_YOGA_C630
+ tristate "Lenovo Yoga C630 battery"
+ depends on OF && EC_LENOVO_YOGA_C630
+ help
+ This driver enables battery support on the Lenovo Yoga C630 laptop.
+
+ To compile the driver as a module, choose M here: the module will be
+ called lenovo_yoga_c630_battery.
+
config BATTERY_PMU
tristate "Apple PMU battery"
depends on PPC32 && ADB_PMU
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index 58b567278034..8ebbdcf92dac 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -32,6 +32,7 @@ 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_LEGO_EV3) += lego_ev3_battery.o
+obj-$(CONFIG_BATTERY_LENOVO_YOGA_C630) += lenovo_yoga_c630_battery.o
obj-$(CONFIG_BATTERY_PMU) += pmu_battery.o
obj-$(CONFIG_BATTERY_QCOM_BATTMGR) += qcom_battmgr.o
obj-$(CONFIG_BATTERY_OLPC) += olpc_battery.o
diff --git a/drivers/power/supply/lenovo_yoga_c630_battery.c b/drivers/power/supply/lenovo_yoga_c630_battery.c
new file mode 100644
index 000000000000..0013994a22a0
--- /dev/null
+++ b/drivers/power/supply/lenovo_yoga_c630_battery.c
@@ -0,0 +1,500 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022-2024, Linaro Ltd
+ * Authors:
+ * Bjorn Andersson
+ * Dmitry Baryshkov
+ */
+#include <linux/auxiliary_bus.h>
+#include <linux/bits.h>
+#include <linux/delay.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/notifier.h>
+#include <linux/power_supply.h>
+#include <linux/platform_data/lenovo-yoga-c630.h>
+
+struct yoga_c630_psy {
+ struct yoga_c630_ec *ec;
+ struct device *dev;
+ struct fwnode_handle *fwnode;
+ struct notifier_block nb;
+
+ /* guards all battery properties and registration of power supplies */
+ struct mutex lock;
+
+ struct power_supply *adp_psy;
+ struct power_supply *bat_psy;
+
+ unsigned long last_status_update;
+
+ bool adapter_online;
+
+ bool unit_mA;
+
+ bool bat_present;
+ unsigned int bat_status;
+ unsigned int design_capacity;
+ unsigned int design_voltage;
+ unsigned int full_charge_capacity;
+
+ unsigned int capacity_now;
+ unsigned int voltage_now;
+
+ int current_now;
+ int rate_now;
+};
+
+#define LENOVO_EC_CACHE_TIME (10 * HZ)
+
+#define LENOVO_EC_ADPT_STATUS 0xa3
+#define LENOVO_EC_ADPT_STATUS_PRESENT BIT(7)
+#define LENOVO_EC_BAT_ATTRIBUTES 0xc0
+#define LENOVO_EC_BAT_ATTRIBUTES_UNIT_IS_MA BIT(1)
+#define LENOVO_EC_BAT_STATUS 0xc1
+#define LENOVO_EC_BAT_STATUS_DISCHARGING BIT(0)
+#define LENOVO_EC_BAT_STATUS_CHARGING BIT(1)
+#define LENOVO_EC_BAT_REMAIN_CAPACITY 0xc2
+#define LENOVO_EC_BAT_VOLTAGE 0xc6
+#define LENOVO_EC_BAT_DESIGN_VOLTAGE 0xc8
+#define LENOVO_EC_BAT_DESIGN_CAPACITY 0xca
+#define LENOVO_EC_BAT_FULL_CAPACITY 0xcc
+#define LENOVO_EC_BAT_CURRENT 0xd2
+#define LENOVO_EC_BAT_FULL_FACTORY 0xd6
+#define LENOVO_EC_BAT_PRESENT 0xda
+#define LENOVO_EC_BAT_PRESENT_IS_PRESENT BIT(0)
+#define LENOVO_EC_BAT_FULL_REGISTER 0xdb
+#define LENOVO_EC_BAT_FULL_REGISTER_IS_FACTORY BIT(0)
+
+/* the mutex should already be locked */
+static int yoga_c630_psy_update_bat_info(struct yoga_c630_psy *ecbat)
+{
+ struct yoga_c630_ec *ec = ecbat->ec;
+ int val;
+
+ val = yoga_c630_ec_read8(ec, LENOVO_EC_BAT_PRESENT);
+ if (val < 0)
+ return val;
+ ecbat->bat_present = !!(val & LENOVO_EC_BAT_PRESENT_IS_PRESENT);
+ if (!ecbat->bat_present)
+ return val;
+
+ val = yoga_c630_ec_read8(ec, LENOVO_EC_BAT_ATTRIBUTES);
+ if (val < 0)
+ return val;
+ ecbat->unit_mA = val & LENOVO_EC_BAT_ATTRIBUTES_UNIT_IS_MA;
+
+ val = yoga_c630_ec_read16(ec, LENOVO_EC_BAT_DESIGN_CAPACITY);
+ if (val < 0)
+ return val;
+ ecbat->design_capacity = val * 1000;
+
+ /*
+ * DSDT has delays after most of EC reads in these methods.
+ * Having no documentation for the EC we have to follow and sleep here.
+ */
+ msleep(50);
+
+ val = yoga_c630_ec_read16(ec, LENOVO_EC_BAT_DESIGN_VOLTAGE);
+ if (val < 0)
+ return val;
+ ecbat->design_voltage = val;
+
+ msleep(50);
+
+ val = yoga_c630_ec_read8(ec, LENOVO_EC_BAT_FULL_REGISTER);
+ if (val < 0)
+ return val;
+ val = yoga_c630_ec_read16(ec,
+ val & LENOVO_EC_BAT_FULL_REGISTER_IS_FACTORY ?
+ LENOVO_EC_BAT_FULL_FACTORY :
+ LENOVO_EC_BAT_FULL_CAPACITY);
+ if (val < 0)
+ return val;
+
+ ecbat->full_charge_capacity = val * 1000;
+
+ if (!ecbat->unit_mA) {
+ ecbat->design_capacity *= 10;
+ ecbat->full_charge_capacity *= 10;
+ }
+
+ return 0;
+}
+
+static int yoga_c630_psy_maybe_update_bat_status(struct yoga_c630_psy *ecbat)
+{
+ struct yoga_c630_ec *ec = ecbat->ec;
+ int current_mA;
+ int val;
+
+ scoped_guard(mutex, &ecbat->lock) {
+ if (time_before(jiffies, ecbat->last_status_update + LENOVO_EC_CACHE_TIME))
+ return 0;
+
+ val = yoga_c630_ec_read8(ec, LENOVO_EC_BAT_STATUS);
+ if (val < 0)
+ return val;
+ ecbat->bat_status = val;
+
+ msleep(50);
+
+ val = yoga_c630_ec_read16(ec, LENOVO_EC_BAT_REMAIN_CAPACITY);
+ if (val < 0)
+ return val;
+ ecbat->capacity_now = val * 1000;
+
+ msleep(50);
+
+ val = yoga_c630_ec_read16(ec, LENOVO_EC_BAT_VOLTAGE);
+ if (val < 0)
+ return val;
+ ecbat->voltage_now = val * 1000;
+
+ msleep(50);
+
+ val = yoga_c630_ec_read16(ec, LENOVO_EC_BAT_CURRENT);
+ if (val < 0)
+ return val;
+ current_mA = sign_extend32(val, 15);
+ ecbat->current_now = current_mA * 1000;
+ ecbat->rate_now = current_mA * (ecbat->voltage_now / 1000);
+
+ msleep(50);
+
+ if (!ecbat->unit_mA)
+ ecbat->capacity_now *= 10;
+
+ ecbat->last_status_update = jiffies;
+ }
+
+ return 0;
+}
+
+static int yoga_c630_psy_update_adapter_status(struct yoga_c630_psy *ecbat)
+{
+ struct yoga_c630_ec *ec = ecbat->ec;
+ int val;
+
+ scoped_guard(mutex, &ecbat->lock) {
+ val = yoga_c630_ec_read8(ec, LENOVO_EC_ADPT_STATUS);
+ if (val < 0)
+ return val;
+
+ ecbat->adapter_online = !!(val & LENOVO_EC_ADPT_STATUS_PRESENT);
+ }
+
+ return 0;
+}
+
+static bool yoga_c630_psy_is_charged(struct yoga_c630_psy *ecbat)
+{
+ if (ecbat->bat_status != 0)
+ return false;
+
+ if (ecbat->full_charge_capacity <= ecbat->capacity_now)
+ return true;
+
+ if (ecbat->design_capacity <= ecbat->capacity_now)
+ return true;
+
+ return false;
+}
+
+static int yoga_c630_psy_bat_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct yoga_c630_psy *ecbat = power_supply_get_drvdata(psy);
+ int rc = 0;
+
+ if (!ecbat->bat_present && psp != POWER_SUPPLY_PROP_PRESENT)
+ return -ENODEV;
+
+ rc = yoga_c630_psy_maybe_update_bat_status(ecbat);
+ if (rc)
+ return rc;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ if (ecbat->bat_status & LENOVO_EC_BAT_STATUS_DISCHARGING)
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ else if (ecbat->bat_status & LENOVO_EC_BAT_STATUS_CHARGING)
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ else if (yoga_c630_psy_is_charged(ecbat))
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ else
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = ecbat->bat_present;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ val->intval = ecbat->design_voltage;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN:
+ val->intval = ecbat->design_capacity;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ case POWER_SUPPLY_PROP_ENERGY_FULL:
+ val->intval = ecbat->full_charge_capacity;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ case POWER_SUPPLY_PROP_ENERGY_NOW:
+ val->intval = ecbat->capacity_now;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ val->intval = ecbat->current_now;
+ break;
+ case POWER_SUPPLY_PROP_POWER_NOW:
+ val->intval = ecbat->rate_now;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = ecbat->voltage_now;
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = "PABAS0241231";
+ break;
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = "Compal";
+ break;
+ case POWER_SUPPLY_PROP_SCOPE:
+ val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
+ break;
+ default:
+ rc = -EINVAL;
+ break;
+ }
+
+ return rc;
+}
+
+static enum power_supply_property yoga_c630_psy_bat_mA_properties[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_POWER_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+ POWER_SUPPLY_PROP_SCOPE,
+};
+
+static enum power_supply_property yoga_c630_psy_bat_mWh_properties[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN,
+ POWER_SUPPLY_PROP_ENERGY_FULL,
+ POWER_SUPPLY_PROP_ENERGY_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_POWER_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+ POWER_SUPPLY_PROP_SCOPE,
+};
+
+static const struct power_supply_desc yoga_c630_psy_bat_psy_desc_mA = {
+ .name = "yoga-c630-battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = yoga_c630_psy_bat_mA_properties,
+ .num_properties = ARRAY_SIZE(yoga_c630_psy_bat_mA_properties),
+ .get_property = yoga_c630_psy_bat_get_property,
+};
+
+static const struct power_supply_desc yoga_c630_psy_bat_psy_desc_mWh = {
+ .name = "yoga-c630-battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = yoga_c630_psy_bat_mWh_properties,
+ .num_properties = ARRAY_SIZE(yoga_c630_psy_bat_mWh_properties),
+ .get_property = yoga_c630_psy_bat_get_property,
+};
+
+static int yoga_c630_psy_adpt_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct yoga_c630_psy *ecbat = power_supply_get_drvdata(psy);
+ int ret = 0;
+
+ ret = yoga_c630_psy_update_adapter_status(ecbat);
+ if (ret < 0)
+ return ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = ecbat->adapter_online;
+ break;
+ case POWER_SUPPLY_PROP_USB_TYPE:
+ val->intval = POWER_SUPPLY_USB_TYPE_C;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static enum power_supply_property yoga_c630_psy_adpt_properties[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_USB_TYPE,
+};
+
+static const enum power_supply_usb_type yoga_c630_psy_adpt_usb_type[] = {
+ POWER_SUPPLY_USB_TYPE_C,
+};
+
+static const struct power_supply_desc yoga_c630_psy_adpt_psy_desc = {
+ .name = "yoga-c630-adapter",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .usb_types = yoga_c630_psy_adpt_usb_type,
+ .num_usb_types = ARRAY_SIZE(yoga_c630_psy_adpt_usb_type),
+ .properties = yoga_c630_psy_adpt_properties,
+ .num_properties = ARRAY_SIZE(yoga_c630_psy_adpt_properties),
+ .get_property = yoga_c630_psy_adpt_get_property,
+};
+
+static int yoga_c630_psy_register_bat_psy(struct yoga_c630_psy *ecbat)
+{
+ struct power_supply_config bat_cfg = {};
+
+ bat_cfg.drv_data = ecbat;
+ bat_cfg.fwnode = ecbat->fwnode;
+ ecbat->bat_psy = power_supply_register_no_ws(ecbat->dev,
+ ecbat->unit_mA ?
+ &yoga_c630_psy_bat_psy_desc_mA :
+ &yoga_c630_psy_bat_psy_desc_mWh,
+ &bat_cfg);
+ if (IS_ERR(ecbat->bat_psy)) {
+ dev_err(ecbat->dev, "failed to register battery supply\n");
+ return PTR_ERR(ecbat->bat_psy);
+ }
+
+ return 0;
+}
+
+static void yoga_c630_ec_refresh_bat_info(struct yoga_c630_psy *ecbat)
+{
+ bool current_unit;
+
+ scoped_guard(mutex, &ecbat->lock) {
+ current_unit = ecbat->unit_mA;
+
+ yoga_c630_psy_update_bat_info(ecbat);
+
+ if (current_unit != ecbat->unit_mA) {
+ power_supply_unregister(ecbat->bat_psy);
+ yoga_c630_psy_register_bat_psy(ecbat);
+ }
+ }
+}
+
+static int yoga_c630_psy_notify(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct yoga_c630_psy *ecbat = container_of(nb, struct yoga_c630_psy, nb);
+
+ switch (action) {
+ case LENOVO_EC_EVENT_BAT_INFO:
+ yoga_c630_ec_refresh_bat_info(ecbat);
+ break;
+ case LENOVO_EC_EVENT_BAT_ADPT_STATUS:
+ power_supply_changed(ecbat->adp_psy);
+ fallthrough;
+ case LENOVO_EC_EVENT_BAT_STATUS:
+ power_supply_changed(ecbat->bat_psy);
+ break;
+ }
+
+ return NOTIFY_OK;
+}
+
+static int yoga_c630_psy_probe(struct auxiliary_device *adev,
+ const struct auxiliary_device_id *id)
+{
+ struct yoga_c630_ec *ec = adev->dev.platform_data;
+ struct power_supply_config adp_cfg = {};
+ struct device *dev = &adev->dev;
+ struct yoga_c630_psy *ecbat;
+ int ret;
+
+ ecbat = devm_kzalloc(&adev->dev, sizeof(*ecbat), GFP_KERNEL);
+ if (!ecbat)
+ return -ENOMEM;
+
+ ecbat->ec = ec;
+ ecbat->dev = dev;
+ mutex_init(&ecbat->lock);
+ ecbat->fwnode = adev->dev.parent->fwnode;
+ ecbat->nb.notifier_call = yoga_c630_psy_notify;
+
+ auxiliary_set_drvdata(adev, ecbat);
+
+ adp_cfg.drv_data = ecbat;
+ adp_cfg.fwnode = ecbat->fwnode;
+ adp_cfg.supplied_to = (char **)&yoga_c630_psy_bat_psy_desc_mA.name;
+ adp_cfg.num_supplicants = 1;
+ ecbat->adp_psy = devm_power_supply_register_no_ws(dev, &yoga_c630_psy_adpt_psy_desc, &adp_cfg);
+ if (IS_ERR(ecbat->adp_psy)) {
+ dev_err(dev, "failed to register AC adapter supply\n");
+ return PTR_ERR(ecbat->adp_psy);
+ }
+
+ scoped_guard(mutex, &ecbat->lock) {
+ ret = yoga_c630_psy_update_bat_info(ecbat);
+ if (ret)
+ goto err_unreg_bat;
+
+ ret = yoga_c630_psy_register_bat_psy(ecbat);
+ if (ret)
+ goto err_unreg_bat;
+ }
+
+ ret = yoga_c630_ec_register_notify(ecbat->ec, &ecbat->nb);
+ if (ret)
+ goto err_unreg_bat;
+
+ return 0;
+
+err_unreg_bat:
+ power_supply_unregister(ecbat->bat_psy);
+ return ret;
+}
+
+static void yoga_c630_psy_remove(struct auxiliary_device *adev)
+{
+ struct yoga_c630_psy *ecbat = auxiliary_get_drvdata(adev);
+
+ yoga_c630_ec_unregister_notify(ecbat->ec, &ecbat->nb);
+ power_supply_unregister(ecbat->bat_psy);
+}
+
+static const struct auxiliary_device_id yoga_c630_psy_id_table[] = {
+ { .name = YOGA_C630_MOD_NAME "." YOGA_C630_DEV_PSY, },
+ {}
+};
+MODULE_DEVICE_TABLE(auxiliary, yoga_c630_psy_id_table);
+
+static struct auxiliary_driver yoga_c630_psy_driver = {
+ .name = YOGA_C630_DEV_PSY,
+ .id_table = yoga_c630_psy_id_table,
+ .probe = yoga_c630_psy_probe,
+ .remove = yoga_c630_psy_remove,
+};
+
+module_auxiliary_driver(yoga_c630_psy_driver);
+
+MODULE_DESCRIPTION("Lenovo Yoga C630 psy");
+MODULE_LICENSE("GPL");

--
2.39.2


2024-06-12 10:01:44

by Dmitry Baryshkov

[permalink] [raw]
Subject: [PATCH v6 5/6] arm64: dts: qcom: sdm845: describe connections of USB/DP port

Describe links between the first USB3 host and the DisplayPort that is
routed to the same pins.

Reviewed-by: Bryan O'Donoghue <[email protected]>
Signed-off-by: Dmitry Baryshkov <[email protected]>
---
arch/arm64/boot/dts/qcom/sdm845.dtsi | 53 +++++++++++++++++++++++++++++++++++-
1 file changed, 52 insertions(+), 1 deletion(-)

diff --git a/arch/arm64/boot/dts/qcom/sdm845.dtsi b/arch/arm64/boot/dts/qcom/sdm845.dtsi
index a0125f3d92b2..23b101bb3842 100644
--- a/arch/arm64/boot/dts/qcom/sdm845.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm845.dtsi
@@ -4031,6 +4031,35 @@ usb_1_qmpphy: phy@88e8000 {

#clock-cells = <1>;
#phy-cells = <1>;
+ orientation-switch;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+
+ usb_1_qmpphy_out: endpoint {
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+
+ usb_1_qmpphy_usb_ss_in: endpoint {
+ remote-endpoint = <&usb_1_dwc3_ss>;
+ };
+ };
+
+ port@2 {
+ reg = <2>;
+
+ usb_1_qmpphy_dp_in: endpoint {
+ remote-endpoint = <&dp_out>;
+ };
+ };
+ };
};

usb_2_qmpphy: phy@88eb000 {
@@ -4111,6 +4140,26 @@ usb_1_dwc3: usb@a600000 {
snps,dis_enblslpm_quirk;
phys = <&usb_1_hsphy>, <&usb_1_qmpphy QMP_USB43DP_USB3_PHY>;
phy-names = "usb2-phy", "usb3-phy";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+
+ usb_1_dwc3_hs: endpoint {
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+
+ usb_1_dwc3_ss: endpoint {
+ remote-endpoint = <&usb_1_qmpphy_usb_ss_in>;
+ };
+ };
+ };
};
};

@@ -4601,7 +4650,9 @@ dp_in: endpoint {

port@1 {
reg = <1>;
- dp_out: endpoint { };
+ dp_out: endpoint {
+ remote-endpoint = <&usb_1_qmpphy_dp_in>;
+ };
};
};


--
2.39.2


2024-06-12 10:01:45

by Dmitry Baryshkov

[permalink] [raw]
Subject: [PATCH v6 6/6] arm64: dts: qcom: c630: Add Embedded Controller node

From: Bjorn Andersson <[email protected]>

The Embedded Controller in the Lenovo Yoga C630 is accessible on &i2c1
and provides battery and adapter status, as well as altmode
notifications for the second USB Type-C port.

Add a definition for the EC.

Signed-off-by: Bjorn Andersson <[email protected]>
Signed-off-by: Dmitry Baryshkov <[email protected]>
---
.../boot/dts/qcom/sdm850-lenovo-yoga-c630.dts | 75 ++++++++++++++++++++++
1 file changed, 75 insertions(+)

diff --git a/arch/arm64/boot/dts/qcom/sdm850-lenovo-yoga-c630.dts b/arch/arm64/boot/dts/qcom/sdm850-lenovo-yoga-c630.dts
index 8402ea2d93a7..f18050848cd8 100644
--- a/arch/arm64/boot/dts/qcom/sdm850-lenovo-yoga-c630.dts
+++ b/arch/arm64/boot/dts/qcom/sdm850-lenovo-yoga-c630.dts
@@ -370,6 +370,66 @@ zap-shader {
&i2c1 {
status = "okay";
clock-frequency = <400000>;
+
+ embedded-controller@70 {
+ compatible = "lenovo,yoga-c630-ec";
+ reg = <0x70>;
+
+ interrupts-extended = <&tlmm 20 IRQ_TYPE_LEVEL_HIGH>;
+
+ pinctrl-names = "default";
+ pinctrl-0 = <&ec_int_state>;
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ connector@0 {
+ compatible = "usb-c-connector";
+ reg = <0>;
+ power-role = "dual";
+ data-role = "host";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+
+ ucsi0_hs_in: endpoint {
+ remote-endpoint = <&usb_1_dwc3_hs>;
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+
+ ucsi0_ss_in: endpoint {
+ remote-endpoint = <&usb_1_qmpphy_out>;
+ };
+ };
+
+ port@2 {
+ reg = <2>;
+
+ ucsi0_sbu: endpoint {
+ };
+ };
+ };
+ };
+
+ connector@1 {
+ compatible = "usb-c-connector";
+ reg = <1>;
+ power-role = "dual";
+ data-role = "host";
+
+ /*
+ * connected to the onboard USB hub, orientation is
+ * handled by the controller
+ */
+ };
+ };
};

&i2c3 {
@@ -695,6 +755,13 @@ mode_pin_active: mode-pin-state {

bias-disable;
};
+
+ ec_int_state: ec-int-state {
+ pins = "gpio20";
+ function = "gpio";
+
+ bias-disable;
+ };
};

&uart6 {
@@ -742,6 +809,10 @@ &usb_1_dwc3 {
dr_mode = "host";
};

+&usb_1_dwc3_hs {
+ remote-endpoint = <&ucsi0_hs_in>;
+};
+
&usb_1_hsphy {
status = "okay";

@@ -762,6 +833,10 @@ &usb_1_qmpphy {
vdda-pll-supply = <&vdda_usb1_ss_core>;
};

+&usb_1_qmpphy_out {
+ remote-endpoint = <&ucsi0_ss_in>;
+};
+
&usb_2 {
status = "okay";
};

--
2.39.2


2024-06-13 08:00:10

by Ilpo Järvinen

[permalink] [raw]
Subject: Re: [PATCH v6 4/6] power: supply: lenovo_yoga_c630_battery: add Lenovo C630 driver

On Wed, 12 Jun 2024, Dmitry Baryshkov wrote:

> On the Lenovo Yoga C630 WOS laptop the EC provides access to the adapter
> and battery status. Add the driver to read power supply status on the
> laptop.
>
> Signed-off-by: Dmitry Baryshkov <[email protected]>
> ---

> +/* the mutex should already be locked */

Enforce this with lockdep_assert_held() (and remove the comment).

> +static int yoga_c630_psy_update_bat_info(struct yoga_c630_psy *ecbat)
> +{
> + struct yoga_c630_ec *ec = ecbat->ec;
> + int val;
> +
> + val = yoga_c630_ec_read8(ec, LENOVO_EC_BAT_PRESENT);
> + if (val < 0)
> + return val;
> + ecbat->bat_present = !!(val & LENOVO_EC_BAT_PRESENT_IS_PRESENT);
> + if (!ecbat->bat_present)
> + return val;
> +
> + val = yoga_c630_ec_read8(ec, LENOVO_EC_BAT_ATTRIBUTES);
> + if (val < 0)
> + return val;
> + ecbat->unit_mA = val & LENOVO_EC_BAT_ATTRIBUTES_UNIT_IS_MA;
> +
> + val = yoga_c630_ec_read16(ec, LENOVO_EC_BAT_DESIGN_CAPACITY);
> + if (val < 0)
> + return val;
> + ecbat->design_capacity = val * 1000;
> +
> + /*
> + * DSDT has delays after most of EC reads in these methods.
> + * Having no documentation for the EC we have to follow and sleep here.
> + */
> + msleep(50);
> +
> + val = yoga_c630_ec_read16(ec, LENOVO_EC_BAT_DESIGN_VOLTAGE);
> + if (val < 0)
> + return val;
> + ecbat->design_voltage = val;
> +
> + msleep(50);
> +
> + val = yoga_c630_ec_read8(ec, LENOVO_EC_BAT_FULL_REGISTER);
> + if (val < 0)
> + return val;
> + val = yoga_c630_ec_read16(ec,
> + val & LENOVO_EC_BAT_FULL_REGISTER_IS_FACTORY ?
> + LENOVO_EC_BAT_FULL_FACTORY :
> + LENOVO_EC_BAT_FULL_CAPACITY);
> + if (val < 0)
> + return val;
> +
> + ecbat->full_charge_capacity = val * 1000;
> +
> + if (!ecbat->unit_mA) {
> + ecbat->design_capacity *= 10;
> + ecbat->full_charge_capacity *= 10;
> + }
> +
> + return 0;
> +}
> +
> +static int yoga_c630_psy_maybe_update_bat_status(struct yoga_c630_psy *ecbat)
> +{
> + struct yoga_c630_ec *ec = ecbat->ec;
> + int current_mA;
> + int val;
> +
> + scoped_guard(mutex, &ecbat->lock) {

This too could be simply guard() to bring down the indentation level.

> + if (time_before(jiffies, ecbat->last_status_update + LENOVO_EC_CACHE_TIME))
> + return 0;
> +
> + val = yoga_c630_ec_read8(ec, LENOVO_EC_BAT_STATUS);
> + if (val < 0)
> + return val;
> + ecbat->bat_status = val;
> +
> + msleep(50);
> +
> + val = yoga_c630_ec_read16(ec, LENOVO_EC_BAT_REMAIN_CAPACITY);
> + if (val < 0)
> + return val;
> + ecbat->capacity_now = val * 1000;
> +
> + msleep(50);
> +
> + val = yoga_c630_ec_read16(ec, LENOVO_EC_BAT_VOLTAGE);
> + if (val < 0)
> + return val;
> + ecbat->voltage_now = val * 1000;
> +
> + msleep(50);
> +
> + val = yoga_c630_ec_read16(ec, LENOVO_EC_BAT_CURRENT);
> + if (val < 0)
> + return val;
> + current_mA = sign_extend32(val, 15);
> + ecbat->current_now = current_mA * 1000;
> + ecbat->rate_now = current_mA * (ecbat->voltage_now / 1000);
> +
> + msleep(50);
> +
> + if (!ecbat->unit_mA)
> + ecbat->capacity_now *= 10;
> +
> + ecbat->last_status_update = jiffies;
> + }
> +
> + return 0;
> +}
> +
> +static int yoga_c630_psy_update_adapter_status(struct yoga_c630_psy *ecbat)
> +{
> + struct yoga_c630_ec *ec = ecbat->ec;
> + int val;
> +
> + scoped_guard(mutex, &ecbat->lock) {

Ditto.

> + val = yoga_c630_ec_read8(ec, LENOVO_EC_ADPT_STATUS);
> + if (val < 0)
> + return val;
> +
> + ecbat->adapter_online = !!(val & LENOVO_EC_ADPT_STATUS_PRESENT);
> + }
> +
> + return 0;
> +}


> +static const struct power_supply_desc yoga_c630_psy_bat_psy_desc_mA = {
> + .name = "yoga-c630-battery",
> + .type = POWER_SUPPLY_TYPE_BATTERY,
> + .properties = yoga_c630_psy_bat_mA_properties,
> + .num_properties = ARRAY_SIZE(yoga_c630_psy_bat_mA_properties),
> + .get_property = yoga_c630_psy_bat_get_property,
> +};
> +
> +static const struct power_supply_desc yoga_c630_psy_bat_psy_desc_mWh = {
> + .name = "yoga-c630-battery",
> + .type = POWER_SUPPLY_TYPE_BATTERY,
> + .properties = yoga_c630_psy_bat_mWh_properties,
> + .num_properties = ARRAY_SIZE(yoga_c630_psy_bat_mWh_properties),
> + .get_property = yoga_c630_psy_bat_get_property,
> +};
> +
> +static int yoga_c630_psy_adpt_get_property(struct power_supply *psy,
> + enum power_supply_property psp,
> + union power_supply_propval *val)
> +{
> + struct yoga_c630_psy *ecbat = power_supply_get_drvdata(psy);
> + int ret = 0;
> +
> + ret = yoga_c630_psy_update_adapter_status(ecbat);
> + if (ret < 0)
> + return ret;
> +
> + switch (psp) {
> + case POWER_SUPPLY_PROP_ONLINE:
> + val->intval = ecbat->adapter_online;
> + break;
> + case POWER_SUPPLY_PROP_USB_TYPE:
> + val->intval = POWER_SUPPLY_USB_TYPE_C;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +static enum power_supply_property yoga_c630_psy_adpt_properties[] = {
> + POWER_SUPPLY_PROP_ONLINE,
> + POWER_SUPPLY_PROP_USB_TYPE,
> +};
> +
> +static const enum power_supply_usb_type yoga_c630_psy_adpt_usb_type[] = {
> + POWER_SUPPLY_USB_TYPE_C,
> +};
> +
> +static const struct power_supply_desc yoga_c630_psy_adpt_psy_desc = {
> + .name = "yoga-c630-adapter",
> + .type = POWER_SUPPLY_TYPE_USB,
> + .usb_types = yoga_c630_psy_adpt_usb_type,
> + .num_usb_types = ARRAY_SIZE(yoga_c630_psy_adpt_usb_type),
> + .properties = yoga_c630_psy_adpt_properties,
> + .num_properties = ARRAY_SIZE(yoga_c630_psy_adpt_properties),
> + .get_property = yoga_c630_psy_adpt_get_property,
> +};
> +
> +static int yoga_c630_psy_register_bat_psy(struct yoga_c630_psy *ecbat)
> +{
> + struct power_supply_config bat_cfg = {};
> +
> + bat_cfg.drv_data = ecbat;
> + bat_cfg.fwnode = ecbat->fwnode;
> + ecbat->bat_psy = power_supply_register_no_ws(ecbat->dev,
> + ecbat->unit_mA ?
> + &yoga_c630_psy_bat_psy_desc_mA :
> + &yoga_c630_psy_bat_psy_desc_mWh,
> + &bat_cfg);
> + if (IS_ERR(ecbat->bat_psy)) {
> + dev_err(ecbat->dev, "failed to register battery supply\n");
> + return PTR_ERR(ecbat->bat_psy);
> + }
> +
> + return 0;
> +}
> +
> +static void yoga_c630_ec_refresh_bat_info(struct yoga_c630_psy *ecbat)
> +{
> + bool current_unit;
> +
> + scoped_guard(mutex, &ecbat->lock) {

guard()

> + current_unit = ecbat->unit_mA;
> +
> + yoga_c630_psy_update_bat_info(ecbat);
> +
> + if (current_unit != ecbat->unit_mA) {
> + power_supply_unregister(ecbat->bat_psy);
> + yoga_c630_psy_register_bat_psy(ecbat);
> + }
> + }
> +}


> + adp_cfg.supplied_to = (char **)&yoga_c630_psy_bat_psy_desc_mA.name;

This is not problem with your patch but I'm wondering why supplied_to
needs to be non-const char *. Are those strings expected to be altered by
something, I couldn't find anything to that effect (the pointer itself
does not become const if supplied_to is changed to const char **)?

--
i.


2024-06-13 08:29:59

by Dmitry Baryshkov

[permalink] [raw]
Subject: Re: [PATCH v6 3/6] usb: typec: ucsi: add Lenovo Yoga C630 glue driver

On Thu, 13 Jun 2024 at 10:30, Ilpo Järvinen
<[email protected]> wrote:
>
> On Wed, 12 Jun 2024, Dmitry Baryshkov wrote:
>
> > The Lenovo Yoga C630 WOS laptop provides implements UCSI interface in
> > the onboard EC. Add glue driver to interface the platform's UCSI
> > implementation.
> >
> > Reviewed-by: Bryan O'Donoghue <[email protected]>
> > Reviewed-by: Heikki Krogerus <[email protected]>
> > Signed-off-by: Dmitry Baryshkov <[email protected]>
> > ---
>
> > +static int yoga_c630_ucsi_read(struct ucsi *ucsi, unsigned int offset,
> > + void *val, size_t val_len)
> > +{
> > + struct yoga_c630_ucsi *uec = ucsi_get_drvdata(ucsi);
> > + u8 buf[YOGA_C630_UCSI_READ_SIZE];
> > + int ret;
> > +
> > + ret = yoga_c630_ec_ucsi_read(uec->ec, buf);
> > + if (ret)
> > + return ret;
> > +
> > + if (offset == UCSI_VERSION) {
> > + memcpy(val, &uec->version, min(val_len, sizeof(uec->version)));
> > + return 0;
> > + }
> > +
> > + if (offset == UCSI_CCI)
> > + memcpy(val, buf, min(val_len, YOGA_C630_UCSI_CCI_SIZE));
> > + else if (offset == UCSI_MESSAGE_IN)
> > + memcpy(val, buf + YOGA_C630_UCSI_CCI_SIZE,
> > + min(val_len, YOGA_C630_UCSI_DATA_SIZE));
> > + else
> > + return -EINVAL;
> > +
> > + return 0;
>
> Hmm, the inconsistency when to do return 0 is a bit odd. Also, using
> switch (offset) would probably be better here anyway to replace all the
> ifs.

I'll see if I can improve this bit.

>
> > +}
> > +
> > +static int yoga_c630_ucsi_async_write(struct ucsi *ucsi, unsigned int offset,
> > + const void *val, size_t val_len)
> > +{
> > + struct yoga_c630_ucsi *uec = ucsi_get_drvdata(ucsi);
> > +
> > + if (offset != UCSI_CONTROL ||
> > + val_len != YOGA_C630_UCSI_WRITE_SIZE)
> > + return -EINVAL;
> > +
> > + return yoga_c630_ec_ucsi_write(uec->ec, val);
> > +}
> > +
> > +static int yoga_c630_ucsi_sync_write(struct ucsi *ucsi, unsigned int offset,
> > + const void *val, size_t val_len)
> > +{
> > + struct yoga_c630_ucsi *uec = ucsi_get_drvdata(ucsi);
> > + bool ack = UCSI_COMMAND(*(u64 *)val) == UCSI_ACK_CC_CI;
> > + int ret;
> > +
> > + if (ack)
> > + set_bit(UCSI_C630_ACK_PENDING, &uec->flags);
> > + else
> > + set_bit(UCSI_C630_COMMAND_PENDING, &uec->flags);
> > +
> > + reinit_completion(&uec->complete);
> > +
> > + ret = yoga_c630_ucsi_async_write(ucsi, offset, val, val_len);
> > + if (ret)
> > + goto out_clear_bit;
> > +
> > + if (!wait_for_completion_timeout(&uec->complete, 5 * HZ))
> > + ret = -ETIMEDOUT;
> > +
> > +out_clear_bit:
> > + if (ack)
> > + clear_bit(UCSI_C630_ACK_PENDING, &uec->flags);
> > + else
> > + clear_bit(UCSI_C630_COMMAND_PENDING, &uec->flags);
> > +
> > + return ret;
> > +}
> > +
> > +const struct ucsi_operations yoga_c630_ucsi_ops = {
> > + .read = yoga_c630_ucsi_read,
> > + .sync_write = yoga_c630_ucsi_sync_write,
> > + .async_write = yoga_c630_ucsi_async_write,
> > +};
> > +
> > +static void yoga_c630_ucsi_notify_ucsi(struct yoga_c630_ucsi *uec, u32 cci)
> > +{
> > + if (UCSI_CCI_CONNECTOR(cci))
> > + ucsi_connector_change(uec->ucsi, UCSI_CCI_CONNECTOR(cci));
> > +
> > + if (cci & UCSI_CCI_ACK_COMPLETE &&
> > + test_bit(UCSI_C630_ACK_PENDING, &uec->flags))
> > + complete(&uec->complete);
> > +
> > + if (cci & UCSI_CCI_COMMAND_COMPLETE &&
> > + test_bit(UCSI_C630_COMMAND_PENDING, &uec->flags))
> > + complete(&uec->complete);
>
> Is this racy? Can another command start after an ACK in between these two
> ifs and complete() is called prematurely for the new command? (Or will
> different value in cci protect against that?)

No, there is no race. The UCSI is locked for the duration of the command.

>
> > +}
> > +
> > +static int yoga_c630_ucsi_notify(struct notifier_block *nb,
> > + unsigned long action, void *data)
> > +{
> > + struct yoga_c630_ucsi *uec = container_of(nb, struct yoga_c630_ucsi, nb);
> > + u32 cci;
> > + int ret;
> > +
> > + switch (action) {
> > + case LENOVO_EC_EVENT_USB:
> > + case LENOVO_EC_EVENT_HPD:
> > + ucsi_connector_change(uec->ucsi, 1);
> > + return NOTIFY_OK;
> > +
> > + case LENOVO_EC_EVENT_UCSI:
> > + ret = uec->ucsi->ops->read(uec->ucsi, UCSI_CCI, &cci, sizeof(cci));
> > + if (ret)
> > + return NOTIFY_DONE;
> > +
> > + yoga_c630_ucsi_notify_ucsi(uec, cci);
> > +
> > + return NOTIFY_OK;
> > +
> > + default:
> > + return NOTIFY_DONE;
> > + }
> > +}
> > +
> > +static int yoga_c630_ucsi_probe(struct auxiliary_device *adev,
> > + const struct auxiliary_device_id *id)
> > +{
> > + struct yoga_c630_ec *ec = adev->dev.platform_data;
> > + struct yoga_c630_ucsi *uec;
> > + int ret;
> > +
> > + uec = devm_kzalloc(&adev->dev, sizeof(*uec), GFP_KERNEL);
> > + if (!uec)
> > + return -ENOMEM;
> > +
> > + uec->ec = ec;
> > + init_completion(&uec->complete);
> > + uec->nb.notifier_call = yoga_c630_ucsi_notify;
> > +
> > + uec->ucsi = ucsi_create(&adev->dev, &yoga_c630_ucsi_ops);
> > + if (IS_ERR(uec->ucsi))
> > + return PTR_ERR(uec->ucsi);
> > +
> > + ucsi_set_drvdata(uec->ucsi, uec);
> > +
> > + uec->version = yoga_c630_ec_ucsi_get_version(uec->ec);
> > +
> > + auxiliary_set_drvdata(adev, uec);
> > +
> > + ret = yoga_c630_ec_register_notify(ec, &uec->nb);
> > + if (ret)
> > + return ret;
> > +
> > + return ucsi_register(uec->ucsi);
> > +}
> > +
> > +static void yoga_c630_ucsi_remove(struct auxiliary_device *adev)
> > +{
> > + struct yoga_c630_ucsi *uec = auxiliary_get_drvdata(adev);
> > +
> > + yoga_c630_ec_unregister_notify(uec->ec, &uec->nb);
> > + ucsi_unregister(uec->ucsi);
>
> Usually, the remove should tear down in reverse order than the probe side.
> Is the divergence from that here intentional?

Yes, it's intentional, so that the driver doesn't get a notification
while UCSI is being torn down. Consider it to be paired with
ucsi_create().

--
With best wishes
Dmitry

2024-06-13 08:46:49

by Ilpo Järvinen

[permalink] [raw]
Subject: Re: [PATCH v6 3/6] usb: typec: ucsi: add Lenovo Yoga C630 glue driver

On Wed, 12 Jun 2024, Dmitry Baryshkov wrote:

> The Lenovo Yoga C630 WOS laptop provides implements UCSI interface in
> the onboard EC. Add glue driver to interface the platform's UCSI
> implementation.
>
> Reviewed-by: Bryan O'Donoghue <[email protected]>
> Reviewed-by: Heikki Krogerus <[email protected]>
> Signed-off-by: Dmitry Baryshkov <[email protected]>
> ---

> +static int yoga_c630_ucsi_read(struct ucsi *ucsi, unsigned int offset,
> + void *val, size_t val_len)
> +{
> + struct yoga_c630_ucsi *uec = ucsi_get_drvdata(ucsi);
> + u8 buf[YOGA_C630_UCSI_READ_SIZE];
> + int ret;
> +
> + ret = yoga_c630_ec_ucsi_read(uec->ec, buf);
> + if (ret)
> + return ret;
> +
> + if (offset == UCSI_VERSION) {
> + memcpy(val, &uec->version, min(val_len, sizeof(uec->version)));
> + return 0;
> + }
> +
> + if (offset == UCSI_CCI)
> + memcpy(val, buf, min(val_len, YOGA_C630_UCSI_CCI_SIZE));
> + else if (offset == UCSI_MESSAGE_IN)
> + memcpy(val, buf + YOGA_C630_UCSI_CCI_SIZE,
> + min(val_len, YOGA_C630_UCSI_DATA_SIZE));
> + else
> + return -EINVAL;
> +
> + return 0;

Hmm, the inconsistency when to do return 0 is a bit odd. Also, using
switch (offset) would probably be better here anyway to replace all the
ifs.

> +}
> +
> +static int yoga_c630_ucsi_async_write(struct ucsi *ucsi, unsigned int offset,
> + const void *val, size_t val_len)
> +{
> + struct yoga_c630_ucsi *uec = ucsi_get_drvdata(ucsi);
> +
> + if (offset != UCSI_CONTROL ||
> + val_len != YOGA_C630_UCSI_WRITE_SIZE)
> + return -EINVAL;
> +
> + return yoga_c630_ec_ucsi_write(uec->ec, val);
> +}
> +
> +static int yoga_c630_ucsi_sync_write(struct ucsi *ucsi, unsigned int offset,
> + const void *val, size_t val_len)
> +{
> + struct yoga_c630_ucsi *uec = ucsi_get_drvdata(ucsi);
> + bool ack = UCSI_COMMAND(*(u64 *)val) == UCSI_ACK_CC_CI;
> + int ret;
> +
> + if (ack)
> + set_bit(UCSI_C630_ACK_PENDING, &uec->flags);
> + else
> + set_bit(UCSI_C630_COMMAND_PENDING, &uec->flags);
> +
> + reinit_completion(&uec->complete);
> +
> + ret = yoga_c630_ucsi_async_write(ucsi, offset, val, val_len);
> + if (ret)
> + goto out_clear_bit;
> +
> + if (!wait_for_completion_timeout(&uec->complete, 5 * HZ))
> + ret = -ETIMEDOUT;
> +
> +out_clear_bit:
> + if (ack)
> + clear_bit(UCSI_C630_ACK_PENDING, &uec->flags);
> + else
> + clear_bit(UCSI_C630_COMMAND_PENDING, &uec->flags);
> +
> + return ret;
> +}
> +
> +const struct ucsi_operations yoga_c630_ucsi_ops = {
> + .read = yoga_c630_ucsi_read,
> + .sync_write = yoga_c630_ucsi_sync_write,
> + .async_write = yoga_c630_ucsi_async_write,
> +};
> +
> +static void yoga_c630_ucsi_notify_ucsi(struct yoga_c630_ucsi *uec, u32 cci)
> +{
> + if (UCSI_CCI_CONNECTOR(cci))
> + ucsi_connector_change(uec->ucsi, UCSI_CCI_CONNECTOR(cci));
> +
> + if (cci & UCSI_CCI_ACK_COMPLETE &&
> + test_bit(UCSI_C630_ACK_PENDING, &uec->flags))
> + complete(&uec->complete);
> +
> + if (cci & UCSI_CCI_COMMAND_COMPLETE &&
> + test_bit(UCSI_C630_COMMAND_PENDING, &uec->flags))
> + complete(&uec->complete);

Is this racy? Can another command start after an ACK in between these two
ifs and complete() is called prematurely for the new command? (Or will
different value in cci protect against that?)

> +}
> +
> +static int yoga_c630_ucsi_notify(struct notifier_block *nb,
> + unsigned long action, void *data)
> +{
> + struct yoga_c630_ucsi *uec = container_of(nb, struct yoga_c630_ucsi, nb);
> + u32 cci;
> + int ret;
> +
> + switch (action) {
> + case LENOVO_EC_EVENT_USB:
> + case LENOVO_EC_EVENT_HPD:
> + ucsi_connector_change(uec->ucsi, 1);
> + return NOTIFY_OK;
> +
> + case LENOVO_EC_EVENT_UCSI:
> + ret = uec->ucsi->ops->read(uec->ucsi, UCSI_CCI, &cci, sizeof(cci));
> + if (ret)
> + return NOTIFY_DONE;
> +
> + yoga_c630_ucsi_notify_ucsi(uec, cci);
> +
> + return NOTIFY_OK;
> +
> + default:
> + return NOTIFY_DONE;
> + }
> +}
> +
> +static int yoga_c630_ucsi_probe(struct auxiliary_device *adev,
> + const struct auxiliary_device_id *id)
> +{
> + struct yoga_c630_ec *ec = adev->dev.platform_data;
> + struct yoga_c630_ucsi *uec;
> + int ret;
> +
> + uec = devm_kzalloc(&adev->dev, sizeof(*uec), GFP_KERNEL);
> + if (!uec)
> + return -ENOMEM;
> +
> + uec->ec = ec;
> + init_completion(&uec->complete);
> + uec->nb.notifier_call = yoga_c630_ucsi_notify;
> +
> + uec->ucsi = ucsi_create(&adev->dev, &yoga_c630_ucsi_ops);
> + if (IS_ERR(uec->ucsi))
> + return PTR_ERR(uec->ucsi);
> +
> + ucsi_set_drvdata(uec->ucsi, uec);
> +
> + uec->version = yoga_c630_ec_ucsi_get_version(uec->ec);
> +
> + auxiliary_set_drvdata(adev, uec);
> +
> + ret = yoga_c630_ec_register_notify(ec, &uec->nb);
> + if (ret)
> + return ret;
> +
> + return ucsi_register(uec->ucsi);
> +}
> +
> +static void yoga_c630_ucsi_remove(struct auxiliary_device *adev)
> +{
> + struct yoga_c630_ucsi *uec = auxiliary_get_drvdata(adev);
> +
> + yoga_c630_ec_unregister_notify(uec->ec, &uec->nb);
> + ucsi_unregister(uec->ucsi);

Usually, the remove should tear down in reverse order than the probe side.
Is the divergence from that here intentional?


--
i.


2024-06-14 01:35:43

by Sebastian Reichel

[permalink] [raw]
Subject: Re: [PATCH v6 4/6] power: supply: lenovo_yoga_c630_battery: add Lenovo C630 driver

Hi,

On Wed, Jun 12, 2024 at 12:59:35PM GMT, Dmitry Baryshkov wrote:
> On the Lenovo Yoga C630 WOS laptop the EC provides access to the adapter
> and battery status. Add the driver to read power supply status on the
> laptop.
>
> Signed-off-by: Dmitry Baryshkov <[email protected]>
> ---
> drivers/power/supply/Kconfig | 9 +
> drivers/power/supply/Makefile | 1 +
> drivers/power/supply/lenovo_yoga_c630_battery.c | 500 ++++++++++++++++++++++++
> 3 files changed, 510 insertions(+)
>
> diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
> index 3e31375491d5..55ab8e90747d 100644
> --- a/drivers/power/supply/Kconfig
> +++ b/drivers/power/supply/Kconfig
> @@ -167,6 +167,15 @@ config BATTERY_LEGO_EV3
> help
> Say Y here to enable support for the LEGO MINDSTORMS EV3 battery.
>
> +config BATTERY_LENOVO_YOGA_C630
> + tristate "Lenovo Yoga C630 battery"
> + depends on OF && EC_LENOVO_YOGA_C630

The driver should no longer depend on OF. Otherwise LGTM.
Thanks for reworking it.

Greetings,

-- Sebastian


Attachments:
(No filename) (1.09 kB)
signature.asc (849.00 B)
Download all attachments

2024-06-14 01:47:41

by Sebastian Reichel

[permalink] [raw]
Subject: Re: [PATCH v6 4/6] power: supply: lenovo_yoga_c630_battery: add Lenovo C630 driver

Hi,

On Thu, Jun 13, 2024 at 10:57:41AM GMT, Ilpo J?rvinen wrote:
> > + adp_cfg.supplied_to = (char **)&yoga_c630_psy_bat_psy_desc_mA.name;
>
> This is not problem with your patch but I'm wondering why supplied_to
> needs to be non-const char *. Are those strings expected to be altered by
> something, I couldn't find anything to that effect (the pointer itself
> does not become const if supplied_to is changed to const char **)?

No, they are not supposed to be modified. It should be fine to make
the struct member const char **, patches are welcome :)

-- Sebastian


Attachments:
(No filename) (589.00 B)
signature.asc (849.00 B)
Download all attachments

2024-06-14 10:36:23

by Dmitry Baryshkov

[permalink] [raw]
Subject: Re: [PATCH v6 4/6] power: supply: lenovo_yoga_c630_battery: add Lenovo C630 driver

On Fri, Jun 14, 2024 at 03:35:25AM GMT, Sebastian Reichel wrote:
> Hi,
>
> On Wed, Jun 12, 2024 at 12:59:35PM GMT, Dmitry Baryshkov wrote:
> > On the Lenovo Yoga C630 WOS laptop the EC provides access to the adapter
> > and battery status. Add the driver to read power supply status on the
> > laptop.
> >
> > Signed-off-by: Dmitry Baryshkov <[email protected]>
> > ---
> > drivers/power/supply/Kconfig | 9 +
> > drivers/power/supply/Makefile | 1 +
> > drivers/power/supply/lenovo_yoga_c630_battery.c | 500 ++++++++++++++++++++++++
> > 3 files changed, 510 insertions(+)
> >
> > diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
> > index 3e31375491d5..55ab8e90747d 100644
> > --- a/drivers/power/supply/Kconfig
> > +++ b/drivers/power/supply/Kconfig
> > @@ -167,6 +167,15 @@ config BATTERY_LEGO_EV3
> > help
> > Say Y here to enable support for the LEGO MINDSTORMS EV3 battery.
> >
> > +config BATTERY_LENOVO_YOGA_C630
> > + tristate "Lenovo Yoga C630 battery"
> > + depends on OF && EC_LENOVO_YOGA_C630
>
> The driver should no longer depend on OF. Otherwise LGTM.
> Thanks for reworking it.

Ack, I'll post a fixed version once Ilpo announces an immutable branch.



--
With best wishes
Dmitry