| // SPDX-License-Identifier: GPL-2.0-or-later | 
 | /* | 
 |  * MIPI Display Bus Interface (DBI) LCD controller support | 
 |  * | 
 |  * Copyright 2016 Noralf Trønnes | 
 |  */ | 
 |  | 
 | #include <linux/debugfs.h> | 
 | #include <linux/delay.h> | 
 | #include <linux/dma-buf.h> | 
 | #include <linux/gpio/consumer.h> | 
 | #include <linux/module.h> | 
 | #include <linux/regulator/consumer.h> | 
 | #include <linux/spi/spi.h> | 
 |  | 
 | #include <drm/drm_connector.h> | 
 | #include <drm/drm_damage_helper.h> | 
 | #include <drm/drm_drv.h> | 
 | #include <drm/drm_gem_cma_helper.h> | 
 | #include <drm/drm_format_helper.h> | 
 | #include <drm/drm_fourcc.h> | 
 | #include <drm/drm_gem_framebuffer_helper.h> | 
 | #include <drm/drm_mipi_dbi.h> | 
 | #include <drm/drm_modes.h> | 
 | #include <drm/drm_probe_helper.h> | 
 | #include <drm/drm_rect.h> | 
 | #include <drm/drm_vblank.h> | 
 | #include <video/mipi_display.h> | 
 |  | 
 | #define MIPI_DBI_MAX_SPI_READ_SPEED 2000000 /* 2MHz */ | 
 |  | 
 | #define DCS_POWER_MODE_DISPLAY			BIT(2) | 
 | #define DCS_POWER_MODE_DISPLAY_NORMAL_MODE	BIT(3) | 
 | #define DCS_POWER_MODE_SLEEP_MODE		BIT(4) | 
 | #define DCS_POWER_MODE_PARTIAL_MODE		BIT(5) | 
 | #define DCS_POWER_MODE_IDLE_MODE		BIT(6) | 
 | #define DCS_POWER_MODE_RESERVED_MASK		(BIT(0) | BIT(1) | BIT(7)) | 
 |  | 
 | /** | 
 |  * DOC: overview | 
 |  * | 
 |  * This library provides helpers for MIPI Display Bus Interface (DBI) | 
 |  * compatible display controllers. | 
 |  * | 
 |  * Many controllers for tiny lcd displays are MIPI compliant and can use this | 
 |  * library. If a controller uses registers 0x2A and 0x2B to set the area to | 
 |  * update and uses register 0x2C to write to frame memory, it is most likely | 
 |  * MIPI compliant. | 
 |  * | 
 |  * Only MIPI Type 1 displays are supported since a full frame memory is needed. | 
 |  * | 
 |  * There are 3 MIPI DBI implementation types: | 
 |  * | 
 |  * A. Motorola 6800 type parallel bus | 
 |  * | 
 |  * B. Intel 8080 type parallel bus | 
 |  * | 
 |  * C. SPI type with 3 options: | 
 |  * | 
 |  *    1. 9-bit with the Data/Command signal as the ninth bit | 
 |  *    2. Same as above except it's sent as 16 bits | 
 |  *    3. 8-bit with the Data/Command signal as a separate D/CX pin | 
 |  * | 
 |  * Currently mipi_dbi only supports Type C options 1 and 3 with | 
 |  * mipi_dbi_spi_init(). | 
 |  */ | 
 |  | 
 | #define MIPI_DBI_DEBUG_COMMAND(cmd, data, len) \ | 
 | ({ \ | 
 | 	if (!len) \ | 
 | 		DRM_DEBUG_DRIVER("cmd=%02x\n", cmd); \ | 
 | 	else if (len <= 32) \ | 
 | 		DRM_DEBUG_DRIVER("cmd=%02x, par=%*ph\n", cmd, (int)len, data);\ | 
 | 	else \ | 
 | 		DRM_DEBUG_DRIVER("cmd=%02x, len=%zu\n", cmd, len); \ | 
 | }) | 
 |  | 
 | static const u8 mipi_dbi_dcs_read_commands[] = { | 
 | 	MIPI_DCS_GET_DISPLAY_ID, | 
 | 	MIPI_DCS_GET_RED_CHANNEL, | 
 | 	MIPI_DCS_GET_GREEN_CHANNEL, | 
 | 	MIPI_DCS_GET_BLUE_CHANNEL, | 
 | 	MIPI_DCS_GET_DISPLAY_STATUS, | 
 | 	MIPI_DCS_GET_POWER_MODE, | 
 | 	MIPI_DCS_GET_ADDRESS_MODE, | 
 | 	MIPI_DCS_GET_PIXEL_FORMAT, | 
 | 	MIPI_DCS_GET_DISPLAY_MODE, | 
 | 	MIPI_DCS_GET_SIGNAL_MODE, | 
 | 	MIPI_DCS_GET_DIAGNOSTIC_RESULT, | 
 | 	MIPI_DCS_READ_MEMORY_START, | 
 | 	MIPI_DCS_READ_MEMORY_CONTINUE, | 
 | 	MIPI_DCS_GET_SCANLINE, | 
 | 	MIPI_DCS_GET_DISPLAY_BRIGHTNESS, | 
 | 	MIPI_DCS_GET_CONTROL_DISPLAY, | 
 | 	MIPI_DCS_GET_POWER_SAVE, | 
 | 	MIPI_DCS_GET_CABC_MIN_BRIGHTNESS, | 
 | 	MIPI_DCS_READ_DDB_START, | 
 | 	MIPI_DCS_READ_DDB_CONTINUE, | 
 | 	0, /* sentinel */ | 
 | }; | 
 |  | 
 | static bool mipi_dbi_command_is_read(struct mipi_dbi *dbi, u8 cmd) | 
 | { | 
 | 	unsigned int i; | 
 |  | 
 | 	if (!dbi->read_commands) | 
 | 		return false; | 
 |  | 
 | 	for (i = 0; i < 0xff; i++) { | 
 | 		if (!dbi->read_commands[i]) | 
 | 			return false; | 
 | 		if (cmd == dbi->read_commands[i]) | 
 | 			return true; | 
 | 	} | 
 |  | 
 | 	return false; | 
 | } | 
 |  | 
 | /** | 
 |  * mipi_dbi_command_read - MIPI DCS read command | 
 |  * @dbi: MIPI DBI structure | 
 |  * @cmd: Command | 
 |  * @val: Value read | 
 |  * | 
 |  * Send MIPI DCS read command to the controller. | 
 |  * | 
 |  * Returns: | 
 |  * Zero on success, negative error code on failure. | 
 |  */ | 
 | int mipi_dbi_command_read(struct mipi_dbi *dbi, u8 cmd, u8 *val) | 
 | { | 
 | 	if (!dbi->read_commands) | 
 | 		return -EACCES; | 
 |  | 
 | 	if (!mipi_dbi_command_is_read(dbi, cmd)) | 
 | 		return -EINVAL; | 
 |  | 
 | 	return mipi_dbi_command_buf(dbi, cmd, val, 1); | 
 | } | 
 | EXPORT_SYMBOL(mipi_dbi_command_read); | 
 |  | 
 | /** | 
 |  * mipi_dbi_command_buf - MIPI DCS command with parameter(s) in an array | 
 |  * @dbi: MIPI DBI structure | 
 |  * @cmd: Command | 
 |  * @data: Parameter buffer | 
 |  * @len: Buffer length | 
 |  * | 
 |  * Returns: | 
 |  * Zero on success, negative error code on failure. | 
 |  */ | 
 | int mipi_dbi_command_buf(struct mipi_dbi *dbi, u8 cmd, u8 *data, size_t len) | 
 | { | 
 | 	u8 *cmdbuf; | 
 | 	int ret; | 
 |  | 
 | 	/* SPI requires dma-safe buffers */ | 
 | 	cmdbuf = kmemdup(&cmd, 1, GFP_KERNEL); | 
 | 	if (!cmdbuf) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	mutex_lock(&dbi->cmdlock); | 
 | 	ret = dbi->command(dbi, cmdbuf, data, len); | 
 | 	mutex_unlock(&dbi->cmdlock); | 
 |  | 
 | 	kfree(cmdbuf); | 
 |  | 
 | 	return ret; | 
 | } | 
 | EXPORT_SYMBOL(mipi_dbi_command_buf); | 
 |  | 
 | /* This should only be used by mipi_dbi_command() */ | 
 | int mipi_dbi_command_stackbuf(struct mipi_dbi *dbi, u8 cmd, u8 *data, size_t len) | 
 | { | 
 | 	u8 *buf; | 
 | 	int ret; | 
 |  | 
 | 	buf = kmemdup(data, len, GFP_KERNEL); | 
 | 	if (!buf) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	ret = mipi_dbi_command_buf(dbi, cmd, buf, len); | 
 |  | 
 | 	kfree(buf); | 
 |  | 
 | 	return ret; | 
 | } | 
 | EXPORT_SYMBOL(mipi_dbi_command_stackbuf); | 
 |  | 
 | /** | 
 |  * mipi_dbi_buf_copy - Copy a framebuffer, transforming it if necessary | 
 |  * @dst: The destination buffer | 
 |  * @fb: The source framebuffer | 
 |  * @clip: Clipping rectangle of the area to be copied | 
 |  * @swap: When true, swap MSB/LSB of 16-bit values | 
 |  * | 
 |  * Returns: | 
 |  * Zero on success, negative error code on failure. | 
 |  */ | 
 | int mipi_dbi_buf_copy(void *dst, struct drm_framebuffer *fb, | 
 | 		      struct drm_rect *clip, bool swap) | 
 | { | 
 | 	struct drm_gem_object *gem = drm_gem_fb_get_obj(fb, 0); | 
 | 	struct drm_gem_cma_object *cma_obj = to_drm_gem_cma_obj(gem); | 
 | 	struct dma_buf_attachment *import_attach = gem->import_attach; | 
 | 	struct drm_format_name_buf format_name; | 
 | 	void *src = cma_obj->vaddr; | 
 | 	int ret = 0; | 
 |  | 
 | 	if (import_attach) { | 
 | 		ret = dma_buf_begin_cpu_access(import_attach->dmabuf, | 
 | 					       DMA_FROM_DEVICE); | 
 | 		if (ret) | 
 | 			return ret; | 
 | 	} | 
 |  | 
 | 	switch (fb->format->format) { | 
 | 	case DRM_FORMAT_RGB565: | 
 | 		if (swap) | 
 | 			drm_fb_swab16(dst, src, fb, clip); | 
 | 		else | 
 | 			drm_fb_memcpy(dst, src, fb, clip); | 
 | 		break; | 
 | 	case DRM_FORMAT_XRGB8888: | 
 | 		drm_fb_xrgb8888_to_rgb565(dst, src, fb, clip, swap); | 
 | 		break; | 
 | 	default: | 
 | 		dev_err_once(fb->dev->dev, "Format is not supported: %s\n", | 
 | 			     drm_get_format_name(fb->format->format, | 
 | 						 &format_name)); | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	if (import_attach) | 
 | 		ret = dma_buf_end_cpu_access(import_attach->dmabuf, | 
 | 					     DMA_FROM_DEVICE); | 
 | 	return ret; | 
 | } | 
 | EXPORT_SYMBOL(mipi_dbi_buf_copy); | 
 |  | 
 | static void mipi_dbi_fb_dirty(struct drm_framebuffer *fb, struct drm_rect *rect) | 
 | { | 
 | 	struct drm_gem_object *gem = drm_gem_fb_get_obj(fb, 0); | 
 | 	struct drm_gem_cma_object *cma_obj = to_drm_gem_cma_obj(gem); | 
 | 	struct mipi_dbi_dev *dbidev = drm_to_mipi_dbi_dev(fb->dev); | 
 | 	unsigned int height = rect->y2 - rect->y1; | 
 | 	unsigned int width = rect->x2 - rect->x1; | 
 | 	struct mipi_dbi *dbi = &dbidev->dbi; | 
 | 	bool swap = dbi->swap_bytes; | 
 | 	int idx, ret = 0; | 
 | 	bool full; | 
 | 	void *tr; | 
 |  | 
 | 	if (!dbidev->enabled) | 
 | 		return; | 
 |  | 
 | 	if (!drm_dev_enter(fb->dev, &idx)) | 
 | 		return; | 
 |  | 
 | 	full = width == fb->width && height == fb->height; | 
 |  | 
 | 	DRM_DEBUG_KMS("Flushing [FB:%d] " DRM_RECT_FMT "\n", fb->base.id, DRM_RECT_ARG(rect)); | 
 |  | 
 | 	if (!dbi->dc || !full || swap || | 
 | 	    fb->format->format == DRM_FORMAT_XRGB8888) { | 
 | 		tr = dbidev->tx_buf; | 
 | 		ret = mipi_dbi_buf_copy(dbidev->tx_buf, fb, rect, swap); | 
 | 		if (ret) | 
 | 			goto err_msg; | 
 | 	} else { | 
 | 		tr = cma_obj->vaddr; | 
 | 	} | 
 |  | 
 | 	mipi_dbi_command(dbi, MIPI_DCS_SET_COLUMN_ADDRESS, | 
 | 			 (rect->x1 >> 8) & 0xff, rect->x1 & 0xff, | 
 | 			 ((rect->x2 - 1) >> 8) & 0xff, (rect->x2 - 1) & 0xff); | 
 | 	mipi_dbi_command(dbi, MIPI_DCS_SET_PAGE_ADDRESS, | 
 | 			 (rect->y1 >> 8) & 0xff, rect->y1 & 0xff, | 
 | 			 ((rect->y2 - 1) >> 8) & 0xff, (rect->y2 - 1) & 0xff); | 
 |  | 
 | 	ret = mipi_dbi_command_buf(dbi, MIPI_DCS_WRITE_MEMORY_START, tr, | 
 | 				   width * height * 2); | 
 | err_msg: | 
 | 	if (ret) | 
 | 		dev_err_once(fb->dev->dev, "Failed to update display %d\n", ret); | 
 |  | 
 | 	drm_dev_exit(idx); | 
 | } | 
 |  | 
 | /** | 
 |  * mipi_dbi_pipe_update - Display pipe update helper | 
 |  * @pipe: Simple display pipe | 
 |  * @old_state: Old plane state | 
 |  * | 
 |  * This function handles framebuffer flushing and vblank events. Drivers can use | 
 |  * this as their &drm_simple_display_pipe_funcs->update callback. | 
 |  */ | 
 | void mipi_dbi_pipe_update(struct drm_simple_display_pipe *pipe, | 
 | 			  struct drm_plane_state *old_state) | 
 | { | 
 | 	struct drm_plane_state *state = pipe->plane.state; | 
 | 	struct drm_crtc *crtc = &pipe->crtc; | 
 | 	struct drm_rect rect; | 
 |  | 
 | 	if (drm_atomic_helper_damage_merged(old_state, state, &rect)) | 
 | 		mipi_dbi_fb_dirty(state->fb, &rect); | 
 |  | 
 | 	if (crtc->state->event) { | 
 | 		spin_lock_irq(&crtc->dev->event_lock); | 
 | 		drm_crtc_send_vblank_event(crtc, crtc->state->event); | 
 | 		spin_unlock_irq(&crtc->dev->event_lock); | 
 | 		crtc->state->event = NULL; | 
 | 	} | 
 | } | 
 | EXPORT_SYMBOL(mipi_dbi_pipe_update); | 
 |  | 
 | /** | 
 |  * mipi_dbi_enable_flush - MIPI DBI enable helper | 
 |  * @dbidev: MIPI DBI device structure | 
 |  * @crtc_state: CRTC state | 
 |  * @plane_state: Plane state | 
 |  * | 
 |  * This function sets &mipi_dbi->enabled, flushes the whole framebuffer and | 
 |  * enables the backlight. Drivers can use this in their | 
 |  * &drm_simple_display_pipe_funcs->enable callback. | 
 |  * | 
 |  * Note: Drivers which don't use mipi_dbi_pipe_update() because they have custom | 
 |  * framebuffer flushing, can't use this function since they both use the same | 
 |  * flushing code. | 
 |  */ | 
 | void mipi_dbi_enable_flush(struct mipi_dbi_dev *dbidev, | 
 | 			   struct drm_crtc_state *crtc_state, | 
 | 			   struct drm_plane_state *plane_state) | 
 | { | 
 | 	struct drm_framebuffer *fb = plane_state->fb; | 
 | 	struct drm_rect rect = { | 
 | 		.x1 = 0, | 
 | 		.x2 = fb->width, | 
 | 		.y1 = 0, | 
 | 		.y2 = fb->height, | 
 | 	}; | 
 | 	int idx; | 
 |  | 
 | 	if (!drm_dev_enter(&dbidev->drm, &idx)) | 
 | 		return; | 
 |  | 
 | 	dbidev->enabled = true; | 
 | 	mipi_dbi_fb_dirty(fb, &rect); | 
 | 	backlight_enable(dbidev->backlight); | 
 |  | 
 | 	drm_dev_exit(idx); | 
 | } | 
 | EXPORT_SYMBOL(mipi_dbi_enable_flush); | 
 |  | 
 | static void mipi_dbi_blank(struct mipi_dbi_dev *dbidev) | 
 | { | 
 | 	struct drm_device *drm = &dbidev->drm; | 
 | 	u16 height = drm->mode_config.min_height; | 
 | 	u16 width = drm->mode_config.min_width; | 
 | 	struct mipi_dbi *dbi = &dbidev->dbi; | 
 | 	size_t len = width * height * 2; | 
 | 	int idx; | 
 |  | 
 | 	if (!drm_dev_enter(drm, &idx)) | 
 | 		return; | 
 |  | 
 | 	memset(dbidev->tx_buf, 0, len); | 
 |  | 
 | 	mipi_dbi_command(dbi, MIPI_DCS_SET_COLUMN_ADDRESS, 0, 0, | 
 | 			 ((width - 1) >> 8) & 0xFF, (width - 1) & 0xFF); | 
 | 	mipi_dbi_command(dbi, MIPI_DCS_SET_PAGE_ADDRESS, 0, 0, | 
 | 			 ((height - 1) >> 8) & 0xFF, (height - 1) & 0xFF); | 
 | 	mipi_dbi_command_buf(dbi, MIPI_DCS_WRITE_MEMORY_START, | 
 | 			     (u8 *)dbidev->tx_buf, len); | 
 |  | 
 | 	drm_dev_exit(idx); | 
 | } | 
 |  | 
 | /** | 
 |  * mipi_dbi_pipe_disable - MIPI DBI pipe disable helper | 
 |  * @pipe: Display pipe | 
 |  * | 
 |  * This function disables backlight if present, if not the display memory is | 
 |  * blanked. The regulator is disabled if in use. Drivers can use this as their | 
 |  * &drm_simple_display_pipe_funcs->disable callback. | 
 |  */ | 
 | void mipi_dbi_pipe_disable(struct drm_simple_display_pipe *pipe) | 
 | { | 
 | 	struct mipi_dbi_dev *dbidev = drm_to_mipi_dbi_dev(pipe->crtc.dev); | 
 |  | 
 | 	if (!dbidev->enabled) | 
 | 		return; | 
 |  | 
 | 	DRM_DEBUG_KMS("\n"); | 
 |  | 
 | 	dbidev->enabled = false; | 
 |  | 
 | 	if (dbidev->backlight) | 
 | 		backlight_disable(dbidev->backlight); | 
 | 	else | 
 | 		mipi_dbi_blank(dbidev); | 
 |  | 
 | 	if (dbidev->regulator) | 
 | 		regulator_disable(dbidev->regulator); | 
 | } | 
 | EXPORT_SYMBOL(mipi_dbi_pipe_disable); | 
 |  | 
 | static int mipi_dbi_connector_get_modes(struct drm_connector *connector) | 
 | { | 
 | 	struct mipi_dbi_dev *dbidev = drm_to_mipi_dbi_dev(connector->dev); | 
 | 	struct drm_display_mode *mode; | 
 |  | 
 | 	mode = drm_mode_duplicate(connector->dev, &dbidev->mode); | 
 | 	if (!mode) { | 
 | 		DRM_ERROR("Failed to duplicate mode\n"); | 
 | 		return 0; | 
 | 	} | 
 |  | 
 | 	if (mode->name[0] == '\0') | 
 | 		drm_mode_set_name(mode); | 
 |  | 
 | 	mode->type |= DRM_MODE_TYPE_PREFERRED; | 
 | 	drm_mode_probed_add(connector, mode); | 
 |  | 
 | 	if (mode->width_mm) { | 
 | 		connector->display_info.width_mm = mode->width_mm; | 
 | 		connector->display_info.height_mm = mode->height_mm; | 
 | 	} | 
 |  | 
 | 	return 1; | 
 | } | 
 |  | 
 | static const struct drm_connector_helper_funcs mipi_dbi_connector_hfuncs = { | 
 | 	.get_modes = mipi_dbi_connector_get_modes, | 
 | }; | 
 |  | 
 | static const struct drm_connector_funcs mipi_dbi_connector_funcs = { | 
 | 	.reset = drm_atomic_helper_connector_reset, | 
 | 	.fill_modes = drm_helper_probe_single_connector_modes, | 
 | 	.destroy = drm_connector_cleanup, | 
 | 	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, | 
 | 	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state, | 
 | }; | 
 |  | 
 | static int mipi_dbi_rotate_mode(struct drm_display_mode *mode, | 
 | 				unsigned int rotation) | 
 | { | 
 | 	if (rotation == 0 || rotation == 180) { | 
 | 		return 0; | 
 | 	} else if (rotation == 90 || rotation == 270) { | 
 | 		swap(mode->hdisplay, mode->vdisplay); | 
 | 		swap(mode->hsync_start, mode->vsync_start); | 
 | 		swap(mode->hsync_end, mode->vsync_end); | 
 | 		swap(mode->htotal, mode->vtotal); | 
 | 		swap(mode->width_mm, mode->height_mm); | 
 | 		return 0; | 
 | 	} else { | 
 | 		return -EINVAL; | 
 | 	} | 
 | } | 
 |  | 
 | static const struct drm_mode_config_funcs mipi_dbi_mode_config_funcs = { | 
 | 	.fb_create = drm_gem_fb_create_with_dirty, | 
 | 	.atomic_check = drm_atomic_helper_check, | 
 | 	.atomic_commit = drm_atomic_helper_commit, | 
 | }; | 
 |  | 
 | static const uint32_t mipi_dbi_formats[] = { | 
 | 	DRM_FORMAT_RGB565, | 
 | 	DRM_FORMAT_XRGB8888, | 
 | }; | 
 |  | 
 | /** | 
 |  * mipi_dbi_dev_init_with_formats - MIPI DBI device initialization with custom formats | 
 |  * @dbidev: MIPI DBI device structure to initialize | 
 |  * @funcs: Display pipe functions | 
 |  * @formats: Array of supported formats (DRM_FORMAT\_\*). | 
 |  * @format_count: Number of elements in @formats | 
 |  * @mode: Display mode | 
 |  * @rotation: Initial rotation in degrees Counter Clock Wise | 
 |  * @tx_buf_size: Allocate a transmit buffer of this size. | 
 |  * | 
 |  * This function sets up a &drm_simple_display_pipe with a &drm_connector that | 
 |  * has one fixed &drm_display_mode which is rotated according to @rotation. | 
 |  * This mode is used to set the mode config min/max width/height properties. | 
 |  * | 
 |  * Use mipi_dbi_dev_init() if you don't need custom formats. | 
 |  * | 
 |  * Note: | 
 |  * Some of the helper functions expects RGB565 to be the default format and the | 
 |  * transmit buffer sized to fit that. | 
 |  * | 
 |  * Returns: | 
 |  * Zero on success, negative error code on failure. | 
 |  */ | 
 | int mipi_dbi_dev_init_with_formats(struct mipi_dbi_dev *dbidev, | 
 | 				   const struct drm_simple_display_pipe_funcs *funcs, | 
 | 				   const uint32_t *formats, unsigned int format_count, | 
 | 				   const struct drm_display_mode *mode, | 
 | 				   unsigned int rotation, size_t tx_buf_size) | 
 | { | 
 | 	static const uint64_t modifiers[] = { | 
 | 		DRM_FORMAT_MOD_LINEAR, | 
 | 		DRM_FORMAT_MOD_INVALID | 
 | 	}; | 
 | 	struct drm_device *drm = &dbidev->drm; | 
 | 	int ret; | 
 |  | 
 | 	if (!dbidev->dbi.command) | 
 | 		return -EINVAL; | 
 |  | 
 | 	dbidev->tx_buf = devm_kmalloc(drm->dev, tx_buf_size, GFP_KERNEL); | 
 | 	if (!dbidev->tx_buf) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	drm_mode_copy(&dbidev->mode, mode); | 
 | 	ret = mipi_dbi_rotate_mode(&dbidev->mode, rotation); | 
 | 	if (ret) { | 
 | 		DRM_ERROR("Illegal rotation value %u\n", rotation); | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	drm_connector_helper_add(&dbidev->connector, &mipi_dbi_connector_hfuncs); | 
 | 	ret = drm_connector_init(drm, &dbidev->connector, &mipi_dbi_connector_funcs, | 
 | 				 DRM_MODE_CONNECTOR_SPI); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	ret = drm_simple_display_pipe_init(drm, &dbidev->pipe, funcs, formats, format_count, | 
 | 					   modifiers, &dbidev->connector); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	drm_plane_enable_fb_damage_clips(&dbidev->pipe.plane); | 
 |  | 
 | 	drm->mode_config.funcs = &mipi_dbi_mode_config_funcs; | 
 | 	drm->mode_config.min_width = dbidev->mode.hdisplay; | 
 | 	drm->mode_config.max_width = dbidev->mode.hdisplay; | 
 | 	drm->mode_config.min_height = dbidev->mode.vdisplay; | 
 | 	drm->mode_config.max_height = dbidev->mode.vdisplay; | 
 | 	dbidev->rotation = rotation; | 
 |  | 
 | 	DRM_DEBUG_KMS("rotation = %u\n", rotation); | 
 |  | 
 | 	return 0; | 
 | } | 
 | EXPORT_SYMBOL(mipi_dbi_dev_init_with_formats); | 
 |  | 
 | /** | 
 |  * mipi_dbi_dev_init - MIPI DBI device initialization | 
 |  * @dbidev: MIPI DBI device structure to initialize | 
 |  * @funcs: Display pipe functions | 
 |  * @mode: Display mode | 
 |  * @rotation: Initial rotation in degrees Counter Clock Wise | 
 |  * | 
 |  * This function sets up a &drm_simple_display_pipe with a &drm_connector that | 
 |  * has one fixed &drm_display_mode which is rotated according to @rotation. | 
 |  * This mode is used to set the mode config min/max width/height properties. | 
 |  * Additionally &mipi_dbi.tx_buf is allocated. | 
 |  * | 
 |  * Supported formats: Native RGB565 and emulated XRGB8888. | 
 |  * | 
 |  * Returns: | 
 |  * Zero on success, negative error code on failure. | 
 |  */ | 
 | int mipi_dbi_dev_init(struct mipi_dbi_dev *dbidev, | 
 | 		      const struct drm_simple_display_pipe_funcs *funcs, | 
 | 		      const struct drm_display_mode *mode, unsigned int rotation) | 
 | { | 
 | 	size_t bufsize = mode->vdisplay * mode->hdisplay * sizeof(u16); | 
 |  | 
 | 	dbidev->drm.mode_config.preferred_depth = 16; | 
 |  | 
 | 	return mipi_dbi_dev_init_with_formats(dbidev, funcs, mipi_dbi_formats, | 
 | 					      ARRAY_SIZE(mipi_dbi_formats), mode, | 
 | 					      rotation, bufsize); | 
 | } | 
 | EXPORT_SYMBOL(mipi_dbi_dev_init); | 
 |  | 
 | /** | 
 |  * mipi_dbi_release - DRM driver release helper | 
 |  * @drm: DRM device | 
 |  * | 
 |  * This function finalizes and frees &mipi_dbi. | 
 |  * | 
 |  * Drivers can use this as their &drm_driver->release callback. | 
 |  */ | 
 | void mipi_dbi_release(struct drm_device *drm) | 
 | { | 
 | 	struct mipi_dbi_dev *dbidev = drm_to_mipi_dbi_dev(drm); | 
 |  | 
 | 	DRM_DEBUG_DRIVER("\n"); | 
 |  | 
 | 	drm_mode_config_cleanup(drm); | 
 | 	drm_dev_fini(drm); | 
 | 	kfree(dbidev); | 
 | } | 
 | EXPORT_SYMBOL(mipi_dbi_release); | 
 |  | 
 | /** | 
 |  * mipi_dbi_hw_reset - Hardware reset of controller | 
 |  * @dbi: MIPI DBI structure | 
 |  * | 
 |  * Reset controller if the &mipi_dbi->reset gpio is set. | 
 |  */ | 
 | void mipi_dbi_hw_reset(struct mipi_dbi *dbi) | 
 | { | 
 | 	if (!dbi->reset) | 
 | 		return; | 
 |  | 
 | 	gpiod_set_value_cansleep(dbi->reset, 0); | 
 | 	usleep_range(20, 1000); | 
 | 	gpiod_set_value_cansleep(dbi->reset, 1); | 
 | 	msleep(120); | 
 | } | 
 | EXPORT_SYMBOL(mipi_dbi_hw_reset); | 
 |  | 
 | /** | 
 |  * mipi_dbi_display_is_on - Check if display is on | 
 |  * @dbi: MIPI DBI structure | 
 |  * | 
 |  * This function checks the Power Mode register (if readable) to see if | 
 |  * display output is turned on. This can be used to see if the bootloader | 
 |  * has already turned on the display avoiding flicker when the pipeline is | 
 |  * enabled. | 
 |  * | 
 |  * Returns: | 
 |  * true if the display can be verified to be on, false otherwise. | 
 |  */ | 
 | bool mipi_dbi_display_is_on(struct mipi_dbi *dbi) | 
 | { | 
 | 	u8 val; | 
 |  | 
 | 	if (mipi_dbi_command_read(dbi, MIPI_DCS_GET_POWER_MODE, &val)) | 
 | 		return false; | 
 |  | 
 | 	val &= ~DCS_POWER_MODE_RESERVED_MASK; | 
 |  | 
 | 	/* The poweron/reset value is 08h DCS_POWER_MODE_DISPLAY_NORMAL_MODE */ | 
 | 	if (val != (DCS_POWER_MODE_DISPLAY | | 
 | 	    DCS_POWER_MODE_DISPLAY_NORMAL_MODE | DCS_POWER_MODE_SLEEP_MODE)) | 
 | 		return false; | 
 |  | 
 | 	DRM_DEBUG_DRIVER("Display is ON\n"); | 
 |  | 
 | 	return true; | 
 | } | 
 | EXPORT_SYMBOL(mipi_dbi_display_is_on); | 
 |  | 
 | static int mipi_dbi_poweron_reset_conditional(struct mipi_dbi_dev *dbidev, bool cond) | 
 | { | 
 | 	struct device *dev = dbidev->drm.dev; | 
 | 	struct mipi_dbi *dbi = &dbidev->dbi; | 
 | 	int ret; | 
 |  | 
 | 	if (dbidev->regulator) { | 
 | 		ret = regulator_enable(dbidev->regulator); | 
 | 		if (ret) { | 
 | 			DRM_DEV_ERROR(dev, "Failed to enable regulator (%d)\n", ret); | 
 | 			return ret; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	if (cond && mipi_dbi_display_is_on(dbi)) | 
 | 		return 1; | 
 |  | 
 | 	mipi_dbi_hw_reset(dbi); | 
 | 	ret = mipi_dbi_command(dbi, MIPI_DCS_SOFT_RESET); | 
 | 	if (ret) { | 
 | 		DRM_DEV_ERROR(dev, "Failed to send reset command (%d)\n", ret); | 
 | 		if (dbidev->regulator) | 
 | 			regulator_disable(dbidev->regulator); | 
 | 		return ret; | 
 | 	} | 
 |  | 
 | 	/* | 
 | 	 * If we did a hw reset, we know the controller is in Sleep mode and | 
 | 	 * per MIPI DSC spec should wait 5ms after soft reset. If we didn't, | 
 | 	 * we assume worst case and wait 120ms. | 
 | 	 */ | 
 | 	if (dbi->reset) | 
 | 		usleep_range(5000, 20000); | 
 | 	else | 
 | 		msleep(120); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | /** | 
 |  * mipi_dbi_poweron_reset - MIPI DBI poweron and reset | 
 |  * @dbidev: MIPI DBI device structure | 
 |  * | 
 |  * This function enables the regulator if used and does a hardware and software | 
 |  * reset. | 
 |  * | 
 |  * Returns: | 
 |  * Zero on success, or a negative error code. | 
 |  */ | 
 | int mipi_dbi_poweron_reset(struct mipi_dbi_dev *dbidev) | 
 | { | 
 | 	return mipi_dbi_poweron_reset_conditional(dbidev, false); | 
 | } | 
 | EXPORT_SYMBOL(mipi_dbi_poweron_reset); | 
 |  | 
 | /** | 
 |  * mipi_dbi_poweron_conditional_reset - MIPI DBI poweron and conditional reset | 
 |  * @dbidev: MIPI DBI device structure | 
 |  * | 
 |  * This function enables the regulator if used and if the display is off, it | 
 |  * does a hardware and software reset. If mipi_dbi_display_is_on() determines | 
 |  * that the display is on, no reset is performed. | 
 |  * | 
 |  * Returns: | 
 |  * Zero if the controller was reset, 1 if the display was already on, or a | 
 |  * negative error code. | 
 |  */ | 
 | int mipi_dbi_poweron_conditional_reset(struct mipi_dbi_dev *dbidev) | 
 | { | 
 | 	return mipi_dbi_poweron_reset_conditional(dbidev, true); | 
 | } | 
 | EXPORT_SYMBOL(mipi_dbi_poweron_conditional_reset); | 
 |  | 
 | #if IS_ENABLED(CONFIG_SPI) | 
 |  | 
 | /** | 
 |  * mipi_dbi_spi_cmd_max_speed - get the maximum SPI bus speed | 
 |  * @spi: SPI device | 
 |  * @len: The transfer buffer length. | 
 |  * | 
 |  * Many controllers have a max speed of 10MHz, but can be pushed way beyond | 
 |  * that. Increase reliability by running pixel data at max speed and the rest | 
 |  * at 10MHz, preventing transfer glitches from messing up the init settings. | 
 |  */ | 
 | u32 mipi_dbi_spi_cmd_max_speed(struct spi_device *spi, size_t len) | 
 | { | 
 | 	if (len > 64) | 
 | 		return 0; /* use default */ | 
 |  | 
 | 	return min_t(u32, 10000000, spi->max_speed_hz); | 
 | } | 
 | EXPORT_SYMBOL(mipi_dbi_spi_cmd_max_speed); | 
 |  | 
 | static bool mipi_dbi_machine_little_endian(void) | 
 | { | 
 | #if defined(__LITTLE_ENDIAN) | 
 | 	return true; | 
 | #else | 
 | 	return false; | 
 | #endif | 
 | } | 
 |  | 
 | /* | 
 |  * MIPI DBI Type C Option 1 | 
 |  * | 
 |  * If the SPI controller doesn't have 9 bits per word support, | 
 |  * use blocks of 9 bytes to send 8x 9-bit words using a 8-bit SPI transfer. | 
 |  * Pad partial blocks with MIPI_DCS_NOP (zero). | 
 |  * This is how the D/C bit (x) is added: | 
 |  *     x7654321 | 
 |  *     0x765432 | 
 |  *     10x76543 | 
 |  *     210x7654 | 
 |  *     3210x765 | 
 |  *     43210x76 | 
 |  *     543210x7 | 
 |  *     6543210x | 
 |  *     76543210 | 
 |  */ | 
 |  | 
 | static int mipi_dbi_spi1e_transfer(struct mipi_dbi *dbi, int dc, | 
 | 				   const void *buf, size_t len, | 
 | 				   unsigned int bpw) | 
 | { | 
 | 	bool swap_bytes = (bpw == 16 && mipi_dbi_machine_little_endian()); | 
 | 	size_t chunk, max_chunk = dbi->tx_buf9_len; | 
 | 	struct spi_device *spi = dbi->spi; | 
 | 	struct spi_transfer tr = { | 
 | 		.tx_buf = dbi->tx_buf9, | 
 | 		.bits_per_word = 8, | 
 | 	}; | 
 | 	struct spi_message m; | 
 | 	const u8 *src = buf; | 
 | 	int i, ret; | 
 | 	u8 *dst; | 
 |  | 
 | 	if (drm_debug_enabled(DRM_UT_DRIVER)) | 
 | 		pr_debug("[drm:%s] dc=%d, max_chunk=%zu, transfers:\n", | 
 | 			 __func__, dc, max_chunk); | 
 |  | 
 | 	tr.speed_hz = mipi_dbi_spi_cmd_max_speed(spi, len); | 
 | 	spi_message_init_with_transfers(&m, &tr, 1); | 
 |  | 
 | 	if (!dc) { | 
 | 		if (WARN_ON_ONCE(len != 1)) | 
 | 			return -EINVAL; | 
 |  | 
 | 		/* Command: pad no-op's (zeroes) at beginning of block */ | 
 | 		dst = dbi->tx_buf9; | 
 | 		memset(dst, 0, 9); | 
 | 		dst[8] = *src; | 
 | 		tr.len = 9; | 
 |  | 
 | 		return spi_sync(spi, &m); | 
 | 	} | 
 |  | 
 | 	/* max with room for adding one bit per byte */ | 
 | 	max_chunk = max_chunk / 9 * 8; | 
 | 	/* but no bigger than len */ | 
 | 	max_chunk = min(max_chunk, len); | 
 | 	/* 8 byte blocks */ | 
 | 	max_chunk = max_t(size_t, 8, max_chunk & ~0x7); | 
 |  | 
 | 	while (len) { | 
 | 		size_t added = 0; | 
 |  | 
 | 		chunk = min(len, max_chunk); | 
 | 		len -= chunk; | 
 | 		dst = dbi->tx_buf9; | 
 |  | 
 | 		if (chunk < 8) { | 
 | 			u8 val, carry = 0; | 
 |  | 
 | 			/* Data: pad no-op's (zeroes) at end of block */ | 
 | 			memset(dst, 0, 9); | 
 |  | 
 | 			if (swap_bytes) { | 
 | 				for (i = 1; i < (chunk + 1); i++) { | 
 | 					val = src[1]; | 
 | 					*dst++ = carry | BIT(8 - i) | (val >> i); | 
 | 					carry = val << (8 - i); | 
 | 					i++; | 
 | 					val = src[0]; | 
 | 					*dst++ = carry | BIT(8 - i) | (val >> i); | 
 | 					carry = val << (8 - i); | 
 | 					src += 2; | 
 | 				} | 
 | 				*dst++ = carry; | 
 | 			} else { | 
 | 				for (i = 1; i < (chunk + 1); i++) { | 
 | 					val = *src++; | 
 | 					*dst++ = carry | BIT(8 - i) | (val >> i); | 
 | 					carry = val << (8 - i); | 
 | 				} | 
 | 				*dst++ = carry; | 
 | 			} | 
 |  | 
 | 			chunk = 8; | 
 | 			added = 1; | 
 | 		} else { | 
 | 			for (i = 0; i < chunk; i += 8) { | 
 | 				if (swap_bytes) { | 
 | 					*dst++ =                 BIT(7) | (src[1] >> 1); | 
 | 					*dst++ = (src[1] << 7) | BIT(6) | (src[0] >> 2); | 
 | 					*dst++ = (src[0] << 6) | BIT(5) | (src[3] >> 3); | 
 | 					*dst++ = (src[3] << 5) | BIT(4) | (src[2] >> 4); | 
 | 					*dst++ = (src[2] << 4) | BIT(3) | (src[5] >> 5); | 
 | 					*dst++ = (src[5] << 3) | BIT(2) | (src[4] >> 6); | 
 | 					*dst++ = (src[4] << 2) | BIT(1) | (src[7] >> 7); | 
 | 					*dst++ = (src[7] << 1) | BIT(0); | 
 | 					*dst++ = src[6]; | 
 | 				} else { | 
 | 					*dst++ =                 BIT(7) | (src[0] >> 1); | 
 | 					*dst++ = (src[0] << 7) | BIT(6) | (src[1] >> 2); | 
 | 					*dst++ = (src[1] << 6) | BIT(5) | (src[2] >> 3); | 
 | 					*dst++ = (src[2] << 5) | BIT(4) | (src[3] >> 4); | 
 | 					*dst++ = (src[3] << 4) | BIT(3) | (src[4] >> 5); | 
 | 					*dst++ = (src[4] << 3) | BIT(2) | (src[5] >> 6); | 
 | 					*dst++ = (src[5] << 2) | BIT(1) | (src[6] >> 7); | 
 | 					*dst++ = (src[6] << 1) | BIT(0); | 
 | 					*dst++ = src[7]; | 
 | 				} | 
 |  | 
 | 				src += 8; | 
 | 				added++; | 
 | 			} | 
 | 		} | 
 |  | 
 | 		tr.len = chunk + added; | 
 |  | 
 | 		ret = spi_sync(spi, &m); | 
 | 		if (ret) | 
 | 			return ret; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int mipi_dbi_spi1_transfer(struct mipi_dbi *dbi, int dc, | 
 | 				  const void *buf, size_t len, | 
 | 				  unsigned int bpw) | 
 | { | 
 | 	struct spi_device *spi = dbi->spi; | 
 | 	struct spi_transfer tr = { | 
 | 		.bits_per_word = 9, | 
 | 	}; | 
 | 	const u16 *src16 = buf; | 
 | 	const u8 *src8 = buf; | 
 | 	struct spi_message m; | 
 | 	size_t max_chunk; | 
 | 	u16 *dst16; | 
 | 	int ret; | 
 |  | 
 | 	if (!spi_is_bpw_supported(spi, 9)) | 
 | 		return mipi_dbi_spi1e_transfer(dbi, dc, buf, len, bpw); | 
 |  | 
 | 	tr.speed_hz = mipi_dbi_spi_cmd_max_speed(spi, len); | 
 | 	max_chunk = dbi->tx_buf9_len; | 
 | 	dst16 = dbi->tx_buf9; | 
 |  | 
 | 	if (drm_debug_enabled(DRM_UT_DRIVER)) | 
 | 		pr_debug("[drm:%s] dc=%d, max_chunk=%zu, transfers:\n", | 
 | 			 __func__, dc, max_chunk); | 
 |  | 
 | 	max_chunk = min(max_chunk / 2, len); | 
 |  | 
 | 	spi_message_init_with_transfers(&m, &tr, 1); | 
 | 	tr.tx_buf = dst16; | 
 |  | 
 | 	while (len) { | 
 | 		size_t chunk = min(len, max_chunk); | 
 | 		unsigned int i; | 
 |  | 
 | 		if (bpw == 16 && mipi_dbi_machine_little_endian()) { | 
 | 			for (i = 0; i < (chunk * 2); i += 2) { | 
 | 				dst16[i]     = *src16 >> 8; | 
 | 				dst16[i + 1] = *src16++ & 0xFF; | 
 | 				if (dc) { | 
 | 					dst16[i]     |= 0x0100; | 
 | 					dst16[i + 1] |= 0x0100; | 
 | 				} | 
 | 			} | 
 | 		} else { | 
 | 			for (i = 0; i < chunk; i++) { | 
 | 				dst16[i] = *src8++; | 
 | 				if (dc) | 
 | 					dst16[i] |= 0x0100; | 
 | 			} | 
 | 		} | 
 |  | 
 | 		tr.len = chunk; | 
 | 		len -= chunk; | 
 |  | 
 | 		ret = spi_sync(spi, &m); | 
 | 		if (ret) | 
 | 			return ret; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int mipi_dbi_typec1_command(struct mipi_dbi *dbi, u8 *cmd, | 
 | 				   u8 *parameters, size_t num) | 
 | { | 
 | 	unsigned int bpw = (*cmd == MIPI_DCS_WRITE_MEMORY_START) ? 16 : 8; | 
 | 	int ret; | 
 |  | 
 | 	if (mipi_dbi_command_is_read(dbi, *cmd)) | 
 | 		return -EOPNOTSUPP; | 
 |  | 
 | 	MIPI_DBI_DEBUG_COMMAND(*cmd, parameters, num); | 
 |  | 
 | 	ret = mipi_dbi_spi1_transfer(dbi, 0, cmd, 1, 8); | 
 | 	if (ret || !num) | 
 | 		return ret; | 
 |  | 
 | 	return mipi_dbi_spi1_transfer(dbi, 1, parameters, num, bpw); | 
 | } | 
 |  | 
 | /* MIPI DBI Type C Option 3 */ | 
 |  | 
 | static int mipi_dbi_typec3_command_read(struct mipi_dbi *dbi, u8 *cmd, | 
 | 					u8 *data, size_t len) | 
 | { | 
 | 	struct spi_device *spi = dbi->spi; | 
 | 	u32 speed_hz = min_t(u32, MIPI_DBI_MAX_SPI_READ_SPEED, | 
 | 			     spi->max_speed_hz / 2); | 
 | 	struct spi_transfer tr[2] = { | 
 | 		{ | 
 | 			.speed_hz = speed_hz, | 
 | 			.tx_buf = cmd, | 
 | 			.len = 1, | 
 | 		}, { | 
 | 			.speed_hz = speed_hz, | 
 | 			.len = len, | 
 | 		}, | 
 | 	}; | 
 | 	struct spi_message m; | 
 | 	u8 *buf; | 
 | 	int ret; | 
 |  | 
 | 	if (!len) | 
 | 		return -EINVAL; | 
 |  | 
 | 	/* | 
 | 	 * Support non-standard 24-bit and 32-bit Nokia read commands which | 
 | 	 * start with a dummy clock, so we need to read an extra byte. | 
 | 	 */ | 
 | 	if (*cmd == MIPI_DCS_GET_DISPLAY_ID || | 
 | 	    *cmd == MIPI_DCS_GET_DISPLAY_STATUS) { | 
 | 		if (!(len == 3 || len == 4)) | 
 | 			return -EINVAL; | 
 |  | 
 | 		tr[1].len = len + 1; | 
 | 	} | 
 |  | 
 | 	buf = kmalloc(tr[1].len, GFP_KERNEL); | 
 | 	if (!buf) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	tr[1].rx_buf = buf; | 
 | 	gpiod_set_value_cansleep(dbi->dc, 0); | 
 |  | 
 | 	spi_message_init_with_transfers(&m, tr, ARRAY_SIZE(tr)); | 
 | 	ret = spi_sync(spi, &m); | 
 | 	if (ret) | 
 | 		goto err_free; | 
 |  | 
 | 	if (tr[1].len == len) { | 
 | 		memcpy(data, buf, len); | 
 | 	} else { | 
 | 		unsigned int i; | 
 |  | 
 | 		for (i = 0; i < len; i++) | 
 | 			data[i] = (buf[i] << 1) | (buf[i + 1] >> 7); | 
 | 	} | 
 |  | 
 | 	MIPI_DBI_DEBUG_COMMAND(*cmd, data, len); | 
 |  | 
 | err_free: | 
 | 	kfree(buf); | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | static int mipi_dbi_typec3_command(struct mipi_dbi *dbi, u8 *cmd, | 
 | 				   u8 *par, size_t num) | 
 | { | 
 | 	struct spi_device *spi = dbi->spi; | 
 | 	unsigned int bpw = 8; | 
 | 	u32 speed_hz; | 
 | 	int ret; | 
 |  | 
 | 	if (mipi_dbi_command_is_read(dbi, *cmd)) | 
 | 		return mipi_dbi_typec3_command_read(dbi, cmd, par, num); | 
 |  | 
 | 	MIPI_DBI_DEBUG_COMMAND(*cmd, par, num); | 
 |  | 
 | 	gpiod_set_value_cansleep(dbi->dc, 0); | 
 | 	speed_hz = mipi_dbi_spi_cmd_max_speed(spi, 1); | 
 | 	ret = mipi_dbi_spi_transfer(spi, speed_hz, 8, cmd, 1); | 
 | 	if (ret || !num) | 
 | 		return ret; | 
 |  | 
 | 	if (*cmd == MIPI_DCS_WRITE_MEMORY_START && !dbi->swap_bytes) | 
 | 		bpw = 16; | 
 |  | 
 | 	gpiod_set_value_cansleep(dbi->dc, 1); | 
 | 	speed_hz = mipi_dbi_spi_cmd_max_speed(spi, num); | 
 |  | 
 | 	return mipi_dbi_spi_transfer(spi, speed_hz, bpw, par, num); | 
 | } | 
 |  | 
 | /** | 
 |  * mipi_dbi_spi_init - Initialize MIPI DBI SPI interface | 
 |  * @spi: SPI device | 
 |  * @dbi: MIPI DBI structure to initialize | 
 |  * @dc: D/C gpio (optional) | 
 |  * | 
 |  * This function sets &mipi_dbi->command, enables &mipi_dbi->read_commands for the | 
 |  * usual read commands. It should be followed by a call to mipi_dbi_dev_init() or | 
 |  * a driver-specific init. | 
 |  * | 
 |  * If @dc is set, a Type C Option 3 interface is assumed, if not | 
 |  * Type C Option 1. | 
 |  * | 
 |  * If the SPI master driver doesn't support the necessary bits per word, | 
 |  * the following transformation is used: | 
 |  * | 
 |  * - 9-bit: reorder buffer as 9x 8-bit words, padded with no-op command. | 
 |  * - 16-bit: if big endian send as 8-bit, if little endian swap bytes | 
 |  * | 
 |  * Returns: | 
 |  * Zero on success, negative error code on failure. | 
 |  */ | 
 | int mipi_dbi_spi_init(struct spi_device *spi, struct mipi_dbi *dbi, | 
 | 		      struct gpio_desc *dc) | 
 | { | 
 | 	struct device *dev = &spi->dev; | 
 | 	int ret; | 
 |  | 
 | 	/* | 
 | 	 * Even though it's not the SPI device that does DMA (the master does), | 
 | 	 * the dma mask is necessary for the dma_alloc_wc() in | 
 | 	 * drm_gem_cma_create(). The dma_addr returned will be a physical | 
 | 	 * address which might be different from the bus address, but this is | 
 | 	 * not a problem since the address will not be used. | 
 | 	 * The virtual address is used in the transfer and the SPI core | 
 | 	 * re-maps it on the SPI master device using the DMA streaming API | 
 | 	 * (spi_map_buf()). | 
 | 	 */ | 
 | 	if (!dev->coherent_dma_mask) { | 
 | 		ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32)); | 
 | 		if (ret) { | 
 | 			dev_warn(dev, "Failed to set dma mask %d\n", ret); | 
 | 			return ret; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	dbi->spi = spi; | 
 | 	dbi->read_commands = mipi_dbi_dcs_read_commands; | 
 |  | 
 | 	if (dc) { | 
 | 		dbi->command = mipi_dbi_typec3_command; | 
 | 		dbi->dc = dc; | 
 | 		if (mipi_dbi_machine_little_endian() && !spi_is_bpw_supported(spi, 16)) | 
 | 			dbi->swap_bytes = true; | 
 | 	} else { | 
 | 		dbi->command = mipi_dbi_typec1_command; | 
 | 		dbi->tx_buf9_len = SZ_16K; | 
 | 		dbi->tx_buf9 = devm_kmalloc(dev, dbi->tx_buf9_len, GFP_KERNEL); | 
 | 		if (!dbi->tx_buf9) | 
 | 			return -ENOMEM; | 
 | 	} | 
 |  | 
 | 	mutex_init(&dbi->cmdlock); | 
 |  | 
 | 	DRM_DEBUG_DRIVER("SPI speed: %uMHz\n", spi->max_speed_hz / 1000000); | 
 |  | 
 | 	return 0; | 
 | } | 
 | EXPORT_SYMBOL(mipi_dbi_spi_init); | 
 |  | 
 | /** | 
 |  * mipi_dbi_spi_transfer - SPI transfer helper | 
 |  * @spi: SPI device | 
 |  * @speed_hz: Override speed (optional) | 
 |  * @bpw: Bits per word | 
 |  * @buf: Buffer to transfer | 
 |  * @len: Buffer length | 
 |  * | 
 |  * This SPI transfer helper breaks up the transfer of @buf into chunks which | 
 |  * the SPI controller driver can handle. | 
 |  * | 
 |  * Returns: | 
 |  * Zero on success, negative error code on failure. | 
 |  */ | 
 | int mipi_dbi_spi_transfer(struct spi_device *spi, u32 speed_hz, | 
 | 			  u8 bpw, const void *buf, size_t len) | 
 | { | 
 | 	size_t max_chunk = spi_max_transfer_size(spi); | 
 | 	struct spi_transfer tr = { | 
 | 		.bits_per_word = bpw, | 
 | 		.speed_hz = speed_hz, | 
 | 	}; | 
 | 	struct spi_message m; | 
 | 	size_t chunk; | 
 | 	int ret; | 
 |  | 
 | 	spi_message_init_with_transfers(&m, &tr, 1); | 
 |  | 
 | 	while (len) { | 
 | 		chunk = min(len, max_chunk); | 
 |  | 
 | 		tr.tx_buf = buf; | 
 | 		tr.len = chunk; | 
 | 		buf += chunk; | 
 | 		len -= chunk; | 
 |  | 
 | 		ret = spi_sync(spi, &m); | 
 | 		if (ret) | 
 | 			return ret; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 | EXPORT_SYMBOL(mipi_dbi_spi_transfer); | 
 |  | 
 | #endif /* CONFIG_SPI */ | 
 |  | 
 | #ifdef CONFIG_DEBUG_FS | 
 |  | 
 | static ssize_t mipi_dbi_debugfs_command_write(struct file *file, | 
 | 					      const char __user *ubuf, | 
 | 					      size_t count, loff_t *ppos) | 
 | { | 
 | 	struct seq_file *m = file->private_data; | 
 | 	struct mipi_dbi_dev *dbidev = m->private; | 
 | 	u8 val, cmd = 0, parameters[64]; | 
 | 	char *buf, *pos, *token; | 
 | 	int i, ret, idx; | 
 |  | 
 | 	if (!drm_dev_enter(&dbidev->drm, &idx)) | 
 | 		return -ENODEV; | 
 |  | 
 | 	buf = memdup_user_nul(ubuf, count); | 
 | 	if (IS_ERR(buf)) { | 
 | 		ret = PTR_ERR(buf); | 
 | 		goto err_exit; | 
 | 	} | 
 |  | 
 | 	/* strip trailing whitespace */ | 
 | 	for (i = count - 1; i > 0; i--) | 
 | 		if (isspace(buf[i])) | 
 | 			buf[i] = '\0'; | 
 | 		else | 
 | 			break; | 
 | 	i = 0; | 
 | 	pos = buf; | 
 | 	while (pos) { | 
 | 		token = strsep(&pos, " "); | 
 | 		if (!token) { | 
 | 			ret = -EINVAL; | 
 | 			goto err_free; | 
 | 		} | 
 |  | 
 | 		ret = kstrtou8(token, 16, &val); | 
 | 		if (ret < 0) | 
 | 			goto err_free; | 
 |  | 
 | 		if (token == buf) | 
 | 			cmd = val; | 
 | 		else | 
 | 			parameters[i++] = val; | 
 |  | 
 | 		if (i == 64) { | 
 | 			ret = -E2BIG; | 
 | 			goto err_free; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	ret = mipi_dbi_command_buf(&dbidev->dbi, cmd, parameters, i); | 
 |  | 
 | err_free: | 
 | 	kfree(buf); | 
 | err_exit: | 
 | 	drm_dev_exit(idx); | 
 |  | 
 | 	return ret < 0 ? ret : count; | 
 | } | 
 |  | 
 | static int mipi_dbi_debugfs_command_show(struct seq_file *m, void *unused) | 
 | { | 
 | 	struct mipi_dbi_dev *dbidev = m->private; | 
 | 	struct mipi_dbi *dbi = &dbidev->dbi; | 
 | 	u8 cmd, val[4]; | 
 | 	int ret, idx; | 
 | 	size_t len; | 
 |  | 
 | 	if (!drm_dev_enter(&dbidev->drm, &idx)) | 
 | 		return -ENODEV; | 
 |  | 
 | 	for (cmd = 0; cmd < 255; cmd++) { | 
 | 		if (!mipi_dbi_command_is_read(dbi, cmd)) | 
 | 			continue; | 
 |  | 
 | 		switch (cmd) { | 
 | 		case MIPI_DCS_READ_MEMORY_START: | 
 | 		case MIPI_DCS_READ_MEMORY_CONTINUE: | 
 | 			len = 2; | 
 | 			break; | 
 | 		case MIPI_DCS_GET_DISPLAY_ID: | 
 | 			len = 3; | 
 | 			break; | 
 | 		case MIPI_DCS_GET_DISPLAY_STATUS: | 
 | 			len = 4; | 
 | 			break; | 
 | 		default: | 
 | 			len = 1; | 
 | 			break; | 
 | 		} | 
 |  | 
 | 		seq_printf(m, "%02x: ", cmd); | 
 | 		ret = mipi_dbi_command_buf(dbi, cmd, val, len); | 
 | 		if (ret) { | 
 | 			seq_puts(m, "XX\n"); | 
 | 			continue; | 
 | 		} | 
 | 		seq_printf(m, "%*phN\n", (int)len, val); | 
 | 	} | 
 |  | 
 | 	drm_dev_exit(idx); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int mipi_dbi_debugfs_command_open(struct inode *inode, | 
 | 					 struct file *file) | 
 | { | 
 | 	return single_open(file, mipi_dbi_debugfs_command_show, | 
 | 			   inode->i_private); | 
 | } | 
 |  | 
 | static const struct file_operations mipi_dbi_debugfs_command_fops = { | 
 | 	.owner = THIS_MODULE, | 
 | 	.open = mipi_dbi_debugfs_command_open, | 
 | 	.read = seq_read, | 
 | 	.llseek = seq_lseek, | 
 | 	.release = single_release, | 
 | 	.write = mipi_dbi_debugfs_command_write, | 
 | }; | 
 |  | 
 | /** | 
 |  * mipi_dbi_debugfs_init - Create debugfs entries | 
 |  * @minor: DRM minor | 
 |  * | 
 |  * This function creates a 'command' debugfs file for sending commands to the | 
 |  * controller or getting the read command values. | 
 |  * Drivers can use this as their &drm_driver->debugfs_init callback. | 
 |  * | 
 |  * Returns: | 
 |  * Zero on success, negative error code on failure. | 
 |  */ | 
 | int mipi_dbi_debugfs_init(struct drm_minor *minor) | 
 | { | 
 | 	struct mipi_dbi_dev *dbidev = drm_to_mipi_dbi_dev(minor->dev); | 
 | 	umode_t mode = S_IFREG | S_IWUSR; | 
 |  | 
 | 	if (dbidev->dbi.read_commands) | 
 | 		mode |= S_IRUGO; | 
 | 	debugfs_create_file("command", mode, minor->debugfs_root, dbidev, | 
 | 			    &mipi_dbi_debugfs_command_fops); | 
 |  | 
 | 	return 0; | 
 | } | 
 | EXPORT_SYMBOL(mipi_dbi_debugfs_init); | 
 |  | 
 | #endif | 
 |  | 
 | MODULE_LICENSE("GPL"); |