1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * SPI host driver for ICP DAS LP-8841 RTC |
4 | * |
5 | * Copyright (C) 2016 Sergei Ianovich |
6 | * |
7 | * based on |
8 | * |
9 | * Dallas DS1302 RTC Support |
10 | * Copyright (C) 2002 David McCullough |
11 | * Copyright (C) 2003 - 2007 Paul Mundt |
12 | */ |
13 | #include <linux/delay.h> |
14 | #include <linux/kernel.h> |
15 | #include <linux/module.h> |
16 | #include <linux/platform_device.h> |
17 | #include <linux/of.h> |
18 | #include <linux/spi/spi.h> |
19 | |
20 | #define DRIVER_NAME "spi_lp8841_rtc" |
21 | |
22 | #define SPI_LP8841_RTC_CE 0x01 |
23 | #define SPI_LP8841_RTC_CLK 0x02 |
24 | #define SPI_LP8841_RTC_nWE 0x04 |
25 | #define SPI_LP8841_RTC_MOSI 0x08 |
26 | #define SPI_LP8841_RTC_MISO 0x01 |
27 | |
28 | /* |
29 | * REVISIT If there is support for SPI_3WIRE and SPI_LSB_FIRST in SPI |
30 | * GPIO driver, this SPI driver can be replaced by a simple GPIO driver |
31 | * providing 3 GPIO pins. |
32 | */ |
33 | |
34 | struct spi_lp8841_rtc { |
35 | void *iomem; |
36 | unsigned long state; |
37 | }; |
38 | |
39 | static inline void |
40 | setsck(struct spi_lp8841_rtc *data, int is_on) |
41 | { |
42 | if (is_on) |
43 | data->state |= SPI_LP8841_RTC_CLK; |
44 | else |
45 | data->state &= ~SPI_LP8841_RTC_CLK; |
46 | writeb(val: data->state, addr: data->iomem); |
47 | } |
48 | |
49 | static inline void |
50 | setmosi(struct spi_lp8841_rtc *data, int is_on) |
51 | { |
52 | if (is_on) |
53 | data->state |= SPI_LP8841_RTC_MOSI; |
54 | else |
55 | data->state &= ~SPI_LP8841_RTC_MOSI; |
56 | writeb(val: data->state, addr: data->iomem); |
57 | } |
58 | |
59 | static inline int |
60 | getmiso(struct spi_lp8841_rtc *data) |
61 | { |
62 | return ioread8(data->iomem) & SPI_LP8841_RTC_MISO; |
63 | } |
64 | |
65 | static inline u32 |
66 | bitbang_txrx_be_cpha0_lsb(struct spi_lp8841_rtc *data, |
67 | unsigned usecs, unsigned cpol, unsigned flags, |
68 | u32 word, u8 bits) |
69 | { |
70 | /* if (cpol == 0) this is SPI_MODE_0; else this is SPI_MODE_2 */ |
71 | |
72 | u32 shift = 32 - bits; |
73 | /* clock starts at inactive polarity */ |
74 | for (; likely(bits); bits--) { |
75 | |
76 | /* setup LSB (to target) on leading edge */ |
77 | if ((flags & SPI_CONTROLLER_NO_TX) == 0) |
78 | setmosi(data, is_on: (word & 1)); |
79 | |
80 | usleep_range(min: usecs, max: usecs + 1); /* T(setup) */ |
81 | |
82 | /* sample LSB (from target) on trailing edge */ |
83 | word >>= 1; |
84 | if ((flags & SPI_CONTROLLER_NO_RX) == 0) |
85 | word |= (getmiso(data) << 31); |
86 | |
87 | setsck(data, is_on: !cpol); |
88 | usleep_range(min: usecs, max: usecs + 1); |
89 | |
90 | setsck(data, is_on: cpol); |
91 | } |
92 | |
93 | word >>= shift; |
94 | return word; |
95 | } |
96 | |
97 | static int |
98 | spi_lp8841_rtc_transfer_one(struct spi_controller *host, |
99 | struct spi_device *spi, |
100 | struct spi_transfer *t) |
101 | { |
102 | struct spi_lp8841_rtc *data = spi_controller_get_devdata(ctlr: host); |
103 | unsigned count = t->len; |
104 | const u8 *tx = t->tx_buf; |
105 | u8 *rx = t->rx_buf; |
106 | u8 word = 0; |
107 | int ret = 0; |
108 | |
109 | if (tx) { |
110 | data->state &= ~SPI_LP8841_RTC_nWE; |
111 | writeb(val: data->state, addr: data->iomem); |
112 | while (likely(count > 0)) { |
113 | word = *tx++; |
114 | bitbang_txrx_be_cpha0_lsb(data, usecs: 1, cpol: 0, |
115 | SPI_CONTROLLER_NO_RX, word, bits: 8); |
116 | count--; |
117 | } |
118 | } else if (rx) { |
119 | data->state |= SPI_LP8841_RTC_nWE; |
120 | writeb(val: data->state, addr: data->iomem); |
121 | while (likely(count > 0)) { |
122 | word = bitbang_txrx_be_cpha0_lsb(data, usecs: 1, cpol: 0, |
123 | SPI_CONTROLLER_NO_TX, word, bits: 8); |
124 | *rx++ = word; |
125 | count--; |
126 | } |
127 | } else { |
128 | ret = -EINVAL; |
129 | } |
130 | |
131 | spi_finalize_current_transfer(ctlr: host); |
132 | |
133 | return ret; |
134 | } |
135 | |
136 | static void |
137 | spi_lp8841_rtc_set_cs(struct spi_device *spi, bool enable) |
138 | { |
139 | struct spi_lp8841_rtc *data = spi_controller_get_devdata(ctlr: spi->controller); |
140 | |
141 | data->state = 0; |
142 | writeb(val: data->state, addr: data->iomem); |
143 | if (enable) { |
144 | usleep_range(min: 4, max: 5); |
145 | data->state |= SPI_LP8841_RTC_CE; |
146 | writeb(val: data->state, addr: data->iomem); |
147 | usleep_range(min: 4, max: 5); |
148 | } |
149 | } |
150 | |
151 | static int |
152 | spi_lp8841_rtc_setup(struct spi_device *spi) |
153 | { |
154 | if ((spi->mode & SPI_CS_HIGH) == 0) { |
155 | dev_err(&spi->dev, "unsupported active low chip select\n" ); |
156 | return -EINVAL; |
157 | } |
158 | |
159 | if ((spi->mode & SPI_LSB_FIRST) == 0) { |
160 | dev_err(&spi->dev, "unsupported MSB first mode\n" ); |
161 | return -EINVAL; |
162 | } |
163 | |
164 | if ((spi->mode & SPI_3WIRE) == 0) { |
165 | dev_err(&spi->dev, "unsupported wiring. 3 wires required\n" ); |
166 | return -EINVAL; |
167 | } |
168 | |
169 | return 0; |
170 | } |
171 | |
172 | #ifdef CONFIG_OF |
173 | static const struct of_device_id spi_lp8841_rtc_dt_ids[] = { |
174 | { .compatible = "icpdas,lp8841-spi-rtc" }, |
175 | { } |
176 | }; |
177 | |
178 | MODULE_DEVICE_TABLE(of, spi_lp8841_rtc_dt_ids); |
179 | #endif |
180 | |
181 | static int |
182 | spi_lp8841_rtc_probe(struct platform_device *pdev) |
183 | { |
184 | int ret; |
185 | struct spi_controller *host; |
186 | struct spi_lp8841_rtc *data; |
187 | |
188 | host = spi_alloc_host(dev: &pdev->dev, size: sizeof(*data)); |
189 | if (!host) |
190 | return -ENOMEM; |
191 | platform_set_drvdata(pdev, data: host); |
192 | |
193 | host->flags = SPI_CONTROLLER_HALF_DUPLEX; |
194 | host->mode_bits = SPI_CS_HIGH | SPI_3WIRE | SPI_LSB_FIRST; |
195 | |
196 | host->bus_num = pdev->id; |
197 | host->num_chipselect = 1; |
198 | host->setup = spi_lp8841_rtc_setup; |
199 | host->set_cs = spi_lp8841_rtc_set_cs; |
200 | host->transfer_one = spi_lp8841_rtc_transfer_one; |
201 | host->bits_per_word_mask = SPI_BPW_MASK(8); |
202 | #ifdef CONFIG_OF |
203 | host->dev.of_node = pdev->dev.of_node; |
204 | #endif |
205 | |
206 | data = spi_controller_get_devdata(ctlr: host); |
207 | |
208 | data->iomem = devm_platform_ioremap_resource(pdev, index: 0); |
209 | ret = PTR_ERR_OR_ZERO(ptr: data->iomem); |
210 | if (ret) { |
211 | dev_err(&pdev->dev, "failed to get IO address\n" ); |
212 | goto err_put_host; |
213 | } |
214 | |
215 | /* register with the SPI framework */ |
216 | ret = devm_spi_register_controller(dev: &pdev->dev, ctlr: host); |
217 | if (ret) { |
218 | dev_err(&pdev->dev, "cannot register spi host\n" ); |
219 | goto err_put_host; |
220 | } |
221 | |
222 | return ret; |
223 | |
224 | |
225 | err_put_host: |
226 | spi_controller_put(ctlr: host); |
227 | |
228 | return ret; |
229 | } |
230 | |
231 | MODULE_ALIAS("platform:" DRIVER_NAME); |
232 | |
233 | static struct platform_driver spi_lp8841_rtc_driver = { |
234 | .driver = { |
235 | .name = DRIVER_NAME, |
236 | .of_match_table = of_match_ptr(spi_lp8841_rtc_dt_ids), |
237 | }, |
238 | .probe = spi_lp8841_rtc_probe, |
239 | }; |
240 | module_platform_driver(spi_lp8841_rtc_driver); |
241 | |
242 | MODULE_DESCRIPTION("SPI host driver for ICP DAS LP-8841 RTC" ); |
243 | MODULE_AUTHOR("Sergei Ianovich" ); |
244 | MODULE_LICENSE("GPL" ); |
245 | |