| /* Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #include "sde_hw_mdss.h" |
| #include "sde_hwio.h" |
| #include "sde_hw_catalog.h" |
| #include "sde_hw_pingpong.h" |
| |
| #define PP_TEAR_CHECK_EN 0x000 |
| #define PP_SYNC_CONFIG_VSYNC 0x004 |
| #define PP_SYNC_CONFIG_HEIGHT 0x008 |
| #define PP_SYNC_WRCOUNT 0x00C |
| #define PP_VSYNC_INIT_VAL 0x010 |
| #define PP_INT_COUNT_VAL 0x014 |
| #define PP_SYNC_THRESH 0x018 |
| #define PP_START_POS 0x01C |
| #define PP_RD_PTR_IRQ 0x020 |
| #define PP_WR_PTR_IRQ 0x024 |
| #define PP_OUT_LINE_COUNT 0x028 |
| #define PP_LINE_COUNT 0x02C |
| #define PP_AUTOREFRESH_CONFIG 0x030 |
| |
| #define PP_FBC_MODE 0x034 |
| #define PP_FBC_BUDGET_CTL 0x038 |
| #define PP_FBC_LOSSY_MODE 0x03C |
| #define PP_DSC_MODE 0x0a0 |
| #define PP_DCE_DATA_IN_SWAP 0x0ac |
| #define PP_DCE_DATA_OUT_SWAP 0x0c8 |
| |
| static struct sde_pingpong_cfg *_pingpong_offset(enum sde_pingpong pp, |
| struct sde_mdss_cfg *m, |
| void __iomem *addr, |
| struct sde_hw_blk_reg_map *b) |
| { |
| int i; |
| |
| for (i = 0; i < m->pingpong_count; i++) { |
| if (pp == m->pingpong[i].id) { |
| b->base_off = addr; |
| b->blk_off = m->pingpong[i].base; |
| b->hwversion = m->hwversion; |
| b->log_mask = SDE_DBG_MASK_PINGPONG; |
| return &m->pingpong[i]; |
| } |
| } |
| |
| return ERR_PTR(-EINVAL); |
| } |
| |
| static int sde_hw_pp_setup_te_config(struct sde_hw_pingpong *pp, |
| struct sde_hw_tear_check *te) |
| { |
| struct sde_hw_blk_reg_map *c = &pp->hw; |
| int cfg; |
| |
| cfg = BIT(19); /*VSYNC_COUNTER_EN */ |
| if (te->hw_vsync_mode) |
| cfg |= BIT(20); |
| |
| cfg |= te->vsync_count; |
| |
| SDE_REG_WRITE(c, PP_SYNC_CONFIG_VSYNC, cfg); |
| SDE_REG_WRITE(c, PP_SYNC_CONFIG_HEIGHT, te->sync_cfg_height); |
| SDE_REG_WRITE(c, PP_VSYNC_INIT_VAL, te->vsync_init_val); |
| SDE_REG_WRITE(c, PP_RD_PTR_IRQ, te->rd_ptr_irq); |
| SDE_REG_WRITE(c, PP_START_POS, te->start_pos); |
| SDE_REG_WRITE(c, PP_SYNC_THRESH, |
| ((te->sync_threshold_continue << 16) | |
| te->sync_threshold_start)); |
| SDE_REG_WRITE(c, PP_SYNC_WRCOUNT, |
| (te->start_pos + te->sync_threshold_start + 1)); |
| |
| return 0; |
| } |
| |
| int sde_hw_pp_setup_autorefresh_config(struct sde_hw_pingpong *pp, |
| struct sde_hw_autorefresh *cfg) |
| { |
| struct sde_hw_blk_reg_map *c = &pp->hw; |
| u32 refresh_cfg; |
| |
| if (cfg->enable) |
| refresh_cfg = BIT(31) | cfg->frame_count; |
| else |
| refresh_cfg = 0; |
| |
| SDE_REG_WRITE(c, PP_AUTOREFRESH_CONFIG, |
| refresh_cfg); |
| |
| return 0; |
| } |
| |
| int sde_hw_pp_setup_dsc_compression(struct sde_hw_pingpong *pp, |
| struct sde_hw_dsc_cfg *cfg) |
| { |
| return 0; |
| } |
| int sde_hw_pp_enable_te(struct sde_hw_pingpong *pp, bool enable) |
| { |
| struct sde_hw_blk_reg_map *c = &pp->hw; |
| |
| SDE_REG_WRITE(c, PP_TEAR_CHECK_EN, enable); |
| return 0; |
| } |
| |
| int sde_hw_pp_get_vsync_info(struct sde_hw_pingpong *pp, |
| struct sde_hw_pp_vsync_info *info) |
| { |
| struct sde_hw_blk_reg_map *c = &pp->hw; |
| u32 val; |
| |
| val = SDE_REG_READ(c, PP_VSYNC_INIT_VAL); |
| info->init_val = val & 0xffff; |
| |
| val = SDE_REG_READ(c, PP_INT_COUNT_VAL); |
| info->vsync_count = (val & 0xffff0000) >> 16; |
| info->line_count = val & 0xffff; |
| |
| return 0; |
| } |
| |
| static void _setup_pingpong_ops(struct sde_hw_pingpong_ops *ops, |
| unsigned long cap) |
| { |
| ops->setup_tearcheck = sde_hw_pp_setup_te_config; |
| ops->enable_tearcheck = sde_hw_pp_enable_te; |
| ops->get_vsync_info = sde_hw_pp_get_vsync_info; |
| ops->setup_autorefresh = sde_hw_pp_setup_autorefresh_config; |
| ops->setup_dsc = sde_hw_pp_setup_dsc_compression; |
| }; |
| |
| struct sde_hw_pingpong *sde_hw_pingpong_init(enum sde_pingpong idx, |
| void __iomem *addr, |
| struct sde_mdss_cfg *m) |
| { |
| struct sde_hw_pingpong *c; |
| struct sde_pingpong_cfg *cfg; |
| |
| c = kzalloc(sizeof(*c), GFP_KERNEL); |
| if (!c) |
| return ERR_PTR(-ENOMEM); |
| |
| cfg = _pingpong_offset(idx, m, addr, &c->hw); |
| if (IS_ERR_OR_NULL(cfg)) { |
| kfree(c); |
| return ERR_PTR(-EINVAL); |
| } |
| |
| c->idx = idx; |
| c->pingpong_hw_cap = cfg; |
| _setup_pingpong_ops(&c->ops, c->pingpong_hw_cap->features); |
| |
| return c; |
| } |
| |
| void sde_hw_pingpong_destroy(struct sde_hw_pingpong *pp) |
| { |
| kfree(pp); |
| } |