1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * ChromeOS Device Tree Hardware Prober
4 *
5 * Copyright (c) 2024 Google LLC
6 */
7
8#include <linux/array_size.h>
9#include <linux/errno.h>
10#include <linux/i2c-of-prober.h>
11#include <linux/module.h>
12#include <linux/of.h>
13#include <linux/platform_device.h>
14#include <linux/stddef.h>
15
16#define DRV_NAME "chromeos_of_hw_prober"
17
18/**
19 * struct hw_prober_entry - Holds an entry for the hardware prober
20 *
21 * @compatible: compatible string to match against the machine
22 * @prober: prober function to call when machine matches
23 * @data: extra data for the prober function
24 */
25struct hw_prober_entry {
26 const char *compatible;
27 int (*prober)(struct device *dev, const void *data);
28 const void *data;
29};
30
31struct chromeos_i2c_probe_data {
32 const struct i2c_of_probe_cfg *cfg;
33 const struct i2c_of_probe_simple_opts *opts;
34};
35
36static int chromeos_i2c_component_prober(struct device *dev, const void *_data)
37{
38 const struct chromeos_i2c_probe_data *data = _data;
39 struct i2c_of_probe_simple_ctx ctx = {
40 .opts = data->opts,
41 };
42
43 return i2c_of_probe_component(dev, cfg: data->cfg, ctx: &ctx);
44}
45
46#define DEFINE_CHROMEOS_I2C_PROBE_CFG_SIMPLE_BY_TYPE(_type) \
47 static const struct i2c_of_probe_cfg chromeos_i2c_probe_simple_ ## _type ## _cfg = { \
48 .type = #_type, \
49 .ops = &i2c_of_probe_simple_ops, \
50 }
51
52#define DEFINE_CHROMEOS_I2C_PROBE_DATA_DUMB_BY_TYPE(_type) \
53 static const struct chromeos_i2c_probe_data chromeos_i2c_probe_dumb_ ## _type = { \
54 .cfg = &(const struct i2c_of_probe_cfg) { \
55 .type = #_type, \
56 }, \
57 }
58
59DEFINE_CHROMEOS_I2C_PROBE_DATA_DUMB_BY_TYPE(touchscreen);
60DEFINE_CHROMEOS_I2C_PROBE_DATA_DUMB_BY_TYPE(trackpad);
61
62DEFINE_CHROMEOS_I2C_PROBE_CFG_SIMPLE_BY_TYPE(touchscreen);
63DEFINE_CHROMEOS_I2C_PROBE_CFG_SIMPLE_BY_TYPE(trackpad);
64
65static const struct chromeos_i2c_probe_data chromeos_i2c_probe_hana_trackpad = {
66 .cfg = &chromeos_i2c_probe_simple_trackpad_cfg,
67 .opts = &(const struct i2c_of_probe_simple_opts) {
68 .res_node_compatible = "elan,ekth3000",
69 .supply_name = "vcc",
70 /*
71 * ELAN trackpad needs 2 ms for H/W init and 100 ms for F/W init.
72 * Synaptics trackpad needs 100 ms.
73 * However, the regulator is set to "always-on", presumably to
74 * avoid this delay. The ELAN driver is also missing delays.
75 */
76 .post_power_on_delay_ms = 0,
77 },
78};
79
80static const struct chromeos_i2c_probe_data chromeos_i2c_probe_squirtle_touchscreen = {
81 .cfg = &chromeos_i2c_probe_simple_touchscreen_cfg,
82 .opts = &(const struct i2c_of_probe_simple_opts) {
83 .res_node_compatible = "elan,ekth6a12nay",
84 .supply_name = "vcc33",
85 .gpio_name = "reset",
86 .post_power_on_delay_ms = 10,
87 .post_gpio_config_delay_ms = 300,
88 },
89};
90
91static const struct hw_prober_entry hw_prober_platforms[] = {
92 {
93 .compatible = "google,hana",
94 .prober = chromeos_i2c_component_prober,
95 .data = &chromeos_i2c_probe_dumb_touchscreen,
96 }, {
97 .compatible = "google,hana",
98 .prober = chromeos_i2c_component_prober,
99 .data = &chromeos_i2c_probe_hana_trackpad,
100 }, {
101 .compatible = "google,spherion",
102 .prober = chromeos_i2c_component_prober,
103 .data = &chromeos_i2c_probe_hana_trackpad,
104 }, {
105 .compatible = "google,squirtle",
106 .prober = chromeos_i2c_component_prober,
107 .data = &chromeos_i2c_probe_dumb_trackpad,
108 }, {
109 .compatible = "google,squirtle",
110 .prober = chromeos_i2c_component_prober,
111 .data = &chromeos_i2c_probe_squirtle_touchscreen,
112 }, {
113 .compatible = "google,steelix",
114 .prober = chromeos_i2c_component_prober,
115 .data = &chromeos_i2c_probe_dumb_trackpad,
116 }, {
117 .compatible = "google,voltorb",
118 .prober = chromeos_i2c_component_prober,
119 .data = &chromeos_i2c_probe_dumb_trackpad,
120 },
121};
122
123static int chromeos_of_hw_prober_probe(struct platform_device *pdev)
124{
125 for (size_t i = 0; i < ARRAY_SIZE(hw_prober_platforms); i++) {
126 int ret;
127
128 if (!of_machine_is_compatible(compat: hw_prober_platforms[i].compatible))
129 continue;
130
131 ret = hw_prober_platforms[i].prober(&pdev->dev, hw_prober_platforms[i].data);
132 /* Ignore unrecoverable errors and keep going through other probers */
133 if (ret == -EPROBE_DEFER)
134 return ret;
135 }
136
137 return 0;
138}
139
140static struct platform_driver chromeos_of_hw_prober_driver = {
141 .probe = chromeos_of_hw_prober_probe,
142 .driver = {
143 .name = DRV_NAME,
144 },
145};
146
147static struct platform_device *chromeos_of_hw_prober_pdev;
148
149static int chromeos_of_hw_prober_driver_init(void)
150{
151 size_t i;
152 int ret;
153
154 for (i = 0; i < ARRAY_SIZE(hw_prober_platforms); i++)
155 if (of_machine_is_compatible(compat: hw_prober_platforms[i].compatible))
156 break;
157 if (i == ARRAY_SIZE(hw_prober_platforms))
158 return -ENODEV;
159
160 ret = platform_driver_register(&chromeos_of_hw_prober_driver);
161 if (ret)
162 return ret;
163
164 chromeos_of_hw_prober_pdev =
165 platform_device_register_simple(DRV_NAME, PLATFORM_DEVID_NONE, NULL, num: 0);
166 if (IS_ERR(ptr: chromeos_of_hw_prober_pdev))
167 goto err;
168
169 return 0;
170
171err:
172 platform_driver_unregister(&chromeos_of_hw_prober_driver);
173
174 return PTR_ERR(ptr: chromeos_of_hw_prober_pdev);
175}
176module_init(chromeos_of_hw_prober_driver_init);
177
178static void chromeos_of_hw_prober_driver_exit(void)
179{
180 platform_device_unregister(chromeos_of_hw_prober_pdev);
181 platform_driver_unregister(&chromeos_of_hw_prober_driver);
182}
183module_exit(chromeos_of_hw_prober_driver_exit);
184
185MODULE_LICENSE("GPL");
186MODULE_DESCRIPTION("ChromeOS device tree hardware prober");
187MODULE_IMPORT_NS("I2C_OF_PROBER");
188

source code of linux/drivers/platform/chrome/chromeos_of_hw_prober.c