Commit | Line | Data |
---|---|---|
b886d83c | 1 | // SPDX-License-Identifier: GPL-2.0-only |
0edbfea5 CR |
2 | /* |
3 | * Copyright (C) 2015 Infineon Technologies AG | |
4 | * Copyright (C) 2016 STMicroelectronics SAS | |
5 | * | |
6 | * Authors: | |
7 | * Peter Huewe <peter.huewe@infineon.com> | |
8 | * Christophe Ricard <christophe-h.ricard@st.com> | |
9 | * | |
10 | * Maintained by: <tpmdd-devel@lists.sourceforge.net> | |
11 | * | |
12 | * Device driver for TCG/TCPA TPM (trusted platform module). | |
13 | * Specifications at www.trustedcomputinggroup.org | |
14 | * | |
15 | * This device driver implements the TPM interface as defined in | |
16 | * the TCG TPM Interface Spec version 1.3, revision 27 via _raw/native | |
17 | * SPI access_. | |
18 | * | |
19 | * It is based on the original tpm_tis device driver from Leendert van | |
20 | * Dorn and Kyleen Hall and Jarko Sakkinnen. | |
0edbfea5 CR |
21 | */ |
22 | ||
86cd45e0 | 23 | #include <linux/acpi.h> |
797c0113 | 24 | #include <linux/completion.h> |
0edbfea5 | 25 | #include <linux/init.h> |
86cd45e0 SB |
26 | #include <linux/interrupt.h> |
27 | #include <linux/kernel.h> | |
0edbfea5 | 28 | #include <linux/module.h> |
0edbfea5 | 29 | #include <linux/slab.h> |
0edbfea5 | 30 | |
797c0113 | 31 | #include <linux/of_device.h> |
86cd45e0 | 32 | #include <linux/spi/spi.h> |
0edbfea5 | 33 | #include <linux/tpm.h> |
86cd45e0 | 34 | |
0edbfea5 CR |
35 | #include "tpm.h" |
36 | #include "tpm_tis_core.h" | |
797c0113 | 37 | #include "tpm_tis_spi.h" |
0edbfea5 CR |
38 | |
39 | #define MAX_SPI_FRAMESIZE 64 | |
40 | ||
8ab5e82a SB |
41 | /* |
42 | * TCG SPI flow control is documented in section 6.4 of the spec[1]. In short, | |
43 | * keep trying to read from the device until MISO goes high indicating the | |
44 | * wait state has ended. | |
45 | * | |
46 | * [1] https://trustedcomputinggroup.org/resource/pc-client-platform-tpm-profile-ptp-specification/ | |
47 | */ | |
48 | static int tpm_tis_spi_flow_control(struct tpm_tis_spi_phy *phy, | |
49 | struct spi_transfer *spi_xfer) | |
50 | { | |
51 | struct spi_message m; | |
52 | int ret, i; | |
53 | ||
54 | if ((phy->iobuf[3] & 0x01) == 0) { | |
55 | // handle SPI wait states | |
8ab5e82a SB |
56 | for (i = 0; i < TPM_RETRY; i++) { |
57 | spi_xfer->len = 1; | |
58 | spi_message_init(&m); | |
59 | spi_message_add_tail(spi_xfer, &m); | |
60 | ret = spi_sync_locked(phy->spi_device, &m); | |
61 | if (ret < 0) | |
62 | return ret; | |
63 | if (phy->iobuf[0] & 0x01) | |
64 | break; | |
65 | } | |
66 | ||
67 | if (i == TPM_RETRY) | |
68 | return -ETIMEDOUT; | |
69 | } | |
70 | ||
71 | return 0; | |
72 | } | |
73 | ||
797c0113 AP |
74 | int tpm_tis_spi_transfer(struct tpm_tis_data *data, u32 addr, u16 len, |
75 | u8 *in, const u8 *out) | |
0edbfea5 CR |
76 | { |
77 | struct tpm_tis_spi_phy *phy = to_tpm_tis_spi_phy(data); | |
591e48c2 | 78 | int ret = 0; |
0edbfea5 | 79 | struct spi_message m; |
591e48c2 PH |
80 | struct spi_transfer spi_xfer; |
81 | u8 transfer_len; | |
0edbfea5 | 82 | |
591e48c2 | 83 | spi_bus_lock(phy->spi_device->master); |
0edbfea5 | 84 | |
591e48c2 PH |
85 | while (len) { |
86 | transfer_len = min_t(u16, len, MAX_SPI_FRAMESIZE); | |
0edbfea5 | 87 | |
6b3a1317 AS |
88 | phy->iobuf[0] = (in ? 0x80 : 0) | (transfer_len - 1); |
89 | phy->iobuf[1] = 0xd4; | |
90 | phy->iobuf[2] = addr >> 8; | |
91 | phy->iobuf[3] = addr; | |
0edbfea5 | 92 | |
591e48c2 | 93 | memset(&spi_xfer, 0, sizeof(spi_xfer)); |
6b3a1317 AS |
94 | spi_xfer.tx_buf = phy->iobuf; |
95 | spi_xfer.rx_buf = phy->iobuf; | |
591e48c2 PH |
96 | spi_xfer.len = 4; |
97 | spi_xfer.cs_change = 1; | |
98 | ||
99 | spi_message_init(&m); | |
100 | spi_message_add_tail(&spi_xfer, &m); | |
101 | ret = spi_sync_locked(phy->spi_device, &m); | |
102 | if (ret < 0) | |
103 | goto exit; | |
104 | ||
eac9347d DA |
105 | /* Flow control transfers are receive only */ |
106 | spi_xfer.tx_buf = NULL; | |
8ab5e82a SB |
107 | ret = phy->flow_control(phy, &spi_xfer); |
108 | if (ret < 0) | |
109 | goto exit; | |
e110cc69 | 110 | |
591e48c2 PH |
111 | spi_xfer.cs_change = 0; |
112 | spi_xfer.len = transfer_len; | |
ad28db73 AA |
113 | spi_xfer.delay.value = 5; |
114 | spi_xfer.delay.unit = SPI_DELAY_UNIT_USECS; | |
6b3a1317 | 115 | |
eac9347d DA |
116 | if (out) { |
117 | spi_xfer.tx_buf = phy->iobuf; | |
6b3a1317 AS |
118 | spi_xfer.rx_buf = NULL; |
119 | memcpy(phy->iobuf, out, transfer_len); | |
120 | out += transfer_len; | |
121 | } | |
975094dd | 122 | |
591e48c2 PH |
123 | spi_message_init(&m); |
124 | spi_message_add_tail(&spi_xfer, &m); | |
797c0113 | 125 | reinit_completion(&phy->ready); |
591e48c2 PH |
126 | ret = spi_sync_locked(phy->spi_device, &m); |
127 | if (ret < 0) | |
128 | goto exit; | |
f848f214 | 129 | |
6b3a1317 AS |
130 | if (in) { |
131 | memcpy(in, phy->iobuf, transfer_len); | |
c37fbc09 | 132 | in += transfer_len; |
6b3a1317 AS |
133 | } |
134 | ||
135 | len -= transfer_len; | |
f848f214 | 136 | } |
0edbfea5 | 137 | |
0edbfea5 CR |
138 | exit: |
139 | spi_bus_unlock(phy->spi_device->master); | |
140 | return ret; | |
141 | } | |
142 | ||
f848f214 | 143 | static int tpm_tis_spi_read_bytes(struct tpm_tis_data *data, u32 addr, |
6422cbd3 | 144 | u16 len, u8 *result, enum tpm_tis_io_mode io_mode) |
f848f214 | 145 | { |
c37fbc09 | 146 | return tpm_tis_spi_transfer(data, addr, len, result, NULL); |
f848f214 PH |
147 | } |
148 | ||
0edbfea5 | 149 | static int tpm_tis_spi_write_bytes(struct tpm_tis_data *data, u32 addr, |
6422cbd3 | 150 | u16 len, const u8 *value, enum tpm_tis_io_mode io_mode) |
0edbfea5 | 151 | { |
c37fbc09 | 152 | return tpm_tis_spi_transfer(data, addr, len, NULL, value); |
0edbfea5 CR |
153 | } |
154 | ||
797c0113 AP |
155 | int tpm_tis_spi_init(struct spi_device *spi, struct tpm_tis_spi_phy *phy, |
156 | int irq, const struct tpm_tis_phy_ops *phy_ops) | |
157 | { | |
158 | phy->iobuf = devm_kmalloc(&spi->dev, MAX_SPI_FRAMESIZE, GFP_KERNEL); | |
159 | if (!phy->iobuf) | |
160 | return -ENOMEM; | |
161 | ||
162 | phy->spi_device = spi; | |
163 | ||
164 | return tpm_tis_core_init(&spi->dev, &phy->priv, irq, phy_ops, NULL); | |
165 | } | |
166 | ||
0edbfea5 CR |
167 | static const struct tpm_tis_phy_ops tpm_spi_phy_ops = { |
168 | .read_bytes = tpm_tis_spi_read_bytes, | |
169 | .write_bytes = tpm_tis_spi_write_bytes, | |
0edbfea5 CR |
170 | }; |
171 | ||
172 | static int tpm_tis_spi_probe(struct spi_device *dev) | |
173 | { | |
174 | struct tpm_tis_spi_phy *phy; | |
1a339b65 | 175 | int irq; |
0edbfea5 CR |
176 | |
177 | phy = devm_kzalloc(&dev->dev, sizeof(struct tpm_tis_spi_phy), | |
178 | GFP_KERNEL); | |
179 | if (!phy) | |
180 | return -ENOMEM; | |
181 | ||
8ab5e82a | 182 | phy->flow_control = tpm_tis_spi_flow_control; |
6b3a1317 | 183 | |
1a339b65 LW |
184 | /* If the SPI device has an IRQ then use that */ |
185 | if (dev->irq > 0) | |
186 | irq = dev->irq; | |
187 | else | |
188 | irq = -1; | |
189 | ||
797c0113 AP |
190 | init_completion(&phy->ready); |
191 | return tpm_tis_spi_init(dev, phy, irq, &tpm_spi_phy_ops); | |
192 | } | |
193 | ||
194 | typedef int (*tpm_tis_spi_probe_func)(struct spi_device *); | |
195 | ||
196 | static int tpm_tis_spi_driver_probe(struct spi_device *spi) | |
197 | { | |
198 | const struct spi_device_id *spi_dev_id = spi_get_device_id(spi); | |
199 | tpm_tis_spi_probe_func probe_func; | |
200 | ||
201 | probe_func = of_device_get_match_data(&spi->dev); | |
114e4337 LZ |
202 | if (!probe_func) { |
203 | if (spi_dev_id) { | |
204 | probe_func = (tpm_tis_spi_probe_func)spi_dev_id->driver_data; | |
205 | if (!probe_func) | |
206 | return -ENODEV; | |
207 | } else | |
208 | probe_func = tpm_tis_spi_probe; | |
209 | } | |
797c0113 AP |
210 | |
211 | return probe_func(spi); | |
0edbfea5 CR |
212 | } |
213 | ||
797c0113 | 214 | static SIMPLE_DEV_PM_OPS(tpm_tis_pm, tpm_pm_suspend, tpm_tis_spi_resume); |
0edbfea5 | 215 | |
a0386bba | 216 | static void tpm_tis_spi_remove(struct spi_device *dev) |
0edbfea5 CR |
217 | { |
218 | struct tpm_chip *chip = spi_get_drvdata(dev); | |
219 | ||
220 | tpm_chip_unregister(chip); | |
221 | tpm_tis_remove(chip); | |
0edbfea5 CR |
222 | } |
223 | ||
224 | static const struct spi_device_id tpm_tis_spi_id[] = { | |
c46ed228 JMC |
225 | { "st33htpm-spi", (unsigned long)tpm_tis_spi_probe }, |
226 | { "slb9670", (unsigned long)tpm_tis_spi_probe }, | |
797c0113 | 227 | { "tpm_tis_spi", (unsigned long)tpm_tis_spi_probe }, |
7eba41fe | 228 | { "tpm_tis-spi", (unsigned long)tpm_tis_spi_probe }, |
797c0113 | 229 | { "cr50", (unsigned long)cr50_spi_probe }, |
0edbfea5 CR |
230 | {} |
231 | }; | |
232 | MODULE_DEVICE_TABLE(spi, tpm_tis_spi_id); | |
233 | ||
3fb29a23 | 234 | static const struct of_device_id of_tis_spi_match[] __maybe_unused = { |
797c0113 AP |
235 | { .compatible = "st,st33htpm-spi", .data = tpm_tis_spi_probe }, |
236 | { .compatible = "infineon,slb9670", .data = tpm_tis_spi_probe }, | |
237 | { .compatible = "tcg,tpm_tis-spi", .data = tpm_tis_spi_probe }, | |
238 | { .compatible = "google,cr50", .data = cr50_spi_probe }, | |
0edbfea5 CR |
239 | {} |
240 | }; | |
241 | MODULE_DEVICE_TABLE(of, of_tis_spi_match); | |
242 | ||
3fb29a23 | 243 | static const struct acpi_device_id acpi_tis_spi_match[] __maybe_unused = { |
0edbfea5 CR |
244 | {"SMO0768", 0}, |
245 | {} | |
246 | }; | |
247 | MODULE_DEVICE_TABLE(acpi, acpi_tis_spi_match); | |
248 | ||
249 | static struct spi_driver tpm_tis_spi_driver = { | |
250 | .driver = { | |
0edbfea5 CR |
251 | .name = "tpm_tis_spi", |
252 | .pm = &tpm_tis_pm, | |
253 | .of_match_table = of_match_ptr(of_tis_spi_match), | |
254 | .acpi_match_table = ACPI_PTR(acpi_tis_spi_match), | |
7187bf7f | 255 | .probe_type = PROBE_PREFER_ASYNCHRONOUS, |
0edbfea5 | 256 | }, |
797c0113 | 257 | .probe = tpm_tis_spi_driver_probe, |
0edbfea5 CR |
258 | .remove = tpm_tis_spi_remove, |
259 | .id_table = tpm_tis_spi_id, | |
260 | }; | |
261 | module_spi_driver(tpm_tis_spi_driver); | |
262 | ||
263 | MODULE_DESCRIPTION("TPM Driver for native SPI access"); | |
264 | MODULE_LICENSE("GPL"); |