linux/drivers/power/reset/atc260x-poweroff.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * Poweroff & reset driver for Actions Semi ATC260x PMICs
   4 *
   5 * Copyright (c) 2020 Cristian Ciocaltea <cristian.ciocaltea@gmail.com>
   6 */
   7
   8#include <linux/delay.h>
   9#include <linux/mfd/atc260x/core.h>
  10#include <linux/module.h>
  11#include <linux/platform_device.h>
  12#include <linux/power_supply.h>
  13#include <linux/reboot.h>
  14#include <linux/regmap.h>
  15
  16struct atc260x_pwrc {
  17        struct device *dev;
  18        struct regmap *regmap;
  19        struct notifier_block restart_nb;
  20        int (*do_poweroff)(const struct atc260x_pwrc *pwrc, bool restart);
  21};
  22
  23/* Global variable needed only for pm_power_off */
  24static struct atc260x_pwrc *atc260x_pwrc_data;
  25
  26static int atc2603c_do_poweroff(const struct atc260x_pwrc *pwrc, bool restart)
  27{
  28        int ret, deep_sleep = 0;
  29        uint reg_mask, reg_val;
  30
  31        /* S4-Deep Sleep Mode is NOT available for WALL/USB power */
  32        if (!restart && !power_supply_is_system_supplied()) {
  33                deep_sleep = 1;
  34                dev_info(pwrc->dev, "Enabling S4-Deep Sleep Mode");
  35        }
  36
  37        /* Update wakeup sources */
  38        reg_val = ATC2603C_PMU_SYS_CTL0_ONOFF_LONG_WK_EN |
  39                  (restart ? ATC2603C_PMU_SYS_CTL0_RESET_WK_EN
  40                           : ATC2603C_PMU_SYS_CTL0_ONOFF_SHORT_WK_EN);
  41
  42        ret = regmap_update_bits(pwrc->regmap, ATC2603C_PMU_SYS_CTL0,
  43                                 ATC2603C_PMU_SYS_CTL0_WK_ALL, reg_val);
  44        if (ret)
  45                dev_warn(pwrc->dev, "failed to write SYS_CTL0: %d\n", ret);
  46
  47        /* Update power mode */
  48        reg_mask = ATC2603C_PMU_SYS_CTL3_EN_S2 | ATC2603C_PMU_SYS_CTL3_EN_S3;
  49
  50        ret = regmap_update_bits(pwrc->regmap, ATC2603C_PMU_SYS_CTL3, reg_mask,
  51                                 deep_sleep ? 0 : ATC2603C_PMU_SYS_CTL3_EN_S3);
  52        if (ret) {
  53                dev_err(pwrc->dev, "failed to write SYS_CTL3: %d\n", ret);
  54                return ret;
  55        }
  56
  57        /* Trigger poweroff / restart sequence */
  58        reg_mask = restart ? ATC2603C_PMU_SYS_CTL0_RESTART_EN
  59                           : ATC2603C_PMU_SYS_CTL1_EN_S1;
  60        reg_val = restart ? ATC2603C_PMU_SYS_CTL0_RESTART_EN : 0;
  61
  62        ret = regmap_update_bits(pwrc->regmap,
  63                                 restart ? ATC2603C_PMU_SYS_CTL0 : ATC2603C_PMU_SYS_CTL1,
  64                                 reg_mask, reg_val);
  65        if (ret) {
  66                dev_err(pwrc->dev, "failed to write SYS_CTL%d: %d\n",
  67                        restart ? 0 : 1, ret);
  68                return ret;
  69        }
  70
  71        /* Wait for trigger completion */
  72        mdelay(200);
  73
  74        return 0;
  75}
  76
  77static int atc2609a_do_poweroff(const struct atc260x_pwrc *pwrc, bool restart)
  78{
  79        int ret, deep_sleep = 0;
  80        uint reg_mask, reg_val;
  81
  82        /* S4-Deep Sleep Mode is NOT available for WALL/USB power */
  83        if (!restart && !power_supply_is_system_supplied()) {
  84                deep_sleep = 1;
  85                dev_info(pwrc->dev, "Enabling S4-Deep Sleep Mode");
  86        }
  87
  88        /* Update wakeup sources */
  89        reg_val = ATC2609A_PMU_SYS_CTL0_ONOFF_LONG_WK_EN |
  90                  (restart ? ATC2609A_PMU_SYS_CTL0_RESET_WK_EN
  91                           : ATC2609A_PMU_SYS_CTL0_ONOFF_SHORT_WK_EN);
  92
  93        ret = regmap_update_bits(pwrc->regmap, ATC2609A_PMU_SYS_CTL0,
  94                                 ATC2609A_PMU_SYS_CTL0_WK_ALL, reg_val);
  95        if (ret)
  96                dev_warn(pwrc->dev, "failed to write SYS_CTL0: %d\n", ret);
  97
  98        /* Update power mode */
  99        reg_mask = ATC2609A_PMU_SYS_CTL3_EN_S2 | ATC2609A_PMU_SYS_CTL3_EN_S3;
 100
 101        ret = regmap_update_bits(pwrc->regmap, ATC2609A_PMU_SYS_CTL3, reg_mask,
 102                                 deep_sleep ? 0 : ATC2609A_PMU_SYS_CTL3_EN_S3);
 103        if (ret) {
 104                dev_err(pwrc->dev, "failed to write SYS_CTL3: %d\n", ret);
 105                return ret;
 106        }
 107
 108        /* Trigger poweroff / restart sequence */
 109        reg_mask = restart ? ATC2609A_PMU_SYS_CTL0_RESTART_EN
 110                           : ATC2609A_PMU_SYS_CTL1_EN_S1;
 111        reg_val = restart ? ATC2609A_PMU_SYS_CTL0_RESTART_EN : 0;
 112
 113        ret = regmap_update_bits(pwrc->regmap,
 114                                 restart ? ATC2609A_PMU_SYS_CTL0 : ATC2609A_PMU_SYS_CTL1,
 115                                 reg_mask, reg_val);
 116        if (ret) {
 117                dev_err(pwrc->dev, "failed to write SYS_CTL%d: %d\n",
 118                        restart ? 0 : 1, ret);
 119                return ret;
 120        }
 121
 122        /* Wait for trigger completion */
 123        mdelay(200);
 124
 125        return 0;
 126}
 127
 128static int atc2603c_init(const struct atc260x_pwrc *pwrc)
 129{
 130        int ret;
 131
 132        /*
 133         * Delay transition from S2/S3 to S1 in order to avoid
 134         * DDR init failure in Bootloader.
 135         */
 136        ret = regmap_update_bits(pwrc->regmap, ATC2603C_PMU_SYS_CTL3,
 137                                 ATC2603C_PMU_SYS_CTL3_S2S3TOS1_TIMER_EN,
 138                                 ATC2603C_PMU_SYS_CTL3_S2S3TOS1_TIMER_EN);
 139        if (ret)
 140                dev_warn(pwrc->dev, "failed to write SYS_CTL3: %d\n", ret);
 141
 142        /* Set wakeup sources */
 143        ret = regmap_update_bits(pwrc->regmap, ATC2603C_PMU_SYS_CTL0,
 144                                 ATC2603C_PMU_SYS_CTL0_WK_ALL,
 145                                 ATC2603C_PMU_SYS_CTL0_HDSW_WK_EN |
 146                                 ATC2603C_PMU_SYS_CTL0_ONOFF_LONG_WK_EN);
 147        if (ret)
 148                dev_warn(pwrc->dev, "failed to write SYS_CTL0: %d\n", ret);
 149
 150        return ret;
 151}
 152
 153static int atc2609a_init(const struct atc260x_pwrc *pwrc)
 154{
 155        int ret;
 156
 157        /* Set wakeup sources */
 158        ret = regmap_update_bits(pwrc->regmap, ATC2609A_PMU_SYS_CTL0,
 159                                 ATC2609A_PMU_SYS_CTL0_WK_ALL,
 160                                 ATC2609A_PMU_SYS_CTL0_HDSW_WK_EN |
 161                                 ATC2609A_PMU_SYS_CTL0_ONOFF_LONG_WK_EN);
 162        if (ret)
 163                dev_warn(pwrc->dev, "failed to write SYS_CTL0: %d\n", ret);
 164
 165        return ret;
 166}
 167
 168static void atc260x_pwrc_pm_handler(void)
 169{
 170        atc260x_pwrc_data->do_poweroff(atc260x_pwrc_data, false);
 171
 172        WARN_ONCE(1, "Unable to power off system\n");
 173}
 174
 175static int atc260x_pwrc_restart_handler(struct notifier_block *nb,
 176                                        unsigned long mode, void *cmd)
 177{
 178        struct atc260x_pwrc *pwrc = container_of(nb, struct atc260x_pwrc,
 179                                                 restart_nb);
 180        pwrc->do_poweroff(pwrc, true);
 181
 182        return NOTIFY_DONE;
 183}
 184
 185static int atc260x_pwrc_probe(struct platform_device *pdev)
 186{
 187        struct atc260x *atc260x = dev_get_drvdata(pdev->dev.parent);
 188        struct atc260x_pwrc *priv;
 189        int ret;
 190
 191        priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
 192        if (!priv)
 193                return -ENOMEM;
 194
 195        priv->dev = &pdev->dev;
 196        priv->regmap = atc260x->regmap;
 197        priv->restart_nb.notifier_call = atc260x_pwrc_restart_handler;
 198        priv->restart_nb.priority = 192;
 199
 200        switch (atc260x->ic_type) {
 201        case ATC2603C:
 202                priv->do_poweroff = atc2603c_do_poweroff;
 203                ret = atc2603c_init(priv);
 204                break;
 205        case ATC2609A:
 206                priv->do_poweroff = atc2609a_do_poweroff;
 207                ret = atc2609a_init(priv);
 208                break;
 209        default:
 210                dev_err(priv->dev,
 211                        "Poweroff not supported for ATC260x PMIC type: %u\n",
 212                        atc260x->ic_type);
 213                return -EINVAL;
 214        }
 215
 216        if (ret)
 217                return ret;
 218
 219        platform_set_drvdata(pdev, priv);
 220
 221        if (!pm_power_off) {
 222                atc260x_pwrc_data = priv;
 223                pm_power_off = atc260x_pwrc_pm_handler;
 224        } else {
 225                dev_warn(priv->dev, "Poweroff callback already assigned\n");
 226        }
 227
 228        ret = register_restart_handler(&priv->restart_nb);
 229        if (ret)
 230                dev_err(priv->dev, "failed to register restart handler: %d\n",
 231                        ret);
 232
 233        return ret;
 234}
 235
 236static int atc260x_pwrc_remove(struct platform_device *pdev)
 237{
 238        struct atc260x_pwrc *priv = platform_get_drvdata(pdev);
 239
 240        if (atc260x_pwrc_data == priv) {
 241                pm_power_off = NULL;
 242                atc260x_pwrc_data = NULL;
 243        }
 244
 245        unregister_restart_handler(&priv->restart_nb);
 246
 247        return 0;
 248}
 249
 250static struct platform_driver atc260x_pwrc_driver = {
 251        .probe = atc260x_pwrc_probe,
 252        .remove = atc260x_pwrc_remove,
 253        .driver = {
 254                .name = "atc260x-pwrc",
 255        },
 256};
 257
 258module_platform_driver(atc260x_pwrc_driver);
 259
 260MODULE_DESCRIPTION("Poweroff & reset driver for ATC260x PMICs");
 261MODULE_AUTHOR("Cristian Ciocaltea <cristian.ciocaltea@gmail.com>");
 262MODULE_LICENSE("GPL");
 263