diff options
Diffstat (limited to 'drivers/spi/spi_s3c24xx.c')
-rw-r--r-- | drivers/spi/spi_s3c24xx.c | 126 |
1 files changed, 89 insertions, 37 deletions
diff --git a/drivers/spi/spi_s3c24xx.c b/drivers/spi/spi_s3c24xx.c index 03695b67ebd5..33d94f76b9ef 100644 --- a/drivers/spi/spi_s3c24xx.c +++ b/drivers/spi/spi_s3c24xx.c | |||
@@ -28,6 +28,20 @@ | |||
28 | #include <plat/regs-spi.h> | 28 | #include <plat/regs-spi.h> |
29 | #include <mach/spi.h> | 29 | #include <mach/spi.h> |
30 | 30 | ||
31 | /** | ||
32 | * s3c24xx_spi_devstate - per device data | ||
33 | * @hz: Last frequency calculated for @sppre field. | ||
34 | * @mode: Last mode setting for the @spcon field. | ||
35 | * @spcon: Value to write to the SPCON register. | ||
36 | * @sppre: Value to write to the SPPRE register. | ||
37 | */ | ||
38 | struct s3c24xx_spi_devstate { | ||
39 | unsigned int hz; | ||
40 | unsigned int mode; | ||
41 | u8 spcon; | ||
42 | u8 sppre; | ||
43 | }; | ||
44 | |||
31 | struct s3c24xx_spi { | 45 | struct s3c24xx_spi { |
32 | /* bitbang has to be first */ | 46 | /* bitbang has to be first */ |
33 | struct spi_bitbang bitbang; | 47 | struct spi_bitbang bitbang; |
@@ -68,43 +82,31 @@ static void s3c24xx_spi_gpiocs(struct s3c2410_spi_info *spi, int cs, int pol) | |||
68 | 82 | ||
69 | static void s3c24xx_spi_chipsel(struct spi_device *spi, int value) | 83 | static void s3c24xx_spi_chipsel(struct spi_device *spi, int value) |
70 | { | 84 | { |
85 | struct s3c24xx_spi_devstate *cs = spi->controller_state; | ||
71 | struct s3c24xx_spi *hw = to_hw(spi); | 86 | struct s3c24xx_spi *hw = to_hw(spi); |
72 | unsigned int cspol = spi->mode & SPI_CS_HIGH ? 1 : 0; | 87 | unsigned int cspol = spi->mode & SPI_CS_HIGH ? 1 : 0; |
73 | unsigned int spcon; | 88 | |
89 | /* change the chipselect state and the state of the spi engine clock */ | ||
74 | 90 | ||
75 | switch (value) { | 91 | switch (value) { |
76 | case BITBANG_CS_INACTIVE: | 92 | case BITBANG_CS_INACTIVE: |
77 | hw->set_cs(hw->pdata, spi->chip_select, cspol^1); | 93 | hw->set_cs(hw->pdata, spi->chip_select, cspol^1); |
94 | writeb(cs->spcon, hw->regs + S3C2410_SPCON); | ||
78 | break; | 95 | break; |
79 | 96 | ||
80 | case BITBANG_CS_ACTIVE: | 97 | case BITBANG_CS_ACTIVE: |
81 | spcon = readb(hw->regs + S3C2410_SPCON); | 98 | writeb(cs->spcon | S3C2410_SPCON_ENSCK, |
82 | 99 | hw->regs + S3C2410_SPCON); | |
83 | if (spi->mode & SPI_CPHA) | ||
84 | spcon |= S3C2410_SPCON_CPHA_FMTB; | ||
85 | else | ||
86 | spcon &= ~S3C2410_SPCON_CPHA_FMTB; | ||
87 | |||
88 | if (spi->mode & SPI_CPOL) | ||
89 | spcon |= S3C2410_SPCON_CPOL_HIGH; | ||
90 | else | ||
91 | spcon &= ~S3C2410_SPCON_CPOL_HIGH; | ||
92 | |||
93 | spcon |= S3C2410_SPCON_ENSCK; | ||
94 | |||
95 | /* write new configration */ | ||
96 | |||
97 | writeb(spcon, hw->regs + S3C2410_SPCON); | ||
98 | hw->set_cs(hw->pdata, spi->chip_select, cspol); | 100 | hw->set_cs(hw->pdata, spi->chip_select, cspol); |
99 | |||
100 | break; | 101 | break; |
101 | } | 102 | } |
102 | } | 103 | } |
103 | 104 | ||
104 | static int s3c24xx_spi_setupxfer(struct spi_device *spi, | 105 | static int s3c24xx_spi_update_state(struct spi_device *spi, |
105 | struct spi_transfer *t) | 106 | struct spi_transfer *t) |
106 | { | 107 | { |
107 | struct s3c24xx_spi *hw = to_hw(spi); | 108 | struct s3c24xx_spi *hw = to_hw(spi); |
109 | struct s3c24xx_spi_devstate *cs = spi->controller_state; | ||
108 | unsigned int bpw; | 110 | unsigned int bpw; |
109 | unsigned int hz; | 111 | unsigned int hz; |
110 | unsigned int div; | 112 | unsigned int div; |
@@ -124,41 +126,89 @@ static int s3c24xx_spi_setupxfer(struct spi_device *spi, | |||
124 | return -EINVAL; | 126 | return -EINVAL; |
125 | } | 127 | } |
126 | 128 | ||
127 | clk = clk_get_rate(hw->clk); | 129 | if (spi->mode != cs->mode) { |
128 | div = DIV_ROUND_UP(clk, hz * 2) - 1; | 130 | u8 spcon = SPCON_DEFAULT; |
129 | 131 | ||
130 | if (div > 255) | 132 | if (spi->mode & SPI_CPHA) |
131 | div = 255; | 133 | spcon |= S3C2410_SPCON_CPHA_FMTB; |
132 | 134 | ||
133 | dev_dbg(&spi->dev, "setting pre-scaler to %d (wanted %d, got %ld)\n", | 135 | if (spi->mode & SPI_CPOL) |
134 | div, hz, clk / (2 * (div + 1))); | 136 | spcon |= S3C2410_SPCON_CPOL_HIGH; |
135 | 137 | ||
138 | cs->mode = spi->mode; | ||
139 | cs->spcon = spcon; | ||
140 | } | ||
136 | 141 | ||
137 | writeb(div, hw->regs + S3C2410_SPPRE); | 142 | if (cs->hz != hz) { |
143 | clk = clk_get_rate(hw->clk); | ||
144 | div = DIV_ROUND_UP(clk, hz * 2) - 1; | ||
138 | 145 | ||
139 | spin_lock(&hw->bitbang.lock); | 146 | if (div > 255) |
140 | if (!hw->bitbang.busy) { | 147 | div = 255; |
141 | hw->bitbang.chipselect(spi, BITBANG_CS_INACTIVE); | 148 | |
142 | /* need to ndelay for 0.5 clocktick ? */ | 149 | dev_dbg(&spi->dev, "pre-scaler=%d (wanted %d, got %ld)\n", |
150 | div, hz, clk / (2 * (div + 1))); | ||
151 | |||
152 | cs->hz = hz; | ||
153 | cs->sppre = div; | ||
143 | } | 154 | } |
144 | spin_unlock(&hw->bitbang.lock); | ||
145 | 155 | ||
146 | return 0; | 156 | return 0; |
147 | } | 157 | } |
148 | 158 | ||
159 | static int s3c24xx_spi_setupxfer(struct spi_device *spi, | ||
160 | struct spi_transfer *t) | ||
161 | { | ||
162 | struct s3c24xx_spi_devstate *cs = spi->controller_state; | ||
163 | struct s3c24xx_spi *hw = to_hw(spi); | ||
164 | int ret; | ||
165 | |||
166 | ret = s3c24xx_spi_update_state(spi, t); | ||
167 | if (!ret) | ||
168 | writeb(cs->sppre, hw->regs + S3C2410_SPPRE); | ||
169 | |||
170 | return ret; | ||
171 | } | ||
172 | |||
149 | static int s3c24xx_spi_setup(struct spi_device *spi) | 173 | static int s3c24xx_spi_setup(struct spi_device *spi) |
150 | { | 174 | { |
175 | struct s3c24xx_spi_devstate *cs = spi->controller_state; | ||
176 | struct s3c24xx_spi *hw = to_hw(spi); | ||
151 | int ret; | 177 | int ret; |
152 | 178 | ||
153 | ret = s3c24xx_spi_setupxfer(spi, NULL); | 179 | /* allocate settings on the first call */ |
154 | if (ret < 0) { | 180 | if (!cs) { |
155 | dev_err(&spi->dev, "setupxfer returned %d\n", ret); | 181 | cs = kzalloc(sizeof(struct s3c24xx_spi_devstate), GFP_KERNEL); |
182 | if (!cs) { | ||
183 | dev_err(&spi->dev, "no memory for controller state\n"); | ||
184 | return -ENOMEM; | ||
185 | } | ||
186 | |||
187 | cs->spcon = SPCON_DEFAULT; | ||
188 | cs->hz = -1; | ||
189 | spi->controller_state = cs; | ||
190 | } | ||
191 | |||
192 | /* initialise the state from the device */ | ||
193 | ret = s3c24xx_spi_update_state(spi, NULL); | ||
194 | if (ret) | ||
156 | return ret; | 195 | return ret; |
196 | |||
197 | spin_lock(&hw->bitbang.lock); | ||
198 | if (!hw->bitbang.busy) { | ||
199 | hw->bitbang.chipselect(spi, BITBANG_CS_INACTIVE); | ||
200 | /* need to ndelay for 0.5 clocktick ? */ | ||
157 | } | 201 | } |
202 | spin_unlock(&hw->bitbang.lock); | ||
158 | 203 | ||
159 | return 0; | 204 | return 0; |
160 | } | 205 | } |
161 | 206 | ||
207 | static void s3c24xx_spi_cleanup(struct spi_device *spi) | ||
208 | { | ||
209 | kfree(spi->controller_state); | ||
210 | } | ||
211 | |||
162 | static inline unsigned int hw_txbyte(struct s3c24xx_spi *hw, int count) | 212 | static inline unsigned int hw_txbyte(struct s3c24xx_spi *hw, int count) |
163 | { | 213 | { |
164 | return hw->tx ? hw->tx[count] : 0; | 214 | return hw->tx ? hw->tx[count] : 0; |
@@ -286,7 +336,9 @@ static int __init s3c24xx_spi_probe(struct platform_device *pdev) | |||
286 | hw->bitbang.setup_transfer = s3c24xx_spi_setupxfer; | 336 | hw->bitbang.setup_transfer = s3c24xx_spi_setupxfer; |
287 | hw->bitbang.chipselect = s3c24xx_spi_chipsel; | 337 | hw->bitbang.chipselect = s3c24xx_spi_chipsel; |
288 | hw->bitbang.txrx_bufs = s3c24xx_spi_txrx; | 338 | hw->bitbang.txrx_bufs = s3c24xx_spi_txrx; |
289 | hw->bitbang.master->setup = s3c24xx_spi_setup; | 339 | |
340 | hw->master->setup = s3c24xx_spi_setup; | ||
341 | hw->master->cleanup = s3c24xx_spi_cleanup; | ||
290 | 342 | ||
291 | dev_dbg(hw->dev, "bitbang at %p\n", &hw->bitbang); | 343 | dev_dbg(hw->dev, "bitbang at %p\n", &hw->bitbang); |
292 | 344 | ||