lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-ID: <CACRpkda833CiwA+ihMLm6zzTFsoCMFoesbAVrW7EMA0-vGFTFQ@mail.gmail.com>
Date: Tue, 14 Oct 2025 12:23:07 +0200
From: Linus Walleij <linus.walleij@...aro.org>
To: 1647395606@...com
Cc: sre@...nel.org, brgl@...ev.pl, linux-kernel@...r.kernel.org, 
	linux-pm@...r.kernel.org, linux-gpio@...r.kernel.org, 
	wangwenqiang <wenqiang.wang@...ot.com>
Subject: Re: [PATCH] power: supply: Add SC8541 charger drivers

Hi Wang,

thanks for your patch!

On Mon, Oct 13, 2025 at 10:54 AM <1647395606@...com> wrote:

> From: wangwenqiang <wenqiang.wang@...ot.com>
>
> The SC8541 is a charger pump from South Chip.
> By adjusting the voltage difference between the input and output terminals,
> it can achieve a maximum charging current of 8A.
> It has been verified that this driver can operate normally on the Qualcomm QCS615 platform.
>
> Signed-off-by: wangwenqiang <wenqiang.wang@...ot.com>

(...)
> +#include <linux/gpio.h>

Don't use this legacy API, include <linux/gpio/consumer.h> if you need
to use GPIO lines and use GPIO descriptors.

> +#include <linux/i2c.h>
> +#include <linux/init.h>
> +#include <linux/interrupt.h>
> +#include <linux/module.h>
> +#include <linux/power_supply.h>
> +#include <linux/slab.h>
> +#include <linux/kernel.h>
> +#include <linux/sched.h>
> +#include <linux/kthread.h>

Are you using sched.h and thread.h really?
You are using threaded interrupt handlers but that is
not coming from this file but interrupt.h.

> +#include <linux/delay.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/of_gpio.h>

Don't use this legacy API either, use <linux/gpio/consumer.h>

> +#include <linux/err.h>
> +#include <linux/regulator/driver.h>
> +#include <linux/regulator/of_regulator.h>
> +#include <linux/regulator/machine.h>

I don't think you're defining regulator machines either.

> +#include <linux/debugfs.h>
> +#include <linux/bitops.h>
> +#include <linux/math64.h>
> +#include <linux/regmap.h>

Please go over the includes and make sure you only include the ones
you really need.

> +#define SC8541_DRV_VERSION              "1.0.0_G"

I would just drop this, we don't use these kind of strings much these days.

> +struct flag_bit {
> +    int notify;
> +    int mask;
> +    char *name;
> +};
> +
> +struct intr_flag {
> +    int reg;
> +    int len;
> +    struct flag_bit bit[8];
> +};

Are these things such as notify, mask, reg, len really int?
Aren't they better as u32?

> +struct reg_range {
> +    u32 min;
> +    u32 max;
> +    u32 step;
> +    u32 offset;
> +    const u32 *table;
> +    u16 num_table;
> +    bool round_up;
> +};

Here is some nice use of proper types!

> +static const struct regmap_config sc8541_regmap_config = {
> +    .reg_bits = 8,
> +    .val_bits = 8,
> +
> +    .max_register = SC8541_REGMAX,
> +};

Nice use of regmap.

> +struct sc8541_chip {
> +    struct device *dev;
> +    struct i2c_client *client;
> +    struct regmap *regmap;
> +    struct regmap_field *rmap_fields[F_MAX_FIELDS];
> +
> +    struct sc8541_cfg_e cfg;
> +    int irq_gpio;
> +    int irq;
> +
> +    int mode;
> +
> +    bool charge_enabled;
> +    int usb_present;
> +    int vbus_volt;
> +    int ibus_curr;
> +    int vbat_volt;
> +    int ibat_curr;
> +    int die_temp;

Can all of these really be negative? Otherwise use unsigned int.
I guess the die_temp should be int because it could be negative.

> +/********************COMMON API***********************/

Drop these headers, we can see it is a common API anyway.

> +__maybe_unused static u8 val2reg(enum sc8541_reg_range id, u32 val)
> +{
> +    int i;
> +    u8 reg;
> +    const struct reg_range *range = &sc8541_reg_range[id];
> +
> +    if (!range)
> +       return val;
> +
> +    if (range->table) {
> +       if (val <= range->table[0])
> +           return 0;
> +       for (i = 1; i < range->num_table - 1; i++) {
> +           if (val == range->table[i])
> +               return i;
> +           if (val > range->table[i] &&
> +               val < range->table[i + 1])
> +               return range->round_up ? i + 1 : i;
> +       }
> +       return range->num_table - 1;
> +    }
> +    if (val <= range->min)
> +       reg = 0;
> +    else if (val >= range->max)
> +       reg = (range->max - range->offset) / range->step;
> +    else if (range->round_up)
> +       reg = (val - range->offset) / range->step + 1;
> +    else
> +       reg = (val - range->offset) / range->step;
> +    return reg;
> +}

Add some description of what the function is doing, it's hard
to understand, kerneldoc please.

> +__maybe_unused static u32 reg2val(enum sc8541_reg_range id, u8 reg)
> +{
> +    const struct reg_range *range = &sc8541_reg_range[id];
> +
> +    if (!range)
> +       return reg;
> +    return range->table ? range->table[reg] :
> +                 range->offset + range->step * reg;
> +}

Same here.

> +static int sc8541_field_read(struct sc8541_chip *sc,
> +               enum sc8541_fields field_id, int *val)
> +{
> +    int ret;
> +
> +    ret = regmap_field_read(sc->rmap_fields[field_id], val);
> +    if (ret < 0) {
> +       dev_err(sc->dev, "sc8541 read field %d fail: %d\n", field_id, ret);
> +    }
> +
> +    return ret;
> +}
> +
> +static int sc8541_field_write(struct sc8541_chip *sc,
> +               enum sc8541_fields field_id, int val)
> +{
> +    int ret;
> +
> +    ret = regmap_field_write(sc->rmap_fields[field_id], val);
> +    if (ret < 0) {
> +       dev_err(sc->dev, "sc8541 read field %d fail: %d\n", field_id, ret);
> +    }
> +
> +    return ret;
> +}
> +
> +static int sc8541_read_block(struct sc8541_chip *sc,
> +               int reg, uint8_t *val, int len)
> +{
> +    int ret;
> +
> +    ret = regmap_bulk_read(sc->regmap, reg, val, len);
> +    if (ret < 0) {
> +       dev_err(sc->dev, "sc8541 read %02x block failed %d\n", reg, ret);
> +    }
> +
> +    return ret;
> +}

Does these three indirections really but you something? You just need to
handle the returned ret once more in the code. Isn't it better to just use
regmap_* field etc directly and report errors in the code.

> +__maybe_unused static int sc8541_reg_reset(struct sc8541_chip *sc)
> +{
> +    return sc8541_field_write(sc, REG_RST, 1);
> +}
> +
> +__maybe_unused static int sc8541_dump_reg(struct sc8541_chip *sc)
> +{
> +    int ret;
> +    int i;
> +    int val;
> +
> +    for (i = 0; i <= SC8541_REGMAX; i++) {
> +       ret = regmap_read(sc->regmap, i, &val);
> +       dev_err(sc->dev, "%s reg[0x%02x] = 0x%02x\n",
> +               __func__, i, val);
> +    }
> +
> +    return ret;
> +}
> +
> +__maybe_unused static int sc8541_enable_charge(struct sc8541_chip *sc, bool en)
> +{
> +    int ret;
> +
> +    dev_info(sc->dev, "%s:%d", __func__, en);
> +
> +    ret = sc8541_field_write(sc, CHG_EN, !!en);
> +
> +    return ret;
> +}
> +
> +
> +__maybe_unused static int sc8541_check_charge_enabled(struct sc8541_chip *sc, bool *enabled)
> +{
> +    int ret, val;
> +
> +    ret = sc8541_field_read(sc, CP_SWITCHING_STAT, &val);
> +
> +    *enabled = (bool)val;
> +
> +    dev_info(sc->dev, "%s:%d", __func__, val);
> +
> +    return ret;
> +}
> +
> +__maybe_unused static int sc8541_get_status(struct sc8541_chip *sc, uint32_t *status)
> +{
> +    int ret, val;
> +    *status = 0;
> +
> +    ret = sc8541_field_read(sc, VBUS_ERRORHI_STAT, &val);
> +    if (ret < 0) {
> +       dev_err(sc->dev, "%s fail to read VBUS_ERRORHI_STAT(%d)\n", __func__, ret);
> +       return ret;
> +    }
> +    if (val != 0)
> +       *status |= BIT(ERROR_VBUS_HIGH);
> +
> +    ret = sc8541_field_read(sc, VBUS_ERRORLO_STAT, &val);
> +    if (ret < 0) {
> +       dev_err(sc->dev, "%s fail to read VBUS_ERRORLO_STAT(%d)\n", __func__, ret);
> +       return ret;
> +    }
> +    if (val != 0)
> +       *status |= BIT(ERROR_VBUS_LOW);
> +
> +
> +    return ret;
> +
> +}
> +
> +__maybe_unused static int sc8541_enable_adc(struct sc8541_chip *sc, bool en)
> +{
> +    dev_info(sc->dev, "%s:%d", __func__, en);
> +    return sc8541_field_write(sc, ADC_EN, !!en);
> +}
> +
> +__maybe_unused static int sc8541_set_adc_scanrate(struct sc8541_chip *sc, bool oneshot)
> +{
> +    dev_info(sc->dev, "%s:%d", __func__, oneshot);
> +    return sc8541_field_write(sc, ADC_RATE, !!oneshot);
> +}

This overuse of __maybe_unused means you are potentially
compiling in a lot of crap that will not be used.

Consider using static inline in a .h file instead, then they will
just not be compiled if not used.

> +__maybe_unused static int sc8541_disable_vbusovp_alarm(struct sc8541_chip *sc, bool en)
> +{
> +    int ret;
> +
> +    dev_info(sc->dev, "%s:%d", __func__, en);

Convert all these to dev_debug() to not litter the log.

> +
> +    ret = sc8541_field_write(sc, VBUS_OVP_ALM_DIS, !!en);
> +
> +    return ret;
> +}

This kind of functions also look like unnecessary indirection to me
but at least rewrite them like this:

return sc8541_field_write(sc, VBUS_OVP_ALM_DIS, !!en);

and it saves you 4-5 lines of code in each functions ince you don't
need to declare ret and return it on a separate line.

> +static int mtk_sc8541_set_vbusovp(struct charger_device *chg_dev, u32 uV)
> +{
> +    struct sc8541_chip *sc = charger_get_data(chg_dev);
> +    int mv;
> +
> +    mv = uV / 1000;

If this is coming from an IIO ADC you can use the existing prescaler
in IIO instead.

ret = iio_read_channel_processed_scale(adc_main_charger_v,
                                                       &vch, 1000);

Otherwise, maybe your ADC  *should* be an IIO device, hm?

> +static int mtk_sc8541_get_adc(struct charger_device *chg_dev, enum adc_channel chan,
> +                         int *min, int *max)
> +{
> +    struct sc8541_chip *sc = charger_get_data(chg_dev);
> +
> +    sc8541_get_adc_data(sc, to_sc8541_adc(chan), max);
> +
> +    if (chan != ADC_CHANNEL_TEMP_JC)
> +       *max = *max * 1000;
> +
> +    if (min != max)
> +               *min = *max;
> +
> +    return 0;
> +}
> +
> +static int mtk_sc8541_get_adc_accuracy(struct charger_device *chg_dev,
> +                                  enum adc_channel chan, int *min, int *max)
> +{
> +    *min = *max = sc8541_adc_accuracy_tbl[to_sc8541_adc(chan)];
> +    return 0;
> +}

Yeah this looks like this part of the driver should be in
drivers/iio/adc/*

> +static int sc8541_register_interrupt(struct sc8541_chip *sc)
> +{
> +    int ret;
> +
> +    if (gpio_is_valid(sc->irq_gpio)) {
> +       ret = gpio_request_one(sc->irq_gpio, GPIOF_IN, "sc8541_irq");
> +       if (ret) {
> +            dev_err(sc->dev, "failed to request sc8541_irq\n");
> +           return -EINVAL;
> +       }
> +       sc->irq = gpio_to_irq(sc->irq_gpio);
> +       if (sc->irq < 0) {
> +            dev_err(sc->dev, "failed to gpio_to_irq\n");
> +           return -EINVAL;
> +       }

NACK do not use these old GPIO APIs.

Use devm_gpiod_get() instead.

Just grep in the kernel for many good examples of how to
do this:

struct gpio_desc *gd = devm_gpiod_get(dev, "irq", GPIOD_IN);
int irq = gpiod_to_irq(gd);
(etc)

> +    if (sc->irq) {
> +       ret = devm_request_threaded_irq(&sc->client->dev, sc->irq,
> +               NULL, sc8541_irq_handler,
> +               IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
> +               sc8541_irq_name[sc->mode], sc);
> +
> +       if (ret < 0) {
> +            dev_err(sc->dev, "request irq for irq=%d failed, ret =%d\n",
> +                           sc->irq, ret);
> +           return ret;
> +       }
> +       enable_irq_wake(sc->irq);

It's nice that you use threaded IRQs with oneshot!

> +static int sc8541_set_work_mode(struct sc8541_chip *sc, int mode)
> +{
> +    sc->mode = mode;
> +
> +    dev_err(sc->dev, "work mode is %s\n", sc->mode == SC8541_STANDALONE
> +       ? "standalone" : (sc->mode == SC8541_MASTER ? "master" : "slave"));

Why is this dev_err()?

It's not an error.

> +#ifdef CONFIG_PM_SLEEP
> +static int sc8541_suspend(struct device *dev)
> +{
> +    struct sc8541_chip *sc = dev_get_drvdata(dev);
> +
> +    dev_info(sc->dev, "Suspend successfully!");
> +    if (device_may_wakeup(dev))
> +       enable_irq_wake(sc->irq);
> +    disable_irq(sc->irq);
> +
> +    return 0;
> +}
> +static int sc8541_resume(struct device *dev)
> +{
> +    struct sc8541_chip *sc = dev_get_drvdata(dev);
> +
> +    dev_info(sc->dev, "Resume successfully!");
> +    if (device_may_wakeup(dev))
> +       disable_irq_wake(sc->irq);
> +    enable_irq(sc->irq);
> +
> +    return 0;
> +}
> +
> +static const struct dev_pm_ops sc8541_pm = {
> +    SET_SYSTEM_SLEEP_PM_OPS(sc8541_suspend, sc8541_resume)
> +};
> +#endif

IIRC there is a new API for this, look through recent changes to the
kernel concerning PM ops. I don't think you need the #ifdefs anymore.

> +MODULE_DESCRIPTION("SC SC8541 Driver");
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("South Chip <Aiden-yu@...thchip.com>");

I think this is supposed to be a real person, not a function, can you
put in your own name and mail instead?

Yours,
Linus Walleij

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ