Jan Dabros | 78d5e9e | 2022-02-08 15:12:18 +0100 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | |
Jan Dabros | 78d5e9e | 2022-02-08 15:12:18 +0100 | [diff] [blame] | 3 | #include <linux/i2c.h> |
Mario Limonciello | 482c84e | 2023-04-14 09:40:08 -0500 | [diff] [blame] | 4 | #include <linux/pci.h> |
Mario Limonciello | 440da73 | 2023-04-14 09:40:07 -0500 | [diff] [blame] | 5 | #include <linux/psp-platform-access.h> |
Mario Limonciello | ae7d45fb | 2023-03-10 15:19:45 -0600 | [diff] [blame] | 6 | #include <linux/psp.h> |
Jan Dabros | 786f01d | 2022-08-12 09:15:26 +0200 | [diff] [blame] | 7 | #include <linux/workqueue.h> |
Jan Dabros | 78d5e9e | 2022-02-08 15:12:18 +0100 | [diff] [blame] | 8 | |
Jan Dabros | 78d5e9e | 2022-02-08 15:12:18 +0100 | [diff] [blame] | 9 | #include "i2c-designware-core.h" |
| 10 | |
Jan Dabros | 786f01d | 2022-08-12 09:15:26 +0200 | [diff] [blame] | 11 | #define PSP_I2C_RESERVATION_TIME_MS 100 |
| 12 | |
Jan Dabros | be18ce1 | 2022-04-28 14:26:51 +0200 | [diff] [blame] | 13 | #define PSP_I2C_REQ_RETRY_CNT 400 |
| 14 | #define PSP_I2C_REQ_RETRY_DELAY_US (25 * USEC_PER_MSEC) |
Jan Dabros | 78d5e9e | 2022-02-08 15:12:18 +0100 | [diff] [blame] | 15 | #define PSP_I2C_REQ_STS_OK 0x0 |
| 16 | #define PSP_I2C_REQ_STS_BUS_BUSY 0x1 |
| 17 | #define PSP_I2C_REQ_STS_INV_PARAM 0x3 |
| 18 | |
Jan Dabros | 78d5e9e | 2022-02-08 15:12:18 +0100 | [diff] [blame] | 19 | enum psp_i2c_req_type { |
| 20 | PSP_I2C_REQ_ACQUIRE, |
| 21 | PSP_I2C_REQ_RELEASE, |
| 22 | PSP_I2C_REQ_MAX |
| 23 | }; |
| 24 | |
| 25 | struct psp_i2c_req { |
| 26 | struct psp_req_buffer_hdr hdr; |
| 27 | enum psp_i2c_req_type type; |
| 28 | }; |
| 29 | |
Jan Dabros | 78d5e9e | 2022-02-08 15:12:18 +0100 | [diff] [blame] | 30 | static DEFINE_MUTEX(psp_i2c_access_mutex); |
| 31 | static unsigned long psp_i2c_sem_acquired; |
Jan Dabros | 78d5e9e | 2022-02-08 15:12:18 +0100 | [diff] [blame] | 32 | static u32 psp_i2c_access_count; |
| 33 | static bool psp_i2c_mbox_fail; |
| 34 | static struct device *psp_i2c_dev; |
| 35 | |
Mario Limonciello | 482c84e | 2023-04-14 09:40:08 -0500 | [diff] [blame] | 36 | static int (*_psp_send_i2c_req)(struct psp_i2c_req *req); |
| 37 | |
Jan Dabros | 78d5e9e | 2022-02-08 15:12:18 +0100 | [diff] [blame] | 38 | /* Helper to verify status returned by PSP */ |
| 39 | static int check_i2c_req_sts(struct psp_i2c_req *req) |
| 40 | { |
Jan Dabros | 17ba1e8 | 2022-02-18 14:33:48 +0100 | [diff] [blame] | 41 | u32 status; |
Jan Dabros | 78d5e9e | 2022-02-08 15:12:18 +0100 | [diff] [blame] | 42 | |
Jan Dabros | 17ba1e8 | 2022-02-18 14:33:48 +0100 | [diff] [blame] | 43 | /* Status field in command-response buffer is updated by PSP */ |
| 44 | status = READ_ONCE(req->hdr.status); |
Jan Dabros | 78d5e9e | 2022-02-08 15:12:18 +0100 | [diff] [blame] | 45 | |
| 46 | switch (status) { |
| 47 | case PSP_I2C_REQ_STS_OK: |
| 48 | return 0; |
| 49 | case PSP_I2C_REQ_STS_BUS_BUSY: |
| 50 | return -EBUSY; |
| 51 | case PSP_I2C_REQ_STS_INV_PARAM: |
| 52 | default: |
| 53 | return -EIO; |
Yang Li | 1e4fe54 | 2022-02-14 20:47:01 +0800 | [diff] [blame] | 54 | } |
Jan Dabros | 78d5e9e | 2022-02-08 15:12:18 +0100 | [diff] [blame] | 55 | } |
| 56 | |
Mario Limonciello | 440da73 | 2023-04-14 09:40:07 -0500 | [diff] [blame] | 57 | /* |
| 58 | * Errors in x86-PSP i2c-arbitration protocol may occur at two levels: |
| 59 | * 1. mailbox communication - PSP is not operational or some IO errors with |
| 60 | * basic communication had happened. |
| 61 | * 2. i2c-requests - PSP refuses to grant i2c arbitration to x86 for too long. |
| 62 | * |
| 63 | * In order to distinguish between these in error handling code all mailbox |
| 64 | * communication errors on the first level (from CCP symbols) will be passed |
| 65 | * up and if -EIO is returned the second level will be checked. |
| 66 | */ |
| 67 | static int psp_send_i2c_req_cezanne(struct psp_i2c_req *req) |
Jan Dabros | 78d5e9e | 2022-02-08 15:12:18 +0100 | [diff] [blame] | 68 | { |
Mario Limonciello | 440da73 | 2023-04-14 09:40:07 -0500 | [diff] [blame] | 69 | int ret; |
Jan Dabros | 78d5e9e | 2022-02-08 15:12:18 +0100 | [diff] [blame] | 70 | |
Mario Limonciello | 440da73 | 2023-04-14 09:40:07 -0500 | [diff] [blame] | 71 | ret = psp_send_platform_access_msg(PSP_I2C_REQ_BUS_CMD, (struct psp_request *)req); |
| 72 | if (ret == -EIO) |
| 73 | return check_i2c_req_sts(req); |
| 74 | |
| 75 | return ret; |
Jan Dabros | 78d5e9e | 2022-02-08 15:12:18 +0100 | [diff] [blame] | 76 | } |
| 77 | |
Mario Limonciello | 482c84e | 2023-04-14 09:40:08 -0500 | [diff] [blame] | 78 | static int psp_send_i2c_req_doorbell(struct psp_i2c_req *req) |
| 79 | { |
| 80 | int ret; |
| 81 | |
| 82 | ret = psp_ring_platform_doorbell(req->type, &req->hdr.status); |
| 83 | if (ret == -EIO) |
| 84 | return check_i2c_req_sts(req); |
| 85 | |
| 86 | return ret; |
| 87 | } |
| 88 | |
Jan Dabros | 78d5e9e | 2022-02-08 15:12:18 +0100 | [diff] [blame] | 89 | static int psp_send_i2c_req(enum psp_i2c_req_type i2c_req_type) |
| 90 | { |
| 91 | struct psp_i2c_req *req; |
| 92 | unsigned long start; |
| 93 | int status, ret; |
| 94 | |
| 95 | /* Allocate command-response buffer */ |
| 96 | req = kzalloc(sizeof(*req), GFP_KERNEL); |
| 97 | if (!req) |
| 98 | return -ENOMEM; |
| 99 | |
Mario Limonciello | 440da73 | 2023-04-14 09:40:07 -0500 | [diff] [blame] | 100 | req->hdr.payload_size = sizeof(*req); |
Jan Dabros | 78d5e9e | 2022-02-08 15:12:18 +0100 | [diff] [blame] | 101 | req->type = i2c_req_type; |
| 102 | |
| 103 | start = jiffies; |
Mario Limonciello | 482c84e | 2023-04-14 09:40:08 -0500 | [diff] [blame] | 104 | ret = read_poll_timeout(_psp_send_i2c_req, status, |
Jan Dabros | 78d5e9e | 2022-02-08 15:12:18 +0100 | [diff] [blame] | 105 | (status != -EBUSY), |
| 106 | PSP_I2C_REQ_RETRY_DELAY_US, |
| 107 | PSP_I2C_REQ_RETRY_CNT * PSP_I2C_REQ_RETRY_DELAY_US, |
| 108 | 0, req); |
Jan Dabros | 3364c0e | 2022-03-14 10:03:23 +0100 | [diff] [blame] | 109 | if (ret) { |
| 110 | dev_err(psp_i2c_dev, "Timed out waiting for PSP to %s I2C bus\n", |
| 111 | (i2c_req_type == PSP_I2C_REQ_ACQUIRE) ? |
| 112 | "release" : "acquire"); |
Jan Dabros | 78d5e9e | 2022-02-08 15:12:18 +0100 | [diff] [blame] | 113 | goto cleanup; |
Jan Dabros | 3364c0e | 2022-03-14 10:03:23 +0100 | [diff] [blame] | 114 | } |
Jan Dabros | 78d5e9e | 2022-02-08 15:12:18 +0100 | [diff] [blame] | 115 | |
| 116 | ret = status; |
Jan Dabros | 3364c0e | 2022-03-14 10:03:23 +0100 | [diff] [blame] | 117 | if (ret) { |
| 118 | dev_err(psp_i2c_dev, "PSP communication error\n"); |
Jan Dabros | 78d5e9e | 2022-02-08 15:12:18 +0100 | [diff] [blame] | 119 | goto cleanup; |
Jan Dabros | 3364c0e | 2022-03-14 10:03:23 +0100 | [diff] [blame] | 120 | } |
Jan Dabros | 78d5e9e | 2022-02-08 15:12:18 +0100 | [diff] [blame] | 121 | |
| 122 | dev_dbg(psp_i2c_dev, "Request accepted by PSP after %ums\n", |
| 123 | jiffies_to_msecs(jiffies - start)); |
| 124 | |
| 125 | cleanup: |
Jan Dabros | 3364c0e | 2022-03-14 10:03:23 +0100 | [diff] [blame] | 126 | if (ret) { |
| 127 | dev_err(psp_i2c_dev, "Assume i2c bus is for exclusive host usage\n"); |
| 128 | psp_i2c_mbox_fail = true; |
| 129 | } |
| 130 | |
Jan Dabros | 78d5e9e | 2022-02-08 15:12:18 +0100 | [diff] [blame] | 131 | kfree(req); |
| 132 | return ret; |
| 133 | } |
| 134 | |
Jan Dabros | 786f01d | 2022-08-12 09:15:26 +0200 | [diff] [blame] | 135 | static void release_bus(void) |
| 136 | { |
| 137 | int status; |
| 138 | |
| 139 | if (!psp_i2c_sem_acquired) |
| 140 | return; |
| 141 | |
| 142 | status = psp_send_i2c_req(PSP_I2C_REQ_RELEASE); |
| 143 | if (status) |
| 144 | return; |
| 145 | |
| 146 | dev_dbg(psp_i2c_dev, "PSP semaphore held for %ums\n", |
| 147 | jiffies_to_msecs(jiffies - psp_i2c_sem_acquired)); |
| 148 | |
| 149 | psp_i2c_sem_acquired = 0; |
| 150 | } |
| 151 | |
| 152 | static void psp_release_i2c_bus_deferred(struct work_struct *work) |
| 153 | { |
| 154 | mutex_lock(&psp_i2c_access_mutex); |
| 155 | |
| 156 | /* |
| 157 | * If there is any pending transaction, cannot release the bus here. |
| 158 | * psp_release_i2c_bus will take care of this later. |
| 159 | */ |
| 160 | if (psp_i2c_access_count) |
| 161 | goto cleanup; |
| 162 | |
| 163 | release_bus(); |
| 164 | |
| 165 | cleanup: |
| 166 | mutex_unlock(&psp_i2c_access_mutex); |
| 167 | } |
| 168 | static DECLARE_DELAYED_WORK(release_queue, psp_release_i2c_bus_deferred); |
| 169 | |
Jan Dabros | 78d5e9e | 2022-02-08 15:12:18 +0100 | [diff] [blame] | 170 | static int psp_acquire_i2c_bus(void) |
| 171 | { |
| 172 | int status; |
| 173 | |
| 174 | mutex_lock(&psp_i2c_access_mutex); |
| 175 | |
| 176 | /* Return early if mailbox malfunctioned */ |
| 177 | if (psp_i2c_mbox_fail) |
| 178 | goto cleanup; |
| 179 | |
Jan Dabros | 786f01d | 2022-08-12 09:15:26 +0200 | [diff] [blame] | 180 | psp_i2c_access_count++; |
| 181 | |
Jan Dabros | 78d5e9e | 2022-02-08 15:12:18 +0100 | [diff] [blame] | 182 | /* |
Jan Dabros | 786f01d | 2022-08-12 09:15:26 +0200 | [diff] [blame] | 183 | * No need to request bus arbitration once we are inside semaphore |
| 184 | * reservation period. |
Jan Dabros | 78d5e9e | 2022-02-08 15:12:18 +0100 | [diff] [blame] | 185 | */ |
Jan Dabros | 786f01d | 2022-08-12 09:15:26 +0200 | [diff] [blame] | 186 | if (psp_i2c_sem_acquired) |
Jan Dabros | 78d5e9e | 2022-02-08 15:12:18 +0100 | [diff] [blame] | 187 | goto cleanup; |
Jan Dabros | 78d5e9e | 2022-02-08 15:12:18 +0100 | [diff] [blame] | 188 | |
| 189 | status = psp_send_i2c_req(PSP_I2C_REQ_ACQUIRE); |
Jan Dabros | 3364c0e | 2022-03-14 10:03:23 +0100 | [diff] [blame] | 190 | if (status) |
Jan Dabros | 78d5e9e | 2022-02-08 15:12:18 +0100 | [diff] [blame] | 191 | goto cleanup; |
Jan Dabros | 78d5e9e | 2022-02-08 15:12:18 +0100 | [diff] [blame] | 192 | |
| 193 | psp_i2c_sem_acquired = jiffies; |
Jan Dabros | 786f01d | 2022-08-12 09:15:26 +0200 | [diff] [blame] | 194 | |
| 195 | schedule_delayed_work(&release_queue, |
| 196 | msecs_to_jiffies(PSP_I2C_RESERVATION_TIME_MS)); |
Jan Dabros | 78d5e9e | 2022-02-08 15:12:18 +0100 | [diff] [blame] | 197 | |
| 198 | /* |
| 199 | * In case of errors with PSP arbitrator psp_i2c_mbox_fail variable is |
| 200 | * set above. As a consequence consecutive calls to acquire will bypass |
| 201 | * communication with PSP. At any case i2c bus is granted to the caller, |
| 202 | * thus always return success. |
| 203 | */ |
| 204 | cleanup: |
| 205 | mutex_unlock(&psp_i2c_access_mutex); |
| 206 | return 0; |
| 207 | } |
| 208 | |
| 209 | static void psp_release_i2c_bus(void) |
| 210 | { |
Jan Dabros | 78d5e9e | 2022-02-08 15:12:18 +0100 | [diff] [blame] | 211 | mutex_lock(&psp_i2c_access_mutex); |
| 212 | |
| 213 | /* Return early if mailbox was malfunctional */ |
| 214 | if (psp_i2c_mbox_fail) |
| 215 | goto cleanup; |
| 216 | |
| 217 | /* |
| 218 | * If we are last owner of PSP semaphore, need to release aribtration |
| 219 | * via mailbox. |
| 220 | */ |
| 221 | psp_i2c_access_count--; |
| 222 | if (psp_i2c_access_count) |
| 223 | goto cleanup; |
| 224 | |
Jan Dabros | 786f01d | 2022-08-12 09:15:26 +0200 | [diff] [blame] | 225 | /* |
| 226 | * Send a release command to PSP if the semaphore reservation timeout |
| 227 | * elapsed but x86 still owns the controller. |
| 228 | */ |
| 229 | if (!delayed_work_pending(&release_queue)) |
| 230 | release_bus(); |
Jan Dabros | 78d5e9e | 2022-02-08 15:12:18 +0100 | [diff] [blame] | 231 | |
| 232 | cleanup: |
| 233 | mutex_unlock(&psp_i2c_access_mutex); |
| 234 | } |
| 235 | |
| 236 | /* |
| 237 | * Locking methods are based on the default implementation from |
| 238 | * drivers/i2c/i2c-core-base.c, but with psp acquire and release operations |
| 239 | * added. With this in place we can ensure that i2c clients on the bus shared |
| 240 | * with psp are able to lock HW access to the bus for arbitrary number of |
| 241 | * operations - that is e.g. write-wait-read. |
| 242 | */ |
| 243 | static void i2c_adapter_dw_psp_lock_bus(struct i2c_adapter *adapter, |
| 244 | unsigned int flags) |
| 245 | { |
| 246 | psp_acquire_i2c_bus(); |
| 247 | rt_mutex_lock_nested(&adapter->bus_lock, i2c_adapter_depth(adapter)); |
| 248 | } |
| 249 | |
| 250 | static int i2c_adapter_dw_psp_trylock_bus(struct i2c_adapter *adapter, |
| 251 | unsigned int flags) |
| 252 | { |
| 253 | int ret; |
| 254 | |
| 255 | ret = rt_mutex_trylock(&adapter->bus_lock); |
| 256 | if (ret) |
| 257 | return ret; |
| 258 | |
| 259 | psp_acquire_i2c_bus(); |
| 260 | |
| 261 | return ret; |
| 262 | } |
| 263 | |
| 264 | static void i2c_adapter_dw_psp_unlock_bus(struct i2c_adapter *adapter, |
| 265 | unsigned int flags) |
| 266 | { |
| 267 | psp_release_i2c_bus(); |
| 268 | rt_mutex_unlock(&adapter->bus_lock); |
| 269 | } |
| 270 | |
| 271 | static const struct i2c_lock_operations i2c_dw_psp_lock_ops = { |
| 272 | .lock_bus = i2c_adapter_dw_psp_lock_bus, |
| 273 | .trylock_bus = i2c_adapter_dw_psp_trylock_bus, |
| 274 | .unlock_bus = i2c_adapter_dw_psp_unlock_bus, |
| 275 | }; |
| 276 | |
| 277 | int i2c_dw_amdpsp_probe_lock_support(struct dw_i2c_dev *dev) |
| 278 | { |
Mario Limonciello | 482c84e | 2023-04-14 09:40:08 -0500 | [diff] [blame] | 279 | struct pci_dev *rdev; |
| 280 | |
Mario Limonciello | 440da73 | 2023-04-14 09:40:07 -0500 | [diff] [blame] | 281 | if (!IS_REACHABLE(CONFIG_CRYPTO_DEV_CCP_DD)) |
| 282 | return -ENODEV; |
Jan Dabros | 78d5e9e | 2022-02-08 15:12:18 +0100 | [diff] [blame] | 283 | |
| 284 | if (!dev) |
| 285 | return -ENODEV; |
| 286 | |
| 287 | if (!(dev->flags & ARBITRATION_SEMAPHORE)) |
| 288 | return -ENODEV; |
| 289 | |
| 290 | /* Allow to bind only one instance of a driver */ |
| 291 | if (psp_i2c_dev) |
| 292 | return -EEXIST; |
| 293 | |
Mario Limonciello | 482c84e | 2023-04-14 09:40:08 -0500 | [diff] [blame] | 294 | /* Cezanne uses platform mailbox, Mendocino and later use doorbell */ |
| 295 | rdev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(0, 0)); |
| 296 | if (rdev->device == 0x1630) |
| 297 | _psp_send_i2c_req = psp_send_i2c_req_cezanne; |
| 298 | else |
| 299 | _psp_send_i2c_req = psp_send_i2c_req_doorbell; |
| 300 | pci_dev_put(rdev); |
| 301 | |
Mario Limonciello | 440da73 | 2023-04-14 09:40:07 -0500 | [diff] [blame] | 302 | if (psp_check_platform_access_status()) |
| 303 | return -EPROBE_DEFER; |
Jan Dabros | 78d5e9e | 2022-02-08 15:12:18 +0100 | [diff] [blame] | 304 | |
Mario Limonciello | 440da73 | 2023-04-14 09:40:07 -0500 | [diff] [blame] | 305 | psp_i2c_dev = dev->dev; |
Jan Dabros | 78d5e9e | 2022-02-08 15:12:18 +0100 | [diff] [blame] | 306 | |
| 307 | dev_info(psp_i2c_dev, "I2C bus managed by AMD PSP\n"); |
| 308 | |
| 309 | /* |
| 310 | * Install global locking callbacks for adapter as well as internal i2c |
| 311 | * controller locks. |
| 312 | */ |
| 313 | dev->adapter.lock_ops = &i2c_dw_psp_lock_ops; |
| 314 | dev->acquire_lock = psp_acquire_i2c_bus; |
| 315 | dev->release_lock = psp_release_i2c_bus; |
| 316 | |
| 317 | return 0; |
| 318 | } |