1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Copyright (C) 2021 Samsung Electronics Co., Ltd.
4  *		http://www.samsung.com
5  * Author: Marek Szyprowski <m.szyprowski@samsung.com>
6  */
7 
8 #include <adc.h>
9 #include <button.h>
10 #include <log.h>
11 #include <dm.h>
12 #include <dm/lists.h>
13 #include <dm/of_access.h>
14 #include <dm/uclass-internal.h>
15 
16 /**
17  * struct button_adc_priv - private data for button-adc driver.
18  *
19  * @adc: Analog to Digital Converter device to which button is connected.
20  * @channel: channel of the ADC device to probe the button state.
21  * @min: minimal uV value to consider button as pressed.
22  * @max: maximal uV value to consider button as pressed.
23  */
24 struct button_adc_priv {
25 	struct udevice *adc;
26 	int channel;
27 	int min;
28 	int max;
29 };
30 
button_adc_get_state(struct udevice * dev)31 static enum button_state_t button_adc_get_state(struct udevice *dev)
32 {
33 	struct button_adc_priv *priv = dev_get_priv(dev);
34 	unsigned int val;
35 	int ret, uV;
36 
37 	ret = adc_start_channel(priv->adc, priv->channel);
38 	if (ret)
39 		return ret;
40 
41 	ret = adc_channel_data(priv->adc, priv->channel, &val);
42 	if (ret)
43 		return ret;
44 
45 	ret = adc_raw_to_uV(priv->adc, val, &uV);
46 	if (ret)
47 		return ret;
48 
49 	return (uV >= priv->min && uV < priv->max) ? BUTTON_ON : BUTTON_OFF;
50 }
51 
button_adc_of_to_plat(struct udevice * dev)52 static int button_adc_of_to_plat(struct udevice *dev)
53 {
54 	struct button_uc_plat *uc_plat = dev_get_uclass_plat(dev);
55 	struct button_adc_priv *priv = dev_get_priv(dev);
56 	struct ofnode_phandle_args args;
57 	u32 down_threshold = 0, up_threshold, voltage, t;
58 	ofnode node;
59 	int ret;
60 
61 	/* Ignore the top-level button node */
62 	if (!uc_plat->label)
63 		return 0;
64 
65 	ret = dev_read_phandle_with_args(dev->parent, "io-channels",
66 					 "#io-channel-cells", 0, 0, &args);
67 	if (ret)
68 		return ret;
69 
70 	ret = uclass_get_device_by_ofnode(UCLASS_ADC, args.node, &priv->adc);
71 	if (ret)
72 		return ret;
73 
74 	ret = ofnode_read_u32(dev_ofnode(dev->parent),
75 			      "keyup-threshold-microvolt", &up_threshold);
76 	if (ret)
77 		return ret;
78 
79 	ret = ofnode_read_u32(dev_ofnode(dev), "press-threshold-microvolt",
80 			      &voltage);
81 	if (ret)
82 		return ret;
83 
84 	dev_for_each_subnode(node, dev->parent) {
85 		ret = ofnode_read_u32(node, "press-threshold-microvolt", &t);
86 		if (ret)
87 			return ret;
88 
89 		if (t > voltage && t < up_threshold)
90 			up_threshold = t;
91 		else if (t < voltage && t > down_threshold)
92 			down_threshold = t;
93 	}
94 
95 	priv->channel = args.args[0];
96 
97 	/*
98 	 * Define the voltage range such that the button is only pressed
99 	 * when the voltage is closest to its own press-threshold-microvolt
100 	 */
101 	if (down_threshold == 0)
102 		priv->min = 0;
103 	else
104 		priv->min = down_threshold + (voltage - down_threshold) / 2;
105 
106 	priv->max = voltage + (up_threshold - voltage) / 2;
107 
108 	return ret;
109 }
110 
button_adc_bind(struct udevice * parent)111 static int button_adc_bind(struct udevice *parent)
112 {
113 	struct udevice *dev;
114 	ofnode node;
115 	int ret;
116 
117 	dev_for_each_subnode(node, parent) {
118 		struct button_uc_plat *uc_plat;
119 		const char *label;
120 
121 		label = ofnode_read_string(node, "label");
122 		if (!label) {
123 			debug("%s: node %s has no label\n", __func__,
124 			      ofnode_get_name(node));
125 			return -EINVAL;
126 		}
127 		ret = device_bind_driver_to_node(parent, "button_adc",
128 						 ofnode_get_name(node),
129 						 node, &dev);
130 		if (ret)
131 			return ret;
132 		uc_plat = dev_get_uclass_plat(dev);
133 		uc_plat->label = label;
134 	}
135 
136 	return 0;
137 }
138 
139 static const struct button_ops button_adc_ops = {
140 	.get_state	= button_adc_get_state,
141 };
142 
143 static const struct udevice_id button_adc_ids[] = {
144 	{ .compatible = "adc-keys" },
145 	{ }
146 };
147 
148 U_BOOT_DRIVER(button_adc) = {
149 	.name		= "button_adc",
150 	.id		= UCLASS_BUTTON,
151 	.of_match	= button_adc_ids,
152 	.ops		= &button_adc_ops,
153 	.priv_auto	= sizeof(struct button_adc_priv),
154 	.bind		= button_adc_bind,
155 	.of_to_plat	= button_adc_of_to_plat,
156 };
157