linux/drivers/mfd/ntxec.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * The Netronix embedded controller is a microcontroller found in some
   4 * e-book readers designed by the original design manufacturer Netronix, Inc.
   5 * It contains RTC, battery monitoring, system power management, and PWM
   6 * functionality.
   7 *
   8 * This driver implements register access, version detection, and system
   9 * power-off/reset.
  10 *
  11 * Copyright 2020 Jonathan Neuschäfer <j.neuschaefer@gmx.net>
  12 */
  13
  14#include <linux/delay.h>
  15#include <linux/errno.h>
  16#include <linux/i2c.h>
  17#include <linux/mfd/core.h>
  18#include <linux/mfd/ntxec.h>
  19#include <linux/module.h>
  20#include <linux/pm.h>
  21#include <linux/reboot.h>
  22#include <linux/regmap.h>
  23#include <linux/types.h>
  24#include <asm/unaligned.h>
  25
  26#define NTXEC_REG_VERSION       0x00
  27#define NTXEC_REG_POWEROFF      0x50
  28#define NTXEC_REG_POWERKEEP     0x70
  29#define NTXEC_REG_RESET         0x90
  30
  31#define NTXEC_POWEROFF_VALUE    0x0100
  32#define NTXEC_POWERKEEP_VALUE   0x0800
  33#define NTXEC_RESET_VALUE       0xff00
  34
  35static struct i2c_client *poweroff_restart_client;
  36
  37static void ntxec_poweroff(void)
  38{
  39        int res;
  40        u8 buf[3] = { NTXEC_REG_POWEROFF };
  41        struct i2c_msg msgs[] = {
  42                {
  43                        .addr = poweroff_restart_client->addr,
  44                        .flags = 0,
  45                        .len = sizeof(buf),
  46                        .buf = buf,
  47                },
  48        };
  49
  50        put_unaligned_be16(NTXEC_POWEROFF_VALUE, buf + 1);
  51
  52        res = i2c_transfer(poweroff_restart_client->adapter, msgs, ARRAY_SIZE(msgs));
  53        if (res < 0)
  54                dev_warn(&poweroff_restart_client->dev,
  55                         "Failed to power off (err = %d)\n", res);
  56
  57        /*
  58         * The time from the register write until the host CPU is powered off
  59         * has been observed to be about 2.5 to 3 seconds. Sleep long enough to
  60         * safely avoid returning from the poweroff handler.
  61         */
  62        msleep(5000);
  63}
  64
  65static int ntxec_restart(struct notifier_block *nb,
  66                         unsigned long action, void *data)
  67{
  68        int res;
  69        u8 buf[3] = { NTXEC_REG_RESET };
  70        /*
  71         * NOTE: The lower half of the reset value is not sent, because sending
  72         * it causes an I2C error. (The reset handler in the downstream driver
  73         * does send the full two-byte value, but doesn't check the result).
  74         */
  75        struct i2c_msg msgs[] = {
  76                {
  77                        .addr = poweroff_restart_client->addr,
  78                        .flags = 0,
  79                        .len = sizeof(buf) - 1,
  80                        .buf = buf,
  81                },
  82        };
  83
  84        put_unaligned_be16(NTXEC_RESET_VALUE, buf + 1);
  85
  86        res = i2c_transfer(poweroff_restart_client->adapter, msgs, ARRAY_SIZE(msgs));
  87        if (res < 0)
  88                dev_warn(&poweroff_restart_client->dev,
  89                         "Failed to restart (err = %d)\n", res);
  90
  91        return NOTIFY_DONE;
  92}
  93
  94static struct notifier_block ntxec_restart_handler = {
  95        .notifier_call = ntxec_restart,
  96        .priority = 128,
  97};
  98
  99static int regmap_ignore_write(void *context,
 100                               unsigned int reg, unsigned int val)
 101
 102{
 103        struct regmap *regmap = context;
 104
 105        regmap_write(regmap, reg, val);
 106
 107        return 0;
 108}
 109
 110static int regmap_wrap_read(void *context, unsigned int reg,
 111                            unsigned int *val)
 112{
 113        struct regmap *regmap = context;
 114
 115        return regmap_read(regmap, reg, val);
 116}
 117
 118/*
 119 * Some firmware versions do not ack written data, add a wrapper. It
 120 * is used to stack another regmap on top.
 121 */
 122static const struct regmap_config regmap_config_noack = {
 123        .name = "ntxec_noack",
 124        .reg_bits = 8,
 125        .val_bits = 16,
 126        .cache_type = REGCACHE_NONE,
 127        .reg_write = regmap_ignore_write,
 128        .reg_read = regmap_wrap_read
 129};
 130
 131static const struct regmap_config regmap_config = {
 132        .name = "ntxec",
 133        .reg_bits = 8,
 134        .val_bits = 16,
 135        .cache_type = REGCACHE_NONE,
 136        .val_format_endian = REGMAP_ENDIAN_BIG,
 137};
 138
 139static const struct mfd_cell ntxec_subdev[] = {
 140        { .name = "ntxec-rtc" },
 141        { .name = "ntxec-pwm" },
 142};
 143
 144static const struct mfd_cell ntxec_subdev_pwm[] = {
 145        { .name = "ntxec-pwm" },
 146};
 147
 148static int ntxec_probe(struct i2c_client *client)
 149{
 150        struct ntxec *ec;
 151        unsigned int version;
 152        int res;
 153        const struct mfd_cell *subdevs;
 154        size_t n_subdevs;
 155
 156        ec = devm_kmalloc(&client->dev, sizeof(*ec), GFP_KERNEL);
 157        if (!ec)
 158                return -ENOMEM;
 159
 160        ec->dev = &client->dev;
 161
 162        ec->regmap = devm_regmap_init_i2c(client, &regmap_config);
 163        if (IS_ERR(ec->regmap)) {
 164                dev_err(ec->dev, "Failed to set up regmap for device\n");
 165                return PTR_ERR(ec->regmap);
 166        }
 167
 168        /* Determine the firmware version */
 169        res = regmap_read(ec->regmap, NTXEC_REG_VERSION, &version);
 170        if (res < 0) {
 171                dev_err(ec->dev, "Failed to read firmware version number\n");
 172                return res;
 173        }
 174
 175        /* Bail out if we encounter an unknown firmware version */
 176        switch (version) {
 177        case NTXEC_VERSION_KOBO_AURA:
 178                subdevs = ntxec_subdev;
 179                n_subdevs = ARRAY_SIZE(ntxec_subdev);
 180                break;
 181        case NTXEC_VERSION_TOLINO_SHINE2:
 182                subdevs = ntxec_subdev_pwm;
 183                n_subdevs = ARRAY_SIZE(ntxec_subdev_pwm);
 184                /* Another regmap stacked on top of the other */
 185                ec->regmap = devm_regmap_init(ec->dev, NULL,
 186                                              ec->regmap,
 187                                              &regmap_config_noack);
 188                if (IS_ERR(ec->regmap))
 189                        return PTR_ERR(ec->regmap);
 190                break;
 191        default:
 192                dev_err(ec->dev,
 193                        "Netronix embedded controller version %04x is not supported.\n",
 194                        version);
 195                return -ENODEV;
 196        }
 197
 198        dev_info(ec->dev,
 199                 "Netronix embedded controller version %04x detected.\n", version);
 200
 201        if (of_device_is_system_power_controller(ec->dev->of_node)) {
 202                /*
 203                 * Set the 'powerkeep' bit. This is necessary on some boards
 204                 * in order to keep the system running.
 205                 */
 206                res = regmap_write(ec->regmap, NTXEC_REG_POWERKEEP,
 207                                   NTXEC_POWERKEEP_VALUE);
 208                if (res < 0)
 209                        return res;
 210
 211                if (poweroff_restart_client)
 212                        /*
 213                         * Another instance of the driver already took
 214                         * poweroff/restart duties.
 215                         */
 216                        dev_err(ec->dev, "poweroff_restart_client already assigned\n");
 217                else
 218                        poweroff_restart_client = client;
 219
 220                if (pm_power_off)
 221                        /* Another driver already registered a poweroff handler. */
 222                        dev_err(ec->dev, "pm_power_off already assigned\n");
 223                else
 224                        pm_power_off = ntxec_poweroff;
 225
 226                res = register_restart_handler(&ntxec_restart_handler);
 227                if (res)
 228                        dev_err(ec->dev,
 229                                "Failed to register restart handler: %d\n", res);
 230        }
 231
 232        i2c_set_clientdata(client, ec);
 233
 234        res = devm_mfd_add_devices(ec->dev, PLATFORM_DEVID_NONE,
 235                                   subdevs, n_subdevs, NULL, 0, NULL);
 236        if (res)
 237                dev_err(ec->dev, "Failed to add subdevices: %d\n", res);
 238
 239        return res;
 240}
 241
 242static int ntxec_remove(struct i2c_client *client)
 243{
 244        if (client == poweroff_restart_client) {
 245                poweroff_restart_client = NULL;
 246                pm_power_off = NULL;
 247                unregister_restart_handler(&ntxec_restart_handler);
 248        }
 249
 250        return 0;
 251}
 252
 253static const struct of_device_id of_ntxec_match_table[] = {
 254        { .compatible = "netronix,ntxec", },
 255        {}
 256};
 257MODULE_DEVICE_TABLE(of, of_ntxec_match_table);
 258
 259static struct i2c_driver ntxec_driver = {
 260        .driver = {
 261                .name = "ntxec",
 262                .of_match_table = of_ntxec_match_table,
 263        },
 264        .probe_new = ntxec_probe,
 265        .remove = ntxec_remove,
 266};
 267module_i2c_driver(ntxec_driver);
 268
 269MODULE_AUTHOR("Jonathan Neuschäfer <j.neuschaefer@gmx.net>");
 270MODULE_DESCRIPTION("Core driver for Netronix EC");
 271MODULE_LICENSE("GPL");
 272