Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

power: reset: Add poweroff driver for ATC260x PMICs

This driver provides poweroff and reboot support for a system through
the ATC2603C and ATC2609A chip variants of the Actions Semi ATC260x
family of PMICs.

Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@gmail.com>
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>

authored by

Cristian Ciocaltea and committed by
Sebastian Reichel
3235d130 2f7cf24f

+270 -1
+7 -1
drivers/power/reset/Kconfig
··· 39 39 This driver supports the alternate shutdown controller for some Atmel 40 40 SAMA5 SoCs. It is present for example on SAMA5D2 SoC. 41 41 42 + config POWER_RESET_ATC260X 43 + tristate "Actions Semi ATC260x PMIC power-off driver" 44 + depends on MFD_ATC260X 45 + help 46 + This driver provides power-off and restart support for a system 47 + through Actions Semi ATC260x series PMICs. 48 + 42 49 config POWER_RESET_AXXIA 43 50 bool "LSI Axxia reset driver" 44 51 depends on ARCH_AXXIA ··· 299 292 action according to the mode. 300 293 301 294 endif 302 -
+1
drivers/power/reset/Makefile
··· 3 3 obj-$(CONFIG_POWER_RESET_AT91_POWEROFF) += at91-poweroff.o 4 4 obj-$(CONFIG_POWER_RESET_AT91_RESET) += at91-reset.o 5 5 obj-$(CONFIG_POWER_RESET_AT91_SAMA5D2_SHDWC) += at91-sama5d2_shdwc.o 6 + obj-$(CONFIG_POWER_RESET_ATC260X) += atc260x-poweroff.o 6 7 obj-$(CONFIG_POWER_RESET_AXXIA) += axxia-reset.o 7 8 obj-$(CONFIG_POWER_RESET_BRCMKONA) += brcm-kona-reset.o 8 9 obj-$(CONFIG_POWER_RESET_BRCMSTB) += brcmstb-reboot.o
+262
drivers/power/reset/atc260x-poweroff.c
··· 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 + 16 + struct 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 */ 24 + static struct atc260x_pwrc *atc260x_pwrc_data; 25 + 26 + static 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 + 77 + static 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 + 128 + static 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 + 153 + static 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 + 168 + static 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 + 175 + static 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 + 185 + static 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 + 236 + static 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 + 250 + static 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 + 258 + module_platform_driver(atc260x_pwrc_driver); 259 + 260 + MODULE_DESCRIPTION("Poweroff & reset driver for ATC260x PMICs"); 261 + MODULE_AUTHOR("Cristian Ciocaltea <cristian.ciocaltea@gmail.com>"); 262 + MODULE_LICENSE("GPL");