1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC |
4 | * |
5 | * Authors: |
6 | * Serge Semin <Sergey.Semin@baikalelectronics.ru> |
7 | * |
8 | * Baikal-T1 Physically Mapped Internal ROM driver |
9 | */ |
10 | #include <linux/bits.h> |
11 | #include <linux/device.h> |
12 | #include <linux/kernel.h> |
13 | #include <linux/mtd/map.h> |
14 | #include <linux/mtd/xip.h> |
15 | #include <linux/mux/consumer.h> |
16 | #include <linux/of.h> |
17 | #include <linux/platform_device.h> |
18 | #include <linux/string.h> |
19 | #include <linux/types.h> |
20 | |
21 | #include "physmap-bt1-rom.h" |
22 | |
23 | /* |
24 | * Baikal-T1 SoC ROMs are only accessible by the dword-aligned instructions. |
25 | * We have to take this into account when implementing the data read-methods. |
26 | * Note there is no need in bothering with endianness, since both Baikal-T1 |
27 | * CPU and MMIO are LE. |
28 | */ |
29 | static map_word __xipram bt1_rom_map_read(struct map_info *map, |
30 | unsigned long ofs) |
31 | { |
32 | void __iomem *src = map->virt + ofs; |
33 | unsigned int shift; |
34 | map_word ret; |
35 | u32 data; |
36 | |
37 | /* Read data within offset dword. */ |
38 | shift = (uintptr_t)src & 0x3; |
39 | data = readl_relaxed(src - shift); |
40 | if (!shift) { |
41 | ret.x[0] = data; |
42 | return ret; |
43 | } |
44 | ret.x[0] = data >> (shift * BITS_PER_BYTE); |
45 | |
46 | /* Read data from the next dword. */ |
47 | shift = 4 - shift; |
48 | if (ofs + shift >= map->size) |
49 | return ret; |
50 | |
51 | data = readl_relaxed(src + shift); |
52 | ret.x[0] |= data << (shift * BITS_PER_BYTE); |
53 | |
54 | return ret; |
55 | } |
56 | |
57 | static void __xipram bt1_rom_map_copy_from(struct map_info *map, |
58 | void *to, unsigned long from, |
59 | ssize_t len) |
60 | { |
61 | void __iomem *src = map->virt + from; |
62 | unsigned int shift, chunk; |
63 | u32 data; |
64 | |
65 | if (len <= 0 || from >= map->size) |
66 | return; |
67 | |
68 | /* Make sure we don't go over the map limit. */ |
69 | len = min_t(ssize_t, map->size - from, len); |
70 | |
71 | /* |
72 | * Since requested data size can be pretty big we have to implement |
73 | * the copy procedure as optimal as possible. That's why it's split |
74 | * up into the next three stages: unaligned head, aligned body, |
75 | * unaligned tail. |
76 | */ |
77 | shift = (uintptr_t)src & 0x3; |
78 | if (shift) { |
79 | chunk = min_t(ssize_t, 4 - shift, len); |
80 | data = readl_relaxed(src - shift); |
81 | memcpy(to, (char *)&data + shift, chunk); |
82 | src += chunk; |
83 | to += chunk; |
84 | len -= chunk; |
85 | } |
86 | |
87 | while (len >= 4) { |
88 | data = readl_relaxed(src); |
89 | memcpy(to, &data, 4); |
90 | src += 4; |
91 | to += 4; |
92 | len -= 4; |
93 | } |
94 | |
95 | if (len) { |
96 | data = readl_relaxed(src); |
97 | memcpy(to, &data, len); |
98 | } |
99 | } |
100 | |
101 | int of_flash_probe_bt1_rom(struct platform_device *pdev, |
102 | struct device_node *np, |
103 | struct map_info *map) |
104 | { |
105 | struct device *dev = &pdev->dev; |
106 | |
107 | /* It's supposed to be read-only MTD. */ |
108 | if (!of_device_is_compatible(device: np, "mtd-rom" )) { |
109 | dev_info(dev, "No mtd-rom compatible string\n" ); |
110 | return 0; |
111 | } |
112 | |
113 | /* Multiplatform guard. */ |
114 | if (!of_device_is_compatible(device: np, "baikal,bt1-int-rom" )) |
115 | return 0; |
116 | |
117 | /* Sanity check the device parameters retrieved from DTB. */ |
118 | if (map->bankwidth != 4) |
119 | dev_warn(dev, "Bank width is supposed to be 32 bits wide\n" ); |
120 | |
121 | map->read = bt1_rom_map_read; |
122 | map->copy_from = bt1_rom_map_copy_from; |
123 | |
124 | return 0; |
125 | } |
126 | |