]>
Commit | Line | Data |
---|---|---|
5bee27aa SG |
1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* | |
3 | * Uclass for Primary-to-sideband bus, used to access various peripherals | |
4 | * | |
5 | * Copyright 2019 Google LLC | |
6 | * Written by Simon Glass <[email protected]> | |
7 | */ | |
8 | ||
9 | #include <common.h> | |
10 | #include <dm.h> | |
336d4615 | 11 | #include <malloc.h> |
5bee27aa SG |
12 | #include <mapmem.h> |
13 | #include <p2sb.h> | |
14 | #include <spl.h> | |
15 | #include <asm/io.h> | |
16 | #include <dm/uclass-internal.h> | |
17 | ||
18 | #define PCR_COMMON_IOSF_1_0 1 | |
19 | ||
20 | static void *_pcr_reg_address(struct udevice *dev, uint offset) | |
21 | { | |
22 | struct p2sb_child_platdata *pplat = dev_get_parent_platdata(dev); | |
23 | struct udevice *p2sb = dev_get_parent(dev); | |
24 | struct p2sb_uc_priv *upriv = dev_get_uclass_priv(p2sb); | |
25 | uintptr_t reg_addr; | |
26 | ||
27 | /* Create an address based off of port id and offset */ | |
28 | reg_addr = upriv->mmio_base; | |
29 | reg_addr += pplat->pid << PCR_PORTID_SHIFT; | |
30 | reg_addr += offset; | |
31 | ||
32 | return map_sysmem(reg_addr, 4); | |
33 | } | |
34 | ||
35 | /* | |
36 | * The mapping of addresses via the SBREG_BAR assumes the IOSF-SB | |
37 | * agents are using 32-bit aligned accesses for their configuration | |
38 | * registers. For IOSF versions greater than 1_0, IOSF-SB | |
39 | * agents can use any access (8/16/32 bit aligned) for their | |
40 | * configuration registers | |
41 | */ | |
42 | static inline void check_pcr_offset_align(uint offset, uint size) | |
43 | { | |
44 | const size_t align = PCR_COMMON_IOSF_1_0 ? sizeof(uint32_t) : size; | |
45 | ||
46 | assert(IS_ALIGNED(offset, align)); | |
47 | } | |
48 | ||
49 | uint pcr_read32(struct udevice *dev, uint offset) | |
50 | { | |
51 | void *ptr; | |
52 | uint val; | |
53 | ||
54 | /* Ensure the PCR offset is correctly aligned */ | |
55 | assert(IS_ALIGNED(offset, sizeof(uint32_t))); | |
56 | ||
57 | ptr = _pcr_reg_address(dev, offset); | |
58 | val = readl(ptr); | |
59 | unmap_sysmem(ptr); | |
60 | ||
61 | return val; | |
62 | } | |
63 | ||
64 | uint pcr_read16(struct udevice *dev, uint offset) | |
65 | { | |
66 | /* Ensure the PCR offset is correctly aligned */ | |
67 | check_pcr_offset_align(offset, sizeof(uint16_t)); | |
68 | ||
69 | return readw(_pcr_reg_address(dev, offset)); | |
70 | } | |
71 | ||
72 | uint pcr_read8(struct udevice *dev, uint offset) | |
73 | { | |
74 | /* Ensure the PCR offset is correctly aligned */ | |
75 | check_pcr_offset_align(offset, sizeof(uint8_t)); | |
76 | ||
77 | return readb(_pcr_reg_address(dev, offset)); | |
78 | } | |
79 | ||
80 | /* | |
81 | * After every write one needs to perform a read an innocuous register to | |
82 | * ensure the writes are completed for certain ports. This is done for | |
83 | * all ports so that the callers don't need the per-port knowledge for | |
84 | * each transaction. | |
85 | */ | |
86 | static void write_completion(struct udevice *dev, uint offset) | |
87 | { | |
88 | readl(_pcr_reg_address(dev, ALIGN_DOWN(offset, sizeof(uint32_t)))); | |
89 | } | |
90 | ||
91 | void pcr_write32(struct udevice *dev, uint offset, uint indata) | |
92 | { | |
93 | /* Ensure the PCR offset is correctly aligned */ | |
94 | assert(IS_ALIGNED(offset, sizeof(indata))); | |
95 | ||
96 | writel(indata, _pcr_reg_address(dev, offset)); | |
97 | /* Ensure the writes complete */ | |
98 | write_completion(dev, offset); | |
99 | } | |
100 | ||
101 | void pcr_write16(struct udevice *dev, uint offset, uint indata) | |
102 | { | |
103 | /* Ensure the PCR offset is correctly aligned */ | |
104 | check_pcr_offset_align(offset, sizeof(uint16_t)); | |
105 | ||
106 | writew(indata, _pcr_reg_address(dev, offset)); | |
107 | /* Ensure the writes complete */ | |
108 | write_completion(dev, offset); | |
109 | } | |
110 | ||
111 | void pcr_write8(struct udevice *dev, uint offset, uint indata) | |
112 | { | |
113 | /* Ensure the PCR offset is correctly aligned */ | |
114 | check_pcr_offset_align(offset, sizeof(uint8_t)); | |
115 | ||
116 | writeb(indata, _pcr_reg_address(dev, offset)); | |
117 | /* Ensure the writes complete */ | |
118 | write_completion(dev, offset); | |
119 | } | |
120 | ||
121 | void pcr_clrsetbits32(struct udevice *dev, uint offset, uint clr, uint set) | |
122 | { | |
123 | uint data32; | |
124 | ||
125 | data32 = pcr_read32(dev, offset); | |
126 | data32 &= ~clr; | |
127 | data32 |= set; | |
128 | pcr_write32(dev, offset, data32); | |
129 | } | |
130 | ||
131 | void pcr_clrsetbits16(struct udevice *dev, uint offset, uint clr, uint set) | |
132 | { | |
133 | uint data16; | |
134 | ||
135 | data16 = pcr_read16(dev, offset); | |
136 | data16 &= ~clr; | |
137 | data16 |= set; | |
138 | pcr_write16(dev, offset, data16); | |
139 | } | |
140 | ||
141 | void pcr_clrsetbits8(struct udevice *dev, uint offset, uint clr, uint set) | |
142 | { | |
143 | uint data8; | |
144 | ||
145 | data8 = pcr_read8(dev, offset); | |
146 | data8 &= ~clr; | |
147 | data8 |= set; | |
148 | pcr_write8(dev, offset, data8); | |
149 | } | |
150 | ||
151 | int p2sb_get_port_id(struct udevice *dev) | |
152 | { | |
153 | struct p2sb_child_platdata *pplat = dev_get_parent_platdata(dev); | |
154 | ||
155 | return pplat->pid; | |
156 | } | |
157 | ||
158 | int p2sb_set_port_id(struct udevice *dev, int portid) | |
159 | { | |
160 | struct udevice *ps2b; | |
161 | struct p2sb_child_platdata *pplat; | |
162 | ||
163 | if (!CONFIG_IS_ENABLED(OF_PLATDATA)) | |
164 | return -ENOSYS; | |
165 | ||
166 | uclass_find_first_device(UCLASS_P2SB, &ps2b); | |
167 | if (!ps2b) | |
168 | return -EDEADLK; | |
169 | dev->parent = ps2b; | |
170 | ||
171 | /* | |
172 | * We must allocate this, since when the device was bound it did not | |
173 | * have a parent. | |
174 | * TODO([email protected]): Add a parent pointer to child devices in dtoc | |
175 | */ | |
176 | dev->parent_platdata = malloc(sizeof(*pplat)); | |
177 | if (!dev->parent_platdata) | |
178 | return -ENOMEM; | |
179 | pplat = dev_get_parent_platdata(dev); | |
180 | pplat->pid = portid; | |
181 | ||
182 | return 0; | |
183 | } | |
184 | ||
185 | static int p2sb_child_post_bind(struct udevice *dev) | |
186 | { | |
187 | #if !CONFIG_IS_ENABLED(OF_PLATDATA) | |
188 | struct p2sb_child_platdata *pplat = dev_get_parent_platdata(dev); | |
189 | int ret; | |
190 | u32 pid; | |
191 | ||
192 | ret = dev_read_u32(dev, "intel,p2sb-port-id", &pid); | |
193 | if (ret) | |
194 | return ret; | |
195 | pplat->pid = pid; | |
196 | #endif | |
197 | ||
198 | return 0; | |
199 | } | |
200 | ||
201 | static int p2sb_post_bind(struct udevice *dev) | |
202 | { | |
203 | if (spl_phase() > PHASE_TPL && !CONFIG_IS_ENABLED(OF_PLATDATA)) | |
204 | return dm_scan_fdt_dev(dev); | |
205 | ||
206 | return 0; | |
207 | } | |
208 | ||
209 | UCLASS_DRIVER(p2sb) = { | |
210 | .id = UCLASS_P2SB, | |
211 | .name = "p2sb", | |
212 | .per_device_auto_alloc_size = sizeof(struct p2sb_uc_priv), | |
213 | .post_bind = p2sb_post_bind, | |
214 | .child_post_bind = p2sb_child_post_bind, | |
215 | .per_child_platdata_auto_alloc_size = | |
216 | sizeof(struct p2sb_child_platdata), | |
217 | }; |