summaryrefslogtreecommitdiffstats
path: root/drivers/button/button-adc.c
blob: fd896c76f9d80188bfbc40dc16b28358a2c69b30 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2021 Samsung Electronics Co., Ltd.
 *		http://www.samsung.com
 * Author: Marek Szyprowski <m.szyprowski@samsung.com>
 */

#include <common.h>
#include <adc.h>
#include <button.h>
#include <log.h>
#include <dm.h>
#include <dm/lists.h>
#include <dm/of_access.h>
#include <dm/uclass-internal.h>

/**
 * struct button_adc_priv - private data for button-adc driver.
 *
 * @adc: Analog to Digital Converter device to which button is connected.
 * @channel: channel of the ADC device to probe the button state.
 * @min: minimal uV value to consider button as pressed.
 * @max: maximal uV value to consider button as pressed.
 */
struct button_adc_priv {
	struct udevice *adc;
	int channel;
	int min;
	int max;
};

static enum button_state_t button_adc_get_state(struct udevice *dev)
{
	struct button_adc_priv *priv = dev_get_priv(dev);
	unsigned int val;
	int ret, uV;

	ret = adc_start_channel(priv->adc, priv->channel);
	if (ret)
		return ret;

	ret = adc_channel_data(priv->adc, priv->channel, &val);
	if (ret)
		return ret;

	ret = adc_raw_to_uV(priv->adc, val, &uV);
	if (ret)
		return ret;

	return (uV >= priv->min && uV < priv->max) ? BUTTON_ON : BUTTON_OFF;
}

static int button_adc_of_to_plat(struct udevice *dev)
{
	struct button_uc_plat *uc_plat = dev_get_uclass_plat(dev);
	struct button_adc_priv *priv = dev_get_priv(dev);
	struct ofnode_phandle_args args;
	u32 threshold, up_threshold, t;
	ofnode node;
	int ret;

	/* Ignore the top-level button node */
	if (!uc_plat->label)
		return 0;

	ret = dev_read_phandle_with_args(dev->parent, "io-channels",
					 "#io-channel-cells", 0, 0, &args);
	if (ret)
		return ret;

	ret = uclass_get_device_by_ofnode(UCLASS_ADC, args.node, &priv->adc);
	if (ret)
		return ret;

	ret = ofnode_read_u32(dev_ofnode(dev->parent),
			      "keyup-threshold-microvolt", &up_threshold);
	if (ret)
		return ret;

	ret = ofnode_read_u32(dev_ofnode(dev), "press-threshold-microvolt",
			      &threshold);
	if (ret)
		return ret;

	dev_for_each_subnode(node, dev->parent) {
		ret = ofnode_read_u32(node, "press-threshold-microvolt", &t);
		if (ret)
			return ret;

		if (t > threshold)
			up_threshold = t;
	}

	priv->channel = args.args[0];
	priv->min = threshold;
	priv->max = up_threshold;

	return ret;
}

static int button_adc_bind(struct udevice *parent)
{
	struct udevice *dev;
	ofnode node;
	int ret;

	dev_for_each_subnode(node, parent) {
		struct button_uc_plat *uc_plat;
		const char *label;

		label = ofnode_read_string(node, "label");
		if (!label) {
			debug("%s: node %s has no label\n", __func__,
			      ofnode_get_name(node));
			return -EINVAL;
		}
		ret = device_bind_driver_to_node(parent, "button_adc",
						 ofnode_get_name(node),
						 node, &dev);
		if (ret)
			return ret;
		uc_plat = dev_get_uclass_plat(dev);
		uc_plat->label = label;
	}

	return 0;
}

static const struct button_ops button_adc_ops = {
	.get_state	= button_adc_get_state,
};

static const struct udevice_id button_adc_ids[] = {
	{ .compatible = "adc-keys" },
	{ }
};

U_BOOT_DRIVER(button_adc) = {
	.name		= "button_adc",
	.id		= UCLASS_BUTTON,
	.of_match	= button_adc_ids,
	.ops		= &button_adc_ops,
	.priv_auto	= sizeof(struct button_adc_priv),
	.bind		= button_adc_bind,
	.of_to_plat	= button_adc_of_to_plat,
};