Message ID | 20230512102844.51084-4-jack.zhu@starfivetech.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | Add StarFive Camera Subsystem driver | expand |
On 12/05/2023 11:28, Jack Zhu wrote: > Add basic platform driver for StarFive Camera Subsystem. > > Signed-off-by: Jack Zhu <jack.zhu@starfivetech.com> A couple of nits below Other than those Reviewed-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> > --- > MAINTAINERS | 1 + > drivers/media/platform/Kconfig | 1 + > drivers/media/platform/Makefile | 1 + > drivers/media/platform/starfive/Kconfig | 19 + > drivers/media/platform/starfive/Makefile | 9 + > drivers/media/platform/starfive/stf_camss.c | 372 +++++++++++++++++++ > drivers/media/platform/starfive/stf_camss.h | 153 ++++++++ > drivers/media/platform/starfive/stf_common.h | 18 + > 8 files changed, 574 insertions(+) > create mode 100644 drivers/media/platform/starfive/Kconfig > create mode 100644 drivers/media/platform/starfive/Makefile > create mode 100644 drivers/media/platform/starfive/stf_camss.c > create mode 100644 drivers/media/platform/starfive/stf_camss.h > create mode 100644 drivers/media/platform/starfive/stf_common.h > > diff --git a/MAINTAINERS b/MAINTAINERS > index 71291dc58671..33a752569942 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -20077,6 +20077,7 @@ L: linux-media@vger.kernel.org > S: Maintained > F: Documentation/admin-guide/media/starfive_camss.rst > F: Documentation/devicetree/bindings/media/starfive,jh7110-camss.yaml > +F: drivers/media/platform/starfive/ > > STARFIVE DEVICETREES > M: Emil Renner Berthing <kernel@esmil.dk> > diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig > index ee579916f874..627eaa0ab3ee 100644 > --- a/drivers/media/platform/Kconfig > +++ b/drivers/media/platform/Kconfig > @@ -80,6 +80,7 @@ source "drivers/media/platform/renesas/Kconfig" > source "drivers/media/platform/rockchip/Kconfig" > source "drivers/media/platform/samsung/Kconfig" > source "drivers/media/platform/st/Kconfig" > +source "drivers/media/platform/starfive/Kconfig" > source "drivers/media/platform/sunxi/Kconfig" > source "drivers/media/platform/ti/Kconfig" > source "drivers/media/platform/verisilicon/Kconfig" > diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile > index 5453bb868e67..5a038498a370 100644 > --- a/drivers/media/platform/Makefile > +++ b/drivers/media/platform/Makefile > @@ -23,6 +23,7 @@ obj-y += renesas/ > obj-y += rockchip/ > obj-y += samsung/ > obj-y += st/ > +obj-y += starfive/ > obj-y += sunxi/ > obj-y += ti/ > obj-y += verisilicon/ > diff --git a/drivers/media/platform/starfive/Kconfig b/drivers/media/platform/starfive/Kconfig > new file mode 100644 > index 000000000000..1a2293475bd2 > --- /dev/null > +++ b/drivers/media/platform/starfive/Kconfig > @@ -0,0 +1,19 @@ > +# SPDX-License-Identifier: GPL-2.0-only > + > +comment "Starfive media platform drivers" > + > +config VIDEO_STARFIVE_CAMSS > + tristate "Starfive Camera Subsystem driver" > + depends on V4L_PLATFORM_DRIVERS > + depends on VIDEO_DEV && OF > + depends on DMA_CMA > + select MEDIA_CONTROLLER > + select VIDEO_V4L2_SUBDEV_API > + select VIDEOBUF2_DMA_CONTIG > + select V4L2_FWNODE > + help > + Enable this to support for the Starfive Camera subsystem > + found on Starfive JH7110 SoC. > + > + To compile this driver as a module, choose M here: the > + module will be called stf-camss. > diff --git a/drivers/media/platform/starfive/Makefile b/drivers/media/platform/starfive/Makefile > new file mode 100644 > index 000000000000..796775fa52f4 > --- /dev/null > +++ b/drivers/media/platform/starfive/Makefile > @@ -0,0 +1,9 @@ > +# SPDX-License-Identifier: GPL-2.0 > +# > +# Makefile for StarFive camera subsystem driver. > +# > + > +starfive-camss-objs += \ > + stf_camss.o > + > +obj-$(CONFIG_VIDEO_STARFIVE_CAMSS) += starfive-camss.o \ > diff --git a/drivers/media/platform/starfive/stf_camss.c b/drivers/media/platform/starfive/stf_camss.c > new file mode 100644 > index 000000000000..ba3a2bc8a8ab > --- /dev/null > +++ b/drivers/media/platform/starfive/stf_camss.c > @@ -0,0 +1,372 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * stf_camss.c > + * > + * Starfive Camera Subsystem driver > + * > + * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. > + */ > +#include <linux/module.h> > +#include <linux/of.h> > +#include <linux/of_graph.h> > +#include <linux/platform_device.h> > +#include <linux/pm_runtime.h> > +#include <linux/videodev2.h> > +#include <media/media-device.h> > +#include <media/v4l2-async.h> > +#include <media/v4l2-fwnode.h> > +#include <media/v4l2-mc.h> > + > +#include "stf_camss.h" > + > +static const char * const stfcamss_clocks[] = { > + "clk_apb_func", > + "clk_wrapper_clk_c", > + "clk_dvp_inv", > + "clk_axiwr", > + "clk_mipi_rx0_pxl", > + "clk_ispcore_2x", > + "clk_isp_axi", > +}; > + > +static const char * const stfcamss_resets[] = { > + "rst_wrapper_p", > + "rst_wrapper_c", > + "rst_axird", > + "rst_axiwr", > + "rst_isp_top_n", > + "rst_isp_top_axi", > +}; > + > +static int stfcamss_get_mem_res(struct platform_device *pdev, > + struct stfcamss *stfcamss) > +{ > + stfcamss->syscon_base = > + devm_platform_ioremap_resource_byname(pdev, "syscon"); > + if (IS_ERR(stfcamss->syscon_base)) > + return PTR_ERR(stfcamss->syscon_base); > + > + stfcamss->isp_base = > + devm_platform_ioremap_resource_byname(pdev, "isp"); > + if (IS_ERR(stfcamss->isp_base)) > + return PTR_ERR(stfcamss->isp_base); > + > + return 0; > +} > + > +/* > + * stfcamss_of_parse_endpoint_node - Parse port endpoint node > + * @dev: Device > + * @node: Device node to be parsed > + * @csd: Parsed data from port endpoint node > + * > + * Return 0 on success or a negative error code on failure > + */ > +static int stfcamss_of_parse_endpoint_node(struct device *dev, > + struct device_node *node, > + struct stfcamss_async_subdev *csd) > +{ > + struct v4l2_fwnode_endpoint vep = { { 0 } }; > + > + v4l2_fwnode_endpoint_parse(of_fwnode_handle(node), &vep); > + dev_dbg(dev, "vep.base.port = 0x%x, id = 0x%x\n", > + vep.base.port, vep.base.id); > + > + csd->port = vep.base.port; > + > + return 0; > +} > + > +/* > + * stfcamss_of_parse_ports - Parse ports node > + * @stfcamss: STFCAMSS device > + * > + * Return number of "port" nodes found in "ports" node > + */ > +static int stfcamss_of_parse_ports(struct stfcamss *stfcamss) > +{ > + struct device *dev = stfcamss->dev; > + struct device_node *node = NULL; > + struct device_node *remote = NULL; > + int ret, num_subdevs = 0; > + > + for_each_endpoint_of_node(dev->of_node, node) { > + struct stfcamss_async_subdev *csd; > + > + if (!of_device_is_available(node)) > + continue; > + > + remote = of_graph_get_remote_port_parent(node); > + if (!remote) { > + dev_err(dev, "Cannot get remote parent\n"); > + ret = -EINVAL; > + goto err_cleanup; > + } > + > + csd = v4l2_async_nf_add_fwnode(&stfcamss->notifier, > + of_fwnode_handle(remote), > + struct stfcamss_async_subdev); > + of_node_put(remote); > + if (IS_ERR(csd)) { > + ret = PTR_ERR(csd); > + goto err_cleanup; > + } > + > + ret = stfcamss_of_parse_endpoint_node(dev, node, csd); > + if (ret < 0) > + goto err_cleanup; > + > + num_subdevs++; > + } > + > + return num_subdevs; > + > +err_cleanup: > + of_node_put(node); > + return ret; > +} > + > +static int stfcamss_subdev_notifier_bound(struct v4l2_async_notifier *async, > + struct v4l2_subdev *subdev, > + struct v4l2_async_subdev *asd) > +{ > + struct stfcamss *stfcamss = > + container_of(async, struct stfcamss, notifier); > + struct host_data *host_data = &stfcamss->host_data; > + struct media_entity *source; > + int i, j; > + > + source = &subdev->entity; > + > + for (i = 0; i < source->num_pads; i++) { > + if (source->pads[i].flags & MEDIA_PAD_FL_SOURCE) > + break; > + } > + > + if (i == source->num_pads) { > + dev_err(stfcamss->dev, "No source pad in external entity\n"); > + return -EINVAL; > + } > + > + for (j = 0; host_data->host_entity[j] && (j < HOST_ENTITY_MAX); j++) { > + struct media_entity *input; > + int ret; > + > + input = host_data->host_entity[j]; > + > + ret = media_create_pad_link( > + source, > + i, > + input, > + STF_PAD_SINK, > + source->function == MEDIA_ENT_F_CAM_SENSOR ? > + MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED : > + 0); > + if (ret < 0) { > + dev_err(stfcamss->dev, > + "Failed to link %s->%s entities: %d\n", > + source->name, input->name, ret); > + return ret; > + } > + } > + > + return 0; > +} > + > +static int stfcamss_subdev_notifier_complete(struct v4l2_async_notifier *ntf) > +{ > + struct stfcamss *stfcamss = > + container_of(ntf, struct stfcamss, notifier); > + > + return v4l2_device_register_subdev_nodes(&stfcamss->v4l2_dev); > +} > + > +static const struct v4l2_async_notifier_operations > +stfcamss_subdev_notifier_ops = { > + .bound = stfcamss_subdev_notifier_bound, > + .complete = stfcamss_subdev_notifier_complete, > +}; > + > +static const struct media_device_ops stfcamss_media_ops = { > + .link_notify = v4l2_pipeline_link_notify, > +}; > + > +static void stfcamss_mc_init(struct platform_device *pdev, > + struct stfcamss *stfcamss) > +{ > + stfcamss->media_dev.dev = stfcamss->dev; > + strscpy(stfcamss->media_dev.model, "Starfive Camera Subsystem", > + sizeof(stfcamss->media_dev.model)); > + snprintf(stfcamss->media_dev.bus_info, > + sizeof(stfcamss->media_dev.bus_info), > + "%s:%s", dev_bus_name(&pdev->dev), pdev->name); > + stfcamss->media_dev.hw_revision = 0x01; > + stfcamss->media_dev.ops = &stfcamss_media_ops; > + media_device_init(&stfcamss->media_dev); > + > + stfcamss->v4l2_dev.mdev = &stfcamss->media_dev; > +} > + > +/* > + * stfcamss_probe - Probe STFCAMSS platform device > + * @pdev: Pointer to STFCAMSS platform device > + * > + * Return 0 on success or a negative error code on failure > + */ > +static int stfcamss_probe(struct platform_device *pdev) > +{ > + struct stfcamss *stfcamss; > + struct device *dev = &pdev->dev; > + int ret = 0, i, num_subdevs; > + > + stfcamss = devm_kzalloc(dev, sizeof(*stfcamss), GFP_KERNEL); > + if (!stfcamss) > + return -ENOMEM; > + > + for (i = 0; i < ARRAY_SIZE(stfcamss->irq); ++i) { > + stfcamss->irq[i] = platform_get_irq(pdev, i); > + if (stfcamss->irq[i] < 0) > + return dev_err_probe(&pdev->dev, stfcamss->irq[i], > + "Failed to get clock%d", i); > + } > + > + stfcamss->nclks = ARRAY_SIZE(stfcamss->sys_clk); > + for (i = 0; i < ARRAY_SIZE(stfcamss->sys_clk); ++i) Why use ARRAY_SIZE twice - you just derived stfcamss->nclks > + stfcamss->sys_clk[i].id = stfcamss_clocks[i]; > + ret = devm_clk_bulk_get(dev, stfcamss->nclks, stfcamss->sys_clk); > + if (ret) { > + dev_err(dev, "Failed to get clk controls\n"); > + return ret; > + } > + > + stfcamss->nrsts = ARRAY_SIZE(stfcamss->sys_rst); > + for (i = 0; i < ARRAY_SIZE(stfcamss->sys_rst); ++i) i < stfcamss->nrsts > + stfcamss->sys_rst[i].id = stfcamss_resets[i]; > + ret = devm_reset_control_bulk_get_shared(dev, stfcamss->nrsts, > + stfcamss->sys_rst); > + if (ret) { > + dev_err(dev, "Failed to get reset controls\n"); > + return ret; > + } > + > + ret = stfcamss_get_mem_res(pdev, stfcamss); > + if (ret) { > + dev_err(dev, "Could not map registers\n"); > + return ret; > + } > + > + stfcamss->dev = dev; > + platform_set_drvdata(pdev, stfcamss); > + > + v4l2_async_nf_init(&stfcamss->notifier); > + > + num_subdevs = stfcamss_of_parse_ports(stfcamss); > + if (num_subdevs < 0) { > + dev_err(dev, "Failed to find subdevices\n"); > + return -ENODEV; > + } > + > + stfcamss_mc_init(pdev, stfcamss); > + > + ret = v4l2_device_register(stfcamss->dev, &stfcamss->v4l2_dev); > + if (ret < 0) { > + dev_err(dev, "Failed to register V4L2 device: %d\n", ret); > + goto err_cleanup_notifier; > + } > + > + ret = media_device_register(&stfcamss->media_dev); > + if (ret) { > + dev_err(dev, "Failed to register media device: %d\n", ret); > + goto err_unregister_device; > + } > + > + stfcamss->notifier.ops = &stfcamss_subdev_notifier_ops; > + ret = v4l2_async_nf_register(&stfcamss->v4l2_dev, &stfcamss->notifier); > + if (ret) { > + dev_err(dev, "Failed to register async subdev nodes: %d\n", > + ret); > + goto err_unregister_media_dev; > + } > + > + pm_runtime_enable(dev); > + > + return 0; > + > +err_unregister_media_dev: > + media_device_unregister(&stfcamss->media_dev); > +err_unregister_device: > + v4l2_device_unregister(&stfcamss->v4l2_dev); > +err_cleanup_notifier: > + v4l2_async_nf_cleanup(&stfcamss->notifier); > + return ret; > +} > + > +/* > + * stfcamss_remove - Remove STFCAMSS platform device > + * @pdev: Pointer to STFCAMSS platform device > + * > + * Always returns 0. > + */ > +static int stfcamss_remove(struct platform_device *pdev) > +{ > + struct stfcamss *stfcamss = platform_get_drvdata(pdev); > + > + v4l2_device_unregister(&stfcamss->v4l2_dev); > + media_device_cleanup(&stfcamss->media_dev); > + pm_runtime_disable(&pdev->dev); > + > + return 0; > +} > + > +static const struct of_device_id stfcamss_of_match[] = { > + { .compatible = "starfive,jh7110-camss" }, > + { /* sentinel */ }, > +}; > + > +MODULE_DEVICE_TABLE(of, stfcamss_of_match); > + > +static int __maybe_unused stfcamss_runtime_suspend(struct device *dev) > +{ > + struct stfcamss *stfcamss = dev_get_drvdata(dev); > + > + reset_control_assert(stfcamss->sys_rst[STF_RST_ISP_TOP_AXI].rstc); > + reset_control_assert(stfcamss->sys_rst[STF_RST_ISP_TOP_N].rstc); > + clk_disable_unprepare(stfcamss->sys_clk[STF_CLK_ISP_AXI].clk); > + clk_disable_unprepare(stfcamss->sys_clk[STF_CLK_ISPCORE_2X].clk); > + > + return 0; > +} > + > +static int __maybe_unused stfcamss_runtime_resume(struct device *dev) > +{ > + struct stfcamss *stfcamss = dev_get_drvdata(dev); > + > + clk_prepare_enable(stfcamss->sys_clk[STF_CLK_ISPCORE_2X].clk); > + clk_prepare_enable(stfcamss->sys_clk[STF_CLK_ISP_AXI].clk); > + reset_control_deassert(stfcamss->sys_rst[STF_RST_ISP_TOP_N].rstc); > + reset_control_deassert(stfcamss->sys_rst[STF_RST_ISP_TOP_AXI].rstc); > + > + return 0; > +} > + > +static const struct dev_pm_ops stfcamss_pm_ops = { > + SET_RUNTIME_PM_OPS(stfcamss_runtime_suspend, > + stfcamss_runtime_resume, > + NULL) > +}; > + > +static struct platform_driver stfcamss_driver = { > + .probe = stfcamss_probe, > + .remove = stfcamss_remove, > + .driver = { > + .name = DRV_NAME, > + .pm = &stfcamss_pm_ops, > + .of_match_table = of_match_ptr(stfcamss_of_match), > + }, > +}; > + > +module_platform_driver(stfcamss_driver); > + > +MODULE_AUTHOR("StarFive Corporation"); > +MODULE_DESCRIPTION("StarFive Camera Subsystem driver"); > +MODULE_LICENSE("GPL"); > diff --git a/drivers/media/platform/starfive/stf_camss.h b/drivers/media/platform/starfive/stf_camss.h > new file mode 100644 > index 000000000000..8f39cd65950c > --- /dev/null > +++ b/drivers/media/platform/starfive/stf_camss.h > @@ -0,0 +1,153 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > +/* > + * stf_camss.h > + * > + * Starfive Camera Subsystem driver > + * > + * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. > + */ > + > +#ifndef STF_CAMSS_H > +#define STF_CAMSS_H > + > +#include <linux/clk.h> > +#include <linux/delay.h> > +#include <linux/reset.h> > +#include <media/v4l2-device.h> > + > +#include "stf_common.h" > + > +#define DRV_NAME "starfive-camss" > +#define STF_DVP_NAME "stf_dvp" > +#define STF_CSI_NAME "cdns_csi2rx" > +#define STF_ISP_NAME "stf_isp" > +#define STF_VIN_NAME "stf_vin" > + > +#define STF_PAD_SINK 0 > +#define STF_PAD_SRC 1 > +#define STF_PADS_NUM 2 > + > +enum port_num { > + PORT_NUMBER_DVP_SENSOR = 0, > + PORT_NUMBER_CSI2RX > +}; > + > +enum stf_clk { > + STF_CLK_APB_FUNC = 0, > + STF_CLK_WRAPPER_CLK_C, > + STF_CLK_DVP_INV, > + STF_CLK_AXIWR, > + STF_CLK_MIPI_RX0_PXL, > + STF_CLK_ISPCORE_2X, > + STF_CLK_ISP_AXI, > + STF_CLK_NUM > +}; > + > +enum stf_rst { > + STF_RST_WRAPPER_P = 0, > + STF_RST_WRAPPER_C, > + STF_RST_AXIRD, > + STF_RST_AXIWR, > + STF_RST_ISP_TOP_N, > + STF_RST_ISP_TOP_AXI, > + STF_RST_NUM > +}; > + > +enum stf_irq { > + STF_IRQ_VINWR = 0, > + STF_IRQ_ISP, > + STF_IRQ_ISPCSIL, > + STF_IRQ_NUM > +}; > + > +#define HOST_ENTITY_MAX 2 > + > +struct host_data { > + struct media_entity *host_entity[HOST_ENTITY_MAX]; > +}; > + > +struct stfcamss { > + struct v4l2_device v4l2_dev; > + struct media_device media_dev; > + struct media_pipeline pipe; > + struct device *dev; > + struct v4l2_async_notifier notifier; > + struct host_data host_data; > + void __iomem *syscon_base; > + void __iomem *isp_base; > + int irq[STF_IRQ_NUM]; > + struct clk_bulk_data sys_clk[STF_CLK_NUM]; > + int nclks; > + struct reset_control_bulk_data sys_rst[STF_RST_NUM]; > + int nrsts; > +}; > + > +struct stfcamss_async_subdev { > + struct v4l2_async_subdev asd; /* must be first */ > + enum port_num port; > +}; > + > +static inline u32 stf_isp_reg_read(struct stfcamss *stfcamss, u32 reg) > +{ > + return ioread32(stfcamss->isp_base + reg); > +} > + > +static inline void stf_isp_reg_write(struct stfcamss *stfcamss, > + u32 reg, u32 val) > +{ > + iowrite32(val, stfcamss->isp_base + reg); > +} > + > +static inline void stf_isp_reg_write_delay(struct stfcamss *stfcamss, > + u32 reg, u32 val, u32 delay) > +{ > + iowrite32(val, stfcamss->isp_base + reg); > + usleep_range(1000 * delay, 1000 * delay + 100); > +} > + > +static inline void stf_isp_reg_set_bit(struct stfcamss *stfcamss, > + u32 reg, u32 mask, u32 val) > +{ > + u32 value; > + > + value = ioread32(stfcamss->isp_base + reg) & ~mask; > + val &= mask; > + val |= value; > + iowrite32(val, stfcamss->isp_base + reg); > +} > + > +static inline void stf_isp_reg_set(struct stfcamss *stfcamss, u32 reg, u32 mask) > +{ > + iowrite32(ioread32(stfcamss->isp_base + reg) | mask, > + stfcamss->isp_base + reg); > +} > + > +static inline u32 stf_syscon_reg_read(struct stfcamss *stfcamss, u32 reg) > +{ > + return ioread32(stfcamss->syscon_base + reg); > +} > + > +static inline void stf_syscon_reg_write(struct stfcamss *stfcamss, > + u32 reg, u32 val) > +{ > + iowrite32(val, stfcamss->syscon_base + reg); > +} > + > +static inline void stf_syscon_reg_set_bit(struct stfcamss *stfcamss, > + u32 reg, u32 bit_mask) > +{ > + u32 value; > + > + value = ioread32(stfcamss->syscon_base + reg); > + iowrite32(value | bit_mask, stfcamss->syscon_base + reg); > +} > + > +static inline void stf_syscon_reg_clear_bit(struct stfcamss *stfcamss, > + u32 reg, u32 bit_mask) > +{ > + u32 value; > + > + value = ioread32(stfcamss->syscon_base + reg); > + iowrite32(value & ~bit_mask, stfcamss->syscon_base + reg); > +} > +#endif /* STF_CAMSS_H */ > diff --git a/drivers/media/platform/starfive/stf_common.h b/drivers/media/platform/starfive/stf_common.h > new file mode 100644 > index 000000000000..e04fca2d9cd4 > --- /dev/null > +++ b/drivers/media/platform/starfive/stf_common.h > @@ -0,0 +1,18 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > +/* > + * stf_common.h > + * > + * StarFive Camera Subsystem - Common definitions > + * > + * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. > + */ > + > +#ifndef STF_COMMON_H > +#define STF_COMMON_H > + > +enum stf_subdev_type { > + STF_SUBDEV_TYPE_VIN, > + STF_SUBDEV_TYPE_ISP, > +}; > + > +#endif /* STF_COMMON_H */
On 2023/5/12 20:50, Bryan O'Donoghue wrote: > On 12/05/2023 11:28, Jack Zhu wrote: >> Add basic platform driver for StarFive Camera Subsystem. >> >> Signed-off-by: Jack Zhu <jack.zhu@starfivetech.com> > > A couple of nits below > > Other than those > > Reviewed-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> > >> --- >> MAINTAINERS | 1 + >> drivers/media/platform/Kconfig | 1 + >> drivers/media/platform/Makefile | 1 + >> drivers/media/platform/starfive/Kconfig | 19 + >> drivers/media/platform/starfive/Makefile | 9 + >> drivers/media/platform/starfive/stf_camss.c | 372 +++++++++++++++++++ >> drivers/media/platform/starfive/stf_camss.h | 153 ++++++++ >> drivers/media/platform/starfive/stf_common.h | 18 + >> 8 files changed, 574 insertions(+) >> create mode 100644 drivers/media/platform/starfive/Kconfig >> create mode 100644 drivers/media/platform/starfive/Makefile >> create mode 100644 drivers/media/platform/starfive/stf_camss.c >> create mode 100644 drivers/media/platform/starfive/stf_camss.h >> create mode 100644 drivers/media/platform/starfive/stf_common.h >> >> diff --git a/MAINTAINERS b/MAINTAINERS >> index 71291dc58671..33a752569942 100644 >> --- a/MAINTAINERS >> +++ b/MAINTAINERS >> @@ -20077,6 +20077,7 @@ L: linux-media@vger.kernel.org >> S: Maintained >> F: Documentation/admin-guide/media/starfive_camss.rst >> F: Documentation/devicetree/bindings/media/starfive,jh7110-camss.yaml >> +F: drivers/media/platform/starfive/ >> STARFIVE DEVICETREES >> M: Emil Renner Berthing <kernel@esmil.dk> >> diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig >> index ee579916f874..627eaa0ab3ee 100644 >> --- a/drivers/media/platform/Kconfig >> +++ b/drivers/media/platform/Kconfig >> @@ -80,6 +80,7 @@ source "drivers/media/platform/renesas/Kconfig" >> source "drivers/media/platform/rockchip/Kconfig" >> source "drivers/media/platform/samsung/Kconfig" >> source "drivers/media/platform/st/Kconfig" >> +source "drivers/media/platform/starfive/Kconfig" >> source "drivers/media/platform/sunxi/Kconfig" >> source "drivers/media/platform/ti/Kconfig" >> source "drivers/media/platform/verisilicon/Kconfig" >> diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile >> index 5453bb868e67..5a038498a370 100644 >> --- a/drivers/media/platform/Makefile >> +++ b/drivers/media/platform/Makefile >> @@ -23,6 +23,7 @@ obj-y += renesas/ >> obj-y += rockchip/ >> obj-y += samsung/ >> obj-y += st/ >> +obj-y += starfive/ >> obj-y += sunxi/ >> obj-y += ti/ >> obj-y += verisilicon/ >> diff --git a/drivers/media/platform/starfive/Kconfig b/drivers/media/platform/starfive/Kconfig >> new file mode 100644 >> index 000000000000..1a2293475bd2 >> --- /dev/null >> +++ b/drivers/media/platform/starfive/Kconfig >> @@ -0,0 +1,19 @@ >> +# SPDX-License-Identifier: GPL-2.0-only >> + >> +comment "Starfive media platform drivers" >> + >> +config VIDEO_STARFIVE_CAMSS >> + tristate "Starfive Camera Subsystem driver" >> + depends on V4L_PLATFORM_DRIVERS >> + depends on VIDEO_DEV && OF >> + depends on DMA_CMA >> + select MEDIA_CONTROLLER >> + select VIDEO_V4L2_SUBDEV_API >> + select VIDEOBUF2_DMA_CONTIG >> + select V4L2_FWNODE >> + help >> + Enable this to support for the Starfive Camera subsystem >> + found on Starfive JH7110 SoC. >> + >> + To compile this driver as a module, choose M here: the >> + module will be called stf-camss. >> diff --git a/drivers/media/platform/starfive/Makefile b/drivers/media/platform/starfive/Makefile >> new file mode 100644 >> index 000000000000..796775fa52f4 >> --- /dev/null >> +++ b/drivers/media/platform/starfive/Makefile >> @@ -0,0 +1,9 @@ >> +# SPDX-License-Identifier: GPL-2.0 >> +# >> +# Makefile for StarFive camera subsystem driver. >> +# >> + >> +starfive-camss-objs += \ >> + stf_camss.o >> + >> +obj-$(CONFIG_VIDEO_STARFIVE_CAMSS) += starfive-camss.o \ >> diff --git a/drivers/media/platform/starfive/stf_camss.c b/drivers/media/platform/starfive/stf_camss.c >> new file mode 100644 >> index 000000000000..ba3a2bc8a8ab >> --- /dev/null >> +++ b/drivers/media/platform/starfive/stf_camss.c >> @@ -0,0 +1,372 @@ >> +// SPDX-License-Identifier: GPL-2.0 >> +/* >> + * stf_camss.c >> + * >> + * Starfive Camera Subsystem driver >> + * >> + * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. >> + */ >> +#include <linux/module.h> >> +#include <linux/of.h> >> +#include <linux/of_graph.h> >> +#include <linux/platform_device.h> >> +#include <linux/pm_runtime.h> >> +#include <linux/videodev2.h> >> +#include <media/media-device.h> >> +#include <media/v4l2-async.h> >> +#include <media/v4l2-fwnode.h> >> +#include <media/v4l2-mc.h> >> + >> +#include "stf_camss.h" >> + >> +static const char * const stfcamss_clocks[] = { >> + "clk_apb_func", >> + "clk_wrapper_clk_c", >> + "clk_dvp_inv", >> + "clk_axiwr", >> + "clk_mipi_rx0_pxl", >> + "clk_ispcore_2x", >> + "clk_isp_axi", >> +}; >> + >> +static const char * const stfcamss_resets[] = { >> + "rst_wrapper_p", >> + "rst_wrapper_c", >> + "rst_axird", >> + "rst_axiwr", >> + "rst_isp_top_n", >> + "rst_isp_top_axi", >> +}; >> + >> +static int stfcamss_get_mem_res(struct platform_device *pdev, >> + struct stfcamss *stfcamss) >> +{ >> + stfcamss->syscon_base = >> + devm_platform_ioremap_resource_byname(pdev, "syscon"); >> + if (IS_ERR(stfcamss->syscon_base)) >> + return PTR_ERR(stfcamss->syscon_base); >> + >> + stfcamss->isp_base = >> + devm_platform_ioremap_resource_byname(pdev, "isp"); >> + if (IS_ERR(stfcamss->isp_base)) >> + return PTR_ERR(stfcamss->isp_base); >> + >> + return 0; >> +} >> + >> +/* >> + * stfcamss_of_parse_endpoint_node - Parse port endpoint node >> + * @dev: Device >> + * @node: Device node to be parsed >> + * @csd: Parsed data from port endpoint node >> + * >> + * Return 0 on success or a negative error code on failure >> + */ >> +static int stfcamss_of_parse_endpoint_node(struct device *dev, >> + struct device_node *node, >> + struct stfcamss_async_subdev *csd) >> +{ >> + struct v4l2_fwnode_endpoint vep = { { 0 } }; >> + >> + v4l2_fwnode_endpoint_parse(of_fwnode_handle(node), &vep); >> + dev_dbg(dev, "vep.base.port = 0x%x, id = 0x%x\n", >> + vep.base.port, vep.base.id); >> + >> + csd->port = vep.base.port; >> + >> + return 0; >> +} >> + >> +/* >> + * stfcamss_of_parse_ports - Parse ports node >> + * @stfcamss: STFCAMSS device >> + * >> + * Return number of "port" nodes found in "ports" node >> + */ >> +static int stfcamss_of_parse_ports(struct stfcamss *stfcamss) >> +{ >> + struct device *dev = stfcamss->dev; >> + struct device_node *node = NULL; >> + struct device_node *remote = NULL; >> + int ret, num_subdevs = 0; >> + >> + for_each_endpoint_of_node(dev->of_node, node) { >> + struct stfcamss_async_subdev *csd; >> + >> + if (!of_device_is_available(node)) >> + continue; >> + >> + remote = of_graph_get_remote_port_parent(node); >> + if (!remote) { >> + dev_err(dev, "Cannot get remote parent\n"); >> + ret = -EINVAL; >> + goto err_cleanup; >> + } >> + >> + csd = v4l2_async_nf_add_fwnode(&stfcamss->notifier, >> + of_fwnode_handle(remote), >> + struct stfcamss_async_subdev); >> + of_node_put(remote); >> + if (IS_ERR(csd)) { >> + ret = PTR_ERR(csd); >> + goto err_cleanup; >> + } >> + >> + ret = stfcamss_of_parse_endpoint_node(dev, node, csd); >> + if (ret < 0) >> + goto err_cleanup; >> + >> + num_subdevs++; >> + } >> + >> + return num_subdevs; >> + >> +err_cleanup: >> + of_node_put(node); >> + return ret; >> +} >> + >> +static int stfcamss_subdev_notifier_bound(struct v4l2_async_notifier *async, >> + struct v4l2_subdev *subdev, >> + struct v4l2_async_subdev *asd) >> +{ >> + struct stfcamss *stfcamss = >> + container_of(async, struct stfcamss, notifier); >> + struct host_data *host_data = &stfcamss->host_data; >> + struct media_entity *source; >> + int i, j; >> + >> + source = &subdev->entity; >> + >> + for (i = 0; i < source->num_pads; i++) { >> + if (source->pads[i].flags & MEDIA_PAD_FL_SOURCE) >> + break; >> + } >> + >> + if (i == source->num_pads) { >> + dev_err(stfcamss->dev, "No source pad in external entity\n"); >> + return -EINVAL; >> + } >> + >> + for (j = 0; host_data->host_entity[j] && (j < HOST_ENTITY_MAX); j++) { >> + struct media_entity *input; >> + int ret; >> + >> + input = host_data->host_entity[j]; >> + >> + ret = media_create_pad_link( >> + source, >> + i, >> + input, >> + STF_PAD_SINK, >> + source->function == MEDIA_ENT_F_CAM_SENSOR ? >> + MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED : >> + 0); >> + if (ret < 0) { >> + dev_err(stfcamss->dev, >> + "Failed to link %s->%s entities: %d\n", >> + source->name, input->name, ret); >> + return ret; >> + } >> + } >> + >> + return 0; >> +} >> + >> +static int stfcamss_subdev_notifier_complete(struct v4l2_async_notifier *ntf) >> +{ >> + struct stfcamss *stfcamss = >> + container_of(ntf, struct stfcamss, notifier); >> + >> + return v4l2_device_register_subdev_nodes(&stfcamss->v4l2_dev); >> +} >> + >> +static const struct v4l2_async_notifier_operations >> +stfcamss_subdev_notifier_ops = { >> + .bound = stfcamss_subdev_notifier_bound, >> + .complete = stfcamss_subdev_notifier_complete, >> +}; >> + >> +static const struct media_device_ops stfcamss_media_ops = { >> + .link_notify = v4l2_pipeline_link_notify, >> +}; >> + >> +static void stfcamss_mc_init(struct platform_device *pdev, >> + struct stfcamss *stfcamss) >> +{ >> + stfcamss->media_dev.dev = stfcamss->dev; >> + strscpy(stfcamss->media_dev.model, "Starfive Camera Subsystem", >> + sizeof(stfcamss->media_dev.model)); >> + snprintf(stfcamss->media_dev.bus_info, >> + sizeof(stfcamss->media_dev.bus_info), >> + "%s:%s", dev_bus_name(&pdev->dev), pdev->name); >> + stfcamss->media_dev.hw_revision = 0x01; >> + stfcamss->media_dev.ops = &stfcamss_media_ops; >> + media_device_init(&stfcamss->media_dev); >> + >> + stfcamss->v4l2_dev.mdev = &stfcamss->media_dev; >> +} >> + >> +/* >> + * stfcamss_probe - Probe STFCAMSS platform device >> + * @pdev: Pointer to STFCAMSS platform device >> + * >> + * Return 0 on success or a negative error code on failure >> + */ >> +static int stfcamss_probe(struct platform_device *pdev) >> +{ >> + struct stfcamss *stfcamss; >> + struct device *dev = &pdev->dev; >> + int ret = 0, i, num_subdevs; >> + >> + stfcamss = devm_kzalloc(dev, sizeof(*stfcamss), GFP_KERNEL); >> + if (!stfcamss) >> + return -ENOMEM; >> + >> + for (i = 0; i < ARRAY_SIZE(stfcamss->irq); ++i) { >> + stfcamss->irq[i] = platform_get_irq(pdev, i); >> + if (stfcamss->irq[i] < 0) >> + return dev_err_probe(&pdev->dev, stfcamss->irq[i], >> + "Failed to get clock%d", i); >> + } >> + >> + stfcamss->nclks = ARRAY_SIZE(stfcamss->sys_clk); >> + for (i = 0; i < ARRAY_SIZE(stfcamss->sys_clk); ++i) > > Why use ARRAY_SIZE twice - you just derived stfcamss->nclks OK, will use stfcamss->nclks. Thank you for your review. > >> + stfcamss->sys_clk[i].id = stfcamss_clocks[i]; >> + ret = devm_clk_bulk_get(dev, stfcamss->nclks, stfcamss->sys_clk); >> + if (ret) { >> + dev_err(dev, "Failed to get clk controls\n"); >> + return ret; >> + } >> + >> + stfcamss->nrsts = ARRAY_SIZE(stfcamss->sys_rst); >> + for (i = 0; i < ARRAY_SIZE(stfcamss->sys_rst); ++i) > > i < stfcamss->nrsts OK, will fix it. Thanks! > >> + stfcamss->sys_rst[i].id = stfcamss_resets[i]; >> + ret = devm_reset_control_bulk_get_shared(dev, stfcamss->nrsts, >> + stfcamss->sys_rst); >> + if (ret) { >> + dev_err(dev, "Failed to get reset controls\n"); >> + return ret; >> + } >> + >> + ret = stfcamss_get_mem_res(pdev, stfcamss); >> + if (ret) { >> + dev_err(dev, "Could not map registers\n"); >> + return ret; >> + } >> + >> + stfcamss->dev = dev; >> + platform_set_drvdata(pdev, stfcamss); >> + >> + v4l2_async_nf_init(&stfcamss->notifier); >> + >> + num_subdevs = stfcamss_of_parse_ports(stfcamss); >> + if (num_subdevs < 0) { >> + dev_err(dev, "Failed to find subdevices\n"); >> + return -ENODEV; >> + } >> + >> + stfcamss_mc_init(pdev, stfcamss); >> + >> + ret = v4l2_device_register(stfcamss->dev, &stfcamss->v4l2_dev); >> + if (ret < 0) { >> + dev_err(dev, "Failed to register V4L2 device: %d\n", ret); >> + goto err_cleanup_notifier; >> + } >> + >> + ret = media_device_register(&stfcamss->media_dev); >> + if (ret) { >> + dev_err(dev, "Failed to register media device: %d\n", ret); >> + goto err_unregister_device; >> + } >> + >> + stfcamss->notifier.ops = &stfcamss_subdev_notifier_ops; >> + ret = v4l2_async_nf_register(&stfcamss->v4l2_dev, &stfcamss->notifier); >> + if (ret) { >> + dev_err(dev, "Failed to register async subdev nodes: %d\n", >> + ret); >> + goto err_unregister_media_dev; >> + } >> + >> + pm_runtime_enable(dev); >> + >> + return 0; >> + >> +err_unregister_media_dev: >> + media_device_unregister(&stfcamss->media_dev); >> +err_unregister_device: >> + v4l2_device_unregister(&stfcamss->v4l2_dev); >> +err_cleanup_notifier: >> + v4l2_async_nf_cleanup(&stfcamss->notifier); >> + return ret; >> +} >> + >> +/* >> + * stfcamss_remove - Remove STFCAMSS platform device >> + * @pdev: Pointer to STFCAMSS platform device >> + * >> + * Always returns 0. >> + */ >> +static int stfcamss_remove(struct platform_device *pdev) >> +{ >> + struct stfcamss *stfcamss = platform_get_drvdata(pdev); >> + >> + v4l2_device_unregister(&stfcamss->v4l2_dev); >> + media_device_cleanup(&stfcamss->media_dev); >> + pm_runtime_disable(&pdev->dev); >> + >> + return 0; >> +} >> + >> +static const struct of_device_id stfcamss_of_match[] = { >> + { .compatible = "starfive,jh7110-camss" }, >> + { /* sentinel */ }, >> +}; >> + >> +MODULE_DEVICE_TABLE(of, stfcamss_of_match); >> + >> +static int __maybe_unused stfcamss_runtime_suspend(struct device *dev) >> +{ >> + struct stfcamss *stfcamss = dev_get_drvdata(dev); >> + >> + reset_control_assert(stfcamss->sys_rst[STF_RST_ISP_TOP_AXI].rstc); >> + reset_control_assert(stfcamss->sys_rst[STF_RST_ISP_TOP_N].rstc); >> + clk_disable_unprepare(stfcamss->sys_clk[STF_CLK_ISP_AXI].clk); >> + clk_disable_unprepare(stfcamss->sys_clk[STF_CLK_ISPCORE_2X].clk); >> + >> + return 0; >> +} >> + >> +static int __maybe_unused stfcamss_runtime_resume(struct device *dev) >> +{ >> + struct stfcamss *stfcamss = dev_get_drvdata(dev); >> + >> + clk_prepare_enable(stfcamss->sys_clk[STF_CLK_ISPCORE_2X].clk); >> + clk_prepare_enable(stfcamss->sys_clk[STF_CLK_ISP_AXI].clk); >> + reset_control_deassert(stfcamss->sys_rst[STF_RST_ISP_TOP_N].rstc); >> + reset_control_deassert(stfcamss->sys_rst[STF_RST_ISP_TOP_AXI].rstc); >> + >> + return 0; >> +} >> + >> +static const struct dev_pm_ops stfcamss_pm_ops = { >> + SET_RUNTIME_PM_OPS(stfcamss_runtime_suspend, >> + stfcamss_runtime_resume, >> + NULL) >> +}; >> + >> +static struct platform_driver stfcamss_driver = { >> + .probe = stfcamss_probe, >> + .remove = stfcamss_remove, >> + .driver = { >> + .name = DRV_NAME, >> + .pm = &stfcamss_pm_ops, >> + .of_match_table = of_match_ptr(stfcamss_of_match), >> + }, >> +}; >> + >> +module_platform_driver(stfcamss_driver); >> + >> +MODULE_AUTHOR("StarFive Corporation"); >> +MODULE_DESCRIPTION("StarFive Camera Subsystem driver"); >> +MODULE_LICENSE("GPL"); >> diff --git a/drivers/media/platform/starfive/stf_camss.h b/drivers/media/platform/starfive/stf_camss.h >> new file mode 100644 >> index 000000000000..8f39cd65950c >> --- /dev/null >> +++ b/drivers/media/platform/starfive/stf_camss.h >> @@ -0,0 +1,153 @@ >> +/* SPDX-License-Identifier: GPL-2.0 */ >> +/* >> + * stf_camss.h >> + * >> + * Starfive Camera Subsystem driver >> + * >> + * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. >> + */ >> + >> +#ifndef STF_CAMSS_H >> +#define STF_CAMSS_H >> + >> +#include <linux/clk.h> >> +#include <linux/delay.h> >> +#include <linux/reset.h> >> +#include <media/v4l2-device.h> >> + >> +#include "stf_common.h" >> + >> +#define DRV_NAME "starfive-camss" >> +#define STF_DVP_NAME "stf_dvp" >> +#define STF_CSI_NAME "cdns_csi2rx" >> +#define STF_ISP_NAME "stf_isp" >> +#define STF_VIN_NAME "stf_vin" >> + >> +#define STF_PAD_SINK 0 >> +#define STF_PAD_SRC 1 >> +#define STF_PADS_NUM 2 >> + >> +enum port_num { >> + PORT_NUMBER_DVP_SENSOR = 0, >> + PORT_NUMBER_CSI2RX >> +}; >> + >> +enum stf_clk { >> + STF_CLK_APB_FUNC = 0, >> + STF_CLK_WRAPPER_CLK_C, >> + STF_CLK_DVP_INV, >> + STF_CLK_AXIWR, >> + STF_CLK_MIPI_RX0_PXL, >> + STF_CLK_ISPCORE_2X, >> + STF_CLK_ISP_AXI, >> + STF_CLK_NUM >> +}; >> + >> +enum stf_rst { >> + STF_RST_WRAPPER_P = 0, >> + STF_RST_WRAPPER_C, >> + STF_RST_AXIRD, >> + STF_RST_AXIWR, >> + STF_RST_ISP_TOP_N, >> + STF_RST_ISP_TOP_AXI, >> + STF_RST_NUM >> +}; >> + >> +enum stf_irq { >> + STF_IRQ_VINWR = 0, >> + STF_IRQ_ISP, >> + STF_IRQ_ISPCSIL, >> + STF_IRQ_NUM >> +}; >> + >> +#define HOST_ENTITY_MAX 2 >> + >> +struct host_data { >> + struct media_entity *host_entity[HOST_ENTITY_MAX]; >> +}; >> + >> +struct stfcamss { >> + struct v4l2_device v4l2_dev; >> + struct media_device media_dev; >> + struct media_pipeline pipe; >> + struct device *dev; >> + struct v4l2_async_notifier notifier; >> + struct host_data host_data; >> + void __iomem *syscon_base; >> + void __iomem *isp_base; >> + int irq[STF_IRQ_NUM]; >> + struct clk_bulk_data sys_clk[STF_CLK_NUM]; >> + int nclks; >> + struct reset_control_bulk_data sys_rst[STF_RST_NUM]; >> + int nrsts; >> +}; >> + >> +struct stfcamss_async_subdev { >> + struct v4l2_async_subdev asd; /* must be first */ >> + enum port_num port; >> +}; >> + >> +static inline u32 stf_isp_reg_read(struct stfcamss *stfcamss, u32 reg) >> +{ >> + return ioread32(stfcamss->isp_base + reg); >> +} >> + >> +static inline void stf_isp_reg_write(struct stfcamss *stfcamss, >> + u32 reg, u32 val) >> +{ >> + iowrite32(val, stfcamss->isp_base + reg); >> +} >> + >> +static inline void stf_isp_reg_write_delay(struct stfcamss *stfcamss, >> + u32 reg, u32 val, u32 delay) >> +{ >> + iowrite32(val, stfcamss->isp_base + reg); >> + usleep_range(1000 * delay, 1000 * delay + 100); >> +} >> + >> +static inline void stf_isp_reg_set_bit(struct stfcamss *stfcamss, >> + u32 reg, u32 mask, u32 val) >> +{ >> + u32 value; >> + >> + value = ioread32(stfcamss->isp_base + reg) & ~mask; >> + val &= mask; >> + val |= value; >> + iowrite32(val, stfcamss->isp_base + reg); >> +} >> + >> +static inline void stf_isp_reg_set(struct stfcamss *stfcamss, u32 reg, u32 mask) >> +{ >> + iowrite32(ioread32(stfcamss->isp_base + reg) | mask, >> + stfcamss->isp_base + reg); >> +} >> + >> +static inline u32 stf_syscon_reg_read(struct stfcamss *stfcamss, u32 reg) >> +{ >> + return ioread32(stfcamss->syscon_base + reg); >> +} >> + >> +static inline void stf_syscon_reg_write(struct stfcamss *stfcamss, >> + u32 reg, u32 val) >> +{ >> + iowrite32(val, stfcamss->syscon_base + reg); >> +} >> + >> +static inline void stf_syscon_reg_set_bit(struct stfcamss *stfcamss, >> + u32 reg, u32 bit_mask) >> +{ >> + u32 value; >> + >> + value = ioread32(stfcamss->syscon_base + reg); >> + iowrite32(value | bit_mask, stfcamss->syscon_base + reg); >> +} >> + >> +static inline void stf_syscon_reg_clear_bit(struct stfcamss *stfcamss, >> + u32 reg, u32 bit_mask) >> +{ >> + u32 value; >> + >> + value = ioread32(stfcamss->syscon_base + reg); >> + iowrite32(value & ~bit_mask, stfcamss->syscon_base + reg); >> +} >> +#endif /* STF_CAMSS_H */ >> diff --git a/drivers/media/platform/starfive/stf_common.h b/drivers/media/platform/starfive/stf_common.h >> new file mode 100644 >> index 000000000000..e04fca2d9cd4 >> --- /dev/null >> +++ b/drivers/media/platform/starfive/stf_common.h >> @@ -0,0 +1,18 @@ >> +/* SPDX-License-Identifier: GPL-2.0 */ >> +/* >> + * stf_common.h >> + * >> + * StarFive Camera Subsystem - Common definitions >> + * >> + * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. >> + */ >> + >> +#ifndef STF_COMMON_H >> +#define STF_COMMON_H >> + >> +enum stf_subdev_type { >> + STF_SUBDEV_TYPE_VIN, >> + STF_SUBDEV_TYPE_ISP, >> +}; >> + >> +#endif /* STF_COMMON_H */ >
diff --git a/MAINTAINERS b/MAINTAINERS index 71291dc58671..33a752569942 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20077,6 +20077,7 @@ L: linux-media@vger.kernel.org S: Maintained F: Documentation/admin-guide/media/starfive_camss.rst F: Documentation/devicetree/bindings/media/starfive,jh7110-camss.yaml +F: drivers/media/platform/starfive/ STARFIVE DEVICETREES M: Emil Renner Berthing <kernel@esmil.dk> diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig index ee579916f874..627eaa0ab3ee 100644 --- a/drivers/media/platform/Kconfig +++ b/drivers/media/platform/Kconfig @@ -80,6 +80,7 @@ source "drivers/media/platform/renesas/Kconfig" source "drivers/media/platform/rockchip/Kconfig" source "drivers/media/platform/samsung/Kconfig" source "drivers/media/platform/st/Kconfig" +source "drivers/media/platform/starfive/Kconfig" source "drivers/media/platform/sunxi/Kconfig" source "drivers/media/platform/ti/Kconfig" source "drivers/media/platform/verisilicon/Kconfig" diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile index 5453bb868e67..5a038498a370 100644 --- a/drivers/media/platform/Makefile +++ b/drivers/media/platform/Makefile @@ -23,6 +23,7 @@ obj-y += renesas/ obj-y += rockchip/ obj-y += samsung/ obj-y += st/ +obj-y += starfive/ obj-y += sunxi/ obj-y += ti/ obj-y += verisilicon/ diff --git a/drivers/media/platform/starfive/Kconfig b/drivers/media/platform/starfive/Kconfig new file mode 100644 index 000000000000..1a2293475bd2 --- /dev/null +++ b/drivers/media/platform/starfive/Kconfig @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0-only + +comment "Starfive media platform drivers" + +config VIDEO_STARFIVE_CAMSS + tristate "Starfive Camera Subsystem driver" + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV && OF + depends on DMA_CMA + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select VIDEOBUF2_DMA_CONTIG + select V4L2_FWNODE + help + Enable this to support for the Starfive Camera subsystem + found on Starfive JH7110 SoC. + + To compile this driver as a module, choose M here: the + module will be called stf-camss. diff --git a/drivers/media/platform/starfive/Makefile b/drivers/media/platform/starfive/Makefile new file mode 100644 index 000000000000..796775fa52f4 --- /dev/null +++ b/drivers/media/platform/starfive/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for StarFive camera subsystem driver. +# + +starfive-camss-objs += \ + stf_camss.o + +obj-$(CONFIG_VIDEO_STARFIVE_CAMSS) += starfive-camss.o \ diff --git a/drivers/media/platform/starfive/stf_camss.c b/drivers/media/platform/starfive/stf_camss.c new file mode 100644 index 000000000000..ba3a2bc8a8ab --- /dev/null +++ b/drivers/media/platform/starfive/stf_camss.c @@ -0,0 +1,372 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * stf_camss.c + * + * Starfive Camera Subsystem driver + * + * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. + */ +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/videodev2.h> +#include <media/media-device.h> +#include <media/v4l2-async.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-mc.h> + +#include "stf_camss.h" + +static const char * const stfcamss_clocks[] = { + "clk_apb_func", + "clk_wrapper_clk_c", + "clk_dvp_inv", + "clk_axiwr", + "clk_mipi_rx0_pxl", + "clk_ispcore_2x", + "clk_isp_axi", +}; + +static const char * const stfcamss_resets[] = { + "rst_wrapper_p", + "rst_wrapper_c", + "rst_axird", + "rst_axiwr", + "rst_isp_top_n", + "rst_isp_top_axi", +}; + +static int stfcamss_get_mem_res(struct platform_device *pdev, + struct stfcamss *stfcamss) +{ + stfcamss->syscon_base = + devm_platform_ioremap_resource_byname(pdev, "syscon"); + if (IS_ERR(stfcamss->syscon_base)) + return PTR_ERR(stfcamss->syscon_base); + + stfcamss->isp_base = + devm_platform_ioremap_resource_byname(pdev, "isp"); + if (IS_ERR(stfcamss->isp_base)) + return PTR_ERR(stfcamss->isp_base); + + return 0; +} + +/* + * stfcamss_of_parse_endpoint_node - Parse port endpoint node + * @dev: Device + * @node: Device node to be parsed + * @csd: Parsed data from port endpoint node + * + * Return 0 on success or a negative error code on failure + */ +static int stfcamss_of_parse_endpoint_node(struct device *dev, + struct device_node *node, + struct stfcamss_async_subdev *csd) +{ + struct v4l2_fwnode_endpoint vep = { { 0 } }; + + v4l2_fwnode_endpoint_parse(of_fwnode_handle(node), &vep); + dev_dbg(dev, "vep.base.port = 0x%x, id = 0x%x\n", + vep.base.port, vep.base.id); + + csd->port = vep.base.port; + + return 0; +} + +/* + * stfcamss_of_parse_ports - Parse ports node + * @stfcamss: STFCAMSS device + * + * Return number of "port" nodes found in "ports" node + */ +static int stfcamss_of_parse_ports(struct stfcamss *stfcamss) +{ + struct device *dev = stfcamss->dev; + struct device_node *node = NULL; + struct device_node *remote = NULL; + int ret, num_subdevs = 0; + + for_each_endpoint_of_node(dev->of_node, node) { + struct stfcamss_async_subdev *csd; + + if (!of_device_is_available(node)) + continue; + + remote = of_graph_get_remote_port_parent(node); + if (!remote) { + dev_err(dev, "Cannot get remote parent\n"); + ret = -EINVAL; + goto err_cleanup; + } + + csd = v4l2_async_nf_add_fwnode(&stfcamss->notifier, + of_fwnode_handle(remote), + struct stfcamss_async_subdev); + of_node_put(remote); + if (IS_ERR(csd)) { + ret = PTR_ERR(csd); + goto err_cleanup; + } + + ret = stfcamss_of_parse_endpoint_node(dev, node, csd); + if (ret < 0) + goto err_cleanup; + + num_subdevs++; + } + + return num_subdevs; + +err_cleanup: + of_node_put(node); + return ret; +} + +static int stfcamss_subdev_notifier_bound(struct v4l2_async_notifier *async, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct stfcamss *stfcamss = + container_of(async, struct stfcamss, notifier); + struct host_data *host_data = &stfcamss->host_data; + struct media_entity *source; + int i, j; + + source = &subdev->entity; + + for (i = 0; i < source->num_pads; i++) { + if (source->pads[i].flags & MEDIA_PAD_FL_SOURCE) + break; + } + + if (i == source->num_pads) { + dev_err(stfcamss->dev, "No source pad in external entity\n"); + return -EINVAL; + } + + for (j = 0; host_data->host_entity[j] && (j < HOST_ENTITY_MAX); j++) { + struct media_entity *input; + int ret; + + input = host_data->host_entity[j]; + + ret = media_create_pad_link( + source, + i, + input, + STF_PAD_SINK, + source->function == MEDIA_ENT_F_CAM_SENSOR ? + MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED : + 0); + if (ret < 0) { + dev_err(stfcamss->dev, + "Failed to link %s->%s entities: %d\n", + source->name, input->name, ret); + return ret; + } + } + + return 0; +} + +static int stfcamss_subdev_notifier_complete(struct v4l2_async_notifier *ntf) +{ + struct stfcamss *stfcamss = + container_of(ntf, struct stfcamss, notifier); + + return v4l2_device_register_subdev_nodes(&stfcamss->v4l2_dev); +} + +static const struct v4l2_async_notifier_operations +stfcamss_subdev_notifier_ops = { + .bound = stfcamss_subdev_notifier_bound, + .complete = stfcamss_subdev_notifier_complete, +}; + +static const struct media_device_ops stfcamss_media_ops = { + .link_notify = v4l2_pipeline_link_notify, +}; + +static void stfcamss_mc_init(struct platform_device *pdev, + struct stfcamss *stfcamss) +{ + stfcamss->media_dev.dev = stfcamss->dev; + strscpy(stfcamss->media_dev.model, "Starfive Camera Subsystem", + sizeof(stfcamss->media_dev.model)); + snprintf(stfcamss->media_dev.bus_info, + sizeof(stfcamss->media_dev.bus_info), + "%s:%s", dev_bus_name(&pdev->dev), pdev->name); + stfcamss->media_dev.hw_revision = 0x01; + stfcamss->media_dev.ops = &stfcamss_media_ops; + media_device_init(&stfcamss->media_dev); + + stfcamss->v4l2_dev.mdev = &stfcamss->media_dev; +} + +/* + * stfcamss_probe - Probe STFCAMSS platform device + * @pdev: Pointer to STFCAMSS platform device + * + * Return 0 on success or a negative error code on failure + */ +static int stfcamss_probe(struct platform_device *pdev) +{ + struct stfcamss *stfcamss; + struct device *dev = &pdev->dev; + int ret = 0, i, num_subdevs; + + stfcamss = devm_kzalloc(dev, sizeof(*stfcamss), GFP_KERNEL); + if (!stfcamss) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(stfcamss->irq); ++i) { + stfcamss->irq[i] = platform_get_irq(pdev, i); + if (stfcamss->irq[i] < 0) + return dev_err_probe(&pdev->dev, stfcamss->irq[i], + "Failed to get clock%d", i); + } + + stfcamss->nclks = ARRAY_SIZE(stfcamss->sys_clk); + for (i = 0; i < ARRAY_SIZE(stfcamss->sys_clk); ++i) + stfcamss->sys_clk[i].id = stfcamss_clocks[i]; + ret = devm_clk_bulk_get(dev, stfcamss->nclks, stfcamss->sys_clk); + if (ret) { + dev_err(dev, "Failed to get clk controls\n"); + return ret; + } + + stfcamss->nrsts = ARRAY_SIZE(stfcamss->sys_rst); + for (i = 0; i < ARRAY_SIZE(stfcamss->sys_rst); ++i) + stfcamss->sys_rst[i].id = stfcamss_resets[i]; + ret = devm_reset_control_bulk_get_shared(dev, stfcamss->nrsts, + stfcamss->sys_rst); + if (ret) { + dev_err(dev, "Failed to get reset controls\n"); + return ret; + } + + ret = stfcamss_get_mem_res(pdev, stfcamss); + if (ret) { + dev_err(dev, "Could not map registers\n"); + return ret; + } + + stfcamss->dev = dev; + platform_set_drvdata(pdev, stfcamss); + + v4l2_async_nf_init(&stfcamss->notifier); + + num_subdevs = stfcamss_of_parse_ports(stfcamss); + if (num_subdevs < 0) { + dev_err(dev, "Failed to find subdevices\n"); + return -ENODEV; + } + + stfcamss_mc_init(pdev, stfcamss); + + ret = v4l2_device_register(stfcamss->dev, &stfcamss->v4l2_dev); + if (ret < 0) { + dev_err(dev, "Failed to register V4L2 device: %d\n", ret); + goto err_cleanup_notifier; + } + + ret = media_device_register(&stfcamss->media_dev); + if (ret) { + dev_err(dev, "Failed to register media device: %d\n", ret); + goto err_unregister_device; + } + + stfcamss->notifier.ops = &stfcamss_subdev_notifier_ops; + ret = v4l2_async_nf_register(&stfcamss->v4l2_dev, &stfcamss->notifier); + if (ret) { + dev_err(dev, "Failed to register async subdev nodes: %d\n", + ret); + goto err_unregister_media_dev; + } + + pm_runtime_enable(dev); + + return 0; + +err_unregister_media_dev: + media_device_unregister(&stfcamss->media_dev); +err_unregister_device: + v4l2_device_unregister(&stfcamss->v4l2_dev); +err_cleanup_notifier: + v4l2_async_nf_cleanup(&stfcamss->notifier); + return ret; +} + +/* + * stfcamss_remove - Remove STFCAMSS platform device + * @pdev: Pointer to STFCAMSS platform device + * + * Always returns 0. + */ +static int stfcamss_remove(struct platform_device *pdev) +{ + struct stfcamss *stfcamss = platform_get_drvdata(pdev); + + v4l2_device_unregister(&stfcamss->v4l2_dev); + media_device_cleanup(&stfcamss->media_dev); + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static const struct of_device_id stfcamss_of_match[] = { + { .compatible = "starfive,jh7110-camss" }, + { /* sentinel */ }, +}; + +MODULE_DEVICE_TABLE(of, stfcamss_of_match); + +static int __maybe_unused stfcamss_runtime_suspend(struct device *dev) +{ + struct stfcamss *stfcamss = dev_get_drvdata(dev); + + reset_control_assert(stfcamss->sys_rst[STF_RST_ISP_TOP_AXI].rstc); + reset_control_assert(stfcamss->sys_rst[STF_RST_ISP_TOP_N].rstc); + clk_disable_unprepare(stfcamss->sys_clk[STF_CLK_ISP_AXI].clk); + clk_disable_unprepare(stfcamss->sys_clk[STF_CLK_ISPCORE_2X].clk); + + return 0; +} + +static int __maybe_unused stfcamss_runtime_resume(struct device *dev) +{ + struct stfcamss *stfcamss = dev_get_drvdata(dev); + + clk_prepare_enable(stfcamss->sys_clk[STF_CLK_ISPCORE_2X].clk); + clk_prepare_enable(stfcamss->sys_clk[STF_CLK_ISP_AXI].clk); + reset_control_deassert(stfcamss->sys_rst[STF_RST_ISP_TOP_N].rstc); + reset_control_deassert(stfcamss->sys_rst[STF_RST_ISP_TOP_AXI].rstc); + + return 0; +} + +static const struct dev_pm_ops stfcamss_pm_ops = { + SET_RUNTIME_PM_OPS(stfcamss_runtime_suspend, + stfcamss_runtime_resume, + NULL) +}; + +static struct platform_driver stfcamss_driver = { + .probe = stfcamss_probe, + .remove = stfcamss_remove, + .driver = { + .name = DRV_NAME, + .pm = &stfcamss_pm_ops, + .of_match_table = of_match_ptr(stfcamss_of_match), + }, +}; + +module_platform_driver(stfcamss_driver); + +MODULE_AUTHOR("StarFive Corporation"); +MODULE_DESCRIPTION("StarFive Camera Subsystem driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/starfive/stf_camss.h b/drivers/media/platform/starfive/stf_camss.h new file mode 100644 index 000000000000..8f39cd65950c --- /dev/null +++ b/drivers/media/platform/starfive/stf_camss.h @@ -0,0 +1,153 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * stf_camss.h + * + * Starfive Camera Subsystem driver + * + * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. + */ + +#ifndef STF_CAMSS_H +#define STF_CAMSS_H + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/reset.h> +#include <media/v4l2-device.h> + +#include "stf_common.h" + +#define DRV_NAME "starfive-camss" +#define STF_DVP_NAME "stf_dvp" +#define STF_CSI_NAME "cdns_csi2rx" +#define STF_ISP_NAME "stf_isp" +#define STF_VIN_NAME "stf_vin" + +#define STF_PAD_SINK 0 +#define STF_PAD_SRC 1 +#define STF_PADS_NUM 2 + +enum port_num { + PORT_NUMBER_DVP_SENSOR = 0, + PORT_NUMBER_CSI2RX +}; + +enum stf_clk { + STF_CLK_APB_FUNC = 0, + STF_CLK_WRAPPER_CLK_C, + STF_CLK_DVP_INV, + STF_CLK_AXIWR, + STF_CLK_MIPI_RX0_PXL, + STF_CLK_ISPCORE_2X, + STF_CLK_ISP_AXI, + STF_CLK_NUM +}; + +enum stf_rst { + STF_RST_WRAPPER_P = 0, + STF_RST_WRAPPER_C, + STF_RST_AXIRD, + STF_RST_AXIWR, + STF_RST_ISP_TOP_N, + STF_RST_ISP_TOP_AXI, + STF_RST_NUM +}; + +enum stf_irq { + STF_IRQ_VINWR = 0, + STF_IRQ_ISP, + STF_IRQ_ISPCSIL, + STF_IRQ_NUM +}; + +#define HOST_ENTITY_MAX 2 + +struct host_data { + struct media_entity *host_entity[HOST_ENTITY_MAX]; +}; + +struct stfcamss { + struct v4l2_device v4l2_dev; + struct media_device media_dev; + struct media_pipeline pipe; + struct device *dev; + struct v4l2_async_notifier notifier; + struct host_data host_data; + void __iomem *syscon_base; + void __iomem *isp_base; + int irq[STF_IRQ_NUM]; + struct clk_bulk_data sys_clk[STF_CLK_NUM]; + int nclks; + struct reset_control_bulk_data sys_rst[STF_RST_NUM]; + int nrsts; +}; + +struct stfcamss_async_subdev { + struct v4l2_async_subdev asd; /* must be first */ + enum port_num port; +}; + +static inline u32 stf_isp_reg_read(struct stfcamss *stfcamss, u32 reg) +{ + return ioread32(stfcamss->isp_base + reg); +} + +static inline void stf_isp_reg_write(struct stfcamss *stfcamss, + u32 reg, u32 val) +{ + iowrite32(val, stfcamss->isp_base + reg); +} + +static inline void stf_isp_reg_write_delay(struct stfcamss *stfcamss, + u32 reg, u32 val, u32 delay) +{ + iowrite32(val, stfcamss->isp_base + reg); + usleep_range(1000 * delay, 1000 * delay + 100); +} + +static inline void stf_isp_reg_set_bit(struct stfcamss *stfcamss, + u32 reg, u32 mask, u32 val) +{ + u32 value; + + value = ioread32(stfcamss->isp_base + reg) & ~mask; + val &= mask; + val |= value; + iowrite32(val, stfcamss->isp_base + reg); +} + +static inline void stf_isp_reg_set(struct stfcamss *stfcamss, u32 reg, u32 mask) +{ + iowrite32(ioread32(stfcamss->isp_base + reg) | mask, + stfcamss->isp_base + reg); +} + +static inline u32 stf_syscon_reg_read(struct stfcamss *stfcamss, u32 reg) +{ + return ioread32(stfcamss->syscon_base + reg); +} + +static inline void stf_syscon_reg_write(struct stfcamss *stfcamss, + u32 reg, u32 val) +{ + iowrite32(val, stfcamss->syscon_base + reg); +} + +static inline void stf_syscon_reg_set_bit(struct stfcamss *stfcamss, + u32 reg, u32 bit_mask) +{ + u32 value; + + value = ioread32(stfcamss->syscon_base + reg); + iowrite32(value | bit_mask, stfcamss->syscon_base + reg); +} + +static inline void stf_syscon_reg_clear_bit(struct stfcamss *stfcamss, + u32 reg, u32 bit_mask) +{ + u32 value; + + value = ioread32(stfcamss->syscon_base + reg); + iowrite32(value & ~bit_mask, stfcamss->syscon_base + reg); +} +#endif /* STF_CAMSS_H */ diff --git a/drivers/media/platform/starfive/stf_common.h b/drivers/media/platform/starfive/stf_common.h new file mode 100644 index 000000000000..e04fca2d9cd4 --- /dev/null +++ b/drivers/media/platform/starfive/stf_common.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * stf_common.h + * + * StarFive Camera Subsystem - Common definitions + * + * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. + */ + +#ifndef STF_COMMON_H +#define STF_COMMON_H + +enum stf_subdev_type { + STF_SUBDEV_TYPE_VIN, + STF_SUBDEV_TYPE_ISP, +}; + +#endif /* STF_COMMON_H */
Add basic platform driver for StarFive Camera Subsystem. Signed-off-by: Jack Zhu <jack.zhu@starfivetech.com> --- MAINTAINERS | 1 + drivers/media/platform/Kconfig | 1 + drivers/media/platform/Makefile | 1 + drivers/media/platform/starfive/Kconfig | 19 + drivers/media/platform/starfive/Makefile | 9 + drivers/media/platform/starfive/stf_camss.c | 372 +++++++++++++++++++ drivers/media/platform/starfive/stf_camss.h | 153 ++++++++ drivers/media/platform/starfive/stf_common.h | 18 + 8 files changed, 574 insertions(+) create mode 100644 drivers/media/platform/starfive/Kconfig create mode 100644 drivers/media/platform/starfive/Makefile create mode 100644 drivers/media/platform/starfive/stf_camss.c create mode 100644 drivers/media/platform/starfive/stf_camss.h create mode 100644 drivers/media/platform/starfive/stf_common.h