From 48fa558e2ae91e3df60605d43c85663174ce3772 Mon Sep 17 00:00:00 2001 From: Jens Reidel Date: Sun, 21 Apr 2024 21:42:29 +0200 Subject: [PATCH] media: Import xiaomi camera ispv3 driver Change-Id: If359c12219100477c49d0bac563158921754c6ee Signed-off-by: Jens Reidel --- drivers/media/platform/Kconfig | 1 + drivers/media/platform/Makefile | 1 + drivers/media/platform/xiaomi/Kconfig | 1 + drivers/media/platform/xiaomi/Makefile | 2 + drivers/media/platform/xiaomi/ispv3/Kconfig | 6 + drivers/media/platform/xiaomi/ispv3/Makefile | 6 + .../platform/xiaomi/ispv3/ispv3_cam_dev.c | 263 +++++ .../platform/xiaomi/ispv3/ispv3_cam_dev.h | 30 + .../media/platform/xiaomi/ispv3/ispv3_dev.c | 902 ++++++++++++++++++ .../media/platform/xiaomi/ispv3/ispv3_power.c | 447 +++++++++ include/linux/mfd/ispv3_dev.h | 156 +++ include/uapi/linux/ispv3_ioparam.h | 42 + include/uapi/media/ispv3_defs.h | 125 +++ 13 files changed, 1982 insertions(+) create mode 100644 drivers/media/platform/xiaomi/Kconfig create mode 100644 drivers/media/platform/xiaomi/Makefile create mode 100644 drivers/media/platform/xiaomi/ispv3/Kconfig create mode 100644 drivers/media/platform/xiaomi/ispv3/Makefile create mode 100644 drivers/media/platform/xiaomi/ispv3/ispv3_cam_dev.c create mode 100644 drivers/media/platform/xiaomi/ispv3/ispv3_cam_dev.h create mode 100644 drivers/media/platform/xiaomi/ispv3/ispv3_dev.c create mode 100644 drivers/media/platform/xiaomi/ispv3/ispv3_power.c create mode 100644 include/linux/mfd/ispv3_dev.h create mode 100644 include/uapi/linux/ispv3_ioparam.h create mode 100644 include/uapi/media/ispv3_defs.h diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig index 29ff7354e702..248ae7dce7ed 100644 --- a/drivers/media/platform/Kconfig +++ b/drivers/media/platform/Kconfig @@ -586,3 +586,4 @@ config VIDEO_RCAR_DRIF endif # SDR_PLATFORM_DRIVERS source "drivers/media/platform/msm/Kconfig" +source "drivers/media/platform/xiaomi/Kconfig" diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile index 2fe913b829b7..5774901e69a5 100644 --- a/drivers/media/platform/Makefile +++ b/drivers/media/platform/Makefile @@ -82,3 +82,4 @@ obj-$(CONFIG_VIDEO_QCOM_VENUS) += qcom/venus/ obj-y += sunxi/ obj-y += msm/ +obj-y += xiaomi/ diff --git a/drivers/media/platform/xiaomi/Kconfig b/drivers/media/platform/xiaomi/Kconfig new file mode 100644 index 000000000000..de1c377fb681 --- /dev/null +++ b/drivers/media/platform/xiaomi/Kconfig @@ -0,0 +1 @@ +source "drivers/media/platform/xiaomi/ispv3/Kconfig" diff --git a/drivers/media/platform/xiaomi/Makefile b/drivers/media/platform/xiaomi/Makefile new file mode 100644 index 000000000000..b8b06504cded --- /dev/null +++ b/drivers/media/platform/xiaomi/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_ISPV3) += ispv3/ +#obj-m += ispv3/ diff --git a/drivers/media/platform/xiaomi/ispv3/Kconfig b/drivers/media/platform/xiaomi/ispv3/Kconfig new file mode 100644 index 000000000000..179e95007497 --- /dev/null +++ b/drivers/media/platform/xiaomi/ispv3/Kconfig @@ -0,0 +1,6 @@ +config ISPV3 + tristate "XIAOMI isp for SN/MFNR capture and SN video support" + depends on PCI + depends on PCI_MSM + help + Enable support for XIAOMI isp SN/MFNR capture and SN video diff --git a/drivers/media/platform/xiaomi/ispv3/Makefile b/drivers/media/platform/xiaomi/ispv3/Makefile new file mode 100644 index 000000000000..6a031ea4fee2 --- /dev/null +++ b/drivers/media/platform/xiaomi/ispv3/Makefile @@ -0,0 +1,6 @@ +TARGET=ispv3_mfd_dev +obj-$(CONFIG_ISPV3)+=$(TARGET).o +$(TARGET)-objs:=ispv3_dev.o ispv3_power.o +obj-$(CONFIG_ISPV3)+=ispv3_cam_dev.o +#obj-$(CONFIG_ISPV3) += ispv3_dev.o ispv3_power.o ispv3_cam_dev.o + diff --git a/drivers/media/platform/xiaomi/ispv3/ispv3_cam_dev.c b/drivers/media/platform/xiaomi/ispv3/ispv3_cam_dev.c new file mode 100644 index 000000000000..1dd0d13e4f17 --- /dev/null +++ b/drivers/media/platform/xiaomi/ispv3/ispv3_cam_dev.c @@ -0,0 +1,263 @@ +/* + * Copyright (c) 2020, Xiaomi, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ispv3_cam_dev.h" + +static int ispv3_open(struct file *file) +{ + int ret; + struct ispv3_v4l2_dev *priv = video_drvdata(file); + + if (!priv) + return -ENODEV; + + mutex_lock(&priv->isp_lock); + ret = v4l2_fh_open(file); + if (ret) + goto end; + + priv->open_cnt++; + mutex_unlock(&priv->isp_lock); + + dev_info(priv->dev, "ispv3 open cnt = %d", priv->open_cnt); + return ret; + +end: + mutex_unlock(&priv->isp_lock); + return ret; +} + +static __poll_t ispv3_poll(struct file *file, + struct poll_table_struct *pll_table) +{ + struct ispv3_v4l2_dev *priv = video_drvdata(file); + int ret = 0; + + poll_wait(file, &priv->wait, pll_table); + + if (atomic_read(&priv->int_sof)) { + atomic_set(&priv->int_sof, 0); + ret = POLLOUT | POLLWRNORM; + } + + if (atomic_read(&priv->int_eof)) { + atomic_set(&priv->int_eof, 0); + ret = POLLIN | POLLRDNORM; + } + + return ret; +} + +static int ispv3_close(struct file *file) +{ + struct ispv3_v4l2_dev *priv = video_drvdata(file); + + mutex_lock(&priv->isp_lock); + + if (priv->open_cnt <= 0) { + mutex_unlock(&priv->isp_lock); + return -EINVAL; + } + + priv->open_cnt--; + v4l2_fh_release(file); + mutex_unlock(&priv->isp_lock); + + return 0; +} + +/* maps the PCIe BAR into user space for memory-like access using mmap() */ +static int ispv3_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct ispv3_v4l2_dev *priv = video_drvdata(file); + struct ispv3_data *data = priv->pdata; + unsigned long vsize; + unsigned long paddr; + unsigned long psize; + unsigned long off; + int ret; + + off = vma->vm_pgoff << PAGE_SHIFT; + vsize = vma->vm_end - vma->vm_start; + + switch (off) { + case ISP_MITOP_REG_MAPOFFSET: + paddr = data->bar_res[0].start; + psize = MITOP_REG_SIZE; + break; + case ISP_MIPORT_REG_MAPOFFSET: + paddr = data->bar_res[0].start + MIPORT_REG_OFFSET; + psize = MIPORT_REG_SIZE; + break; + case ISP_MITOP_OCRAM_MAPOFFSET: + paddr = data->bar_res[1].start; + psize = MITOP_OCRAM_SIZE; + break; + case ISP_MITOP_DDR_MAPOFFSET: + paddr = data->bar_res[2].start; + psize = MITOP_DDR_SIZE; + break; + default: + return -EINVAL; + + } + + if (vsize > psize) + return -EINVAL; + + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + vma->vm_flags |= VM_IO | VM_DONTEXPAND | VM_DONTDUMP; + + ret = io_remap_pfn_range(vma, vma->vm_start, paddr >> PAGE_SHIFT, + vsize, vma->vm_page_prot); + + dev_dbg(priv->dev, "vma=%ps, vma->vm_start=0x%lx, phys=0x%lx, vsize=%lu, psize=%lu, rv=%d\n", + vma, vma->vm_start, paddr >> PAGE_SHIFT, vsize, psize, ret); + + if (ret) + return -EAGAIN; + + return 0; +} + +static struct v4l2_file_operations ispv3_v4l2_fops = { + .owner = THIS_MODULE, + .open = ispv3_open, + .poll = ispv3_poll, + .release = ispv3_close, + .mmap = ispv3_mmap, +}; + +static int isp_v4l2_device_setup(struct ispv3_v4l2_dev *priv) +{ + int ret = 0; + + /* register v4l2 device */ + priv->v4l2_dev = kzalloc(sizeof(*priv->v4l2_dev), + GFP_KERNEL); + if (!priv->v4l2_dev) + return -ENOMEM; + + ret = v4l2_device_register(priv->dev, priv->v4l2_dev); + if (ret) + goto v4l2_fail; + + /* register media device */ + priv->v4l2_dev->mdev = kzalloc(sizeof(*priv->v4l2_dev->mdev), GFP_KERNEL); + if (!priv->v4l2_dev->mdev) { + ret = -ENOMEM; + goto v4l2_fail; + } + + media_device_init(priv->v4l2_dev->mdev); + priv->v4l2_dev->mdev->dev = priv->dev; + strlcpy(priv->v4l2_dev->mdev->model, ISPV3_VNODE_NAME, + sizeof(priv->v4l2_dev->mdev->model)); + + ret = media_device_register(priv->v4l2_dev->mdev); + if (ret) + goto media_fail; + + /* register video device */ + priv->video = video_device_alloc(); + if (!priv->video) { + ret = -ENOMEM; + goto media_fail; + } + + priv->video->v4l2_dev = priv->v4l2_dev; + + strlcpy(priv->video->name, "ispv3", sizeof(priv->video->name)); + priv->video->release = video_device_release; + priv->video->fops = &ispv3_v4l2_fops; + priv->video->minor = -1; + priv->video->vfl_type = VFL_TYPE_VIDEO; + priv->video->device_caps = V4L2_CAP_VIDEO_CAPTURE; + ret = video_register_device(priv->video, VFL_TYPE_VIDEO, -1); + if (ret) + goto video_fail; + + video_set_drvdata(priv->video, priv); + + ret = media_entity_pads_init(&priv->video->entity, 0, NULL); + if (ret) + goto entity_fail; + + priv->video->entity.function = ISP_VNODE_DEVICE_TYPE; + priv->video->entity.name = video_device_node_name(priv->video); + + return ret; + +entity_fail: + video_unregister_device(priv->video); +video_fail: + video_device_release(priv->video); + priv->video = NULL; +media_fail: + kfree(priv->v4l2_dev->mdev); + priv->v4l2_dev->mdev = NULL; +v4l2_fail: + kfree(priv->v4l2_dev); + priv->v4l2_dev = NULL; + return ret; +} + +static int ispv3_v4l2_probe(struct platform_device *pdev) +{ + struct ispv3_v4l2_dev *priv; + struct ispv3_data *data; + int ret = -EIO; + + priv = devm_kzalloc(&pdev->dev, sizeof(struct ispv3_v4l2_dev), + GFP_KERNEL); + if (!priv) + return -ENOMEM; + + data = dev_get_drvdata(pdev->dev.parent); + if (!data) { + dev_err(&pdev->dev, "The ispv3 data struct is NULL!\n"); + return -EINVAL; + } + + mutex_init(&priv->isp_lock); + priv->pdata = data; + priv->open_cnt = 0; + priv->dev = &pdev->dev; + + platform_set_drvdata(pdev, priv); + + ret = isp_v4l2_device_setup(priv); + if (ret) + return ret; + + return 0; +} + +static int ispv3_v4l2_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver ispv3_v4l2_driver = { + .probe = ispv3_v4l2_probe, + .remove = ispv3_v4l2_remove, + .driver = { + .name = "ispv3-v4l2", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(ispv3_v4l2_driver); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/xiaomi/ispv3/ispv3_cam_dev.h b/drivers/media/platform/xiaomi/ispv3/ispv3_cam_dev.h new file mode 100644 index 000000000000..47c4eeceaadb --- /dev/null +++ b/drivers/media/platform/xiaomi/ispv3/ispv3_cam_dev.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2020, Xiaomi, Inc. All rights reserved. + */ +#ifndef _ISPV3_CAM_DEV_H_ +#define _ISPV3_CAM_DEV_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "ispv3-v4l2" + +struct ispv3_v4l2_dev { + struct v4l2_device *v4l2_dev; + struct video_device *video; + struct ispv3_data *pdata; + wait_queue_head_t wait; + struct mutex isp_lock; + struct device *dev; + atomic_t int_sof; + atomic_t int_eof; + int open_cnt; +}; + +#endif diff --git a/drivers/media/platform/xiaomi/ispv3/ispv3_dev.c b/drivers/media/platform/xiaomi/ispv3/ispv3_dev.c new file mode 100644 index 000000000000..0adbef32b49d --- /dev/null +++ b/drivers/media/platform/xiaomi/ispv3/ispv3_dev.c @@ -0,0 +1,902 @@ +/* + * Copyright (c) 2020, Xiaomi, Inc. All rights reserved. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ispv3_cam_dev.h" + +#define DRIVER_NAME "xiaomi_ispv3" +static struct ispv3_data *ispv3_dev_info; + +static struct resource ispv3_rpmsg_res[RPMSG_RES_NUM]; +static struct resource ispv3_cam_res[CAM_RES_NUM]; + +static struct mfd_cell ispv3_v4l2_cell = { + .name = "ispv3-v4l2", + .ignore_resource_conflicts = true, +}; + +static struct mfd_cell ispv3_rpmsg_pci_cell = { + .name = "ispv3-rpmsg_pci", + .num_resources = ARRAY_SIZE(ispv3_rpmsg_res), + .resources = ispv3_rpmsg_res, + .ignore_resource_conflicts = true, +}; + +static struct mfd_cell ispv3_rpmsg_spi_cell = { + .name = "ispv3-rpmsg_spi", + .num_resources = ARRAY_SIZE(ispv3_rpmsg_res), + .resources = ispv3_rpmsg_res, + .ignore_resource_conflicts = true, +}; + +static struct mfd_cell ispv3_cam_cell = { + .name = "ispv3-cam", + .num_resources = ARRAY_SIZE(ispv3_cam_res), + .resources = ispv3_cam_res, + .ignore_resource_conflicts = true, +}; + +static struct ispv3_data *ispv3_get_plat_priv(void); +static void ispv3_set_plat_priv(struct ispv3_data *plat_priv); + +static pci_ers_result_t ispv3_io_error_detected(struct pci_dev *pdev, + pci_channel_state_t state) +{ + if (state == pci_channel_io_perm_failure) + return PCI_ERS_RESULT_DISCONNECT; + + pci_disable_device(pdev); + + /* Request a slot reset. */ + return PCI_ERS_RESULT_NEED_RESET; +} + +static pci_ers_result_t ispv3_io_slot_reset(struct pci_dev *pdev) +{ + pci_ers_result_t result; + int err; + + err = pci_enable_device(pdev); + if (err) { + dev_err(&pdev->dev, + "Cannot re-enable PCI device after reset.\n"); + result = PCI_ERS_RESULT_DISCONNECT; + } else { + pdev->state_saved = true; + pci_restore_state(pdev); + pci_set_master(pdev); + + result = PCI_ERS_RESULT_RECOVERED; + } + + return result; +} + +static const struct pci_error_handlers ispv3_err_handler = { + .error_detected = ispv3_io_error_detected, + .slot_reset = ispv3_io_slot_reset, +}; + +static int ispv3_init_rpmsg_callback(struct ispv3_data *pdata) +{ + int ret = 0; + struct device *pdev = pdata->dev; + + ret = mfd_add_devices(pdev, PLATFORM_DEVID_NONE, + &ispv3_rpmsg_spi_cell, 1, NULL, 0, NULL); + if (ret) { + dev_err(pdev, "MFD add rpmsg devices failed: %d\n", ret); + return ret; + } + dev_info(pdev, "ispv3 rpmsg device registered.\n"); + + return 0; +} + +static int ispv3_init_v4l2(struct pci_dev *pdev) +{ + int ret = 0; + + ret = mfd_add_devices(&pdev->dev, PLATFORM_DEVID_NONE, + &ispv3_v4l2_cell, 1, NULL, 0, NULL); + if (ret) { + dev_err(&pdev->dev, "MFD add v4l2 devices failed: %d\n", ret); + return ret; + } + dev_info(&pdev->dev, "ispv3 v4l2 device registered.\n"); + + return 0; +} + +static int ispv3_init_rpmsg(struct ispv3_data *pdata) +{ + int ret = 0, idx = 0; + struct pci_dev *pdev = pdata->pci; + + for (idx = 0; idx < ARRAY_SIZE(ispv3_rpmsg_res); idx++) { + switch (idx) { + case RPMSG_RAM: + ispv3_rpmsg_res[idx].flags = IORESOURCE_MEM; + ispv3_rpmsg_res[idx].start = pci_resource_start(pdev, BAR_2) + + OCRAM_OFFSET; + ispv3_rpmsg_res[idx].end = pci_resource_start(pdev, BAR_2) + + OCRAM_OFFSET + RPMSG_SIZE - 1; + break; + case RPMSG_IRQ: + ispv3_rpmsg_res[idx].flags = IORESOURCE_IRQ; + ispv3_rpmsg_res[idx].start = pdev->irq; + ispv3_rpmsg_res[idx].end = pdev->irq; + break; + + default: + dev_err(&pdev->dev, "Invalid parameter!\n"); + break; + } + } + + ret = mfd_add_devices(&pdev->dev, PLATFORM_DEVID_NONE, + &ispv3_rpmsg_pci_cell, 1, NULL, 0, NULL); + if (ret) { + dev_err(&pdev->dev, "MFD add rpmsg devices failed: %d\n", ret); + return ret; + } + dev_info(&pdev->dev, "ispv3 rpmsg device registered.\n"); + + return 0; +} + +static int ispv3_init_camera(struct pci_dev *pdev, struct ispv3_data *priv) +{ + int ret = 0, idx = 0; + + for (idx = 0; idx < ARRAY_SIZE(ispv3_cam_res); idx++) { + switch (idx) { + case ISP_RAM: + ispv3_cam_res[idx].flags = IORESOURCE_MEM; + ispv3_cam_res[idx].start = pci_resource_start(pdev, BAR_2) + + OCRAM_OFFSET + RPMSG_SIZE; + ispv3_cam_res[idx].end = pci_resource_end(pdev, BAR_2); + break; + case ISP_DDR: + ispv3_cam_res[idx].flags = IORESOURCE_MEM; + ispv3_cam_res[idx].start = pci_resource_start(pdev, BAR_4); + ispv3_cam_res[idx].end = pci_resource_end(pdev, BAR_4); + break; + default: + dev_err(&pdev->dev, "Invalid parameter!\n"); + break; + } + } + + ispv3_cam_cell.platform_data = (void *)priv; + ret = mfd_add_devices(&pdev->dev, PLATFORM_DEVID_NONE, + &ispv3_cam_cell, 1, NULL, 0, NULL); + if (ret) { + dev_err(&pdev->dev, "MFD add camera device failed: %d\n", ret); + return ret; + } + dev_info(&pdev->dev, "ispv3 cam device registered.\n"); + + return 0; +} + +static void ispv3_init_bar_resource(struct pci_dev *pdev, struct ispv3_data *priv) +{ + int idx = 0; + int bar_idx = 0; + struct resource *res = priv->bar_res; + + for (idx = 0; idx < ISPV3_BAR_NUM; idx++) { + + res->flags = IORESOURCE_MEM; + res->start = pci_resource_start(pdev, bar_idx); + res->end = pci_resource_end(pdev, bar_idx); + + bar_idx += 2; + res++; + } +} + +static bool ispv3_alloc_irq_vectors(struct pci_dev *pdev, struct ispv3_data *priv) +{ + struct device *dev = &pdev->dev; + bool res = true; + int irq = -1; + + switch (priv->pci_irq_type) { + case IRQ_TYPE_LEGACY: + irq = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_LEGACY); + if (irq < 0) + dev_err(dev, "Failed to get Legacy interrupt!\n"); + break; + case IRQ_TYPE_MSI: + irq = pci_alloc_irq_vectors(pdev, 1, 3, PCI_IRQ_MSI); + if (irq < 0) + dev_err(dev, "Failed to get MSI interrupts!\n"); + break; + default: + dev_err(dev, "Invalid IRQ type selected.\n"); + break; + } + + if (irq < 0) { + irq = 0; + res = false; + } + + return res; +} + +static void ispv3_free_irq_vectors(struct pci_dev *pdev) +{ + pci_free_irq_vectors(pdev); +} + +static int ispv3_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + int idx, ret; + struct ispv3_data *priv = ispv3_get_plat_priv(); + + if (!priv) + return -ENOMEM; + + for (idx = PCI_STD_RESOURCES; idx < (PCI_STD_RESOURCE_END + 1); idx++) { + ret = pci_assign_resource(pdev, idx); + if (ret) + dev_err(&pdev->dev, "assign pci resource failed!\n"); + + if (pci_resource_flags(pdev, idx) & IORESOURCE_MEM_64) + idx = idx + 1; + } + + ret = pci_enable_device(pdev); + if (ret) { + dev_err(&pdev->dev, "enable pci device failed: %d\n", ret); + return ret; + } + + ret = dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(&pdev->dev, + "DMA configuration failed: 0x%x\n", ret); + goto err_disable; + } + + /* set up pci connections */ + ret = pci_request_selected_regions(pdev, ((1 << 1) - 1), DRIVER_NAME); + if (ret) { + dev_err(&pdev->dev, + "pci_request_selected_regions failed %d\n", ret); + goto err_disable; + } + + if (pci_resource_flags(pdev, BAR_0) & IORESOURCE_MEM) { + priv->base = pci_ioremap_bar(pdev, BAR_0); + + if (!priv->base) { + dev_err(&pdev->dev, "Failed to map the register of BAR0!\n"); + goto err_release_region; + } + } + + pci_enable_pcie_error_reporting(pdev); + pci_set_master(pdev); + + priv->pci = pdev; + pci_save_state(pdev); + priv->default_state = pci_store_saved_state(pdev); + pci_set_drvdata(pdev, priv); + + atomic_set(&priv->pci_link_state, ISPV3_PCI_LINK_UP); + + priv->pci_irq_type = IRQ_TYPE_MSI; + if (!ispv3_alloc_irq_vectors(pdev, priv)) + goto err_disable; + + ispv3_init_bar_resource(pdev, priv); + + priv->remote_callback = ispv3_init_rpmsg; + + ret = ispv3_init_camera(pdev, priv); + if (ret) { + dev_err(&pdev->dev, "init cam mfd failed: %d\n", ret); + goto err_disable_irq; + } + + ret = ispv3_init_v4l2(pdev); + if (ret) { + dev_err(&pdev->dev, "init v4l2 mfd failed: %d\n", ret); + goto err_disable_irq; + } + dev_info(&pdev->dev, "ispv3 all ispv3 mfd devices registered.\n"); + + return 0; + +err_disable_irq: + ispv3_free_irq_vectors(pdev); + +err_release_region: + pci_release_selected_regions(pdev, ((1 << 1) - 1)); + +err_disable: + pci_disable_device(pdev); + + return ret; + +} + +static void ispv3_pci_remove(struct pci_dev *pdev) +{ + struct ispv3_data *priv = ispv3_get_plat_priv(); + + if (priv->saved_state) + pci_load_and_free_saved_state(pdev, &priv->saved_state); + pci_load_and_free_saved_state(pdev, &priv->default_state); + mfd_remove_devices(&pdev->dev); + pci_disable_pcie_error_reporting(pdev); + pci_release_selected_regions(pdev, ((1 << 1) - 1)); + pci_disable_device(pdev); +} + +static void ispv3_shutdown(struct pci_dev *pdev) +{ + pci_disable_device(pdev); +} + +static const struct pci_device_id ispv3_pci_tbl[] = { + { PCI_DEVICE(ISPV3_PCI_VENDOR_ID, ISPV3_PCI_DEVICE_ID) }, + { 0 } +}; + +MODULE_DEVICE_TABLE(pci, ispv3_pci_tbl); + +static struct pci_driver ispv3_pci_driver = { + .name = DRIVER_NAME, + .id_table = ispv3_pci_tbl, + .probe = ispv3_pci_probe, + .remove = ispv3_pci_remove, + .shutdown = ispv3_shutdown, + .err_handler = &ispv3_err_handler, + +}; + +int ispv3_pci_init(struct ispv3_data *data) +{ + int ret = 0; + + /* enumerate it on PCIE */ + ret = msm_pcie_enumerate(data->rc_index); + if (ret < 0) { + dev_err(data->dev, "ispv3 PCIE enumeration failed! Not PCIE mode\n"); + return ret; + } + + data->interface_type = ISPV3_PCIE; + + ret = pci_register_driver(&ispv3_pci_driver); + if (ret) { + dev_err(data->dev, "Failed to register PCIe driver!\n"); + goto out; + } +out: + return ret; +} + +void ispv3_pci_deinit(u32 rc_index) +{ + pci_unregister_driver(&ispv3_pci_driver); +} + +static uint32_t RGLTR_COUNT[2] = { + ISPV3_SOC_8450_RGLTR_COUNT, + ISPV3_SOC_8475_RGLTR_COUNT, +}; + +static int ispv3_get_dt_regulator_info(struct ispv3_data *data) +{ + int ret = 0, count = 0, i = 0; + struct device_node *of_node = data->dev->of_node; + + if (!data || !data->dev) { + dev_err(data->dev, "Invalid parameters!\n"); + return -EINVAL; + } + + data->num_rgltr = 0; + count = of_property_count_strings(of_node, "regulator-names"); + if (count != RGLTR_COUNT[data->soc_id]) { + dev_err(data->dev, "regulators num error!\n"); + return -EINVAL; + } + data->num_rgltr = count; + + for (i = 0; i < data->num_rgltr; i++) { + ret = of_property_read_string_index(of_node, + "regulator-names", i, &data->rgltr_name[i]); + dev_dbg(data->dev, "rgltr_name[%d] = %s\n", + i, data->rgltr_name[i]); + if (ret) { + dev_err(data->dev, "no regulator resource at cnt=%d\n", + i); + return -ENODEV; + } + } + + ret = of_property_read_u32_array(of_node, "rgltr-min-voltage", + data->rgltr_min_volt, data->num_rgltr); + if (ret) { + dev_err(data->dev, "No min volatage value found, ret=%d\n", ret); + return -EINVAL; + } + + ret = of_property_read_u32_array(of_node, "rgltr-max-voltage", + data->rgltr_max_volt, data->num_rgltr); + if (ret) { + dev_err(data->dev, "No max volatage value found, ret=%d\n", ret); + return -EINVAL; + } + + for (i = 0; i < data->num_rgltr; i++) { + data->rgltr[i] = regulator_get(data->dev, data->rgltr_name[i]); + if (IS_ERR_OR_NULL(data->rgltr[i])) { + dev_err(data->dev, "Regulator %s get failed!\n", + data->rgltr_name[i]); + return -EINVAL; + } + } + + return ret; +} + +static int ispv3_get_dt_clk_info(struct ispv3_data *data) +{ + int ret = 0; + int count; + int num_clk_rates; + struct device_node *of_node = data->dev->of_node; + + if (!data || !data->dev) { + dev_err(data->dev, "Invalid parameters!\n"); + return -EINVAL; + } + + count = of_property_count_strings(of_node, "clock-names"); + if (count != ISPV3_CLK_NUM) { + dev_err(data->dev, "invalid count of clocks, count=%d\n", + count); + ret = -EINVAL; + return ret; + } + + ret = of_property_read_string(of_node, "clock-names", &data->clk_name); + if (ret) { + dev_err(data->dev, "reading clock-names failed!\n"); + return ret; + } + + num_clk_rates = of_property_count_u32_elems(of_node, "clock-rates"); + if (num_clk_rates <= 0) { + dev_err(data->dev, "reading clock-rates count failed!\n"); + return -EINVAL; + } + + ret = of_property_read_u32_index(of_node, "clock-rates", + 0, &data->clk_rate); + if (ret) { + dev_err(data->dev, "Error reading clock-rates, ret=%d\n", ret); + return ret; + } + + data->clk_rate = (data->clk_rate == 0) ? (int32_t)ISPV3_NO_SET_RATE : data->clk_rate; + dev_dbg(data->dev, "mclk_rate = %d", data->clk_rate); + + data->clk = devm_clk_get(data->dev, data->clk_name); + if (!data->clk) { + dev_err(data->dev, "get clk failed for %s\n", data->clk_name); + ret = -ENOENT; + return ret; + } + + return ret; +} + +static int ispv3_pinctrl_init(struct ispv3_pinctrl_info *sensor_pctrl, + struct device *dev) +{ + sensor_pctrl->pinctrl = devm_pinctrl_get(dev); + if (IS_ERR_OR_NULL(sensor_pctrl->pinctrl)) { + dev_err(dev, "Getting pinctrl handle failed!\n"); + return -EINVAL; + } + + sensor_pctrl->gpio_state_active = + pinctrl_lookup_state(sensor_pctrl->pinctrl, + ISPV3_PINCTRL_STATE_DEFAULT); + if (IS_ERR_OR_NULL(sensor_pctrl->gpio_state_active)) { + dev_err(dev, "Failed to get the active state pinctrl handle!\n"); + return -EINVAL; + } + + sensor_pctrl->gpio_state_suspend = + pinctrl_lookup_state(sensor_pctrl->pinctrl, + ISPV3_PINCTRL_STATE_SLEEP); + if (IS_ERR_OR_NULL(sensor_pctrl->gpio_state_suspend)) { + dev_err(dev, "Failed to get the suspend state pinctrl handle!\n"); + return -EINVAL; + } + + return 0; +} + +static int ispv3_conf_gpio(struct ispv3_data *data) +{ + int ret = 0; + struct device_node *np = data->dev->of_node; + + data->gpio_sys_reset = of_get_named_gpio(np, "ispv3_gpio_reset", 0); + if (data->gpio_sys_reset < 0) { + dev_err(data->dev, "get reset gpio failed!\n"); + return -EINVAL; + } + + ret = devm_gpio_request(data->dev, data->gpio_sys_reset, "reset_gpio0"); + if (ret) { + dev_err(data->dev, "reset gpio request failed!\n"); + return ret; + } + + ret = gpio_direction_output(data->gpio_sys_reset, 0); + if (ret) { + dev_err(data->dev, "cannot set direction for reset gpio[%d]\n", + data->gpio_sys_reset); + return ret; + } + + data->gpio_isolation = of_get_named_gpio(np, "ispv3_gpio_15", 0); + if (data->gpio_isolation < 0) { + dev_err(data->dev, "get isolation gpio failed\n"); + return -EINVAL; + } + + ret = devm_gpio_request(data->dev, data->gpio_isolation, "isolation_gpio"); + if (ret) { + dev_err(data->dev, "request isolation gpio failed\n"); + return ret; + } + + ret = gpio_direction_output(data->gpio_isolation, 0); + if (ret) { + dev_err(data->dev, "cannot set direction for isolation gpio[%d]\n", + data->gpio_isolation); + return ret; + } + + data->gpio_swcr_reset = of_get_named_gpio(np, "ispv3_gpio_14", 0); + if (data->gpio_swcr_reset < 0) { + dev_err(data->dev, "get reset1 gpio failed\n"); + return -EINVAL; + } + + ret = devm_gpio_request(data->dev, data->gpio_swcr_reset, "reset_gpio1"); + if (ret) { + dev_err(data->dev, "request reset1 gpio failed\n"); + return ret; + } + + ret = gpio_direction_output(data->gpio_swcr_reset, 0); + if (ret) { + dev_err(data->dev, "cannot set direction for reset1 gpio[%d]\n", + data->gpio_swcr_reset); + return ret; + } + + data->gpio_fan_en = of_get_named_gpio(np, "ispv3_gpio_fan_en", 0); + if (data->gpio_fan_en < 0) { + dev_err(data->dev, "get fan53526 enable gpio failed\n"); + return -EINVAL; + } + + ret = devm_gpio_request(data->dev, data->gpio_fan_en, + "fan_en_gpio"); + if (ret) { + dev_err(data->dev, "request fan53526 enable gpio failed\n"); + return ret; + } + + ret = gpio_direction_output(data->gpio_fan_en, 0); + if (ret) { + dev_err(data->dev, "cannot set direction for fan53526 enable gpio[%d]\n", + data->gpio_fan_en); + return ret; + } + + data->gpio_int0 = of_get_named_gpio(np, "ispv3_gpio_int0", 0); + if (data->gpio_int0 < 0) { + dev_err(data->dev, "get interrupt0 gpio failed\n"); + return -EINVAL; + } + + data->gpio_irq_cam = gpio_to_irq(data->gpio_int0); + + ret = devm_gpio_request(data->dev, data->gpio_int0, "interrupt_gpio_cam"); + if (ret) { + dev_err(data->dev, "request interrupt0 gpio failed\n"); + return ret; + } + + ret = gpio_direction_input(data->gpio_int0); + if (ret) { + dev_err(data->dev, "cannot set direction for interrupt0 gpio[%d]\n", + data->gpio_int0); + return ret; + } + + data->gpio_int1 = of_get_named_gpio(np, "ispv3_gpio_int1", 0); + if (data->gpio_int1 < 0) { + dev_err(data->dev, "get interrupt1 gpio failed\n"); + return -EINVAL; + } + + data->gpio_irq_power = gpio_to_irq(data->gpio_int1); + + ret = devm_gpio_request(data->dev, data->gpio_int1, "interrupt_gpio_power"); + if (ret) { + dev_err(data->dev, "request interrupt1 gpio failed\n"); + return ret; + } + + ret = gpio_direction_input(data->gpio_int1); + if (ret) { + dev_err(data->dev, "cannot set direction for interrupt1 gpio[%d]\n", + data->gpio_int1); + return ret; + } + + return 0; +} + +static void ispv3_free_gpio(struct ispv3_data *data) +{ + if (data->gpio_sys_reset) + devm_gpio_free(data->dev, data->gpio_sys_reset); + data->gpio_sys_reset = 0; + + if (data->gpio_isolation) + devm_gpio_free(data->dev, data->gpio_isolation); + data->gpio_isolation = 0; + + if (data->gpio_swcr_reset) + devm_gpio_free(data->dev, data->gpio_swcr_reset); + data->gpio_swcr_reset = 0; + + if (data->gpio_int0) + devm_gpio_free(data->dev, data->gpio_int0); + data->gpio_int0 = 0; + + if (data->gpio_int1) + devm_gpio_free(data->dev, data->gpio_int1); + data->gpio_int1 = 0; + + if (data->gpio_fan_en) + devm_gpio_free(data->dev, data->gpio_fan_en); + data->gpio_fan_en = 0; +} + +static int ispv3_get_dt_data(struct ispv3_data *data) +{ + int ret = 0; + u32 rc_index; + struct device_node *np = data->dev->of_node; + + if (of_get_property(np, "sm8475", NULL) != NULL) + data->soc_id = ISPV3_SOC_ID_SM8475; + else + data->soc_id = ISPV3_SOC_ID_SM8450; + + dev_info(data->dev, "soc id is %d", data->soc_id); + + + ret = of_property_read_u32(np, "qcom,ispv3-rc-index", &rc_index); + if (ret) { + dev_err(data->dev, "Failed to find PCIe RC number!\n"); + return -EINVAL; + } + data->rc_index = rc_index; + + ret = ispv3_conf_gpio(data); + if (ret) { + dev_err(data->dev, "config pin function failed!\n"); + return ret; + } + + ret = ispv3_get_dt_regulator_info(data); + if (ret) { + dev_err(data->dev, "get dt regulator failed!\n"); + return ret; + } + + ret = ispv3_get_dt_clk_info(data); + if (ret) { + dev_err(data->dev, "get dt clk failed!\n"); + return ret; + } + + memset(&(data->pinctrl_info), 0x0, sizeof(data->pinctrl_info)); + ret = ispv3_pinctrl_init(&(data->pinctrl_info), data->dev); + if (ret < 0) { + dev_err(data->dev, "Initialization of pinctrl failed!\n"); + data->pinctrl_info.pinctrl_status = 0; + return ret; + } + + data->pinctrl_info.pinctrl_status = 1; + + return ret; +} + +static void ispv3_set_plat_priv(struct ispv3_data *plat_priv) +{ + ispv3_dev_info = plat_priv; +} + +static struct ispv3_data *ispv3_get_plat_priv(void) +{ + return ispv3_dev_info; +} + +static int ispv3_spi_probe(struct spi_device *spi) +{ + struct ispv3_data *data = NULL; + struct device *dev = &spi->dev; + int ret = 0; + + spi->max_speed_hz = ISPV3_SPI_SPEED_HZ; + spi->bits_per_word = 8; + spi->mode = SPI_MODE_0; + + ret = spi_setup(spi); + if (ret < 0) { + dev_err(dev, "spi_setup failed (%d)!\n", ret); + goto out; + } + + data = devm_kzalloc(dev, sizeof(struct ispv3_data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto out; + } + + spi_set_drvdata(spi, data); + ispv3_set_plat_priv(data); + data->spi = spi; + data->dev = dev; + + ret = ispv3_get_dt_data(data); + if (ret < 0) { + dev_err(dev, "ispv3_get_dt_data failed (%d)!\n", ret); + goto free_data; + } + +#ifdef BUG_SOF + atomic_set(&data->power_state, 1); +#else + data->power_state = ISPV3_POWER_OFF; +#endif + data->interface_type = ISPV3_SPI; + mutex_init(&data->ispv3_interf_mutex); + + ispv3_gpio_reset_clear(data); + + ret = ispv3_power_on(data); + if (ret < 0) { + dev_err(dev, "ispv3 power on failed (%d)!\n", ret); + goto remove_pin; + } + + ret = ispv3_pci_init(data); + if (ret) { + dev_err(dev, "ispv3 pci init failed (%d)! Not PCIE mode\n", ret); + data->interface_type = ISPV3_SPI; + } + + if (data->interface_type == ISPV3_SPI) { + data->remote_callback = ispv3_init_rpmsg_callback; + + ispv3_cam_cell.platform_data = (void *)data; + ret = mfd_add_devices(dev, PLATFORM_DEVID_NONE, + &ispv3_cam_cell, 1, NULL, 0, NULL); + if (ret) { + dev_err(dev, "MFD add camera device failed: %d\n", ret); + goto power_off; + } + dev_info(dev, "ispv3 cam device registered.\n"); + } + + dev_info(dev, "ispv3 all ispv3 mfd devices registered.\n"); + + return 0; + +power_off: + ispv3_power_off(data); +remove_pin: + ispv3_free_gpio(data); +free_data: + devm_kfree(dev, data); + ispv3_set_plat_priv(NULL); +out: + return ret; +} + +static int ispv3_spi_remove(struct spi_device *spi) +{ + struct ispv3_data *data = spi_get_drvdata(spi); + int ret = 0; + + ispv3_free_gpio(data); +#ifdef CONFIG_ZISP_OCRAM_AON + ret = ispv3_power_off(data); +#endif + dev_info(&spi->dev, "ispv3 remove success !\n"); + + return ret; +} + +static const struct of_device_id ispv3_spi_of_match[] = { + {.compatible = "xiaomi,ispv3_spi",}, + {}, +}; + +static const struct spi_device_id ispv3_spi_device_id[] = { + {"ispv3_spi", 0}, + {} +}; + +static struct spi_driver ispv3_spi_drv = { + .driver = { + .name = "ispv3_spi", + .owner = THIS_MODULE, + .of_match_table = ispv3_spi_of_match, + }, + .probe = ispv3_spi_probe, + .remove = ispv3_spi_remove, + .id_table = ispv3_spi_device_id, +}; + +static int __init ispv3_module_init(void) +{ + int ret; + + ret = spi_register_driver(&ispv3_spi_drv); + + return ret; +} + +static void __exit ispv3_module_exit(void) +{ + spi_unregister_driver(&ispv3_spi_drv); +} + +module_init(ispv3_module_init); +module_exit(ispv3_module_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Platform driver for Xiaomi, Inc. ZISP V3"); diff --git a/drivers/media/platform/xiaomi/ispv3/ispv3_power.c b/drivers/media/platform/xiaomi/ispv3/ispv3_power.c new file mode 100644 index 000000000000..48dedc004220 --- /dev/null +++ b/drivers/media/platform/xiaomi/ispv3/ispv3_power.c @@ -0,0 +1,447 @@ +/* + * Copyright (c) 2020, Xiaomi, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int ispv3_regulator_enable(struct ispv3_data *data, + enum ispv3_rgltr_type type) +{ + int32_t ret = 0; + + if (!data->rgltr[type]) { + dev_err(data->dev, "Invalid NULL parameter\n"); + return -EINVAL; + } + + if (regulator_count_voltages(data->rgltr[type]) > 0) { + dev_dbg(data->dev, "rgltr_name %s voltage min=%d, max=%d", + data->rgltr_name[type], data->rgltr_min_volt[type], + data->rgltr_max_volt[type]); + + ret = regulator_set_voltage(data->rgltr[type], + data->rgltr_min_volt[type], data->rgltr_max_volt[type]); + if (ret) { + dev_err(data->dev, "%s set voltage failed\n", + data->rgltr_name[type]); + return ret; + } + } + + ret = regulator_enable(data->rgltr[type]); + if (ret) { + dev_err(data->dev, "%s regulator_enable failed\n", + data->rgltr_name[type]); + return ret; + } + + return ret; +} + +static int ispv3_regulator_disable(struct ispv3_data *data, + enum ispv3_rgltr_type type) +{ + int32_t ret = 0; + + if (!data->rgltr[type]) { + dev_err(data->dev, "Invalid NULL parameter\n"); + return -EINVAL; + } + + ret = regulator_disable(data->rgltr[type]); + if (ret) { + dev_err(data->dev, "%s regulator disable failed\n", + data->rgltr_name[type]); + return ret; + } + + if (regulator_count_voltages(data->rgltr[type]) > 0) { + //regulator_set_load(data->rgltr[type], 0); + regulator_set_voltage(data->rgltr[type], 0, + data->rgltr_max_volt[type]); + } + + return ret; +} + +static int ispv3_clk_enable(struct ispv3_data *data) +{ + long clk_rate_round; + int ret = 0; + + if (!data->clk || !data->clk_name) + return -EINVAL; + + clk_rate_round = clk_round_rate(data->clk, data->clk_rate); + if (clk_rate_round < 0) { + dev_err(data->dev, "round failed for clock %s ret = %ld", + data->clk_name, clk_rate_round); + return clk_rate_round; + } + + ret = clk_set_rate(data->clk, clk_rate_round); + if (ret) { + dev_err(data->dev, "set_rate failed on %s", data->clk_name); + return ret; + } + dev_dbg(data->dev, "set %s, rate %d, new_rate %ld", data->clk_name, + data->clk_rate, clk_rate_round); + + ret = clk_prepare_enable(data->clk); + if (ret) { + dev_err(data->dev, "enable failed for %s: ret(%d)", data->clk_name, ret); + return ret; + } + + return ret; +} + +static int ispv3_clk_disable(struct ispv3_data *data) +{ + if (!data->clk || !data->clk_name) + return -EINVAL; + + clk_disable_unprepare(data->clk); + + return 0; +} + +void ispv3_sys_reset(struct ispv3_data *data, u32 value) +{ + if (data->gpio_sys_reset > 0) + gpio_set_value(data->gpio_sys_reset, value); +} + +void ispv3_swcr_isolation(struct ispv3_data *data, u32 value) +{ + if (data->gpio_isolation > 0) + gpio_set_value(data->gpio_isolation, value); +} + +void ispv3_swcr_reset(struct ispv3_data *data, u32 value) +{ + if (data->gpio_swcr_reset > 0) + gpio_set_value(data->gpio_swcr_reset, value); +} + +void ispv3_fan_enable(struct ispv3_data *data, u32 value) +{ + if (data->gpio_fan_en > 0) + gpio_set_value(data->gpio_fan_en, value); +} + +static int ispv3_set_pci_config_space(struct ispv3_data *data, bool save) +{ + int ret = 0; + + if (save) { + ret = pci_save_state(data->pci); + data->saved_state = pci_store_saved_state(data->pci); + } else { + if (data->saved_state) + pci_load_and_free_saved_state(data->pci, &data->saved_state); + else + pci_load_saved_state(data->pci, data->default_state); + + pci_restore_state(data->pci); + } + + return ret; +} + +static int ispv3_set_pci_link(struct ispv3_data *data, bool link_up) +{ + enum msm_pcie_pm_opt pm_ops; + int retry_time = 0; + int ret = 0; + + if (link_up) + pm_ops = MSM_PCIE_RESUME; + else + pm_ops = MSM_PCIE_SUSPEND; + +retry: + ret = msm_pcie_pm_control(pm_ops, data->pci->bus->number, data->pci, + NULL, PM_OPTIONS_DEFAULT); + if (ret) { + dev_err(data->dev, "Failed to %s PCI link with default option, err = %d\n", + link_up ? "resume" : "suspend", ret); + if (link_up && retry_time++ < LINK_TRAINING_RETRY_MAX_TIMES) { + dev_dbg(data->dev, "Retry PCI link training #%d\n", + retry_time); + goto retry; + } + } + + return ret; +} + +int ispv3_resume_pci_link(struct ispv3_data *data) +{ + int ret = 0; + + if (!data->pci) + return -ENODEV; + + ret = ispv3_set_pci_link(data, ISPV3_PCI_LINK_UP); + if (ret) { + dev_err(data->dev, "Failed to set link up status, err = %d\n", + ret); + return ret; + } + + ret = pci_enable_device(data->pci); + if (ret) { + dev_err(data->dev, "Failed to enable PCI device, err = %d\n", + ret); + return ret; + } + + ret = ispv3_set_pci_config_space(data, RESTORE_PCI_CONFIG_SPACE); + if (ret) { + dev_err(data->dev, "Failed to restore config space, err = %d\n", + ret); + return ret; + } + + pci_set_master(data->pci); + + atomic_set(&data->pci_link_state, ISPV3_PCI_LINK_UP); + + return 0; +} +EXPORT_SYMBOL_GPL(ispv3_resume_pci_link); + +int ispv3_suspend_pci_link(struct ispv3_data *data) +{ + int ret = 0; + + if (!data->pci) + return -ENODEV; + + pci_clear_master(data->pci); + + ret = ispv3_set_pci_config_space(data, SAVE_PCI_CONFIG_SPACE); + if (ret) { + dev_err(data->dev, "Failed to save config space, err = %d\n", + ret); + return ret; + } + + pci_disable_device(data->pci); + + mutex_lock(&data->ispv3_interf_mutex); + ret = pci_set_power_state(data->pci, PCI_D3hot); + mutex_unlock(&data->ispv3_interf_mutex); + if (ret) { + dev_err(data->dev, "Failed to set D3Hot, err = %d\n", ret); + return ret; + } + + ret = ispv3_set_pci_link(data, ISPV3_PCI_LINK_DOWN); + if (ret) { + dev_err(data->dev, "Failed to set link down status, err = %d\n", + ret); + return ret; + } + + atomic_set(&data->pci_link_state, ISPV3_PCI_LINK_DOWN); + + return 0; +} +EXPORT_SYMBOL_GPL(ispv3_suspend_pci_link); + +void ispv3_gpio_reset_clear(struct ispv3_data *data) +{ + ispv3_swcr_isolation(data, 0); + udelay(100); + ispv3_swcr_reset(data, 0); + udelay(100); + ispv3_sys_reset(data, 0); + udelay(100); +} +EXPORT_SYMBOL_GPL(ispv3_gpio_reset_clear); + +int ispv3_power_on(struct ispv3_data *data) +{ + int ret = 0; + + /* regulator enable and configure reset pin to work state */ + if (data->soc_id == ISPV3_SOC_ID_SM8475) { + ret = ispv3_regulator_enable(data, ISPV3_RGLTR_S11B); + if (ret) { + dev_err(data->dev, "ISPV3_RGLTR_S11B regulator enable failed\n"); + return ret; + } + ret = ispv3_regulator_enable(data, ISPV3_RGLTR_S12B); + if (ret) { + dev_err(data->dev, "ISPV3_RGLTR_S12B regulator enable failed\n"); + return ret; + } + } + + ret = ispv3_regulator_enable(data, ISPV3_RGLTR_VDD1); + if (ret) { + dev_err(data->dev, "ISPV3_RGLTR_VDD1 regulator enable failed\n"); + return ret; + } + udelay(100); + + ret = ispv3_regulator_enable(data, ISPV3_RGLTR_L12C); + if (ret) { + dev_err(data->dev, "ISPV3_RGLTR_L12C regulator enable failed\n"); + return ret; + } + udelay(1); + + ispv3_fan_enable(data, 1); + udelay(50); + ispv3_swcr_isolation(data, 1); + udelay(1); + ispv3_swcr_reset(data, 1); + udelay(50); + + ret = ispv3_regulator_enable(data, ISPV3_RGLTR_VDD2); + if (ret) { + dev_err(data->dev, "ISPV3_RGLTR_VDD2 regulator enable failed\n"); + return ret; + } + udelay(100); + + ret = ispv3_regulator_enable(data, ISPV3_RGLTR_VDD); + if (ret) { + dev_err(data->dev, "ISPV3_RGLTR_VDD regulator enable failed\n"); + return ret; + } + udelay(1); + + ispv3_swcr_isolation(data, 0); + udelay(100); + + ret = ispv3_regulator_enable(data, ISPV3_RGLTR_VDDR); + if (ret) { + dev_err(data->dev, "ISPV3_RGLTR_VDDR regulator enable failed\n"); + return ret; + } + udelay(100); + + ret = ispv3_regulator_enable(data, ISPV3_RGLTR_L10C); + if (ret) { + dev_err(data->dev, "ISPV3_RGLTR_L10C regulator enable failed\n"); + return ret; + } + + udelay(2500); + if (data->pinctrl_info.pinctrl_status) { + ret = pinctrl_select_state( + data->pinctrl_info.pinctrl, + data->pinctrl_info.gpio_state_active); + if (ret) { + dev_err(data->dev, "cannot set pin to active state\n"); + return ret; + } + } + + ret = ispv3_regulator_enable(data, ISPV3_RGLTR_MCLK); + if (ret) { + dev_err(data->dev, "ISPV3_RGLTR_MCLK regulator enable failed\n"); + return ret; + } + + ret = ispv3_clk_enable(data); + if (ret) { + dev_err(data->dev, "mclk enable failed\n"); + return ret; + } + udelay(1); + + ispv3_sys_reset(data, 1); + +#ifdef BUG_SOF + atomic_set(&data->power_state, 0); +#else + data->power_state = ISPV3_POWER_ON; +#endif + dev_info(data->dev, "ispv3 power on success"); + + return ret; +} +EXPORT_SYMBOL_GPL(ispv3_power_on); + +int ispv3_power_off(struct ispv3_data *data) +{ + int ret = 0; + + /* regulator disable and configure reset pin to defaule value */ + ispv3_sys_reset(data, 0); + udelay(1); + ispv3_swcr_isolation(data, 1); + + ispv3_clk_disable(data); + if (data->pinctrl_info.pinctrl_status) { + ret = pinctrl_select_state( + data->pinctrl_info.pinctrl, + data->pinctrl_info.gpio_state_suspend); + if (ret) + dev_err(data->dev, "cannot set pin to suspend state\n"); + } + + ispv3_regulator_disable(data, ISPV3_RGLTR_MCLK); + udelay(1); + + ispv3_regulator_disable(data, ISPV3_RGLTR_L10C); + udelay(1); + + ispv3_regulator_disable(data, ISPV3_RGLTR_VDDR); + udelay(1); + +#ifndef CONFIG_ZISP_OCRAM_AON + ispv3_swcr_reset(data, 0); +#endif + + ispv3_regulator_disable(data, ISPV3_RGLTR_VDD); + udelay(1); + + ispv3_regulator_disable(data, ISPV3_RGLTR_VDD2); + udelay(1); + + ispv3_fan_enable(data, 0); + udelay(1); + +#ifndef CONFIG_ZISP_OCRAM_AON + ispv3_regulator_disable(data, ISPV3_RGLTR_L12C); + udelay(1); + + ispv3_regulator_disable(data, ISPV3_RGLTR_VDD1); + udelay(1); + + if (data->soc_id == ISPV3_SOC_ID_SM8475) { + ispv3_regulator_disable(data, ISPV3_RGLTR_S11B); + ispv3_regulator_disable(data, ISPV3_RGLTR_S12B); + } +#endif + + ispv3_swcr_isolation(data, 0); +#ifdef BUG_SOF + atomic_set(&data->power_state, 1); +#else + data->power_state = ISPV3_POWER_OFF; +#endif + dev_info(data->dev, "ispv3 power down success"); + + return ret; +} +EXPORT_SYMBOL_GPL(ispv3_power_off); + +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/mfd/ispv3_dev.h b/include/linux/mfd/ispv3_dev.h new file mode 100644 index 000000000000..876c5d52b53e --- /dev/null +++ b/include/linux/mfd/ispv3_dev.h @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2020, Xiaomi, Inc. All rights reserved. + */ +#ifndef _ISPV3_DEV_H_ +#define _ISPV3_DEV_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MIPORT_MAX_REG_ADDR 0x1000000 + +#define ISPV3_SPI_SPEED_HZ (1 * 1000 * 1000) +#define ISPV3_CLK_NUM (1) +#define ISPV3_NO_SET_RATE (-1) + +#define ISPV3_PINCTRL_STATE_SLEEP "ispv3_suspend" +#define ISPV3_PINCTRL_STATE_DEFAULT "ispv3_default" +#define ISPV3_PCI_VENDOR_ID 0x17cd +#define ISPV3_PCI_DEVICE_ID 0x0100 +#define ISPV3_BAR_NUM 3 +#define ISPV3_PCI_LINK_DOWN 0 +#define ISPV3_PCI_LINK_UP 1 +#define LINK_TRAINING_RETRY_MAX_TIMES 3 +#define PM_OPTIONS_DEFAULT 0 +#define SAVE_PCI_CONFIG_SPACE 1 +#define RESTORE_PCI_CONFIG_SPACE 0 + +#define IRQ_TYPE_UNDEFINED (-1) +#define IRQ_TYPE_LEGACY 0 +#define IRQ_TYPE_MSI 1 +#define OCRAM_OFFSET 0x700000 +#define RPMSG_SIZE (1024 * 300) + +#define BUG_SOF 1 + +#define ISPV3_SOC_8450_RGLTR_COUNT 7 +#define ISPV3_SOC_8475_RGLTR_COUNT 9 + +enum ispv3_rgltr_type { + ISPV3_RGLTR_VDD1, + ISPV3_RGLTR_L12C, + ISPV3_RGLTR_VDD2, + ISPV3_RGLTR_VDD, + ISPV3_RGLTR_VDDR, + ISPV3_RGLTR_L10C, + ISPV3_RGLTR_MCLK, + ISPV3_RGLTR_S11B, + ISPV3_RGLTR_S12B, + ISPV3_RGLTR_MAX, +}; + +enum pci_barno { + BAR_0, + BAR_1, + BAR_2, + BAR_3, + BAR_4, + BAR_5, +}; + +enum rpmsg_res { + RPMSG_RAM, + RPMSG_IRQ, + RPMSG_RES_NUM, +}; + +enum ispv3_power_state_type { + ISPV3_POWER_ON, + ISPV3_POWER_OFF, + ISPV3_POWER_MAX, +}; + +enum cam_res { + ISP_RAM, + ISP_DDR, + CAM_RES_NUM, +}; + +enum ispv3_interface_type { + ISPV3_SPI, + ISPV3_PCIE, +}; + +struct ispv3_pinctrl_info { + struct pinctrl *pinctrl; + struct pinctrl_state *gpio_state_active; + struct pinctrl_state *gpio_state_suspend; + uint8_t pinctrl_status; +}; + +enum ispv3_soc_id { + ISPV3_SOC_ID_SM8450 = 0, + ISPV3_SOC_ID_SM8475, +}; + +struct ispv3_data { + struct ispv3_pinctrl_info pinctrl_info; + struct spi_device *spi; + struct pci_dev *pci; + struct device *dev; + void __iomem *base; + void *rproc; + struct resource bar_res[ISPV3_BAR_NUM]; +#ifdef BUG_SOF + atomic_t power_state; +#else + enum ispv3_power_state_type power_state; +#endif + struct pci_saved_state *saved_state; + struct pci_saved_state *default_state; + enum ispv3_interface_type interface_type; + enum ispv3_soc_id soc_id; + uint32_t num_rgltr; + struct regulator *rgltr[ISPV3_RGLTR_MAX]; + const char *rgltr_name[ISPV3_RGLTR_MAX]; + uint32_t rgltr_min_volt[ISPV3_RGLTR_MAX]; + uint32_t rgltr_max_volt[ISPV3_RGLTR_MAX]; + uint32_t rgltr_op_mode[ISPV3_RGLTR_MAX]; + const char *clk_name; + struct clk *clk; + int32_t clk_rate; + int32_t gpio_sys_reset; + int32_t gpio_isolation; + int32_t gpio_swcr_reset; + int32_t gpio_int0; + int32_t gpio_int1; + int32_t gpio_fan_en; + int32_t irq_num; + int32_t rc_index; + int32_t pci_irq_type; + uint32_t gpio_irq_cam; + uint32_t gpio_irq_power; + atomic_t pci_link_state; + struct mutex ispv3_interf_mutex; + int (*remote_callback)(struct ispv3_data *pdata); +}; + +void ispv3_gpio_reset_clear(struct ispv3_data *data); +int ispv3_power_on(struct ispv3_data *data); +int ispv3_power_off(struct ispv3_data *data); +void ispv3_write_reset(struct ispv3_data *data, u32 value); +void ispv3_write_reset1(struct ispv3_data *data, u32 value); +void ispv3_write_isolation(struct ispv3_data *data, u32 value); +int ispv3_resume_pci_link(struct ispv3_data *data); +int ispv3_suspend_pci_link(struct ispv3_data *data); + +#endif + diff --git a/include/uapi/linux/ispv3_ioparam.h b/include/uapi/linux/ispv3_ioparam.h new file mode 100644 index 000000000000..9b9faa718180 --- /dev/null +++ b/include/uapi/linux/ispv3_ioparam.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2021, Xiaomi, Inc. All rights reserved. + */ + +#ifndef __ISPV3_IOPARAM_H__ +#define __ISPV3_IOPARAM_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __KERNEL__ +#include +#else +#include +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; +#endif + + +/* Command Reserved Types */ +#define CAM_RESERVED_OPCODE_BASE 0x2000 +#define CAM_RESERVED_POWERUP_EX (CAM_RESERVED_OPCODE_BASE+0x1) + +/* PowerSetting Config Value properties */ +#define POWER_CFG_VAL_TYPE_MASK 0xF000 +#define POWER_CFG_VAL_TYPE_EX 0x2000 +#define POWER_CFG_VAL_MASK 0x000F +typedef enum +{ + ISPV3EventExit = 0, + ISPV3EventMipiRXErr, + ISPV3EventMax, +} ISPV3ICEventType; + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // __ISPV3_IOPARAM_H__ diff --git a/include/uapi/media/ispv3_defs.h b/include/uapi/media/ispv3_defs.h new file mode 100644 index 000000000000..1a76b4b647a9 --- /dev/null +++ b/include/uapi/media/ispv3_defs.h @@ -0,0 +1,125 @@ +/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ +/* + * Copyright (c) 2016-2019, The Linux Foundation. All rights reserved. + */ + +#ifndef __UAPI_ISP_DEFS_H__ +#define __UAPI_ISP_DEFS_H__ + +#include +#include +#include + +#define ISPV3_VNODE_NAME "ispv3-devnode" + +#define MFNR_REF_FRAME_NUM 7 + +#define ISP_DEVICE_TYPE_BASE (MEDIA_ENT_F_OLD_BASE + 20) +#define ISP_VNODE_DEVICE_TYPE (ISP_DEVICE_TYPE_BASE + 1) + +/* MMAP offset definition */ +#define ISP_MITOP_REG_MAPOFFSET 0 +#define ISP_MIPORT_REG_MAPOFFSET PAGE_SIZE +#define ISP_MITOP_OCRAM_MAPOFFSET (2*PAGE_SIZE) +#define ISP_MITOP_DDR_MAPOFFSET (3*PAGE_SIZE) + +/***MMAP Mapping Addr***/ +#define MIPORT_REG_SIZE 0x200000 +#define MIPORT_REG_OFFSET 0xe00000 +#define MIPORT_REG_MASK 0xFFE00000 +#define MIPORT_REG_MIN_ADDR 0xFFE00000 +#define MIPORT_REG_MAX_ADDR (MIPORT_REG_MIN_ADDR-1+MIPORT_REG_SIZE) + +#define MITOP_REG_SIZE 0x200000 +#define MITOP_REG_OFFSET 0x0 +#define MITOP_REG_MASK 0xFF000000 +#define MITOP_REG_MIN_ADDR 0xFF000000 +#define MITOP_REG_MAX_ADDR (MITOP_REG_MIN_ADDR-1+MITOP_REG_SIZE) + +#define MITOP_OCRAM_SIZE 0x750000 /*include 300K OCRAM for test**/ +#define MITOP_OCRAM_OFFSET 0x0 +#define MITOP_OCRAM_MASK 0x7F000000 +#define MITOP_OCRAM_MIN_ADDR 0x7F000000 +#define MITOP_OCRAM_MAX_ADDR (MITOP_OCRAM_MIN_ADDR-1+MITOP_OCRAM_SIZE) + +#define MITOP_DDR_SIZE 0x10000000 +#define MITOP_DDR_OFFSET 0x0 +#define MITOP_DDR_MASK 0x00000000 +#define MITOP_DDR_MIN_ADDR 0x00000000 +#define MITOP_DDR_MAX_ADDR (MITOP_DDR_MIN_ADDR-1+MITOP_DDR_SIZE) + +/** + * struct ispv3_control - Structure used by ioctl control for ispv3 + * + * @op_code: This is the op code for isp control + * @size: Control command size + * @handle: Control command payload + */ +struct ispv3_control { + uint32_t op_code; + uint32_t size; + uint64_t handle; +}; + +/* ISP IOCTL */ +#define VIDIOC_ISPV3_CONTROL \ + _IOWR('V', BASE_VIDIOC_PRIVATE+15, struct ispv3_control) + + +struct ispv3_mfnr_capture_info { + int32_t full_keyframe_fd; + int32_t full_refframe_fd[MFNR_REF_FRAME_NUM]; + int32_t ds_keyframe_fd; + int32_t ds_refframe_fd[MFNR_REF_FRAME_NUM]; + int32_t result_fd; + bool is_transfer_ds; +}; + +//===================================================== + +enum isp_interface_type { + ISP_INTERFACE_INVALID = 0, + ISP_INTERFACE_IIC, + ISP_INTERFACE_SPI, + ISP_INTERFACE_PCIE, + ISP_INTERFACE_MAX, +}; + +struct ispv3_reg_array { + uint32_t reg_addr; + uint32_t reg_data; +}; + +enum isp_access_mode { + ISP_SINGLECPY, + ISP_BURSTCPY, + ISP_BURSTSET, +}; + +struct ispv3_reg_burst_setting { + uint32_t reg_addr; + uint64_t data_buf; +}; + +struct ispv3_config_setting { + enum isp_interface_type bus_type; + enum isp_access_mode access_mode; + uint32_t data_num; + union { + //single mode read&write + struct ispv3_reg_array *reg_array_buf; + //burst mode read&write + struct ispv3_reg_burst_setting reg_burst_setting; + //burst mode memset + struct ispv3_reg_array reg_array; + }; +}; +struct ispv3_info { + int ispv3_fd; + int fd_mem; + uint32_t *ispv3_miport_reg_pointer; + uint32_t *ispv3_mitop_reg_pointer; + uint32_t *ispv3_mitop_ocram_pointer; + uint32_t *ispv3_ddr_pointer; +}; +#endif