1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Power-source driver for Bay Trail Crystal Cove PMIC |
4 | * |
5 | * Copyright (c) 2023 Hans de Goede <hdegoede@redhat.com> |
6 | * |
7 | * Based on intel_crystalcove_pwrsrc.c from Android kernel sources, which is: |
8 | * Copyright (C) 2013 Intel Corporation |
9 | */ |
10 | |
11 | #include <linux/debugfs.h> |
12 | #include <linux/mfd/intel_soc_pmic.h> |
13 | #include <linux/module.h> |
14 | #include <linux/platform_device.h> |
15 | #include <linux/regmap.h> |
16 | |
17 | #define CRYSTALCOVE_SPWRSRC_REG 0x1E |
18 | #define CRYSTALCOVE_RESETSRC0_REG 0x20 |
19 | #define CRYSTALCOVE_RESETSRC1_REG 0x21 |
20 | #define CRYSTALCOVE_WAKESRC_REG 0x22 |
21 | |
22 | struct crc_pwrsrc_data { |
23 | struct regmap *regmap; |
24 | struct dentry *debug_dentry; |
25 | unsigned int resetsrc0; |
26 | unsigned int resetsrc1; |
27 | unsigned int wakesrc; |
28 | }; |
29 | |
30 | static const char * const pwrsrc_pwrsrc_info[] = { |
31 | /* bit 0 */ "USB" , |
32 | /* bit 1 */ "DC in" , |
33 | /* bit 2 */ "Battery" , |
34 | NULL, |
35 | }; |
36 | |
37 | static const char * const pwrsrc_resetsrc0_info[] = { |
38 | /* bit 0 */ "SOC reporting a thermal event" , |
39 | /* bit 1 */ "critical PMIC temperature" , |
40 | /* bit 2 */ "critical system temperature" , |
41 | /* bit 3 */ "critical battery temperature" , |
42 | /* bit 4 */ "VSYS under voltage" , |
43 | /* bit 5 */ "VSYS over voltage" , |
44 | /* bit 6 */ "battery removal" , |
45 | NULL, |
46 | }; |
47 | |
48 | static const char * const pwrsrc_resetsrc1_info[] = { |
49 | /* bit 0 */ "VCRIT threshold" , |
50 | /* bit 1 */ "BATID reporting battery removal" , |
51 | /* bit 2 */ "user pressing the power button" , |
52 | NULL, |
53 | }; |
54 | |
55 | static const char * const pwrsrc_wakesrc_info[] = { |
56 | /* bit 0 */ "user pressing the power button" , |
57 | /* bit 1 */ "a battery insertion" , |
58 | /* bit 2 */ "a USB charger insertion" , |
59 | /* bit 3 */ "an adapter insertion" , |
60 | NULL, |
61 | }; |
62 | |
63 | static void crc_pwrsrc_log(struct seq_file *seq, const char *prefix, |
64 | const char * const *info, unsigned int reg_val) |
65 | { |
66 | int i; |
67 | |
68 | for (i = 0; info[i]; i++) { |
69 | if (reg_val & BIT(i)) |
70 | seq_printf(m: seq, fmt: "%s by %s\n" , prefix, info[i]); |
71 | } |
72 | } |
73 | |
74 | static int pwrsrc_show(struct seq_file *seq, void *unused) |
75 | { |
76 | struct crc_pwrsrc_data *data = seq->private; |
77 | unsigned int reg_val; |
78 | int ret; |
79 | |
80 | ret = regmap_read(map: data->regmap, CRYSTALCOVE_SPWRSRC_REG, val: ®_val); |
81 | if (ret) |
82 | return ret; |
83 | |
84 | crc_pwrsrc_log(seq, prefix: "System powered" , info: pwrsrc_pwrsrc_info, reg_val); |
85 | return 0; |
86 | } |
87 | |
88 | static int resetsrc_show(struct seq_file *seq, void *unused) |
89 | { |
90 | struct crc_pwrsrc_data *data = seq->private; |
91 | |
92 | crc_pwrsrc_log(seq, prefix: "Last shutdown caused" , info: pwrsrc_resetsrc0_info, reg_val: data->resetsrc0); |
93 | crc_pwrsrc_log(seq, prefix: "Last shutdown caused" , info: pwrsrc_resetsrc1_info, reg_val: data->resetsrc1); |
94 | return 0; |
95 | } |
96 | |
97 | static int wakesrc_show(struct seq_file *seq, void *unused) |
98 | { |
99 | struct crc_pwrsrc_data *data = seq->private; |
100 | |
101 | crc_pwrsrc_log(seq, prefix: "Last wake caused" , info: pwrsrc_wakesrc_info, reg_val: data->wakesrc); |
102 | return 0; |
103 | } |
104 | |
105 | DEFINE_SHOW_ATTRIBUTE(pwrsrc); |
106 | DEFINE_SHOW_ATTRIBUTE(resetsrc); |
107 | DEFINE_SHOW_ATTRIBUTE(wakesrc); |
108 | |
109 | static int crc_pwrsrc_read_and_clear(struct crc_pwrsrc_data *data, |
110 | unsigned int reg, unsigned int *val) |
111 | { |
112 | int ret; |
113 | |
114 | ret = regmap_read(map: data->regmap, reg, val); |
115 | if (ret) |
116 | return ret; |
117 | |
118 | return regmap_write(map: data->regmap, reg, val: *val); |
119 | } |
120 | |
121 | static int crc_pwrsrc_probe(struct platform_device *pdev) |
122 | { |
123 | struct intel_soc_pmic *pmic = dev_get_drvdata(dev: pdev->dev.parent); |
124 | struct crc_pwrsrc_data *data; |
125 | int ret; |
126 | |
127 | data = devm_kzalloc(dev: &pdev->dev, size: sizeof(*data), GFP_KERNEL); |
128 | if (!data) |
129 | return -ENOMEM; |
130 | |
131 | data->regmap = pmic->regmap; |
132 | |
133 | /* |
134 | * Read + clear resetsrc0/1 and wakesrc now, so that they get |
135 | * cleared even if the debugfs interface is never used. |
136 | * |
137 | * Properly clearing the wakesrc is important, leaving bit 0 of it |
138 | * set turns reboot into poweroff on some tablets. |
139 | */ |
140 | ret = crc_pwrsrc_read_and_clear(data, CRYSTALCOVE_RESETSRC0_REG, val: &data->resetsrc0); |
141 | if (ret) |
142 | return ret; |
143 | |
144 | ret = crc_pwrsrc_read_and_clear(data, CRYSTALCOVE_RESETSRC1_REG, val: &data->resetsrc1); |
145 | if (ret) |
146 | return ret; |
147 | |
148 | ret = crc_pwrsrc_read_and_clear(data, CRYSTALCOVE_WAKESRC_REG, val: &data->wakesrc); |
149 | if (ret) |
150 | return ret; |
151 | |
152 | data->debug_dentry = debugfs_create_dir(KBUILD_MODNAME, NULL); |
153 | debugfs_create_file(name: "pwrsrc" , mode: 0444, parent: data->debug_dentry, data, fops: &pwrsrc_fops); |
154 | debugfs_create_file(name: "resetsrc" , mode: 0444, parent: data->debug_dentry, data, fops: &resetsrc_fops); |
155 | debugfs_create_file(name: "wakesrc" , mode: 0444, parent: data->debug_dentry, data, fops: &wakesrc_fops); |
156 | |
157 | platform_set_drvdata(pdev, data); |
158 | return 0; |
159 | } |
160 | |
161 | static void crc_pwrsrc_remove(struct platform_device *pdev) |
162 | { |
163 | struct crc_pwrsrc_data *data = platform_get_drvdata(pdev); |
164 | |
165 | debugfs_remove_recursive(dentry: data->debug_dentry); |
166 | } |
167 | |
168 | static struct platform_driver crc_pwrsrc_driver = { |
169 | .probe = crc_pwrsrc_probe, |
170 | .remove_new = crc_pwrsrc_remove, |
171 | .driver = { |
172 | .name = "crystal_cove_pwrsrc" , |
173 | }, |
174 | }; |
175 | module_platform_driver(crc_pwrsrc_driver); |
176 | |
177 | MODULE_ALIAS("platform:crystal_cove_pwrsrc" ); |
178 | MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>" ); |
179 | MODULE_DESCRIPTION("Power-source driver for Bay Trail Crystal Cove PMIC" ); |
180 | MODULE_LICENSE("GPL" ); |
181 | |