]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * CLPS711X SPI bus driver | |
3 | * | |
4 | * Copyright (C) 2012-2016 Alexander Shiyan <shc_work@mail.ru> | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation; either version 2 of the License, or | |
9 | * (at your option) any later version. | |
10 | */ | |
11 | ||
12 | #include <linux/io.h> | |
13 | #include <linux/clk.h> | |
14 | #include <linux/gpio/consumer.h> | |
15 | #include <linux/module.h> | |
16 | #include <linux/interrupt.h> | |
17 | #include <linux/platform_device.h> | |
18 | #include <linux/regmap.h> | |
19 | #include <linux/mfd/syscon.h> | |
20 | #include <linux/mfd/syscon/clps711x.h> | |
21 | #include <linux/spi/spi.h> | |
22 | ||
23 | #define DRIVER_NAME "clps711x-spi" | |
24 | ||
25 | #define SYNCIO_FRMLEN(x) ((x) << 8) | |
26 | #define SYNCIO_TXFRMEN (1 << 14) | |
27 | ||
28 | struct spi_clps711x_data { | |
29 | void __iomem *syncio; | |
30 | struct regmap *syscon; | |
31 | struct clk *spi_clk; | |
32 | ||
33 | u8 *tx_buf; | |
34 | u8 *rx_buf; | |
35 | unsigned int bpw; | |
36 | int len; | |
37 | }; | |
38 | ||
39 | static int spi_clps711x_prepare_message(struct spi_master *master, | |
40 | struct spi_message *msg) | |
41 | { | |
42 | struct spi_clps711x_data *hw = spi_master_get_devdata(master); | |
43 | struct spi_device *spi = msg->spi; | |
44 | ||
45 | /* Setup mode for transfer */ | |
46 | return regmap_update_bits(hw->syscon, SYSCON_OFFSET, SYSCON3_ADCCKNSEN, | |
47 | (spi->mode & SPI_CPHA) ? | |
48 | SYSCON3_ADCCKNSEN : 0); | |
49 | } | |
50 | ||
51 | static int spi_clps711x_transfer_one(struct spi_master *master, | |
52 | struct spi_device *spi, | |
53 | struct spi_transfer *xfer) | |
54 | { | |
55 | struct spi_clps711x_data *hw = spi_master_get_devdata(master); | |
56 | u8 data; | |
57 | ||
58 | clk_set_rate(hw->spi_clk, xfer->speed_hz ? : spi->max_speed_hz); | |
59 | ||
60 | hw->len = xfer->len; | |
61 | hw->bpw = xfer->bits_per_word; | |
62 | hw->tx_buf = (u8 *)xfer->tx_buf; | |
63 | hw->rx_buf = (u8 *)xfer->rx_buf; | |
64 | ||
65 | /* Initiate transfer */ | |
66 | data = hw->tx_buf ? *hw->tx_buf++ : 0; | |
67 | writel(data | SYNCIO_FRMLEN(hw->bpw) | SYNCIO_TXFRMEN, hw->syncio); | |
68 | ||
69 | return 1; | |
70 | } | |
71 | ||
72 | static irqreturn_t spi_clps711x_isr(int irq, void *dev_id) | |
73 | { | |
74 | struct spi_master *master = dev_id; | |
75 | struct spi_clps711x_data *hw = spi_master_get_devdata(master); | |
76 | u8 data; | |
77 | ||
78 | /* Handle RX */ | |
79 | data = readb(hw->syncio); | |
80 | if (hw->rx_buf) | |
81 | *hw->rx_buf++ = data; | |
82 | ||
83 | /* Handle TX */ | |
84 | if (--hw->len > 0) { | |
85 | data = hw->tx_buf ? *hw->tx_buf++ : 0; | |
86 | writel(data | SYNCIO_FRMLEN(hw->bpw) | SYNCIO_TXFRMEN, | |
87 | hw->syncio); | |
88 | } else | |
89 | spi_finalize_current_transfer(master); | |
90 | ||
91 | return IRQ_HANDLED; | |
92 | } | |
93 | ||
94 | static int spi_clps711x_probe(struct platform_device *pdev) | |
95 | { | |
96 | struct spi_clps711x_data *hw; | |
97 | struct spi_master *master; | |
98 | struct resource *res; | |
99 | int irq, ret; | |
100 | ||
101 | irq = platform_get_irq(pdev, 0); | |
102 | if (irq < 0) | |
103 | return irq; | |
104 | ||
105 | master = spi_alloc_master(&pdev->dev, sizeof(*hw)); | |
106 | if (!master) | |
107 | return -ENOMEM; | |
108 | ||
109 | master->use_gpio_descriptors = true; | |
110 | master->bus_num = -1; | |
111 | master->mode_bits = SPI_CPHA | SPI_CS_HIGH; | |
112 | master->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 8); | |
113 | master->dev.of_node = pdev->dev.of_node; | |
114 | master->prepare_message = spi_clps711x_prepare_message; | |
115 | master->transfer_one = spi_clps711x_transfer_one; | |
116 | ||
117 | hw = spi_master_get_devdata(master); | |
118 | ||
119 | hw->spi_clk = devm_clk_get(&pdev->dev, NULL); | |
120 | if (IS_ERR(hw->spi_clk)) { | |
121 | ret = PTR_ERR(hw->spi_clk); | |
122 | goto err_out; | |
123 | } | |
124 | ||
125 | hw->syscon = | |
126 | syscon_regmap_lookup_by_compatible("cirrus,ep7209-syscon3"); | |
127 | if (IS_ERR(hw->syscon)) { | |
128 | ret = PTR_ERR(hw->syscon); | |
129 | goto err_out; | |
130 | } | |
131 | ||
132 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
133 | hw->syncio = devm_ioremap_resource(&pdev->dev, res); | |
134 | if (IS_ERR(hw->syncio)) { | |
135 | ret = PTR_ERR(hw->syncio); | |
136 | goto err_out; | |
137 | } | |
138 | ||
139 | /* Disable extended mode due hardware problems */ | |
140 | regmap_update_bits(hw->syscon, SYSCON_OFFSET, SYSCON3_ADCCON, 0); | |
141 | ||
142 | /* Clear possible pending interrupt */ | |
143 | readl(hw->syncio); | |
144 | ||
145 | ret = devm_request_irq(&pdev->dev, irq, spi_clps711x_isr, 0, | |
146 | dev_name(&pdev->dev), master); | |
147 | if (ret) | |
148 | goto err_out; | |
149 | ||
150 | ret = devm_spi_register_master(&pdev->dev, master); | |
151 | if (!ret) | |
152 | return 0; | |
153 | ||
154 | err_out: | |
155 | spi_master_put(master); | |
156 | ||
157 | return ret; | |
158 | } | |
159 | ||
160 | static const struct of_device_id clps711x_spi_dt_ids[] = { | |
161 | { .compatible = "cirrus,ep7209-spi", }, | |
162 | { } | |
163 | }; | |
164 | MODULE_DEVICE_TABLE(of, clps711x_spi_dt_ids); | |
165 | ||
166 | static struct platform_driver clps711x_spi_driver = { | |
167 | .driver = { | |
168 | .name = DRIVER_NAME, | |
169 | .of_match_table = clps711x_spi_dt_ids, | |
170 | }, | |
171 | .probe = spi_clps711x_probe, | |
172 | }; | |
173 | module_platform_driver(clps711x_spi_driver); | |
174 | ||
175 | MODULE_LICENSE("GPL"); | |
176 | MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>"); | |
177 | MODULE_DESCRIPTION("CLPS711X SPI bus driver"); | |
178 | MODULE_ALIAS("platform:" DRIVER_NAME); |