diff mbox series

[1/2] platform/x86/intel: bytcrc_pwrsrc: Optionally register a power_supply dev

Message ID 20241104203555.61104-1-hdegoede@redhat.com
State New
Headers show
Series [1/2] platform/x86/intel: bytcrc_pwrsrc: Optionally register a power_supply dev | expand

Commit Message

Hans de Goede Nov. 4, 2024, 8:35 p.m. UTC
On some Android tablets with Crystal Cove PMIC the DSDT lacks an ACPI AC
device to indicate whether a charger is plugged in or not.

Add support for registering a "crystal_cove_pwrsrc" power_supply class
device to indicate charger online status. This is made conditional on
a "linux,register-pwrsrc-power_supply" boolean device-property to avoid
registering a duplicate power_supply class device on devices where this
is already handled by an ACPI AC device.

Note the "linux,register-pwrsrc-power_supply" property is only used on
x86/ACPI (non devicetree) devs and the devicetree-bindings maintainers
have requested properties like these to not be added to the devicetree
bindings, so the new property is deliberately not added to any bindings.

Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
 drivers/platform/x86/intel/bytcrc_pwrsrc.c | 76 +++++++++++++++++++++-
 1 file changed, 75 insertions(+), 1 deletion(-)

Comments

Andy Shevchenko Nov. 5, 2024, 8:51 a.m. UTC | #1
On Mon, Nov 4, 2024 at 10:36 PM Hans de Goede <hdegoede@redhat.com> wrote:
>
> On some Android tablets with Crystal Cove PMIC the DSDT lacks an ACPI AC
> device to indicate whether a charger is plugged in or not.
>
> Add support for registering a "crystal_cove_pwrsrc" power_supply class
> device to indicate charger online status. This is made conditional on
> a "linux,register-pwrsrc-power_supply" boolean device-property to avoid
> registering a duplicate power_supply class device on devices where this
> is already handled by an ACPI AC device.
>
> Note the "linux,register-pwrsrc-power_supply" property is only used on
> x86/ACPI (non devicetree) devs and the devicetree-bindings maintainers
> have requested properties like these to not be added to the devicetree
> bindings, so the new property is deliberately not added to any bindings.

Reviewed-by: Andy Shevchenko <andy@kernel.org>

...

+ array_size.h

> +#include <linux/bits.h>
>  #include <linux/debugfs.h>
> +#include <linux/interrupt.h>
>  #include <linux/mfd/intel_soc_pmic.h>
>  #include <linux/module.h>
>  #include <linux/platform_device.h>
> +#include <linux/power_supply.h>
> +#include <linux/property.h>
>  #include <linux/regmap.h>

...

> +       if (device_property_read_bool(pdev->dev.parent, "linux,register-pwrsrc-power_supply")) {

Btw, is that property type of boolean? If not,
device_property_present() has to be used.

...

> +               irq = platform_get_irq(pdev, 0);
> +               if (irq < 0)
> +                       return dev_err_probe(&pdev->dev, irq, "getting IRQ\n");

This dups the embedded error message.

...

> +               data->psy = devm_power_supply_register(&pdev->dev, &crc_pwrsrc_psy_desc, &psy_cfg);
> +               if (IS_ERR(data->psy))
> +                       return dev_err_probe(&pdev->dev, PTR_ERR(data->psy),
> +                                            "registering power-supply\n");
> +
> +               ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
> +                                               crc_pwrsrc_irq_handler,
> +                                               IRQF_ONESHOT, KBUILD_MODNAME, data);
> +               if (ret)
> +                       return dev_err_probe(&pdev->dev, ret, "requesting IRQ\n");

With

    struct device *dev = &pdev->dev;

at the top of the function you may make lines shorten and neater.

> +       }
Andy Shevchenko Nov. 5, 2024, 10:39 a.m. UTC | #2
On Mon, Nov 4, 2024 at 10:36 PM Hans de Goede <hdegoede@redhat.com> wrote:
>
> The Vexia EDU ATLA 10 tablet has an embedded controller instead of
> giving the os direct access to the charger + fuel-gauge ICs as is normal
> on tablets designed for Android.
>
> There is ACPI Battery device in the DSDT using the EC which should work
> expect that it expects the I2C controller to be enumerated as an ACPI

expect --> except

> device and the tablet's BIOS enumerates all LPSS devices as PCI devices
> (and changing the LPSS BIOS settings from PCI -> ACPI does not work).
>
> Add a power_supply class driver for the Atla 10 EC to expert battery info
> to userspace. This is made part of the x86-android-tablets directory and
> Kconfig option because the i2c_client it binds to is instantiated by
> the x86-android-tablets kmod.

Reviewed-by: Andy Shevchenko <andy@kernel.org>

...

>  obj-$(CONFIG_X86_ANDROID_TABLETS) += x86-android-tablets.o
> +obj-$(CONFIG_X86_ANDROID_TABLETS) += vexia_atla10_ec.o

This splits the original (compound) object lines, please move it
either before (and this seems even better with ordering by name in
mind) or after this block.
>

Actually this blank line gives the false impression that the
originally two lines are not related. I would drop this blank line as
well.

>  x86-android-tablets-y := core.o dmi.o shared-psy-info.o \
>                          asus.o lenovo.o other.o

...

> +#include <linux/bits.h>
> +#include <linux/devm-helpers.h>

+ err.h

> +#include <linux/i2c.h>
> +#include <linux/module.h>
> +#include <linux/power_supply.h>
> +#include <linux/types.h>
> +#include <linux/workqueue.h>
> +
> +#include <asm/byteorder.h>

...

> +/* From broken ACPI battery device in DSDT */
> +#define ATLA10_EC_VOLTAGE_MIN_DESIGN           3750000

_uV ?

...

> +struct atla10_ec_battery_state {
> +       u8 len;                         /* Struct length excluding the len field, always 12 */
> +       u8 status;                      /* Using ACPI Battery spec status bits */
> +       u8 capacity;                    /* Percent */
> +       __le16 charge_now;              /* mAh */
> +       __le16 voltage_now;             /* mV */
> +       __le16 current_now;             /* mA */
> +       __le16 charge_full;             /* mAh */
> +       __le16 temp;                    /* centi degrees celcius */

Celsius / celsius

> +} __packed;
> +
> +struct atla10_ec_battery_info {
> +       u8 len;                         /* Struct length excluding the len field, always 6 */
> +       __le16 charge_full_design;      /* mAh */
> +       __le16 voltage_now;             /* mV, should be design voltage, but is not ? */
> +       __le16 charge_full_design2;     /* mAh */
> +} __packed;

Instead I would add the respective units to the variable names:
_mAh
_mV
...etc.

(* yes, with the capital letters to follow the proper spelling)

...

> +static int atla10_ec_cmd(struct atla10_ec_data *data, u8 cmd, u8 len, u8 *values)
> +{
> +       struct device *dev = &data->client->dev;
> +       int ret;
> +
> +       ret = i2c_smbus_read_i2c_block_data(data->client, cmd, len, values);
> +       if (ret != len) {
> +               dev_err(dev, "I2C command 0x%02x error: %d\n", cmd, ret);
> +               return -EIO;
> +       }

> +       if (values[0] != (len - 1)) {

Hmm... AFAIU this is part of SMBus protocol. Why do we need to care
about this? Or is this an additional header on top of that?

> +               dev_err(dev, "I2C command 0x%02x header length mismatch expected %u got %u\n",
> +                       cmd, len - 1, values[0]);
> +               return -EIO;
> +       }
> +
> +       return 0;
> +}

...

> +               val->intval = min(charge_now, charge_full) * 1000;

MILLI (here and below)?

> +               break;
> +       case POWER_SUPPLY_PROP_VOLTAGE_NOW:
> +               val->intval = le16_to_cpu(data->state.voltage_now) * 1000;
> +               break;
> +       case POWER_SUPPLY_PROP_CURRENT_NOW:
> +               val->intval = le16_to_cpu(data->state.current_now) * 1000;
> +               /*
> +                * Documentation/ABI/testing/sysfs-class-power specifies
> +                * negative current for discharing.

discharging

> +                */

--
With Best Regards,
Andy Shevchenko
diff mbox series

Patch

diff --git a/drivers/platform/x86/intel/bytcrc_pwrsrc.c b/drivers/platform/x86/intel/bytcrc_pwrsrc.c
index 418b71af27ff..98b5057d4c68 100644
--- a/drivers/platform/x86/intel/bytcrc_pwrsrc.c
+++ b/drivers/platform/x86/intel/bytcrc_pwrsrc.c
@@ -8,13 +8,21 @@ 
  * Copyright (C) 2013 Intel Corporation
  */
 
+#include <linux/bits.h>
 #include <linux/debugfs.h>
+#include <linux/interrupt.h>
 #include <linux/mfd/intel_soc_pmic.h>
 #include <linux/module.h>
 #include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/property.h>
 #include <linux/regmap.h>
 
+#define CRYSTALCOVE_PWRSRC_IRQ		0x03
 #define CRYSTALCOVE_SPWRSRC_REG		0x1E
+#define CRYSTALCOVE_SPWRSRC_USB		BIT(0)
+#define CRYSTALCOVE_SPWRSRC_DC		BIT(1)
+#define CRYSTALCOVE_SPWRSRC_BATTERY	BIT(2)
 #define CRYSTALCOVE_RESETSRC0_REG	0x20
 #define CRYSTALCOVE_RESETSRC1_REG	0x21
 #define CRYSTALCOVE_WAKESRC_REG		0x22
@@ -22,6 +30,7 @@ 
 struct crc_pwrsrc_data {
 	struct regmap *regmap;
 	struct dentry *debug_dentry;
+	struct power_supply *psy;
 	unsigned int resetsrc0;
 	unsigned int resetsrc1;
 	unsigned int wakesrc;
@@ -118,11 +127,57 @@  static int crc_pwrsrc_read_and_clear(struct crc_pwrsrc_data *data,
 	return regmap_write(data->regmap, reg, *val);
 }
 
+static irqreturn_t crc_pwrsrc_irq_handler(int irq, void *_data)
+{
+	struct crc_pwrsrc_data *data = _data;
+	unsigned int irq_mask;
+
+	if (regmap_read(data->regmap, CRYSTALCOVE_PWRSRC_IRQ, &irq_mask))
+		return IRQ_NONE;
+
+	regmap_write(data->regmap, CRYSTALCOVE_PWRSRC_IRQ, irq_mask);
+
+	power_supply_changed(data->psy);
+	return IRQ_HANDLED;
+}
+
+static int crc_pwrsrc_psy_get_property(struct power_supply *psy,
+				       enum power_supply_property psp,
+				       union power_supply_propval *val)
+{
+	struct crc_pwrsrc_data *data = power_supply_get_drvdata(psy);
+	unsigned int pwrsrc;
+	int ret;
+
+	if (psp != POWER_SUPPLY_PROP_ONLINE)
+		return -EINVAL;
+
+	ret = regmap_read(data->regmap, CRYSTALCOVE_SPWRSRC_REG, &pwrsrc);
+	if (ret)
+		return ret;
+
+	val->intval = !!(pwrsrc & (CRYSTALCOVE_SPWRSRC_USB |
+				   CRYSTALCOVE_SPWRSRC_DC));
+	return 0;
+}
+
+static const enum power_supply_property crc_pwrsrc_psy_props[] = {
+	POWER_SUPPLY_PROP_ONLINE,
+};
+
+static const struct power_supply_desc crc_pwrsrc_psy_desc = {
+	.name = "crystal_cove_pwrsrc",
+	.type = POWER_SUPPLY_TYPE_MAINS,
+	.properties = crc_pwrsrc_psy_props,
+	.num_properties = ARRAY_SIZE(crc_pwrsrc_psy_props),
+	.get_property = crc_pwrsrc_psy_get_property,
+};
+
 static int crc_pwrsrc_probe(struct platform_device *pdev)
 {
 	struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent);
 	struct crc_pwrsrc_data *data;
-	int ret;
+	int irq, ret;
 
 	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
 	if (!data)
@@ -149,6 +204,25 @@  static int crc_pwrsrc_probe(struct platform_device *pdev)
 	if (ret)
 		return ret;
 
+	if (device_property_read_bool(pdev->dev.parent, "linux,register-pwrsrc-power_supply")) {
+		struct power_supply_config psy_cfg = { .drv_data = data };
+
+		irq = platform_get_irq(pdev, 0);
+		if (irq < 0)
+			return dev_err_probe(&pdev->dev, irq, "getting IRQ\n");
+
+		data->psy = devm_power_supply_register(&pdev->dev, &crc_pwrsrc_psy_desc, &psy_cfg);
+		if (IS_ERR(data->psy))
+			return dev_err_probe(&pdev->dev, PTR_ERR(data->psy),
+					     "registering power-supply\n");
+
+		ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
+						crc_pwrsrc_irq_handler,
+						IRQF_ONESHOT, KBUILD_MODNAME, data);
+		if (ret)
+			return dev_err_probe(&pdev->dev, ret, "requesting IRQ\n");
+	}
+
 	data->debug_dentry = debugfs_create_dir(KBUILD_MODNAME, NULL);
 	debugfs_create_file("pwrsrc", 0444, data->debug_dentry, data, &pwrsrc_fops);
 	debugfs_create_file("resetsrc", 0444, data->debug_dentry, data, &resetsrc_fops);