Message ID | 20200924192455.2484005-4-j.neuschaefer@gmx.net (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | Netronix embedded controller driver for Kobo and Tolino ebook readers | expand |
On Thu, Sep 24, 2020 at 10:26 PM Jonathan Neuschäfer <j.neuschaefer@gmx.net> wrote: > > The Netronix embedded controller is a microcontroller found in some > e-book readers designed by the ODM Netronix, Inc. It contains RTC, > battery monitoring, system power management, and PWM functionality. > > This driver implements register access and version detection. > > Third-party hardware documentation is available at: > > https://github.com/neuschaefer/linux/wiki/Netronix-MSP430-embedded-controller > > The EC supports interrupts, but the driver doesn't make use of them so > far. ... > +#include <asm/unaligned.h> This usually goes after linux/*.h (and actually not visible how it's being used, but see below first) > +#include <linux/delay.h> > +#include <linux/errno.h> > +#include <linux/i2c.h> > +#include <linux/mfd/core.h> > +#include <linux/mfd/ntxec.h> > +#include <linux/module.h> > +#include <linux/pm.h> > +#include <linux/reboot.h> > +#include <linux/regmap.h> > +#include <linux/types.h> ... > +static void ntxec_poweroff(void) > +{ > + int res; > + u8 buf[] = { > + NTXEC_REG_POWEROFF, > + (NTXEC_POWEROFF_VALUE >> 8) & 0xff, > + NTXEC_POWEROFF_VALUE & 0xff, '& 0xff' parts are redundant. *u8 implies that. Fix in all cases. Also I would rather see something like buf[0] = _POWEROFF; put_unaligned_be16(_VALUE, &buf[1]); to explicitly show the endianess of the register values. > + }; > + struct i2c_msg msgs[] = { > + { > + .addr = poweroff_restart_client->addr, > + .flags = 0, > + .len = sizeof(buf), > + .buf = buf It's slightly better to keep trailing commas in cases like this. > + } > + }; > + > + res = i2c_transfer(poweroff_restart_client->adapter, msgs, ARRAY_SIZE(msgs)); > + if (res < 0) > + dev_alert(&poweroff_restart_client->dev, > + "Failed to power off (err = %d)\n", res); alert? This needs to be explained. > + /* > + * The time from the register write until the host CPU is powered off > + * has been observed to be about 2.5 to 3 seconds. Sleep long enough to > + * safely avoid returning from the poweroff handler. > + */ > + msleep(5000); > +} > + > +static int ntxec_restart(struct notifier_block *nb, > + unsigned long action, void *data) > +{ > + int res; > + /* > + * NOTE: The lower half of the reset value is not sent, because sending > + * it causes an error Why? Any root cause? Perhaps you need to send 0xffff ? > + */ > + u8 buf[] = { > + NTXEC_REG_RESET, > + (NTXEC_RESET_VALUE >> 8) & 0xff, Here you may still use put_unaligned_be16() but move the comment to be before len hardcoded to sizeof(buf) - 1. > + }; > + struct i2c_msg msgs[] = { > + { > + .addr = poweroff_restart_client->addr, > + .flags = 0, > + .len = sizeof(buf), > + .buf = buf > + } > + }; > + > + res = i2c_transfer(poweroff_restart_client->adapter, msgs, ARRAY_SIZE(msgs)); > + if (res < 0) > + dev_alert(&poweroff_restart_client->dev, > + "Failed to restart (err = %d)\n", res); > + > + return NOTIFY_DONE; > +} ... > +static int ntxec_probe(struct i2c_client *client) > +{ > + struct ntxec *ec; > + unsigned int version; > + int res; > + > + ec = devm_kmalloc(&client->dev, sizeof(*ec), GFP_KERNEL); > + if (!ec) > + return -ENOMEM; > + > + ec->dev = &client->dev; > + > + ec->regmap = devm_regmap_init_i2c(client, ®map_config); > + if (IS_ERR(ec->regmap)) { > + dev_err(ec->dev, "Failed to set up regmap for device\n"); > + return res; > + } > + > + /* Determine the firmware version */ > + res = regmap_read(ec->regmap, NTXEC_REG_VERSION, &version); > + if (res < 0) { > + dev_err(ec->dev, "Failed to read firmware version number\n"); > + return res; > + } > + dev_info(ec->dev, > + "Netronix embedded controller version %04x detected.\n", > + version); This info level may confuse users if followed by an error path. > + /* Bail out if we encounter an unknown firmware version */ > + switch (version) { > + case 0xd726: /* found in Kobo Aura */ > + break; > + default: > + return -ENODEV; > + } > + > + if (of_device_is_system_power_controller(ec->dev->of_node)) { > + /* > + * Set the 'powerkeep' bit. This is necessary on some boards > + * in order to keep the system running. > + */ > + res = regmap_write(ec->regmap, NTXEC_REG_POWERKEEP, > + NTXEC_POWERKEEP_VALUE); > + if (res < 0) > + return res; > + WARN_ON(poweroff_restart_client); WARN_ON? All these alerts, WARNs, BUGs must be explained. Screaming to the user is not good if it wasn't justified. > + poweroff_restart_client = client; > + if (pm_power_off) > + dev_err(ec->dev, "pm_power_off already assigned\n"); > + else > + pm_power_off = ntxec_poweroff; > + > + res = register_restart_handler(&ntxec_restart_handler); > + if (res) > + dev_err(ec->dev, > + "Failed to register restart handler: %d\n", res); > + } > + > + i2c_set_clientdata(client, ec); > + > + res = devm_mfd_add_devices(ec->dev, PLATFORM_DEVID_NONE, ntxec_subdevices, > + ARRAY_SIZE(ntxec_subdevices), NULL, 0, NULL); > + if (res) > + dev_warn(ec->dev, "Failed to add subdevices: %d\n", res); 'warn' is inconsistent with 'return err'. Either do not return an error, or mark a message as an error one. And above with the restart handler has the same issue. > + return res; > +} > + > +static int ntxec_remove(struct i2c_client *client) > +{ > + if (client == poweroff_restart_client) { When it's not the case? > + poweroff_restart_client = NULL; > + pm_power_off = NULL; > + unregister_restart_handler(&ntxec_restart_handler); > + } > + > + return 0; > +} ... > +#include <linux/types.h> > + Missed struct device; struct regmap; here. > +struct ntxec { > + struct device *dev; > + struct regmap *regmap; > +}; > +/* > + * Some registers, such as the battery status register (0x41), are in > + * big-endian, but others only have eight significant bits, which are in the > + * first byte transmitted over I2C (the MSB of the big-endian value). > + * This convenience function converts an 8-bit value to 16-bit for use in the > + * second kind of register. > + */ > +static inline u16 ntxec_reg8(u8 value) > +{ > + return value << 8; > +} I'm wondering why __be16 is not used as returned type.
On Fri, Sep 25, 2020 at 12:29:45PM +0300, Andy Shevchenko wrote: > On Thu, Sep 24, 2020 at 10:26 PM Jonathan Neuschäfer > <j.neuschaefer@gmx.net> wrote: > > > > The Netronix embedded controller is a microcontroller found in some > > e-book readers designed by the ODM Netronix, Inc. It contains RTC, > > battery monitoring, system power management, and PWM functionality. > > > > This driver implements register access and version detection. > > > > Third-party hardware documentation is available at: > > > > https://github.com/neuschaefer/linux/wiki/Netronix-MSP430-embedded-controller > > > > The EC supports interrupts, but the driver doesn't make use of them so > > far. > > ... > > > +#include <asm/unaligned.h> > > This usually goes after linux/*.h > (and actually not visible how it's being used, but see below first) I think it was a leftover from v1 before I used regmap for general access to the registers. Will fix the ordering. > > +#include <linux/delay.h> > > +#include <linux/errno.h> > > +#include <linux/i2c.h> > > +#include <linux/mfd/core.h> > > +#include <linux/mfd/ntxec.h> > > +#include <linux/module.h> > > +#include <linux/pm.h> > > +#include <linux/reboot.h> > > +#include <linux/regmap.h> > > +#include <linux/types.h> > > ... > > > +static void ntxec_poweroff(void) > > +{ > > + int res; > > + u8 buf[] = { > > + NTXEC_REG_POWEROFF, > > > + (NTXEC_POWEROFF_VALUE >> 8) & 0xff, > > + NTXEC_POWEROFF_VALUE & 0xff, > > '& 0xff' parts are redundant. *u8 implies that. Fix in all cases. > Also I would rather see something like > > buf[0] = _POWEROFF; > put_unaligned_be16(_VALUE, &buf[1]); > > to explicitly show the endianess of the register values. Good idea. > > + }; > > + struct i2c_msg msgs[] = { > > + { > > + .addr = poweroff_restart_client->addr, > > + .flags = 0, > > + .len = sizeof(buf), > > > + .buf = buf > > It's slightly better to keep trailing commas in cases like this. Ok. > > > + } > > + }; > > + > > + res = i2c_transfer(poweroff_restart_client->adapter, msgs, ARRAY_SIZE(msgs)); > > + if (res < 0) > > > + dev_alert(&poweroff_restart_client->dev, > > + "Failed to power off (err = %d)\n", res); > > alert? This needs to be explained. I copied the dev_alert from drivers/mfd/rn5t618.c. Upon reconsideration, I'm not sure what the correct log level would be, but _warn seems enough, or maybe _err is better > > + /* > > + * The time from the register write until the host CPU is powered off > > + * has been observed to be about 2.5 to 3 seconds. Sleep long enough to > > + * safely avoid returning from the poweroff handler. > > + */ > > + msleep(5000); > > +} > > + > > +static int ntxec_restart(struct notifier_block *nb, > > + unsigned long action, void *data) > > +{ > > + int res; > > + /* > > + * NOTE: The lower half of the reset value is not sent, because sending > > + * it causes an error > > Why? Any root cause? Perhaps you need to send 0xffff ? Unknown, because I don't have the EC firmware for analysis. The vendor kernel sends 0xff00 and gets an error. Sending 0xffff doesn't help. > > + */ > > + u8 buf[] = { > > + NTXEC_REG_RESET, > > > + (NTXEC_RESET_VALUE >> 8) & 0xff, > > Here you may still use put_unaligned_be16() but move the comment to be > before len hardcoded to sizeof(buf) - 1. Okay, will do. > > > + }; > > + struct i2c_msg msgs[] = { > > + { > > + .addr = poweroff_restart_client->addr, > > + .flags = 0, > > + .len = sizeof(buf), > > + .buf = buf > > + } > > + }; > > + > > + res = i2c_transfer(poweroff_restart_client->adapter, msgs, ARRAY_SIZE(msgs)); > > + if (res < 0) > > + dev_alert(&poweroff_restart_client->dev, > > + "Failed to restart (err = %d)\n", res); > > + > > + return NOTIFY_DONE; > > +} > > ... An error in the i2c transfer here is an abnormal situation that should in my opinion be logged, but I don't see what else the code can do here to handle the error. > > > +static int ntxec_probe(struct i2c_client *client) > > +{ > > + struct ntxec *ec; > > + unsigned int version; > > + int res; > > + > > + ec = devm_kmalloc(&client->dev, sizeof(*ec), GFP_KERNEL); > > + if (!ec) > > + return -ENOMEM; > > + > > + ec->dev = &client->dev; > > + > > + ec->regmap = devm_regmap_init_i2c(client, ®map_config); > > + if (IS_ERR(ec->regmap)) { > > + dev_err(ec->dev, "Failed to set up regmap for device\n"); > > + return res; > > + } > > + > > + /* Determine the firmware version */ > > + res = regmap_read(ec->regmap, NTXEC_REG_VERSION, &version); > > + if (res < 0) { > > + dev_err(ec->dev, "Failed to read firmware version number\n"); > > + return res; > > + } > > > + dev_info(ec->dev, > > + "Netronix embedded controller version %04x detected.\n", > > + version); > > This info level may confuse users if followed by an error path. Right. I suppose printing incompatible versions is still useful, so how about something like the following? /* Bail out if we encounter an unknown firmware version */ switch (version) { case 0xd726: /* found in Kobo Aura */ dev_info(ec->dev, "Netronix embedded controller version %04x detected.\n", version); break; default: dev_err(ec->dev, "Netronix embedded controller version %04x is not supported.\n", version); return -ENODEV; } > > + > > + if (of_device_is_system_power_controller(ec->dev->of_node)) { > > + /* > > + * Set the 'powerkeep' bit. This is necessary on some boards > > + * in order to keep the system running. > > + */ > > + res = regmap_write(ec->regmap, NTXEC_REG_POWERKEEP, > > + NTXEC_POWERKEEP_VALUE); > > + if (res < 0) > > + return res; > > > + WARN_ON(poweroff_restart_client); > > WARN_ON? All these alerts, WARNs, BUGs must be explained. Screaming to > the user is not good if it wasn't justified. poweroff_restart_client being already set is not a situation that should happen (and would indicate a bug in this driver, AFAICT), but I guess the log message could be better in that case... > > + poweroff_restart_client = client; > > + if (pm_power_off) > > + dev_err(ec->dev, "pm_power_off already assigned\n"); > > + else > > + pm_power_off = ntxec_poweroff; > > + > > + res = register_restart_handler(&ntxec_restart_handler); > > + if (res) > > + dev_err(ec->dev, > > + "Failed to register restart handler: %d\n", res); > > + } > > + > > + i2c_set_clientdata(client, ec); > > + > > + res = devm_mfd_add_devices(ec->dev, PLATFORM_DEVID_NONE, ntxec_subdevices, > > + ARRAY_SIZE(ntxec_subdevices), NULL, 0, NULL); > > + if (res) > > > + dev_warn(ec->dev, "Failed to add subdevices: %d\n", res); > > 'warn' is inconsistent with 'return err'. Either do not return an > error, or mark a message as an error one. Okay, I'll change it to dev_err. > > And above with the restart handler has the same issue. > > > + return res; > > +} > > + > > +static int ntxec_remove(struct i2c_client *client) > > +{ > > > + if (client == poweroff_restart_client) { > > When it's not the case? The EC doesn't always need to provide poweroff/restart functionality, and AFAIK, in some systems it doesn't. In those systems, ntxec_remove would run with poweroff_restart_client == NULL. In theory, there might also be two of it in the same system, of which only one controls system poweroff/restart, but I'm not sure if that is actually the case on any existing board design. > > + poweroff_restart_client = NULL; > > + pm_power_off = NULL; > > + unregister_restart_handler(&ntxec_restart_handler); > > + } > > + > > + return 0; > > +} > > ... > > > +#include <linux/types.h> > > + > > Missed > > struct device; > struct regmap; > > here. I'll add them. > > +struct ntxec { > > + struct device *dev; > > + struct regmap *regmap; > > +}; > > > +/* > > + * Some registers, such as the battery status register (0x41), are in > > + * big-endian, but others only have eight significant bits, which are in the > > + * first byte transmitted over I2C (the MSB of the big-endian value). > > + * This convenience function converts an 8-bit value to 16-bit for use in the > > + * second kind of register. > > + */ > > +static inline u16 ntxec_reg8(u8 value) > > +{ > > + return value << 8; > > +} > > I'm wondering why __be16 is not used as returned type. I didn't think of it, but it's a good idea. Will do. Thanks, Jonathan Neuschäfer
On Sat, Sep 26, 2020 at 12:32 AM Jonathan Neuschäfer <j.neuschaefer@gmx.net> wrote: > On Fri, Sep 25, 2020 at 12:29:45PM +0300, Andy Shevchenko wrote: > > On Thu, Sep 24, 2020 at 10:26 PM Jonathan Neuschäfer > > <j.neuschaefer@gmx.net> wrote: ... > > > + dev_alert(&poweroff_restart_client->dev, > > > + "Failed to power off (err = %d)\n", res); > > > > alert? This needs to be explained. > > I copied the dev_alert from drivers/mfd/rn5t618.c. > > Upon reconsideration, I'm not sure what the correct log level would be, > but _warn seems enough, or maybe _err is better It's up to you to decide, but crit/alert and similar has to be justified (commit message / comment in the code). ... > > > + /* > > > + * NOTE: The lower half of the reset value is not sent, because sending > > > + * it causes an error > > > > Why? Any root cause? Perhaps you need to send 0xffff ? > > Unknown, because I don't have the EC firmware for analysis. The vendor > kernel sends 0xff00 and gets an error. > > Sending 0xffff doesn't help. Maybe a slightly elaborated comment that it's copied from the vendor kernel (which is?). > > > + */ ... > > > + dev_info(ec->dev, > > > + "Netronix embedded controller version %04x detected.\n", > > > + version); > > > > This info level may confuse users if followed by an error path. > > Right. I suppose printing incompatible versions is still useful, so how > about something like the following? > > > /* Bail out if we encounter an unknown firmware version */ > switch (version) { > case 0xd726: /* found in Kobo Aura */ > dev_info(ec->dev, > "Netronix embedded controller version %04x detected.\n", > version); > break; > default: > dev_err(ec->dev, > "Netronix embedded controller version %04x is not supported.\n", > version); > return -ENODEV; > } This is better. ... > > > + WARN_ON(poweroff_restart_client); > > > > WARN_ON? All these alerts, WARNs, BUGs must be explained. Screaming to > > the user is not good if it wasn't justified. > > poweroff_restart_client being already set is not a situation that should > happen (and would indicate a bug in this driver, AFAICT), but I guess > the log message could be better in that case... As per above.
On Tue, Sep 29, 2020 at 07:37:21PM +0300, Andy Shevchenko wrote: > On Sat, Sep 26, 2020 at 12:32 AM Jonathan Neuschäfer > <j.neuschaefer@gmx.net> wrote: > > On Fri, Sep 25, 2020 at 12:29:45PM +0300, Andy Shevchenko wrote: > > > On Thu, Sep 24, 2020 at 10:26 PM Jonathan Neuschäfer > > > <j.neuschaefer@gmx.net> wrote: > > ... > > > > > + dev_alert(&poweroff_restart_client->dev, > > > > + "Failed to power off (err = %d)\n", res); > > > > > > alert? This needs to be explained. > > > > I copied the dev_alert from drivers/mfd/rn5t618.c. > > > > Upon reconsideration, I'm not sure what the correct log level would be, > > but _warn seems enough, or maybe _err is better > > It's up to you to decide, but crit/alert and similar has to be > justified (commit message / comment in the code). Alright, thanks for explaining. > > > > + /* > > > > + * NOTE: The lower half of the reset value is not sent, because sending > > > > + * it causes an error > > > > > > Why? Any root cause? Perhaps you need to send 0xffff ? > > > > Unknown, because I don't have the EC firmware for analysis. The vendor > > kernel sends 0xff00 and gets an error. > > > > Sending 0xffff doesn't help. > > Maybe a slightly elaborated comment that it's copied from the vendor > kernel (which is?). Good idea, I'll do that. > ... > > > > > + dev_info(ec->dev, > > > > + "Netronix embedded controller version %04x detected.\n", > > > > + version); > > > > > > This info level may confuse users if followed by an error path. > > > > Right. I suppose printing incompatible versions is still useful, so how > > about something like the following? > > > > > > /* Bail out if we encounter an unknown firmware version */ > > switch (version) { > > case 0xd726: /* found in Kobo Aura */ > > dev_info(ec->dev, > > "Netronix embedded controller version %04x detected.\n", > > version); > > break; > > default: > > dev_err(ec->dev, > > "Netronix embedded controller version %04x is not supported.\n", > > version); > > return -ENODEV; > > } > > This is better. > > ... > > > > > + WARN_ON(poweroff_restart_client); > > > > > > WARN_ON? All these alerts, WARNs, BUGs must be explained. Screaming to > > > the user is not good if it wasn't justified. > > > > poweroff_restart_client being already set is not a situation that should > > happen (and would indicate a bug in this driver, AFAICT), but I guess > > the log message could be better in that case... > > As per above. Okay, I'll rework the dev_alert/WARN_ON parts. Thanks, Jonathan Neuschäfer
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 33df0837ab415..b313103151508 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -978,6 +978,16 @@ config MFD_VIPERBOARD You need to select the mfd cell drivers separately. The drivers do not support all features the board exposes. +config MFD_NTXEC + tristate "Netronix embedded controller (EC)" + depends on OF || COMPILE_TEST + depends on I2C + select REGMAP_I2C + select MFD_CORE + help + Say yes here if you want to support the embedded controller found in + certain e-book readers designed by the ODM Netronix. + config MFD_RETU tristate "Nokia Retu and Tahvo multi-function device" select MFD_CORE diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index a60e5f835283e..236a8acd917a0 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -217,6 +217,7 @@ obj-$(CONFIG_MFD_INTEL_MSIC) += intel_msic.o obj-$(CONFIG_MFD_INTEL_PMC_BXT) += intel_pmc_bxt.o obj-$(CONFIG_MFD_PALMAS) += palmas.o obj-$(CONFIG_MFD_VIPERBOARD) += viperboard.o +obj-$(CONFIG_MFD_NTXEC) += ntxec.o obj-$(CONFIG_MFD_RC5T583) += rc5t583.o rc5t583-irq.o obj-$(CONFIG_MFD_RK808) += rk808.o obj-$(CONFIG_MFD_RN5T618) += rn5t618.o diff --git a/drivers/mfd/ntxec.c b/drivers/mfd/ntxec.c new file mode 100644 index 0000000000000..93611b85a32e0 --- /dev/null +++ b/drivers/mfd/ntxec.c @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * The Netronix embedded controller is a microcontroller found in some + * e-book readers designed by the ODM Netronix, Inc. It contains RTC, + * battery monitoring, system power management, and PWM functionality. + * + * This driver implements register access, version detection, and system + * power-off/reset. + * + * Copyright 2020 Jonathan Neuschäfer <j.neuschaefer@gmx.net> + */ + +#include <asm/unaligned.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/i2c.h> +#include <linux/mfd/core.h> +#include <linux/mfd/ntxec.h> +#include <linux/module.h> +#include <linux/pm.h> +#include <linux/reboot.h> +#include <linux/regmap.h> +#include <linux/types.h> + +#define NTXEC_REG_VERSION 0x00 +#define NTXEC_REG_POWEROFF 0x50 +#define NTXEC_REG_POWERKEEP 0x70 +#define NTXEC_REG_RESET 0x90 + +#define NTXEC_POWEROFF_VALUE 0x0100 +#define NTXEC_POWERKEEP_VALUE 0x0800 +#define NTXEC_RESET_VALUE 0xff00 + +static struct i2c_client *poweroff_restart_client; + +static void ntxec_poweroff(void) +{ + int res; + u8 buf[] = { + NTXEC_REG_POWEROFF, + (NTXEC_POWEROFF_VALUE >> 8) & 0xff, + NTXEC_POWEROFF_VALUE & 0xff, + }; + struct i2c_msg msgs[] = { + { + .addr = poweroff_restart_client->addr, + .flags = 0, + .len = sizeof(buf), + .buf = buf + } + }; + + res = i2c_transfer(poweroff_restart_client->adapter, msgs, ARRAY_SIZE(msgs)); + if (res < 0) + dev_alert(&poweroff_restart_client->dev, + "Failed to power off (err = %d)\n", res); + + /* + * The time from the register write until the host CPU is powered off + * has been observed to be about 2.5 to 3 seconds. Sleep long enough to + * safely avoid returning from the poweroff handler. + */ + msleep(5000); +} + +static int ntxec_restart(struct notifier_block *nb, + unsigned long action, void *data) +{ + int res; + /* + * NOTE: The lower half of the reset value is not sent, because sending + * it causes an error + */ + u8 buf[] = { + NTXEC_REG_RESET, + (NTXEC_RESET_VALUE >> 8) & 0xff, + }; + struct i2c_msg msgs[] = { + { + .addr = poweroff_restart_client->addr, + .flags = 0, + .len = sizeof(buf), + .buf = buf + } + }; + + res = i2c_transfer(poweroff_restart_client->adapter, msgs, ARRAY_SIZE(msgs)); + if (res < 0) + dev_alert(&poweroff_restart_client->dev, + "Failed to restart (err = %d)\n", res); + + return NOTIFY_DONE; +} + +static struct notifier_block ntxec_restart_handler = { + .notifier_call = ntxec_restart, + .priority = 128 +}; + +static const struct regmap_config regmap_config = { + .name = "ntxec", + .reg_bits = 8, + .val_bits = 16, + .cache_type = REGCACHE_NONE, + .val_format_endian = REGMAP_ENDIAN_BIG, +}; + +static const struct mfd_cell ntxec_subdevices[] = { + { .name = "ntxec-rtc" }, + { .name = "ntxec-pwm" }, +}; + +static int ntxec_probe(struct i2c_client *client) +{ + struct ntxec *ec; + unsigned int version; + int res; + + ec = devm_kmalloc(&client->dev, sizeof(*ec), GFP_KERNEL); + if (!ec) + return -ENOMEM; + + ec->dev = &client->dev; + + ec->regmap = devm_regmap_init_i2c(client, ®map_config); + if (IS_ERR(ec->regmap)) { + dev_err(ec->dev, "Failed to set up regmap for device\n"); + return res; + } + + /* Determine the firmware version */ + res = regmap_read(ec->regmap, NTXEC_REG_VERSION, &version); + if (res < 0) { + dev_err(ec->dev, "Failed to read firmware version number\n"); + return res; + } + dev_info(ec->dev, + "Netronix embedded controller version %04x detected.\n", + version); + + /* Bail out if we encounter an unknown firmware version */ + switch (version) { + case 0xd726: /* found in Kobo Aura */ + break; + default: + return -ENODEV; + } + + if (of_device_is_system_power_controller(ec->dev->of_node)) { + /* + * Set the 'powerkeep' bit. This is necessary on some boards + * in order to keep the system running. + */ + res = regmap_write(ec->regmap, NTXEC_REG_POWERKEEP, + NTXEC_POWERKEEP_VALUE); + if (res < 0) + return res; + + WARN_ON(poweroff_restart_client); + poweroff_restart_client = client; + if (pm_power_off) + dev_err(ec->dev, "pm_power_off already assigned\n"); + else + pm_power_off = ntxec_poweroff; + + res = register_restart_handler(&ntxec_restart_handler); + if (res) + dev_err(ec->dev, + "Failed to register restart handler: %d\n", res); + } + + i2c_set_clientdata(client, ec); + + res = devm_mfd_add_devices(ec->dev, PLATFORM_DEVID_NONE, ntxec_subdevices, + ARRAY_SIZE(ntxec_subdevices), NULL, 0, NULL); + if (res) + dev_warn(ec->dev, "Failed to add subdevices: %d\n", res); + + return res; +} + +static int ntxec_remove(struct i2c_client *client) +{ + if (client == poweroff_restart_client) { + poweroff_restart_client = NULL; + pm_power_off = NULL; + unregister_restart_handler(&ntxec_restart_handler); + } + + return 0; +} + +static const struct of_device_id of_ntxec_match_table[] = { + { .compatible = "netronix,ntxec", }, + {} +}; + +static struct i2c_driver ntxec_driver = { + .driver = { + .name = "ntxec", + .of_match_table = of_ntxec_match_table, + }, + .probe_new = ntxec_probe, + .remove = ntxec_remove, +}; +module_i2c_driver(ntxec_driver); diff --git a/include/linux/mfd/ntxec.h b/include/linux/mfd/ntxec.h new file mode 100644 index 0000000000000..a39c85978f61b --- /dev/null +++ b/include/linux/mfd/ntxec.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2020 Jonathan Neuschäfer + * + * Register access and version information for the Netronix embedded + * controller. + */ + +#ifndef NTXEC_H +#define NTXEC_H + +#include <linux/types.h> + +struct ntxec { + struct device *dev; + struct regmap *regmap; +}; + +/* + * Some registers, such as the battery status register (0x41), are in + * big-endian, but others only have eight significant bits, which are in the + * first byte transmitted over I2C (the MSB of the big-endian value). + * This convenience function converts an 8-bit value to 16-bit for use in the + * second kind of register. + */ +static inline u16 ntxec_reg8(u8 value) +{ + return value << 8; +} + +#endif
The Netronix embedded controller is a microcontroller found in some e-book readers designed by the ODM Netronix, Inc. It contains RTC, battery monitoring, system power management, and PWM functionality. This driver implements register access and version detection. Third-party hardware documentation is available at: https://github.com/neuschaefer/linux/wiki/Netronix-MSP430-embedded-controller The EC supports interrupts, but the driver doesn't make use of them so far. Signed-off-by: Jonathan Neuschäfer <j.neuschaefer@gmx.net> --- v3: - Add (EC) to CONFIG_MFD_NTXEC prompt - Relicense as GPLv2 or later - Add email address to copyright line - remove empty lines in ntxec_poweroff and ntxec_restart functions - Split long lines - Remove 'Install ... handler' comments - Make naming of struct i2c_client parameter consistent - Remove struct ntxec_info - Rework 'depends on' lines in Kconfig, hard-depend on I2C, select REGMAP_I2C and MFD_CORE - Register subdevices via mfd_cells - Move 8-bit register conversion to ntxec.h v2: - https://lore.kernel.org/lkml/20200905133230.1014581-4-j.neuschaefer@gmx.net/ - Add a description of the device to the patch text - Unify spelling as 'Netronix embedded controller'. 'Netronix' is the proper name of the manufacturer, but 'embedded controller' is just a label that I have assigned to the device. - Switch to regmap, avoid regmap use in poweroff and reboot handlers. Inspired by cf84dc0bb40f4 ("mfd: rn5t618: Make restart handler atomic safe") - Use a list of known-working firmware versions instead of checking for a known-incompatible version - Prefix registers with NTXEC_REG_ - Define register values as constants - Various style cleanups as suggested by Lee Jones - Don't align = signs in struct initializers [Uwe Kleine-König] - Don't use dev_dbg for an error message - Explain sleep in poweroff handler - Remove (struct ntxec).client - Switch to .probe_new in i2c driver - Add .remove callback - Make CONFIG_MFD_NTXEC a tristate option --- drivers/mfd/Kconfig | 10 ++ drivers/mfd/Makefile | 1 + drivers/mfd/ntxec.c | 206 ++++++++++++++++++++++++++++++++++++++ include/linux/mfd/ntxec.h | 31 ++++++ 4 files changed, 248 insertions(+) create mode 100644 drivers/mfd/ntxec.c create mode 100644 include/linux/mfd/ntxec.h -- 2.28.0