]> git.kernelconcepts.de Git - karo-tx-linux.git/blob - drivers/spi/spi-clps711x.c
spi/clps711x: drop clk_put for devm_clk_get in spi_clps711x_probe()
[karo-tx-linux.git] / drivers / spi / spi-clps711x.c
1 /*
2  *  CLPS711X SPI bus driver
3  *
4  *  Copyright (C) 2012 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/init.h>
15 #include <linux/gpio.h>
16 #include <linux/delay.h>
17 #include <linux/module.h>
18 #include <linux/interrupt.h>
19 #include <linux/platform_device.h>
20 #include <linux/spi/spi.h>
21 #include <linux/platform_data/spi-clps711x.h>
22
23 #include <mach/hardware.h>
24
25 #define DRIVER_NAME     "spi-clps711x"
26
27 struct spi_clps711x_data {
28         struct completion       done;
29
30         struct clk              *spi_clk;
31         u32                     max_speed_hz;
32
33         u8                      *tx_buf;
34         u8                      *rx_buf;
35         int                     count;
36         int                     len;
37
38         int                     chipselect[0];
39 };
40
41 static int spi_clps711x_setup(struct spi_device *spi)
42 {
43         struct spi_clps711x_data *hw = spi_master_get_devdata(spi->master);
44
45         /* We are expect that SPI-device is not selected */
46         gpio_direction_output(hw->chipselect[spi->chip_select],
47                               !(spi->mode & SPI_CS_HIGH));
48
49         return 0;
50 }
51
52 static void spi_clps711x_setup_mode(struct spi_device *spi)
53 {
54         /* Setup edge for transfer */
55         if (spi->mode & SPI_CPHA)
56                 clps_writew(clps_readw(SYSCON3) | SYSCON3_ADCCKNSEN, SYSCON3);
57         else
58                 clps_writew(clps_readw(SYSCON3) & ~SYSCON3_ADCCKNSEN, SYSCON3);
59 }
60
61 static int spi_clps711x_setup_xfer(struct spi_device *spi,
62                                    struct spi_transfer *xfer)
63 {
64         u32 speed = xfer->speed_hz ? : spi->max_speed_hz;
65         u8 bpw = xfer->bits_per_word;
66         struct spi_clps711x_data *hw = spi_master_get_devdata(spi->master);
67
68         if (bpw != 8) {
69                 dev_err(&spi->dev, "Unsupported master bus width %i\n", bpw);
70                 return -EINVAL;
71         }
72
73         /* Setup SPI frequency divider */
74         if (!speed || (speed >= hw->max_speed_hz))
75                 clps_writel((clps_readl(SYSCON1) & ~SYSCON1_ADCKSEL_MASK) |
76                             SYSCON1_ADCKSEL(3), SYSCON1);
77         else if (speed >= (hw->max_speed_hz / 2))
78                 clps_writel((clps_readl(SYSCON1) & ~SYSCON1_ADCKSEL_MASK) |
79                             SYSCON1_ADCKSEL(2), SYSCON1);
80         else if (speed >= (hw->max_speed_hz / 8))
81                 clps_writel((clps_readl(SYSCON1) & ~SYSCON1_ADCKSEL_MASK) |
82                             SYSCON1_ADCKSEL(1), SYSCON1);
83         else
84                 clps_writel((clps_readl(SYSCON1) & ~SYSCON1_ADCKSEL_MASK) |
85                             SYSCON1_ADCKSEL(0), SYSCON1);
86
87         return 0;
88 }
89
90 static int spi_clps711x_transfer_one_message(struct spi_master *master,
91                                              struct spi_message *msg)
92 {
93         struct spi_clps711x_data *hw = spi_master_get_devdata(master);
94         struct spi_transfer *xfer;
95         int status = 0, cs = hw->chipselect[msg->spi->chip_select];
96         u32 data;
97
98         spi_clps711x_setup_mode(msg->spi);
99
100         list_for_each_entry(xfer, &msg->transfers, transfer_list) {
101                 if (spi_clps711x_setup_xfer(msg->spi, xfer)) {
102                         status = -EINVAL;
103                         goto out_xfr;
104                 }
105
106                 gpio_set_value(cs, !!(msg->spi->mode & SPI_CS_HIGH));
107
108                 INIT_COMPLETION(hw->done);
109
110                 hw->count = 0;
111                 hw->len = xfer->len;
112                 hw->tx_buf = (u8 *)xfer->tx_buf;
113                 hw->rx_buf = (u8 *)xfer->rx_buf;
114
115                 /* Initiate transfer */
116                 data = hw->tx_buf ? hw->tx_buf[hw->count] : 0;
117                 clps_writel(data | SYNCIO_FRMLEN(8) | SYNCIO_TXFRMEN, SYNCIO);
118
119                 wait_for_completion(&hw->done);
120
121                 if (xfer->delay_usecs)
122                         udelay(xfer->delay_usecs);
123
124                 if (xfer->cs_change ||
125                     list_is_last(&xfer->transfer_list, &msg->transfers))
126                         gpio_set_value(cs, !(msg->spi->mode & SPI_CS_HIGH));
127
128                 msg->actual_length += xfer->len;
129         }
130
131 out_xfr:
132         msg->status = status;
133         spi_finalize_current_message(master);
134
135         return 0;
136 }
137
138 static irqreturn_t spi_clps711x_isr(int irq, void *dev_id)
139 {
140         struct spi_clps711x_data *hw = (struct spi_clps711x_data *)dev_id;
141         u32 data;
142
143         /* Handle RX */
144         data = clps_readb(SYNCIO);
145         if (hw->rx_buf)
146                 hw->rx_buf[hw->count] = (u8)data;
147
148         hw->count++;
149
150         /* Handle TX */
151         if (hw->count < hw->len) {
152                 data = hw->tx_buf ? hw->tx_buf[hw->count] : 0;
153                 clps_writel(data | SYNCIO_FRMLEN(8) | SYNCIO_TXFRMEN, SYNCIO);
154         } else
155                 complete(&hw->done);
156
157         return IRQ_HANDLED;
158 }
159
160 static int spi_clps711x_probe(struct platform_device *pdev)
161 {
162         int i, ret;
163         struct spi_master *master;
164         struct spi_clps711x_data *hw;
165         struct spi_clps711x_pdata *pdata = dev_get_platdata(&pdev->dev);
166
167         if (!pdata) {
168                 dev_err(&pdev->dev, "No platform data supplied\n");
169                 return -EINVAL;
170         }
171
172         if (pdata->num_chipselect < 1) {
173                 dev_err(&pdev->dev, "At least one CS must be defined\n");
174                 return -EINVAL;
175         }
176
177         master = spi_alloc_master(&pdev->dev,
178                                   sizeof(struct spi_clps711x_data) +
179                                   sizeof(int) * pdata->num_chipselect);
180         if (!master) {
181                 dev_err(&pdev->dev, "SPI allocating memory error\n");
182                 return -ENOMEM;
183         }
184
185         master->bus_num = pdev->id;
186         master->mode_bits = SPI_CPHA | SPI_CS_HIGH;
187         master->bits_per_word_mask = SPI_BPW_MASK(8);
188         master->num_chipselect = pdata->num_chipselect;
189         master->setup = spi_clps711x_setup;
190         master->transfer_one_message = spi_clps711x_transfer_one_message;
191
192         hw = spi_master_get_devdata(master);
193
194         for (i = 0; i < master->num_chipselect; i++) {
195                 hw->chipselect[i] = pdata->chipselect[i];
196                 if (!gpio_is_valid(hw->chipselect[i])) {
197                         dev_err(&pdev->dev, "Invalid CS GPIO %i\n", i);
198                         ret = -EINVAL;
199                         goto err_out;
200                 }
201                 if (gpio_request(hw->chipselect[i], DRIVER_NAME)) {
202                         dev_err(&pdev->dev, "Can't get CS GPIO %i\n", i);
203                         ret = -EINVAL;
204                         goto err_out;
205                 }
206         }
207
208         hw->spi_clk = devm_clk_get(&pdev->dev, "spi");
209         if (IS_ERR(hw->spi_clk)) {
210                 dev_err(&pdev->dev, "Can't get clocks\n");
211                 ret = PTR_ERR(hw->spi_clk);
212                 goto err_out;
213         }
214         hw->max_speed_hz = clk_get_rate(hw->spi_clk);
215
216         init_completion(&hw->done);
217         platform_set_drvdata(pdev, master);
218
219         /* Disable extended mode due hardware problems */
220         clps_writew(clps_readw(SYSCON3) & ~SYSCON3_ADCCON, SYSCON3);
221
222         /* Clear possible pending interrupt */
223         clps_readl(SYNCIO);
224
225         ret = devm_request_irq(&pdev->dev, IRQ_SSEOTI, spi_clps711x_isr, 0,
226                                dev_name(&pdev->dev), hw);
227         if (ret) {
228                 dev_err(&pdev->dev, "Can't request IRQ\n");
229                 goto clk_out;
230         }
231
232         ret = spi_register_master(master);
233         if (!ret) {
234                 dev_info(&pdev->dev,
235                          "SPI bus driver initialized. Master clock %u Hz\n",
236                          hw->max_speed_hz);
237                 return 0;
238         }
239
240         dev_err(&pdev->dev, "Failed to register master\n");
241
242 clk_out:
243 err_out:
244         while (--i >= 0)
245                 if (gpio_is_valid(hw->chipselect[i]))
246                         gpio_free(hw->chipselect[i]);
247
248         spi_master_put(master);
249
250         return ret;
251 }
252
253 static int spi_clps711x_remove(struct platform_device *pdev)
254 {
255         int i;
256         struct spi_master *master = platform_get_drvdata(pdev);
257         struct spi_clps711x_data *hw = spi_master_get_devdata(master);
258
259         for (i = 0; i < master->num_chipselect; i++)
260                 if (gpio_is_valid(hw->chipselect[i]))
261                         gpio_free(hw->chipselect[i]);
262
263         spi_unregister_master(master);
264
265         return 0;
266 }
267
268 static struct platform_driver clps711x_spi_driver = {
269         .driver = {
270                 .name   = DRIVER_NAME,
271                 .owner  = THIS_MODULE,
272         },
273         .probe  = spi_clps711x_probe,
274         .remove = spi_clps711x_remove,
275 };
276 module_platform_driver(clps711x_spi_driver);
277
278 MODULE_LICENSE("GPL");
279 MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>");
280 MODULE_DESCRIPTION("CLPS711X SPI bus driver");