summaryrefslogtreecommitdiff
path: root/drivers/platform/chrome/chromeos_of_hw_prober.c
blob: 297d4704b75fde873b13b42a428b39d90ade3a6c (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
147
148
149
150
151
152
153
154
// SPDX-License-Identifier: GPL-2.0-only
/*
 * ChromeOS Device Tree Hardware Prober
 *
 * Copyright (c) 2024 Google LLC
 */

#include <linux/array_size.h>
#include <linux/errno.h>
#include <linux/i2c-of-prober.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/stddef.h>

#define DRV_NAME	"chromeos_of_hw_prober"

/**
 * struct hw_prober_entry - Holds an entry for the hardware prober
 *
 * @compatible:	compatible string to match against the machine
 * @prober:	prober function to call when machine matches
 * @data:	extra data for the prober function
 */
struct hw_prober_entry {
	const char *compatible;
	int (*prober)(struct device *dev, const void *data);
	const void *data;
};

struct chromeos_i2c_probe_data {
	const struct i2c_of_probe_cfg *cfg;
	const struct i2c_of_probe_simple_opts *opts;
};

static int chromeos_i2c_component_prober(struct device *dev, const void *_data)
{
	const struct chromeos_i2c_probe_data *data = _data;
	struct i2c_of_probe_simple_ctx ctx = {
		.opts = data->opts,
	};

	return i2c_of_probe_component(dev, data->cfg, &ctx);
}

#define DEFINE_CHROMEOS_I2C_PROBE_CFG_SIMPLE_BY_TYPE(_type)					\
	static const struct i2c_of_probe_cfg chromeos_i2c_probe_simple_ ## _type ## _cfg = {	\
		.type = #_type,									\
		.ops = &i2c_of_probe_simple_ops,						\
	}

#define DEFINE_CHROMEOS_I2C_PROBE_DATA_DUMB_BY_TYPE(_type)					\
	static const struct chromeos_i2c_probe_data chromeos_i2c_probe_dumb_ ## _type = {	\
		.cfg = &(const struct i2c_of_probe_cfg) {					\
			.type = #_type,								\
		},										\
	}

DEFINE_CHROMEOS_I2C_PROBE_DATA_DUMB_BY_TYPE(touchscreen);

DEFINE_CHROMEOS_I2C_PROBE_CFG_SIMPLE_BY_TYPE(trackpad);

static const struct chromeos_i2c_probe_data chromeos_i2c_probe_hana_trackpad = {
	.cfg = &chromeos_i2c_probe_simple_trackpad_cfg,
	.opts = &(const struct i2c_of_probe_simple_opts) {
		.res_node_compatible = "elan,ekth3000",
		.supply_name = "vcc",
		/*
		 * ELAN trackpad needs 2 ms for H/W init and 100 ms for F/W init.
		 * Synaptics trackpad needs 100 ms.
		 * However, the regulator is set to "always-on", presumably to
		 * avoid this delay. The ELAN driver is also missing delays.
		 */
		.post_power_on_delay_ms = 0,
	},
};

static const struct hw_prober_entry hw_prober_platforms[] = {
	{
		.compatible = "google,hana",
		.prober = chromeos_i2c_component_prober,
		.data = &chromeos_i2c_probe_dumb_touchscreen,
	}, {
		.compatible = "google,hana",
		.prober = chromeos_i2c_component_prober,
		.data = &chromeos_i2c_probe_hana_trackpad,
	},
};

static int chromeos_of_hw_prober_probe(struct platform_device *pdev)
{
	for (size_t i = 0; i < ARRAY_SIZE(hw_prober_platforms); i++) {
		int ret;

		if (!of_machine_is_compatible(hw_prober_platforms[i].compatible))
			continue;

		ret = hw_prober_platforms[i].prober(&pdev->dev, hw_prober_platforms[i].data);
		/* Ignore unrecoverable errors and keep going through other probers */
		if (ret == -EPROBE_DEFER)
			return ret;
	}

	return 0;
}

static struct platform_driver chromeos_of_hw_prober_driver = {
	.probe	= chromeos_of_hw_prober_probe,
	.driver	= {
		.name = DRV_NAME,
	},
};

static struct platform_device *chromeos_of_hw_prober_pdev;

static int chromeos_of_hw_prober_driver_init(void)
{
	size_t i;
	int ret;

	for (i = 0; i < ARRAY_SIZE(hw_prober_platforms); i++)
		if (of_machine_is_compatible(hw_prober_platforms[i].compatible))
			break;
	if (i == ARRAY_SIZE(hw_prober_platforms))
		return -ENODEV;

	ret = platform_driver_register(&chromeos_of_hw_prober_driver);
	if (ret)
		return ret;

	chromeos_of_hw_prober_pdev =
			platform_device_register_simple(DRV_NAME, PLATFORM_DEVID_NONE, NULL, 0);
	if (IS_ERR(chromeos_of_hw_prober_pdev))
		goto err;

	return 0;

err:
	platform_driver_unregister(&chromeos_of_hw_prober_driver);

	return PTR_ERR(chromeos_of_hw_prober_pdev);
}
module_init(chromeos_of_hw_prober_driver_init);

static void chromeos_of_hw_prober_driver_exit(void)
{
	platform_device_unregister(chromeos_of_hw_prober_pdev);
	platform_driver_unregister(&chromeos_of_hw_prober_driver);
}
module_exit(chromeos_of_hw_prober_driver_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("ChromeOS device tree hardware prober");
MODULE_IMPORT_NS(I2C_OF_PROBER);