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 > 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 > > 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 > > +#include > > +#include > > +#include > > +#include > > +#include > > +#include > > +#include > > +#include > > +#include > > ... > > > +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 > > + > > 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