configs: Resync with savedefconfig
[platform/kernel/u-boot.git] / drivers / mmc / iproc_sdhci.c
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Copyright 2019 Broadcom.
4  *
5  */
6
7 #include <common.h>
8 #include <dm.h>
9 #include <errno.h>
10 #include <malloc.h>
11 #include <sdhci.h>
12 #include <asm/global_data.h>
13 #include "mmc_private.h"
14 #include <linux/delay.h>
15
16 #define MAX_TUNING_LOOP 40
17
18 DECLARE_GLOBAL_DATA_PTR;
19
20 struct sdhci_iproc_host {
21         struct sdhci_host host;
22         u32 shadow_cmd;
23         u32 shadow_blk;
24 };
25
26 #define REG_OFFSET_IN_BITS(reg) ((reg) << 3 & 0x18)
27
28 static inline struct sdhci_iproc_host *to_iproc(struct sdhci_host *host)
29 {
30         return (struct sdhci_iproc_host *)host;
31 }
32
33 #ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS
34 static u32 sdhci_iproc_readl(struct sdhci_host *host, int reg)
35 {
36         u32 val = readl(host->ioaddr + reg);
37 #ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS_TRACE
38         printf("%s %d: readl [0x%02x] 0x%08x\n",
39                host->name, host->index, reg, val);
40 #endif
41         return val;
42 }
43
44 static u16 sdhci_iproc_readw(struct sdhci_host *host, int reg)
45 {
46         u32 val = sdhci_iproc_readl(host, (reg & ~3));
47         u16 word = val >> REG_OFFSET_IN_BITS(reg) & 0xffff;
48         return word;
49 }
50
51 static u8 sdhci_iproc_readb(struct sdhci_host *host, int reg)
52 {
53         u32 val = sdhci_iproc_readl(host, (reg & ~3));
54         u8 byte = val >> REG_OFFSET_IN_BITS(reg) & 0xff;
55         return byte;
56 }
57
58 static void sdhci_iproc_writel(struct sdhci_host *host, u32 val, int reg)
59 {
60         u32 clock = 0;
61 #ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS_TRACE
62         printf("%s %d: writel [0x%02x] 0x%08x\n",
63                host->name, host->index, reg, val);
64 #endif
65         writel(val, host->ioaddr + reg);
66
67         if (host->mmc)
68                 clock = host->mmc->clock;
69         if (clock <= 400000) {
70                 /* Round up to micro-second four SD clock delay */
71                 if (clock)
72                         udelay((4 * 1000000 + clock - 1) / clock);
73                 else
74                         udelay(10);
75         }
76 }
77
78 /*
79  * The Arasan has a bugette whereby it may lose the content of successive
80  * writes to the same register that are within two SD-card clock cycles of
81  * each other (a clock domain crossing problem). The data
82  * register does not have this problem, which is just as well - otherwise we'd
83  * have to nobble the DMA engine too.
84  *
85  * This wouldn't be a problem with the code except that we can only write the
86  * controller with 32-bit writes.  So two different 16-bit registers are
87  * written back to back creates the problem.
88  *
89  * In reality, this only happens when SDHCI_BLOCK_SIZE and SDHCI_BLOCK_COUNT
90  * are written followed by SDHCI_TRANSFER_MODE and SDHCI_COMMAND.
91  * The BLOCK_SIZE and BLOCK_COUNT are meaningless until a command issued so
92  * the work around can be further optimized. We can keep shadow values of
93  * BLOCK_SIZE, BLOCK_COUNT, and TRANSFER_MODE until a COMMAND is issued.
94  * Then, write the BLOCK_SIZE+BLOCK_COUNT in a single 32-bit write followed
95  * by the TRANSFER+COMMAND in another 32-bit write.
96  */
97 static void sdhci_iproc_writew(struct sdhci_host *host, u16 val, int reg)
98 {
99         struct sdhci_iproc_host *iproc_host = to_iproc(host);
100         u32 word_shift = REG_OFFSET_IN_BITS(reg);
101         u32 mask = 0xffff << word_shift;
102         u32 oldval, newval;
103
104         if (reg == SDHCI_COMMAND) {
105                 /* Write the block now as we are issuing a command */
106                 if (iproc_host->shadow_blk != 0) {
107                         sdhci_iproc_writel(host, iproc_host->shadow_blk,
108                                            SDHCI_BLOCK_SIZE);
109                         iproc_host->shadow_blk = 0;
110                 }
111                 oldval = iproc_host->shadow_cmd;
112         } else if (reg == SDHCI_BLOCK_SIZE || reg == SDHCI_BLOCK_COUNT) {
113                 /* Block size and count are stored in shadow reg */
114                 oldval = iproc_host->shadow_blk;
115         } else {
116                 /* Read reg, all other registers are not shadowed */
117                 oldval = sdhci_iproc_readl(host, (reg & ~3));
118         }
119         newval = (oldval & ~mask) | (val << word_shift);
120
121         if (reg == SDHCI_TRANSFER_MODE) {
122                 /* Save the transfer mode until the command is issued */
123                 iproc_host->shadow_cmd = newval;
124         } else if (reg == SDHCI_BLOCK_SIZE || reg == SDHCI_BLOCK_COUNT) {
125                 /* Save the block info until the command is issued */
126                 iproc_host->shadow_blk = newval;
127         } else {
128                 /* Command or other regular 32-bit write */
129                 sdhci_iproc_writel(host, newval, reg & ~3);
130         }
131 }
132
133 static void sdhci_iproc_writeb(struct sdhci_host *host, u8 val, int reg)
134 {
135         u32 oldval = sdhci_iproc_readl(host, (reg & ~3));
136         u32 byte_shift = REG_OFFSET_IN_BITS(reg);
137         u32 mask = 0xff << byte_shift;
138         u32 newval = (oldval & ~mask) | (val << byte_shift);
139
140         sdhci_iproc_writel(host, newval, reg & ~3);
141 }
142 #endif
143
144 static int sdhci_iproc_set_ios_post(struct sdhci_host *host)
145 {
146         struct mmc *mmc = (struct mmc *)host->mmc;
147         u32 ctrl;
148
149         if (mmc->signal_voltage == MMC_SIGNAL_VOLTAGE_180) {
150                 ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
151                 ctrl |= SDHCI_CTRL_VDD_180;
152                 sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2);
153         }
154
155         sdhci_set_uhs_timing(host);
156         return 0;
157 }
158
159 static void sdhci_start_tuning(struct sdhci_host *host)
160 {
161         u32 ctrl;
162
163         ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
164         ctrl |= SDHCI_CTRL_EXEC_TUNING;
165         sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2);
166
167         sdhci_writel(host, SDHCI_INT_DATA_AVAIL, SDHCI_INT_ENABLE);
168         sdhci_writel(host, SDHCI_INT_DATA_AVAIL, SDHCI_SIGNAL_ENABLE);
169 }
170
171 static void sdhci_end_tuning(struct sdhci_host *host)
172 {
173         /* Enable only interrupts served by the SD controller */
174         sdhci_writel(host, SDHCI_INT_DATA_MASK | SDHCI_INT_CMD_MASK,
175                      SDHCI_INT_ENABLE);
176         /* Mask all sdhci interrupt sources */
177         sdhci_writel(host, 0x0, SDHCI_SIGNAL_ENABLE);
178 }
179
180 static int sdhci_iproc_execute_tuning(struct mmc *mmc, u8 opcode)
181 {
182         struct mmc_cmd cmd;
183         u32 ctrl;
184         u32 blocksize = SDHCI_MAKE_BLKSZ(SDHCI_DEFAULT_BOUNDARY_ARG, 64);
185         struct sdhci_host *host = dev_get_priv(mmc->dev);
186         char tuning_loop_counter = MAX_TUNING_LOOP;
187         int ret = 0;
188
189         sdhci_start_tuning(host);
190
191         cmd.cmdidx = opcode;
192         cmd.resp_type = MMC_RSP_R1;
193         cmd.cmdarg = 0;
194
195         if (opcode == MMC_CMD_SEND_TUNING_BLOCK_HS200 && mmc->bus_width == 8)
196                 blocksize = SDHCI_MAKE_BLKSZ(SDHCI_DEFAULT_BOUNDARY_ARG, 128);
197
198         sdhci_writew(host, blocksize, SDHCI_BLOCK_SIZE);
199         sdhci_writew(host, 1, SDHCI_BLOCK_COUNT);
200         sdhci_writew(host, SDHCI_TRNS_READ, SDHCI_TRANSFER_MODE);
201
202         do {
203                 mmc_send_cmd(mmc, &cmd, NULL);
204                 if (opcode == MMC_CMD_SEND_TUNING_BLOCK)
205                         /*
206                          * For tuning command, do not do busy loop. As tuning
207                          * is happening (CLK-DATA latching for setup/hold time
208                          * requirements), give time to complete
209                          */
210                         udelay(1);
211
212                 ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
213
214                 if (tuning_loop_counter-- == 0)
215                         break;
216
217         } while (ctrl & SDHCI_CTRL_EXEC_TUNING);
218
219         if (tuning_loop_counter < 0 || (!(ctrl & SDHCI_CTRL_TUNED_CLK))) {
220                 ctrl &= ~(SDHCI_CTRL_TUNED_CLK | SDHCI_CTRL_EXEC_TUNING);
221                 sdhci_writel(host, ctrl, SDHCI_HOST_CONTROL2);
222                 printf("%s:Tuning failed, opcode = 0x%02x\n", __func__, opcode);
223                 ret = -EIO;
224         }
225
226         sdhci_end_tuning(host);
227
228         return ret;
229 }
230
231 static struct sdhci_ops sdhci_platform_ops = {
232 #ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS
233         .read_l = sdhci_iproc_readl,
234         .read_w = sdhci_iproc_readw,
235         .read_b = sdhci_iproc_readb,
236         .write_l = sdhci_iproc_writel,
237         .write_w = sdhci_iproc_writew,
238         .write_b = sdhci_iproc_writeb,
239 #endif
240         .set_ios_post = sdhci_iproc_set_ios_post,
241         .platform_execute_tuning = sdhci_iproc_execute_tuning,
242 };
243
244 struct iproc_sdhci_plat {
245         struct mmc_config cfg;
246         struct mmc mmc;
247 };
248
249 static int iproc_sdhci_probe(struct udevice *dev)
250 {
251         struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev);
252         struct iproc_sdhci_plat *plat = dev_get_plat(dev);
253         struct sdhci_host *host = dev_get_priv(dev);
254         struct sdhci_iproc_host *iproc_host;
255         int node = dev_of_offset(dev);
256         u32 f_min_max[2];
257         int ret;
258
259         iproc_host = malloc(sizeof(struct sdhci_iproc_host));
260         if (!iproc_host) {
261                 printf("%s: sdhci host malloc fail!\n", __func__);
262                 return -ENOMEM;
263         }
264         iproc_host->shadow_cmd = 0;
265         iproc_host->shadow_blk = 0;
266
267         host->name = dev->name;
268         host->ioaddr = dev_read_addr_ptr(dev);
269         host->quirks = SDHCI_QUIRK_BROKEN_R1B;
270         host->host_caps = MMC_MODE_DDR_52MHz;
271         host->index = fdtdec_get_uint(gd->fdt_blob, node, "index", 0);
272         host->ops = &sdhci_platform_ops;
273         host->version = sdhci_readw(host, SDHCI_HOST_VERSION);
274         ret = fdtdec_get_int_array(gd->fdt_blob, dev_of_offset(dev),
275                                    "clock-freq-min-max", f_min_max, 2);
276         if (ret) {
277                 printf("sdhci: clock-freq-min-max not found\n");
278                 free(iproc_host);
279                 return ret;
280         }
281         host->max_clk = f_min_max[1];
282         host->bus_width = fdtdec_get_int(gd->fdt_blob,
283                                          dev_of_offset(dev), "bus-width", 4);
284
285         /* Update host_caps for 8 bit bus width */
286         if (host->bus_width == 8)
287                 host->host_caps |= MMC_MODE_8BIT;
288
289         memcpy(&iproc_host->host, host, sizeof(struct sdhci_host));
290
291         iproc_host->host.mmc = &plat->mmc;
292         iproc_host->host.mmc->dev = dev;
293         iproc_host->host.mmc->priv = &iproc_host->host;
294         upriv->mmc = iproc_host->host.mmc;
295
296         ret = sdhci_setup_cfg(&plat->cfg, &iproc_host->host,
297                               f_min_max[1], f_min_max[0]);
298         if (ret) {
299                 free(iproc_host);
300                 return ret;
301         }
302
303         return sdhci_probe(dev);
304 }
305
306 static int iproc_sdhci_bind(struct udevice *dev)
307 {
308         struct iproc_sdhci_plat *plat = dev_get_plat(dev);
309
310         return sdhci_bind(dev, &plat->mmc, &plat->cfg);
311 }
312
313 static const struct udevice_id iproc_sdhci_ids[] = {
314         { .compatible = "brcm,iproc-sdhci" },
315         { }
316 };
317
318 U_BOOT_DRIVER(iproc_sdhci_drv) = {
319         .name = "iproc_sdhci",
320         .id = UCLASS_MMC,
321         .of_match = iproc_sdhci_ids,
322         .ops = &sdhci_ops,
323         .bind = iproc_sdhci_bind,
324         .probe = iproc_sdhci_probe,
325         .priv_auto      = sizeof(struct sdhci_host),
326         .plat_auto      = sizeof(struct iproc_sdhci_plat),
327 };