diff options
Diffstat (limited to 'drivers/firmware/raspberrypi.c')
| -rw-r--r-- | drivers/firmware/raspberrypi.c | 69 | 
1 files changed, 66 insertions, 3 deletions
| diff --git a/drivers/firmware/raspberrypi.c b/drivers/firmware/raspberrypi.c index 30259dc9b805..250e01680742 100644 --- a/drivers/firmware/raspberrypi.c +++ b/drivers/firmware/raspberrypi.c @@ -7,6 +7,7 @@   */  #include <linux/dma-mapping.h> +#include <linux/kref.h>  #include <linux/mailbox_client.h>  #include <linux/module.h>  #include <linux/of_platform.h> @@ -27,6 +28,8 @@ struct rpi_firmware {  	struct mbox_chan *chan; /* The property channel. */  	struct completion c;  	u32 enabled; + +	struct kref consumers;  };  static DEFINE_MUTEX(transaction_lock); @@ -225,12 +228,38 @@ static void rpi_register_clk_driver(struct device *dev)  						-1, NULL, 0);  } +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; @@ -247,6 +276,7 @@ static int rpi_firmware_probe(struct platform_device *pdev)  	}  	init_completion(&fw->c); +	kref_init(&fw->consumers);  	platform_set_drvdata(pdev, fw); @@ -275,7 +305,8 @@ static int rpi_firmware_remove(struct platform_device *pdev)  	rpi_hwmon = NULL;  	platform_device_unregister(rpi_clk);  	rpi_clk = NULL; -	mbox_free_channel(fw->chan); + +	rpi_firmware_put(fw);  	return 0;  } @@ -284,19 +315,51 @@ static int rpi_firmware_remove(struct platform_device *pdev)   * 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) +		return NULL; + +	if (!kref_get_unless_zero(&fw->consumers)) +		return NULL; + +	return fw;  }  EXPORT_SYMBOL_GPL(rpi_firmware_get); +/** + * devm_rpi_firmware_get - Get pointer to rpi_firmware 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 const struct of_device_id rpi_firmware_of_match[] = {  	{ .compatible = "raspberrypi,bcm2835-firmware", },  	{}, | 
