diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2021-04-28 15:59:13 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2021-04-28 15:59:13 -0700 |
commit | 71a5cc28e88b0db69c3f83d4061ad4cc684af09f (patch) | |
tree | 3cc40d902e7cba992c27c946f69ef65fc7d2a807 /drivers/mfd/ntxec.c | |
parent | be18cd1fcae2ed7db58d92d20733dfa8aa0a5173 (diff) | |
parent | f9386c91574fe6da9f4fca9a47734816b0db0019 (diff) | |
download | linux-71a5cc28e88b0db69c3f83d4061ad4cc684af09f.tar.bz2 |
Merge tag 'mfd-next-5.13' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd
Pull MFD updates from Lee Jones:
"Core Framework:
- Add support for Software Nodes to MFD Core
- Remove support for Device Properties from MFD Core
- Use standard APIs in MFD Core
New Drivers:
- Add support for ROHM BD9576MUF and BD9573MUF PMICs
- Add support for Netronix Embedded Controller, PWM and RTC
- Add support for Actions Semi ATC260x PMICs and OnKey
New Device Support:
- Add support for DG1 PCIe Graphics Card to Intel PMT
- Add support for ROHM BD71815 PMIC to ROHM BD71828
- Add support for Tolino Shine 2 HD to Netronix Embedded Controller
- Add support for AX10 BMC Secure Updates to Intel M10 BMC
Removed Device Support:
- Remove Arizona Extcon support from MFD
- Remove ST-E AB8500 Power Supply code from MFD
- Remove AB3100 altogether
New Functionality:
- Add support for SMBus and I2C modes to Dialog DA9063
- Switch to using Software Nodes in Intel (various)
New/converted Device Tree bindings:
- rohm bd71815-pmic, rohm bd9576-pmic, netronix ntxec, actions
atc260x, ricoh rn5t618, qcom pm8xxx
- Fix-ups:
- Fix error handling/path; intel_pmt
- Simplify code; rohm-bd718x7, ab8500-core, intel-m10-bmc
- Trivial clean-ups (reordering, spelling); rohm-generic, rn5t618,
max8997
- Use correct data-type; db8500-prcmu
- Remove superfluous code; lp87565, intel_quark_i2c_gpi, lpc_sch, twl
- Use generic APIs/defines; lm3533-core, intel_quark_i2c_gpio
- Regmap related fix-ups; intel-m10-bmc, sec-core
- Reorder resource freeing during remove; intel_quark_i2c_gpio
- Make table indexing more robust; intel_quark_i2c_gpio
- Fix reference imbalances; arizona-irq
- Staticify and (un)constify things; arizona-spi, stmpe, ene-kb3930,
intel-lpss-acpi, intel-lpss-pci, atc260x-i2c, intel_quark_i2c_gpio
Bug Fixes:
- Fix incorrect (register) values; intel-m10-bmc
- Kconfig related fixes; ABX500_CORE
- Do not clear the Auto Reload Register; stm32-timers"
* tag 'mfd-next-5.13' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd: (84 commits)
mfd: intel-m10-bmc: Add support for MAX10 BMC Secure Updates
Revert "mfd: max8997: Add of_compatible to Extcon and Charger mfd_cell"
mfd: twl: Remove unused inline function twl4030charger_usb_en()
dt-bindings: mfd: Convert pm8xxx bindings to yaml
dt-bindings: mfd: Add compatible for pmk8350 rtc
i2c: designware: Get rid of legacy platform data
mfd: intel_quark_i2c_gpio: Convert I²C to use software nodes
mfd: lpc_sch: Partially revert "Add support for Intel Quark X1000"
mfd: arizona: Fix rumtime PM imbalance on error
mfd: max8997: Replace 8998 with 8997
mfd: core: Use acpi_find_child_device() for child devices lookup
mfd: intel_quark_i2c_gpio: Don't play dirty trick with const
mfd: intel_quark_i2c_gpio: Enable MSI interrupt
mfd: intel_quark_i2c_gpio: Reuse BAR definitions for MFD cell indexing
mfd: ntxec: Support for EC in Tolino Shine 2 HD
mfd: stm32-timers: Avoid clearing auto reload register
mfd: intel_quark_i2c_gpio: Replace I²C speeds with descriptive definitions
mfd: intel_quark_i2c_gpio: Remove unused struct device member
mfd: intel_quark_i2c_gpio: Unregister resources in reversed order
mfd: Kconfig: ABX500_CORE should depend on ARCH_U8500
...
Diffstat (limited to 'drivers/mfd/ntxec.c')
-rw-r--r-- | drivers/mfd/ntxec.c | 271 |
1 files changed, 271 insertions, 0 deletions
diff --git a/drivers/mfd/ntxec.c b/drivers/mfd/ntxec.c new file mode 100644 index 000000000000..b711e73eedcb --- /dev/null +++ b/drivers/mfd/ntxec.c @@ -0,0 +1,271 @@ +// 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 int regmap_ignore_write(void *context, + unsigned int reg, unsigned int val) + +{ + struct regmap *regmap = context; + + regmap_write(regmap, reg, val); + + return 0; +} + +static int regmap_wrap_read(void *context, unsigned int reg, + unsigned int *val) +{ + struct regmap *regmap = context; + + return regmap_read(regmap, reg, val); +} + +/* + * Some firmware versions do not ack written data, add a wrapper. It + * is used to stack another regmap on top. + */ +static const struct regmap_config regmap_config_noack = { + .name = "ntxec_noack", + .reg_bits = 8, + .val_bits = 16, + .cache_type = REGCACHE_NONE, + .reg_write = regmap_ignore_write, + .reg_read = regmap_wrap_read +}; + +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_subdev[] = { + { .name = "ntxec-rtc" }, + { .name = "ntxec-pwm" }, +}; + +static const struct mfd_cell ntxec_subdev_pwm[] = { + { .name = "ntxec-pwm" }, +}; + +static int ntxec_probe(struct i2c_client *client) +{ + struct ntxec *ec; + unsigned int version; + int res; + const struct mfd_cell *subdevs; + size_t n_subdevs; + + 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 PTR_ERR(ec->regmap); + } + + /* 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: + subdevs = ntxec_subdev; + n_subdevs = ARRAY_SIZE(ntxec_subdev); + break; + case NTXEC_VERSION_TOLINO_SHINE2: + subdevs = ntxec_subdev_pwm; + n_subdevs = ARRAY_SIZE(ntxec_subdev_pwm); + /* Another regmap stacked on top of the other */ + ec->regmap = devm_regmap_init(ec->dev, NULL, + ec->regmap, + ®map_config_noack); + if (IS_ERR(ec->regmap)) + return PTR_ERR(ec->regmap); + 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, + subdevs, n_subdevs, 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(of, of_ntxec_match_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_AUTHOR("Jonathan Neuschäfer <j.neuschaefer@gmx.net>"); +MODULE_DESCRIPTION("Core driver for Netronix EC"); +MODULE_LICENSE("GPL"); |