1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Copyright (c) 2011, NVIDIA Corporation.
4 */
5
6#include <linux/init.h>
7#include <linux/kernel.h>
8#include <linux/module.h>
9#include <linux/mod_devicetable.h>
10#include <linux/rfkill.h>
11#include <linux/platform_device.h>
12#include <linux/clk.h>
13#include <linux/slab.h>
14#include <linux/acpi.h>
15#include <linux/gpio/consumer.h>
16
17struct rfkill_gpio_data {
18	const char		*name;
19	enum rfkill_type	type;
20	struct gpio_desc	*reset_gpio;
21	struct gpio_desc	*shutdown_gpio;
22
23	struct rfkill		*rfkill_dev;
24	struct clk		*clk;
25
26	bool			clk_enabled;
27};
28
29static int rfkill_gpio_set_power(void *data, bool blocked)
30{
31	struct rfkill_gpio_data *rfkill = data;
32
33	if (!blocked && !IS_ERR(rfkill->clk) && !rfkill->clk_enabled)
34		clk_enable(rfkill->clk);
35
36	gpiod_set_value_cansleep(rfkill->shutdown_gpio, !blocked);
37	gpiod_set_value_cansleep(rfkill->reset_gpio, !blocked);
38
39	if (blocked && !IS_ERR(rfkill->clk) && rfkill->clk_enabled)
40		clk_disable(rfkill->clk);
41
42	rfkill->clk_enabled = !blocked;
43
44	return 0;
45}
46
47static const struct rfkill_ops rfkill_gpio_ops = {
48	.set_block = rfkill_gpio_set_power,
49};
50
51static const struct acpi_gpio_params reset_gpios = { 0, 0, false };
52static const struct acpi_gpio_params shutdown_gpios = { 1, 0, false };
53
54static const struct acpi_gpio_mapping acpi_rfkill_default_gpios[] = {
55	{ "reset-gpios", &reset_gpios, 1 },
56	{ "shutdown-gpios", &shutdown_gpios, 1 },
57	{ },
58};
59
60static int rfkill_gpio_acpi_probe(struct device *dev,
61				  struct rfkill_gpio_data *rfkill)
62{
63	const struct acpi_device_id *id;
64
65	id = acpi_match_device(dev->driver->acpi_match_table, dev);
66	if (!id)
67		return -ENODEV;
68
69	rfkill->type = (unsigned)id->driver_data;
70
71	return devm_acpi_dev_add_driver_gpios(dev, acpi_rfkill_default_gpios);
72}
73
74static int rfkill_gpio_probe(struct platform_device *pdev)
75{
76	struct rfkill_gpio_data *rfkill;
77	struct gpio_desc *gpio;
78	const char *type_name;
79	int ret;
80
81	rfkill = devm_kzalloc(&pdev->dev, sizeof(*rfkill), GFP_KERNEL);
82	if (!rfkill)
83		return -ENOMEM;
84
85	device_property_read_string(&pdev->dev, "name", &rfkill->name);
86	device_property_read_string(&pdev->dev, "type", &type_name);
87
88	if (!rfkill->name)
89		rfkill->name = dev_name(&pdev->dev);
90
91	rfkill->type = rfkill_find_type(type_name);
92
93	if (ACPI_HANDLE(&pdev->dev)) {
94		ret = rfkill_gpio_acpi_probe(&pdev->dev, rfkill);
95		if (ret)
96			return ret;
97	}
98
99	rfkill->clk = devm_clk_get(&pdev->dev, NULL);
100
101	gpio = devm_gpiod_get_optional(&pdev->dev, "reset", GPIOD_ASIS);
102	if (IS_ERR(gpio))
103		return PTR_ERR(gpio);
104
105	rfkill->reset_gpio = gpio;
106
107	gpio = devm_gpiod_get_optional(&pdev->dev, "shutdown", GPIOD_ASIS);
108	if (IS_ERR(gpio))
109		return PTR_ERR(gpio);
110
111	rfkill->shutdown_gpio = gpio;
112
113	/* Make sure at-least one GPIO is defined for this instance */
114	if (!rfkill->reset_gpio && !rfkill->shutdown_gpio) {
115		dev_err(&pdev->dev, "invalid platform data\n");
116		return -EINVAL;
117	}
118
119	ret = gpiod_direction_output(rfkill->reset_gpio, true);
120	if (ret)
121		return ret;
122
123	ret = gpiod_direction_output(rfkill->shutdown_gpio, true);
124	if (ret)
125		return ret;
126
127	rfkill->rfkill_dev = rfkill_alloc(rfkill->name, &pdev->dev,
128					  rfkill->type, &rfkill_gpio_ops,
129					  rfkill);
130	if (!rfkill->rfkill_dev)
131		return -ENOMEM;
132
133	ret = rfkill_register(rfkill->rfkill_dev);
134	if (ret < 0)
135		goto err_destroy;
136
137	platform_set_drvdata(pdev, rfkill);
138
139	dev_info(&pdev->dev, "%s device registered.\n", rfkill->name);
140
141	return 0;
142
143err_destroy:
144	rfkill_destroy(rfkill->rfkill_dev);
145
146	return ret;
147}
148
149static int rfkill_gpio_remove(struct platform_device *pdev)
150{
151	struct rfkill_gpio_data *rfkill = platform_get_drvdata(pdev);
152
153	rfkill_unregister(rfkill->rfkill_dev);
154	rfkill_destroy(rfkill->rfkill_dev);
155
156	return 0;
157}
158
159#ifdef CONFIG_ACPI
160static const struct acpi_device_id rfkill_acpi_match[] = {
161	{ "BCM4752", RFKILL_TYPE_GPS },
162	{ "LNV4752", RFKILL_TYPE_GPS },
163	{ },
164};
165MODULE_DEVICE_TABLE(acpi, rfkill_acpi_match);
166#endif
167
168static struct platform_driver rfkill_gpio_driver = {
169	.probe = rfkill_gpio_probe,
170	.remove = rfkill_gpio_remove,
171	.driver = {
172		.name = "rfkill_gpio",
173		.acpi_match_table = ACPI_PTR(rfkill_acpi_match),
174	},
175};
176
177module_platform_driver(rfkill_gpio_driver);
178
179MODULE_DESCRIPTION("gpio rfkill");
180MODULE_AUTHOR("NVIDIA");
181MODULE_LICENSE("GPL");
182