diff options
Diffstat (limited to 'drivers/spi/spi-uniphier.c')
-rw-r--r-- | drivers/spi/spi-uniphier.c | 525 |
1 files changed, 525 insertions, 0 deletions
diff --git a/drivers/spi/spi-uniphier.c b/drivers/spi/spi-uniphier.c new file mode 100644 index 000000000000..089985465890 --- /dev/null +++ b/drivers/spi/spi-uniphier.c | |||
@@ -0,0 +1,525 @@ | |||
1 | // SPDX-License-Identifier: GPL-2.0 | ||
2 | // spi-uniphier.c - Socionext UniPhier SPI controller driver | ||
3 | // Copyright 2012 Panasonic Corporation | ||
4 | // Copyright 2016-2018 Socionext Inc. | ||
5 | |||
6 | #include <linux/kernel.h> | ||
7 | #include <linux/bitfield.h> | ||
8 | #include <linux/bitops.h> | ||
9 | #include <linux/clk.h> | ||
10 | #include <linux/interrupt.h> | ||
11 | #include <linux/io.h> | ||
12 | #include <linux/module.h> | ||
13 | #include <linux/of.h> | ||
14 | #include <linux/of_platform.h> | ||
15 | #include <linux/platform_device.h> | ||
16 | #include <linux/spi/spi.h> | ||
17 | |||
18 | #include <asm/unaligned.h> | ||
19 | |||
20 | #define SSI_TIMEOUT_MS 2000 | ||
21 | #define SSI_MAX_CLK_DIVIDER 254 | ||
22 | #define SSI_MIN_CLK_DIVIDER 4 | ||
23 | |||
24 | struct uniphier_spi_priv { | ||
25 | void __iomem *base; | ||
26 | struct clk *clk; | ||
27 | struct spi_master *master; | ||
28 | struct completion xfer_done; | ||
29 | |||
30 | int error; | ||
31 | unsigned int tx_bytes; | ||
32 | unsigned int rx_bytes; | ||
33 | const u8 *tx_buf; | ||
34 | u8 *rx_buf; | ||
35 | |||
36 | bool is_save_param; | ||
37 | u8 bits_per_word; | ||
38 | u16 mode; | ||
39 | u32 speed_hz; | ||
40 | }; | ||
41 | |||
42 | #define SSI_CTL 0x00 | ||
43 | #define SSI_CTL_EN BIT(0) | ||
44 | |||
45 | #define SSI_CKS 0x04 | ||
46 | #define SSI_CKS_CKRAT_MASK GENMASK(7, 0) | ||
47 | #define SSI_CKS_CKPHS BIT(14) | ||
48 | #define SSI_CKS_CKINIT BIT(13) | ||
49 | #define SSI_CKS_CKDLY BIT(12) | ||
50 | |||
51 | #define SSI_TXWDS 0x08 | ||
52 | #define SSI_TXWDS_WDLEN_MASK GENMASK(13, 8) | ||
53 | #define SSI_TXWDS_TDTF_MASK GENMASK(7, 6) | ||
54 | #define SSI_TXWDS_DTLEN_MASK GENMASK(5, 0) | ||
55 | |||
56 | #define SSI_RXWDS 0x0c | ||
57 | #define SSI_RXWDS_DTLEN_MASK GENMASK(5, 0) | ||
58 | |||
59 | #define SSI_FPS 0x10 | ||
60 | #define SSI_FPS_FSPOL BIT(15) | ||
61 | #define SSI_FPS_FSTRT BIT(14) | ||
62 | |||
63 | #define SSI_SR 0x14 | ||
64 | #define SSI_SR_RNE BIT(0) | ||
65 | |||
66 | #define SSI_IE 0x18 | ||
67 | #define SSI_IE_RCIE BIT(3) | ||
68 | #define SSI_IE_RORIE BIT(0) | ||
69 | |||
70 | #define SSI_IS 0x1c | ||
71 | #define SSI_IS_RXRS BIT(9) | ||
72 | #define SSI_IS_RCID BIT(3) | ||
73 | #define SSI_IS_RORID BIT(0) | ||
74 | |||
75 | #define SSI_IC 0x1c | ||
76 | #define SSI_IC_TCIC BIT(4) | ||
77 | #define SSI_IC_RCIC BIT(3) | ||
78 | #define SSI_IC_RORIC BIT(0) | ||
79 | |||
80 | #define SSI_FC 0x20 | ||
81 | #define SSI_FC_TXFFL BIT(12) | ||
82 | #define SSI_FC_TXFTH_MASK GENMASK(11, 8) | ||
83 | #define SSI_FC_RXFFL BIT(4) | ||
84 | #define SSI_FC_RXFTH_MASK GENMASK(3, 0) | ||
85 | |||
86 | #define SSI_TXDR 0x24 | ||
87 | #define SSI_RXDR 0x24 | ||
88 | |||
89 | #define SSI_FIFO_DEPTH 8U | ||
90 | |||
91 | static inline unsigned int bytes_per_word(unsigned int bits) | ||
92 | { | ||
93 | return bits <= 8 ? 1 : (bits <= 16 ? 2 : 4); | ||
94 | } | ||
95 | |||
96 | static inline void uniphier_spi_irq_enable(struct spi_device *spi, u32 mask) | ||
97 | { | ||
98 | struct uniphier_spi_priv *priv = spi_master_get_devdata(spi->master); | ||
99 | u32 val; | ||
100 | |||
101 | val = readl(priv->base + SSI_IE); | ||
102 | val |= mask; | ||
103 | writel(val, priv->base + SSI_IE); | ||
104 | } | ||
105 | |||
106 | static inline void uniphier_spi_irq_disable(struct spi_device *spi, u32 mask) | ||
107 | { | ||
108 | struct uniphier_spi_priv *priv = spi_master_get_devdata(spi->master); | ||
109 | u32 val; | ||
110 | |||
111 | val = readl(priv->base + SSI_IE); | ||
112 | val &= ~mask; | ||
113 | writel(val, priv->base + SSI_IE); | ||
114 | } | ||
115 | |||
116 | static void uniphier_spi_set_mode(struct spi_device *spi) | ||
117 | { | ||
118 | struct uniphier_spi_priv *priv = spi_master_get_devdata(spi->master); | ||
119 | u32 val1, val2; | ||
120 | |||
121 | /* | ||
122 | * clock setting | ||
123 | * CKPHS capture timing. 0:rising edge, 1:falling edge | ||
124 | * CKINIT clock initial level. 0:low, 1:high | ||
125 | * CKDLY clock delay. 0:no delay, 1:delay depending on FSTRT | ||
126 | * (FSTRT=0: 1 clock, FSTRT=1: 0.5 clock) | ||
127 | * | ||
128 | * frame setting | ||
129 | * FSPOL frame signal porarity. 0: low, 1: high | ||
130 | * FSTRT start frame timing | ||
131 | * 0: rising edge of clock, 1: falling edge of clock | ||
132 | */ | ||
133 | switch (spi->mode & (SPI_CPOL | SPI_CPHA)) { | ||
134 | case SPI_MODE_0: | ||
135 | /* CKPHS=1, CKINIT=0, CKDLY=1, FSTRT=0 */ | ||
136 | val1 = SSI_CKS_CKPHS | SSI_CKS_CKDLY; | ||
137 | val2 = 0; | ||
138 | break; | ||
139 | case SPI_MODE_1: | ||
140 | /* CKPHS=0, CKINIT=0, CKDLY=0, FSTRT=1 */ | ||
141 | val1 = 0; | ||
142 | val2 = SSI_FPS_FSTRT; | ||
143 | break; | ||
144 | case SPI_MODE_2: | ||
145 | /* CKPHS=0, CKINIT=1, CKDLY=1, FSTRT=1 */ | ||
146 | val1 = SSI_CKS_CKINIT | SSI_CKS_CKDLY; | ||
147 | val2 = SSI_FPS_FSTRT; | ||
148 | break; | ||
149 | case SPI_MODE_3: | ||
150 | /* CKPHS=1, CKINIT=1, CKDLY=0, FSTRT=0 */ | ||
151 | val1 = SSI_CKS_CKPHS | SSI_CKS_CKINIT; | ||
152 | val2 = 0; | ||
153 | break; | ||
154 | } | ||
155 | |||
156 | if (!(spi->mode & SPI_CS_HIGH)) | ||
157 | val2 |= SSI_FPS_FSPOL; | ||
158 | |||
159 | writel(val1, priv->base + SSI_CKS); | ||
160 | writel(val2, priv->base + SSI_FPS); | ||
161 | |||
162 | val1 = 0; | ||
163 | if (spi->mode & SPI_LSB_FIRST) | ||
164 | val1 |= FIELD_PREP(SSI_TXWDS_TDTF_MASK, 1); | ||
165 | writel(val1, priv->base + SSI_TXWDS); | ||
166 | writel(val1, priv->base + SSI_RXWDS); | ||
167 | } | ||
168 | |||
169 | static void uniphier_spi_set_transfer_size(struct spi_device *spi, int size) | ||
170 | { | ||
171 | struct uniphier_spi_priv *priv = spi_master_get_devdata(spi->master); | ||
172 | u32 val; | ||
173 | |||
174 | val = readl(priv->base + SSI_TXWDS); | ||
175 | val &= ~(SSI_TXWDS_WDLEN_MASK | SSI_TXWDS_DTLEN_MASK); | ||
176 | val |= FIELD_PREP(SSI_TXWDS_WDLEN_MASK, size); | ||
177 | val |= FIELD_PREP(SSI_TXWDS_DTLEN_MASK, size); | ||
178 | writel(val, priv->base + SSI_TXWDS); | ||
179 | |||
180 | val = readl(priv->base + SSI_RXWDS); | ||
181 | val &= ~SSI_RXWDS_DTLEN_MASK; | ||
182 | val |= FIELD_PREP(SSI_RXWDS_DTLEN_MASK, size); | ||
183 | writel(val, priv->base + SSI_RXWDS); | ||
184 | } | ||
185 | |||
186 | static void uniphier_spi_set_baudrate(struct spi_device *spi, | ||
187 | unsigned int speed) | ||
188 | { | ||
189 | struct uniphier_spi_priv *priv = spi_master_get_devdata(spi->master); | ||
190 | u32 val, ckdiv; | ||
191 | |||
192 | /* | ||
193 | * the supported rates are even numbers from 4 to 254. (4,6,8...254) | ||
194 | * round up as we look for equal or less speed | ||
195 | */ | ||
196 | ckdiv = DIV_ROUND_UP(clk_get_rate(priv->clk), speed); | ||
197 | ckdiv = round_up(ckdiv, 2); | ||
198 | |||
199 | val = readl(priv->base + SSI_CKS); | ||
200 | val &= ~SSI_CKS_CKRAT_MASK; | ||
201 | val |= ckdiv & SSI_CKS_CKRAT_MASK; | ||
202 | writel(val, priv->base + SSI_CKS); | ||
203 | } | ||
204 | |||
205 | static void uniphier_spi_setup_transfer(struct spi_device *spi, | ||
206 | struct spi_transfer *t) | ||
207 | { | ||
208 | struct uniphier_spi_priv *priv = spi_master_get_devdata(spi->master); | ||
209 | u32 val; | ||
210 | |||
211 | priv->error = 0; | ||
212 | priv->tx_buf = t->tx_buf; | ||
213 | priv->rx_buf = t->rx_buf; | ||
214 | priv->tx_bytes = priv->rx_bytes = t->len; | ||
215 | |||
216 | if (!priv->is_save_param || priv->mode != spi->mode) { | ||
217 | uniphier_spi_set_mode(spi); | ||
218 | priv->mode = spi->mode; | ||
219 | } | ||
220 | |||
221 | if (!priv->is_save_param || priv->bits_per_word != t->bits_per_word) { | ||
222 | uniphier_spi_set_transfer_size(spi, t->bits_per_word); | ||
223 | priv->bits_per_word = t->bits_per_word; | ||
224 | } | ||
225 | |||
226 | if (!priv->is_save_param || priv->speed_hz != t->speed_hz) { | ||
227 | uniphier_spi_set_baudrate(spi, t->speed_hz); | ||
228 | priv->speed_hz = t->speed_hz; | ||
229 | } | ||
230 | |||
231 | if (!priv->is_save_param) | ||
232 | priv->is_save_param = true; | ||
233 | |||
234 | /* reset FIFOs */ | ||
235 | val = SSI_FC_TXFFL | SSI_FC_RXFFL; | ||
236 | writel(val, priv->base + SSI_FC); | ||
237 | } | ||
238 | |||
239 | static void uniphier_spi_send(struct uniphier_spi_priv *priv) | ||
240 | { | ||
241 | int wsize; | ||
242 | u32 val = 0; | ||
243 | |||
244 | wsize = min(bytes_per_word(priv->bits_per_word), priv->tx_bytes); | ||
245 | priv->tx_bytes -= wsize; | ||
246 | |||
247 | if (priv->tx_buf) { | ||
248 | switch (wsize) { | ||
249 | case 1: | ||
250 | val = *priv->tx_buf; | ||
251 | break; | ||
252 | case 2: | ||
253 | val = get_unaligned_le16(priv->tx_buf); | ||
254 | break; | ||
255 | case 4: | ||
256 | val = get_unaligned_le32(priv->tx_buf); | ||
257 | break; | ||
258 | } | ||
259 | |||
260 | priv->tx_buf += wsize; | ||
261 | } | ||
262 | |||
263 | writel(val, priv->base + SSI_TXDR); | ||
264 | } | ||
265 | |||
266 | static void uniphier_spi_recv(struct uniphier_spi_priv *priv) | ||
267 | { | ||
268 | int rsize; | ||
269 | u32 val; | ||
270 | |||
271 | rsize = min(bytes_per_word(priv->bits_per_word), priv->rx_bytes); | ||
272 | priv->rx_bytes -= rsize; | ||
273 | |||
274 | val = readl(priv->base + SSI_RXDR); | ||
275 | |||
276 | if (priv->rx_buf) { | ||
277 | switch (rsize) { | ||
278 | case 1: | ||
279 | *priv->rx_buf = val; | ||
280 | break; | ||
281 | case 2: | ||
282 | put_unaligned_le16(val, priv->rx_buf); | ||
283 | break; | ||
284 | case 4: | ||
285 | put_unaligned_le32(val, priv->rx_buf); | ||
286 | break; | ||
287 | } | ||
288 | |||
289 | priv->rx_buf += rsize; | ||
290 | } | ||
291 | } | ||
292 | |||
293 | static void uniphier_spi_fill_tx_fifo(struct uniphier_spi_priv *priv) | ||
294 | { | ||
295 | unsigned int tx_count; | ||
296 | u32 val; | ||
297 | |||
298 | tx_count = DIV_ROUND_UP(priv->tx_bytes, | ||
299 | bytes_per_word(priv->bits_per_word)); | ||
300 | tx_count = min(tx_count, SSI_FIFO_DEPTH); | ||
301 | |||
302 | /* set fifo threshold */ | ||
303 | val = readl(priv->base + SSI_FC); | ||
304 | val &= ~(SSI_FC_TXFTH_MASK | SSI_FC_RXFTH_MASK); | ||
305 | val |= FIELD_PREP(SSI_FC_TXFTH_MASK, tx_count); | ||
306 | val |= FIELD_PREP(SSI_FC_RXFTH_MASK, tx_count); | ||
307 | writel(val, priv->base + SSI_FC); | ||
308 | |||
309 | while (tx_count--) | ||
310 | uniphier_spi_send(priv); | ||
311 | } | ||
312 | |||
313 | static void uniphier_spi_set_cs(struct spi_device *spi, bool enable) | ||
314 | { | ||
315 | struct uniphier_spi_priv *priv = spi_master_get_devdata(spi->master); | ||
316 | u32 val; | ||
317 | |||
318 | val = readl(priv->base + SSI_FPS); | ||
319 | |||
320 | if (enable) | ||
321 | val |= SSI_FPS_FSPOL; | ||
322 | else | ||
323 | val &= ~SSI_FPS_FSPOL; | ||
324 | |||
325 | writel(val, priv->base + SSI_FPS); | ||
326 | } | ||
327 | |||
328 | static int uniphier_spi_transfer_one(struct spi_master *master, | ||
329 | struct spi_device *spi, | ||
330 | struct spi_transfer *t) | ||
331 | { | ||
332 | struct uniphier_spi_priv *priv = spi_master_get_devdata(master); | ||
333 | int status; | ||
334 | |||
335 | uniphier_spi_setup_transfer(spi, t); | ||
336 | |||
337 | reinit_completion(&priv->xfer_done); | ||
338 | |||
339 | uniphier_spi_fill_tx_fifo(priv); | ||
340 | |||
341 | uniphier_spi_irq_enable(spi, SSI_IE_RCIE | SSI_IE_RORIE); | ||
342 | |||
343 | status = wait_for_completion_timeout(&priv->xfer_done, | ||
344 | msecs_to_jiffies(SSI_TIMEOUT_MS)); | ||
345 | |||
346 | uniphier_spi_irq_disable(spi, SSI_IE_RCIE | SSI_IE_RORIE); | ||
347 | |||
348 | if (status < 0) | ||
349 | return status; | ||
350 | |||
351 | return priv->error; | ||
352 | } | ||
353 | |||
354 | static int uniphier_spi_prepare_transfer_hardware(struct spi_master *master) | ||
355 | { | ||
356 | struct uniphier_spi_priv *priv = spi_master_get_devdata(master); | ||
357 | |||
358 | writel(SSI_CTL_EN, priv->base + SSI_CTL); | ||
359 | |||
360 | return 0; | ||
361 | } | ||
362 | |||
363 | static int uniphier_spi_unprepare_transfer_hardware(struct spi_master *master) | ||
364 | { | ||
365 | struct uniphier_spi_priv *priv = spi_master_get_devdata(master); | ||
366 | |||
367 | writel(0, priv->base + SSI_CTL); | ||
368 | |||
369 | return 0; | ||
370 | } | ||
371 | |||
372 | static irqreturn_t uniphier_spi_handler(int irq, void *dev_id) | ||
373 | { | ||
374 | struct uniphier_spi_priv *priv = dev_id; | ||
375 | u32 val, stat; | ||
376 | |||
377 | stat = readl(priv->base + SSI_IS); | ||
378 | val = SSI_IC_TCIC | SSI_IC_RCIC | SSI_IC_RORIC; | ||
379 | writel(val, priv->base + SSI_IC); | ||
380 | |||
381 | /* rx fifo overrun */ | ||
382 | if (stat & SSI_IS_RORID) { | ||
383 | priv->error = -EIO; | ||
384 | goto done; | ||
385 | } | ||
386 | |||
387 | /* rx complete */ | ||
388 | if ((stat & SSI_IS_RCID) && (stat & SSI_IS_RXRS)) { | ||
389 | while ((readl(priv->base + SSI_SR) & SSI_SR_RNE) && | ||
390 | (priv->rx_bytes - priv->tx_bytes) > 0) | ||
391 | uniphier_spi_recv(priv); | ||
392 | |||
393 | if ((readl(priv->base + SSI_SR) & SSI_SR_RNE) || | ||
394 | (priv->rx_bytes != priv->tx_bytes)) { | ||
395 | priv->error = -EIO; | ||
396 | goto done; | ||
397 | } else if (priv->rx_bytes == 0) | ||
398 | goto done; | ||
399 | |||
400 | /* next tx transfer */ | ||
401 | uniphier_spi_fill_tx_fifo(priv); | ||
402 | |||
403 | return IRQ_HANDLED; | ||
404 | } | ||
405 | |||
406 | return IRQ_NONE; | ||
407 | |||
408 | done: | ||
409 | complete(&priv->xfer_done); | ||
410 | return IRQ_HANDLED; | ||
411 | } | ||
412 | |||
413 | static int uniphier_spi_probe(struct platform_device *pdev) | ||
414 | { | ||
415 | struct uniphier_spi_priv *priv; | ||
416 | struct spi_master *master; | ||
417 | struct resource *res; | ||
418 | unsigned long clk_rate; | ||
419 | int irq; | ||
420 | int ret; | ||
421 | |||
422 | master = spi_alloc_master(&pdev->dev, sizeof(*priv)); | ||
423 | if (!master) | ||
424 | return -ENOMEM; | ||
425 | |||
426 | platform_set_drvdata(pdev, master); | ||
427 | |||
428 | priv = spi_master_get_devdata(master); | ||
429 | priv->master = master; | ||
430 | priv->is_save_param = false; | ||
431 | |||
432 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
433 | priv->base = devm_ioremap_resource(&pdev->dev, res); | ||
434 | if (IS_ERR(priv->base)) { | ||
435 | ret = PTR_ERR(priv->base); | ||
436 | goto out_master_put; | ||
437 | } | ||
438 | |||
439 | priv->clk = devm_clk_get(&pdev->dev, NULL); | ||
440 | if (IS_ERR(priv->clk)) { | ||
441 | dev_err(&pdev->dev, "failed to get clock\n"); | ||
442 | ret = PTR_ERR(priv->clk); | ||
443 | goto out_master_put; | ||
444 | } | ||
445 | |||
446 | ret = clk_prepare_enable(priv->clk); | ||
447 | if (ret) | ||
448 | goto out_master_put; | ||
449 | |||
450 | irq = platform_get_irq(pdev, 0); | ||
451 | if (irq < 0) { | ||
452 | dev_err(&pdev->dev, "failed to get IRQ\n"); | ||
453 | ret = irq; | ||
454 | goto out_disable_clk; | ||
455 | } | ||
456 | |||
457 | ret = devm_request_irq(&pdev->dev, irq, uniphier_spi_handler, | ||
458 | 0, "uniphier-spi", priv); | ||
459 | if (ret) { | ||
460 | dev_err(&pdev->dev, "failed to request IRQ\n"); | ||
461 | goto out_disable_clk; | ||
462 | } | ||
463 | |||
464 | init_completion(&priv->xfer_done); | ||
465 | |||
466 | clk_rate = clk_get_rate(priv->clk); | ||
467 | |||
468 | master->max_speed_hz = DIV_ROUND_UP(clk_rate, SSI_MIN_CLK_DIVIDER); | ||
469 | master->min_speed_hz = DIV_ROUND_UP(clk_rate, SSI_MAX_CLK_DIVIDER); | ||
470 | master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_LSB_FIRST; | ||
471 | master->dev.of_node = pdev->dev.of_node; | ||
472 | master->bus_num = pdev->id; | ||
473 | master->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 32); | ||
474 | |||
475 | master->set_cs = uniphier_spi_set_cs; | ||
476 | master->transfer_one = uniphier_spi_transfer_one; | ||
477 | master->prepare_transfer_hardware | ||
478 | = uniphier_spi_prepare_transfer_hardware; | ||
479 | master->unprepare_transfer_hardware | ||
480 | = uniphier_spi_unprepare_transfer_hardware; | ||
481 | master->num_chipselect = 1; | ||
482 | |||
483 | ret = devm_spi_register_master(&pdev->dev, master); | ||
484 | if (ret) | ||
485 | goto out_disable_clk; | ||
486 | |||
487 | return 0; | ||
488 | |||
489 | out_disable_clk: | ||
490 | clk_disable_unprepare(priv->clk); | ||
491 | |||
492 | out_master_put: | ||
493 | spi_master_put(master); | ||
494 | return ret; | ||
495 | } | ||
496 | |||
497 | static int uniphier_spi_remove(struct platform_device *pdev) | ||
498 | { | ||
499 | struct uniphier_spi_priv *priv = platform_get_drvdata(pdev); | ||
500 | |||
501 | clk_disable_unprepare(priv->clk); | ||
502 | |||
503 | return 0; | ||
504 | } | ||
505 | |||
506 | static const struct of_device_id uniphier_spi_match[] = { | ||
507 | { .compatible = "socionext,uniphier-scssi" }, | ||
508 | { /* sentinel */ } | ||
509 | }; | ||
510 | MODULE_DEVICE_TABLE(of, uniphier_spi_match); | ||
511 | |||
512 | static struct platform_driver uniphier_spi_driver = { | ||
513 | .probe = uniphier_spi_probe, | ||
514 | .remove = uniphier_spi_remove, | ||
515 | .driver = { | ||
516 | .name = "uniphier-spi", | ||
517 | .of_match_table = uniphier_spi_match, | ||
518 | }, | ||
519 | }; | ||
520 | module_platform_driver(uniphier_spi_driver); | ||
521 | |||
522 | MODULE_AUTHOR("Kunihiko Hayashi <hayashi.kunihiko@socionext.com>"); | ||
523 | MODULE_AUTHOR("Keiji Hayashibara <hayashibara.keiji@socionext.com>"); | ||
524 | MODULE_DESCRIPTION("Socionext UniPhier SPI controller driver"); | ||
525 | MODULE_LICENSE("GPL v2"); | ||