Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 | // SPDX-License-Identifier: GPL-2.0+ /* * Copyright (c) 2023 Neil Armstrong <neil.armstrong@linaro.org> */ #include <linux/kernel.h> #include <linux/init.h> #include <linux/of.h> #include <linux/platform_device.h> #include <linux/mfd/rk808.h> #include <linux/regmap.h> #include <linux/module.h> #include <linux/reboot.h> #include <linux/i2c.h> /* * The Odroid Go Ultra has 2 PMICs: * - RK818 (manages the battery and USB-C power supply) * - RK817 * Both PMICs feeds power to the S922X SoC, so they must be powered-off in sequence. * Vendor does power-off the RK817 first, then the RK818 so here we follow this sequence. */ struct odroid_go_ultra_poweroff_data { struct device *dev; struct device *rk817; struct device *rk818; }; static int odroid_go_ultra_poweroff_prepare(struct sys_off_data *data) { struct odroid_go_ultra_poweroff_data *poweroff_data = data->cb_data; struct regmap *rk817, *rk818; int ret; /* RK817 Regmap */ rk817 = dev_get_regmap(poweroff_data->rk817, NULL); if (!rk817) { dev_err(poweroff_data->dev, "failed to get rk817 regmap\n"); return notifier_from_errno(-EINVAL); } /* RK818 Regmap */ rk818 = dev_get_regmap(poweroff_data->rk818, NULL); if (!rk818) { dev_err(poweroff_data->dev, "failed to get rk818 regmap\n"); return notifier_from_errno(-EINVAL); } dev_info(poweroff_data->dev, "Setting PMICs for power off"); /* RK817 */ ret = regmap_update_bits(rk817, RK817_SYS_CFG(3), DEV_OFF, DEV_OFF); if (ret) { dev_err(poweroff_data->dev, "failed to poweroff rk817\n"); return notifier_from_errno(ret); } /* RK818 */ ret = regmap_update_bits(rk818, RK818_DEVCTRL_REG, DEV_OFF, DEV_OFF); if (ret) { dev_err(poweroff_data->dev, "failed to poweroff rk818\n"); return notifier_from_errno(ret); } return NOTIFY_OK; } static void odroid_go_ultra_poweroff_put_pmic_device(void *data) { struct device *dev = data; put_device(dev); } static int odroid_go_ultra_poweroff_get_pmic_device(struct device *dev, const char *compatible, struct device **pmic) { struct device_node *pmic_node; struct i2c_client *pmic_client; pmic_node = of_find_compatible_node(NULL, NULL, compatible); if (!pmic_node) return -ENODEV; pmic_client = of_find_i2c_device_by_node(pmic_node); of_node_put(pmic_node); if (!pmic_client) return -EPROBE_DEFER; *pmic = &pmic_client->dev; return devm_add_action_or_reset(dev, odroid_go_ultra_poweroff_put_pmic_device, *pmic); } static int odroid_go_ultra_poweroff_probe(struct platform_device *pdev) { struct odroid_go_ultra_poweroff_data *poweroff_data; int ret; poweroff_data = devm_kzalloc(&pdev->dev, sizeof(*poweroff_data), GFP_KERNEL); if (!poweroff_data) return -ENOMEM; dev_set_drvdata(&pdev->dev, poweroff_data); /* RK818 PMIC Device */ ret = odroid_go_ultra_poweroff_get_pmic_device(&pdev->dev, "rockchip,rk818", &poweroff_data->rk818); if (ret) return dev_err_probe(&pdev->dev, ret, "failed to get rk818 mfd data\n"); /* RK817 PMIC Device */ ret = odroid_go_ultra_poweroff_get_pmic_device(&pdev->dev, "rockchip,rk817", &poweroff_data->rk817); if (ret) return dev_err_probe(&pdev->dev, ret, "failed to get rk817 mfd data\n"); /* Register as SYS_OFF_MODE_POWER_OFF_PREPARE because regmap_update_bits may sleep */ ret = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_POWER_OFF_PREPARE, SYS_OFF_PRIO_DEFAULT, odroid_go_ultra_poweroff_prepare, poweroff_data); if (ret) return dev_err_probe(&pdev->dev, ret, "failed to register sys-off handler\n"); dev_info(&pdev->dev, "Registered Power-Off handler\n"); return 0; } static struct platform_device *pdev; static struct platform_driver odroid_go_ultra_poweroff_driver = { .driver = { .name = "odroid-go-ultra-poweroff", }, .probe = odroid_go_ultra_poweroff_probe, }; static int __init odroid_go_ultra_poweroff_init(void) { int ret; /* Only create when running on the Odroid Go Ultra device */ if (!of_device_is_compatible(of_root, "hardkernel,odroid-go-ultra")) return -ENODEV; ret = platform_driver_register(&odroid_go_ultra_poweroff_driver); if (ret) return ret; pdev = platform_device_register_resndata(NULL, "odroid-go-ultra-poweroff", -1, NULL, 0, NULL, 0); if (IS_ERR(pdev)) { platform_driver_unregister(&odroid_go_ultra_poweroff_driver); return PTR_ERR(pdev); } return 0; } static void __exit odroid_go_ultra_poweroff_exit(void) { /* Only delete when running on the Odroid Go Ultra device */ if (!of_device_is_compatible(of_root, "hardkernel,odroid-go-ultra")) return; platform_device_unregister(pdev); platform_driver_unregister(&odroid_go_ultra_poweroff_driver); } module_init(odroid_go_ultra_poweroff_init); module_exit(odroid_go_ultra_poweroff_exit); MODULE_AUTHOR("Neil Armstrong <neil.armstrong@linaro.org>"); MODULE_DESCRIPTION("Odroid Go Ultra poweroff driver"); MODULE_LICENSE("GPL"); |