mbox series

[v7,0/7] Netronix embedded controller driver for Kobo and Tolino ebook readers

Message ID 20210109180220.121511-1-j.neuschaefer@gmx.net
Headers show
Series Netronix embedded controller driver for Kobo and Tolino ebook readers | expand

Message

J. Neuschäfer Jan. 9, 2021, 6:02 p.m. UTC
This patchset adds basic support for the embedded controller found on
older ebook reader boards designed by/with the ODM Netronix Inc.[1] and
sold by Kobo or Tolino, for example the Kobo Aura and the Tolino Shine.
These drivers are based on information contained in the vendor kernel
sources, but in order to all information in a single place, I documented
the register interface of the EC on GitHub[2].

[1]: http://www.netronixinc.com/products.aspx?ID=1
[2]: https://github.com/neuschaefer/linux/wiki/Netronix-MSP430-embedded-controller

v7:
- Adjust the RTC patch to a change in the RTC API:
  rtc_register_device is now devm_rtc_register_device.
- Add a #define for the known firmware version (0xd726).
  Lee Jones suggested doing this in a follow-up patch, but since I'm
  respinning the series anyway, I'm doing it here.

v6:
- Additional cleanups in the PWM driver
- Added Lee Jones' ACK to the MFD driver patch

v5:
- https://lore.kernel.org/lkml/20201201011513.1627028-1-j.neuschaefer@gmx.net/
- Avoid truncation of PWM period and duty cycle to 32 bits
- A few cleanups and additional comments in the PWM driver
- Use regmap_multi_reg_write

v4:
- https://lore.kernel.org/lkml/20201122222739.1455132-1-j.neuschaefer@gmx.net/
- Spell out ODM (original design manufacturer)
- Solve corner cases in the RTC driver
- Clean up use of log levels vs. error codes
- Add more comments explaining some peculiarities
- Add missing MODULE_ALIAS lines
- Various other cleanups


v3:
- https://lore.kernel.org/lkml/20200924192455.2484005-1-j.neuschaefer@gmx.net/
- A few code cleanups
- A few devicetree related cleanups
- PWM and RTC functionality were moved from subnodes in the devicetree to
  the main node. This also means that the subdrivers no longer need DT
  compatible strings, but are instead loaded via the mfd_cell mechanism.
- The drivers are now published under GPLv2-or-later rather than GPLv2-only.


v2:
- https://lore.kernel.org/lkml/20200905133230.1014581-1-j.neuschaefer@gmx.net/
- Moved txt DT bindings to patch descriptions and removed patch 1/10
  "DT bindings in plain text format"
- New patch 7/10 "rtc: Introduce RTC_TIMESTAMP_END_2255"
- Rebased on 5.9-rc3
- Various other changes which are documented in each patch

v1:
- https://lore.kernel.org/lkml/20200620223915.1311485-1-j.neuschaefer@gmx.net/


Jonathan Neuschäfer (7):
  dt-bindings: Add vendor prefix for Netronix, Inc.
  dt-bindings: mfd: Add binding for Netronix embedded controller
  mfd: Add base driver for Netronix embedded controller
  pwm: ntxec: Add driver for PWM function in Netronix EC
  rtc: New driver for RTC in Netronix embedded controller
  MAINTAINERS: Add entry for Netronix embedded controller
  ARM: dts: imx50-kobo-aura: Add Netronix embedded controller

 .../bindings/mfd/netronix,ntxec.yaml          |  76 ++++++
 .../devicetree/bindings/vendor-prefixes.yaml  |   2 +
 MAINTAINERS                                   |   9 +
 arch/arm/boot/dts/imx50-kobo-aura.dts         |  16 +-
 drivers/mfd/Kconfig                           |  11 +
 drivers/mfd/Makefile                          |   1 +
 drivers/mfd/ntxec.c                           | 216 ++++++++++++++++++
 drivers/pwm/Kconfig                           |   8 +
 drivers/pwm/Makefile                          |   1 +
 drivers/pwm/pwm-ntxec.c                       | 182 +++++++++++++++
 drivers/rtc/Kconfig                           |   8 +
 drivers/rtc/Makefile                          |   1 +
 drivers/rtc/rtc-ntxec.c                       | 143 ++++++++++++
 include/linux/mfd/ntxec.h                     |  37 +++
 14 files changed, 710 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/devicetree/bindings/mfd/netronix,ntxec.yaml
 create mode 100644 drivers/mfd/ntxec.c
 create mode 100644 drivers/pwm/pwm-ntxec.c
 create mode 100644 drivers/rtc/rtc-ntxec.c
 create mode 100644 include/linux/mfd/ntxec.h

--
2.29.2

Comments

Uwe Kleine-König Jan. 11, 2021, 9:13 a.m. UTC | #1
Hello,

On Sat, Jan 09, 2021 at 07:02:17PM +0100, Jonathan Neuschäfer wrote:
> The Netronix EC provides a PWM output which is used for the backlight
> on some ebook readers. This patches adds a driver for the PWM output.
> 
> The .get_state callback is not implemented, because the PWM state can't
> be read back from the hardware.

There is only very little I would have done differently (only indention
which is too minor to make a fuss), so:

Reviewed-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
 
Thanks for your work
Uwe
Thierry Reding Jan. 11, 2021, 12:53 p.m. UTC | #2
On Sat, Jan 09, 2021 at 07:02:17PM +0100, Jonathan Neuschäfer wrote:
> The Netronix EC provides a PWM output which is used for the backlight
> on some ebook readers. This patches adds a driver for the PWM output.
> 
> The .get_state callback is not implemented, because the PWM state can't
> be read back from the hardware.
> 
> Signed-off-by: Jonathan Neuschäfer <j.neuschaefer@gmx.net>
> ---
> v7:
> - no changes
> 
> v6:
> - https://lore.kernel.org/lkml/20201208011000.3060239-5-j.neuschaefer@gmx.net/
> - Move period / duty cycle setting code to a function
> - Rename pwmchip_to_priv to ntxec_pwm_from_chip
> - Set period and duty cycle only before enabling the output
> - Mention that duty=0, enable=1 is assumed not to happen
> - Interleave writes to the period and duty cycle registers, to minimize the
>   window of time that an inconsistent state is configured
> 
> v5:
> - https://lore.kernel.org/lkml/20201201011513.1627028-5-j.neuschaefer@gmx.net/
> - Avoid truncation of period and duty cycle to 32 bits
> - Make ntxec_pwm_ops const
> - Use regmap_multi_reg_write
> - Add comment about get_state to ntxec_pwm_ops
> - Add comments about non-atomicity of (period, duty cycle) update
> 
> v4:
> - https://lore.kernel.org/lkml/20201122222739.1455132-5-j.neuschaefer@gmx.net/
> - Document hardware/driver limitations
> - Only accept normal polarity
> - Fix a typo ("zone" -> "zero")
> - change MAX_PERIOD_NS to 0xffff * 125
> - Clamp period to the maximum rather than returning an error
> - Rename private struct pointer to priv
> - Rearrage control flow in _probe to save a few lines and a temporary variable
> - Add missing MODULE_ALIAS line
> - Spell out ODM
> 
> v3:
> - https://lore.kernel.org/lkml/20200924192455.2484005-5-j.neuschaefer@gmx.net/
> - Relicense as GPLv2 or later
> - Add email address to copyright line
> - Remove OF compatible string and don't include linux/of_device.h
> - Fix bogus ?: in return line
> - Don't use a comma after sentinels
> - Avoid ret |= ... pattern
> - Move 8-bit register conversion to ntxec.h
> 
> v2:
> - https://lore.kernel.org/lkml/20200905133230.1014581-6-j.neuschaefer@gmx.net/
> - Various grammar and style improvements, as suggested by Uwe Kleine-König,
>   Lee Jones, and Alexandre Belloni
> - Switch to regmap
> - Prefix registers with NTXEC_REG_
> - Add help text to the Kconfig option
> - Use the .apply callback instead of the old API
> - Add a #define for the time base (125ns)
> - Don't change device state in .probe; this avoids multiple problems
> - Rework division and overflow check logic to perform divisions in 32 bits
> - Avoid setting duty cycle to zero, to work around a hardware quirk
> ---
>  drivers/pwm/Kconfig     |   8 ++
>  drivers/pwm/Makefile    |   1 +
>  drivers/pwm/pwm-ntxec.c | 182 ++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 191 insertions(+)
>  create mode 100644 drivers/pwm/pwm-ntxec.c

Lee, I assume you'll want to pick the whole set up into the MFD tree? If
so:

Acked-by: Thierry Reding <thierry.reding@gmail.com>
Lee Jones Jan. 11, 2021, 4:42 p.m. UTC | #3
On Mon, 11 Jan 2021, Thierry Reding wrote:

> On Sat, Jan 09, 2021 at 07:02:17PM +0100, Jonathan Neuschäfer wrote:
> > The Netronix EC provides a PWM output which is used for the backlight
> > on some ebook readers. This patches adds a driver for the PWM output.
> > 
> > The .get_state callback is not implemented, because the PWM state can't
> > be read back from the hardware.
> > 
> > Signed-off-by: Jonathan Neuschäfer <j.neuschaefer@gmx.net>
> > ---
> > v7:
> > - no changes
> > 
> > v6:
> > - https://lore.kernel.org/lkml/20201208011000.3060239-5-j.neuschaefer@gmx.net/
> > - Move period / duty cycle setting code to a function
> > - Rename pwmchip_to_priv to ntxec_pwm_from_chip
> > - Set period and duty cycle only before enabling the output
> > - Mention that duty=0, enable=1 is assumed not to happen
> > - Interleave writes to the period and duty cycle registers, to minimize the
> >   window of time that an inconsistent state is configured
> > 
> > v5:
> > - https://lore.kernel.org/lkml/20201201011513.1627028-5-j.neuschaefer@gmx.net/
> > - Avoid truncation of period and duty cycle to 32 bits
> > - Make ntxec_pwm_ops const
> > - Use regmap_multi_reg_write
> > - Add comment about get_state to ntxec_pwm_ops
> > - Add comments about non-atomicity of (period, duty cycle) update
> > 
> > v4:
> > - https://lore.kernel.org/lkml/20201122222739.1455132-5-j.neuschaefer@gmx.net/
> > - Document hardware/driver limitations
> > - Only accept normal polarity
> > - Fix a typo ("zone" -> "zero")
> > - change MAX_PERIOD_NS to 0xffff * 125
> > - Clamp period to the maximum rather than returning an error
> > - Rename private struct pointer to priv
> > - Rearrage control flow in _probe to save a few lines and a temporary variable
> > - Add missing MODULE_ALIAS line
> > - Spell out ODM
> > 
> > v3:
> > - https://lore.kernel.org/lkml/20200924192455.2484005-5-j.neuschaefer@gmx.net/
> > - Relicense as GPLv2 or later
> > - Add email address to copyright line
> > - Remove OF compatible string and don't include linux/of_device.h
> > - Fix bogus ?: in return line
> > - Don't use a comma after sentinels
> > - Avoid ret |= ... pattern
> > - Move 8-bit register conversion to ntxec.h
> > 
> > v2:
> > - https://lore.kernel.org/lkml/20200905133230.1014581-6-j.neuschaefer@gmx.net/
> > - Various grammar and style improvements, as suggested by Uwe Kleine-König,
> >   Lee Jones, and Alexandre Belloni
> > - Switch to regmap
> > - Prefix registers with NTXEC_REG_
> > - Add help text to the Kconfig option
> > - Use the .apply callback instead of the old API
> > - Add a #define for the time base (125ns)
> > - Don't change device state in .probe; this avoids multiple problems
> > - Rework division and overflow check logic to perform divisions in 32 bits
> > - Avoid setting duty cycle to zero, to work around a hardware quirk
> > ---
> >  drivers/pwm/Kconfig     |   8 ++
> >  drivers/pwm/Makefile    |   1 +
> >  drivers/pwm/pwm-ntxec.c | 182 ++++++++++++++++++++++++++++++++++++++++
> >  3 files changed, 191 insertions(+)
> >  create mode 100644 drivers/pwm/pwm-ntxec.c
> 
> Lee, I assume you'll want to pick the whole set up into the MFD tree? If
> so:

Yes, I'll pick this up once we have all the Acks.

The Arm parts usually go in separately.

> Acked-by: Thierry Reding <thierry.reding@gmail.com>

Thanks.
Andreas Kemnade Jan. 12, 2021, 7:36 p.m. UTC | #4
On Sat,  9 Jan 2021 19:02:16 +0100
Jonathan Neuschäfer <j.neuschaefer@gmx.net> wrote:

> The Netronix embedded controller is a microcontroller found in some
> e-book readers designed by the original design manufacturer 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>
> Acked-for-MFD-by: Lee Jones <lee.jones@linaro.org>
> ---
> v7:
> - Add #define for version number (suggested by Lee Jones).
> 
> v6:
> - https://lore.kernel.org/lkml/20201208011000.3060239-4-j.neuschaefer@gmx.net/
> - Add Lee Jones' ACK
> 
> v5:
> - no changes
> 
> v4:
> - https://lore.kernel.org/lkml/20201122222739.1455132-4-j.neuschaefer@gmx.net/
> - include asm/unaligned.h after linux/*
> - Use put_unaligned_be16 instead of open-coded big-endian packing
> - Clarify that 0x90=0xff00 causes an error in downstream kernel too
> - Add commas after non-sentinel positions
> - ntxec.h: declare structs device and regmap
> - Replace WARN_ON usage and add comments to explain errors
> - Replace dev_alert with dev_warn when the result isn't handled
> - Change subdevice registration error message to dev_err
> - Declare ntxec_reg8 as returning __be16
> - Restructure version detection code
> - Spell out ODM
> 
> v3:
> - https://lore.kernel.org/lkml/20200924192455.2484005-4-j.neuschaefer@gmx.net/
> - 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       |  11 ++
>  drivers/mfd/Makefile      |   1 +
>  drivers/mfd/ntxec.c       | 216 ++++++++++++++++++++++++++++++++++++++
>  include/linux/mfd/ntxec.h |  37 +++++++
>  4 files changed, 265 insertions(+)
>  create mode 100644 drivers/mfd/ntxec.c
>  create mode 100644 include/linux/mfd/ntxec.h
> 
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index bdfce7b156216..4280bcd47ec7d 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -976,6 +976,17 @@ 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 original design manufacturer
> +	  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 14fdb188af022..948a3bf8e3e57 100644
> --- a/drivers/mfd/Makefile
> +++ b/drivers/mfd/Makefile
> @@ -219,6 +219,7 @@ obj-$(CONFIG_MFD_INTEL_PMC_BXT)	+= intel_pmc_bxt.o
>  obj-$(CONFIG_MFD_INTEL_PMT)	+= intel_pmt.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..22ed2ef0669cb
> --- /dev/null
> +++ b/drivers/mfd/ntxec.c
> @@ -0,0 +1,216 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * The Netronix embedded controller is a microcontroller found in some
> + * e-book readers designed by the original design manufacturer 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 <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>
> +#include <asm/unaligned.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[3] = { NTXEC_REG_POWEROFF };
> +	struct i2c_msg msgs[] = {
> +		{
> +			.addr = poweroff_restart_client->addr,
> +			.flags = 0,
> +			.len = sizeof(buf),
> +			.buf = buf,
> +		},
> +	};
> +
> +	put_unaligned_be16(NTXEC_POWEROFF_VALUE, buf + 1);
> +
> +	res = i2c_transfer(poweroff_restart_client->adapter, msgs, ARRAY_SIZE(msgs));
> +	if (res < 0)
> +		dev_warn(&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;
> +	u8 buf[3] = { NTXEC_REG_RESET };
> +	/*
> +	 * NOTE: The lower half of the reset value is not sent, because sending
> +	 * it causes an I2C error. (The reset handler in the downstream driver
> +	 * does send the full two-byte value, but doesn't check the result).
> +	 */
> +	struct i2c_msg msgs[] = {
> +		{
> +			.addr = poweroff_restart_client->addr,
> +			.flags = 0,
> +			.len = sizeof(buf) - 1,
> +			.buf = buf,
> +		},
> +	};
> +
> +	put_unaligned_be16(NTXEC_RESET_VALUE, buf + 1);
> +
> +	res = i2c_transfer(poweroff_restart_client->adapter, msgs, ARRAY_SIZE(msgs));
> +	if (res < 0)
> +		dev_warn(&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, &regmap_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;
> +	}
> +
> +	/* Bail out if we encounter an unknown firmware version */
> +	switch (version) {
> +	case NTXEC_VERSION_KOBO_AURA:
> +		break;
> +	default:
> +		dev_err(ec->dev,
> +			"Netronix embedded controller version %04x is not supported.\n",
> +			version);
> +		return -ENODEV;
> +	}
> +
> +	dev_info(ec->dev,
> +		 "Netronix embedded controller version %04x detected.\n", version);
> +
> +	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;
> +
> +		if (poweroff_restart_client)
> +			/*
> +			 * Another instance of the driver already took
> +			 * poweroff/restart duties.
> +			 */
> +			dev_err(ec->dev, "poweroff_restart_client already assigned\n");
> +		else
> +			poweroff_restart_client = client;
> +
> +		if (pm_power_off)
> +			/* Another driver already registered a poweroff handler. */
> +			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_err(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", },
> +	{}
> +};
> +
MODULE_DEVICE_TABLE?

> +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);

MODULE_LICENSE()?

modpost moans about that here.

Regards,
Andreas
Andreas Kemnade Jan. 12, 2021, 7:39 p.m. UTC | #5
On Sat,  9 Jan 2021 19:02:17 +0100
Jonathan Neuschäfer <j.neuschaefer@gmx.net> wrote:

> The Netronix EC provides a PWM output which is used for the backlight
> on some ebook readers. This patches adds a driver for the PWM output.
> 
> The .get_state callback is not implemented, because the PWM state can't
> be read back from the hardware.
> 
> Signed-off-by: Jonathan Neuschäfer <j.neuschaefer@gmx.net>
> ---
> v7:
> - no changes
> 
> v6:
> - https://lore.kernel.org/lkml/20201208011000.3060239-5-j.neuschaefer@gmx.net/
> - Move period / duty cycle setting code to a function
> - Rename pwmchip_to_priv to ntxec_pwm_from_chip
> - Set period and duty cycle only before enabling the output
> - Mention that duty=0, enable=1 is assumed not to happen
> - Interleave writes to the period and duty cycle registers, to minimize the
>   window of time that an inconsistent state is configured
> 
> v5:
> - https://lore.kernel.org/lkml/20201201011513.1627028-5-j.neuschaefer@gmx.net/
> - Avoid truncation of period and duty cycle to 32 bits
> - Make ntxec_pwm_ops const
> - Use regmap_multi_reg_write
> - Add comment about get_state to ntxec_pwm_ops
> - Add comments about non-atomicity of (period, duty cycle) update
> 
> v4:
> - https://lore.kernel.org/lkml/20201122222739.1455132-5-j.neuschaefer@gmx.net/
> - Document hardware/driver limitations
> - Only accept normal polarity
> - Fix a typo ("zone" -> "zero")
> - change MAX_PERIOD_NS to 0xffff * 125
> - Clamp period to the maximum rather than returning an error
> - Rename private struct pointer to priv
> - Rearrage control flow in _probe to save a few lines and a temporary variable
> - Add missing MODULE_ALIAS line
> - Spell out ODM
> 
> v3:
> - https://lore.kernel.org/lkml/20200924192455.2484005-5-j.neuschaefer@gmx.net/
> - Relicense as GPLv2 or later
> - Add email address to copyright line
> - Remove OF compatible string and don't include linux/of_device.h
> - Fix bogus ?: in return line
> - Don't use a comma after sentinels
> - Avoid ret |= ... pattern
> - Move 8-bit register conversion to ntxec.h
> 
> v2:
> - https://lore.kernel.org/lkml/20200905133230.1014581-6-j.neuschaefer@gmx.net/
> - Various grammar and style improvements, as suggested by Uwe Kleine-König,
>   Lee Jones, and Alexandre Belloni
> - Switch to regmap
> - Prefix registers with NTXEC_REG_
> - Add help text to the Kconfig option
> - Use the .apply callback instead of the old API
> - Add a #define for the time base (125ns)
> - Don't change device state in .probe; this avoids multiple problems
> - Rework division and overflow check logic to perform divisions in 32 bits
> - Avoid setting duty cycle to zero, to work around a hardware quirk
> ---
>  drivers/pwm/Kconfig     |   8 ++
>  drivers/pwm/Makefile    |   1 +
>  drivers/pwm/pwm-ntxec.c | 182 ++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 191 insertions(+)
>  create mode 100644 drivers/pwm/pwm-ntxec.c
> 
> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> index 0937e1c047acb..a2830b8832b97 100644
> --- a/drivers/pwm/Kconfig
> +++ b/drivers/pwm/Kconfig
> @@ -393,6 +393,14 @@ config PWM_MXS
>  	  To compile this driver as a module, choose M here: the module
>  	  will be called pwm-mxs.
> 
> +config PWM_NTXEC
> +	tristate "Netronix embedded controller PWM support"
> +	depends on MFD_NTXEC
> +	help
> +	  Say yes here if you want to support the PWM output of the embedded
> +	  controller found in certain e-book readers designed by the original
> +	  design manufacturer Netronix.
> +
>  config PWM_OMAP_DMTIMER
>  	tristate "OMAP Dual-Mode Timer PWM support"
>  	depends on OF
> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> index 18b89d7fd092a..7d97eb595bbef 100644
> --- a/drivers/pwm/Makefile
> +++ b/drivers/pwm/Makefile
> @@ -35,6 +35,7 @@ obj-$(CONFIG_PWM_MESON)		+= pwm-meson.o
>  obj-$(CONFIG_PWM_MEDIATEK)	+= pwm-mediatek.o
>  obj-$(CONFIG_PWM_MTK_DISP)	+= pwm-mtk-disp.o
>  obj-$(CONFIG_PWM_MXS)		+= pwm-mxs.o
> +obj-$(CONFIG_PWM_NTXEC)		+= pwm-ntxec.o
>  obj-$(CONFIG_PWM_OMAP_DMTIMER)	+= pwm-omap-dmtimer.o
>  obj-$(CONFIG_PWM_PCA9685)	+= pwm-pca9685.o
>  obj-$(CONFIG_PWM_PXA)		+= pwm-pxa.o
> diff --git a/drivers/pwm/pwm-ntxec.c b/drivers/pwm/pwm-ntxec.c
> new file mode 100644
> index 0000000000000..1db30a6caa3ad
> --- /dev/null
> +++ b/drivers/pwm/pwm-ntxec.c
> @@ -0,0 +1,182 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * The Netronix embedded controller is a microcontroller found in some
> + * e-book readers designed by the original design manufacturer Netronix, Inc.
> + * It contains RTC, battery monitoring, system power management, and PWM
> + * functionality.
> + *
> + * This driver implements PWM output.
> + *
> + * Copyright 2020 Jonathan Neuschäfer <j.neuschaefer@gmx.net>
> + *
> + * Limitations:
> + * - The get_state callback is not implemented, because the current state of
> + *   the PWM output can't be read back from the hardware.
> + * - The hardware can only generate normal polarity output.
> + * - The period and duty cycle can't be changed together in one atomic action.
> + */
> +
> +#include <linux/mfd/ntxec.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/pwm.h>
> +#include <linux/regmap.h>
> +#include <linux/types.h>
> +
> +struct ntxec_pwm {
> +	struct device *dev;
> +	struct ntxec *ec;
> +	struct pwm_chip chip;
> +};
> +
> +static struct ntxec_pwm *ntxec_pwm_from_chip(struct pwm_chip *chip)
> +{
> +	return container_of(chip, struct ntxec_pwm, chip);
> +}
> +
> +#define NTXEC_REG_AUTO_OFF_HI	0xa1
> +#define NTXEC_REG_AUTO_OFF_LO	0xa2
> +#define NTXEC_REG_ENABLE	0xa3
> +#define NTXEC_REG_PERIOD_LOW	0xa4
> +#define NTXEC_REG_PERIOD_HIGH	0xa5
> +#define NTXEC_REG_DUTY_LOW	0xa6
> +#define NTXEC_REG_DUTY_HIGH	0xa7
> +
> +/*
> + * The time base used in the EC is 8MHz, or 125ns. Period and duty cycle are
> + * measured in this unit.
> + */
> +#define TIME_BASE_NS 125
> +
> +/*
> + * The maximum input value (in nanoseconds) is determined by the time base and
> + * the range of the hardware registers that hold the converted value.
> + * It fits into 32 bits, so we can do our calculations in 32 bits as well.
> + */
> +#define MAX_PERIOD_NS (TIME_BASE_NS * 0xffff)
> +
> +static int ntxec_pwm_set_raw_period_and_duty_cycle(struct pwm_chip *chip,
> +						   int period, int duty)
> +{
> +	struct ntxec_pwm *priv = ntxec_pwm_from_chip(chip);
> +
> +	/*
> +	 * Changes to the period and duty cycle take effect as soon as the
> +	 * corresponding low byte is written, so the hardware may be configured
> +	 * to an inconsistent state after the period is written and before the
> +	 * duty cycle is fully written. If, in such a case, the old duty cycle
> +	 * is longer than the new period, the EC may output 100% for a moment.
> +	 *
> +	 * To minimize the time between the changes to period and duty cycle
> +	 * taking effect, the writes are interleaved.
> +	 */
> +
> +	struct reg_sequence regs[] = {
> +		{ NTXEC_REG_PERIOD_HIGH, ntxec_reg8(period >> 8) },
> +		{ NTXEC_REG_DUTY_HIGH, ntxec_reg8(duty >> 8) },
> +		{ NTXEC_REG_PERIOD_LOW, ntxec_reg8(period) },
> +		{ NTXEC_REG_DUTY_LOW, ntxec_reg8(duty) },
> +	};
> +
> +	return regmap_multi_reg_write(priv->ec->regmap, regs, ARRAY_SIZE(regs));
> +}
> +
> +static int ntxec_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm_dev,
> +			   const struct pwm_state *state)
> +{
> +	struct ntxec_pwm *priv = ntxec_pwm_from_chip(chip);
> +	unsigned int period, duty;
> +	int res;
> +
> +	if (state->polarity != PWM_POLARITY_NORMAL)
> +		return -EINVAL;
> +
> +	period = min_t(u64, state->period, MAX_PERIOD_NS);
> +	duty   = min_t(u64, state->duty_cycle, period);
> +
> +	period /= TIME_BASE_NS;
> +	duty   /= TIME_BASE_NS;
> +
> +	/*
> +	 * Writing a duty cycle of zero puts the device into a state where
> +	 * writing a higher duty cycle doesn't result in the brightness that it
> +	 * usually results in. This can be fixed by cycling the ENABLE register.
> +	 *
> +	 * As a workaround, write ENABLE=0 when the duty cycle is zero.
> +	 * The case that something has previously set the duty cycle to zero
> +	 * but ENABLE=1, is not handled.
> +	 */
> +	if (state->enabled && duty != 0) {
> +		res = ntxec_pwm_set_raw_period_and_duty_cycle(chip, period, duty);
> +		if (res)
> +			return res;
> +
> +		res = regmap_write(priv->ec->regmap, NTXEC_REG_ENABLE, ntxec_reg8(1));
> +		if (res)
> +			return res;
> +
> +		/* Disable the auto-off timer */
> +		res = regmap_write(priv->ec->regmap, NTXEC_REG_AUTO_OFF_HI, ntxec_reg8(0xff));
> +		if (res)
> +			return res;
> +
> +		return regmap_write(priv->ec->regmap, NTXEC_REG_AUTO_OFF_LO, ntxec_reg8(0xff));
> +	} else {
> +		return regmap_write(priv->ec->regmap, NTXEC_REG_ENABLE, ntxec_reg8(0));
> +	}
> +}
> +
> +static const struct pwm_ops ntxec_pwm_ops = {
> +	.owner = THIS_MODULE,
> +	.apply = ntxec_pwm_apply,
> +	/*
> +	 * No .get_state callback, because the current state cannot be read
> +	 * back from the hardware.
> +	 */
> +};
> +
> +static int ntxec_pwm_probe(struct platform_device *pdev)
> +{
> +	struct ntxec *ec = dev_get_drvdata(pdev->dev.parent);
> +	struct ntxec_pwm *priv;
> +	struct pwm_chip *chip;
> +
> +	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	priv->ec = ec;
> +	priv->dev = &pdev->dev;
> +
> +	platform_set_drvdata(pdev, priv);
> +
> +	chip = &priv->chip;
> +	chip->dev = &pdev->dev;

Hmm, I needed
chip->dev = &pdev->dev.parent to use the backlight example
in patch 2/7.
Not sure what the correct solution is. Maybe the pwm deserves its own
devicetree node.

Regards,
Andreas
Lee Jones Jan. 13, 2021, 8:03 a.m. UTC | #6
On Tue, 12 Jan 2021, Andreas Kemnade wrote:

> On Sat,  9 Jan 2021 19:02:16 +0100
> Jonathan Neuschäfer <j.neuschaefer@gmx.net> wrote:
> 
> > The Netronix embedded controller is a microcontroller found in some
> > e-book readers designed by the original design manufacturer 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>
> > Acked-for-MFD-by: Lee Jones <lee.jones@linaro.org>

[...]

> > +static const struct of_device_id of_ntxec_match_table[] = {
> > +	{ .compatible = "netronix,ntxec", },
> > +	{}
> > +};
> > +
> MODULE_DEVICE_TABLE?
> 
> > +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);
> 
> MODULE_LICENSE()?
> 
> modpost moans about that here.

Andreas, would you be kind enough to snip/trim your replies in future
please.  It would save a *lot* of people a little bit of time (which
adds up fast).  TIA.
Andreas Kemnade Jan. 13, 2021, 10:46 p.m. UTC | #7
On Tue, 12 Jan 2021 20:39:02 +0100
Andreas Kemnade <andreas@kemnade.info> wrote:

[...]
> > +static int ntxec_pwm_probe(struct platform_device *pdev)
> > +{
> > +	struct ntxec *ec = dev_get_drvdata(pdev->dev.parent);
> > +	struct ntxec_pwm *priv;
> > +	struct pwm_chip *chip;
> > +
> > +	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
> > +	if (!priv)
> > +		return -ENOMEM;
> > +
> > +	priv->ec = ec;
> > +	priv->dev = &pdev->dev;
> > +
> > +	platform_set_drvdata(pdev, priv);
> > +
> > +	chip = &priv->chip;
> > +	chip->dev = &pdev->dev;  
> 
> Hmm, I needed
> chip->dev = &pdev->dev.parent to use the backlight example
> in patch 2/7.
> Not sure what the correct solution is. Maybe the pwm deserves its own
> devicetree node.
> 
probably just assigning the node from the parent.

   pdev->dev.of_node = pdev->dev.parent->of_node;

Regards,
Andreas
J. Neuschäfer Jan. 14, 2021, 6:56 p.m. UTC | #8
On Tue, Jan 12, 2021 at 08:36:49PM +0100, Andreas Kemnade wrote:
> On Sat,  9 Jan 2021 19:02:16 +0100
> Jonathan Neuschäfer <j.neuschaefer@gmx.net> wrote:
[...]
> > +static const struct of_device_id of_ntxec_match_table[] = {
> > +	{ .compatible = "netronix,ntxec", },
> > +	{}
> > +};
> > +
> MODULE_DEVICE_TABLE?

Yes, good idea.

> > +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);
> 
> MODULE_LICENSE()?
> 
> modpost moans about that here.

I forgot those. Thanks for noticing.
I'll add the MODULE_ lines in v8.



Thanks,
Jonathan
J. Neuschäfer Jan. 14, 2021, 10:31 p.m. UTC | #9
On Wed, Jan 13, 2021 at 11:46:45PM +0100, Andreas Kemnade wrote:
> On Tue, 12 Jan 2021 20:39:02 +0100
> Andreas Kemnade <andreas@kemnade.info> wrote:
> 
> [...]
> > > +static int ntxec_pwm_probe(struct platform_device *pdev)
[...]
> > Hmm, I needed
> > chip->dev = &pdev->dev.parent to use the backlight example
> > in patch 2/7.
> > Not sure what the correct solution is. Maybe the pwm deserves its own
> > devicetree node.
> > 
> probably just assigning the node from the parent.
> 
>    pdev->dev.of_node = pdev->dev.parent->of_node;

Ah, good catch, thanks. This works, I'll add it to the next version.


Jonathan