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