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