1// SPDX-License-Identifier: GPL-2.0
2/*
3 * Core driver for Wilco Embedded Controller
4 *
5 * Copyright 2018 Google LLC
6 *
7 * This is the entry point for the drivers that control the Wilco EC.
8 */
9
10#include <linux/acpi.h>
11#include <linux/device.h>
12#include <linux/ioport.h>
13#include <linux/module.h>
14#include <linux/platform_data/wilco-ec.h>
15#include <linux/platform_device.h>
16
17#include "../cros_ec_lpc_mec.h"
18
19#define DRV_NAME "wilco-ec"
20
21static struct resource *wilco_get_resource(struct platform_device *pdev,
22					   int index)
23{
24	struct device *dev = &pdev->dev;
25	struct resource *res;
26
27	res = platform_get_resource(pdev, IORESOURCE_IO, index);
28	if (!res) {
29		dev_dbg(dev, "Couldn't find IO resource %d\n", index);
30		return res;
31	}
32
33	return devm_request_region(dev, res->start, resource_size(res),
34				   dev_name(dev));
35}
36
37static int wilco_ec_probe(struct platform_device *pdev)
38{
39	struct device *dev = &pdev->dev;
40	struct wilco_ec_device *ec;
41	int ret;
42
43	ec = devm_kzalloc(dev, sizeof(*ec), GFP_KERNEL);
44	if (!ec)
45		return -ENOMEM;
46
47	platform_set_drvdata(pdev, ec);
48	ec->dev = dev;
49	mutex_init(&ec->mailbox_lock);
50
51	ec->data_size = sizeof(struct wilco_ec_response) + EC_MAILBOX_DATA_SIZE;
52	ec->data_buffer = devm_kzalloc(dev, ec->data_size, GFP_KERNEL);
53	if (!ec->data_buffer)
54		return -ENOMEM;
55
56	/* Prepare access to IO regions provided by ACPI */
57	ec->io_data = wilco_get_resource(pdev, 0);	/* Host Data */
58	ec->io_command = wilco_get_resource(pdev, 1);	/* Host Command */
59	ec->io_packet = wilco_get_resource(pdev, 2);	/* MEC EMI */
60	if (!ec->io_data || !ec->io_command || !ec->io_packet)
61		return -ENODEV;
62
63	/* Initialize cros_ec register interface for communication */
64	cros_ec_lpc_mec_init(ec->io_packet->start,
65			     ec->io_packet->start + EC_MAILBOX_DATA_SIZE);
66
67	/*
68	 * Register a child device that will be found by the debugfs driver.
69	 * Ignore failure.
70	 */
71	ec->debugfs_pdev = platform_device_register_data(dev,
72							 "wilco-ec-debugfs",
73							 PLATFORM_DEVID_AUTO,
74							 NULL, 0);
75
76	/* Register a child device that will be found by the RTC driver. */
77	ec->rtc_pdev = platform_device_register_data(dev, "rtc-wilco-ec",
78						     PLATFORM_DEVID_AUTO,
79						     NULL, 0);
80	if (IS_ERR(ec->rtc_pdev)) {
81		dev_err(dev, "Failed to create RTC platform device\n");
82		ret = PTR_ERR(ec->rtc_pdev);
83		goto unregister_debugfs;
84	}
85
86	/* Set up the keyboard backlight LEDs. */
87	ret = wilco_keyboard_leds_init(ec);
88	if (ret < 0) {
89		dev_err(dev,
90			"Failed to initialize keyboard LEDs: %d\n",
91			ret);
92		goto unregister_rtc;
93	}
94
95	ret = wilco_ec_add_sysfs(ec);
96	if (ret < 0) {
97		dev_err(dev, "Failed to create sysfs entries: %d\n", ret);
98		goto unregister_rtc;
99	}
100
101	/* Register child device to be found by charger config driver. */
102	ec->charger_pdev = platform_device_register_data(dev, "wilco-charger",
103							 PLATFORM_DEVID_AUTO,
104							 NULL, 0);
105	if (IS_ERR(ec->charger_pdev)) {
106		dev_err(dev, "Failed to create charger platform device\n");
107		ret = PTR_ERR(ec->charger_pdev);
108		goto remove_sysfs;
109	}
110
111	/* Register child device that will be found by the telemetry driver. */
112	ec->telem_pdev = platform_device_register_data(dev, "wilco_telem",
113						       PLATFORM_DEVID_AUTO,
114						       ec, sizeof(*ec));
115	if (IS_ERR(ec->telem_pdev)) {
116		dev_err(dev, "Failed to create telemetry platform device\n");
117		ret = PTR_ERR(ec->telem_pdev);
118		goto unregister_charge_config;
119	}
120
121	return 0;
122
123unregister_charge_config:
124	platform_device_unregister(ec->charger_pdev);
125remove_sysfs:
126	wilco_ec_remove_sysfs(ec);
127unregister_rtc:
128	platform_device_unregister(ec->rtc_pdev);
129unregister_debugfs:
130	if (ec->debugfs_pdev)
131		platform_device_unregister(ec->debugfs_pdev);
132	return ret;
133}
134
135static void wilco_ec_remove(struct platform_device *pdev)
136{
137	struct wilco_ec_device *ec = platform_get_drvdata(pdev);
138
139	platform_device_unregister(ec->telem_pdev);
140	platform_device_unregister(ec->charger_pdev);
141	wilco_ec_remove_sysfs(ec);
142	platform_device_unregister(ec->rtc_pdev);
143	if (ec->debugfs_pdev)
144		platform_device_unregister(ec->debugfs_pdev);
145}
146
147static const struct acpi_device_id wilco_ec_acpi_device_ids[] = {
148	{ "GOOG000C", 0 },
149	{ }
150};
151MODULE_DEVICE_TABLE(acpi, wilco_ec_acpi_device_ids);
152
153static struct platform_driver wilco_ec_driver = {
154	.driver = {
155		.name = DRV_NAME,
156		.acpi_match_table = wilco_ec_acpi_device_ids,
157	},
158	.probe = wilco_ec_probe,
159	.remove_new = wilco_ec_remove,
160};
161
162module_platform_driver(wilco_ec_driver);
163
164MODULE_AUTHOR("Nick Crews <ncrews@chromium.org>");
165MODULE_AUTHOR("Duncan Laurie <dlaurie@chromium.org>");
166MODULE_LICENSE("GPL v2");
167MODULE_DESCRIPTION("ChromeOS Wilco Embedded Controller driver");
168MODULE_ALIAS("platform:" DRV_NAME);
169