| 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | /* |
| 3 | * CDX host controller driver for AMD versal-net platform. |
| 4 | * |
| 5 | * Copyright (C) 2022-2023, Advanced Micro Devices, Inc. |
| 6 | */ |
| 7 | |
| 8 | #include <linux/mod_devicetable.h> |
| 9 | #include <linux/platform_device.h> |
| 10 | #include <linux/slab.h> |
| 11 | #include <linux/cdx/cdx_bus.h> |
| 12 | #include <linux/irqdomain.h> |
| 13 | |
| 14 | #include "cdx_controller.h" |
| 15 | #include "../cdx.h" |
| 16 | #include "mcdi_functions.h" |
| 17 | #include "mcdid.h" |
| 18 | |
| 19 | static unsigned int cdx_mcdi_rpc_timeout(struct cdx_mcdi *cdx, unsigned int cmd) |
| 20 | { |
| 21 | return MCDI_RPC_TIMEOUT; |
| 22 | } |
| 23 | |
| 24 | static void cdx_mcdi_request(struct cdx_mcdi *cdx, |
| 25 | const struct cdx_dword *hdr, size_t hdr_len, |
| 26 | const struct cdx_dword *sdu, size_t sdu_len) |
| 27 | { |
| 28 | if (cdx_rpmsg_send(cdx_mcdi: cdx, hdr, hdr_len, sdu, sdu_len)) |
| 29 | dev_err(&cdx->rpdev->dev, "Failed to send rpmsg data\n" ); |
| 30 | } |
| 31 | |
| 32 | static const struct cdx_mcdi_ops mcdi_ops = { |
| 33 | .mcdi_rpc_timeout = cdx_mcdi_rpc_timeout, |
| 34 | .mcdi_request = cdx_mcdi_request, |
| 35 | }; |
| 36 | |
| 37 | static int cdx_bus_enable(struct cdx_controller *cdx, u8 bus_num) |
| 38 | { |
| 39 | return cdx_mcdi_bus_enable(cdx: cdx->priv, bus_num); |
| 40 | } |
| 41 | |
| 42 | static int cdx_bus_disable(struct cdx_controller *cdx, u8 bus_num) |
| 43 | { |
| 44 | return cdx_mcdi_bus_disable(cdx: cdx->priv, bus_num); |
| 45 | } |
| 46 | |
| 47 | void cdx_rpmsg_post_probe(struct cdx_controller *cdx) |
| 48 | { |
| 49 | /* Register CDX controller with CDX bus driver */ |
| 50 | if (cdx_register_controller(cdx)) |
| 51 | dev_err(cdx->dev, "Failed to register CDX controller\n" ); |
| 52 | } |
| 53 | |
| 54 | void cdx_rpmsg_pre_remove(struct cdx_controller *cdx) |
| 55 | { |
| 56 | cdx_unregister_controller(cdx); |
| 57 | cdx_mcdi_wait_for_quiescence(cdx: cdx->priv, MCDI_RPC_TIMEOUT); |
| 58 | } |
| 59 | |
| 60 | static int cdx_configure_device(struct cdx_controller *cdx, |
| 61 | u8 bus_num, u8 dev_num, |
| 62 | struct cdx_device_config *dev_config) |
| 63 | { |
| 64 | u16 msi_index; |
| 65 | int ret = 0; |
| 66 | u32 data; |
| 67 | u64 addr; |
| 68 | |
| 69 | switch (dev_config->type) { |
| 70 | case CDX_DEV_MSI_CONF: |
| 71 | msi_index = dev_config->msi.msi_index; |
| 72 | data = dev_config->msi.data; |
| 73 | addr = dev_config->msi.addr; |
| 74 | |
| 75 | ret = cdx_mcdi_write_msi(cdx: cdx->priv, bus_num, dev_num, msi_vector: msi_index, msi_address: addr, msi_data: data); |
| 76 | break; |
| 77 | case CDX_DEV_RESET_CONF: |
| 78 | ret = cdx_mcdi_reset_device(cdx: cdx->priv, bus_num, dev_num); |
| 79 | break; |
| 80 | case CDX_DEV_BUS_MASTER_CONF: |
| 81 | ret = cdx_mcdi_bus_master_enable(cdx: cdx->priv, bus_num, dev_num, |
| 82 | enable: dev_config->bus_master_enable); |
| 83 | break; |
| 84 | case CDX_DEV_MSI_ENABLE: |
| 85 | ret = cdx_mcdi_msi_enable(cdx: cdx->priv, bus_num, dev_num, enable: dev_config->msi_enable); |
| 86 | break; |
| 87 | default: |
| 88 | ret = -EINVAL; |
| 89 | } |
| 90 | |
| 91 | return ret; |
| 92 | } |
| 93 | |
| 94 | static int cdx_scan_devices(struct cdx_controller *cdx) |
| 95 | { |
| 96 | struct cdx_mcdi *cdx_mcdi = cdx->priv; |
| 97 | u8 bus_num, dev_num, num_cdx_bus; |
| 98 | int ret; |
| 99 | |
| 100 | /* MCDI FW Read: Fetch the number of CDX buses on this controller */ |
| 101 | ret = cdx_mcdi_get_num_buses(cdx: cdx_mcdi); |
| 102 | if (ret < 0) { |
| 103 | dev_err(cdx->dev, |
| 104 | "Get number of CDX buses failed: %d\n" , ret); |
| 105 | return ret; |
| 106 | } |
| 107 | num_cdx_bus = (u8)ret; |
| 108 | |
| 109 | for (bus_num = 0; bus_num < num_cdx_bus; bus_num++) { |
| 110 | struct device *bus_dev; |
| 111 | u8 num_cdx_dev; |
| 112 | |
| 113 | /* Add the bus on cdx subsystem */ |
| 114 | bus_dev = cdx_bus_add(cdx, bus_num); |
| 115 | if (!bus_dev) |
| 116 | continue; |
| 117 | |
| 118 | /* MCDI FW Read: Fetch the number of devices present */ |
| 119 | ret = cdx_mcdi_get_num_devs(cdx: cdx_mcdi, bus_num); |
| 120 | if (ret < 0) { |
| 121 | dev_err(cdx->dev, |
| 122 | "Get devices on CDX bus %d failed: %d\n" , bus_num, ret); |
| 123 | continue; |
| 124 | } |
| 125 | num_cdx_dev = (u8)ret; |
| 126 | |
| 127 | for (dev_num = 0; dev_num < num_cdx_dev; dev_num++) { |
| 128 | struct cdx_dev_params dev_params; |
| 129 | |
| 130 | /* MCDI FW: Get the device config */ |
| 131 | ret = cdx_mcdi_get_dev_config(cdx: cdx_mcdi, bus_num, |
| 132 | dev_num, dev_params: &dev_params); |
| 133 | if (ret) { |
| 134 | dev_err(cdx->dev, |
| 135 | "CDX device config get failed for %d(bus):%d(dev), %d\n" , |
| 136 | bus_num, dev_num, ret); |
| 137 | continue; |
| 138 | } |
| 139 | dev_params.cdx = cdx; |
| 140 | dev_params.parent = bus_dev; |
| 141 | |
| 142 | /* Add the device to the cdx bus */ |
| 143 | ret = cdx_device_add(dev_params: &dev_params); |
| 144 | if (ret) { |
| 145 | dev_err(cdx->dev, "registering cdx dev: %d failed: %d\n" , |
| 146 | dev_num, ret); |
| 147 | continue; |
| 148 | } |
| 149 | |
| 150 | dev_dbg(cdx->dev, "CDX dev: %d on cdx bus: %d created\n" , |
| 151 | dev_num, bus_num); |
| 152 | } |
| 153 | } |
| 154 | |
| 155 | return 0; |
| 156 | } |
| 157 | |
| 158 | static struct cdx_ops cdx_ops = { |
| 159 | .bus_enable = cdx_bus_enable, |
| 160 | .bus_disable = cdx_bus_disable, |
| 161 | .scan = cdx_scan_devices, |
| 162 | .dev_configure = cdx_configure_device, |
| 163 | }; |
| 164 | |
| 165 | static int xlnx_cdx_probe(struct platform_device *pdev) |
| 166 | { |
| 167 | struct cdx_controller *cdx; |
| 168 | struct cdx_mcdi *cdx_mcdi; |
| 169 | int ret; |
| 170 | |
| 171 | cdx_mcdi = kzalloc(sizeof(*cdx_mcdi), GFP_KERNEL); |
| 172 | if (!cdx_mcdi) |
| 173 | return -ENOMEM; |
| 174 | |
| 175 | /* Store the MCDI ops */ |
| 176 | cdx_mcdi->mcdi_ops = &mcdi_ops; |
| 177 | /* MCDI FW: Initialize the FW path */ |
| 178 | ret = cdx_mcdi_init(cdx: cdx_mcdi); |
| 179 | if (ret) { |
| 180 | dev_err_probe(dev: &pdev->dev, err: ret, fmt: "MCDI Initialization failed\n" ); |
| 181 | goto mcdi_init_fail; |
| 182 | } |
| 183 | |
| 184 | cdx = kzalloc(sizeof(*cdx), GFP_KERNEL); |
| 185 | if (!cdx) { |
| 186 | ret = -ENOMEM; |
| 187 | goto cdx_alloc_fail; |
| 188 | } |
| 189 | platform_set_drvdata(pdev, data: cdx); |
| 190 | |
| 191 | cdx->dev = &pdev->dev; |
| 192 | cdx->priv = cdx_mcdi; |
| 193 | cdx->ops = &cdx_ops; |
| 194 | |
| 195 | /* Create MSI domain */ |
| 196 | if (IS_ENABLED(CONFIG_GENERIC_MSI_IRQ)) |
| 197 | cdx->msi_domain = cdx_msi_domain_init(dev: &pdev->dev); |
| 198 | if (!cdx->msi_domain) { |
| 199 | ret = dev_err_probe(dev: &pdev->dev, err: -ENODEV, fmt: "cdx_msi_domain_init() failed" ); |
| 200 | goto cdx_msi_fail; |
| 201 | } |
| 202 | |
| 203 | ret = cdx_setup_rpmsg(pdev); |
| 204 | if (ret) { |
| 205 | dev_err_probe(dev: &pdev->dev, err: ret, fmt: "Failed to register CDX RPMsg transport\n" ); |
| 206 | goto cdx_rpmsg_fail; |
| 207 | } |
| 208 | |
| 209 | return 0; |
| 210 | |
| 211 | cdx_rpmsg_fail: |
| 212 | irq_domain_remove(domain: cdx->msi_domain); |
| 213 | cdx_msi_fail: |
| 214 | kfree(objp: cdx); |
| 215 | cdx_alloc_fail: |
| 216 | cdx_mcdi_finish(cdx: cdx_mcdi); |
| 217 | mcdi_init_fail: |
| 218 | kfree(objp: cdx_mcdi); |
| 219 | |
| 220 | return ret; |
| 221 | } |
| 222 | |
| 223 | static void xlnx_cdx_remove(struct platform_device *pdev) |
| 224 | { |
| 225 | struct cdx_controller *cdx = platform_get_drvdata(pdev); |
| 226 | struct cdx_mcdi *cdx_mcdi = cdx->priv; |
| 227 | |
| 228 | cdx_destroy_rpmsg(pdev); |
| 229 | |
| 230 | irq_domain_remove(domain: cdx->msi_domain); |
| 231 | kfree(objp: cdx); |
| 232 | |
| 233 | cdx_mcdi_finish(cdx: cdx_mcdi); |
| 234 | kfree(objp: cdx_mcdi); |
| 235 | } |
| 236 | |
| 237 | static const struct of_device_id cdx_match_table[] = { |
| 238 | {.compatible = "xlnx,versal-net-cdx" ,}, |
| 239 | { }, |
| 240 | }; |
| 241 | |
| 242 | MODULE_DEVICE_TABLE(of, cdx_match_table); |
| 243 | |
| 244 | static struct platform_driver cdx_pdriver = { |
| 245 | .driver = { |
| 246 | .name = "cdx-controller" , |
| 247 | .of_match_table = cdx_match_table, |
| 248 | }, |
| 249 | .probe = xlnx_cdx_probe, |
| 250 | .remove = xlnx_cdx_remove, |
| 251 | }; |
| 252 | |
| 253 | module_platform_driver(cdx_pdriver); |
| 254 | |
| 255 | MODULE_AUTHOR("AMD Inc." ); |
| 256 | MODULE_DESCRIPTION("CDX controller for AMD devices" ); |
| 257 | MODULE_LICENSE("GPL" ); |
| 258 | MODULE_IMPORT_NS("CDX_BUS_CONTROLLER" ); |
| 259 | |