blob: 85addfe32a27dc7f1c9bc5c2565c5b4a7e853d12 [file] [log] [blame]
Caleb Connolly55759a32023-12-05 13:46:47 +00001// 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>
Caleb Connollyd176ad72024-11-13 05:51:03 +010018#include <time.h>
Caleb Connolly55759a32023-12-05 13:46:47 +000019
20#define REG_TYPE 0x4
21#define REG_SUBTYPE 0x5
22
Neil Armstrong36890802024-04-10 17:59:44 +020023struct qcom_pmic_btn_data {
24 char *compatible;
25 unsigned int status_bit;
26 int code;
27 char *label;
28};
29
Caleb Connolly55759a32023-12-05 13:46:47 +000030struct qcom_pmic_btn_priv {
31 u32 base;
32 u32 status_bit;
33 int code;
34 struct udevice *pmic;
Caleb Connollyd176ad72024-11-13 05:51:03 +010035 ulong last_release_time;
Caleb Connolly55759a32023-12-05 13:46:47 +000036};
37
38#define PON_INT_RT_STS 0x10
Neil Armstrong36890802024-04-10 17:59:44 +020039#define PON_KPDPWR_N_SET 0
40#define PON_RESIN_N_SET 1
Neil Armstrong7231ce32024-04-10 17:59:45 +020041#define PON_GEN3_RESIN_N_SET 6
42#define PON_GEN3_KPDPWR_N_SET 7
Caleb Connolly55759a32023-12-05 13:46:47 +000043
44static enum button_state_t qcom_pwrkey_get_state(struct udevice *dev)
45{
46 struct qcom_pmic_btn_priv *priv = dev_get_priv(dev);
Caleb Connollyd176ad72024-11-13 05:51:03 +010047 bool pressed;
48 int reg;
Caleb Connolly55759a32023-12-05 13:46:47 +000049
Caleb Connollyd176ad72024-11-13 05:51:03 +010050 if (get_timer_us(0) - priv->last_release_time < 25000)
51 return BUTTON_OFF;
Caleb Connolly55759a32023-12-05 13:46:47 +000052
Caleb Connollyd176ad72024-11-13 05:51:03 +010053 reg = pmic_reg_read(priv->pmic, priv->base + PON_INT_RT_STS);
Caleb Connolly55759a32023-12-05 13:46:47 +000054 if (reg < 0)
55 return 0;
56
Caleb Connollyd176ad72024-11-13 05:51:03 +010057 pressed = !!(reg & BIT(priv->status_bit));
58 if (!pressed)
59 priv->last_release_time = get_timer_us(0);
60
61 return pressed;
Caleb Connolly55759a32023-12-05 13:46:47 +000062}
63
64static 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
Neil Armstrong36890802024-04-10 17:59:44 +020071static 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,
Caleb Connollyeaef4732025-03-31 14:23:22 +020076 .label = "Power Button",
Neil Armstrong36890802024-04-10 17:59:44 +020077 },
78 {
79 .compatible = "qcom,pm8941-resin",
80 .status_bit = PON_RESIN_N_SET,
81 .code = KEY_DOWN,
Caleb Connollyeaef4732025-03-31 14:23:22 +020082 .label = "Volume Down",
Neil Armstrong36890802024-04-10 17:59:44 +020083 },
Neil Armstrong7231ce32024-04-10 17:59:45 +020084 {
85 .compatible = "qcom,pmk8350-pwrkey",
86 .status_bit = PON_GEN3_KPDPWR_N_SET,
87 .code = KEY_ENTER,
Caleb Connollyeaef4732025-03-31 14:23:22 +020088 .label = "Power Button",
Neil Armstrong7231ce32024-04-10 17:59:45 +020089 },
90 {
91 .compatible = "qcom,pmk8350-resin",
92 .status_bit = PON_GEN3_RESIN_N_SET,
93 .code = KEY_DOWN,
Caleb Connollyeaef4732025-03-31 14:23:22 +020094 .label = "Volume Down",
Neil Armstrong7231ce32024-04-10 17:59:45 +020095 },
Neil Armstrong36890802024-04-10 17:59:44 +020096};
97
98static 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
Caleb Connolly55759a32023-12-05 13:46:47 +0000111static 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);
Neil Armstrong36890802024-04-10 17:59:44 +0200115 const struct qcom_pmic_btn_data *btn_data;
Caleb Connolly55759a32023-12-05 13:46:47 +0000116 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
Neil Armstrong36890802024-04-10 17:59:44 +0200124 /* 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
Caleb Connolly55759a32023-12-05 13:46:47 +0000132 /* 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
Alexey Minnekhanova4ead2e2025-04-24 04:48:11 +0300146 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
Caleb Connolly55759a32023-12-05 13:46:47 +0000161 /* 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);
Dan Carpenter7f1daec2024-01-31 10:09:15 +0300169 if (ret < 0 || (ret & 0x7) == 0) {
Neil Armstrong36890802024-04-10 17:59:44 +0200170 printf("%s: unexpected PMIC function subtype %d\n", dev->name, ret);
Caleb Connolly55759a32023-12-05 13:46:47 +0000171 return -ENXIO;
172 }
173
Caleb Connolly55759a32023-12-05 13:46:47 +0000174 return 0;
175}
176
177static 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) {
Neil Armstrong36890802024-04-10 17:59:44 +0200184 const struct qcom_pmic_btn_data *btn_data;
Caleb Connolly55759a32023-12-05 13:46:47 +0000185 struct button_uc_plat *uc_plat;
186 const char *label;
187
188 if (!ofnode_is_enabled(node))
189 continue;
190
Neil Armstrong36890802024-04-10 17:59:44 +0200191 /* 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
Caleb Connolly55759a32023-12-05 13:46:47 +0000198 ret = device_bind_driver_to_node(parent, "qcom_pwrkey",
199 ofnode_get_name(node),
200 node, &dev);
201 if (ret) {
202 printf("Failed to bind %s! %d\n", label, ret);
203 return ret;
204 }
205 uc_plat = dev_get_uclass_plat(dev);
Neil Armstrong36890802024-04-10 17:59:44 +0200206 uc_plat->label = btn_data->label;
Caleb Connolly55759a32023-12-05 13:46:47 +0000207 }
208
209 return 0;
210}
211
212static const struct button_ops button_qcom_pmic_ops = {
213 .get_state = qcom_pwrkey_get_state,
214 .get_code = qcom_pwrkey_get_code,
215};
216
217static const struct udevice_id qcom_pwrkey_ids[] = {
218 { .compatible = "qcom,pm8916-pon" },
219 { .compatible = "qcom,pm8941-pon" },
220 { .compatible = "qcom,pm8998-pon" },
Neil Armstrong7231ce32024-04-10 17:59:45 +0200221 { .compatible = "qcom,pmk8350-pon" },
Caleb Connolly55759a32023-12-05 13:46:47 +0000222 { }
223};
224
225U_BOOT_DRIVER(qcom_pwrkey) = {
226 .name = "qcom_pwrkey",
227 .id = UCLASS_BUTTON,
228 .of_match = qcom_pwrkey_ids,
229 .bind = button_qcom_pmic_bind,
230 .probe = qcom_pwrkey_probe,
231 .ops = &button_qcom_pmic_ops,
232 .priv_auto = sizeof(struct qcom_pmic_btn_priv),
233};