--- /dev/null
+/*
+ * 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 = max_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_UPDATE, DC_CMD_STATE_CONTROL);
+ 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 bool tegra_dc_nvsr_early_enable(struct tegra_dc *dc)
+{
+ struct tegra_dc_nvsr_data *nvsr = dc->nvsr;
+ if (nvsr->out_ops.early_enable)
+ return nvsr->out_ops.early_enable(dc);
+
+ return true;
+}
+
+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->early_enable) {
+ tegra_dc_nvsr_ops.early_enable = tegra_dc_nvsr_early_enable;
+ nvsr->out_ops.early_enable = out_ops->early_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_printf(s, "NVSR has not been initialized.\n");
+ return -EINVAL;
+ }
+
+ tegra_dc_nvsr_get(nvsr);
+
+ seq_printf(s, "SRC Info\n");
+ seq_printf(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_printf(s, "\t\tImmediate\n");
+ else {
+ if (nvsr->cap.resync &
+ NVSR_SR_CAPS0_RESYNC_CAP_FL)
+ seq_printf(s, "\t\tFramelock\n");
+ if (nvsr->cap.resync &
+ NVSR_SR_CAPS0_RESYNC_CAP_BS)
+ seq_printf(s, "\t\tBlank stretching\n");
+ if (nvsr->cap.resync &
+ NVSR_SR_CAPS0_RESYNC_CAP_SS)
+ seq_printf(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_printf(s, "\n");
+ seq_printf(s, "SRC status\n");
+ seq_printf(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_printf(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_printf(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 int 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 int 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;
+
+ 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->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);
+ }
+}
+