| /* |
| * drivers/video/tegra/dc/nvsr.c |
| * |
| * Copyright (c) 2014, NVIDIA CORPORATION, All rights reserved. |
| * |
| * This software is licensed under the terms of the GNU General Public |
| * License version 2, as published by the Free Software Foundation, and |
| * may be copied, distributed, and modified under those terms. |
| * |
| * 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 <linux/debugfs.h> |
| #include <linux/delay.h> |
| #include <linux/gpio.h> |
| #include <linux/uaccess.h> |
| #include "nvsr.h" |
| #include "nvsr_regs.h" |
| #include "dpaux_regs.h" |
| |
| #define HIMAX 1 |
| |
| #define NVSR_ERR(...) dev_err(&nvsr->dc->ndev->dev, "NVSR: " __VA_ARGS__); |
| #define NVSR_WARN(...) dev_warn(&nvsr->dc->ndev->dev, "NVSR: " __VA_ARGS__); |
| #define NVSR_INFO(...) dev_info(&nvsr->dc->ndev->dev, "NVSR: " __VA_ARGS__); |
| |
| #define NVSR_RET(ret, ...) \ |
| { \ |
| if (ret) { \ |
| NVSR_ERR(__VA_ARGS__); \ |
| return; \ |
| } \ |
| } |
| |
| #define NVSR_RETV(ret, ...) \ |
| { \ |
| if (ret) { \ |
| NVSR_ERR(__VA_ARGS__); \ |
| return ret; \ |
| } \ |
| } |
| |
| /* Wait for 3 frames max to enter/exit */ |
| #define SR_ENTRY_MAX_TRIES 3 |
| #define SR_EXIT_MAX_TRIES 3 |
| |
| static void tegra_dc_nvsr_get(struct tegra_dc_nvsr_data *nvsr) |
| { |
| tegra_dc_get(nvsr->dc); |
| clk_prepare_enable(nvsr->out_clk); |
| } |
| |
| static void tegra_dc_nvsr_put(struct tegra_dc_nvsr_data *nvsr) |
| { |
| clk_disable_unprepare(nvsr->out_clk); |
| tegra_dc_put(nvsr->dc); |
| } |
| |
| static int tegra_dc_nvsr_enter_sr(struct tegra_dc_nvsr_data *nvsr) |
| { |
| u32 val; |
| int ret; |
| u8 tries = 0; |
| u32 delay = nvsr->sr_timing_frame_time_us / 1000; |
| |
| /* Set entry via sideband */ |
| val = NVSR_SRC_CTL1_SIDEBAND_ENTRY_SEL_YES; |
| val |= NVSR_SRC_CTL1_SIDEBAND_ENTRY_MASK_ENABLE; |
| ret = nvsr->reg_ops.write(nvsr, NVSR_SRC_CTL1, 1, val); |
| NVSR_RETV(ret, "Failed to enter SR via sideband\n"); |
| /* Enable SR */ |
| val = NVSR_SRC_CTL0_SR_ENABLE_CTL_ENABLED; |
| val |= NVSR_SRC_CTL0_SR_ENABLE_MASK_ENABLE; |
| /* Request SR entry */ |
| val |= NVSR_SRC_CTL0_SR_ENTRY_REQ_YES; |
| val |= NVSR_SRC_CTL0_SR_ENTRY_MASK_ENABLE; |
| ret = nvsr->reg_ops.write(nvsr, NVSR_SRC_CTL0, 1, val); |
| NVSR_RETV(ret, "Failed to request SR entry\n"); |
| |
| /* TODO: Polling is for NVSR bringup only. |
| * Need to implement IRQ */ |
| do { |
| ret = nvsr->reg_ops.read(nvsr, NVSR_STATUS, 1, &val); |
| NVSR_RETV(ret, "Failed to read status register.\n"); |
| |
| if ((val & NVSR_STATUS_SR_STATE_MASK) |
| == NVSR_STATUS_SR_STATE_SR_ACTIVE) { |
| nvsr->sr_active = true; |
| return 0; |
| } |
| |
| msleep(delay); |
| } while (++tries < SR_ENTRY_MAX_TRIES); |
| |
| NVSR_ERR("Timed out while waiting for SR entry\n"); |
| return -ETIMEDOUT; |
| } |
| |
| static int tegra_dc_nvsr_exit_sr(struct tegra_dc_nvsr_data *nvsr) |
| { |
| u32 val; |
| int ret; |
| u8 tries = 0; |
| /* Poll every quarter frame in ms */ |
| u32 delay = nvsr->sr_timing_frame_time_us / 1000; |
| |
| switch (nvsr->resync_method) { |
| case NVSR_RESYNC_CTL_METHOD_FL: |
| val = NVSR_RESYNC_CTL_METHOD_FL; |
| val |= nvsr->resync_delay << NVSR_RESYNC_CTL_DELAY_SHIFT; |
| break; |
| case NVSR_RESYNC_CTL_METHOD_SS: |
| case NVSR_RESYNC_CTL_METHOD_BS: |
| NVSR_WARN("Sliding Sync and Blank Stretching resync methods not supported; defaulting to Immediate\n"); |
| case NVSR_RESYNC_CTL_METHOD_IMM: |
| default: |
| val = NVSR_RESYNC_CTL_METHOD_IMM; |
| break; |
| } |
| |
| ret = nvsr->reg_ops.write(nvsr, NVSR_RESYNC_CTL, 1, val); |
| NVSR_RETV(ret, "Couldn't write resync method, aborting SR exit\n"); |
| |
| val = NVSR_SRC_CTL1_SIDEBAND_EXIT_SEL_YES; |
| val |= NVSR_SRC_CTL1_SIDEBAND_EXIT_MASK_ENABLE; |
| ret = nvsr->reg_ops.write(nvsr, NVSR_SRC_CTL1, 1, val); |
| NVSR_RETV(ret, "Couldn't select sideband exit, aborting SR exit\n"); |
| |
| /* Request SR exit */ |
| val = NVSR_SRC_CTL0_SR_ENABLE_MASK_ENABLE; |
| val |= NVSR_SRC_CTL0_SR_ENABLE_CTL_ENABLED; |
| val |= NVSR_SRC_CTL0_SR_EXIT_REQ_YES; |
| val |= NVSR_SRC_CTL0_SR_EXIT_MASK_ENABLE; |
| ret = nvsr->reg_ops.write(nvsr, NVSR_SRC_CTL0, 1, val); |
| NVSR_RETV(ret, "Couldn't request SR exit, aborting SR exit\n"); |
| |
| /* TODO: Polling is for NVSR bringup only. |
| * Need to implement IRQ */ |
| do { |
| ret = nvsr->reg_ops.read(nvsr, NVSR_STATUS, 1, &val); |
| NVSR_RETV(ret, "Failed to read status register.\n"); |
| |
| if ((val & NVSR_STATUS_SR_STATE_MASK) |
| == NVSR_STATUS_SR_STATE_IDLE) { |
| nvsr->sr_active = false; |
| return 0; |
| } |
| |
| msleep(delay); |
| } while (++tries < SR_EXIT_MAX_TRIES); |
| |
| return -ETIMEDOUT; |
| } |
| |
| static int tegra_dc_nvsr_src_power_on(struct tegra_dc_nvsr_data *nvsr) |
| { |
| int ret; |
| u32 reg_val = 0; |
| u8 tries = 0; |
| |
| ret = nvsr->reg_ops.read(nvsr, NVSR_STATUS, 1, ®_val); |
| if (ret) { |
| NVSR_ERR("Failed to read status register.\n"); |
| return ret; |
| } |
| |
| /* If SRC is on, we're done */ |
| if ((reg_val & NVSR_STATUS_SR_STATE_MASK) |
| == NVSR_STATUS_SR_STATE_IDLE) |
| return 0; |
| |
| reg_val = NVSR_SRC_CTL0_SR_ENABLE_CTL_ENABLED; |
| reg_val |= NVSR_SRC_CTL0_SR_ENABLE_MASK_ENABLE; |
| ret = nvsr->reg_ops.write(nvsr, NVSR_SRC_CTL0, 1, reg_val); |
| NVSR_RETV(ret, "Failed to write to control register.\n"); |
| |
| /* TODO: Polling is for NVSR bringup only. |
| * Need to implement IRQ */ |
| do { |
| ret = nvsr->reg_ops.read(nvsr, NVSR_STATUS, 1, ®_val); |
| NVSR_RETV(ret, "Failed to read status register.\n"); |
| |
| if ((reg_val & NVSR_STATUS_SR_STATE_MASK) |
| == NVSR_STATUS_SR_STATE_IDLE) { |
| nvsr->src_on = true; |
| return 0; |
| } |
| |
| msleep(nvsr->sr_timing_frame_time_us/1000); |
| } while (tries++ < 3); |
| |
| NVSR_ERR("Failed to power on the SRC.\n"); |
| return -ETIMEDOUT; |
| } |
| |
| static int tegra_dc_nvsr_src_power_off(struct tegra_dc_nvsr_data *nvsr) |
| { |
| int ret; |
| u32 reg_val = 0; |
| u8 tries = 0; |
| |
| ret = nvsr->reg_ops.read(nvsr, NVSR_STATUS, 1, ®_val); |
| if (ret) { |
| NVSR_ERR("Failed to read status register.\n"); |
| return ret; |
| } |
| |
| if ((reg_val & NVSR_STATUS_SR_STATE_MASK) |
| == NVSR_STATUS_SR_STATE_OFFLINE) |
| return 0; |
| |
| reg_val = NVSR_SRC_CTL0_SR_ENABLE_CTL_DISABLED; |
| reg_val |= NVSR_SRC_CTL0_SR_ENABLE_MASK_ENABLE; |
| ret = nvsr->reg_ops.write(nvsr, NVSR_SRC_CTL0, 1, reg_val); |
| NVSR_RETV(ret, "Failed to write to control register.\n"); |
| |
| /* TODO: Polling is for NVSR bringup only. |
| * Need to implement IRQ */ |
| do { |
| ret = nvsr->reg_ops.read(nvsr, NVSR_STATUS, 1, ®_val); |
| NVSR_RETV(ret, "Failed to read status register.\n"); |
| |
| if ((reg_val & NVSR_STATUS_SR_STATE_MASK) |
| == NVSR_STATUS_SR_STATE_OFFLINE) { |
| nvsr->src_on = false; |
| /* Will need to re-init SRC on next power-on */ |
| nvsr->is_init = false; |
| nvsr->sr_active = false; |
| nvsr->idle_active = false; |
| return 0; |
| } |
| |
| msleep(nvsr->sr_timing_frame_time_us/1000); |
| } while (tries++ < 3); |
| |
| NVSR_ERR("Failed to power off the SRC.\n"); |
| return -ETIMEDOUT; |
| } |
| |
| /* Read SRC capabilities and assess possible refresh modes. */ |
| static int tegra_dc_nvsr_query_capabilities(struct tegra_dc_nvsr_data *nvsr) |
| { |
| u32 reg_val; |
| int ret; |
| |
| ret = nvsr->reg_ops.read(nvsr, NVSR_SR_CAPS0, 1, ®_val); |
| NVSR_RETV(ret, "Failed to read cap reg 0.\n"); |
| |
| nvsr->cap.sr = |
| reg_val & NVSR_SR_CAPS0_SR_CAPABLE_MASK >> |
| NVSR_SR_CAPS0_SR_CAPABLE_SHIFT; |
| nvsr->cap.sr_entry_req = |
| reg_val & NVSR_SR_CAPS0_SR_ENTRY_REQ_MASK >> |
| NVSR_SR_CAPS0_SR_ENTRY_REQ_SHIFT; |
| nvsr->cap.sr_exit_req = |
| reg_val & NVSR_SR_CAPS0_SR_EXIT_REQ_MASK >> |
| NVSR_SR_CAPS0_SR_EXIT_REQ_SHIFT; |
| |
| if (!nvsr->cap.sr || !nvsr->cap.sr_entry_req || |
| !nvsr->cap.sr_exit_req) { |
| NVSR_ERR("SRC can't support self-refresh.\n"); |
| return 0; |
| } |
| |
| nvsr->cap.resync = |
| reg_val & NVSR_SR_CAPS0_RESYNC_CAP_MASK >> |
| NVSR_SR_CAPS0_RESYNC_CAP_SHIFT; |
| |
| nvsr->cap.sparse_mode_support = true; |
| |
| /* Parse device/vendor ID */ |
| |
| ret = nvsr->reg_ops.read(nvsr, NVSR_SRC_ID0, 4, ®_val); |
| NVSR_RETV(ret, "Failed to read device ID.\n"); |
| nvsr->src_id.device_id = reg_val & 0xffff; |
| nvsr->src_id.vendor_id = reg_val >> 16; |
| |
| ret = nvsr->reg_ops.read(nvsr, NVSR_SR_CAPS2, 1, ®_val); |
| NVSR_RETV(ret, "Failed to read capabilities register 2.\n"); |
| |
| if ((reg_val & NVSR_SR_CAPS2_SEPERATE_PCLK_SUPPORTED) && |
| (reg_val & NVSR_SR_CAPS2_BUFFERED_REFRESH_SUPPORTED)) { |
| u32 max_pclk = 0; |
| ret = nvsr->reg_ops.read(nvsr, NVSR_SR_MAX_PCLK0, 2, &max_pclk); |
| if (ret) |
| NVSR_WARN("Failed to read SRC input max pclk\n"); |
| |
| if (max_pclk > 0) { |
| /* NVSR stores SRC input max pclk in units of 20KHz */ |
| nvsr->cap.max_pt_pclk = max_pclk * 20000; |
| nvsr->cap.sep_pclk = true; |
| nvsr->cap.buffered_mode_support = |
| reg_val & NVSR_SR_CAPS2_FRAME_LOCK_MASK >> |
| NVSR_SR_CAPS2_FRAME_LOCK_SHIFT; |
| |
| nvsr->cap.burst_mode_support = |
| nvsr->cap.resync == NVSR_SR_CAPS0_RESYNC_CAP_FL; |
| } else |
| NVSR_WARN("SRC input max pclk is 0.\n"); |
| } |
| |
| nvsr->cap.is_init = true; |
| |
| return 0; |
| } |
| |
| /* Write self-refresh timing: SRC to panel */ |
| static int tegra_dc_nvsr_write_sr_timing(struct tegra_dc_nvsr_data *nvsr) |
| { |
| int ret; |
| struct tegra_dc_mode *mode = &nvsr->sr_timing; |
| u32 val, hblank, vblank, pclk; |
| |
| /* NVSR stores pclk in units of 20KHz */ |
| val = DIV_ROUND_UP(mode->pclk, 20000); |
| ret = nvsr->reg_ops.write(nvsr, NVSR_SRMODE_PIXEL_CLOCK0, 2, val); |
| NVSR_RETV(ret, "Failed to write SR pclk\n"); |
| pclk = val * 20000; |
| |
| /* Horizontal timing */ |
| |
| ret = nvsr->reg_ops.write(nvsr, NVSR_SRMODE_TIMING0, 2, mode->h_active); |
| NVSR_RETV(ret, "Failed to write SR Hactive\n"); |
| |
| val = mode->h_front_porch + mode->h_sync_width + mode->h_back_porch; |
| ret = nvsr->reg_ops.write(nvsr, NVSR_SRMODE_TIMING2, 2, val); |
| NVSR_RETV(ret, "Failed to write SR Hblank\n"); |
| hblank = val; |
| |
| ret = nvsr->reg_ops.write(nvsr, NVSR_SRMODE_TIMING4, 2, |
| mode->h_front_porch); |
| NVSR_RETV(ret, "Failed to write SR Hfp\n"); |
| |
| ret = nvsr->reg_ops.write(nvsr, NVSR_SRMODE_TIMING6, 2, |
| mode->h_back_porch); |
| NVSR_RETV(ret, "Failed to write SR Hbp\n"); |
| |
| ret = nvsr->reg_ops.write(nvsr, NVSR_SRMODE_TIMING8, 1, |
| mode->h_sync_width); |
| NVSR_RETV(ret, "Failed to write SR Hsync width\n"); |
| |
| val = (mode->flags & TEGRA_DC_MODE_FLAG_NEG_H_SYNC) ? |
| NVSR_SRMODE_TIMING9_HSYNC_POL_NEG : |
| NVSR_SRMODE_TIMING9_HSYNC_POL_POS; |
| ret = nvsr->reg_ops.write(nvsr, NVSR_SRMODE_TIMING9, 1, val); |
| NVSR_RETV(ret, "Failed to write SR Hsync pulse polarity\n"); |
| |
| /* Vertical timing */ |
| |
| ret = nvsr->reg_ops.write(nvsr, NVSR_SRMODE_TIMING10, 2, |
| mode->v_active); |
| NVSR_RETV(ret, "Failed to write SR Vactive\n"); |
| |
| val = mode->v_front_porch + mode->v_sync_width + mode->v_back_porch; |
| ret = nvsr->reg_ops.write(nvsr, NVSR_SRMODE_TIMING12, 2, val); |
| NVSR_RETV(ret, "Failed to write SR Vblank\n"); |
| vblank = val; |
| |
| ret = nvsr->reg_ops.write(nvsr, NVSR_SRMODE_TIMING14, 2, |
| mode->v_front_porch); |
| NVSR_RETV(ret, "Failed to write SR Vfp\n"); |
| |
| ret = nvsr->reg_ops.write(nvsr, NVSR_SRMODE_TIMING16, 2, |
| mode->v_back_porch); |
| NVSR_RETV(ret, "Failed to write SR Vbp\n"); |
| |
| ret = nvsr->reg_ops.write(nvsr, NVSR_SRMODE_TIMING18, 1, |
| mode->v_sync_width); |
| NVSR_RETV(ret, "Failed to write SR Vsync width\n"); |
| |
| val = (mode->flags & TEGRA_DC_MODE_FLAG_NEG_V_SYNC) ? |
| NVSR_SRMODE_TIMING19_VSYNC_POL_NEG : |
| NVSR_SRMODE_TIMING19_VSYNC_POL_POS; |
| |
| ret = nvsr->reg_ops.write(nvsr, NVSR_SRMODE_TIMING19, 1, val); |
| NVSR_RETV(ret, "Failed to write SR Hsync pulse polarity\n"); |
| |
| nvsr->sr_timing_frame_time_us = |
| pclk / (mode->h_active + hblank) / (mode->v_active + vblank); |
| nvsr->sr_timing_frame_time_us = 1000000 / nvsr->sr_timing_frame_time_us; |
| |
| return 0; |
| } |
| |
| /* Write pass-through timing: DC to SRC */ |
| static int tegra_dc_nvsr_write_pt_timing(struct tegra_dc_nvsr_data *nvsr) |
| { |
| int ret; |
| struct tegra_dc_mode *mode = &nvsr->pt_timing; |
| u32 val, hblank, vblank, pclk; |
| |
| |
| if (mode->pclk > nvsr->cap.max_pt_pclk) { |
| NVSR_ERR("DC->SRC pixel clock (%dHz) > max (%dHz)\n", |
| mode->pclk, nvsr->cap.max_pt_pclk); |
| return -EINVAL; |
| } |
| |
| /* NVSR stores pclk in units of 20KHz */ |
| val = DIV_ROUND_UP(mode->pclk, 20000); |
| ret = nvsr->reg_ops.write(nvsr, NVSR_PTMODE_PIXEL_CLOCK0, 2, val); |
| NVSR_RETV(ret, "Failed to write PT pclk\n"); |
| pclk = val * 20000; |
| |
| /* Horizontal timing */ |
| |
| ret = nvsr->reg_ops.write(nvsr, NVSR_PTMODE_TIMING0, 2, mode->h_active); |
| NVSR_RETV(ret, "Failed to write PT Hactive\n"); |
| |
| val = mode->h_front_porch + mode->h_sync_width + mode->h_back_porch; |
| ret = nvsr->reg_ops.write(nvsr, NVSR_PTMODE_TIMING2, 2, val); |
| NVSR_RETV(ret, "Failed to write PT Hblank\n"); |
| hblank = val; |
| |
| ret = nvsr->reg_ops.write(nvsr, NVSR_PTMODE_TIMING4, 2, |
| mode->h_front_porch); |
| NVSR_RETV(ret, "Failed to write PT Hfp\n"); |
| |
| ret = nvsr->reg_ops.write(nvsr, NVSR_PTMODE_TIMING6, 2, |
| mode->h_back_porch); |
| NVSR_RETV(ret, "Failed to write PT Hbp\n"); |
| |
| ret = nvsr->reg_ops.write(nvsr, NVSR_PTMODE_TIMING8, 1, |
| mode->h_sync_width); |
| NVSR_RETV(ret, "Failed to write PT Hsync width\n"); |
| |
| val = (mode->flags & TEGRA_DC_MODE_FLAG_NEG_H_SYNC) ? |
| NVSR_PTMODE_TIMING9_HSYNC_POL_NEG : |
| NVSR_PTMODE_TIMING9_HSYNC_POL_POS; |
| ret = nvsr->reg_ops.write(nvsr, NVSR_PTMODE_TIMING9, 1, val); |
| NVSR_RETV(ret, "Failed to write PT Hsync pulse polarity\n"); |
| |
| /* Vertical timing */ |
| |
| ret = nvsr->reg_ops.write(nvsr, NVSR_PTMODE_TIMING10, 2, |
| mode->v_active); |
| NVSR_RETV(ret, "Failed to write PT Vactive\n"); |
| |
| val = mode->v_front_porch + mode->v_sync_width + mode->v_back_porch; |
| ret = nvsr->reg_ops.write(nvsr, NVSR_PTMODE_TIMING12, 2, val); |
| NVSR_RETV(ret, "Failed to write PT Vblank\n"); |
| vblank = val; |
| |
| ret = nvsr->reg_ops.write(nvsr, NVSR_PTMODE_TIMING14, 2, |
| mode->v_front_porch); |
| NVSR_RETV(ret, "Failed to write PT Vfp\n"); |
| |
| ret = nvsr->reg_ops.write(nvsr, NVSR_PTMODE_TIMING16, 2, |
| mode->v_back_porch); |
| NVSR_RETV(ret, "Failed to write PT Vbp\n"); |
| |
| ret = nvsr->reg_ops.write(nvsr, NVSR_PTMODE_TIMING18, 1, |
| mode->v_sync_width); |
| NVSR_RETV(ret, "Failed to write PT Vsync width\n"); |
| |
| val = (mode->flags & TEGRA_DC_MODE_FLAG_NEG_V_SYNC) ? |
| NVSR_PTMODE_TIMING19_VSYNC_POL_NEG : |
| NVSR_PTMODE_TIMING19_VSYNC_POL_POS; |
| ret = nvsr->reg_ops.write(nvsr, NVSR_PTMODE_TIMING19, 1, val); |
| NVSR_RETV(ret, "Failed to write PT Hsync pulse polarity\n"); |
| |
| nvsr->pt_timing_frame_time_us = |
| pclk / (mode->h_active + hblank) / (mode->v_active + vblank); |
| nvsr->pt_timing_frame_time_us = 1000000 / nvsr->pt_timing_frame_time_us; |
| |
| return 0; |
| } |
| |
| static int tegra_dc_nvsr_update_timings(struct tegra_dc_nvsr_data *nvsr) |
| { |
| int ret, i; |
| struct tegra_dc_mode *modes = nvsr->dc->out->modes; |
| struct tegra_dc_mode *mode = &nvsr->dc->mode; |
| int n_modes = nvsr->dc->out->n_modes; |
| u32 min_vblank, max_vblank, min_hblank, max_hblank; |
| u32 vblank, hblank; |
| |
| /* If SR isn't supported, no need to program timings */ |
| if (!nvsr->cap.sparse_mode_support) |
| return 0; |
| |
| /* Find min/max blank timings */ |
| if (n_modes) { |
| min_vblank = max_vblank = |
| modes[0].v_front_porch + modes[0].v_sync_width |
| + modes[0].v_back_porch; |
| min_hblank = max_hblank = |
| modes[0].h_front_porch + modes[0].h_sync_width |
| + modes[0].h_back_porch; |
| |
| for (i = 1; i < n_modes; i++) { |
| vblank = modes[i].v_front_porch + modes[i].v_sync_width |
| + modes[i].v_back_porch; |
| hblank = modes[i].h_front_porch + modes[i].h_sync_width |
| + modes[i].h_back_porch; |
| |
| if (vblank > max_vblank) |
| max_vblank = vblank; |
| if (vblank < min_vblank) |
| min_vblank = vblank; |
| if (hblank > max_hblank) |
| max_hblank = hblank; |
| if (hblank < min_hblank) |
| min_hblank = hblank; |
| } |
| } else { |
| max_vblank = min_vblank = |
| mode->v_front_porch + mode->v_sync_width + |
| mode->v_back_porch; |
| max_hblank = min_hblank = |
| mode->h_front_porch + mode->h_sync_width + |
| mode->h_back_porch; |
| } |
| |
| |
| ret = nvsr->reg_ops.write(nvsr, NVSR_BLANK_TIMING0, 2, min_vblank); |
| NVSR_RETV(ret, "Failed to init timing\n"); |
| ret = nvsr->reg_ops.write(nvsr, NVSR_BLANK_TIMING2, 2, max_vblank); |
| NVSR_RETV(ret, "Failed to init timing\n"); |
| ret = nvsr->reg_ops.write(nvsr, NVSR_BLANK_TIMING4, 2, min_hblank); |
| NVSR_RETV(ret, "Failed to init timing\n"); |
| ret = nvsr->reg_ops.write(nvsr, NVSR_BLANK_TIMING6, 2, max_hblank); |
| NVSR_RETV(ret, "Failed to init timing\n"); |
| |
| ret = tegra_dc_nvsr_write_pt_timing(nvsr); |
| NVSR_RETV(ret, "Failed to init pass-through timing\n"); |
| ret = tegra_dc_nvsr_write_sr_timing(nvsr); |
| NVSR_RETV(ret, "Failed to init self-refresh timing\n"); |
| |
| return 0; |
| } |
| |
| static int tegra_nvsr_read_dpaux(struct tegra_dc_nvsr_data *nvsr, |
| u32 reg, u32 size, u32 *val) |
| { |
| int ret, i; |
| u32 aux_stat; |
| u8 data[DP_AUX_MAX_BYTES] = {0}; |
| |
| ret = tegra_dc_dpaux_read(nvsr->out_data.dp, DPAUX_DP_AUXCTL_CMD_AUXRD, |
| reg, data, &size, &aux_stat); |
| NVSR_RETV(ret, |
| "DPAUX read failed: reg = 0x%x, size = %d, aux_stat = %d\n", |
| reg, size, aux_stat); |
| |
| *val = 0; |
| for (i = 0; i < DP_AUX_MAX_BYTES; i++) |
| *val |= data[i] << (i * 8); |
| |
| return 0; |
| } |
| |
| static int tegra_nvsr_write_dpaux(struct tegra_dc_nvsr_data *nvsr, |
| u32 reg, u32 size, u64 val) |
| { |
| int i, ret; |
| u32 aux_stat; |
| u8 data[DP_AUX_MAX_BYTES] = {0}; |
| |
| for (i = 0; i < size; i++) { |
| data[i] = val & 0xffllu; |
| val = val >> 8; |
| } |
| |
| /* HACK: Himax tcon always reports failure when writing; |
| * write 1 byte at a time to avoid breaking out of DP |
| * write code too early */ |
| ret = tegra_dc_dpaux_write(nvsr->out_data.dp, DPAUX_DP_AUXCTL_CMD_AUXWR, |
| reg, data, &size, &aux_stat); |
| |
| /* Current HIMAX silicon always NACKs dpaux writes */ |
| #if !HIMAX |
| if (ret) |
| return ret; |
| #endif |
| |
| return 0; |
| } |
| |
| /* Enter Sparse and turn off link, or single frame update */ |
| static int tegra_dc_nvsr_enter_idle(struct tegra_dc_nvsr_data *nvsr) |
| { |
| int ret = 0; |
| struct tegra_dc *dc = nvsr->dc; |
| |
| if (!nvsr->is_init || !nvsr->src_on || |
| !nvsr->enable || !nvsr->cap.sparse_mode_support) |
| return -ENODEV; |
| |
| if (!nvsr->idle_active) { |
| /* Start a new self-refresh state */ |
| |
| ret = tegra_dc_nvsr_enter_sr(nvsr); |
| NVSR_RETV(ret, "Entering SR failed\n"); |
| |
| /* TODO: Turn off link here */ |
| |
| /* set non-continuous mode */ |
| tegra_dc_writel(dc, DISP_CTRL_MODE_NC_DISPLAY, |
| DC_CMD_DISPLAY_COMMAND); |
| tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); |
| |
| dc->out->flags |= TEGRA_DC_OUT_ONE_SHOT_MODE; |
| |
| nvsr->idle_active = true; |
| } else { |
| tegra_dc_nvsr_get(nvsr); |
| |
| /* Update cached frame */ |
| switch (nvsr->sr_mode) { |
| case SR_MODE_BURST: |
| /* TODO: Implement Burst Refresh update */ |
| NVSR_ERR("Burst mode unimplemented\n"); |
| break; |
| default: |
| NVSR_ERR("SR re-entry not supported for current refresh mode.\n"); |
| ret = -ENOSYS; |
| } |
| |
| tegra_dc_nvsr_put(nvsr); |
| } |
| |
| return ret; |
| } |
| |
| inline void tegra_dc_nvsr_irq(struct tegra_dc_nvsr_data *nvsr, |
| unsigned long status) |
| { |
| if ((status & MSF_INT) && (nvsr->waiting_on_framelock)) { |
| tegra_dc_mask_interrupt(nvsr->dc, MSF_INT); |
| nvsr->waiting_on_framelock = false; |
| complete(&nvsr->framelock_comp); |
| } |
| } |
| |
| static inline int tegra_dc_nvsr_reset_src(struct tegra_dc_nvsr_data *nvsr) |
| { |
| int ret; |
| |
| ret = tegra_dc_nvsr_src_power_off(nvsr); |
| NVSR_RETV(ret, "Failed to reset SRC.\n"); |
| ret = tegra_dc_nvsr_src_power_on(nvsr); |
| NVSR_RETV(ret, "Failed to reset SRC.\n"); |
| |
| return 0; |
| } |
| |
| static inline void tegra_dc_nvsr_config_resync_method |
| (struct tegra_dc_nvsr_data *nvsr) |
| { |
| u32 val; |
| struct tegra_dc *dc = nvsr->dc; |
| |
| if (nvsr->cap.resync & NVSR_SR_CAPS0_RESYNC_CAP_FL) { |
| nvsr->resync_method = NVSR_RESYNC_CTL_METHOD_FL; |
| |
| /* Configure MSF pin */ |
| val = PIN_OUTPUT_LSPI_OUTPUT_DIS; |
| tegra_dc_writel(dc, val, DC_COM_PIN_OUTPUT_ENABLE3); |
| val = MSF_ENABLE | MSF_LSPI | MSF_POLARITY_LOW; |
| tegra_dc_writel(dc, val, DC_CMD_DISPLAY_COMMAND_OPTION0); |
| /* Enable MSF (framelock) interrupt in DC */ |
| val = tegra_dc_readl(dc, DC_CMD_INT_ENABLE); |
| val |= MSF_INT; |
| tegra_dc_writel(dc, val, DC_CMD_INT_ENABLE); |
| } else if ((nvsr->cap.resync & NVSR_SR_CAPS0_RESYNC_CAP_BS) || |
| (nvsr->cap.resync & NVSR_SR_CAPS0_RESYNC_CAP_SS)) { |
| NVSR_WARN("Sliding Sync and Blank Stretching resync methods not supported; defaulting to Immediate\n"); |
| nvsr->resync_method = NVSR_RESYNC_CTL_METHOD_IMM; |
| } else |
| nvsr->resync_method = NVSR_RESYNC_CTL_METHOD_IMM; |
| |
| nvsr->resync_delay = 0; |
| } |
| |
| static int tegra_dc_nvsr_init_src(struct tegra_dc_nvsr_data *nvsr) |
| { |
| int ret; |
| u32 val; |
| |
| /* Reset SRC if it's not in idle */ |
| ret = nvsr->reg_ops.read(nvsr, NVSR_STATUS, 1, &val); |
| NVSR_RETV(ret, "Failed to read status register.\n"); |
| if ((val & NVSR_STATUS_SR_STATE_MASK) |
| != NVSR_STATUS_SR_STATE_IDLE) { |
| /* Reset SRC */ |
| ret = tegra_dc_nvsr_reset_src(nvsr); |
| NVSR_RETV(ret, "Failed to reset SRC\n"); |
| } |
| |
| /* Unmask and enable interrupts from SRC */ |
| ret = nvsr->reg_ops.write(nvsr, NVSR_INTR0, 1, 0xff); |
| NVSR_RETV(ret, "Failed to write to interrupt mask regiser.\n"); |
| val = NVSR_INTR1_EN_YES; |
| val |= NVSR_INTR1_EN_MASK_ENABLE; |
| ret = nvsr->reg_ops.write(nvsr, NVSR_INTR1, 1, val); |
| NVSR_RETV(ret, "Failed to write to interrupt enable regiser.\n"); |
| |
| /* Can't populate capabilities during init since DP interface |
| * isn't enabled yet at that point. Read capabilities here instead, |
| * just once */ |
| if (unlikely(!nvsr->cap.is_init)) { |
| ret = tegra_dc_nvsr_query_capabilities(nvsr); |
| NVSR_RETV(ret, "Can't ready capabilities\n"); |
| |
| nvsr->pt_timing = nvsr->dc->mode; |
| nvsr->sr_timing = nvsr->dc->mode; |
| } |
| |
| ret = tegra_dc_nvsr_update_timings(nvsr); |
| NVSR_RETV(ret, "Failed to write SRC timings\n"); |
| |
| tegra_dc_nvsr_config_resync_method(nvsr); |
| |
| nvsr->is_init = true; |
| |
| return 0; |
| } |
| |
| static int tegra_dc_nvsr_exit_idle(struct tegra_dc_nvsr_data *nvsr) |
| { |
| int ret; |
| struct tegra_dc *dc = nvsr->dc; |
| u32 exit_timeout = nvsr->sr_timing_frame_time_us / 1000 * |
| SR_EXIT_MAX_TRIES; |
| |
| if (!nvsr->is_init || !nvsr->src_on || |
| !nvsr->enable) |
| return -ENODEV; |
| |
| if (!nvsr->idle_active) |
| return 0; |
| |
| tegra_dc_get(dc); |
| |
| /* Program current frame as NC, to be triggered in MSF/FL interrupt */ |
| tegra_dc_writel(dc, GENERAL_ACT_REQ | NC_HOST_TRIG, |
| DC_CMD_STATE_CONTROL); |
| |
| /* Set up for FL IRQ, request SR-exit from SRC, and wait */ |
| nvsr->waiting_on_framelock = true; |
| tegra_dc_unmask_interrupt(dc, MSF_INT); |
| ret = tegra_dc_nvsr_exit_sr(nvsr); |
| if (ret) { |
| NVSR_ERR("Exiting SR failed\n"); |
| nvsr->waiting_on_framelock = false; |
| tegra_dc_mask_interrupt(dc, MSF_INT); |
| |
| /* Attempt to reset and re-initialize SRC. */ |
| BUG_ON(tegra_dc_nvsr_init_src(nvsr)); |
| |
| tegra_dc_writel(dc, DISP_CTRL_MODE_C_DISPLAY, |
| DC_CMD_DISPLAY_COMMAND); |
| tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); |
| |
| dc->out->flags &= ~TEGRA_DC_OUT_ONE_SHOT_MODE; |
| |
| return ret; |
| } |
| |
| wait_for_completion_interruptible_timeout(&nvsr->framelock_comp, |
| msecs_to_jiffies(exit_timeout)); |
| |
| /* Switch DC NC-->C during the single NC frame */ |
| tegra_dc_writel(dc, DISP_CTRL_MODE_C_DISPLAY, |
| DC_CMD_DISPLAY_COMMAND); |
| tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); |
| |
| dc->out->flags &= ~TEGRA_DC_OUT_ONE_SHOT_MODE; |
| |
| init_completion(&nvsr->framelock_comp); |
| tegra_dc_put(dc); |
| |
| nvsr->idle_active = false; |
| |
| return 0; |
| } |
| |
| static int tegra_dc_nvsr_enable_nvsr(struct tegra_dc_nvsr_data *nvsr) |
| { |
| int ret; |
| |
| if (nvsr->enable) |
| return 0; |
| |
| /* tegra_dc_nvsr_init_src will turn |
| * on SRC and do initial config */ |
| ret = tegra_dc_nvsr_init_src(nvsr); |
| NVSR_RETV(ret, "Failed to init SRC\n"); |
| |
| nvsr->enable = true; |
| |
| return 0; |
| } |
| |
| static int tegra_dc_nvsr_disable_nvsr(struct tegra_dc_nvsr_data *nvsr) |
| { |
| int ret; |
| |
| if (!nvsr->enable) |
| return 0; |
| |
| if (nvsr->idle_active) { |
| ret = tegra_dc_nvsr_exit_idle(nvsr); |
| NVSR_RETV(ret, "Can't exit idle, leaving NVSR enabled\n"); |
| } |
| |
| ret = tegra_dc_nvsr_src_power_off(nvsr); |
| NVSR_RETV(ret, "Failed to shut off SRC\n"); |
| |
| nvsr->enable = false; |
| |
| return 0; |
| } |
| |
| |
| static void tegra_dc_nvsr_destroy(struct tegra_dc *dc) |
| { |
| struct tegra_dc_nvsr_data *nvsr = dc->nvsr; |
| if (nvsr->out_ops.destroy) |
| nvsr->out_ops.destroy(dc); |
| } |
| |
| static bool tegra_dc_nvsr_detect(struct tegra_dc *dc) |
| { |
| struct tegra_dc_nvsr_data *nvsr = dc->nvsr; |
| if (nvsr->out_ops.detect) |
| return nvsr->out_ops.detect(dc); |
| |
| return -EINVAL; |
| } |
| |
| static void tegra_dc_nvsr_enable(struct tegra_dc *dc) |
| { |
| struct tegra_dc_nvsr_data *nvsr = dc->nvsr; |
| |
| if (nvsr->out_ops.enable) |
| nvsr->out_ops.enable(dc); |
| |
| tegra_dc_nvsr_init_src(nvsr); |
| } |
| |
| static void tegra_dc_nvsr_disable(struct tegra_dc *dc) |
| { |
| struct tegra_dc_nvsr_data *nvsr = dc->nvsr; |
| |
| tegra_dc_nvsr_src_power_off(nvsr); |
| |
| if (nvsr->out_ops.disable) |
| nvsr->out_ops.disable(dc); |
| } |
| |
| static void tegra_dc_nvsr_hold(struct tegra_dc *dc) |
| { |
| struct tegra_dc_nvsr_data *nvsr = dc->nvsr; |
| if (nvsr->out_ops.hold) |
| nvsr->out_ops.hold(dc); |
| } |
| |
| static void tegra_dc_nvsr_release(struct tegra_dc *dc) |
| { |
| struct tegra_dc_nvsr_data *nvsr = dc->nvsr; |
| if (nvsr->out_ops.release) |
| nvsr->out_ops.release(dc); |
| } |
| |
| static void tegra_dc_nvsr_idle(struct tegra_dc *dc) |
| { |
| struct tegra_dc_nvsr_data *nvsr = dc->nvsr; |
| if (nvsr->out_ops.idle) |
| nvsr->out_ops.idle(dc); |
| } |
| |
| static void tegra_dc_nvsr_suspend(struct tegra_dc *dc) |
| { |
| struct tegra_dc_nvsr_data *nvsr = dc->nvsr; |
| if (nvsr->out_ops.suspend) |
| nvsr->out_ops.suspend(dc); |
| } |
| |
| static void tegra_dc_nvsr_resume(struct tegra_dc *dc) |
| { |
| struct tegra_dc_nvsr_data *nvsr = dc->nvsr; |
| if (nvsr->out_ops.resume) |
| nvsr->out_ops.resume(dc); |
| } |
| |
| static bool tegra_dc_nvsr_mode_filter(const struct tegra_dc *dc, |
| struct fb_videomode *mode) |
| { |
| struct tegra_dc_nvsr_data *nvsr = dc->nvsr; |
| if (nvsr->out_ops.mode_filter) |
| return nvsr->out_ops.mode_filter(dc, mode); |
| |
| return -EINVAL; |
| } |
| |
| static long tegra_dc_nvsr_setup_clk(struct tegra_dc *dc, struct clk *clk) |
| { |
| struct tegra_dc_nvsr_data *nvsr = dc->nvsr; |
| if (nvsr->out_ops.setup_clk) |
| return nvsr->out_ops.setup_clk(dc, clk); |
| |
| return -EINVAL; |
| } |
| |
| static void tegra_dc_nvsr_init_out_ops(struct tegra_dc_nvsr_data *nvsr, |
| struct tegra_dc_out_ops *out_ops) |
| { |
| if (out_ops->init) |
| nvsr->out_ops.init = out_ops->init; |
| |
| if (out_ops->destroy) { |
| tegra_dc_nvsr_ops.destroy = tegra_dc_nvsr_destroy; |
| nvsr->out_ops.destroy = out_ops->destroy; |
| } |
| |
| if (out_ops->detect) { |
| tegra_dc_nvsr_ops.detect = tegra_dc_nvsr_detect; |
| nvsr->out_ops.detect = out_ops->detect; |
| } |
| |
| if (out_ops->enable) { |
| tegra_dc_nvsr_ops.enable = tegra_dc_nvsr_enable; |
| nvsr->out_ops.enable = out_ops->enable; |
| } |
| |
| if (out_ops->disable) { |
| tegra_dc_nvsr_ops.disable = tegra_dc_nvsr_disable; |
| nvsr->out_ops.disable = out_ops->disable; |
| } |
| |
| if (out_ops->hold) { |
| tegra_dc_nvsr_ops.hold = tegra_dc_nvsr_hold; |
| nvsr->out_ops.hold = out_ops->hold; |
| } |
| |
| if (out_ops->release) { |
| tegra_dc_nvsr_ops.release = tegra_dc_nvsr_release; |
| nvsr->out_ops.release = out_ops->release; |
| } |
| if (out_ops->idle) { |
| tegra_dc_nvsr_ops.idle = tegra_dc_nvsr_idle; |
| nvsr->out_ops.idle = out_ops->idle; |
| } |
| if (out_ops->suspend) { |
| tegra_dc_nvsr_ops.suspend = tegra_dc_nvsr_suspend; |
| nvsr->out_ops.suspend = out_ops->suspend; |
| } |
| if (out_ops->resume) { |
| tegra_dc_nvsr_ops.resume = tegra_dc_nvsr_resume; |
| nvsr->out_ops.resume = out_ops->resume; |
| } |
| if (out_ops->mode_filter) { |
| tegra_dc_nvsr_ops.mode_filter = tegra_dc_nvsr_mode_filter; |
| nvsr->out_ops.mode_filter = out_ops->mode_filter; |
| } |
| if (out_ops->setup_clk) { |
| tegra_dc_nvsr_ops.setup_clk = tegra_dc_nvsr_setup_clk; |
| nvsr->out_ops.setup_clk = out_ops->setup_clk; |
| } |
| } |
| |
| #ifdef CONFIG_DEBUG_FS |
| static int nvsr_dbg_read_read_show(struct seq_file *s, void *unused) |
| { return 0; } |
| static int nvsr_dbg_read_write_show(struct seq_file *s, void *unused) |
| { return 0; } |
| |
| static int nvsr_dbg_read_open(struct inode *inode, struct file *file) |
| { |
| return single_open(file, nvsr_dbg_read_read_show, inode->i_private); |
| } |
| |
| static int nvsr_dbg_write_open(struct inode *inode, struct file *file) |
| { |
| return single_open(file, nvsr_dbg_read_write_show, inode->i_private); |
| } |
| |
| static const char *tegra_dc_nvsr_state_to_string(u32 val) |
| { |
| val &= NVSR_STATUS_SR_STATE_MASK; |
| switch (val) { |
| case NVSR_STATUS_SR_STATE_OFFLINE: |
| return "Offline"; |
| case NVSR_STATUS_SR_STATE_IDLE: |
| return "Idle"; |
| case NVSR_STATUS_SR_STATE_SR_ENTRY_TRIG: |
| return "SR-entry triggered"; |
| case NVSR_STATUS_SR_STATE_SR_ENTRY_CACHING: |
| return "SR-entry caching"; |
| case NVSR_STATUS_SR_STATE_SR_ENTRY_RDY: |
| return "SR-entry ready"; |
| case NVSR_STATUS_SR_STATE_SR_ACTIVE: |
| return "SR active"; |
| case NVSR_STATUS_SR_STATE_SR_EXIT_TRIG: |
| return "SR-exit triggered"; |
| case NVSR_STATUS_SR_STATE_SR_EXIT_RESYNC: |
| return "SR-exit resynching"; |
| default: |
| return "Unknown"; |
| } |
| |
| /* Should never get here */ |
| return ""; |
| } |
| |
| static int nvsr_dbg_status_show(struct seq_file *s, void *unused) |
| { |
| struct tegra_dc_nvsr_data *nvsr = s->private; |
| u32 val, val2; |
| int ret; |
| |
| if (!nvsr->is_init) { |
| seq_puts(s, "NVSR has not been initialized.\n"); |
| return -EINVAL; |
| } |
| |
| tegra_dc_nvsr_get(nvsr); |
| |
| seq_puts(s, "SRC Info\n"); |
| seq_puts(s, "--------\n"); |
| seq_printf(s, "Device ID: \t\t\t\t%d\n", nvsr->src_id.device_id); |
| seq_printf(s, "Vendor ID: \t\t\t\t%d\n", nvsr->src_id.vendor_id); |
| seq_printf(s, "Sparse Refresh mode supported: \t\t%d\n", |
| nvsr->cap.sparse_mode_support); |
| seq_printf(s, "\tEntry request band capability: \t%d\n", |
| nvsr->cap.sr_entry_req); |
| seq_printf(s, "\tExit request band capability: \t%d\n", |
| nvsr->cap.sr_exit_req); |
| seq_printf(s, "\tResync capabilities:\n"); |
| if (!nvsr->cap.resync) { |
| seq_puts(s, "\t\tImmediate\n"); |
| } else { |
| if (nvsr->cap.resync & |
| NVSR_SR_CAPS0_RESYNC_CAP_FL) |
| seq_puts(s, "\t\tFramelock\n"); |
| if (nvsr->cap.resync & |
| NVSR_SR_CAPS0_RESYNC_CAP_BS) |
| seq_puts(s, "\t\tBlank stretching\n"); |
| if (nvsr->cap.resync & |
| NVSR_SR_CAPS0_RESYNC_CAP_SS) |
| seq_puts(s, "\t\tSliding sync\n"); |
| } |
| seq_printf(s, "Buffered mode supported: \t\t%d\n", |
| nvsr->cap.buffered_mode_support); |
| seq_printf(s, "Burst mode supported: \t\t\t%d\n", |
| nvsr->cap.burst_mode_support); |
| seq_printf(s, "\tSRC max input pclk: \t\t%d\n", |
| nvsr->cap.max_pt_pclk); |
| seq_puts(s, "\n"); |
| seq_puts(s, "SRC status\n"); |
| seq_puts(s, "----------\n"); |
| seq_printf(s, "NVSR functionality enabled: \t%d\n", nvsr->enable); |
| seq_printf(s, "In Sparse Refresh: \t\t%d\n", nvsr->sr_active); |
| seq_printf(s, "Resync method: \t\t\t%s\n", |
| !nvsr->resync_method ? |
| "Immediate" : |
| nvsr->resync_method == NVSR_SR_CAPS0_RESYNC_CAP_FL ? |
| "Framelock" : |
| nvsr->resync_method == NVSR_RESYNC_CTL_METHOD_BS ? |
| "Blank stretching" : |
| nvsr->resync_method == NVSR_SR_CAPS0_RESYNC_CAP_SS ? |
| "Sliding sync" : |
| "unknown"); |
| seq_printf(s, "Resync delay (frames): \t\t%d\n", nvsr->resync_delay); |
| seq_printf(s, "SRC powered: \t\t\t%d\n", nvsr->src_on); |
| |
| ret = nvsr->reg_ops.read(nvsr, NVSR_STATUS, 1, &val); |
| seq_printf(s, "Current SRC status: \t%s\n", |
| tegra_dc_nvsr_state_to_string(val)); |
| |
| seq_puts(s, "PT Timing:\n"); |
| |
| ret = nvsr->reg_ops.read(nvsr, NVSR_PTMODE_PIXEL_CLOCK0, 2, &val); |
| seq_printf(s, "\tPixel clock: \t%dHz\n", ret ? -1 : val * 20000); |
| |
| /* Horizontal PT timing */ |
| |
| ret = nvsr->reg_ops.read(nvsr, NVSR_PTMODE_TIMING0, 2, &val); |
| seq_printf(s, "\tHactive: \t%d\n", ret ? -1 : val); |
| |
| ret = nvsr->reg_ops.read(nvsr, NVSR_PTMODE_TIMING2, 2, &val); |
| seq_printf(s, "\tHblank: \t%d\n", ret ? -1 : val); |
| |
| ret = nvsr->reg_ops.read(nvsr, NVSR_PTMODE_TIMING4, 2, &val); |
| seq_printf(s, "\tHfp: \t\t%d\n", ret ? -1 : val); |
| |
| ret = nvsr->reg_ops.read(nvsr, NVSR_PTMODE_TIMING6, 2, &val); |
| seq_printf(s, "\tHbp: \t\t%d\n", ret ? -1 : val); |
| |
| ret = nvsr->reg_ops.read(nvsr, NVSR_PTMODE_TIMING8, 1, &val); |
| seq_printf(s, "\tHsync width: \t%d\n", val); |
| |
| ret = nvsr->reg_ops.read(nvsr, NVSR_PTMODE_TIMING9, 1, &val); |
| val2 = val & NVSR_PTMODE_TIMING9_HORZ_BORDER_MASK; |
| val = (val & NVSR_PTMODE_TIMING9_HSYNC_POL_MASK) >> |
| NVSR_PTMODE_TIMING9_HSYNC_POL_SHIFT; |
| seq_printf(s, "\tHborder: \t%d\n", ret ? -1 : val2); |
| seq_printf(s, "\tHsync pulse polarity: \t%d\n", ret ? -1 : val); |
| |
| /* Vertical PT timing */ |
| |
| ret = nvsr->reg_ops.read(nvsr, NVSR_PTMODE_TIMING10, 2, &val); |
| seq_printf(s, "\tVactive: \t%d\n", ret ? -1 : val); |
| |
| ret = nvsr->reg_ops.read(nvsr, NVSR_PTMODE_TIMING12, 2, &val); |
| seq_printf(s, "\tVblank \t\t%d\n", ret ? -1 : val); |
| |
| ret = nvsr->reg_ops.read(nvsr, NVSR_PTMODE_TIMING14, 2, &val); |
| seq_printf(s, "\tVfp: \t\t%d\n", ret ? -1 : val); |
| |
| ret = nvsr->reg_ops.read(nvsr, NVSR_PTMODE_TIMING16, 2, &val); |
| seq_printf(s, "\tVbp: \t\t%d\n", ret ? -1 : val); |
| |
| ret = nvsr->reg_ops.read(nvsr, NVSR_PTMODE_TIMING18, 1, &val); |
| seq_printf(s, "\tVsync width: \t%d\n", ret ? -1 : val); |
| |
| ret = nvsr->reg_ops.read(nvsr, NVSR_PTMODE_TIMING19, 1, &val); |
| val2 = val & NVSR_PTMODE_TIMING19_VERT_BORDER_MASK; |
| val = (val & NVSR_PTMODE_TIMING19_VSYNC_POL_MASK) >> |
| NVSR_PTMODE_TIMING19_VSYNC_POL_SHIFT; |
| seq_printf(s, "\tVborder: \t%d\n", ret ? -1 : val2); |
| seq_printf(s, "\tVsync pulse polarity: \t%d\n", ret ? -1 : val); |
| seq_printf(s, "\tFrame time: \t%dus\n", nvsr->pt_timing_frame_time_us); |
| |
| seq_puts(s, "SR Timing:\n"); |
| |
| ret = nvsr->reg_ops.read(nvsr, NVSR_SRMODE_PIXEL_CLOCK0, 2, &val); |
| seq_printf(s, "\tPixel clock: \t%dHz\n", ret ? -1 : val * 20000); |
| |
| /* Horizontal SR timing */ |
| |
| ret = nvsr->reg_ops.read(nvsr, NVSR_SRMODE_TIMING0, 2, &val); |
| seq_printf(s, "\tHactive: \t%d\n", ret ? -1 : val); |
| |
| ret = nvsr->reg_ops.read(nvsr, NVSR_SRMODE_TIMING2, 2, &val); |
| seq_printf(s, "\tHblank: \t%d\n", ret ? -1 : val); |
| |
| ret = nvsr->reg_ops.read(nvsr, NVSR_SRMODE_TIMING4, 2, &val); |
| seq_printf(s, "\tHfp: \t\t%d\n", ret ? -1 : val); |
| |
| ret = nvsr->reg_ops.read(nvsr, NVSR_SRMODE_TIMING6, 2, &val); |
| seq_printf(s, "\tHbp: \t\t%d\n", ret ? -1 : val); |
| |
| ret = nvsr->reg_ops.read(nvsr, NVSR_SRMODE_TIMING8, 1, &val); |
| seq_printf(s, "\tHsync width: \t%d\n", val); |
| |
| ret = nvsr->reg_ops.read(nvsr, NVSR_SRMODE_TIMING9, 1, &val); |
| val2 = val & NVSR_SRMODE_TIMING9_HORZ_BORDER_MASK; |
| val = (val & NVSR_SRMODE_TIMING9_HSYNC_POL_MASK) >> |
| NVSR_SRMODE_TIMING9_HSYNC_POL_SHIFT; |
| seq_printf(s, "\tHborder: \t%d\n", ret ? -1 : val2); |
| seq_printf(s, "\tHsync pulse polarity: \t%d\n", ret ? -1 : val); |
| |
| /* Vertical SR timing */ |
| |
| ret = nvsr->reg_ops.read(nvsr, NVSR_SRMODE_TIMING10, 2, &val); |
| seq_printf(s, "\tVactive: \t%d\n", ret ? -1 : val); |
| |
| ret = nvsr->reg_ops.read(nvsr, NVSR_SRMODE_TIMING12, 2, &val); |
| seq_printf(s, "\tVblank \t\t%d\n", ret ? -1 : val); |
| |
| ret = nvsr->reg_ops.read(nvsr, NVSR_SRMODE_TIMING14, 2, &val); |
| seq_printf(s, "\tVfp: \t\t%d\n", ret ? -1 : val); |
| |
| ret = nvsr->reg_ops.read(nvsr, NVSR_SRMODE_TIMING16, 2, &val); |
| seq_printf(s, "\tVbp: \t\t%d\n", ret ? -1 : val); |
| |
| ret = nvsr->reg_ops.read(nvsr, NVSR_SRMODE_TIMING18, 1, &val); |
| seq_printf(s, "\tVsync width: \t%d\n", ret ? -1 : val); |
| |
| ret = nvsr->reg_ops.read(nvsr, NVSR_SRMODE_TIMING19, 1, &val); |
| val2 = val & NVSR_SRMODE_TIMING19_VERT_BORDER; |
| val = (val & NVSR_SRMODE_TIMING19_VSYNC_POL_MASK) >> |
| NVSR_SRMODE_TIMING19_VSYNC_POL_SHIFT; |
| seq_printf(s, "\tVborder: \t%d\n", ret ? -1 : val2); |
| seq_printf(s, "\tVsync pulse polarity: \t%d\n", ret ? -1 : val); |
| seq_printf(s, "\tFrame time: \t%dus\n", nvsr->sr_timing_frame_time_us); |
| |
| tegra_dc_nvsr_put(nvsr); |
| |
| return 0; |
| } |
| |
| static int nvsr_dbg_status_open(struct inode *inode, struct file *file) |
| { |
| return single_open(file, nvsr_dbg_status_show, inode->i_private); |
| } |
| |
| static ssize_t nvsr_dbg_read_write(struct file *file, |
| const char __user *userbuf, size_t count, loff_t *ppos) |
| { |
| struct seq_file *m = file->private_data; |
| struct tegra_dc_nvsr_data *nvsr = m ? m->private : NULL; |
| char buf[128], *addr, *size, *pbuf; |
| u32 uaddr, usize, val; |
| |
| if (!nvsr) |
| return -ENODEV; |
| |
| if (sizeof(buf) <= count) |
| return -EFAULT; |
| |
| if (copy_from_user(buf, userbuf, sizeof(buf))) |
| return -EFAULT; |
| |
| buf[count] = '\0'; |
| strim(buf); |
| |
| pbuf = buf; |
| addr = strsep(&pbuf, " "); |
| size = strsep(&pbuf, " "); |
| |
| if (!size) { |
| NVSR_INFO("Syntax error: <addr_hex> <num_bytes>\n"); |
| return -EINVAL; |
| } |
| |
| if (kstrtou32(addr, 16, &uaddr)) { |
| NVSR_INFO("Can't parse address \"%s\"\n", addr); |
| return -EINVAL; |
| } |
| |
| if (kstrtou32(size, 10, &usize)) { |
| NVSR_INFO("Can't parse data size\"%s\"\n", size); |
| return -EINVAL; |
| } |
| |
| NVSR_INFO("Reading %u bytes from address 0x%x:\n", usize, uaddr); |
| |
| tegra_dc_nvsr_get(nvsr); |
| |
| if (nvsr->reg_ops.read(nvsr, uaddr, usize, &val)) |
| NVSR_INFO("Failed to read register.\n"); |
| |
| NVSR_INFO("NVSR: Read back 0x%x\n", val); |
| |
| tegra_dc_nvsr_put(nvsr); |
| |
| return count; |
| } |
| |
| static ssize_t nvsr_dbg_write_write(struct file *file, |
| const char __user *userbuf, size_t count, loff_t *ppos) |
| { |
| struct seq_file *m = file->private_data; |
| struct tegra_dc_nvsr_data *nvsr = m ? m->private : NULL; |
| char buf[128], *addr, *val, *size, *pbuf; |
| u32 uaddr, usize; |
| u64 uval; |
| |
| if (!nvsr) |
| return -ENODEV; |
| |
| if (sizeof(buf) <= count) |
| return -EFAULT; |
| |
| if (copy_from_user(buf, userbuf, sizeof(buf))) |
| return -EFAULT; |
| |
| buf[count] = '\0'; |
| strim(buf); |
| |
| pbuf = buf; |
| addr = strsep(&pbuf, " "); |
| val = strsep(&pbuf, " "); |
| size = strsep(&pbuf, " "); |
| |
| if (!val || !size) { |
| NVSR_INFO("Syntax error: <addr_hex> <data_hex> <num_bytes>\n"); |
| return -EINVAL; |
| } |
| |
| if (kstrtou32(addr, 16, &uaddr)) { |
| NVSR_INFO("Can't parse address \"%s\"\n", addr); |
| return -EINVAL; |
| } |
| |
| if (kstrtou64(val, 16, &uval)) { |
| NVSR_INFO("Can't parse value\"%s\"\n", val); |
| return -EINVAL; |
| } |
| |
| if (kstrtou32(size, 10, &usize)) { |
| NVSR_INFO("Can't parse data size\"%s\"\n", size); |
| return -EINVAL; |
| } |
| |
| NVSR_INFO("Writing value of 0x%llx (%u bytes) to address 0x%x...\n", |
| uval, usize, uaddr); |
| |
| tegra_dc_nvsr_get(nvsr); |
| |
| if (nvsr->reg_ops.write(nvsr, uaddr, usize, uval)) |
| NVSR_INFO("Failed to write to register.\n"); |
| |
| tegra_dc_nvsr_put(nvsr); |
| |
| return count; |
| } |
| |
| static const struct file_operations nvsr_dbg_read_fops = { |
| .open = nvsr_dbg_read_open, |
| .read = seq_read, |
| .write = nvsr_dbg_read_write, |
| .llseek = seq_lseek, |
| .release = single_release, |
| }; |
| |
| static const struct file_operations nvsr_dbg_write_fops = { |
| .open = nvsr_dbg_write_open, |
| .read = seq_read, |
| .write = nvsr_dbg_write_write, |
| .llseek = seq_lseek, |
| .release = single_release, |
| }; |
| |
| static const struct file_operations nvsr_dbg_status_fops = { |
| .open = nvsr_dbg_status_open, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .release = single_release, |
| }; |
| |
| static void tegra_dc_nvsr_debug_create(struct tegra_dc_nvsr_data *nvsr) |
| { |
| struct dentry *retval, *nvsrdir; |
| |
| nvsrdir = debugfs_create_dir("tegra_nvsr", NULL); |
| if (!nvsrdir) |
| return; |
| |
| retval = debugfs_create_file("read", S_IRUGO | S_IWUGO, |
| nvsrdir, nvsr, &nvsr_dbg_read_fops); |
| if (!retval) |
| goto free_out; |
| retval = debugfs_create_file("write", S_IRUGO | S_IWUGO, |
| nvsrdir, nvsr, &nvsr_dbg_write_fops); |
| if (!retval) |
| goto free_out; |
| retval = debugfs_create_file("status", S_IRUGO, |
| nvsrdir, nvsr, &nvsr_dbg_status_fops); |
| if (!retval) |
| goto free_out; |
| |
| return; |
| |
| free_out: |
| debugfs_remove_recursive(nvsrdir); |
| return; |
| } |
| |
| #else |
| static inline void tegra_dc_nvsr_debug_create(struct tegra_dc_nvsr_data *nvsr) |
| { } |
| #endif |
| |
| static int tegra_dc_nvsr_init(struct tegra_dc *dc) |
| { |
| int ret = 0; |
| |
| struct tegra_dc_nvsr_data *nvsr = kzalloc(sizeof(*nvsr), GFP_KERNEL); |
| if (!nvsr) { |
| ret = -ENOMEM; |
| goto err_nvsr_init_out; |
| } |
| |
| nvsr->dc = dc; |
| dc->nvsr = nvsr; |
| |
| switch (dc->out->type) { |
| case TEGRA_DC_OUT_NVSR_DP: |
| tegra_dc_nvsr_init_out_ops(nvsr, &tegra_dc_dp_ops); |
| if (nvsr->out_ops.init) { |
| ret = nvsr->out_ops.init(dc); |
| NVSR_RETV(ret, "Out ops init failed.\n"); |
| } |
| nvsr->out_data.dp = tegra_dc_get_outdata(dc); |
| nvsr->out_clk = nvsr->out_data.dp->dpaux_clk; |
| nvsr->reg_ops.read = tegra_nvsr_read_dpaux; |
| nvsr->reg_ops.write = tegra_nvsr_write_dpaux; |
| break; |
| default: |
| pr_err("NVSR output interface not found.\n"); |
| ret = -EINVAL; |
| goto err_nvsr_init_free; |
| } |
| |
| tegra_dc_nvsr_debug_create(nvsr); |
| init_completion(&nvsr->framelock_comp); |
| |
| nvsr->enable = true; |
| |
| return ret; |
| |
| err_nvsr_init_free: |
| kfree(nvsr); |
| err_nvsr_init_out: |
| return ret; |
| } |
| |
| struct tegra_dc_out_ops tegra_dc_nvsr_ops = { |
| .init = tegra_dc_nvsr_init, |
| }; |
| |
| static struct kobject *nvsr_kobj; |
| |
| static ssize_t enable_show(struct kobject *kobj, |
| struct kobj_attribute *attr, char *buf) |
| { |
| struct device *dev = container_of((kobj->parent), struct device, kobj); |
| struct platform_device *ndev = to_platform_device(dev); |
| struct tegra_dc *dc = platform_get_drvdata(ndev); |
| |
| return snprintf(buf, PAGE_SIZE, "%d\n", dc->nvsr->enable); |
| } |
| |
| static ssize_t enable_store(struct kobject *kobj, |
| struct kobj_attribute *attr, const char *buf, size_t count) |
| { |
| struct device *dev = container_of((kobj->parent), struct device, kobj); |
| struct platform_device *ndev = to_platform_device(dev); |
| struct tegra_dc *dc = platform_get_drvdata(ndev); |
| struct tegra_dc_nvsr_data *nvsr = dc->nvsr; |
| u32 val; |
| |
| if (kstrtou32(buf, 10, &val) < 0) { |
| dev_err(dev, "NVSR: bad enable input: \"%s\"\n", buf); |
| return -EINVAL; |
| } |
| |
| tegra_dc_nvsr_get(nvsr); |
| |
| if (val) |
| NVSR_RETV(tegra_dc_nvsr_enable_nvsr(nvsr), |
| "Failed to enable NVSR\n") |
| else |
| NVSR_RETV(tegra_dc_nvsr_disable_nvsr(nvsr), |
| "Failed to disable NVSR\n") |
| |
| tegra_dc_nvsr_put(nvsr); |
| |
| return count; |
| } |
| |
| static struct kobj_attribute nvsr_attr_enable = |
| __ATTR(enable, S_IRUGO|S_IWUSR, enable_show, enable_store); |
| |
| static ssize_t idle_show(struct kobject *kobj, |
| struct kobj_attribute *attr, char *buf) |
| { |
| struct device *dev = container_of((kobj->parent), struct device, kobj); |
| struct platform_device *ndev = to_platform_device(dev); |
| struct tegra_dc *dc = platform_get_drvdata(ndev); |
| |
| return snprintf(buf, PAGE_SIZE, "%d\n", dc->nvsr->idle_active); |
| } |
| |
| static ssize_t idle_store(struct kobject *kobj, |
| struct kobj_attribute *attr, const char *buf, size_t count) |
| { |
| struct device *dev = container_of((kobj->parent), struct device, kobj); |
| struct platform_device *ndev = to_platform_device(dev); |
| struct tegra_dc *dc = platform_get_drvdata(ndev); |
| struct tegra_dc_nvsr_data *nvsr = dc->nvsr; |
| u32 val; |
| |
| if (kstrtou32(buf, 10, &val) < 0) { |
| dev_err(dev, "NVSR: bad enable input: \"%s\"\n", buf); |
| return -EINVAL; |
| } |
| |
| if (val) |
| NVSR_RETV(tegra_dc_nvsr_enter_idle(nvsr), |
| "Failed to enter idle\n") |
| else |
| NVSR_RETV(tegra_dc_nvsr_exit_idle(nvsr), |
| "Failed to exit SR\n") |
| |
| return count; |
| } |
| |
| static struct kobj_attribute nvsr_attr_idle = |
| __ATTR(idle, S_IRUGO|S_IWUSR, idle_show, idle_store); |
| |
| static struct attribute *nvsr_attrs[] = { |
| &nvsr_attr_enable.attr, |
| &nvsr_attr_idle.attr, |
| NULL, |
| }; |
| |
| static struct attribute_group nvsr_attr_group = { |
| .attrs = nvsr_attrs, |
| }; |
| |
| /* Sysfs initializer */ |
| int nvsr_create_sysfs(struct device *dev) |
| { |
| int retval; |
| |
| nvsr_kobj = kobject_create_and_add("nvsr", &dev->kobj); |
| |
| if (!nvsr_kobj) |
| return -ENOMEM; |
| |
| retval = sysfs_create_group(nvsr_kobj, &nvsr_attr_group); |
| |
| if (retval) { |
| kobject_put(nvsr_kobj); |
| dev_err(dev, "%s: failed to create attributes\n", __func__); |
| } |
| |
| return retval; |
| } |
| |
| /* Sysfs destructor */ |
| void nvsr_remove_sysfs(struct device *dev) |
| { |
| if (nvsr_kobj) { |
| sysfs_remove_group(nvsr_kobj, &nvsr_attr_group); |
| kobject_put(nvsr_kobj); |
| } |
| } |
| |