1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3 * Qualcomm generic pmic gpio driver
4 *
5 * (C) Copyright 2015 Mateusz Kulikowski <mateusz.kulikowski@gmail.com>
6 * (C) Copyright 2023 Linaro Ltd.
7 */
8
9 #include <button.h>
10 #include <dt-bindings/input/linux-event-codes.h>
11 #include <dm.h>
12 #include <dm/device-internal.h>
13 #include <dm/lists.h>
14 #include <log.h>
15 #include <power/pmic.h>
16 #include <spmi/spmi.h>
17 #include <linux/bitops.h>
18 #include <time.h>
19
20 #define REG_TYPE 0x4
21 #define REG_SUBTYPE 0x5
22
23 struct qcom_pmic_btn_data {
24 char *compatible;
25 unsigned int status_bit;
26 int code;
27 char *label;
28 };
29
30 struct qcom_pmic_btn_priv {
31 u32 base;
32 u32 status_bit;
33 int code;
34 struct udevice *pmic;
35 ulong last_release_time;
36 };
37
38 #define PON_INT_RT_STS 0x10
39 #define PON_KPDPWR_N_SET 0
40 #define PON_RESIN_N_SET 1
41 #define PON_GEN3_RESIN_N_SET 6
42 #define PON_GEN3_KPDPWR_N_SET 7
43
qcom_pwrkey_get_state(struct udevice * dev)44 static enum button_state_t qcom_pwrkey_get_state(struct udevice *dev)
45 {
46 struct qcom_pmic_btn_priv *priv = dev_get_priv(dev);
47 bool pressed;
48 int reg;
49
50 if (get_timer_us(0) - priv->last_release_time < 25000)
51 return BUTTON_OFF;
52
53 reg = pmic_reg_read(priv->pmic, priv->base + PON_INT_RT_STS);
54 if (reg < 0)
55 return 0;
56
57 pressed = !!(reg & BIT(priv->status_bit));
58 if (!pressed)
59 priv->last_release_time = get_timer_us(0);
60
61 return pressed;
62 }
63
qcom_pwrkey_get_code(struct udevice * dev)64 static int qcom_pwrkey_get_code(struct udevice *dev)
65 {
66 struct qcom_pmic_btn_priv *priv = dev_get_priv(dev);
67
68 return priv->code;
69 }
70
71 static const struct qcom_pmic_btn_data qcom_pmic_btn_data_table[] = {
72 {
73 .compatible = "qcom,pm8941-pwrkey",
74 .status_bit = PON_KPDPWR_N_SET,
75 .code = KEY_ENTER,
76 .label = "Power Button",
77 },
78 {
79 .compatible = "qcom,pm8941-resin",
80 .status_bit = PON_RESIN_N_SET,
81 .code = KEY_DOWN,
82 .label = "Volume Down",
83 },
84 {
85 .compatible = "qcom,pmk8350-pwrkey",
86 .status_bit = PON_GEN3_KPDPWR_N_SET,
87 .code = KEY_ENTER,
88 .label = "Power Button",
89 },
90 {
91 .compatible = "qcom,pmk8350-resin",
92 .status_bit = PON_GEN3_RESIN_N_SET,
93 .code = KEY_DOWN,
94 .label = "Volume Down",
95 },
96 };
97
button_qcom_pmic_match(ofnode node)98 static const struct qcom_pmic_btn_data *button_qcom_pmic_match(ofnode node)
99 {
100 int i;
101
102 for (i = 0; i < ARRAY_SIZE(qcom_pmic_btn_data_table); ++i) {
103 if (ofnode_device_is_compatible(node,
104 qcom_pmic_btn_data_table[i].compatible))
105 return &qcom_pmic_btn_data_table[i];
106 }
107
108 return NULL;
109 }
110
qcom_pwrkey_probe(struct udevice * dev)111 static int qcom_pwrkey_probe(struct udevice *dev)
112 {
113 struct button_uc_plat *uc_plat = dev_get_uclass_plat(dev);
114 struct qcom_pmic_btn_priv *priv = dev_get_priv(dev);
115 const struct qcom_pmic_btn_data *btn_data;
116 ofnode node = dev_ofnode(dev);
117 int ret;
118 u64 base;
119
120 /* Ignore the top-level pon node */
121 if (!uc_plat->label)
122 return 0;
123
124 /* Get the data for the node compatible */
125 btn_data = button_qcom_pmic_match(node);
126 if (!btn_data)
127 return -EINVAL;
128
129 priv->status_bit = btn_data->status_bit;
130 priv->code = btn_data->code;
131
132 /* the pwrkey and resin nodes are children of the "pon" node, get the
133 * PMIC device to use in pmic_reg_* calls.
134 */
135 priv->pmic = dev->parent->parent;
136
137 /* Get the address of the parent pon node */
138 base = dev_read_addr(dev->parent);
139 if (base == FDT_ADDR_T_NONE) {
140 printf("%s: Can't find address\n", dev->name);
141 return -EINVAL;
142 }
143
144 priv->base = base;
145
146 ret = dev_read_u32(dev, "linux,code", &priv->code);
147 if (ret == 0) {
148 /* convert key, if read OK */
149 switch (priv->code) {
150 case KEY_VOLUMEDOWN:
151 priv->code = KEY_DOWN;
152 uc_plat->label = "Volume Down";
153 break;
154 case KEY_VOLUMEUP:
155 priv->code = KEY_UP;
156 uc_plat->label = "Volume Up";
157 break;
158 }
159 }
160
161 /* Do a sanity check */
162 ret = pmic_reg_read(priv->pmic, priv->base + REG_TYPE);
163 if (ret != 0x1 && ret != 0xb) {
164 printf("%s: unexpected PMIC function type %d\n", dev->name, ret);
165 return -ENXIO;
166 }
167
168 ret = pmic_reg_read(priv->pmic, priv->base + REG_SUBTYPE);
169 if (ret < 0 || (ret & 0x7) == 0) {
170 printf("%s: unexpected PMIC function subtype %d\n", dev->name, ret);
171 return -ENXIO;
172 }
173
174 return 0;
175 }
176
button_qcom_pmic_bind(struct udevice * parent)177 static int button_qcom_pmic_bind(struct udevice *parent)
178 {
179 struct udevice *dev;
180 ofnode node;
181 int ret;
182
183 dev_for_each_subnode(node, parent) {
184 const struct qcom_pmic_btn_data *btn_data;
185 struct button_uc_plat *uc_plat;
186 const char *label;
187
188 if (!ofnode_is_enabled(node))
189 continue;
190
191 /* Get the data for the node compatible */
192 btn_data = button_qcom_pmic_match(node);
193 if (!btn_data) {
194 debug("Unknown button node '%s'\n", ofnode_get_name(node));
195 continue;
196 }
197
198 label = ofnode_get_name(node);
199 ret = device_bind_driver_to_node(parent, "qcom_pwrkey",
200 label,
201 node, &dev);
202 if (ret) {
203 printf("Failed to bind %s! %d\n", label, ret);
204 return ret;
205 }
206 uc_plat = dev_get_uclass_plat(dev);
207 uc_plat->label = btn_data->label;
208 }
209
210 return 0;
211 }
212
213 static const struct button_ops button_qcom_pmic_ops = {
214 .get_state = qcom_pwrkey_get_state,
215 .get_code = qcom_pwrkey_get_code,
216 };
217
218 static const struct udevice_id qcom_pwrkey_ids[] = {
219 { .compatible = "qcom,pm8916-pon" },
220 { .compatible = "qcom,pm8941-pon" },
221 { .compatible = "qcom,pm8998-pon" },
222 { .compatible = "qcom,pmk8350-pon" },
223 { }
224 };
225
226 U_BOOT_DRIVER(qcom_pwrkey) = {
227 .name = "qcom_pwrkey",
228 .id = UCLASS_BUTTON,
229 .of_match = qcom_pwrkey_ids,
230 .bind = button_qcom_pmic_bind,
231 .probe = qcom_pwrkey_probe,
232 .ops = &button_qcom_pmic_ops,
233 .priv_auto = sizeof(struct qcom_pmic_btn_priv),
234 };
235