| // SPDX-License-Identifier: GPL-2.0-only |
| /* exynos_drm_writeback.c |
| * |
| * Copyright (c) 2020 Samsung Electronics Co., Ltd. |
| * Authors: |
| * Wonyeong Choi <won0.choi@samsung.com> |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License as published by the |
| * Free Software Foundation; either version 2 of the License, or (at your |
| * option) any later version. |
| */ |
| |
| #include <linux/of.h> |
| #include <linux/of_device.h> |
| #include <linux/platform_device.h> |
| #include <linux/component.h> |
| #include <linux/irq.h> |
| #include <linux/videodev2_exynos_media.h> |
| #include <linux/dma-buf.h> |
| #include <linux/of_address.h> |
| #include <linux/of_irq.h> |
| |
| #include <drm/exynos_drm.h> |
| #include <drm/drm_atomic.h> |
| #include <drm/drm_atomic_helper.h> |
| #include <drm/drm_crtc_helper.h> |
| #include <drm/drm_edid.h> |
| #include <drm/drm_fourcc.h> |
| #include <drm/drm_modeset_helper_vtables.h> |
| #include <drm/drm_probe_helper.h> |
| |
| #include <regs-dpp.h> |
| |
| #include "exynos_drm_crtc.h" |
| #include "exynos_drm_decon.h" |
| #include "exynos_drm_dsim.h" |
| #include "exynos_drm_fb.h" |
| #include "exynos_drm_format.h" |
| #include "exynos_drm_writeback.h" |
| |
| static inline bool wb_is_cwb(const struct writeback_device *wb) |
| { |
| const struct decon_device *decon = wb_get_decon(wb); |
| |
| return decon->config.out_type != DECON_OUT_WB; |
| } |
| |
| void wb_dump(struct writeback_device *wb) |
| { |
| if (wb->state != WB_STATE_ON) { |
| pr_info("writeback state is off\n"); |
| return; |
| } |
| |
| __dpp_dump(wb->id, wb->regs.dpp_base_regs, wb->regs.dma_base_regs, |
| wb->attr); |
| } |
| |
| static const uint32_t writeback_formats[] = { |
| /* TODO : add DRM_FORMAT_RGBA1010102 */ |
| DRM_FORMAT_RGBA8888, |
| }; |
| |
| static const struct of_device_id wb_of_match[] = { |
| { |
| .compatible = "samsung,exynos-writeback", |
| /* TODO : check odma, wbmux, decon_win restriction */ |
| .data = &dpp_drv_data, |
| }, |
| }; |
| |
| static int writeback_get_modes(struct drm_connector *connector) |
| { |
| struct drm_device *dev = connector->dev; |
| |
| return drm_add_modes_noedid(connector, dev->mode_config.max_width, |
| dev->mode_config.max_height); |
| } |
| |
| static void wb_convert_connector_state_to_config(struct dpp_params_info *config, |
| const struct exynos_drm_writeback_state *state) |
| { |
| struct drm_framebuffer *fb = state->base.writeback_job->fb; |
| const struct drm_crtc_state *crtc_state = state->base.crtc->state; |
| |
| pr_debug("%s +\n", __func__); |
| |
| config->src.x = 0; |
| config->src.y = 0; |
| config->src.w = crtc_state->mode.hdisplay; |
| config->src.h = crtc_state->mode.vdisplay; |
| config->src.f_w = fb->width; |
| config->src.f_h = fb->height; |
| |
| config->comp_type = COMP_TYPE_NONE; |
| |
| config->format = fb->format->format; |
| config->standard = state->standard; |
| config->range = state->range; |
| config->y_hd_y2_stride = 0; |
| config->y_pl_c2_stride = 0; |
| config->c_hd_stride = 0; |
| config->c_pl_stride = 0; |
| |
| config->addr[0] = exynos_drm_fb_dma_addr(fb, 0); |
| config->addr[1] = exynos_drm_fb_dma_addr(fb, 1); |
| config->addr[2] = exynos_drm_fb_dma_addr(fb, 2); |
| config->addr[3] = exynos_drm_fb_dma_addr(fb, 3); |
| |
| /* TODO: blocking mode will be implemented later */ |
| config->is_block = false; |
| /* TODO: very big count.. recovery will be not working... */ |
| config->rcv_num = 0x7FFFFFFF; |
| |
| pr_debug("%s -\n", __func__); |
| } |
| |
| static int writeback_atomic_check(struct drm_encoder *encoder, |
| struct drm_crtc_state *crtc_state, |
| struct drm_connector_state *conn_state) |
| { |
| const struct drm_framebuffer *fb; |
| int i; |
| |
| conn_state->self_refresh_aware = true; |
| |
| if (!wb_check_job(conn_state)) |
| return 0; |
| |
| fb = conn_state->writeback_job->fb; |
| |
| for (i = 0; i < ARRAY_SIZE(writeback_formats); i++) |
| if (fb->format->format == writeback_formats[i]) |
| break; |
| |
| if (i == ARRAY_SIZE(writeback_formats)) |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| static void writeback_atomic_commit(struct drm_connector *connector, |
| struct drm_connector_state *state) |
| { |
| struct drm_writeback_connector *wb_conn = conn_to_wb_conn(connector); |
| struct writeback_device *wb = conn_to_wb_dev(connector); |
| struct dpp_params_info *config = &wb->win_config; |
| |
| pr_debug("%s +\n", __func__); |
| |
| if (wb->state == WB_STATE_OFF) { |
| pr_info("writeback(dpp%d) disabled(%d)\n", wb->id, wb->state); |
| return; |
| } |
| |
| wb_convert_connector_state_to_config(config, to_exynos_wb_state(state)); |
| dpp_reg_configure_params(wb->id, config, wb->attr); |
| drm_writeback_queue_job(wb_conn, state); |
| |
| DPU_EVENT_LOG(DPU_EVT_WB_ATOMIC_COMMIT, wb->decon_id, wb); |
| |
| pr_debug("%s -\n", __func__); |
| } |
| |
| static const struct drm_connector_helper_funcs wb_connector_helper_funcs = { |
| .get_modes = writeback_get_modes, |
| .atomic_commit = writeback_atomic_commit, |
| }; |
| |
| /* TODO: check the func */ |
| static struct drm_connector_state * |
| exynos_drm_writeback_duplicate_state(struct drm_connector *connector) |
| { |
| struct exynos_drm_writeback_state *exynos_state; |
| struct exynos_drm_writeback_state *copy; |
| |
| pr_debug("%s +\n", __func__); |
| |
| exynos_state = to_exynos_wb_state(connector->state); |
| copy = kzalloc(sizeof(*exynos_state), GFP_KERNEL); |
| if (!copy) |
| return NULL; |
| |
| memcpy(copy, exynos_state, sizeof(*exynos_state)); |
| __drm_atomic_helper_connector_duplicate_state(connector, ©->base); |
| |
| pr_debug("%s -\n", __func__); |
| |
| return ©->base; |
| } |
| |
| static void exynos_drm_writeback_destroy_state(struct drm_connector *connector, |
| struct drm_connector_state *old_state) |
| { |
| struct exynos_drm_writeback_state *old_exynos_state = |
| to_exynos_wb_state(old_state); |
| |
| pr_debug("%s +\n", __func__); |
| |
| __drm_atomic_helper_connector_destroy_state(old_state); |
| kfree(old_exynos_state); |
| |
| pr_debug("%s -\n", __func__); |
| } |
| |
| static void exynos_drm_writeback_reset(struct drm_connector *connector) |
| { |
| struct exynos_drm_writeback_state *exynos_state; |
| |
| pr_debug("%s +\n", __func__); |
| |
| if (connector->state) { |
| exynos_drm_writeback_destroy_state(connector, connector->state); |
| connector->state = NULL; |
| } |
| |
| exynos_state = kzalloc(sizeof(*exynos_state), GFP_KERNEL); |
| if (exynos_state) { |
| connector->state = &exynos_state->base; |
| connector->state->connector = connector; |
| /* |
| * TODO: If it needs to initialize the specific property value, |
| * please add to here. |
| */ |
| } |
| |
| pr_debug("%s -\n", __func__); |
| } |
| |
| static void exynos_drm_writeback_destroy(struct drm_connector *connector) |
| { |
| struct writeback_device *wb = conn_to_wb_dev(connector); |
| |
| drm_connector_cleanup(connector); |
| kfree(wb); |
| } |
| |
| static int exynos_drm_writeback_set_property(struct drm_connector *connector, |
| struct drm_connector_state *state, |
| struct drm_property *property, uint64_t val) |
| { |
| struct writeback_device *wb = conn_to_wb_dev(connector); |
| struct exynos_drm_writeback_state *exynos_state = |
| to_exynos_wb_state(state); |
| |
| if (property == wb->props.standard) |
| exynos_state->standard = val; |
| else if (property == wb->props.range) |
| exynos_state->range = val; |
| else |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| static int exynos_drm_writeback_get_property(struct drm_connector *connector, |
| const struct drm_connector_state *state, |
| struct drm_property *property, uint64_t *val) |
| { |
| struct writeback_device *wb = conn_to_wb_dev(connector); |
| struct exynos_drm_writeback_state *exynos_state = |
| to_exynos_wb_state(state); |
| |
| if (property == wb->props.restriction) |
| *val = exynos_state->blob_id_restriction; |
| else if (property == wb->props.standard) |
| *val = exynos_state->standard; |
| else if (property == wb->props.range) |
| *val = exynos_state->range; |
| else |
| return -EINVAL; |
| |
| return 0; |
| |
| } |
| |
| static const struct drm_connector_funcs wb_connector_funcs = { |
| .reset = exynos_drm_writeback_reset, |
| .fill_modes = drm_helper_probe_single_connector_modes, |
| .destroy = exynos_drm_writeback_destroy, |
| .atomic_duplicate_state = exynos_drm_writeback_duplicate_state, |
| .atomic_destroy_state = exynos_drm_writeback_destroy_state, |
| .atomic_set_property = exynos_drm_writeback_set_property, |
| .atomic_get_property = exynos_drm_writeback_get_property, |
| }; |
| |
| static void _writeback_enable(struct writeback_device *wb) |
| { |
| dpp_reg_init(wb->id, wb->attr); |
| enable_irq(wb->odma_irq); |
| } |
| |
| static void writeback_enable(struct drm_encoder *encoder) |
| { |
| struct writeback_device *wb = enc_to_wb_dev(encoder); |
| const struct decon_device *decon; |
| |
| pr_debug("%s +\n", __func__); |
| |
| if (wb->state == WB_STATE_ON) { |
| pr_info("wb(%d) already enabled(%d)\n", wb->id, wb->state); |
| return; |
| } |
| |
| _writeback_enable(wb); |
| decon = wb_get_decon(wb); |
| wb->decon_id = decon->id; |
| wb->state = WB_STATE_ON; |
| DPU_EVENT_LOG(DPU_EVT_WB_ENABLE, wb->decon_id, wb); |
| |
| pr_debug("%s -\n", __func__); |
| } |
| |
| void writeback_exit_hibernation(struct writeback_device *wb) |
| { |
| if (wb->state != WB_STATE_HIBERNATION) |
| return; |
| |
| _writeback_enable(wb); |
| wb->state = WB_STATE_ON; |
| DPU_EVENT_LOG(DPU_EVT_WB_EXIT_HIBERNATION, wb->decon_id, wb); |
| } |
| |
| static void _writeback_disable(struct writeback_device *wb) |
| { |
| disable_irq(wb->odma_irq); |
| dpp_reg_deinit(wb->id, false, wb->attr); |
| } |
| |
| static void writeback_disable(struct drm_encoder *encoder) |
| { |
| struct writeback_device *wb = enc_to_wb_dev(encoder); |
| |
| pr_debug("%s +\n", __func__); |
| |
| if (wb->state == WB_STATE_OFF) { |
| pr_info("writeback(dpp%d) already disabled(%d)\n", |
| wb->id, wb->state); |
| return; |
| } |
| |
| _writeback_disable(wb); |
| wb->state = WB_STATE_OFF; |
| DPU_EVENT_LOG(DPU_EVT_WB_DISABLE, wb->decon_id, wb); |
| |
| wb->decon_id = -1; |
| |
| pr_debug("%s -\n", __func__); |
| } |
| |
| void writeback_enter_hibernation(struct writeback_device *wb) |
| { |
| if (wb->state != WB_STATE_ON) |
| return; |
| |
| _writeback_disable(wb); |
| wb->state = WB_STATE_HIBERNATION; |
| DPU_EVENT_LOG(DPU_EVT_WB_ENTER_HIBERNATION, wb->decon_id, wb); |
| } |
| |
| static const struct drm_encoder_helper_funcs wb_encoder_helper_funcs = { |
| .enable = writeback_enable, |
| .disable = writeback_disable, |
| .atomic_check = writeback_atomic_check, |
| }; |
| |
| /* TODO : modify create property because same property is created at plane */ |
| static int |
| exynos_drm_wb_conn_create_standard_property(struct drm_connector *connector) |
| { |
| struct writeback_device *wb = conn_to_wb_dev(connector); |
| struct drm_property *prop; |
| |
| static const struct drm_prop_enum_list standard_list[] = { |
| { EXYNOS_STANDARD_UNSPECIFIED, "Unspecified" }, |
| { EXYNOS_STANDARD_BT709, "BT709" }, |
| { EXYNOS_STANDARD_BT601_625, "BT601_625" }, |
| { EXYNOS_STANDARD_BT601_625_UNADJUSTED, "BT601_625_UNADJUSTED"}, |
| { EXYNOS_STANDARD_BT601_525, "BT601_525" }, |
| { EXYNOS_STANDARD_BT601_525_UNADJUSTED, "BT601_525_UNADJUSTED"}, |
| { EXYNOS_STANDARD_BT2020, "BT2020" }, |
| { EXYNOS_STANDARD_BT2020_CONSTANT_LUMINANCE, |
| "BT2020_CONSTANT_LUMINANCE"}, |
| { EXYNOS_STANDARD_BT470M, "BT470M" }, |
| { EXYNOS_STANDARD_FILM, "FILM" }, |
| { EXYNOS_STANDARD_DCI_P3, "DCI-P3" }, |
| { EXYNOS_STANDARD_ADOBE_RGB, "Adobe RGB" }, |
| }; |
| |
| prop = drm_property_create_enum(connector->dev, 0, "standard", |
| standard_list, ARRAY_SIZE(standard_list)); |
| if (!prop) |
| return -ENOMEM; |
| |
| drm_object_attach_property(&connector->base, prop, |
| EXYNOS_STANDARD_UNSPECIFIED); |
| wb->props.standard = prop; |
| |
| return 0; |
| } |
| |
| /* TODO : modify create property because same property is created at plane */ |
| static int |
| exynos_drm_wb_conn_create_range_property(struct drm_connector *connector) |
| { |
| struct writeback_device *wb = conn_to_wb_dev(connector); |
| struct drm_property *prop; |
| static const struct drm_prop_enum_list range_list[] = { |
| { EXYNOS_RANGE_UNSPECIFIED, "Unspecified" }, |
| { EXYNOS_RANGE_FULL, "Full" }, |
| { EXYNOS_RANGE_LIMITED, "Limited" }, |
| { EXYNOS_RANGE_EXTENDED, "Extended" }, |
| }; |
| |
| prop = drm_property_create_enum(connector->dev, 0, "range", range_list, |
| ARRAY_SIZE(range_list)); |
| if (!prop) |
| return -ENOMEM; |
| |
| drm_object_attach_property(&connector->base, prop, |
| EXYNOS_RANGE_UNSPECIFIED); |
| wb->props.range = prop; |
| |
| return 0; |
| } |
| |
| /* TODO : modify create property because same property is created at plane */ |
| static int |
| exynos_drm_wb_conn_create_restriction_property(struct drm_connector *connector) |
| { |
| struct writeback_device *wb = conn_to_wb_dev(connector); |
| struct drm_property *prop; |
| struct drm_property_blob *blob; |
| struct dpp_ch_restriction res; |
| |
| memcpy(&res.restriction, &wb->restriction, |
| sizeof(struct dpp_restriction)); |
| res.id = wb->id; |
| res.attr = wb->attr; |
| |
| blob = drm_property_create_blob(connector->dev, sizeof(res), &res); |
| if (IS_ERR(blob)) |
| return PTR_ERR(blob); |
| |
| prop = drm_property_create(connector->dev, |
| DRM_MODE_PROP_IMMUTABLE | DRM_MODE_PROP_BLOB, |
| "hw restrictions", 0); |
| if (!prop) |
| return -ENOMEM; |
| |
| drm_object_attach_property(&connector->base, prop, blob->base.id); |
| wb->props.restriction = prop; |
| |
| return 0; |
| } |
| |
| static int writeback_bind(struct device *dev, struct device *master, void *data) |
| { |
| struct writeback_device *wb = dev_get_drvdata(dev); |
| struct drm_device *drm_dev = data; |
| struct drm_connector *connector = &wb->writeback.base; |
| int ret; |
| |
| pr_info("%s +\n", __func__); |
| |
| drm_connector_helper_add(connector, &wb_connector_helper_funcs); |
| ret = drm_writeback_connector_init(drm_dev, &wb->writeback, |
| &wb_connector_funcs, &wb_encoder_helper_funcs, |
| wb->pixel_formats, wb->num_pixel_formats); |
| if (ret) { |
| pr_err("%s: failed to init writeback connector\n", __func__); |
| return ret; |
| } |
| |
| exynos_drm_wb_conn_create_standard_property(connector); |
| exynos_drm_wb_conn_create_range_property(connector); |
| exynos_drm_wb_conn_create_restriction_property(connector); |
| |
| pr_info("%s -\n", __func__); |
| |
| return 0; |
| } |
| |
| static void |
| writeback_unbind(struct device *dev, struct device *master, void *data) |
| { |
| pr_debug("%s +\n", __func__); |
| pr_debug("%s -\n", __func__); |
| } |
| |
| static const struct component_ops exynos_wb_component_ops = { |
| .bind = writeback_bind, |
| .unbind = writeback_unbind, |
| }; |
| |
| /* TODO : can be replaced by dpp_print_restriction */ |
| static void wb_print_restriction(struct writeback_device *wb) |
| { |
| struct dpp_restriction *res = &wb->restriction; |
| |
| pr_info("src_f_w[%d %d %d] src_f_h[%d %d %d]\n", |
| res->src_f_w.min, res->src_f_w.max, res->src_f_w.align, |
| res->src_f_h.min, res->src_f_h.max, res->src_f_h.align); |
| pr_info("src_w[%d %d %d] src_h[%d %d %d] src_x_y_align[%d %d]\n", |
| res->src_w.min, res->src_w.max, res->src_w.align, |
| res->src_h.min, res->src_h.max, res->src_h.align, |
| res->src_x_align, res->src_y_align); |
| |
| pr_info("dst_f_w[%d %d %d] dst_f_h[%d %d %d]\n", |
| res->dst_f_w.min, res->dst_f_w.max, res->dst_f_w.align, |
| res->dst_f_h.min, res->dst_f_h.max, res->dst_f_h.align); |
| pr_info("dst_w[%d %d %d] dst_h[%d %d %d] dst_x_y_align[%d %d]\n", |
| res->dst_w.min, res->dst_w.max, res->dst_w.align, |
| res->dst_h.min, res->dst_h.max, res->dst_h.align, |
| res->dst_x_align, res->dst_y_align); |
| |
| pr_info("blk_w[%d %d %d] blk_h[%d %d %d] blk_x_y_align[%d %d]\n", |
| res->blk_w.min, res->blk_w.max, res->blk_w.align, |
| res->blk_h.min, res->blk_h.max, res->blk_h.align, |
| res->blk_x_align, res->blk_y_align); |
| |
| pr_info("src_h_rot_max[%d]\n", res->src_h_rot_max); |
| |
| pr_info("max scale up(%dx), down(1/%dx) ratio\n", res->scale_up, |
| res->scale_down); |
| } |
| |
| static int exynos_wb_parse_dt(struct writeback_device *wb, |
| struct device_node *np) |
| { |
| int ret; |
| struct dpp_restriction *res = &wb->restriction; |
| |
| pr_debug("%s +\n", __func__); |
| |
| ret = of_property_read_u32(np, "dpp,id", &wb->id); |
| if (ret) |
| return ret; |
| |
| of_property_read_u32(np, "attr", (u32 *)&wb->attr); |
| of_property_read_u32(np, "port", &wb->port); |
| |
| wb->pixel_formats = writeback_formats; |
| wb->num_pixel_formats = ARRAY_SIZE(writeback_formats); |
| |
| of_property_read_u32(np, "scale_down", (u32 *)&res->scale_down); |
| of_property_read_u32(np, "scale_up", (u32 *)&res->scale_up); |
| |
| pr_info("attr(0x%lx), port(%d)\n", wb->attr, wb->port); |
| |
| wb_print_restriction(wb); |
| |
| pr_debug("%s -\n", __func__); |
| |
| return ret; |
| } |
| |
| static irqreturn_t odma_irq_handler(int irq, void *priv) |
| { |
| struct writeback_device *wb = priv; |
| u32 irqs; |
| |
| spin_lock(&wb->odma_slock); |
| if (wb->state == WB_STATE_OFF) |
| goto irq_end; |
| |
| irqs = odma_reg_get_irq_and_clear(wb->id); |
| |
| if (irqs & ODMA_STATUS_FRAMEDONE_IRQ || irqs & ODMA_INST_OFF_DONE_IRQ) { |
| if (irqs & ODMA_STATUS_FRAMEDONE_IRQ) |
| pr_debug("wb(%d) framedone irq occurs\n", wb->id); |
| else |
| pr_warn("wb(%d) instant off irq occurs\n", wb->id); |
| |
| if (wb_is_cwb(wb)) |
| decon_reg_set_cwb_enable(wb->decon_id, false); |
| |
| drm_writeback_signal_completion(&wb->writeback, 0); |
| DPU_EVENT_LOG(DPU_EVT_WB_FRAMEDONE, wb->decon_id, wb); |
| } |
| |
| irq_end: |
| spin_unlock(&wb->odma_slock); |
| return IRQ_HANDLED; |
| } |
| |
| static int wb_init_resources(struct writeback_device *wb) |
| { |
| struct resource res; |
| int ret = 0; |
| struct device *dev = wb->dev; |
| struct device_node *np = dev->of_node; |
| struct platform_device *pdev; |
| int i; |
| |
| pr_debug("%s +\n", __func__); |
| |
| pdev = container_of(dev, struct platform_device, dev); |
| |
| i = of_property_match_string(np, "reg-names", "dma"); |
| if (of_address_to_resource(np, i, &res)) { |
| pr_err("failed to get dma resource\n"); |
| return -EINVAL; |
| } |
| wb->regs.dma_base_regs = ioremap(res.start, resource_size(&res)); |
| if (!wb->regs.dma_base_regs) { |
| pr_err("failed to remap DPU_DMA SFR region\n"); |
| return -EINVAL; |
| } |
| dpp_regs_desc_init(wb->regs.dma_base_regs, res.start, "dma", REGS_DMA, wb->id); |
| |
| wb->odma_irq = of_irq_get_byname(np, "dma"); |
| pr_info("dma irq no = %d\n", wb->odma_irq); |
| ret = devm_request_irq(dev, wb->odma_irq, odma_irq_handler, 0, |
| pdev->name, wb); |
| if (ret) { |
| pr_err("failed to install DPU DMA irq\n"); |
| return -EINVAL; |
| } |
| disable_irq(wb->odma_irq); |
| |
| if (test_bit(DPP_ATTR_DPP, &wb->attr)) { |
| i = of_property_match_string(np, "reg-names", "dpp"); |
| if (of_address_to_resource(np, i, &res)) { |
| pr_err("failed to get dpp resource\n"); |
| return -EINVAL; |
| } |
| wb->regs.dpp_base_regs = ioremap(res.start, resource_size(&res)); |
| if (!wb->regs.dpp_base_regs) { |
| pr_err("failed to remap DPP SFR region\n"); |
| return -EINVAL; |
| } |
| dpp_regs_desc_init(wb->regs.dpp_base_regs, res.start, "dpp", REGS_DPP, |
| wb->id); |
| } |
| |
| pr_debug("%s -\n", __func__); |
| |
| return ret; |
| } |
| |
| static int writeback_probe(struct platform_device *pdev) |
| { |
| int ret = 0; |
| struct device *dev = &pdev->dev; |
| struct writeback_device *writeback; |
| const struct dpp_restriction *restriction; |
| |
| writeback = devm_kzalloc(dev, sizeof(struct writeback_device), |
| GFP_KERNEL); |
| if (!writeback) |
| return -ENOMEM; |
| |
| restriction = of_device_get_match_data(dev); |
| memcpy(&writeback->restriction, restriction, sizeof(*restriction)); |
| |
| writeback->dev = dev; |
| ret = exynos_wb_parse_dt(writeback, dev->of_node); |
| if (ret) |
| goto fail; |
| |
| writeback->output_type = EXYNOS_DISPLAY_TYPE_VIDI; |
| |
| spin_lock_init(&writeback->odma_slock); |
| |
| writeback->state = WB_STATE_OFF; |
| |
| ret = wb_init_resources(writeback); |
| if (ret) |
| goto fail; |
| |
| /* dpp is not connected decon now */ |
| writeback->decon_id = -1; |
| |
| platform_set_drvdata(pdev, writeback); |
| |
| pr_info("writeback(dpp%d) successfully probe", writeback->id); |
| |
| ret = component_add(dev, &exynos_wb_component_ops); |
| if (ret) |
| goto fail; |
| |
| return ret; |
| |
| fail: |
| pr_err("writeback(dpp%d) probe failed", writeback->id); |
| return ret; |
| } |
| |
| static int writeback_remove(struct platform_device *pdev) |
| { |
| struct writeback_device *wb = platform_get_drvdata(pdev); |
| |
| component_del(&pdev->dev, &exynos_wb_component_ops); |
| |
| if (test_bit(DPP_ATTR_DPP, &wb->attr)) |
| iounmap(wb->regs.dpp_base_regs); |
| iounmap(wb->regs.dma_base_regs); |
| |
| return 0; |
| } |
| |
| struct platform_driver writeback_driver = { |
| .probe = writeback_probe, |
| .remove = writeback_remove, |
| .driver = { |
| .name = "exynos-writeback", |
| .owner = THIS_MODULE, |
| .of_match_table = wb_of_match, |
| }, |
| }; |
| |
| MODULE_AUTHOR("Wonyeong Choi <won0.choi@samsung.com>"); |
| MODULE_DESCRIPTION("Samsung SoC Display WriteBack"); |
| MODULE_LICENSE("GPL v2"); |