1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Turris Mox Moxtet GPIO expander |
4 | * |
5 | * Copyright (C) 2018 Marek BehĂșn <kabel@kernel.org> |
6 | */ |
7 | |
8 | #include <linux/bitops.h> |
9 | #include <linux/gpio/driver.h> |
10 | #include <linux/moxtet.h> |
11 | #include <linux/module.h> |
12 | |
13 | #define MOXTET_GPIO_NGPIOS 12 |
14 | #define MOXTET_GPIO_INPUTS 4 |
15 | |
16 | struct moxtet_gpio_desc { |
17 | u16 in_mask; |
18 | u16 out_mask; |
19 | }; |
20 | |
21 | static const struct moxtet_gpio_desc descs[] = { |
22 | [TURRIS_MOX_MODULE_SFP] = { |
23 | .in_mask = GENMASK(2, 0), |
24 | .out_mask = GENMASK(5, 4), |
25 | }, |
26 | }; |
27 | |
28 | struct moxtet_gpio_chip { |
29 | struct device *dev; |
30 | struct gpio_chip gpio_chip; |
31 | const struct moxtet_gpio_desc *desc; |
32 | }; |
33 | |
34 | static int moxtet_gpio_get_value(struct gpio_chip *gc, unsigned int offset) |
35 | { |
36 | struct moxtet_gpio_chip *chip = gpiochip_get_data(gc); |
37 | int ret; |
38 | |
39 | if (chip->desc->in_mask & BIT(offset)) { |
40 | ret = moxtet_device_read(dev: chip->dev); |
41 | } else if (chip->desc->out_mask & BIT(offset)) { |
42 | ret = moxtet_device_written(dev: chip->dev); |
43 | if (ret >= 0) |
44 | ret <<= MOXTET_GPIO_INPUTS; |
45 | } else { |
46 | return -EINVAL; |
47 | } |
48 | |
49 | if (ret < 0) |
50 | return ret; |
51 | |
52 | return !!(ret & BIT(offset)); |
53 | } |
54 | |
55 | static void moxtet_gpio_set_value(struct gpio_chip *gc, unsigned int offset, |
56 | int val) |
57 | { |
58 | struct moxtet_gpio_chip *chip = gpiochip_get_data(gc); |
59 | int state; |
60 | |
61 | state = moxtet_device_written(dev: chip->dev); |
62 | if (state < 0) |
63 | return; |
64 | |
65 | offset -= MOXTET_GPIO_INPUTS; |
66 | |
67 | if (val) |
68 | state |= BIT(offset); |
69 | else |
70 | state &= ~BIT(offset); |
71 | |
72 | moxtet_device_write(dev: chip->dev, val: state); |
73 | } |
74 | |
75 | static int moxtet_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) |
76 | { |
77 | struct moxtet_gpio_chip *chip = gpiochip_get_data(gc); |
78 | |
79 | /* All lines are hard wired to be either input or output, not both. */ |
80 | if (chip->desc->in_mask & BIT(offset)) |
81 | return GPIO_LINE_DIRECTION_IN; |
82 | else if (chip->desc->out_mask & BIT(offset)) |
83 | return GPIO_LINE_DIRECTION_OUT; |
84 | else |
85 | return -EINVAL; |
86 | } |
87 | |
88 | static int moxtet_gpio_direction_input(struct gpio_chip *gc, |
89 | unsigned int offset) |
90 | { |
91 | struct moxtet_gpio_chip *chip = gpiochip_get_data(gc); |
92 | |
93 | if (chip->desc->in_mask & BIT(offset)) |
94 | return 0; |
95 | else if (chip->desc->out_mask & BIT(offset)) |
96 | return -ENOTSUPP; |
97 | else |
98 | return -EINVAL; |
99 | } |
100 | |
101 | static int moxtet_gpio_direction_output(struct gpio_chip *gc, |
102 | unsigned int offset, int val) |
103 | { |
104 | struct moxtet_gpio_chip *chip = gpiochip_get_data(gc); |
105 | |
106 | if (chip->desc->out_mask & BIT(offset)) |
107 | moxtet_gpio_set_value(gc, offset, val); |
108 | else if (chip->desc->in_mask & BIT(offset)) |
109 | return -ENOTSUPP; |
110 | else |
111 | return -EINVAL; |
112 | |
113 | return 0; |
114 | } |
115 | |
116 | static int moxtet_gpio_probe(struct device *dev) |
117 | { |
118 | struct moxtet_gpio_chip *chip; |
119 | struct device_node *nc = dev->of_node; |
120 | int id; |
121 | |
122 | id = to_moxtet_device(dev)->id; |
123 | |
124 | if (id >= ARRAY_SIZE(descs)) { |
125 | dev_err(dev, "%pOF Moxtet device id 0x%x is not supported by gpio-moxtet driver\n" , |
126 | nc, id); |
127 | return -ENOTSUPP; |
128 | } |
129 | |
130 | chip = devm_kzalloc(dev, size: sizeof(*chip), GFP_KERNEL); |
131 | if (!chip) |
132 | return -ENOMEM; |
133 | |
134 | chip->dev = dev; |
135 | chip->gpio_chip.parent = dev; |
136 | chip->desc = &descs[id]; |
137 | |
138 | dev_set_drvdata(dev, data: chip); |
139 | |
140 | chip->gpio_chip.label = dev_name(dev); |
141 | chip->gpio_chip.get_direction = moxtet_gpio_get_direction; |
142 | chip->gpio_chip.direction_input = moxtet_gpio_direction_input; |
143 | chip->gpio_chip.direction_output = moxtet_gpio_direction_output; |
144 | chip->gpio_chip.get = moxtet_gpio_get_value; |
145 | chip->gpio_chip.set = moxtet_gpio_set_value; |
146 | chip->gpio_chip.base = -1; |
147 | |
148 | chip->gpio_chip.ngpio = MOXTET_GPIO_NGPIOS; |
149 | |
150 | chip->gpio_chip.can_sleep = true; |
151 | chip->gpio_chip.owner = THIS_MODULE; |
152 | |
153 | return devm_gpiochip_add_data(dev, &chip->gpio_chip, chip); |
154 | } |
155 | |
156 | static const struct of_device_id moxtet_gpio_dt_ids[] = { |
157 | { .compatible = "cznic,moxtet-gpio" , }, |
158 | {}, |
159 | }; |
160 | MODULE_DEVICE_TABLE(of, moxtet_gpio_dt_ids); |
161 | |
162 | static const enum turris_mox_module_id moxtet_gpio_module_table[] = { |
163 | TURRIS_MOX_MODULE_SFP, |
164 | 0, |
165 | }; |
166 | |
167 | static struct moxtet_driver moxtet_gpio_driver = { |
168 | .driver = { |
169 | .name = "moxtet-gpio" , |
170 | .of_match_table = moxtet_gpio_dt_ids, |
171 | .probe = moxtet_gpio_probe, |
172 | }, |
173 | .id_table = moxtet_gpio_module_table, |
174 | }; |
175 | module_moxtet_driver(moxtet_gpio_driver); |
176 | |
177 | MODULE_AUTHOR("Marek Behun <kabel@kernel.org>" ); |
178 | MODULE_DESCRIPTION("Turris Mox Moxtet GPIO expander" ); |
179 | MODULE_LICENSE("GPL v2" ); |
180 | |