162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2012 Red Hat
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * based in parts on udlfb.c:
662306a36Sopenharmony_ci * Copyright (C) 2009 Roberto De Ioris <roberto@unbit.it>
762306a36Sopenharmony_ci * Copyright (C) 2009 Jaya Kumar <jayakumar.lkml@gmail.com>
862306a36Sopenharmony_ci * Copyright (C) 2009 Bernie Thompson <bernie@plugable.com>
962306a36Sopenharmony_ci */
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include <linux/bitfield.h>
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#include <drm/drm_atomic.h>
1462306a36Sopenharmony_ci#include <drm/drm_atomic_helper.h>
1562306a36Sopenharmony_ci#include <drm/drm_crtc_helper.h>
1662306a36Sopenharmony_ci#include <drm/drm_damage_helper.h>
1762306a36Sopenharmony_ci#include <drm/drm_drv.h>
1862306a36Sopenharmony_ci#include <drm/drm_edid.h>
1962306a36Sopenharmony_ci#include <drm/drm_fourcc.h>
2062306a36Sopenharmony_ci#include <drm/drm_gem_atomic_helper.h>
2162306a36Sopenharmony_ci#include <drm/drm_gem_framebuffer_helper.h>
2262306a36Sopenharmony_ci#include <drm/drm_gem_shmem_helper.h>
2362306a36Sopenharmony_ci#include <drm/drm_modeset_helper_vtables.h>
2462306a36Sopenharmony_ci#include <drm/drm_plane_helper.h>
2562306a36Sopenharmony_ci#include <drm/drm_probe_helper.h>
2662306a36Sopenharmony_ci#include <drm/drm_vblank.h>
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci#include "udl_drv.h"
2962306a36Sopenharmony_ci#include "udl_proto.h"
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci/*
3262306a36Sopenharmony_ci * All DisplayLink bulk operations start with 0xaf (UDL_MSG_BULK), followed by
3362306a36Sopenharmony_ci * a specific command code. All operations are written to a command buffer, which
3462306a36Sopenharmony_ci * the driver sends to the device.
3562306a36Sopenharmony_ci */
3662306a36Sopenharmony_cistatic char *udl_set_register(char *buf, u8 reg, u8 val)
3762306a36Sopenharmony_ci{
3862306a36Sopenharmony_ci	*buf++ = UDL_MSG_BULK;
3962306a36Sopenharmony_ci	*buf++ = UDL_CMD_WRITEREG;
4062306a36Sopenharmony_ci	*buf++ = reg;
4162306a36Sopenharmony_ci	*buf++ = val;
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci	return buf;
4462306a36Sopenharmony_ci}
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_cistatic char *udl_vidreg_lock(char *buf)
4762306a36Sopenharmony_ci{
4862306a36Sopenharmony_ci	return udl_set_register(buf, UDL_REG_VIDREG, UDL_VIDREG_LOCK);
4962306a36Sopenharmony_ci}
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_cistatic char *udl_vidreg_unlock(char *buf)
5262306a36Sopenharmony_ci{
5362306a36Sopenharmony_ci	return udl_set_register(buf, UDL_REG_VIDREG, UDL_VIDREG_UNLOCK);
5462306a36Sopenharmony_ci}
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_cistatic char *udl_set_blank_mode(char *buf, u8 mode)
5762306a36Sopenharmony_ci{
5862306a36Sopenharmony_ci	return udl_set_register(buf, UDL_REG_BLANKMODE, mode);
5962306a36Sopenharmony_ci}
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_cistatic char *udl_set_color_depth(char *buf, u8 selection)
6262306a36Sopenharmony_ci{
6362306a36Sopenharmony_ci	return udl_set_register(buf, UDL_REG_COLORDEPTH, selection);
6462306a36Sopenharmony_ci}
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_cistatic char *udl_set_base16bpp(char *buf, u32 base)
6762306a36Sopenharmony_ci{
6862306a36Sopenharmony_ci	/* the base pointer is 24 bits wide, 0x20 is hi byte. */
6962306a36Sopenharmony_ci	u8 reg20 = FIELD_GET(UDL_BASE_ADDR2_MASK, base);
7062306a36Sopenharmony_ci	u8 reg21 = FIELD_GET(UDL_BASE_ADDR1_MASK, base);
7162306a36Sopenharmony_ci	u8 reg22 = FIELD_GET(UDL_BASE_ADDR0_MASK, base);
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	buf = udl_set_register(buf, UDL_REG_BASE16BPP_ADDR2, reg20);
7462306a36Sopenharmony_ci	buf = udl_set_register(buf, UDL_REG_BASE16BPP_ADDR1, reg21);
7562306a36Sopenharmony_ci	buf = udl_set_register(buf, UDL_REG_BASE16BPP_ADDR0, reg22);
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	return buf;
7862306a36Sopenharmony_ci}
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci/*
8162306a36Sopenharmony_ci * DisplayLink HW has separate 16bpp and 8bpp framebuffers.
8262306a36Sopenharmony_ci * In 24bpp modes, the low 323 RGB bits go in the 8bpp framebuffer
8362306a36Sopenharmony_ci */
8462306a36Sopenharmony_cistatic char *udl_set_base8bpp(char *buf, u32 base)
8562306a36Sopenharmony_ci{
8662306a36Sopenharmony_ci	/* the base pointer is 24 bits wide, 0x26 is hi byte. */
8762306a36Sopenharmony_ci	u8 reg26 = FIELD_GET(UDL_BASE_ADDR2_MASK, base);
8862306a36Sopenharmony_ci	u8 reg27 = FIELD_GET(UDL_BASE_ADDR1_MASK, base);
8962306a36Sopenharmony_ci	u8 reg28 = FIELD_GET(UDL_BASE_ADDR0_MASK, base);
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	buf = udl_set_register(buf, UDL_REG_BASE8BPP_ADDR2, reg26);
9262306a36Sopenharmony_ci	buf = udl_set_register(buf, UDL_REG_BASE8BPP_ADDR1, reg27);
9362306a36Sopenharmony_ci	buf = udl_set_register(buf, UDL_REG_BASE8BPP_ADDR0, reg28);
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	return buf;
9662306a36Sopenharmony_ci}
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_cistatic char *udl_set_register_16(char *wrptr, u8 reg, u16 value)
9962306a36Sopenharmony_ci{
10062306a36Sopenharmony_ci	wrptr = udl_set_register(wrptr, reg, value >> 8);
10162306a36Sopenharmony_ci	return udl_set_register(wrptr, reg+1, value);
10262306a36Sopenharmony_ci}
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci/*
10562306a36Sopenharmony_ci * This is kind of weird because the controller takes some
10662306a36Sopenharmony_ci * register values in a different byte order than other registers.
10762306a36Sopenharmony_ci */
10862306a36Sopenharmony_cistatic char *udl_set_register_16be(char *wrptr, u8 reg, u16 value)
10962306a36Sopenharmony_ci{
11062306a36Sopenharmony_ci	wrptr = udl_set_register(wrptr, reg, value);
11162306a36Sopenharmony_ci	return udl_set_register(wrptr, reg+1, value >> 8);
11262306a36Sopenharmony_ci}
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci/*
11562306a36Sopenharmony_ci * LFSR is linear feedback shift register. The reason we have this is
11662306a36Sopenharmony_ci * because the display controller needs to minimize the clock depth of
11762306a36Sopenharmony_ci * various counters used in the display path. So this code reverses the
11862306a36Sopenharmony_ci * provided value into the lfsr16 value by counting backwards to get
11962306a36Sopenharmony_ci * the value that needs to be set in the hardware comparator to get the
12062306a36Sopenharmony_ci * same actual count. This makes sense once you read above a couple of
12162306a36Sopenharmony_ci * times and think about it from a hardware perspective.
12262306a36Sopenharmony_ci */
12362306a36Sopenharmony_cistatic u16 udl_lfsr16(u16 actual_count)
12462306a36Sopenharmony_ci{
12562306a36Sopenharmony_ci	u32 lv = 0xFFFF; /* This is the lfsr value that the hw starts with */
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	while (actual_count--) {
12862306a36Sopenharmony_ci		lv =	 ((lv << 1) |
12962306a36Sopenharmony_ci			(((lv >> 15) ^ (lv >> 4) ^ (lv >> 2) ^ (lv >> 1)) & 1))
13062306a36Sopenharmony_ci			& 0xFFFF;
13162306a36Sopenharmony_ci	}
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	return (u16) lv;
13462306a36Sopenharmony_ci}
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci/*
13762306a36Sopenharmony_ci * This does LFSR conversion on the value that is to be written.
13862306a36Sopenharmony_ci * See LFSR explanation above for more detail.
13962306a36Sopenharmony_ci */
14062306a36Sopenharmony_cistatic char *udl_set_register_lfsr16(char *wrptr, u8 reg, u16 value)
14162306a36Sopenharmony_ci{
14262306a36Sopenharmony_ci	return udl_set_register_16(wrptr, reg, udl_lfsr16(value));
14362306a36Sopenharmony_ci}
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci/*
14662306a36Sopenharmony_ci * Takes a DRM display mode and converts it into the DisplayLink
14762306a36Sopenharmony_ci * equivalent register commands.
14862306a36Sopenharmony_ci */
14962306a36Sopenharmony_cistatic char *udl_set_display_mode(char *buf, struct drm_display_mode *mode)
15062306a36Sopenharmony_ci{
15162306a36Sopenharmony_ci	u16 reg01 = mode->crtc_htotal - mode->crtc_hsync_start;
15262306a36Sopenharmony_ci	u16 reg03 = reg01 + mode->crtc_hdisplay;
15362306a36Sopenharmony_ci	u16 reg05 = mode->crtc_vtotal - mode->crtc_vsync_start;
15462306a36Sopenharmony_ci	u16 reg07 = reg05 + mode->crtc_vdisplay;
15562306a36Sopenharmony_ci	u16 reg09 = mode->crtc_htotal - 1;
15662306a36Sopenharmony_ci	u16 reg0b = 1; /* libdlo hardcodes hsync start to 1 */
15762306a36Sopenharmony_ci	u16 reg0d = mode->crtc_hsync_end - mode->crtc_hsync_start + 1;
15862306a36Sopenharmony_ci	u16 reg0f = mode->hdisplay;
15962306a36Sopenharmony_ci	u16 reg11 = mode->crtc_vtotal;
16062306a36Sopenharmony_ci	u16 reg13 = 0; /* libdlo hardcodes vsync start to 0 */
16162306a36Sopenharmony_ci	u16 reg15 = mode->crtc_vsync_end - mode->crtc_vsync_start;
16262306a36Sopenharmony_ci	u16 reg17 = mode->crtc_vdisplay;
16362306a36Sopenharmony_ci	u16 reg1b = mode->clock / 5;
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	buf = udl_set_register_lfsr16(buf, UDL_REG_XDISPLAYSTART, reg01);
16662306a36Sopenharmony_ci	buf = udl_set_register_lfsr16(buf, UDL_REG_XDISPLAYEND, reg03);
16762306a36Sopenharmony_ci	buf = udl_set_register_lfsr16(buf, UDL_REG_YDISPLAYSTART, reg05);
16862306a36Sopenharmony_ci	buf = udl_set_register_lfsr16(buf, UDL_REG_YDISPLAYEND, reg07);
16962306a36Sopenharmony_ci	buf = udl_set_register_lfsr16(buf, UDL_REG_XENDCOUNT, reg09);
17062306a36Sopenharmony_ci	buf = udl_set_register_lfsr16(buf, UDL_REG_HSYNCSTART, reg0b);
17162306a36Sopenharmony_ci	buf = udl_set_register_lfsr16(buf, UDL_REG_HSYNCEND, reg0d);
17262306a36Sopenharmony_ci	buf = udl_set_register_16(buf, UDL_REG_HPIXELS, reg0f);
17362306a36Sopenharmony_ci	buf = udl_set_register_lfsr16(buf, UDL_REG_YENDCOUNT, reg11);
17462306a36Sopenharmony_ci	buf = udl_set_register_lfsr16(buf, UDL_REG_VSYNCSTART, reg13);
17562306a36Sopenharmony_ci	buf = udl_set_register_lfsr16(buf, UDL_REG_VSYNCEND, reg15);
17662306a36Sopenharmony_ci	buf = udl_set_register_16(buf, UDL_REG_VPIXELS, reg17);
17762306a36Sopenharmony_ci	buf = udl_set_register_16be(buf, UDL_REG_PIXELCLOCK5KHZ, reg1b);
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci	return buf;
18062306a36Sopenharmony_ci}
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_cistatic char *udl_dummy_render(char *wrptr)
18362306a36Sopenharmony_ci{
18462306a36Sopenharmony_ci	*wrptr++ = UDL_MSG_BULK;
18562306a36Sopenharmony_ci	*wrptr++ = UDL_CMD_WRITECOPY16;
18662306a36Sopenharmony_ci	*wrptr++ = 0x00; /* from addr */
18762306a36Sopenharmony_ci	*wrptr++ = 0x00;
18862306a36Sopenharmony_ci	*wrptr++ = 0x00;
18962306a36Sopenharmony_ci	*wrptr++ = 0x01; /* one pixel */
19062306a36Sopenharmony_ci	*wrptr++ = 0x00; /* to address */
19162306a36Sopenharmony_ci	*wrptr++ = 0x00;
19262306a36Sopenharmony_ci	*wrptr++ = 0x00;
19362306a36Sopenharmony_ci	return wrptr;
19462306a36Sopenharmony_ci}
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_cistatic long udl_log_cpp(unsigned int cpp)
19762306a36Sopenharmony_ci{
19862306a36Sopenharmony_ci	if (WARN_ON(!is_power_of_2(cpp)))
19962306a36Sopenharmony_ci		return -EINVAL;
20062306a36Sopenharmony_ci	return __ffs(cpp);
20162306a36Sopenharmony_ci}
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_cistatic int udl_handle_damage(struct drm_framebuffer *fb,
20462306a36Sopenharmony_ci			     const struct iosys_map *map,
20562306a36Sopenharmony_ci			     const struct drm_rect *clip)
20662306a36Sopenharmony_ci{
20762306a36Sopenharmony_ci	struct drm_device *dev = fb->dev;
20862306a36Sopenharmony_ci	void *vaddr = map->vaddr; /* TODO: Use mapping abstraction properly */
20962306a36Sopenharmony_ci	int i, ret;
21062306a36Sopenharmony_ci	char *cmd;
21162306a36Sopenharmony_ci	struct urb *urb;
21262306a36Sopenharmony_ci	int log_bpp;
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci	ret = udl_log_cpp(fb->format->cpp[0]);
21562306a36Sopenharmony_ci	if (ret < 0)
21662306a36Sopenharmony_ci		return ret;
21762306a36Sopenharmony_ci	log_bpp = ret;
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci	urb = udl_get_urb(dev);
22062306a36Sopenharmony_ci	if (!urb)
22162306a36Sopenharmony_ci		return -ENOMEM;
22262306a36Sopenharmony_ci	cmd = urb->transfer_buffer;
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	for (i = clip->y1; i < clip->y2; i++) {
22562306a36Sopenharmony_ci		const int line_offset = fb->pitches[0] * i;
22662306a36Sopenharmony_ci		const int byte_offset = line_offset + (clip->x1 << log_bpp);
22762306a36Sopenharmony_ci		const int dev_byte_offset = (fb->width * i + clip->x1) << log_bpp;
22862306a36Sopenharmony_ci		const int byte_width = drm_rect_width(clip) << log_bpp;
22962306a36Sopenharmony_ci		ret = udl_render_hline(dev, log_bpp, &urb, (char *)vaddr,
23062306a36Sopenharmony_ci				       &cmd, byte_offset, dev_byte_offset,
23162306a36Sopenharmony_ci				       byte_width);
23262306a36Sopenharmony_ci		if (ret)
23362306a36Sopenharmony_ci			return ret;
23462306a36Sopenharmony_ci	}
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	if (cmd > (char *)urb->transfer_buffer) {
23762306a36Sopenharmony_ci		/* Send partial buffer remaining before exiting */
23862306a36Sopenharmony_ci		int len;
23962306a36Sopenharmony_ci		if (cmd < (char *)urb->transfer_buffer + urb->transfer_buffer_length)
24062306a36Sopenharmony_ci			*cmd++ = UDL_MSG_BULK;
24162306a36Sopenharmony_ci		len = cmd - (char *)urb->transfer_buffer;
24262306a36Sopenharmony_ci		ret = udl_submit_urb(dev, urb, len);
24362306a36Sopenharmony_ci	} else {
24462306a36Sopenharmony_ci		udl_urb_completion(urb);
24562306a36Sopenharmony_ci	}
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci	return 0;
24862306a36Sopenharmony_ci}
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci/*
25162306a36Sopenharmony_ci * Primary plane
25262306a36Sopenharmony_ci */
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_cistatic const uint32_t udl_primary_plane_formats[] = {
25562306a36Sopenharmony_ci	DRM_FORMAT_RGB565,
25662306a36Sopenharmony_ci	DRM_FORMAT_XRGB8888,
25762306a36Sopenharmony_ci};
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_cistatic const uint64_t udl_primary_plane_fmtmods[] = {
26062306a36Sopenharmony_ci	DRM_FORMAT_MOD_LINEAR,
26162306a36Sopenharmony_ci	DRM_FORMAT_MOD_INVALID
26262306a36Sopenharmony_ci};
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_cistatic void udl_primary_plane_helper_atomic_update(struct drm_plane *plane,
26562306a36Sopenharmony_ci						   struct drm_atomic_state *state)
26662306a36Sopenharmony_ci{
26762306a36Sopenharmony_ci	struct drm_device *dev = plane->dev;
26862306a36Sopenharmony_ci	struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, plane);
26962306a36Sopenharmony_ci	struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(plane_state);
27062306a36Sopenharmony_ci	struct drm_framebuffer *fb = plane_state->fb;
27162306a36Sopenharmony_ci	struct drm_plane_state *old_plane_state = drm_atomic_get_old_plane_state(state, plane);
27262306a36Sopenharmony_ci	struct drm_atomic_helper_damage_iter iter;
27362306a36Sopenharmony_ci	struct drm_rect damage;
27462306a36Sopenharmony_ci	int ret, idx;
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_ci	if (!fb)
27762306a36Sopenharmony_ci		return; /* no framebuffer; plane is disabled */
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ci	ret = drm_gem_fb_begin_cpu_access(fb, DMA_FROM_DEVICE);
28062306a36Sopenharmony_ci	if (ret)
28162306a36Sopenharmony_ci		return;
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci	if (!drm_dev_enter(dev, &idx))
28462306a36Sopenharmony_ci		goto out_drm_gem_fb_end_cpu_access;
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci	drm_atomic_helper_damage_iter_init(&iter, old_plane_state, plane_state);
28762306a36Sopenharmony_ci	drm_atomic_for_each_plane_damage(&iter, &damage) {
28862306a36Sopenharmony_ci		udl_handle_damage(fb, &shadow_plane_state->data[0], &damage);
28962306a36Sopenharmony_ci	}
29062306a36Sopenharmony_ci
29162306a36Sopenharmony_ci	drm_dev_exit(idx);
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ciout_drm_gem_fb_end_cpu_access:
29462306a36Sopenharmony_ci	drm_gem_fb_end_cpu_access(fb, DMA_FROM_DEVICE);
29562306a36Sopenharmony_ci}
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_cistatic const struct drm_plane_helper_funcs udl_primary_plane_helper_funcs = {
29862306a36Sopenharmony_ci	DRM_GEM_SHADOW_PLANE_HELPER_FUNCS,
29962306a36Sopenharmony_ci	.atomic_check = drm_plane_helper_atomic_check,
30062306a36Sopenharmony_ci	.atomic_update = udl_primary_plane_helper_atomic_update,
30162306a36Sopenharmony_ci};
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_cistatic const struct drm_plane_funcs udl_primary_plane_funcs = {
30462306a36Sopenharmony_ci	.update_plane = drm_atomic_helper_update_plane,
30562306a36Sopenharmony_ci	.disable_plane = drm_atomic_helper_disable_plane,
30662306a36Sopenharmony_ci	.destroy = drm_plane_cleanup,
30762306a36Sopenharmony_ci	DRM_GEM_SHADOW_PLANE_FUNCS,
30862306a36Sopenharmony_ci};
30962306a36Sopenharmony_ci
31062306a36Sopenharmony_ci/*
31162306a36Sopenharmony_ci * CRTC
31262306a36Sopenharmony_ci */
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_cistatic void udl_crtc_helper_atomic_enable(struct drm_crtc *crtc, struct drm_atomic_state *state)
31562306a36Sopenharmony_ci{
31662306a36Sopenharmony_ci	struct drm_device *dev = crtc->dev;
31762306a36Sopenharmony_ci	struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
31862306a36Sopenharmony_ci	struct drm_display_mode *mode = &crtc_state->mode;
31962306a36Sopenharmony_ci	struct urb *urb;
32062306a36Sopenharmony_ci	char *buf;
32162306a36Sopenharmony_ci	int idx;
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_ci	if (!drm_dev_enter(dev, &idx))
32462306a36Sopenharmony_ci		return;
32562306a36Sopenharmony_ci
32662306a36Sopenharmony_ci	urb = udl_get_urb(dev);
32762306a36Sopenharmony_ci	if (!urb)
32862306a36Sopenharmony_ci		goto out;
32962306a36Sopenharmony_ci
33062306a36Sopenharmony_ci	buf = (char *)urb->transfer_buffer;
33162306a36Sopenharmony_ci	buf = udl_vidreg_lock(buf);
33262306a36Sopenharmony_ci	buf = udl_set_color_depth(buf, UDL_COLORDEPTH_16BPP);
33362306a36Sopenharmony_ci	/* set base for 16bpp segment to 0 */
33462306a36Sopenharmony_ci	buf = udl_set_base16bpp(buf, 0);
33562306a36Sopenharmony_ci	/* set base for 8bpp segment to end of fb */
33662306a36Sopenharmony_ci	buf = udl_set_base8bpp(buf, 2 * mode->vdisplay * mode->hdisplay);
33762306a36Sopenharmony_ci	buf = udl_set_display_mode(buf, mode);
33862306a36Sopenharmony_ci	buf = udl_set_blank_mode(buf, UDL_BLANKMODE_ON);
33962306a36Sopenharmony_ci	buf = udl_vidreg_unlock(buf);
34062306a36Sopenharmony_ci	buf = udl_dummy_render(buf);
34162306a36Sopenharmony_ci
34262306a36Sopenharmony_ci	udl_submit_urb(dev, urb, buf - (char *)urb->transfer_buffer);
34362306a36Sopenharmony_ci
34462306a36Sopenharmony_ciout:
34562306a36Sopenharmony_ci	drm_dev_exit(idx);
34662306a36Sopenharmony_ci}
34762306a36Sopenharmony_ci
34862306a36Sopenharmony_cistatic void udl_crtc_helper_atomic_disable(struct drm_crtc *crtc, struct drm_atomic_state *state)
34962306a36Sopenharmony_ci{
35062306a36Sopenharmony_ci	struct drm_device *dev = crtc->dev;
35162306a36Sopenharmony_ci	struct urb *urb;
35262306a36Sopenharmony_ci	char *buf;
35362306a36Sopenharmony_ci	int idx;
35462306a36Sopenharmony_ci
35562306a36Sopenharmony_ci	if (!drm_dev_enter(dev, &idx))
35662306a36Sopenharmony_ci		return;
35762306a36Sopenharmony_ci
35862306a36Sopenharmony_ci	urb = udl_get_urb(dev);
35962306a36Sopenharmony_ci	if (!urb)
36062306a36Sopenharmony_ci		goto out;
36162306a36Sopenharmony_ci
36262306a36Sopenharmony_ci	buf = (char *)urb->transfer_buffer;
36362306a36Sopenharmony_ci	buf = udl_vidreg_lock(buf);
36462306a36Sopenharmony_ci	buf = udl_set_blank_mode(buf, UDL_BLANKMODE_POWERDOWN);
36562306a36Sopenharmony_ci	buf = udl_vidreg_unlock(buf);
36662306a36Sopenharmony_ci	buf = udl_dummy_render(buf);
36762306a36Sopenharmony_ci
36862306a36Sopenharmony_ci	udl_submit_urb(dev, urb, buf - (char *)urb->transfer_buffer);
36962306a36Sopenharmony_ci
37062306a36Sopenharmony_ciout:
37162306a36Sopenharmony_ci	drm_dev_exit(idx);
37262306a36Sopenharmony_ci}
37362306a36Sopenharmony_ci
37462306a36Sopenharmony_cistatic const struct drm_crtc_helper_funcs udl_crtc_helper_funcs = {
37562306a36Sopenharmony_ci	.atomic_check = drm_crtc_helper_atomic_check,
37662306a36Sopenharmony_ci	.atomic_enable = udl_crtc_helper_atomic_enable,
37762306a36Sopenharmony_ci	.atomic_disable = udl_crtc_helper_atomic_disable,
37862306a36Sopenharmony_ci};
37962306a36Sopenharmony_ci
38062306a36Sopenharmony_cistatic const struct drm_crtc_funcs udl_crtc_funcs = {
38162306a36Sopenharmony_ci	.reset = drm_atomic_helper_crtc_reset,
38262306a36Sopenharmony_ci	.destroy = drm_crtc_cleanup,
38362306a36Sopenharmony_ci	.set_config = drm_atomic_helper_set_config,
38462306a36Sopenharmony_ci	.page_flip = drm_atomic_helper_page_flip,
38562306a36Sopenharmony_ci	.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
38662306a36Sopenharmony_ci	.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
38762306a36Sopenharmony_ci};
38862306a36Sopenharmony_ci
38962306a36Sopenharmony_ci/*
39062306a36Sopenharmony_ci * Encoder
39162306a36Sopenharmony_ci */
39262306a36Sopenharmony_ci
39362306a36Sopenharmony_cistatic const struct drm_encoder_funcs udl_encoder_funcs = {
39462306a36Sopenharmony_ci	.destroy = drm_encoder_cleanup,
39562306a36Sopenharmony_ci};
39662306a36Sopenharmony_ci
39762306a36Sopenharmony_ci/*
39862306a36Sopenharmony_ci * Connector
39962306a36Sopenharmony_ci */
40062306a36Sopenharmony_ci
40162306a36Sopenharmony_cistatic int udl_connector_helper_get_modes(struct drm_connector *connector)
40262306a36Sopenharmony_ci{
40362306a36Sopenharmony_ci	struct udl_connector *udl_connector = to_udl_connector(connector);
40462306a36Sopenharmony_ci
40562306a36Sopenharmony_ci	drm_connector_update_edid_property(connector, udl_connector->edid);
40662306a36Sopenharmony_ci	if (udl_connector->edid)
40762306a36Sopenharmony_ci		return drm_add_edid_modes(connector, udl_connector->edid);
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_ci	return 0;
41062306a36Sopenharmony_ci}
41162306a36Sopenharmony_ci
41262306a36Sopenharmony_cistatic const struct drm_connector_helper_funcs udl_connector_helper_funcs = {
41362306a36Sopenharmony_ci	.get_modes = udl_connector_helper_get_modes,
41462306a36Sopenharmony_ci};
41562306a36Sopenharmony_ci
41662306a36Sopenharmony_cistatic int udl_get_edid_block(void *data, u8 *buf, unsigned int block, size_t len)
41762306a36Sopenharmony_ci{
41862306a36Sopenharmony_ci	struct udl_device *udl = data;
41962306a36Sopenharmony_ci	struct drm_device *dev = &udl->drm;
42062306a36Sopenharmony_ci	struct usb_device *udev = udl_to_usb_device(udl);
42162306a36Sopenharmony_ci	u8 *read_buff;
42262306a36Sopenharmony_ci	int ret;
42362306a36Sopenharmony_ci	size_t i;
42462306a36Sopenharmony_ci
42562306a36Sopenharmony_ci	read_buff = kmalloc(2, GFP_KERNEL);
42662306a36Sopenharmony_ci	if (!read_buff)
42762306a36Sopenharmony_ci		return -ENOMEM;
42862306a36Sopenharmony_ci
42962306a36Sopenharmony_ci	for (i = 0; i < len; i++) {
43062306a36Sopenharmony_ci		int bval = (i + block * EDID_LENGTH) << 8;
43162306a36Sopenharmony_ci
43262306a36Sopenharmony_ci		ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
43362306a36Sopenharmony_ci				      0x02, (0x80 | (0x02 << 5)), bval,
43462306a36Sopenharmony_ci				      0xA1, read_buff, 2, USB_CTRL_GET_TIMEOUT);
43562306a36Sopenharmony_ci		if (ret < 0) {
43662306a36Sopenharmony_ci			drm_err(dev, "Read EDID byte %zu failed err %x\n", i, ret);
43762306a36Sopenharmony_ci			goto err_kfree;
43862306a36Sopenharmony_ci		} else if (ret < 1) {
43962306a36Sopenharmony_ci			ret = -EIO;
44062306a36Sopenharmony_ci			drm_err(dev, "Read EDID byte %zu failed\n", i);
44162306a36Sopenharmony_ci			goto err_kfree;
44262306a36Sopenharmony_ci		}
44362306a36Sopenharmony_ci
44462306a36Sopenharmony_ci		buf[i] = read_buff[1];
44562306a36Sopenharmony_ci	}
44662306a36Sopenharmony_ci
44762306a36Sopenharmony_ci	kfree(read_buff);
44862306a36Sopenharmony_ci
44962306a36Sopenharmony_ci	return 0;
45062306a36Sopenharmony_ci
45162306a36Sopenharmony_cierr_kfree:
45262306a36Sopenharmony_ci	kfree(read_buff);
45362306a36Sopenharmony_ci	return ret;
45462306a36Sopenharmony_ci}
45562306a36Sopenharmony_ci
45662306a36Sopenharmony_cistatic enum drm_connector_status udl_connector_detect(struct drm_connector *connector, bool force)
45762306a36Sopenharmony_ci{
45862306a36Sopenharmony_ci	struct drm_device *dev = connector->dev;
45962306a36Sopenharmony_ci	struct udl_device *udl = to_udl(dev);
46062306a36Sopenharmony_ci	struct udl_connector *udl_connector = to_udl_connector(connector);
46162306a36Sopenharmony_ci	enum drm_connector_status status = connector_status_disconnected;
46262306a36Sopenharmony_ci	int idx;
46362306a36Sopenharmony_ci
46462306a36Sopenharmony_ci	/* cleanup previous EDID */
46562306a36Sopenharmony_ci	kfree(udl_connector->edid);
46662306a36Sopenharmony_ci	udl_connector->edid = NULL;
46762306a36Sopenharmony_ci
46862306a36Sopenharmony_ci	if (!drm_dev_enter(dev, &idx))
46962306a36Sopenharmony_ci		return connector_status_disconnected;
47062306a36Sopenharmony_ci
47162306a36Sopenharmony_ci	udl_connector->edid = drm_do_get_edid(connector, udl_get_edid_block, udl);
47262306a36Sopenharmony_ci	if (udl_connector->edid)
47362306a36Sopenharmony_ci		status = connector_status_connected;
47462306a36Sopenharmony_ci
47562306a36Sopenharmony_ci	drm_dev_exit(idx);
47662306a36Sopenharmony_ci
47762306a36Sopenharmony_ci	return status;
47862306a36Sopenharmony_ci}
47962306a36Sopenharmony_ci
48062306a36Sopenharmony_cistatic void udl_connector_destroy(struct drm_connector *connector)
48162306a36Sopenharmony_ci{
48262306a36Sopenharmony_ci	struct udl_connector *udl_connector = to_udl_connector(connector);
48362306a36Sopenharmony_ci
48462306a36Sopenharmony_ci	drm_connector_cleanup(connector);
48562306a36Sopenharmony_ci	kfree(udl_connector->edid);
48662306a36Sopenharmony_ci	kfree(udl_connector);
48762306a36Sopenharmony_ci}
48862306a36Sopenharmony_ci
48962306a36Sopenharmony_cistatic const struct drm_connector_funcs udl_connector_funcs = {
49062306a36Sopenharmony_ci	.reset = drm_atomic_helper_connector_reset,
49162306a36Sopenharmony_ci	.detect = udl_connector_detect,
49262306a36Sopenharmony_ci	.fill_modes = drm_helper_probe_single_connector_modes,
49362306a36Sopenharmony_ci	.destroy = udl_connector_destroy,
49462306a36Sopenharmony_ci	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
49562306a36Sopenharmony_ci	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
49662306a36Sopenharmony_ci};
49762306a36Sopenharmony_ci
49862306a36Sopenharmony_cistruct drm_connector *udl_connector_init(struct drm_device *dev)
49962306a36Sopenharmony_ci{
50062306a36Sopenharmony_ci	struct udl_connector *udl_connector;
50162306a36Sopenharmony_ci	struct drm_connector *connector;
50262306a36Sopenharmony_ci	int ret;
50362306a36Sopenharmony_ci
50462306a36Sopenharmony_ci	udl_connector = kzalloc(sizeof(*udl_connector), GFP_KERNEL);
50562306a36Sopenharmony_ci	if (!udl_connector)
50662306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
50762306a36Sopenharmony_ci
50862306a36Sopenharmony_ci	connector = &udl_connector->connector;
50962306a36Sopenharmony_ci	ret = drm_connector_init(dev, connector, &udl_connector_funcs, DRM_MODE_CONNECTOR_VGA);
51062306a36Sopenharmony_ci	if (ret)
51162306a36Sopenharmony_ci		goto err_kfree;
51262306a36Sopenharmony_ci
51362306a36Sopenharmony_ci	drm_connector_helper_add(connector, &udl_connector_helper_funcs);
51462306a36Sopenharmony_ci
51562306a36Sopenharmony_ci	connector->polled = DRM_CONNECTOR_POLL_HPD |
51662306a36Sopenharmony_ci			    DRM_CONNECTOR_POLL_CONNECT |
51762306a36Sopenharmony_ci			    DRM_CONNECTOR_POLL_DISCONNECT;
51862306a36Sopenharmony_ci
51962306a36Sopenharmony_ci	return connector;
52062306a36Sopenharmony_ci
52162306a36Sopenharmony_cierr_kfree:
52262306a36Sopenharmony_ci	kfree(udl_connector);
52362306a36Sopenharmony_ci	return ERR_PTR(ret);
52462306a36Sopenharmony_ci}
52562306a36Sopenharmony_ci
52662306a36Sopenharmony_ci/*
52762306a36Sopenharmony_ci * Modesetting
52862306a36Sopenharmony_ci */
52962306a36Sopenharmony_ci
53062306a36Sopenharmony_cistatic enum drm_mode_status udl_mode_config_mode_valid(struct drm_device *dev,
53162306a36Sopenharmony_ci						       const struct drm_display_mode *mode)
53262306a36Sopenharmony_ci{
53362306a36Sopenharmony_ci	struct udl_device *udl = to_udl(dev);
53462306a36Sopenharmony_ci
53562306a36Sopenharmony_ci	if (udl->sku_pixel_limit) {
53662306a36Sopenharmony_ci		if (mode->vdisplay * mode->hdisplay > udl->sku_pixel_limit)
53762306a36Sopenharmony_ci			return MODE_MEM;
53862306a36Sopenharmony_ci	}
53962306a36Sopenharmony_ci
54062306a36Sopenharmony_ci	return MODE_OK;
54162306a36Sopenharmony_ci}
54262306a36Sopenharmony_ci
54362306a36Sopenharmony_cistatic const struct drm_mode_config_funcs udl_mode_config_funcs = {
54462306a36Sopenharmony_ci	.fb_create = drm_gem_fb_create_with_dirty,
54562306a36Sopenharmony_ci	.mode_valid = udl_mode_config_mode_valid,
54662306a36Sopenharmony_ci	.atomic_check  = drm_atomic_helper_check,
54762306a36Sopenharmony_ci	.atomic_commit = drm_atomic_helper_commit,
54862306a36Sopenharmony_ci};
54962306a36Sopenharmony_ci
55062306a36Sopenharmony_ciint udl_modeset_init(struct drm_device *dev)
55162306a36Sopenharmony_ci{
55262306a36Sopenharmony_ci	struct udl_device *udl = to_udl(dev);
55362306a36Sopenharmony_ci	struct drm_plane *primary_plane;
55462306a36Sopenharmony_ci	struct drm_crtc *crtc;
55562306a36Sopenharmony_ci	struct drm_encoder *encoder;
55662306a36Sopenharmony_ci	struct drm_connector *connector;
55762306a36Sopenharmony_ci	int ret;
55862306a36Sopenharmony_ci
55962306a36Sopenharmony_ci	ret = drmm_mode_config_init(dev);
56062306a36Sopenharmony_ci	if (ret)
56162306a36Sopenharmony_ci		return ret;
56262306a36Sopenharmony_ci
56362306a36Sopenharmony_ci	dev->mode_config.min_width = 640;
56462306a36Sopenharmony_ci	dev->mode_config.min_height = 480;
56562306a36Sopenharmony_ci	dev->mode_config.max_width = 2048;
56662306a36Sopenharmony_ci	dev->mode_config.max_height = 2048;
56762306a36Sopenharmony_ci	dev->mode_config.preferred_depth = 16;
56862306a36Sopenharmony_ci	dev->mode_config.funcs = &udl_mode_config_funcs;
56962306a36Sopenharmony_ci
57062306a36Sopenharmony_ci	primary_plane = &udl->primary_plane;
57162306a36Sopenharmony_ci	ret = drm_universal_plane_init(dev, primary_plane, 0,
57262306a36Sopenharmony_ci				       &udl_primary_plane_funcs,
57362306a36Sopenharmony_ci				       udl_primary_plane_formats,
57462306a36Sopenharmony_ci				       ARRAY_SIZE(udl_primary_plane_formats),
57562306a36Sopenharmony_ci				       udl_primary_plane_fmtmods,
57662306a36Sopenharmony_ci				       DRM_PLANE_TYPE_PRIMARY, NULL);
57762306a36Sopenharmony_ci	if (ret)
57862306a36Sopenharmony_ci		return ret;
57962306a36Sopenharmony_ci	drm_plane_helper_add(primary_plane, &udl_primary_plane_helper_funcs);
58062306a36Sopenharmony_ci	drm_plane_enable_fb_damage_clips(primary_plane);
58162306a36Sopenharmony_ci
58262306a36Sopenharmony_ci	crtc = &udl->crtc;
58362306a36Sopenharmony_ci	ret = drm_crtc_init_with_planes(dev, crtc, primary_plane, NULL,
58462306a36Sopenharmony_ci					&udl_crtc_funcs, NULL);
58562306a36Sopenharmony_ci	if (ret)
58662306a36Sopenharmony_ci		return ret;
58762306a36Sopenharmony_ci	drm_crtc_helper_add(crtc, &udl_crtc_helper_funcs);
58862306a36Sopenharmony_ci
58962306a36Sopenharmony_ci	encoder = &udl->encoder;
59062306a36Sopenharmony_ci	ret = drm_encoder_init(dev, encoder, &udl_encoder_funcs, DRM_MODE_ENCODER_DAC, NULL);
59162306a36Sopenharmony_ci	if (ret)
59262306a36Sopenharmony_ci		return ret;
59362306a36Sopenharmony_ci	encoder->possible_crtcs = drm_crtc_mask(crtc);
59462306a36Sopenharmony_ci
59562306a36Sopenharmony_ci	connector = udl_connector_init(dev);
59662306a36Sopenharmony_ci	if (IS_ERR(connector))
59762306a36Sopenharmony_ci		return PTR_ERR(connector);
59862306a36Sopenharmony_ci	ret = drm_connector_attach_encoder(connector, encoder);
59962306a36Sopenharmony_ci	if (ret)
60062306a36Sopenharmony_ci		return ret;
60162306a36Sopenharmony_ci
60262306a36Sopenharmony_ci	drm_mode_config_reset(dev);
60362306a36Sopenharmony_ci
60462306a36Sopenharmony_ci	return 0;
60562306a36Sopenharmony_ci}
606