diff options
Diffstat (limited to 'drivers/firmware/raspberrypi.c')
| -rw-r--r-- | drivers/firmware/raspberrypi.c | 182 |
1 files changed, 155 insertions, 27 deletions
diff --git a/drivers/firmware/raspberrypi.c b/drivers/firmware/raspberrypi.c index a13558154ac3..7ecde6921a0a 100644 --- a/drivers/firmware/raspberrypi.c +++ b/drivers/firmware/raspberrypi.c @@ -1,14 +1,17 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Defines interfaces for interacting wtih the Raspberry Pi firmware's + * Defines interfaces for interacting with the Raspberry Pi firmware's * property channel. * * Copyright © 2015 Broadcom */ #include <linux/dma-mapping.h> +#include <linux/kref.h> #include <linux/mailbox_client.h> +#include <linux/mailbox_controller.h> #include <linux/module.h> +#include <linux/of.h> #include <linux/of_platform.h> #include <linux/platform_device.h> #include <linux/slab.h> @@ -20,12 +23,15 @@ #define MBOX_CHAN_PROPERTY 8 static struct platform_device *rpi_hwmon; +static struct platform_device *rpi_clk; struct rpi_firmware { struct mbox_client cl; struct mbox_chan *chan; /* The property channel. */ struct completion c; u32 enabled; + + struct kref consumers; }; static DEFINE_MUTEX(transaction_lock); @@ -56,7 +62,6 @@ rpi_firmware_transaction(struct rpi_firmware *fw, u32 chan, u32 data) ret = 0; } else { ret = -ETIMEDOUT; - WARN_ONCE(1, "Firmware transaction timeout"); } } else { dev_err(fw->cl.dev, "mbox_send_message returned %d\n", ret); @@ -92,8 +97,8 @@ int rpi_firmware_property_list(struct rpi_firmware *fw, if (size & 3) return -EINVAL; - buf = dma_alloc_coherent(fw->cl.dev, PAGE_ALIGN(size), &bus_addr, - GFP_ATOMIC); + buf = dma_alloc_coherent(fw->chan->mbox->dev, PAGE_ALIGN(size), + &bus_addr, GFP_ATOMIC); if (!buf) return -ENOMEM; @@ -119,9 +124,11 @@ int rpi_firmware_property_list(struct rpi_firmware *fw, dev_err(fw->cl.dev, "Request 0x%08x returned status 0x%08x\n", buf[2], buf[1]); ret = -EINVAL; + } else if (ret == -ETIMEDOUT) { + WARN_ONCE(1, "Firmware transaction 0x%08x timeout", buf[2]); } - dma_free_coherent(fw->cl.dev, PAGE_ALIGN(size), buf, bus_addr); + dma_free_coherent(fw->chan->mbox->dev, PAGE_ALIGN(size), buf, bus_addr); return ret; } @@ -176,21 +183,18 @@ EXPORT_SYMBOL_GPL(rpi_firmware_property); static void rpi_firmware_print_firmware_revision(struct rpi_firmware *fw) { + time64_t date_and_time; u32 packet; int ret = rpi_firmware_property(fw, RPI_FIRMWARE_GET_FIRMWARE_REVISION, &packet, sizeof(packet)); - if (ret == 0) { - struct tm tm; - - time64_to_tm(packet, 0, &tm); + if (ret) + return; - dev_info(fw->cl.dev, - "Attached to firmware from %04ld-%02d-%02d %02d:%02d\n", - tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, - tm.tm_hour, tm.tm_min); - } + /* This is not compatible with y2038 */ + date_and_time = packet; + dev_info(fw->cl.dev, "Attached to firmware from %ptT\n", &date_and_time); } static void @@ -207,12 +211,78 @@ rpi_register_hwmon_driver(struct device *dev, struct rpi_firmware *fw) -1, NULL, 0); } +static void rpi_register_clk_driver(struct device *dev) +{ + struct device_node *firmware; + + /* + * Earlier DTs don't have a node for the firmware clocks but + * rely on us creating a platform device by hand. If we do + * have a node for the firmware clocks, just bail out here. + */ + firmware = of_get_compatible_child(dev->of_node, + "raspberrypi,firmware-clocks"); + if (firmware) { + of_node_put(firmware); + return; + } + + rpi_clk = platform_device_register_data(dev, "raspberrypi-clk", + -1, NULL, 0); +} + +unsigned int rpi_firmware_clk_get_max_rate(struct rpi_firmware *fw, unsigned int id) +{ + struct rpi_firmware_clk_rate_request msg = + RPI_FIRMWARE_CLK_RATE_REQUEST(id); + int ret; + + ret = rpi_firmware_property(fw, RPI_FIRMWARE_GET_MAX_CLOCK_RATE, + &msg, sizeof(msg)); + if (ret) + /* + * If our firmware doesn't support that operation, or fails, we + * assume the maximum clock rate is absolute maximum we can + * store over our type. + */ + return UINT_MAX; + + return le32_to_cpu(msg.rate); +} +EXPORT_SYMBOL_GPL(rpi_firmware_clk_get_max_rate); + +static void rpi_firmware_delete(struct kref *kref) +{ + struct rpi_firmware *fw = container_of(kref, struct rpi_firmware, + consumers); + + mbox_free_channel(fw->chan); + kfree(fw); +} + +void rpi_firmware_put(struct rpi_firmware *fw) +{ + kref_put(&fw->consumers, rpi_firmware_delete); +} +EXPORT_SYMBOL_GPL(rpi_firmware_put); + +static void devm_rpi_firmware_put(void *data) +{ + struct rpi_firmware *fw = data; + + rpi_firmware_put(fw); +} + static int rpi_firmware_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct rpi_firmware *fw; - fw = devm_kzalloc(dev, sizeof(*fw), GFP_KERNEL); + /* + * Memory will be freed by rpi_firmware_delete() once all users have + * released their firmware handles. Don't use devm_kzalloc() here. + */ + fw = kzalloc(sizeof(*fw), GFP_KERNEL); if (!fw) return -ENOMEM; @@ -223,54 +293,111 @@ static int rpi_firmware_probe(struct platform_device *pdev) fw->chan = mbox_request_channel(&fw->cl, 0); if (IS_ERR(fw->chan)) { int ret = PTR_ERR(fw->chan); - if (ret != -EPROBE_DEFER) - dev_err(dev, "Failed to get mbox channel: %d\n", ret); - return ret; + kfree(fw); + return dev_err_probe(dev, ret, "Failed to get mbox channel\n"); } init_completion(&fw->c); + kref_init(&fw->consumers); platform_set_drvdata(pdev, fw); rpi_firmware_print_firmware_revision(fw); rpi_register_hwmon_driver(dev, fw); + rpi_register_clk_driver(dev); return 0; } -static int rpi_firmware_remove(struct platform_device *pdev) +static void rpi_firmware_shutdown(struct platform_device *pdev) +{ + struct rpi_firmware *fw = platform_get_drvdata(pdev); + + if (!fw) + return; + + rpi_firmware_property(fw, RPI_FIRMWARE_NOTIFY_REBOOT, NULL, 0); +} + +static void rpi_firmware_remove(struct platform_device *pdev) { struct rpi_firmware *fw = platform_get_drvdata(pdev); platform_device_unregister(rpi_hwmon); rpi_hwmon = NULL; - mbox_free_channel(fw->chan); + platform_device_unregister(rpi_clk); + rpi_clk = NULL; - return 0; + rpi_firmware_put(fw); +} + +static const struct of_device_id rpi_firmware_of_match[] = { + { .compatible = "raspberrypi,bcm2835-firmware", }, + {}, +}; +MODULE_DEVICE_TABLE(of, rpi_firmware_of_match); + +struct device_node *rpi_firmware_find_node(void) +{ + return of_find_matching_node(NULL, rpi_firmware_of_match); } +EXPORT_SYMBOL_GPL(rpi_firmware_find_node); /** * rpi_firmware_get - Get pointer to rpi_firmware structure. * @firmware_node: Pointer to the firmware Device Tree node. * + * The reference to rpi_firmware has to be released with rpi_firmware_put(). + * * Returns NULL is the firmware device is not ready. */ struct rpi_firmware *rpi_firmware_get(struct device_node *firmware_node) { struct platform_device *pdev = of_find_device_by_node(firmware_node); + struct rpi_firmware *fw; if (!pdev) return NULL; - return platform_get_drvdata(pdev); + fw = platform_get_drvdata(pdev); + if (!fw) + goto err_put_device; + + if (!kref_get_unless_zero(&fw->consumers)) + goto err_put_device; + + put_device(&pdev->dev); + + return fw; + +err_put_device: + put_device(&pdev->dev); + return NULL; } EXPORT_SYMBOL_GPL(rpi_firmware_get); -static const struct of_device_id rpi_firmware_of_match[] = { - { .compatible = "raspberrypi,bcm2835-firmware", }, - {}, -}; -MODULE_DEVICE_TABLE(of, rpi_firmware_of_match); +/** + * devm_rpi_firmware_get - Get pointer to rpi_firmware structure. + * @dev: The firmware device structure + * @firmware_node: Pointer to the firmware Device Tree node. + * + * Returns NULL is the firmware device is not ready. + */ +struct rpi_firmware *devm_rpi_firmware_get(struct device *dev, + struct device_node *firmware_node) +{ + struct rpi_firmware *fw; + + fw = rpi_firmware_get(firmware_node); + if (!fw) + return NULL; + + if (devm_add_action_or_reset(dev, devm_rpi_firmware_put, fw)) + return NULL; + + return fw; +} +EXPORT_SYMBOL_GPL(devm_rpi_firmware_get); static struct platform_driver rpi_firmware_driver = { .driver = { @@ -278,6 +405,7 @@ static struct platform_driver rpi_firmware_driver = { .of_match_table = rpi_firmware_of_match, }, .probe = rpi_firmware_probe, + .shutdown = rpi_firmware_shutdown, .remove = rpi_firmware_remove, }; module_platform_driver(rpi_firmware_driver); |
