Re: [RFC PATCH 02/14] media: intel/ipu6: add IPU virtual bus driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Bingbu,

Thank you for the patch.

On Thu, Apr 13, 2023 at 06:04:17PM +0800, bingbu.cao@xxxxxxxxx wrote:
> From: Bingbu Cao <bingbu.cao@xxxxxxxxx>
> 
> Even the IPU input system and processing system are a single PCI
> device, each system has its own power sequence, the processing system
> power up depends on the input system power up.
> 
> Besides, input system and processing system have their own MMU
> hardware for IPU DMA address mapping.
> 
> Define a virtual bus to help us to implement the power sequence
> dependency and DMA mapping requirement on specific device.

Have you considered using the aux bus instead of creating a custom bus
type (see [1]) ?

[1] https://lore.kernel.org/lkml/Y862WTT03%2FJxXUG8@xxxxxxxxx/

> Signed-off-by: Bingbu Cao <bingbu.cao@xxxxxxxxx>
> ---
>  drivers/media/pci/intel/ipu6/ipu6-bus.c | 263 ++++++++++++++++++++++++
>  drivers/media/pci/intel/ipu6/ipu6-bus.h |  68 ++++++
>  2 files changed, 331 insertions(+)
>  create mode 100644 drivers/media/pci/intel/ipu6/ipu6-bus.c
>  create mode 100644 drivers/media/pci/intel/ipu6/ipu6-bus.h
> 
> diff --git a/drivers/media/pci/intel/ipu6/ipu6-bus.c b/drivers/media/pci/intel/ipu6/ipu6-bus.c
> new file mode 100644
> index 000000000000..9a758d7ae73c
> --- /dev/null
> +++ b/drivers/media/pci/intel/ipu6/ipu6-bus.c
> @@ -0,0 +1,263 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +// Copyright (C) 2013 - 2023 Intel Corporation
> +
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/interrupt.h>
> +#include <linux/list.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/pci.h>
> +#include <linux/pm_runtime.h>
> +
> +#include "ipu6.h"
> +#include "ipu6-bus.h"
> +#include "ipu6-buttress.h"
> +#include "ipu6-dma.h"
> +#include "ipu6-platform.h"
> +
> +static struct bus_type ipu6_bus;
> +
> +static int bus_pm_runtime_suspend(struct device *dev)
> +{
> +	struct ipu6_bus_device *adev = to_ipu6_bus_device(dev);
> +	int ret;
> +
> +	ret = pm_generic_runtime_suspend(dev);
> +	if (ret)
> +		return ret;
> +
> +	ret = ipu6_buttress_power(dev, adev->ctrl, false);
> +	dev_dbg(dev, "buttress power down %d\n", ret);
> +	if (!ret)
> +		return 0;
> +
> +	dev_err(dev, "power down failed!\n");
> +
> +	/* Powering down failed, attempt to resume device now */
> +	ret = pm_generic_runtime_resume(dev);
> +	if (!ret)
> +		return -EBUSY;
> +
> +	return -EIO;
> +}
> +
> +static int bus_pm_runtime_resume(struct device *dev)
> +{
> +	struct ipu6_bus_device *adev = to_ipu6_bus_device(dev);
> +	int ret;
> +
> +	ret = ipu6_buttress_power(dev, adev->ctrl, true);
> +	dev_dbg(dev, "buttress power up %d\n", ret);
> +	if (ret)
> +		return ret;
> +
> +	ret = pm_generic_runtime_resume(dev);
> +	if (ret)
> +		goto out_err;
> +
> +	return 0;
> +
> +out_err:
> +	ipu6_buttress_power(dev, adev->ctrl, false);
> +
> +	return -EBUSY;
> +}
> +
> +static const struct dev_pm_ops ipu6_bus_pm_ops = {
> +	.runtime_suspend = bus_pm_runtime_suspend,
> +	.runtime_resume = bus_pm_runtime_resume,
> +};
> +
> +static int ipu6_bus_match(struct device *dev, struct device_driver *drv)
> +{
> +	struct ipu6_bus_driver *adrv = to_ipu6_bus_driver(drv);
> +	struct pci_dev *pci_dev = to_pci_dev(dev->parent);
> +	const struct pci_device_id *found_id;
> +
> +	found_id = pci_match_id(adrv->id_table, pci_dev);
> +	if (found_id)
> +		dev_dbg(dev, "%s %x:%x matched\n", dev_name(dev),
> +			found_id->vendor, found_id->device);
> +
> +	return found_id ? 1 : 0;
> +}
> +
> +static int ipu6_bus_probe(struct device *dev)
> +{
> +	struct ipu6_bus_device *adev = to_ipu6_bus_device(dev);
> +	struct ipu6_bus_driver *adrv = to_ipu6_bus_driver(dev->driver);
> +	int ret;
> +
> +	if (!adev->isp->bus_ready_to_probe)
> +		return -EPROBE_DEFER;
> +
> +	dev_dbg(dev, "bus probe dev %s\n", dev_name(dev));
> +
> +	adev->adrv = adrv;
> +	if (!adrv->probe) {
> +		ret = -ENODEV;
> +		goto out_err;
> +	}
> +
> +	ret = pm_runtime_resume_and_get(&adev->dev);
> +	if (ret < 0) {
> +		dev_err(&adev->dev, "Failed to get runtime PM\n");
> +		goto out_err;
> +	}
> +
> +	ret = adrv->probe(adev);
> +	pm_runtime_put(&adev->dev);
> +
> +	if (ret)
> +		goto out_err;
> +
> +	return 0;
> +
> +out_err:
> +	ipu6_bus_set_drvdata(adev, NULL);
> +	adev->adrv = NULL;
> +
> +	return ret;
> +}
> +
> +static void ipu6_bus_remove(struct device *dev)
> +{
> +	struct ipu6_bus_device *adev = to_ipu6_bus_device(dev);
> +	struct ipu6_bus_driver *adrv = to_ipu6_bus_driver(dev->driver);
> +
> +	if (adrv->remove)
> +		adrv->remove(adev);
> +}
> +
> +static struct bus_type ipu6_bus = {
> +	.name = IPU6_BUS_NAME,
> +	.match = ipu6_bus_match,
> +	.probe = ipu6_bus_probe,
> +	.remove = ipu6_bus_remove,
> +	.pm = &ipu6_bus_pm_ops,
> +};
> +
> +static DEFINE_MUTEX(ipu6_bus_mutex);
> +
> +static void ipu6_bus_release(struct device *dev)
> +{
> +	struct ipu6_bus_device *adev = to_ipu6_bus_device(dev);
> +
> +	kfree(adev);
> +}
> +
> +struct ipu6_bus_device *
> +ipu6_bus_initialize_device(struct pci_dev *pdev, struct device *parent,
> +			   void *pdata, struct ipu6_buttress_ctrl *ctrl,
> +			   char *name)
> +{
> +	struct ipu6_bus_device *adev;
> +	struct ipu6_device *isp = pci_get_drvdata(pdev);
> +
> +	adev = kzalloc(sizeof(*adev), GFP_KERNEL);
> +	if (!adev)
> +		return ERR_PTR(-ENOMEM);
> +
> +	adev->dev.parent = parent;
> +	adev->dev.bus = &ipu6_bus;
> +	adev->dev.release = ipu6_bus_release;
> +	adev->dev.dma_ops = &ipu6_dma_ops;
> +	adev->dma_mask = DMA_BIT_MASK(isp->secure_mode ? IPU6_MMU_ADDR_BITS :
> +				      IPU6_MMU_ADDR_BITS_NON_SECURE);
> +	adev->dev.dma_mask = &adev->dma_mask;
> +	adev->dev.dma_parms = pdev->dev.dma_parms;
> +	adev->dev.coherent_dma_mask = adev->dma_mask;
> +	adev->ctrl = ctrl;
> +	adev->pdata = pdata;
> +	adev->isp = isp;
> +	dev_set_name(&adev->dev, "%s", name);
> +
> +	device_initialize(&adev->dev);
> +	pm_runtime_forbid(&adev->dev);
> +	pm_runtime_enable(&adev->dev);
> +
> +	return adev;
> +}
> +
> +int ipu6_bus_add_device(struct ipu6_bus_device *adev)
> +{
> +	int ret;
> +
> +	ret = device_add(&adev->dev);
> +	if (ret) {
> +		put_device(&adev->dev);
> +		return ret;
> +	}
> +
> +	mutex_lock(&ipu6_bus_mutex);
> +	list_add(&adev->list, &adev->isp->devices);
> +	mutex_unlock(&ipu6_bus_mutex);
> +
> +	pm_runtime_allow(&adev->dev);
> +	return 0;
> +}
> +
> +void ipu6_bus_del_devices(struct pci_dev *pdev)
> +{
> +	struct ipu6_device *isp = pci_get_drvdata(pdev);
> +	struct ipu6_bus_device *adev, *save;
> +
> +	mutex_lock(&ipu6_bus_mutex);
> +
> +	list_for_each_entry_safe(adev, save, &isp->devices, list) {
> +		pm_runtime_disable(&adev->dev);
> +		list_del(&adev->list);
> +		device_unregister(&adev->dev);
> +	}
> +
> +	mutex_unlock(&ipu6_bus_mutex);
> +}
> +
> +int ipu6_bus_register_driver(struct ipu6_bus_driver *adrv)
> +{
> +	adrv->drv.bus = &ipu6_bus;
> +	return driver_register(&adrv->drv);
> +}
> +EXPORT_SYMBOL_NS_GPL(ipu6_bus_register_driver, INTEL_IPU6);
> +
> +int ipu6_bus_unregister_driver(struct ipu6_bus_driver *adrv)
> +{
> +	driver_unregister(&adrv->drv);
> +	return 0;
> +}
> +EXPORT_SYMBOL_NS_GPL(ipu6_bus_unregister_driver, INTEL_IPU6);
> +
> +int ipu6_bus_register(void)
> +{
> +	return bus_register(&ipu6_bus);
> +}
> +
> +void ipu6_bus_unregister(void)
> +{
> +	return bus_unregister(&ipu6_bus);
> +}
> +
> +static int flr_rpm_recovery(struct device *dev, void *p)
> +{
> +	/*
> +	 * We are not necessarily going through device from child to
> +	 * parent. runtime PM refuses to change state for parent if the child
> +	 * is still active. At FLR (full reset for whole IPU6) that doesn't
> +	 * matter. Everything has been power gated by HW during the FLR cycle
> +	 * and we are just cleaning up SW state. Thus, ignore child during
> +	 * set_suspended.
> +	 */
> +	dev_dbg(dev, "FLR recovery call\n");
> +	pm_suspend_ignore_children(dev, true);
> +	pm_runtime_set_suspended(dev);
> +	pm_suspend_ignore_children(dev, false);
> +
> +	return 0;
> +}
> +
> +int ipu6_bus_flr_recovery(void)
> +{
> +	bus_for_each_dev(&ipu6_bus, NULL, NULL, flr_rpm_recovery);
> +	return 0;
> +}
> diff --git a/drivers/media/pci/intel/ipu6/ipu6-bus.h b/drivers/media/pci/intel/ipu6/ipu6-bus.h
> new file mode 100644
> index 000000000000..de01bd56e786
> --- /dev/null
> +++ b/drivers/media/pci/intel/ipu6/ipu6-bus.h
> @@ -0,0 +1,68 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/* Copyright (C) 2013 - 2023 Intel Corporation */
> +
> +#ifndef IPU6_BUS_H
> +#define IPU6_BUS_H
> +
> +#include <linux/device.h>
> +#include <linux/irqreturn.h>
> +#include <linux/list.h>
> +#include <linux/mm.h>
> +#include <linux/pci.h>
> +
> +#define IPU6_BUS_NAME	IPU6_NAME "-bus"
> +
> +struct ipu6_buttress_ctrl;
> +struct ipu6_subsystem_trace_config;
> +
> +struct ipu6_bus_device {
> +	struct device dev;
> +	struct list_head list;
> +	void *pdata;
> +	struct ipu6_bus_driver *adrv;
> +	struct ipu6_mmu *mmu;
> +	struct ipu6_device *isp;
> +	struct ipu6_subsystem_trace_config *trace_cfg;
> +	struct ipu6_buttress_ctrl *ctrl;
> +	u64 dma_mask;
> +
> +	const struct firmware *fw;

Is there a firmware per device ? I may have understood things wrong, but
I thought the whole point of this architecture was to have a base PCI
device running a firmware, exposing multiple functions (ISYS and PSYS),
and implementing separate drivers for those functions.

> +	struct sg_table fw_sgt;
> +	u64 *pkg_dir;
> +	dma_addr_t pkg_dir_dma_addr;
> +	unsigned int pkg_dir_size;
> +};
> +
> +#define to_ipu6_bus_device(_dev) container_of(_dev, struct ipu6_bus_device, dev)
> +
> +struct ipu6_bus_driver {
> +	struct device_driver drv;
> +	const struct pci_device_id *id_table;

Why do you need the PCI ID table here ? The table from ipu6-isys.c is
the same as the one in ipu6.c.

> +	int (*probe)(struct ipu6_bus_device *adev);
> +	void (*remove)(struct ipu6_bus_device *adev);
> +	irqreturn_t (*isr)(struct ipu6_bus_device *adev);
> +	irqreturn_t (*isr_threaded)(struct ipu6_bus_device *adev);
> +	bool wake_isr_thread;
> +};
> +
> +#define to_ipu6_bus_driver(_drv) container_of(_drv, struct ipu6_bus_driver, drv)
> +
> +struct ipu6_bus_device *
> +ipu6_bus_initialize_device(struct pci_dev *pdev, struct device *parent,
> +			   void *pdata, struct ipu6_buttress_ctrl *ctrl,
> +			   char *name);
> +int ipu6_bus_add_device(struct ipu6_bus_device *adev);
> +void ipu6_bus_del_devices(struct pci_dev *pdev);
> +
> +int ipu6_bus_register_driver(struct ipu6_bus_driver *adrv);
> +int ipu6_bus_unregister_driver(struct ipu6_bus_driver *adrv);
> +
> +int ipu6_bus_register(void);
> +void ipu6_bus_unregister(void);
> +
> +#define ipu6_bus_set_drvdata(adev, data) dev_set_drvdata(&(adev)->dev, data)
> +#define ipu6_bus_get_drvdata(adev) dev_get_drvdata(&(adev)->dev)
> +
> +int ipu6_bus_flr_recovery(void);
> +
> +#endif

-- 
Regards,

Laurent Pinchart



[Index of Archives]     [Linux Input]     [Video for Linux]     [Gstreamer Embedded]     [Mplayer Users]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]

  Powered by Linux