Add driver for the video system present on the Chameleon v3. It consists of two Intel DisplayPort DPRX IP cores and six video interfaces (here called "framebuffers"). Signed-off-by: Paweł Anikiel <pan@xxxxxxxxxxxx> --- drivers/media/platform/Kconfig | 1 + drivers/media/platform/Makefile | 1 + drivers/media/platform/google/Kconfig | 4 + drivers/media/platform/google/Makefile | 2 + .../media/platform/google/chameleonv3/Kconfig | 9 + .../platform/google/chameleonv3/Makefile | 15 + .../platform/google/chameleonv3/chv3-core.c | 292 ++++++++++ .../platform/google/chameleonv3/chv3-core.h | 17 + .../platform/google/chameleonv3/chv3-fb.c | 539 ++++++++++++++++++ .../platform/google/chameleonv3/chv3-fb.h | 34 ++ .../platform/google/chameleonv3/dprx-aux.c | 77 +++ .../platform/google/chameleonv3/dprx-dp.c | 82 +++ .../platform/google/chameleonv3/dprx-dpcd.c | 424 ++++++++++++++ .../platform/google/chameleonv3/dprx-dprx.c | 262 +++++++++ .../platform/google/chameleonv3/dprx-edid.c | 39 ++ .../platform/google/chameleonv3/dprx-i2c.c | 41 ++ .../platform/google/chameleonv3/dprx-mt.c | 184 ++++++ .../platform/google/chameleonv3/dprx-sbmsg.c | 162 ++++++ .../media/platform/google/chameleonv3/dprx.h | 128 +++++ 19 files changed, 2313 insertions(+) create mode 100644 drivers/media/platform/google/Kconfig create mode 100644 drivers/media/platform/google/Makefile create mode 100644 drivers/media/platform/google/chameleonv3/Kconfig create mode 100644 drivers/media/platform/google/chameleonv3/Makefile create mode 100644 drivers/media/platform/google/chameleonv3/chv3-core.c create mode 100644 drivers/media/platform/google/chameleonv3/chv3-core.h create mode 100644 drivers/media/platform/google/chameleonv3/chv3-fb.c create mode 100644 drivers/media/platform/google/chameleonv3/chv3-fb.h create mode 100644 drivers/media/platform/google/chameleonv3/dprx-aux.c create mode 100644 drivers/media/platform/google/chameleonv3/dprx-dp.c create mode 100644 drivers/media/platform/google/chameleonv3/dprx-dpcd.c create mode 100644 drivers/media/platform/google/chameleonv3/dprx-dprx.c create mode 100644 drivers/media/platform/google/chameleonv3/dprx-edid.c create mode 100644 drivers/media/platform/google/chameleonv3/dprx-i2c.c create mode 100644 drivers/media/platform/google/chameleonv3/dprx-mt.c create mode 100644 drivers/media/platform/google/chameleonv3/dprx-sbmsg.c create mode 100644 drivers/media/platform/google/chameleonv3/dprx.h diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig index ee579916f874..2f15336cd25e 100644 --- a/drivers/media/platform/Kconfig +++ b/drivers/media/platform/Kconfig @@ -69,6 +69,7 @@ source "drivers/media/platform/aspeed/Kconfig" source "drivers/media/platform/atmel/Kconfig" source "drivers/media/platform/cadence/Kconfig" source "drivers/media/platform/chips-media/Kconfig" +source "drivers/media/platform/google/Kconfig" source "drivers/media/platform/intel/Kconfig" source "drivers/media/platform/marvell/Kconfig" source "drivers/media/platform/mediatek/Kconfig" diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile index 5453bb868e67..db4a0fc7bfd3 100644 --- a/drivers/media/platform/Makefile +++ b/drivers/media/platform/Makefile @@ -12,6 +12,7 @@ obj-y += aspeed/ obj-y += atmel/ obj-y += cadence/ obj-y += chips-media/ +obj-y += google/ obj-y += intel/ obj-y += marvell/ obj-y += mediatek/ diff --git a/drivers/media/platform/google/Kconfig b/drivers/media/platform/google/Kconfig new file mode 100644 index 000000000000..8dd3a955bef8 --- /dev/null +++ b/drivers/media/platform/google/Kconfig @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only + +source "drivers/media/platform/google/chameleonv3/Kconfig" + diff --git a/drivers/media/platform/google/Makefile b/drivers/media/platform/google/Makefile new file mode 100644 index 000000000000..c971a09faeb4 --- /dev/null +++ b/drivers/media/platform/google/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-y += chameleonv3/ diff --git a/drivers/media/platform/google/chameleonv3/Kconfig b/drivers/media/platform/google/chameleonv3/Kconfig new file mode 100644 index 000000000000..ef5130843301 --- /dev/null +++ b/drivers/media/platform/google/chameleonv3/Kconfig @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config VIDEO_CHAMELEONV3 + tristate "Google Chameleon v3 video system driver" + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV + select VIDEOBUF2_DMA_CONTIG + help + Enable support for the Google Chameleon v3 video system driver. diff --git a/drivers/media/platform/google/chameleonv3/Makefile b/drivers/media/platform/google/chameleonv3/Makefile new file mode 100644 index 000000000000..d65e3c392127 --- /dev/null +++ b/drivers/media/platform/google/chameleonv3/Makefile @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-only + +chv3-video-objs := \ + chv3-core.o \ + chv3-fb.o \ + dprx-aux.o \ + dprx-dp.o \ + dprx-dpcd.o \ + dprx-dprx.o \ + dprx-edid.o \ + dprx-i2c.o \ + dprx-mt.o \ + dprx-sbmsg.o + +obj-$(CONFIG_VIDEO_CHAMELEONV3) += chv3-video.o diff --git a/drivers/media/platform/google/chameleonv3/chv3-core.c b/drivers/media/platform/google/chameleonv3/chv3-core.c new file mode 100644 index 000000000000..b571c0afb8bd --- /dev/null +++ b/drivers/media/platform/google/chameleonv3/chv3-core.c @@ -0,0 +1,292 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Chameleon v3 framebuffer + * + * Copyright 2022 Google LLC. + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/of.h> + +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> + +#include <linux/videodev2.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/videobuf2-dma-contig.h> + +#include "chv3-core.h" + +#define MODULE_NAME "chv3-video" + +static const struct chv3_fb_cfg fb0_cfg = { + .reg_core = "fb0", + .reg_irq = "fb0_irq", + .irq = "fb0", + .index = 0, +}; + +static const struct chv3_fb_cfg fb_mst_cfg[4] = { +{ + .reg_core = "fb_mst1", + .reg_irq = "fb_mst1_irq", + .irq = "fb_mst1", + .index = 1, +}, +{ + .reg_core = "fb_mst2", + .reg_irq = "fb_mst2_irq", + .irq = "fb_mst2", + .index = 2, +}, +{ + .reg_core = "fb_mst3", + .reg_irq = "fb_mst3_irq", + .irq = "fb_mst3", + .index = 3, +}, +{ + .reg_core = "fb_mst4", + .reg_irq = "fb_mst4_irq", + .irq = "fb_mst4", + .index = 4, +}, +}; + +static const struct chv3_fb_cfg fb_sst_cfg = { + .reg_core = "fb_sst", + .reg_irq = "fb_sst_irq", + .irq = "fb_sst", + .index = 5, +}; + +static const struct dprx_dp_cfg dp_mst_cfg = { + .reg_core = "dp_mst", + .reg_irq = "dp_mst_irq", + .irq = "dp_mst", + .has_mst = 1, + .sink_count = 4, +}; + +static const struct dprx_dp_cfg dp_sst_cfg = { + .reg_core = "dp_sst", + .reg_irq = "dp_sst_irq", + .irq = "dp_sst", + .has_mst = 0, + .sink_count = 1, +}; + +int chv3_g_edid(struct chv3_video *video, int index, struct v4l2_edid *edid) +{ + u32 end_block = edid->start_block + edid->blocks; + struct sink *sink; + + if (index == 0 || index > 5) + return -ENOTTY; + if (edid->pad) + return -EINVAL; + + if (1 <= index && index <= 4) + sink = &video->dp_mst.sinks[index-1]; + else + sink = &video->dp_sst.sinks[0]; + + if (edid->start_block == 0 && edid->blocks == 0) { + edid->blocks = sink->blocks; + return 0; + } + + if (edid->start_block > sink->blocks) + return -EINVAL; + if (end_block > sink->blocks) { + end_block = sink->blocks; + edid->blocks = end_block - edid->start_block; + } + + memcpy(edid->edid, sink->edid + edid->start_block * 128, edid->blocks * 128); + + return 0; +} + +int chv3_s_edid(struct chv3_video *video, int index, struct v4l2_edid *edid) +{ + struct sink *sink; + + if (index == 0 || index > 5) + return -ENOTTY; + if (edid->pad) + return -EINVAL; + + if (1 <= index && index <= 4) + sink = &video->dp_mst.sinks[index-1]; + else + sink = &video->dp_sst.sinks[0]; + + if (edid->start_block != 0) + return -EINVAL; + if (edid->blocks > DPRX_MAX_EDID_BLOCKS) { + edid->blocks = DPRX_MAX_EDID_BLOCKS; + return -E2BIG; + } + + sink->blocks = edid->blocks; + memcpy(sink->edid, edid->edid, edid->blocks * 128); + + return 0; +} + + +static ssize_t dp_hpd_show(struct device *dev, struct device_attribute *attr, + char *buf); +static ssize_t dp_hpd_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count); + +static struct device_attribute dev_attr_dp0_hpd = { + .attr = { .name = "hpd", .mode = 0644 }, + .show = dp_hpd_show, + .store = dp_hpd_store, +}; + +static struct device_attribute dev_attr_dp1_hpd = { + .attr = { .name = "hpd", .mode = 0644 }, + .show = dp_hpd_show, + .store = dp_hpd_store, +}; + +static struct attribute *dp0_attrs[] = { + &dev_attr_dp0_hpd.attr, + NULL, +}; + +static struct attribute *dp1_attrs[] = { + &dev_attr_dp1_hpd.attr, + NULL, +}; + +static struct attribute_group dp0_attr_group = { + .name = "dp0", + .attrs = dp0_attrs, +}; + +static struct attribute_group dp1_attr_group = { + .name = "dp1", + .attrs = dp1_attrs, +}; + +static ssize_t dp_hpd_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct chv3_video *video = dev_get_drvdata(dev); + struct dprx_dp *dp; + + if (attr == &dev_attr_dp0_hpd) + dp = &video->dp_mst; + else + dp = &video->dp_sst; + + return sprintf(buf, "%d\n", dprx_dprx_get_hpd(dp)); +} + +static ssize_t dp_hpd_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct chv3_video *video = dev_get_drvdata(dev); + struct dprx_dp *dp; + unsigned long val; + int res; + + if (attr == &dev_attr_dp0_hpd) + dp = &video->dp_mst; + else + dp = &video->dp_sst; + + res = kstrtoul(buf, 10, &val); + if (res) + return res; + + dprx_dprx_set_hpd(dp, val); + return count; +} + +static int chv3_video_probe(struct platform_device *pdev) +{ + struct chv3_video *video; + int res; + int i; + + video = devm_kzalloc(&pdev->dev, sizeof(*video), GFP_KERNEL); + if (!video) + return -ENOMEM; + video->dev = &pdev->dev; + platform_set_drvdata(pdev, video); + + /* register v4l2_device */ + res = v4l2_device_register(video->dev, &video->v4l2_dev); + if (res) + return res; + + /* initialize fb devices */ + res = chv3_fb_register(&video->fb0, video, &fb0_cfg); + if (res) + return res; + + for (i = 0; i < 4; i++) { + res = chv3_fb_register(&video->fb_mst[i], video, &fb_mst_cfg[i]); + if (res) + return res; + } + + res = chv3_fb_register(&video->fb_sst, video, &fb_sst_cfg); + if (res) + return res; + + /* initialize dp devices */ + res = dprx_dp_init(&video->dp_mst, video->dev, &dp_mst_cfg); + if (res) + return res; + + res = dprx_dp_init(&video->dp_sst, video->dev, &dp_sst_cfg); + if (res) + return res; + + /* create sysfs files */ + res = sysfs_create_group(&video->dev->kobj, &dp0_attr_group); + if (res) + return res; + + res = sysfs_create_group(&video->dev->kobj, &dp1_attr_group); + if (res) + return res; + + return 0; +} + +static int chv3_video_remove(struct platform_device *pdev) +{ + struct chv3_video *video = platform_get_drvdata(pdev); + + v4l2_device_unregister(&video->v4l2_dev); + + return 0; +} + +static const struct of_device_id chv3_video_match_table[] = { + { .compatible = "google,chv3-video" }, + { }, +}; + +static struct platform_driver chv3_video_platform_driver = { + .probe = chv3_video_probe, + .remove = chv3_video_remove, + .driver = { + .name = MODULE_NAME, + .of_match_table = chv3_video_match_table, + }, +}; + +module_platform_driver(chv3_video_platform_driver); + +MODULE_LICENSE("GPL"); + diff --git a/drivers/media/platform/google/chameleonv3/chv3-core.h b/drivers/media/platform/google/chameleonv3/chv3-core.h new file mode 100644 index 000000000000..9a435cba25bd --- /dev/null +++ b/drivers/media/platform/google/chameleonv3/chv3-core.h @@ -0,0 +1,17 @@ +#include "chv3-fb.h" +#include "dprx.h" + +struct chv3_video { + struct device *dev; + struct v4l2_device v4l2_dev; + + struct chv3_fb fb0; + struct chv3_fb fb_mst[4]; + struct chv3_fb fb_sst; + + struct dprx_dp dp_mst; + struct dprx_dp dp_sst; +}; + +int chv3_g_edid(struct chv3_video *video, int index, struct v4l2_edid *edid); +int chv3_s_edid(struct chv3_video *video, int index, struct v4l2_edid *edid); diff --git a/drivers/media/platform/google/chameleonv3/chv3-fb.c b/drivers/media/platform/google/chameleonv3/chv3-fb.c new file mode 100644 index 000000000000..a9b97d637ed5 --- /dev/null +++ b/drivers/media/platform/google/chameleonv3/chv3-fb.c @@ -0,0 +1,539 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Chameleon v3 framebuffer + * + * Copyright 2022 Google LLC. + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/of.h> + +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> + +#include <linux/videodev2.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/videobuf2-dma-contig.h> + +#include "chv3-core.h" + +#define MODULE_NAME "chv3-fb" + +#define FB_EN 0x00 +#define FB_HEIGHT 0x04 +#define FB_WIDTH 0x08 +#define FB_BUFFERA 0x0c +#define FB_BUFFERB 0x10 +#define FB_BUFFERSIZE 0x14 +#define FB_RESET 0x18 +#define FB_ERRORSTATUS 0x1c +#define FB_IOCOLOR 0x20 +#define FB_IODATARATE 0x24 +#define FB_IOPIXELMODE 0x28 +#define FB_SYNCPOLARITY 0x2c +#define FB_DMAFORMAT 0x30 +#define FB_VERSION 0x34 +#define FB_VERSION_CURRENT 0xc0fb0001 +#define FB_IRQ_MASK 0x8 +#define FB_IRQ_CLR 0xc +#define FB_IRQ_ALL 0xf +#define FB_IRQ_BUFF0 (1 << 0) +#define FB_IRQ_BUFF1 (1 << 1) +#define FB_IRQ_RESOLUTION (1 << 2) +#define FB_IRQ_ERROR (1 << 3) + +struct chv3_fb_buffer { + struct vb2_v4l2_buffer vb; + struct list_head link; +}; + +struct chv3_dma_format { + u32 id; + u32 pixfmt; + int bpp; +}; + +struct chv3_dma_format chv3_dma_formats[] = { + { 0, V4L2_PIX_FMT_RGB24 , 3 }, + { 1, V4L2_PIX_FMT_RGB30U, 6 }, + { 2, V4L2_PIX_FMT_RGB30L, 6 }, + { 3, V4L2_PIX_FMT_RGB36U, 6 }, + { 4, V4L2_PIX_FMT_RGB36L, 6 }, + { 5, V4L2_PIX_FMT_RGB48 , 6 }, + { 7, V4L2_PIX_FMT_BGRX32, 4 }, +}; + +static void fb_set_dma_format(struct chv3_fb *fb, struct chv3_dma_format *dmaf) +{ + writel(dmaf->id, fb->iobase + FB_DMAFORMAT); + /* we need to wait one frame for the width/height to update */ + mdelay(50); + fb->fmt.width = readl(fb->iobase + FB_WIDTH); + fb->fmt.height = readl(fb->iobase + FB_HEIGHT); + + fb->fmt.pixelformat = dmaf->pixfmt; + fb->fmt.field = V4L2_FIELD_NONE; + fb->fmt.bytesperline = fb->fmt.width * dmaf->bpp; + fb->fmt.sizeimage = fb->fmt.bytesperline * fb->fmt.height; + fb->fmt.colorspace = V4L2_COLORSPACE_SRGB; + + writel(fb->fmt.sizeimage, fb->iobase + FB_BUFFERSIZE); +} + +/* v4l2 ioctls */ + +static int vidioc_querycap(struct file *file, void *data, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, MODULE_NAME, sizeof(cap->driver)); + strscpy(cap->card, "Chameleonv3 framebuffer", sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s", + MODULE_NAME); + + return 0; +} + +/* + * We can't control the resolution, we can only read what it currently is from + * the framebuffer. In order not to confuse the application, the resolution is + * saved in fb->fmt, and is only updated when the application calls open() and + * there are no other applications that have the file opened. + */ + +static int vidioc_g_fmt_vid_cap(struct file *file, void *data, + struct v4l2_format *fmt) +{ + struct chv3_fb *fb = video_drvdata(file); + + fmt->fmt.pix = fb->fmt; + return 0; +} + +static int vidioc_s_fmt_vid_cap(struct file *file, void *data, + struct v4l2_format *fmt) +{ + struct chv3_fb *fb = video_drvdata(file); + + if (fmt->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + struct chv3_dma_format *dmaf; + + for (dmaf = chv3_dma_formats; dmaf < &chv3_dma_formats[ARRAY_SIZE(chv3_dma_formats)]; dmaf++) { + if (dmaf->pixfmt == fmt->fmt.pix.pixelformat) { + fb_set_dma_format(fb, dmaf); + break; + } + } + } + + fmt->fmt.pix = fb->fmt; + return 0; +} + +static int vidioc_enum_fmt_vid_cap(struct file *file, void *data, + struct v4l2_fmtdesc *fmt) +{ + if (fmt->index >= ARRAY_SIZE(chv3_dma_formats)) + return -EINVAL; + fmt->flags = 0; + fmt->pixelformat = chv3_dma_formats[fmt->index].pixfmt; + return 0; +} + +static int vidioc_enum_framesizes(struct file *file, void *data, + struct v4l2_frmsizeenum *frm) +{ + struct chv3_fb *fb = video_drvdata(file); + struct chv3_dma_format *dmaf; + bool fmt_ok = false; + + if (frm->index != 0) + return -EINVAL; + + for (dmaf = chv3_dma_formats; dmaf < &chv3_dma_formats[ARRAY_SIZE(chv3_dma_formats)]; dmaf++) { + if (dmaf->pixfmt == frm->pixel_format) { + fmt_ok = true; + break; + } + } + if (!fmt_ok) + return -EINVAL; + + frm->type = V4L2_FRMSIZE_TYPE_DISCRETE; + frm->discrete.width = fb->fmt.width; + frm->discrete.height = fb->fmt.height; + return 0; +} + +static int vidioc_g_input(struct file *file, void *data, unsigned int *index) +{ + *index = 0; + return 0; +} + +static int vidioc_s_input(struct file *file, void *data, unsigned int index) +{ + if (index != 0) + return -EINVAL; + return 0; +} + +static int vidioc_enum_input(struct file *file, void *data, + struct v4l2_input *input) +{ + if (input->index != 0) + return -EINVAL; + strcpy(input->name, "input0"); + input->type = V4L2_INPUT_TYPE_CAMERA; + return 0; +} + +static int vidioc_g_edid(struct file *file, void *data, + struct v4l2_edid *edid) +{ + struct chv3_fb *fb = video_drvdata(file); + + return chv3_g_edid(fb->parent, fb->index, edid); +} + +static int vidioc_s_edid(struct file *file, void *data, + struct v4l2_edid *edid) +{ + struct chv3_fb *fb = video_drvdata(file); + + return chv3_s_edid(fb->parent, fb->index, edid); +} + +static const struct v4l2_ioctl_ops fb_v4l2_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vidioc_g_fmt_vid_cap, + + .vidioc_enum_framesizes = vidioc_enum_framesizes, + + .vidioc_enum_input = vidioc_enum_input, + .vidioc_g_input = vidioc_g_input, + .vidioc_s_input = vidioc_s_input, + .vidioc_g_edid = vidioc_g_edid, + .vidioc_s_edid = vidioc_s_edid, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, +}; + +/* videobuf2 operations */ + +static int fb_queue_setup(struct vb2_queue *q, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct chv3_fb *fb = vb2_get_drv_priv(q); + + if (!fb->fmt.sizeimage) + return -EIO; + + if (*nplanes) { + if (sizes[0] < fb->fmt.sizeimage) + return -EINVAL; + return 0; + } + *nplanes = 1; + sizes[0] = fb->fmt.sizeimage; + return 0; +} + +/* + * There are two address registers: BUFFERA and BUFFERB. The framebuffer + * alternates writing between them (i.e. even frames go to BUFFERA, odd + * ones to BUFFERB). + * + * (buffer queue) > QUEUED ---> QUEUED ---> QUEUED ---> ... + * BUFFERA BUFFERB + * (hw writing to this) ^ + * (and then to this) ^ + * + * The buffer swapping happens at irq time. When an irq comes, the next + * frame is already assigned an address in the buffer queue. This gives + * the irq handler a whole frame's worth of time to update the buffer + * address register. + */ + +static dma_addr_t fb_buffer_dma_addr(struct chv3_fb_buffer *buf) +{ + return vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0); +} + +static void fb_start_frame(struct chv3_fb *fb, struct chv3_fb_buffer *buf) +{ + fb->writing_to_a = 1; + writel(fb_buffer_dma_addr(buf), fb->iobase + FB_BUFFERA); + writel(1, fb->iobase + FB_EN); +} + +static void fb_next_frame(struct chv3_fb *fb, struct chv3_fb_buffer *buf) +{ + u32 reg = fb->writing_to_a ? FB_BUFFERB : FB_BUFFERA; + + writel(fb_buffer_dma_addr(buf), fb->iobase + reg); +} + +static int fb_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct chv3_fb *fb = vb2_get_drv_priv(q); + struct chv3_fb_buffer *buf; + unsigned long flags; + + fb->streaming = 1; + fb->sequence = 0; + + spin_lock_irqsave(&fb->bufs_lock, flags); + buf = list_first_entry_or_null(&fb->bufs, struct chv3_fb_buffer, link); + if (buf) { + fb_start_frame(fb, buf); + if (!list_is_last(&buf->link, &fb->bufs)) + fb_next_frame(fb, list_next_entry(buf, link)); + } + spin_unlock_irqrestore(&fb->bufs_lock, flags); + + return 0; +} + +static void fb_stop_streaming(struct vb2_queue *q) +{ + struct chv3_fb *fb = vb2_get_drv_priv(q); + struct chv3_fb_buffer *buf; + unsigned long flags; + + fb->streaming = 0; + writel(0, fb->iobase + FB_EN); + + spin_lock_irqsave(&fb->bufs_lock, flags); + list_for_each_entry(buf, &fb->bufs, link) + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + INIT_LIST_HEAD(&fb->bufs); + spin_unlock_irqrestore(&fb->bufs_lock, flags); +} + +static struct chv3_fb_buffer *to_chv3_fb_buffer(struct vb2_v4l2_buffer *b) +{ + return container_of(b, struct chv3_fb_buffer, vb); +} + +static void fb_buf_queue(struct vb2_buffer *vb) +{ + struct chv3_fb *fb = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb); + struct chv3_fb_buffer *buf = to_chv3_fb_buffer(v4l2_buf); + bool first, second; + unsigned long flags; + + spin_lock_irqsave(&fb->bufs_lock, flags); + first = list_empty(&fb->bufs); + second = list_is_singular(&fb->bufs); + list_add_tail(&buf->link, &fb->bufs); + if (fb->streaming) { + if (first) + fb_start_frame(fb, buf); + else if (second) + fb_next_frame(fb, buf); + } + spin_unlock_irqrestore(&fb->bufs_lock, flags); +} + +static const struct vb2_ops fb_vb2_ops = { + .queue_setup = fb_queue_setup, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .start_streaming = fb_start_streaming, + .stop_streaming = fb_stop_streaming, + .buf_queue = fb_buf_queue, +}; + +/* file operations */ + +static int fb_open(struct file *file) +{ + struct chv3_fb *fb = video_drvdata(file); + int res; + + mutex_lock(&fb->fb_lock); + res = v4l2_fh_open(file); + if (!res) { + if (v4l2_fh_is_singular_file(file)) + fb_set_dma_format(fb, &chv3_dma_formats[0]); + } + mutex_unlock(&fb->fb_lock); + + return res; +} + +static const struct v4l2_file_operations fb_v4l2_fops = { + .owner = THIS_MODULE, + .open = fb_open, + .release = vb2_fop_release, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, + .poll = vb2_fop_poll, +}; + +/* irq handling */ + +static void fb_frame_irq(struct chv3_fb *fb) +{ + struct chv3_fb_buffer *buf; + + spin_lock(&fb->bufs_lock); + + buf = list_first_entry_or_null(&fb->bufs, struct chv3_fb_buffer, link); + if (!buf) + goto empty; + list_del(&buf->link); + + vb2_set_plane_payload(&buf->vb.vb2_buf, 0, fb->fmt.sizeimage); + buf->vb.vb2_buf.timestamp = ktime_get_ns(); + buf->vb.sequence = fb->sequence++; + buf->vb.field = V4L2_FIELD_NONE; + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE); + + buf = list_first_entry_or_null(&fb->bufs, struct chv3_fb_buffer, link); + if (buf) { + fb->writing_to_a = !fb->writing_to_a; + if (!list_is_last(&buf->link, &fb->bufs)) + fb_next_frame(fb, list_next_entry(buf, link)); + } else { + writel(0, fb->iobase + FB_EN); + } +empty: + spin_unlock(&fb->bufs_lock); +} + +static irqreturn_t fb_isr(int irq, void *data) +{ + struct chv3_fb *fb = data; + unsigned int reg; + + reg = readl(fb->iobase_irq + FB_IRQ_CLR); + if (!reg) + return IRQ_NONE; + + if (reg & (FB_IRQ_BUFF0 | FB_IRQ_BUFF1)) + fb_frame_irq(fb); + if (reg & FB_IRQ_ERROR) { + dev_warn(fb->dev, "framebuffer error: 0x%x\n", + readl(fb->iobase + FB_ERRORSTATUS)); + } + + writel(reg, fb->iobase_irq + FB_IRQ_CLR); + + return IRQ_HANDLED; +} + +/* driver probe & remove */ + +static int fb_check_version(struct chv3_fb *fb) +{ + u32 version; + + version = readl(fb->iobase + FB_VERSION); + if (version != FB_VERSION_CURRENT) { + dev_warn(fb->dev, + "wrong framebuffer version: expected %x, got %x\n", + FB_VERSION_CURRENT, version); + return -1; + } + return 0; +} + +int chv3_fb_register(struct chv3_fb *fb, + struct chv3_video *video, + const struct chv3_fb_cfg *cfg) +{ + struct platform_device *pdev = to_platform_device(video->dev); + int res; + int irq; + + fb->dev = video->dev; + fb->parent = video; + fb->index = cfg->index; + + /* map register space */ + fb->iobase = devm_platform_ioremap_resource_byname(pdev, cfg->reg_core); + if (IS_ERR(fb->iobase)) + return -ENOMEM; + + fb->iobase_irq = devm_platform_ioremap_resource_byname(pdev, cfg->reg_irq); + if (IS_ERR(fb->iobase_irq)) + return -ENOMEM; + + /* check hw version */ + if (fb_check_version(fb)) + return -ENODEV; + + /* setup interrupts */ + irq = platform_get_irq_byname(pdev, cfg->irq); + if (irq < 0) + return -ENXIO; + res = devm_request_irq(fb->dev, irq, fb_isr, 0, cfg->irq, fb); + if (res) + return res; + + /* setup dma */ + dma_set_coherent_mask(fb->dev, DMA_BIT_MASK(32)); + + /* initialize vb2 queue */ + fb->queue.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fb->queue.io_modes = VB2_MMAP; + fb->queue.dev = fb->dev; + fb->queue.lock = &fb->fb_lock; + fb->queue.ops = &fb_vb2_ops; + fb->queue.mem_ops = &vb2_dma_contig_memops; + fb->queue.drv_priv = fb; + fb->queue.buf_struct_size = sizeof(struct chv3_fb_buffer); + fb->queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + res = vb2_queue_init(&fb->queue); + if (res) + return res; + + /* register video_device */ + strcpy(fb->vdev.name, MODULE_NAME); + fb->vdev.fops = &fb_v4l2_fops; + fb->vdev.ioctl_ops = &fb_v4l2_ioctl_ops; + fb->vdev.lock = &fb->fb_lock; + fb->vdev.release = video_device_release_empty; + fb->vdev.device_caps = + V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; + fb->vdev.v4l2_dev = &video->v4l2_dev; + fb->vdev.queue = &fb->queue; + video_set_drvdata(&fb->vdev, fb); + res = video_register_device(&fb->vdev, VFL_TYPE_VIDEO, -1); + if (res) + return res; + + /* initialize rest of driver struct */ + INIT_LIST_HEAD(&fb->bufs); + spin_lock_init(&fb->bufs_lock); + mutex_init(&fb->fb_lock); + + /* initialize hw */ + writel(1, fb->iobase + FB_RESET); + writel(1, fb->iobase + FB_IODATARATE); + writel(1, fb->iobase + FB_IOPIXELMODE); + writel(FB_IRQ_BUFF0 | FB_IRQ_BUFF1 | FB_IRQ_ERROR, fb->iobase_irq + FB_IRQ_MASK); + + return 0; +} + +void chv3_fb_unregister(struct chv3_fb *fb) +{ + video_unregister_device(&fb->vdev); +} diff --git a/drivers/media/platform/google/chameleonv3/chv3-fb.h b/drivers/media/platform/google/chameleonv3/chv3-fb.h new file mode 100644 index 000000000000..2ece35a114df --- /dev/null +++ b/drivers/media/platform/google/chameleonv3/chv3-fb.h @@ -0,0 +1,34 @@ +struct chv3_fb { + struct device *dev; + void __iomem *iobase; + void __iomem *iobase_irq; + struct chv3_video *parent; + int index; + + struct vb2_queue queue; + struct video_device vdev; + struct v4l2_pix_format fmt; + + u32 sequence; + bool streaming; + bool writing_to_a; + + struct list_head bufs; + spinlock_t bufs_lock; + + struct mutex fb_lock; +}; + +struct chv3_fb_cfg { + const char *reg_core; + const char *reg_irq; + const char *irq; + int index; +}; + +int chv3_fb_register(struct chv3_fb *fb, + struct chv3_video *video, + const struct chv3_fb_cfg *cfg); + + +void chv3_fb_unregister(struct chv3_fb *fb); diff --git a/drivers/media/platform/google/chameleonv3/dprx-aux.c b/drivers/media/platform/google/chameleonv3/dprx-aux.c new file mode 100644 index 000000000000..56d82c963b4b --- /dev/null +++ b/drivers/media/platform/google/chameleonv3/dprx-aux.c @@ -0,0 +1,77 @@ +#include <linux/string.h> + +#include "dprx.h" + +static void handle_i2c_read(struct dprx_dp *dp, struct aux_msg *req, + struct aux_msg *res) +{ + int r; + + r = dprx_i2c_read(&dp->sinks[0], req->addr, res->data, req->len); + if (!r) { + res->cmd = AUX_ACK; + res->len = req->len; + } else { + res->cmd = AUX_I2C_NACK; + res->len = 0; + } +} + +static void handle_i2c_write(struct dprx_dp *dp, struct aux_msg *req, + struct aux_msg *res) +{ + int r; + + r = dprx_i2c_write(&dp->sinks[0], req->addr, req->data, req->len); + if (!r) + res->cmd = AUX_ACK; + else + res->cmd = AUX_I2C_NACK; + res->len = 0; +} + +void dprx_aux_handle_request(struct dprx_dp *dp, struct aux_msg *req, + struct aux_msg *res) +{ + if (req->cmd & 8) { + dprx_dpcd_access(dp, req, res); + } else { + if (req->cmd & 1) + handle_i2c_read(dp, req, res); + else + handle_i2c_write(dp, req, res); + if (!(req->cmd & 4)) + dp->sinks[0].segment = 0; + } +} + +int dprx_aux_read_request(struct dprx_dp *dp, struct aux_msg *req) +{ + u8 data[20]; + int len; + + len = dprx_dprx_read_aux(dp, data); + if (!len) + return 0; + + req->cmd = data[0] >> 4; + req->addr = (data[0] & 0xf) << 16 | data[1] << 8 | data[2]; + if (len < 4) { + req->len = 0; + } else { + req->len = data[3] + 1; + memcpy(req->data, &data[4], req->len); + } + + return 1; +} + +void dprx_aux_write_response(struct dprx_dp *dp, struct aux_msg *res) +{ + u8 data[20]; + + data[0] = res->cmd << 4; + memcpy(&data[1], res->data, res->len); + + dprx_dprx_write_aux(dp, data, res->len + 1); +} diff --git a/drivers/media/platform/google/chameleonv3/dprx-dp.c b/drivers/media/platform/google/chameleonv3/dprx-dp.c new file mode 100644 index 000000000000..ede98cb610f6 --- /dev/null +++ b/drivers/media/platform/google/chameleonv3/dprx-dp.c @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2022 Google LLC. + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include "dprx.h" + +#define DPRX_IRQ_MASK 0x8 +#define DPRX_IRQ_CLR 0xc +#define DPRX_IRQ_AUX 0x1 + +static irqreturn_t dprx_dp_isr(int irq, void *data) +{ + struct dprx_dp *dp = data; + unsigned int reg; + struct aux_msg request; + struct aux_msg response; + + reg = readl(dp->iobase_irq + DPRX_IRQ_CLR); + if (!reg) + return IRQ_NONE; + if (dprx_aux_read_request(dp, &request)) { + dprx_aux_handle_request(dp, &request, &response); + dprx_aux_write_response(dp, &response); + } + writel(reg, dp->iobase_irq + DPRX_IRQ_CLR); + return IRQ_HANDLED; +} + +static void dprx_sink_init(struct dprx_dp *dp) +{ + int i; + + for (i = 0; i < 4; i++) { + memcpy(dp->sinks[i].edid, default_edid, 128 * default_edid_blocks); + dp->sinks[i].blocks = default_edid_blocks; + } +} + +int dprx_dp_init(struct dprx_dp *dp, struct device *dev, + const struct dprx_dp_cfg *cfg) +{ + struct platform_device *pdev = to_platform_device(dev); + int irq; + int res; + + dp->dev = &pdev->dev; + + dp->iobase = devm_platform_ioremap_resource_byname(pdev, cfg->reg_core); + if (IS_ERR(dp->iobase)) + return PTR_ERR(dp->iobase); + + dp->iobase_irq = devm_platform_ioremap_resource_byname(pdev, cfg->reg_irq); + if (IS_ERR(dp->iobase_irq)) + return PTR_ERR(dp->iobase_irq); + + irq = platform_get_irq_byname(pdev, cfg->irq); + if (irq < 0) + return irq; + + res = devm_request_irq(dp->dev, irq, dprx_dp_isr, 0, cfg->irq, dp); + if (res) + return res; + + writel(DPRX_IRQ_AUX, dp->iobase_irq + DPRX_IRQ_MASK); + + dp->has_mst = cfg->has_mst; + dp->sink_count = cfg->sink_count; + + dprx_dprx_init(dp); + dprx_dpcd_init(dp); + dprx_sink_init(dp); + + dprx_dprx_set_hpd(dp, 1); + + return 0; +} diff --git a/drivers/media/platform/google/chameleonv3/dprx-dpcd.c b/drivers/media/platform/google/chameleonv3/dprx-dpcd.c new file mode 100644 index 000000000000..10110cc69dc5 --- /dev/null +++ b/drivers/media/platform/google/chameleonv3/dprx-dpcd.c @@ -0,0 +1,424 @@ +#include <linux/string.h> +#include "dprx.h" + +static void dpcd_clear_vc_payload_table(struct dprx_dp *dp) +{ + memset(dp->dpcd.vc_table, 0, 64); +} + +static void dpcd_allocate_vc_payload(struct dprx_dp *dp, int start, int count, u8 id) +{ + if (count > 64 - start) + count = 64 - start; + memset(dp->dpcd.vc_table + start, id, count); +} + +static void dpcd_deallocate_vc_payload(struct dprx_dp *dp, int start, u8 id) +{ + int i; + int to = start; + + for (i = start; i < 64; i++) { + if (dp->dpcd.vc_table[i] == id) + dp->dpcd.vc_table[i] = 0; + else + dp->dpcd.vc_table[to++] = dp->dpcd.vc_table[i]; + } +} + +static void dpcd_handle_payload_allocate(struct dprx_dp *dp) +{ + u8 id = dp->dpcd.vc_alloc[0x0]; + u8 start = dp->dpcd.vc_alloc[0x1]; + u8 count = dp->dpcd.vc_alloc[0x2]; + + if (id == 0 && start == 0 && count == 0x3f) { + dpcd_clear_vc_payload_table(dp); + dprx_dprx_clear_vc_payload_table(dp); + } else { + if (count == 0) + dpcd_deallocate_vc_payload(dp, start, id); + else + dpcd_allocate_vc_payload(dp, start, count, id); + dprx_dprx_set_vc_payload_table(dp, dp->dpcd.vc_table, dp->vc_id); + } + dp->dpcd.vc_table_status |= 1 << 0; +} + + + + + +/* 100h */ +static void dpcd_write_link_bw_set(struct dprx_dp *dp, u8 val) +{ + dp->dpcd.link_conf[0x0] = val; + dprx_dprx_set_link_rate(dp, val); +} + +/* 101h */ +static void dpcd_write_lane_count_set(struct dprx_dp *dp, u8 val) +{ + dp->dpcd.link_conf[0x1] = val; + dprx_dprx_set_lane_count(dp, val & 0x1f); +} + +/* 102h */ +static void dpcd_write_training_pattern_set(struct dprx_dp *dp, u8 val) +{ + dp->dpcd.link_conf[0x2] = val; + dprx_dprx_set_training_pattern(dp, val & 0xf); + dprx_dprx_set_scrambler(dp, !((val >> 5) & 1)); +} + +/* 111h */ +static void dpcd_write_mstm_ctrl(struct dprx_dp *dp, u8 *src, u32 offset, u32 count) +{ + u8 val = *src; + dp->dpcd.mstm_ctrl = val; + dprx_dprx_set_mst(dp, val & 1); +} + +/* 1c0h */ +static void dpcd_write_payload_allocate_set(struct dprx_dp *dp, u8 val) +{ + dp->dpcd.vc_alloc[0x0] = val & 0x7f; +} + +/* 1c1h */ +static void dpcd_write_payload_allocate_start_time_slot(struct dprx_dp *dp, u8 val) +{ + dp->dpcd.vc_alloc[0x1] = val & 0x3f; +} + +/* 1c2h */ +static void dpcd_write_payload_allocate_time_slot_count(struct dprx_dp *dp, u8 val) +{ + dp->dpcd.vc_alloc[0x2] = val & 0x3f; + dpcd_handle_payload_allocate(dp); +} + +/* 201h */ +static void dpcd_write_device_service_irq_vector(struct dprx_dp *dp, u8 val) +{ + dp->dpcd.irq_vector &= ~val; + + if (dprx_sbmsg_pending(dp)) { + dp->dpcd.irq_vector |= 1 << 4; + dprx_sbmsg_write(dp, dp->dpcd.down_rep, 48); + dprx_dprx_pulse_hpd(dp); + } +} + +/* 202h */ +static u8 dpcd_read_lane01_status(struct dprx_dp *dp) +{ + int cr_lock; + int sym_lock; + u8 res = 0; + + cr_lock = dprx_dprx_get_cr_lock(dp); + sym_lock = dprx_dprx_get_sym_lock(dp); + /* lane 0 */ + if (cr_lock & (1 << 0)) + res |= 0x1; + if (sym_lock & (1 << 0)) + res |= 0x6; + /* lane 1 */ + if (cr_lock & (1 << 1)) + res |= 0x10; + if (sym_lock & (1 << 1)) + res |= 0x60; + + return res; +} + +/* 203h */ +static u8 dpcd_read_lane23_status(struct dprx_dp *dp) +{ + int cr_lock; + int sym_lock; + u8 res = 0; + + cr_lock = dprx_dprx_get_cr_lock(dp); + sym_lock = dprx_dprx_get_sym_lock(dp); + /* lane 2 */ + if (cr_lock & (1 << 2)) + res |= 0x1; + if (sym_lock & (1 << 2)) + res |= 0x6; + /* lane 3 */ + if (cr_lock & (1 << 3)) + res |= 0x10; + if (sym_lock & (1 << 3)) + res |= 0x60; + + return res; +} + +/* 204h */ +static u8 dpcd_read_lane_align_status(struct dprx_dp *dp) +{ + return dprx_dprx_get_interlane_align(dp); +} + +/* 205h */ +static u8 dpcd_read_sink_status(struct dprx_dp *dp) +{ + return dprx_dprx_get_sink_status(dp); +} + +/* 2c0h */ +static void dpcd_read_payload_table_update_status(struct dprx_dp *dp, u8 *dest, u32 offset, u32 count) +{ + *dest = dp->dpcd.vc_table_status; + if (dprx_dprx_get_act(dp)) + *dest |= 1 << 1; +} + +/* 2c0h */ +static void dpcd_write_payload_table_update_status(struct dprx_dp *dp, u8 *src, u32 offset, u32 count) +{ + if (*src & 0x1) { + dp->dpcd.vc_table_status = 0; + dprx_dprx_clear_act(dp); + } +} + + + + + +static void dpcd_read_caps(struct dprx_dp *dp, u8 *dest, u32 offset, u32 count) +{ + memcpy(dest, dp->dpcd.caps + offset, count); +} + +static void dpcd_read_mstm_cap(struct dprx_dp *dp, u8 *dest, u32 offset, u32 count) +{ + *dest = dp->dpcd.mstm_cap; +} + +static void dpcd_read_guid(struct dprx_dp *dp, u8 *dest, u32 offset, u32 count) +{ + memcpy(dest, dp->dpcd.guid + offset, count); +} + +static void dpcd_write_guid(struct dprx_dp *dp, u8 *src, u32 offset, u32 count) +{ + memcpy(dp->dpcd.guid + offset, src, count); +} + +static void dpcd_read_link_conf(struct dprx_dp *dp, u8 *dest, u32 offset, u32 count) +{ + memcpy(dest, dp->dpcd.link_conf + offset, count); +} + +static void dpcd_write_link_conf(struct dprx_dp *dp, u8 *src, u32 offset, u32 count) +{ + if (offset <= 0 && 0 < offset + count) + dpcd_write_link_bw_set(dp, src[0 - offset]); + if (offset <= 1 && 1 < offset + count) + dpcd_write_lane_count_set(dp, src[1 - offset]); + if (offset <= 2 && 2 < offset + count) + dpcd_write_training_pattern_set(dp, src[2 - offset]); + + while (dprx_dprx_get_rx_busy(dp)) {} +} + +static void dpcd_read_mstm_ctrl(struct dprx_dp *dp, u8 *dest, u32 start, u32 count) +{ + *dest = dp->dpcd.mstm_ctrl; +} + +static void dpcd_read_vc_alloc(struct dprx_dp *dp, u8 *dest, u32 offset, u32 count) +{ + memcpy(dest, dp->dpcd.vc_alloc + offset, count); +} + +static void dpcd_write_vc_alloc(struct dprx_dp *dp, u8 *src, u32 offset, u32 count) +{ + if (offset <= 0 && 0 < offset + count) + dpcd_write_payload_allocate_set(dp, src[0 - offset]); + if (offset <= 1 && 1 < offset + count) + dpcd_write_payload_allocate_start_time_slot(dp, src[1 - offset]); + if (offset <= 2 && 2 < offset + count) + dpcd_write_payload_allocate_time_slot_count(dp, src[2 - offset]); +} + +static void dpcd_read_sink_stat(struct dprx_dp *dp, u8 *dest, u32 offset, u32 count) +{ + if (offset <= 0 && 0 < offset + count) + dest[0 - offset] = dp->dpcd.sink_count; + if (offset <= 1 && 1 < offset + count) + dest[1 - offset] = dp->dpcd.irq_vector; +} + +static void dpcd_write_sink_stat(struct dprx_dp *dp, u8 *src, u32 offset, u32 count) +{ + if (offset <= 1 && 1 < offset + count) + dpcd_write_device_service_irq_vector(dp, src[1 - offset]); +} + +static void dpcd_read_link_stat(struct dprx_dp *dp, u8 *dest, u32 offset, u32 count) +{ + if (offset <= 0 && 0 < offset + count) + dest[0 - offset] = dpcd_read_lane01_status(dp); + if (offset <= 1 && 1 < offset + count) + dest[1 - offset] = dpcd_read_lane23_status(dp); + if (offset <= 2 && 2 < offset + count) + dest[2 - offset] = dpcd_read_lane_align_status(dp); + if (offset <= 3 && 3 < offset + count) + dest[3 - offset] = dpcd_read_sink_status(dp); + if (offset <= 4 && 4 < offset + count) + dest[4 - offset] = 0x55; + if (offset <= 5 && 5 < offset + count) + dest[5 - offset] = 0x55; +} + +static void dpcd_read_vc_table(struct dprx_dp *dp, u8 *dest, u32 offset, u32 count) +{ + memcpy(dest, dp->dpcd.vc_table + offset + 1, count); +} + +static void dpcd_read_sink_spec(struct dprx_dp *dp, u8 *dest, u32 offset, u32 count) +{ + memcpy(dest, dp->dpcd.sink_spec + offset, count); +} + +static void dpcd_read_down_req(struct dprx_dp *dp, u8 *dest, u32 offset, u32 count) +{ + memcpy(dest, dp->dpcd.down_req + offset, count); +} + +static void dpcd_write_down_req(struct dprx_dp *dp, u8 *src, u32 offset, u32 count) +{ + memcpy(dp->dpcd.down_req + offset, src, count); + /* + * The sideband message may require multiple AUX transactions to be + * fully written. Normally, the source writes the data in order, + * in blocks of 16. Unfortunately, the spec doesn't say what to + * do if the source behaves differently that that. + * + * Approach taken here: when we get a write, assume all the + * bytes before the starting address are valid, try to parse + * the message up to the last byte written in this transaction + * (if it's incomplete, nothing happens). + */ + dprx_sbmsg_read(dp, dp->dpcd.down_req, offset + count); + if (!(dp->dpcd.irq_vector & (1 << 4)) && dprx_sbmsg_pending(dp)) { + dp->dpcd.irq_vector |= 1 << 4; + dprx_sbmsg_write(dp, dp->dpcd.down_rep, 48); + dprx_dprx_pulse_hpd(dp); + } +} + +static void dpcd_read_down_rep(struct dprx_dp *dp, u8 *dest, u32 offset, u32 count) +{ + memcpy(dest, dp->dpcd.down_rep + offset, count); +} + +struct dpcd_range { + u32 start; + u32 end; + void (*read) (struct dprx_dp *, u8 *, u32, u32); + void (*write)(struct dprx_dp *, u8 *, u32, u32); +}; + +struct dpcd_range dpcd_ranges[] = { + { 0x00000, 0x00010, dpcd_read_caps, NULL }, + { 0x00021, 0x00022, dpcd_read_mstm_cap, NULL }, + { 0x00030, 0x00040, dpcd_read_guid, dpcd_write_guid }, + { 0x00100, 0x00103, dpcd_read_link_conf, dpcd_write_link_conf }, + { 0x00111, 0x00112, dpcd_read_mstm_ctrl, dpcd_write_mstm_ctrl }, + { 0x001c0, 0x001c3, dpcd_read_vc_alloc, dpcd_write_vc_alloc }, + { 0x00200, 0x00202, dpcd_read_sink_stat, dpcd_write_sink_stat }, + { 0x00202, 0x00208, dpcd_read_link_stat, NULL }, + { 0x002c0, 0x002c1, dpcd_read_payload_table_update_status, dpcd_write_payload_table_update_status }, + { 0x002c1, 0x00300, dpcd_read_vc_table, NULL }, + { 0x00400, 0x0040c, dpcd_read_sink_spec, NULL }, + { 0x01000, 0x01030, dpcd_read_down_req, dpcd_write_down_req }, + { 0x01400, 0x01430, dpcd_read_down_rep, NULL }, + { 0x02002, 0x02004, dpcd_read_sink_stat, dpcd_write_sink_stat }, + { 0x0200c, 0x02010, dpcd_read_link_stat, NULL }, +}; + +void dprx_dpcd_access(struct dprx_dp *dp, struct aux_msg *req, + struct aux_msg *res) +{ + struct dpcd_range *range; + struct dpcd_range *range_end = dpcd_ranges + ARRAY_SIZE(dpcd_ranges); + bool read = req->cmd & 1; + u32 start; + u32 end; + u8 *buf; + u32 offset; + u32 count; + + res->cmd = AUX_ACK; + if (read) { + res->len = req->len; + memset(res->data, 0, res->len); + } else { + res->len = 0; + } + + for (range = dpcd_ranges; range < range_end; range++) { + if (range->end <= req->addr || req->addr + req->len <= range->start) + continue; + start = max(range->start, req->addr); + end = min(range->end, req->addr + req->len); + count = end - start; + offset = start - range->start; + if (read) { + buf = res->data + (start - req->addr); + range->read(dp, buf, offset, count); + } else if (range->write) { + buf = req->data + (start - req->addr); + range->write(dp, buf, offset, count); + } + } +} + +void dprx_dpcd_init(struct dprx_dp *dp) +{ + struct dpcd_mem *dpcd = &dp->dpcd; + + memset(dpcd, 0, sizeof(struct dpcd_mem)); + + dpcd->caps[0x0] = 0x14, // DPCD 1.4 + dpcd->caps[0x1] = 0x1e, // Max link rate 8.1Gbps + dpcd->caps[0x2] = 0xc4, // Max lane count 4, TPS3, Enhanced frame cap + dpcd->caps[0x3] = 0x81, // Down-spread, TPS4 + dpcd->caps[0x4] = 0x01, // 2 Reciever ports for SST (video & audio) + dpcd->caps[0x5] = 0x00, // no downstream ports + dpcd->caps[0x6] = 0x01, // 8b/10b support + dpcd->caps[0x7] = 0x80, // no downstream ports, OUI present + dpcd->caps[0x8] = 0x02, // has local EDID + dpcd->caps[0x9] = 0x00, // buffer size? + dpcd->caps[0xa] = 0x06, + dpcd->caps[0xb] = 0x00, + dpcd->caps[0xc] = 0x00, // no physical i2c bus + dpcd->caps[0xd] = 0x00, // reserved for eDP + dpcd->caps[0xe] = 0x00, // no extended receiver capability present + dpcd->caps[0xf] = 0x00, // no legacy adaptor caps + + dpcd->mstm_cap = dp->has_mst; + dpcd->sink_count = dp->has_mst ? dp->sink_count : 1; + + dpcd->sink_spec[0x0] = 0x12; + dpcd->sink_spec[0x1] = 0x34; + dpcd->sink_spec[0x2] = 0x56; + dpcd->sink_spec[0x3] = 'c'; + dpcd->sink_spec[0x4] = 'h'; + dpcd->sink_spec[0x5] = 'a'; + dpcd->sink_spec[0x6] = 'm'; + dpcd->sink_spec[0x7] = 'e'; + dpcd->sink_spec[0x8] = 'l'; + dpcd->sink_spec[0x9] = 0x30; + dpcd->sink_spec[0xa] = 0x00; + dpcd->sink_spec[0xb] = 0x00; + + dpcd_write_link_bw_set(dp, 0x1e); + dpcd_write_lane_count_set(dp, 0x04); +}; diff --git a/drivers/media/platform/google/chameleonv3/dprx-dprx.c b/drivers/media/platform/google/chameleonv3/dprx-dprx.c new file mode 100644 index 000000000000..7c7b196539ed --- /dev/null +++ b/drivers/media/platform/google/chameleonv3/dprx-dprx.c @@ -0,0 +1,262 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2022 Google LLC. + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/interrupt.h> +#include <linux/delay.h> + +#include "dprx.h" + +#define DPRX_RX_CONTROL 0x000 +#define DPRX_RX_STATUS 0x001 +#define DPRX0_VBID 0x02f +#define DPRX_MST_CONTROL1 0x0a0 +#define DPRX_MST_STATUS1 0x0a1 +#define DPRX_MST_VCPTAB0 0x0a2 +#define DPRX_AUX_CONTROL 0x100 +#define DPRX_AUX_STATUS 0x101 +#define DPRX_AUX_COMMAND 0x102 +#define DPRX_AUX_HPD 0x119 + +static void dp_wr(struct dprx_dp *dp, int addr, u32 val) +{ + writel(val, dp->iobase + (addr * 4)); +} + +static u32 dp_rd(struct dprx_dp *dp, int addr) +{ + return readl(dp->iobase + (addr * 4)); +} + +/* HPD */ + +void dprx_dprx_set_hpd(struct dprx_dp *dp, int val) +{ + u32 reg; + + reg = dp_rd(dp, DPRX_AUX_HPD); + reg &= ~(1 << 11); + reg |= (val & 1) << 11; + dp_wr(dp, DPRX_AUX_HPD, reg); +} + +int dprx_dprx_get_hpd(struct dprx_dp *dp) +{ + return (dp_rd(dp, DPRX_AUX_HPD) >> 11) & 1; +} + +void dprx_dprx_pulse_hpd(struct dprx_dp *dp) +{ + u32 reg; + + reg = dp_rd(dp, DPRX_AUX_HPD); + reg |= 1 << 12; + dp_wr(dp, DPRX_AUX_HPD, reg); +} + +/* Receiver Control */ + +void dprx_dprx_set_link_rate(struct dprx_dp *dp, int val) +{ + u32 reg; + + reg = dp_rd(dp, DPRX_RX_CONTROL); + reg &= ~(0xff << 16); + reg |= (val & 0xff) << 16; + reg |= 1 << 13; + dp_wr(dp, DPRX_RX_CONTROL, reg); +} + +void dprx_dprx_set_lane_count(struct dprx_dp *dp, int val) +{ + u32 reg; + + reg = dp_rd(dp, DPRX_RX_CONTROL); + reg &= ~0x1f; + reg |= (val & 0x1f); + dp_wr(dp, DPRX_RX_CONTROL, reg); +} + +void dprx_dprx_set_training_pattern(struct dprx_dp *dp, int val) +{ + u32 reg; + + reg = dp_rd(dp, DPRX_RX_CONTROL); + reg &= ~(0x7 << 8); + reg |= (val & 0x7) << 8; + dp_wr(dp, DPRX_RX_CONTROL, reg); +} + +void dprx_dprx_set_scrambler(struct dprx_dp *dp, int val) +{ + u32 reg; + + reg = dp_rd(dp, DPRX_RX_CONTROL); + reg &= ~(1 << 7); + reg |= (~val & 1) << 7; + dp_wr(dp, DPRX_RX_CONTROL, reg); +} + +/* Receiver Status */ + +int dprx_dprx_get_cr_lock(struct dprx_dp *dp) +{ + return dp_rd(dp, DPRX_RX_STATUS) & 0xf; +} + +int dprx_dprx_get_sym_lock(struct dprx_dp *dp) +{ + return (dp_rd(dp, DPRX_RX_STATUS) >> 4) & 0xf; +} + +int dprx_dprx_get_interlane_align(struct dprx_dp *dp) +{ + return (dp_rd(dp, DPRX_RX_STATUS) >> 8) & 0x1; +} + +int dprx_dprx_get_sink_status(struct dprx_dp *dp) +{ + return (dp_rd(dp, DPRX0_VBID) >> 7) & 0x1; +} + +int dprx_dprx_get_rx_busy(struct dprx_dp *dp) +{ + return (dp_rd(dp, DPRX_RX_STATUS) >> 17) & 0x1; +} + +/* MST */ + +void dprx_dprx_set_mst(struct dprx_dp *dp, int val) +{ + u32 reg; + + reg = dp_rd(dp, DPRX_MST_CONTROL1); + reg &= ~0x1; + reg |= (val & 0x1); + dp_wr(dp, DPRX_MST_CONTROL1, reg); +} + +void dprx_dprx_clear_vc_payload_table(struct dprx_dp *dp) +{ + u32 reg; + int i; + + for (i = 0; i < 8; i++) + dp_wr(dp, DPRX_MST_VCPTAB0 + i, 0); + + reg = dp_rd(dp, DPRX_MST_CONTROL1); + reg &= ~(0xffff << 4); + reg |= 1 << 31; + dp_wr(dp, DPRX_MST_CONTROL1, reg); +} + +void dprx_dprx_set_vc_payload_table(struct dprx_dp *dp, u8 *table, u8 *id) +{ + u8 map[64]; + int i, j; + u32 reg; + + memset(map, 0, 64); + for (i = 0; i < 4; i++) { + if (id[i] != 0 && id[i] < 64) + map[id[i]] = i + 1; + } + + for (i = 0; i < 8; i++) { + reg = 0; + for (j = 0; j < 8; j++) + reg |= map[table[i*8+j]] << (j * 4); + dp_wr(dp, DPRX_MST_VCPTAB0 + i, reg); + } + + reg = dp_rd(dp, DPRX_MST_CONTROL1); + reg &= ~(0xffff << 4); + for (i = 0; i < 4; i++) + if (id[i] != 0 && id[i] < 64) + reg |= (i + 1) << ((i + 1) * 4); + reg |= 1 << 30; + dp_wr(dp, DPRX_MST_CONTROL1, reg); +} + +int dprx_dprx_get_act(struct dprx_dp *dp) +{ + return (dp_rd(dp, DPRX_MST_STATUS1) >> 30) & 1; +} + +void dprx_dprx_clear_act(struct dprx_dp *dp) +{ + u32 reg; + + reg = dp_rd(dp, DPRX_MST_CONTROL1); + reg &= ~(1 << 30); + dp_wr(dp, DPRX_MST_CONTROL1, reg); +} + +/* AUX CH */ + +int dprx_dprx_read_aux(struct dprx_dp *dp, u8 *data) +{ + int length; + u32 reg; + int i; + + /* check MSG_READY */ + reg = dp_rd(dp, DPRX_AUX_STATUS); + if (!(reg & (1 << 31))) + return 0; + + /* read LENGTH */ + length = dp_rd(dp, DPRX_AUX_CONTROL) & 0x1f; + if (length > 20) + length = 20; + + /* read request */ + for (i = 0; i < length; i++) + data[i] = dp_rd(dp, DPRX_AUX_COMMAND + i); + + return length; +} + +void dprx_dprx_write_aux(struct dprx_dp *dp, u8 *data, int length) +{ + u32 reg; + int i; + + /* check READY_TO_TX */ + reg = dp_rd(dp, DPRX_AUX_STATUS); + if (!(reg & (1 << 30))) + return; + + /* write request */ + if (length > 17) + length = 17; + for (i = 0; i < length; i++) + dp_wr(dp, DPRX_AUX_COMMAND + i, data[i]); + + /* write LENGTH and TX_STROBE */ + reg = dp_rd(dp, DPRX_AUX_CONTROL); + reg &= ~0x1f; + reg |= length | (1 << 7); + dp_wr(dp, DPRX_AUX_CONTROL, reg); +} + +/* Misc */ + +void dprx_dprx_init(struct dprx_dp *dp) +{ + u32 reg; + + /* Enable AUX_IRQ_EN */ + reg = dp_rd(dp, DPRX_AUX_CONTROL); + reg |= 1 << 8; + dp_wr(dp, DPRX_AUX_CONTROL, reg); + + /* Set CHANNEL_CODING_SET to 8b/10b */ + reg = dp_rd(dp, DPRX_RX_CONTROL); + reg |= 1 << 5; + dp_wr(dp, DPRX_RX_CONTROL, reg); +} diff --git a/drivers/media/platform/google/chameleonv3/dprx-edid.c b/drivers/media/platform/google/chameleonv3/dprx-edid.c new file mode 100644 index 000000000000..19d3a6182eeb --- /dev/null +++ b/drivers/media/platform/google/chameleonv3/dprx-edid.c @@ -0,0 +1,39 @@ +#include <linux/kernel.h> + +u8 default_edid[256] = { + 0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00, + 0x34,0xA9,0x96,0xA2,0x01,0x01,0x01,0x01, + 0x00,0x17,0x01,0x03,0x80,0x80,0x48,0x78, + 0x0A,0xDA,0xFF,0xA3,0x58,0x4A,0xA2,0x29, + 0x17,0x49,0x4B,0x21,0x08,0x00,0x31,0x40, + 0x45,0x40,0x61,0x40,0x81,0x80,0x01,0x01, + 0x01,0x01,0x01,0x01,0x01,0x01,0x08,0xE8, + 0x00,0x30,0xF2,0x70,0x5A,0x80,0xB0,0x58, + 0x8A,0x00,0xBA,0x88,0x21,0x00,0x00,0x1E, + 0x02,0x3A,0x80,0x18,0x71,0x38,0x2D,0x40, + 0x58,0x2C,0x45,0x00,0xBA,0x88,0x21,0x00, + 0x00,0x1E,0x00,0x00,0x00,0xFC,0x00,0x50, + 0x61,0x6E,0x61,0x73,0x6F,0x6E,0x69,0x63, + 0x2D,0x54,0x56,0x0A,0x00,0x00,0x00,0xFD, + 0x00,0x17,0x3D,0x0F,0x88,0x3C,0x00,0x0A, + 0x20,0x20,0x20,0x20,0x20,0x20,0x01,0xA0, + + 0x02,0x03,0x4F,0xF0,0x57,0x1F,0x10,0x14, + 0x05,0x20,0x21,0x22,0x13,0x04,0x12,0x03, + 0x16,0x07,0x60,0x61,0x5D,0x5E,0x5F,0x65, + 0x66,0x62,0x63,0x64,0x23,0x09,0x07,0x01, + 0x7E,0x03,0x0C,0x00,0x40,0x00,0xB8,0x3C, + 0x2F,0xC8,0x90,0x01,0x02,0x03,0x04,0x81, + 0x41,0x01,0x9C,0x06,0x16,0x08,0x00,0x18, + 0x00,0x96,0xA6,0x98,0x00,0xA8,0x00,0x67, + 0xD8,0x5D,0xC4,0x01,0x78,0x80,0x03,0xE2, + 0x00,0x4B,0xE4,0x0F,0x00,0x60,0x0C,0x56, + 0x5E,0x00,0xA0,0xA0,0xA0,0x29,0x50,0x30, + 0x20,0x35,0x00,0xBA,0x88,0x21,0x00,0x00, + 0x1A,0x66,0x21,0x56,0xAA,0x51,0x00,0x1E, + 0x30,0x46,0x8F,0x33,0x00,0xBA,0x88,0x21, + 0x00,0x00,0x1E,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5F +}; + +u8 default_edid_blocks = 2; diff --git a/drivers/media/platform/google/chameleonv3/dprx-i2c.c b/drivers/media/platform/google/chameleonv3/dprx-i2c.c new file mode 100644 index 000000000000..2f0faac7352b --- /dev/null +++ b/drivers/media/platform/google/chameleonv3/dprx-i2c.c @@ -0,0 +1,41 @@ +#include <linux/string.h> +#include "dprx.h" + +int dprx_i2c_read(struct sink *sink, u8 addr, u8 *buf, int len) +{ + int offset; + + if (addr == 0x50) { + offset = sink->offset + sink->segment * 256; + if (len + offset > sink->blocks * 128) + return -1; + memcpy(buf, sink->edid + offset, len); + sink->offset += len; + } else if (addr == 0x30) { + if (len == 1) + buf[0] = sink->segment; + else if (len > 1) + return -1; + } + + return 0; +} + +int dprx_i2c_write(struct sink *sink, u8 addr, u8 *buf, int len) +{ + if (addr == 0x50) { + if (len == 1) + sink->offset = buf[0]; + else if (len > 1) + return -1; + } else if (addr == 0x30) { + if (len == 1) + sink->segment = buf[0]; + else if (len > 1) + return -1; + } else { + return -1; + } + + return 0; +} diff --git a/drivers/media/platform/google/chameleonv3/dprx-mt.c b/drivers/media/platform/google/chameleonv3/dprx-mt.c new file mode 100644 index 000000000000..7b39b2e41d22 --- /dev/null +++ b/drivers/media/platform/google/chameleonv3/dprx-mt.c @@ -0,0 +1,184 @@ +#include <linux/string.h> +#include "dprx.h" + +#define MT_GET_MESSAGE_TRANSACTION_VERSION 0x00 +#define MT_LINK_ADDRESS 0x01 +#define MT_CONNECTION_STATUS_NOTIFY 0x02 +#define MT_ENUM_PATH_RESOURCES 0x10 +#define MT_ALLOCATE_PAYLOAD 0x11 +#define MT_QUERY_PAYLOAD 0x12 +#define MT_RESOURCE_STATUS_NOTIFY 0x13 +#define MT_CLEAR_PAYLOAD_ID_TABLE 0x14 +#define MT_REMOTE_DPCD_READ 0x20 +#define MT_REMOTE_DPCD_WRITE 0x21 +#define MT_REMOTE_I2C_READ 0x22 +#define MT_REMOTE_I2C_WRITE 0x23 +#define MT_POWER_UP_PHY 0x24 +#define MT_POWER_DOWN_PHY 0x25 +#define MT_SINK_EVENT_NOTIFY 0x30 +#define MT_QUERY_STREAM_ENCRYPTION_STATUS 0x38 + +#define MT_NACK 0x80 +#define MT_BAD_PARAM 0x4 + +static void execute_link_address(struct dprx_dp *dp, + struct msg_transaction *req, + struct msg_transaction *rep) +{ + int ports = dp->sink_count + 1; + u8 *buf; + int i; + + rep->buf[0] = MT_LINK_ADDRESS; + memcpy(rep->buf + 1, dp->dpcd.guid, 16); + rep->buf[17] = ports; + /* port 0 */ + rep->buf[18] = 0x90; /* input, source device, port 0 */ + rep->buf[19] = 0x40; /* no msg, connected */ + + buf = rep->buf + 20; + for (i = 1; i < ports; i++) { + buf[0] = 0x30 | i; /* output, sink device, port i */ + buf[1] = 0x40; /* no msg, connected */ + buf[2] = 0x00; /* DPCD 0 */ + memset(buf + 3, 0, 16); /* GUID */ + buf[19] = 0x00; /* 0 SDP streams, 0 SDP stream sinks */ + buf += 20; + } + rep->len = ports * 20; +} + +static void execute_enum_path_resources(struct dprx_dp *dp, + struct msg_transaction *req, + struct msg_transaction *rep) +{ + u8 port; + + port = req->buf[1] >> 4; + + dp->total_pbn = dp->dpcd.link_conf[0] * + dp->dpcd.link_conf[1] * 32; + + rep->buf[0] = MT_ENUM_PATH_RESOURCES; + rep->buf[1] = port << 4; + rep->buf[2] = dp->total_pbn >> 8; + rep->buf[3] = dp->total_pbn & 0xff; + rep->buf[4] = (dp->total_pbn - dp->sum_pbn) >> 8; + rep->buf[5] = (dp->total_pbn - dp->sum_pbn) & 0xff; + rep->len = 6; +} + +static void execute_allocate_payload(struct dprx_dp *dp, + struct msg_transaction *req, + struct msg_transaction *rep) +{ + u8 port; + u8 id; + u16 pbn; + + port = req->buf[1] >> 4; + id = req->buf[2] & 0x7f; + pbn = req->buf[3] << 8 | req->buf[4]; + + dp->vc_id[port-1] = id; + + rep->buf[0] = MT_ALLOCATE_PAYLOAD; + rep->buf[1] = port << 4; + rep->buf[2] = id; + rep->buf[3] = pbn >> 8; + rep->buf[4] = pbn & 0xff; + rep->len = 5; +} + +static void execute_clear_payload_id_table(struct dprx_dp *dp, + struct msg_transaction *req, + struct msg_transaction *rep) +{ + dprx_dprx_clear_vc_payload_table(dp); + + rep->buf[0] = MT_CLEAR_PAYLOAD_ID_TABLE; + rep->len = 1; +} + +static void execute_remote_i2c_read(struct dprx_dp *dp, + struct msg_transaction *req, + struct msg_transaction *rep) +{ + u8 *req_buf = req->buf; + struct sink *sink; + u8 port; + int num_write_transactions; + u8 addr; + int len; + int i; + + port = req_buf[1] >> 4; + + if (port < 1 || port > dp->sink_count) { + rep->buf[0] = MT_NACK | MT_REMOTE_I2C_READ; + memcpy(&rep->buf[1], dp->dpcd.guid, 16); + rep->buf[17] = MT_BAD_PARAM; + rep->buf[18] = 0; + rep->len = 18; + return; + } + + sink = &dp->sinks[port-1]; + + num_write_transactions = req_buf[1] & 0x3; + req_buf += 2; + for (i = 0; i < num_write_transactions; i++) { + addr = req_buf[0] & 0x7f; + len = req_buf[1]; + dprx_i2c_write(sink, addr, &req_buf[2], len); + req_buf += len + 3; + } + addr = req_buf[0] & 0x7f; + len = req_buf[1]; + + rep->buf[0] = MT_REMOTE_I2C_READ; + rep->buf[1] = port; + rep->buf[2] = len; + dprx_i2c_read(sink, addr, rep->buf + 3, len); + rep->len = len + 3; +} + +static void execute_power_up_phy(struct dprx_dp *dp, + struct msg_transaction *req, + struct msg_transaction *rep) +{ + u8 port; + + port = req->buf[1] >> 4; + + rep->buf[0] = MT_POWER_UP_PHY; + rep->buf[1] = port << 4; + rep->len = 2; +} + +void dprx_mt_execute(struct dprx_dp *dp, struct msg_transaction *req, + struct msg_transaction *rep) +{ + switch (req->buf[0] & 0x7f) { + case MT_LINK_ADDRESS: + execute_link_address(dp, req, rep); + break; + case MT_ENUM_PATH_RESOURCES: + execute_enum_path_resources(dp, req, rep); + break; + case MT_ALLOCATE_PAYLOAD: + execute_allocate_payload(dp, req, rep); + break; + case MT_CLEAR_PAYLOAD_ID_TABLE: + execute_clear_payload_id_table(dp, req, rep); + break; + case MT_REMOTE_I2C_READ: + execute_remote_i2c_read(dp, req, rep); + break; + case MT_POWER_UP_PHY: + execute_power_up_phy(dp, req, rep); + break; + default: + rep->len = 0; + } +} diff --git a/drivers/media/platform/google/chameleonv3/dprx-sbmsg.c b/drivers/media/platform/google/chameleonv3/dprx-sbmsg.c new file mode 100644 index 000000000000..ae5db31f225a --- /dev/null +++ b/drivers/media/platform/google/chameleonv3/dprx-sbmsg.c @@ -0,0 +1,162 @@ +#include <linux/string.h> +#include "dprx.h" + +static u8 get_hdr_crc4(const uint8_t *data, size_t num_nibbles) +{ + u8 bitmask = 0x80; + u8 bitshift = 7; + u8 array_index = 0; + int number_of_bits = num_nibbles * 4; + u8 remainder = 0; + + while (number_of_bits != 0) { + number_of_bits--; + remainder <<= 1; + remainder |= (data[array_index] & bitmask) >> bitshift; + bitmask >>= 1; + bitshift--; + if (bitmask == 0) { + bitmask = 0x80; + bitshift = 7; + array_index++; + } + if ((remainder & 0x10) == 0x10) + remainder ^= 0x13; + } + + number_of_bits = 4; + while (number_of_bits != 0) { + number_of_bits--; + remainder <<= 1; + if ((remainder & 0x10) != 0) + remainder ^= 0x13; + } + + return remainder; +} + +static u8 get_body_crc4(const uint8_t *data, u8 number_of_bytes) +{ + u8 bitmask = 0x80; + u8 bitshift = 7; + u8 array_index = 0; + int number_of_bits = number_of_bytes * 8; + u16 remainder = 0; + + while (number_of_bits != 0) { + number_of_bits--; + remainder <<= 1; + remainder |= (data[array_index] & bitmask) >> bitshift; + bitmask >>= 1; + bitshift--; + if (bitmask == 0) { + bitmask = 0x80; + bitshift = 7; + array_index++; + } + if ((remainder & 0x100) == 0x100) + remainder ^= 0xd5; + } + + number_of_bits = 8; + while (number_of_bits != 0) { + number_of_bits--; + remainder <<= 1; + if ((remainder & 0x100) != 0) + remainder ^= 0xd5; + } + + return remainder & 0xff; +} + + +void dprx_sbmsg_read(struct dprx_dp *dp, u8 *buf, int len) +{ + int link_count_total; + int rad_len; + int hdr_len; + int body_len; + bool start; + bool end; + int seq_no; + struct msg_transaction *req; + struct msg_transaction *rep; + + link_count_total = buf[0] >> 4; + rad_len = link_count_total / 2; + hdr_len = rad_len + 3; + body_len = buf[rad_len + 1] & 0x3f; + + /* If message is incomplete, do nothing */ + if (hdr_len + body_len > len) + return; + + start = (buf[rad_len + 2] >> 7) & 1; + end = (buf[rad_len + 2] >> 6) & 1; + seq_no = (buf[rad_len + 2] >> 4) & 1; + + req = &dp->mt_req[seq_no]; + rep = &dp->mt_rep[seq_no]; + + if (start) + req->len = 0; + /* TODO: check overflow */ + memcpy(req->buf + req->len, buf + hdr_len, body_len - 1); + req->len += body_len - 1; + + if (end) { + rep->written = 0; + memcpy(rep->rad, buf + 1, rad_len); + rep->link_count_total = link_count_total; + dprx_mt_execute(dp, req, rep); + } +} + +void dprx_sbmsg_write(struct dprx_dp *dp, u8 *buf, int buf_len) +{ + int rad_len; + int hdr_len; + int body_len; + bool start; + bool end; + u8 hdr_crc4; + u8 body_crc4; + struct msg_transaction *rep; + + rep = &dp->mt_rep[dp->mt_seq_no]; + if (rep->len == 0) { + dp->mt_seq_no ^= 1; + rep = &dp->mt_rep[dp->mt_seq_no]; + if (rep->len == 0) + return; + } + + rad_len = rep->link_count_total / 2; + hdr_len = rad_len + 3; + body_len = min(rep->len - rep->written + 1, buf_len - hdr_len); + + start = (rep->written == 0); + end = (rep->written + body_len - 1 == rep->len); + + buf[0] = rep->link_count_total << 4 | ((rep->link_count_total - 1) & 0xf); + memcpy(buf + 1, rep->rad, rad_len); + buf[rad_len + 1] = body_len; + buf[rad_len + 2] = start << 7 | end << 6 | dp->mt_seq_no << 4; + hdr_crc4 = get_hdr_crc4(buf, hdr_len * 2 - 1); + buf[rad_len + 2] |= hdr_crc4; + memcpy(buf + hdr_len, rep->buf + rep->written, body_len - 1); + body_crc4 = get_body_crc4(buf + hdr_len, body_len - 1); + buf[hdr_len + body_len - 1] = body_crc4; + rep->written += body_len - 1; + + if (end) { + rep->len = 0; + rep->written = 0; + dp->mt_seq_no ^= 1; + } +} + +bool dprx_sbmsg_pending(struct dprx_dp *dp) +{ + return dp->mt_rep[0].len > 0 || dp->mt_rep[1].len > 0; +} diff --git a/drivers/media/platform/google/chameleonv3/dprx.h b/drivers/media/platform/google/chameleonv3/dprx.h new file mode 100644 index 000000000000..8c48b775c9bd --- /dev/null +++ b/drivers/media/platform/google/chameleonv3/dprx.h @@ -0,0 +1,128 @@ +#include <linux/kernel.h> + +struct dprx_dp_cfg { + const char *reg_core; + const char *reg_irq; + const char *irq; + int has_mst; + int sink_count; +}; + +struct msg_transaction { + u8 buf[256]; + int len; + int written; + u8 rad[16]; + int link_count_total; +}; + +struct dpcd_mem { + u8 caps[0x10]; /* 00000 - 0000f */ + u8 mstm_cap; /* 00021 */ + u8 guid[0x10]; /* 00030 - 0003f */ + u8 link_conf[0x3]; /* 00100 - 00102 */ + u8 mstm_ctrl; /* 00111 */ + u8 vc_alloc[0x3]; /* 001c0 - 001c2 */ + u8 sink_count; /* 00200 */ + u8 irq_vector; /* 00201 */ + u8 lane_align_status; /* 00204 */ + u8 vc_table_status; /* 0x2c0 */ + u8 vc_table[0x40]; /* 002c1 - 002ff */ + u8 sink_spec[0xc]; /* 00400 - 0040b */ + u8 down_req[0x30]; /* 01000 - 01030 */ + u8 down_rep[0x30]; /* 01400 - 01430 */ +}; + +#define DPRX_MAX_EDID_BLOCKS 4 + +struct sink { + u8 edid[128 * DPRX_MAX_EDID_BLOCKS]; + int blocks; + int offset; + int segment; +}; + +struct dprx_dp { + struct device *dev; + void __iomem *iobase; + void __iomem *iobase_irq; + + struct sink sinks[4]; + u8 vc_id[4]; + int sink_count; + int has_mst; + int total_pbn; + int sum_pbn; + + /* dpcd */ + struct dpcd_mem dpcd; + + /* msg transaction */ + struct msg_transaction mt_req[2]; + struct msg_transaction mt_rep[2]; + bool mt_seq_no; +}; + +int dprx_dp_init(struct dprx_dp *dp, struct device *dev, + const struct dprx_dp_cfg *cfg); + +#define AUX_ACK 0x0 +#define AUX_I2C_NACK 0x4 + +struct aux_msg { + u8 cmd; + u32 addr; + u8 len; + u8 data[16]; +}; + +/* dprx-aux.c */ +void dprx_aux_handle_request(struct dprx_dp *dp, struct aux_msg *req, + struct aux_msg *res); +int dprx_aux_read_request(struct dprx_dp *dp, struct aux_msg *req); +void dprx_aux_write_response(struct dprx_dp *dp, struct aux_msg *res); + +/* dprx-dpcd.c */ +void dprx_dpcd_init(struct dprx_dp *dp); +void dprx_dpcd_access(struct dprx_dp *dp, struct aux_msg *req, + struct aux_msg *res); + + +/* dprx-dprx.c */ +void dprx_dprx_set_hpd(struct dprx_dp *dp, int val); +int dprx_dprx_get_hpd(struct dprx_dp *dp); +void dprx_dprx_pulse_hpd(struct dprx_dp *dp); +void dprx_dprx_set_link_rate(struct dprx_dp *dp, int val); +void dprx_dprx_set_lane_count(struct dprx_dp *dp, int val); +void dprx_dprx_set_training_pattern(struct dprx_dp *dp, int val); +void dprx_dprx_set_scrambler(struct dprx_dp *dp, int val); +int dprx_dprx_get_cr_lock(struct dprx_dp *dp); +int dprx_dprx_get_sym_lock(struct dprx_dp *dp); +int dprx_dprx_get_interlane_align(struct dprx_dp *dp); +int dprx_dprx_get_sink_status(struct dprx_dp *dp); +int dprx_dprx_get_rx_busy(struct dprx_dp *dp); +void dprx_dprx_set_mst(struct dprx_dp *dp, int val); +void dprx_dprx_clear_vc_payload_table(struct dprx_dp *dp); +void dprx_dprx_set_vc_payload_table(struct dprx_dp *dp, u8 *table, u8 *id); +int dprx_dprx_get_act(struct dprx_dp *dp); +void dprx_dprx_clear_act(struct dprx_dp *dp); +int dprx_dprx_read_aux(struct dprx_dp *dp, u8 *data); +void dprx_dprx_write_aux(struct dprx_dp *dp, u8 *data, int length); +void dprx_dprx_init(struct dprx_dp *dp); + +/* dprx-edid.c */ +extern u8 default_edid[256]; +extern u8 default_edid_blocks; + +/* dprx-i2c.c */ +int dprx_i2c_read(struct sink *sink, u8 addr, u8 *buf, int len); +int dprx_i2c_write(struct sink *sink, u8 addr, u8 *buf, int len); + +/* dprx-mt.c */ +void dprx_mt_execute(struct dprx_dp *dp, struct msg_transaction *req, + struct msg_transaction *rep); + +/* dprx-sbmsg.c */ +void dprx_sbmsg_read(struct dprx_dp *dp, u8 *buf, int len); +void dprx_sbmsg_write(struct dprx_dp *dp, u8 *buf, int buf_len); +bool dprx_sbmsg_pending(struct dprx_dp *dp); -- 2.41.0.255.g8b1d071c50-goog