| 1 | |
| 2 | |
| 3 | * Microchip PolarFire SoC (MPFS) system controller driver
|
| 4 | *
|
| 5 | * Copyright (c) 2020-2021 Microchip Corporation. All rights reserved.
|
| 6 | *
|
| 7 | * Author: Conor Dooley <[email protected]>
|
| 8 | *
|
| 9 |
|
| 10 |
|
| 11 | #include <linux/slab.h>
|
| 12 | #include <linux/kref.h>
|
| 13 | #include <linux/module.h>
|
| 14 | #include <linux/jiffies.h>
|
| 15 | #include <linux/mtd/mtd.h>
|
| 16 | #include <linux/spi/spi.h>
|
| 17 | #include <linux/interrupt.h>
|
| 18 | #include <linux/of.h>
|
| 19 | #include <linux/mailbox_client.h>
|
| 20 | #include <linux/platform_device.h>
|
| 21 | #include <soc/microchip/mpfs.h>
|
| 22 |
|
| 23 | |
| 24 | * This timeout must be long, as some services (example: image authentication)
|
| 25 | * take significant time to complete
|
| 26 |
|
| 27 | #define MPFS_SYS_CTRL_TIMEOUT_MS 30000
|
| 28 |
|
| 29 | static DEFINE_MUTEX(transaction_lock);
|
| 30 |
|
| 31 | struct mpfs_sys_controller {
|
| 32 | struct mbox_client client;
|
| 33 | struct mbox_chan *chan;
|
| 34 | struct completion c;
|
| 35 | struct mtd_info *flash;
|
| 36 | struct kref consumers;
|
| 37 | };
|
| 38 |
|
| 39 | struct mpfs_syscon_config {
|
| 40 | unsigned int nb_subdevs;
|
| 41 | struct platform_device *subdevs;
|
| 42 | };
|
| 43 |
|
| 44 | int mpfs_blocking_transaction(struct mpfs_sys_controller *sys_controller, struct mpfs_mss_msg *msg)
|
| 45 | {
|
| 46 | unsigned long timeout = msecs_to_jiffies(MPFS_SYS_CTRL_TIMEOUT_MS);
|
| 47 | int ret;
|
| 48 |
|
| 49 | ret = mutex_lock_interruptible(&transaction_lock);
|
| 50 | if (ret)
|
| 51 | return ret;
|
| 52 |
|
| 53 | reinit_completion(&sys_controller->c);
|
| 54 |
|
| 55 | ret = mbox_send_message(sys_controller->chan, msg);
|
| 56 | if (ret < 0) {
|
| 57 | dev_warn(sys_controller->client.dev, "MPFS sys controller service timeout\n");
|
| 58 | goto out;
|
| 59 | }
|
| 60 |
|
| 61 | |
| 62 | * Unfortunately, the system controller will only deliver an interrupt
|
| 63 | * if a service succeeds. mbox_send_message() will block until the busy
|
| 64 | * flag is gone. If the busy flag is gone but no interrupt has arrived
|
| 65 | * to trigger the rx callback then the service can be deemed to have
|
| 66 | * failed.
|
| 67 | * The caller can then interrogate msg::response::resp_status to
|
| 68 | * determine the cause of the failure.
|
| 69 | * mbox_send_message() returns positive integers in the success path, so
|
| 70 | * ret needs to be cleared if we do get an interrupt.
|
| 71 |
|
| 72 | if (!wait_for_completion_timeout(&sys_controller->c, timeout)) {
|
| 73 | ret = -EBADMSG;
|
| 74 | dev_warn(sys_controller->client.dev,
|
| 75 | "MPFS sys controller service failed with status: %d\n",
|
| 76 | msg->response->resp_status);
|
| 77 | } else {
|
| 78 | ret = 0;
|
| 79 | }
|
| 80 |
|
| 81 | out:
|
| 82 | mutex_unlock(&transaction_lock);
|
| 83 |
|
| 84 | return ret;
|
| 85 | }
|
| 86 | EXPORT_SYMBOL(mpfs_blocking_transaction);
|
| 87 |
|
| 88 | static void mpfs_sys_controller_rx_callback(struct mbox_client *client, void *msg)
|
| 89 | {
|
| 90 | struct mpfs_sys_controller *sys_controller =
|
| 91 | container_of(client, struct mpfs_sys_controller, client);
|
| 92 |
|
| 93 | complete(&sys_controller->c);
|
| 94 | }
|
| 95 |
|
| 96 | static void mpfs_sys_controller_delete(struct kref *kref)
|
| 97 | {
|
| 98 | struct mpfs_sys_controller *sys_controller =
|
| 99 | container_of(kref, struct mpfs_sys_controller, consumers);
|
| 100 |
|
| 101 | mbox_free_channel(sys_controller->chan);
|
| 102 | kfree(sys_controller);
|
| 103 | }
|
| 104 |
|
| 105 | static void mpfs_sys_controller_put(void *data)
|
| 106 | {
|
| 107 | struct mpfs_sys_controller *sys_controller = data;
|
| 108 |
|
| 109 | kref_put(&sys_controller->consumers, mpfs_sys_controller_delete);
|
| 110 | }
|
| 111 |
|
| 112 | struct mtd_info *mpfs_sys_controller_get_flash(struct mpfs_sys_controller *mpfs_client)
|
| 113 | {
|
| 114 | return mpfs_client->flash;
|
| 115 | }
|
| 116 | EXPORT_SYMBOL(mpfs_sys_controller_get_flash);
|
| 117 |
|
| 118 | static int mpfs_sys_controller_probe(struct platform_device *pdev)
|
| 119 | {
|
| 120 | struct device *dev = &pdev->dev;
|
| 121 | struct mpfs_sys_controller *sys_controller;
|
| 122 | struct mpfs_syscon_config *of_data;
|
| 123 | struct device_node *np;
|
| 124 | int i, ret;
|
| 125 |
|
| 126 | sys_controller = kzalloc_obj(*sys_controller);
|
| 127 | if (!sys_controller)
|
| 128 | return -ENOMEM;
|
| 129 |
|
| 130 | np = of_parse_phandle(dev->of_node, "microchip,bitstream-flash", 0);
|
| 131 | if (!np)
|
| 132 | goto no_flash;
|
| 133 |
|
| 134 | sys_controller->flash = of_get_mtd_device_by_node(np);
|
| 135 | of_node_put(np);
|
| 136 | if (IS_ERR(sys_controller->flash)) {
|
| 137 | ret = dev_err_probe(dev, PTR_ERR(sys_controller->flash), "Failed to get flash\n");
|
| 138 | goto out_free;
|
| 139 | }
|
| 140 |
|
| 141 | no_flash:
|
| 142 | sys_controller->client.dev = dev;
|
| 143 | sys_controller->client.rx_callback = mpfs_sys_controller_rx_callback;
|
| 144 | sys_controller->client.tx_block = 1U;
|
| 145 | sys_controller->client.tx_tout = msecs_to_jiffies(MPFS_SYS_CTRL_TIMEOUT_MS);
|
| 146 |
|
| 147 | sys_controller->chan = mbox_request_channel(&sys_controller->client, 0);
|
| 148 | if (IS_ERR(sys_controller->chan)) {
|
| 149 | ret = dev_err_probe(dev, PTR_ERR(sys_controller->chan),
|
| 150 | "Failed to get mbox channel\n");
|
| 151 | goto out_free;
|
| 152 | }
|
| 153 |
|
| 154 | init_completion(&sys_controller->c);
|
| 155 | kref_init(&sys_controller->consumers);
|
| 156 |
|
| 157 | platform_set_drvdata(pdev, sys_controller);
|
| 158 |
|
| 159 | of_data = (struct mpfs_syscon_config *) device_get_match_data(dev);
|
| 160 | if (!of_data) {
|
| 161 | ret = dev_err_probe(dev, -EINVAL, "Error getting match data\n");
|
| 162 | goto out_free_channel;
|
| 163 | }
|
| 164 |
|
| 165 | for (i = 0; i < of_data->nb_subdevs; i++) {
|
| 166 | of_data->subdevs[i].dev.parent = dev;
|
| 167 | if (platform_device_register(&of_data->subdevs[i]))
|
| 168 | dev_warn(dev, "Error registering sub device %s\n",
|
| 169 | of_data->subdevs[i].name);
|
| 170 | }
|
| 171 |
|
| 172 | dev_info(&pdev->dev, "Registered MPFS system controller\n");
|
| 173 |
|
| 174 | return 0;
|
| 175 |
|
| 176 | out_free_channel:
|
| 177 | mbox_free_channel(sys_controller->chan);
|
| 178 | out_free:
|
| 179 | kfree(sys_controller);
|
| 180 | return ret;
|
| 181 | }
|
| 182 |
|
| 183 | static void mpfs_sys_controller_remove(struct platform_device *pdev)
|
| 184 | {
|
| 185 | struct mpfs_sys_controller *sys_controller = platform_get_drvdata(pdev);
|
| 186 |
|
| 187 | mpfs_sys_controller_put(sys_controller);
|
| 188 | }
|
| 189 |
|
| 190 | static struct platform_device mpfs_subdevs[] = {
|
| 191 | {
|
| 192 | .name = "mpfs-rng",
|
| 193 | .id = -1,
|
| 194 | },
|
| 195 | {
|
| 196 | .name = "mpfs-generic-service",
|
| 197 | .id = -1,
|
| 198 | },
|
| 199 | {
|
| 200 | .name = "mpfs-auto-update",
|
| 201 | .id = -1,
|
| 202 | },
|
| 203 | };
|
| 204 |
|
| 205 | static struct platform_device pic64gx_subdevs[] = {
|
| 206 | {
|
| 207 | .name = "mpfs-rng",
|
| 208 | .id = -1,
|
| 209 | },
|
| 210 | {
|
| 211 | .name = "mpfs-generic-service",
|
| 212 | .id = -1,
|
| 213 | },
|
| 214 | };
|
| 215 |
|
| 216 | static const struct mpfs_syscon_config mpfs_config = {
|
| 217 | .nb_subdevs = ARRAY_SIZE(mpfs_subdevs),
|
| 218 | .subdevs = mpfs_subdevs,
|
| 219 | };
|
| 220 |
|
| 221 | static const struct mpfs_syscon_config pic64gx_config = {
|
| 222 | .nb_subdevs = ARRAY_SIZE(pic64gx_subdevs),
|
| 223 | .subdevs = pic64gx_subdevs,
|
| 224 | };
|
| 225 |
|
| 226 | static const struct of_device_id mpfs_sys_controller_of_match[] = {
|
| 227 | {.compatible = "microchip,mpfs-sys-controller", .data = &mpfs_config},
|
| 228 | {.compatible = "microchip,pic64gx-sys-controller", .data = &pic64gx_config},
|
| 229 | {},
|
| 230 | };
|
| 231 | MODULE_DEVICE_TABLE(of, mpfs_sys_controller_of_match);
|
| 232 |
|
| 233 | struct mpfs_sys_controller *mpfs_sys_controller_get(struct device *dev)
|
| 234 | {
|
| 235 | const struct of_device_id *match;
|
| 236 | struct mpfs_sys_controller *sys_controller;
|
| 237 | int ret;
|
| 238 |
|
| 239 | if (!dev->parent)
|
| 240 | goto err_no_device;
|
| 241 |
|
| 242 | match = of_match_node(mpfs_sys_controller_of_match, dev->parent->of_node);
|
| 243 | of_node_put(dev->parent->of_node);
|
| 244 | if (!match)
|
| 245 | goto err_no_device;
|
| 246 |
|
| 247 | sys_controller = dev_get_drvdata(dev->parent);
|
| 248 | if (!sys_controller)
|
| 249 | goto err_bad_device;
|
| 250 |
|
| 251 | if (!kref_get_unless_zero(&sys_controller->consumers))
|
| 252 | goto err_bad_device;
|
| 253 |
|
| 254 | ret = devm_add_action_or_reset(dev, mpfs_sys_controller_put, sys_controller);
|
| 255 | if (ret)
|
| 256 | return ERR_PTR(ret);
|
| 257 |
|
| 258 | return sys_controller;
|
| 259 |
|
| 260 | err_no_device:
|
| 261 | dev_dbg(dev, "Parent device was not an MPFS system controller\n");
|
| 262 | return ERR_PTR(-ENODEV);
|
| 263 |
|
| 264 | err_bad_device:
|
| 265 | dev_dbg(dev, "MPFS system controller found but could not register as a sub device\n");
|
| 266 | return ERR_PTR(-EPROBE_DEFER);
|
| 267 | }
|
| 268 | EXPORT_SYMBOL(mpfs_sys_controller_get);
|
| 269 |
|
| 270 | static struct platform_driver mpfs_sys_controller_driver = {
|
| 271 | .driver = {
|
| 272 | .name = "mpfs-sys-controller",
|
| 273 | .of_match_table = mpfs_sys_controller_of_match,
|
| 274 | },
|
| 275 | .probe = mpfs_sys_controller_probe,
|
| 276 | .remove = mpfs_sys_controller_remove,
|
| 277 | };
|
| 278 | module_platform_driver(mpfs_sys_controller_driver);
|
| 279 |
|
| 280 | MODULE_LICENSE("GPL v2");
|
| 281 | MODULE_AUTHOR("Conor Dooley <[email protected]>");
|
| 282 | MODULE_DESCRIPTION("MPFS system controller driver");
|