/* * dc_sysfs.c: dc driver sysfs interface. * * Copyright (c) 2011-2019, NVIDIA CORPORATION, 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 as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * 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. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include "dc.h" #include "dc_reg.h" #include "dc_priv.h" #include "vrr.h" static ssize_t mode_show(struct device *device, struct device_attribute *attr, char *buf) { struct platform_device *ndev = to_platform_device(device); struct tegra_dc *dc = platform_get_drvdata(ndev); struct tegra_dc_mode *m; ssize_t res; mutex_lock(&dc->lock); m = &dc->mode; res = snprintf(buf, PAGE_SIZE, "pclk: %d\n" "h_ref_to_sync: %d\n" "v_ref_to_sync: %d\n" "h_sync_width: %d\n" "v_sync_width: %d\n" "h_back_porch: %d\n" "v_back_porch: %d\n" "h_active: %d\n" "v_active: %d\n" "h_front_porch: %d\n" "v_front_porch: %d\n" "stereo_mode: %d\n", m->pclk, m->h_ref_to_sync, m->v_ref_to_sync, m->h_sync_width, m->v_sync_width, m->h_back_porch, m->v_back_porch, m->h_active, m->v_active, m->h_front_porch, m->v_front_porch, m->stereo_mode); mutex_unlock(&dc->lock); return res; } static DEVICE_ATTR(mode, 0444, mode_show, NULL); static ssize_t stats_enable_show(struct device *dev, struct device_attribute *attr, char *buf) { struct platform_device *ndev = to_platform_device(dev); struct tegra_dc *dc = platform_get_drvdata(ndev); bool enabled; if (mutex_lock_killable(&dc->lock)) return -EINTR; enabled = tegra_dc_stats_get(dc); mutex_unlock(&dc->lock); return snprintf(buf, PAGE_SIZE, "%d\n", enabled); } static ssize_t stats_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct platform_device *ndev = to_platform_device(dev); struct tegra_dc *dc = platform_get_drvdata(ndev); unsigned long val = 0; if (kstrtoul(buf, 10, &val) < 0) return -EINVAL; if (mutex_lock_killable(&dc->lock)) return -EINTR; tegra_dc_stats_enable(dc, !!val); mutex_unlock(&dc->lock); return count; } static DEVICE_ATTR(stats_enable, 0644, stats_enable_show, stats_enable_store); static ssize_t enable_show(struct device *device, struct device_attribute *attr, char *buf) { struct platform_device *ndev = to_platform_device(device); struct tegra_dc *dc = platform_get_drvdata(ndev); ssize_t res; mutex_lock(&dc->lock); res = snprintf(buf, PAGE_SIZE, "%d\n", dc->enabled); mutex_unlock(&dc->lock); return res; } static ssize_t enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct platform_device *ndev = to_platform_device(dev); struct tegra_dc *dc = platform_get_drvdata(ndev); unsigned long val = 0; if (kstrtoul(buf, 10, &val) < 0) return -EINVAL; if (val) tegra_dc_enable(dc); else tegra_dc_disable(dc); return count; } static DEVICE_ATTR(enable, 0600, enable_show, enable_store); static ssize_t crc_checksum_latched_show(struct device *device, struct device_attribute *attr, char *buf) { struct platform_device *ndev = to_platform_device(device); struct tegra_dc *dc = platform_get_drvdata(ndev); u32 crc; if (atomic_read(&dc->crc_ref_cnt.global)) return -EBUSY; if (!dc->enabled) { dev_err(&dc->ndev->dev, "%s: DC not enabled.\n", __func__); return -EFAULT; } if (tegra_dc_is_nvdisplay()) crc = tegra_nvdisp_sysfs_read_rg_crc(dc); else crc = tegra_dc_sysfs_read_checksum_latched(dc); return snprintf(buf, PAGE_SIZE, "%u\n", crc); } static ssize_t crc_checksum_latched_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct platform_device *ndev = to_platform_device(dev); struct tegra_dc *dc = platform_get_drvdata(ndev); unsigned long val = 0; if (!dc->enabled) { dev_err(&dc->ndev->dev, "%s: DC not enabled.\n", __func__); return -EFAULT; } if (atomic_read(&dc->crc_ref_cnt.global)) return -EBUSY; if (kstrtoul(buf, 10, &val) < 0) return -EINVAL; if (val == 1) { if (tegra_dc_is_nvdisplay()) tegra_nvdisp_sysfs_enable_crc(dc); else tegra_dc_sysfs_enable_crc(dc); dev_dbg(&dc->ndev->dev, "crc is enabled.\n"); } else if (val == 0) { if (tegra_dc_is_nvdisplay()) tegra_nvdisp_sysfs_disable_crc(dc); else tegra_dc_sysfs_disable_crc(dc); dev_dbg(&dc->ndev->dev, "crc is disabled.\n"); } else dev_err(&dc->ndev->dev, "Invalid input.\n"); return count; } static DEVICE_ATTR(crc_checksum_latched, 0644, crc_checksum_latched_show, crc_checksum_latched_store); static ssize_t out_crc_show(struct device *device, struct device_attribute *attr, char *buf) { struct platform_device *ndev = to_platform_device(device); struct tegra_dc *dc = platform_get_drvdata(ndev); u32 crc = 0; if (!dc->enabled) { dev_err(&dc->ndev->dev, "%s: DC not enabled.\n", __func__); return -EFAULT; } if (dc->out_ops && dc->out_ops->get_crc) crc = dc->out_ops->get_crc(dc); return snprintf(buf, PAGE_SIZE, "%u\n", crc); } static ssize_t out_crc_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct platform_device *ndev = to_platform_device(dev); struct tegra_dc *dc = platform_get_drvdata(ndev); unsigned long val = 0; if (!dc->enabled) { dev_err(&dc->ndev->dev, "%s: DC not enabled.\n", __func__); return -EFAULT; } if (kstrtoul(buf, 10, &val) < 0) return -EINVAL; if (val > 1) { dev_err(&dc->ndev->dev, "Invalid input.\n"); return count; } if (dc->out_ops && dc->out_ops->toggle_crc) dc->out_ops->toggle_crc(dc, val); if (val == 1) dev_dbg(&dc->ndev->dev, "crc is enabled.\n"); else if (val == 0) dev_dbg(&dc->ndev->dev, "crc is disabled.\n"); return count; } static DEVICE_ATTR(out_crc, 0644, out_crc_show, out_crc_store); static ssize_t scanline_show(struct device *device, struct device_attribute *attr, char *buf) { struct platform_device *ndev = to_platform_device(device); struct tegra_dc *dc = platform_get_drvdata(ndev); u32 val; unsigned v_blank; unsigned v_count; unsigned h_blank; unsigned h_count; if (WARN_ON(!dc) || !dc->enabled || WARN_ON(!tegra_dc_is_powered(dc))) { dev_err(&dc->ndev->dev, "%s: DC not enabled.\n", __func__); return -ENXIO; } val = tegra_dc_readl(dc, DC_DISP_DISPLAY_DBG_TIMING); v_count = (val & DBG_V_COUNT_MASK) >> DBG_V_COUNT_SHIFT; v_blank = !!(val & DBG_V_BLANK); h_count = (val & DBG_H_COUNT_MASK) >> DBG_H_COUNT_SHIFT; h_blank = !!(val & DBG_H_BLANK); return scnprintf(buf, PAGE_SIZE, "%u %u %u %u\n", v_count, v_blank, h_count, h_blank); } static DEVICE_ATTR(scanline, 0444, scanline_show, NULL); #define ORIENTATION_PORTRAIT "portrait" #define ORIENTATION_LANDSCAPE "landscape" static ssize_t orientation_3d_show(struct device *dev, struct device_attribute *attr, char *buf) { struct platform_device *ndev = to_platform_device(dev); struct tegra_dc *dc = platform_get_drvdata(ndev); struct tegra_dc_out *dc_out = dc->out; const char *orientation; switch (dc_out->stereo->orientation) { case TEGRA_DC_STEREO_LANDSCAPE: orientation = ORIENTATION_LANDSCAPE; break; case TEGRA_DC_STEREO_PORTRAIT: orientation = ORIENTATION_PORTRAIT; break; default: pr_err("Invalid value is stored for stereo_orientation.\n"); return -EINVAL; } return snprintf(buf, PAGE_SIZE, "%s\n", orientation); } static ssize_t orientation_3d_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t cnt) { struct platform_device *ndev = to_platform_device(dev); struct tegra_dc *dc = platform_get_drvdata(ndev); struct tegra_dc_out *dc_out = dc->out; struct tegra_stereo_out *stereo = dc_out->stereo; int orientation; if (0 == strncmp(buf, ORIENTATION_PORTRAIT, min(cnt, ARRAY_SIZE(ORIENTATION_PORTRAIT) - 1))) { orientation = TEGRA_DC_STEREO_PORTRAIT; } else if (0 == strncmp(buf, ORIENTATION_LANDSCAPE, min(cnt, ARRAY_SIZE(ORIENTATION_LANDSCAPE) - 1))) { orientation = TEGRA_DC_STEREO_LANDSCAPE; } else { pr_err("Invalid property value for stereo_orientation.\n"); return -EINVAL; } stereo->orientation = orientation; stereo->set_orientation(orientation); return cnt; } static DEVICE_ATTR(stereo_orientation, 0644, orientation_3d_show, orientation_3d_store); #define MODE_2D "2d" #define MODE_3D "3d" static ssize_t mode_3d_show(struct device *dev, struct device_attribute *attr, char *buf) { struct platform_device *ndev = to_platform_device(dev); struct tegra_dc *dc = platform_get_drvdata(ndev); struct tegra_dc_out *dc_out = dc->out; const char *mode; switch (dc_out->stereo->mode_2d_3d) { case TEGRA_DC_STEREO_MODE_2D: mode = MODE_2D; break; case TEGRA_DC_STEREO_MODE_3D: mode = MODE_3D; break; default: pr_err("Invalid value is stored for stereo_mode.\n"); return -EINVAL; } return snprintf(buf, PAGE_SIZE, "%s\n", mode); } static ssize_t mode_3d_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t cnt) { struct platform_device *ndev = to_platform_device(dev); struct tegra_dc *dc = platform_get_drvdata(ndev); struct tegra_dc_out *dc_out = dc->out; struct tegra_stereo_out *stereo = dc_out->stereo; int mode; if (0 == strncmp(buf, MODE_2D, min(cnt, ARRAY_SIZE(MODE_2D) - 1))) { mode = TEGRA_DC_STEREO_MODE_2D; } else if (0 == strncmp(buf, MODE_3D, min(cnt, ARRAY_SIZE(MODE_3D) - 1))) { mode = TEGRA_DC_STEREO_MODE_3D; } else { pr_err("Invalid property value for stereo_mode.\n"); return -EINVAL; } stereo->mode_2d_3d = mode; stereo->set_mode(mode); return cnt; } static DEVICE_ATTR(stereo_mode, 0644, mode_3d_show, mode_3d_store); static ssize_t cmu_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int val; int e; struct platform_device *ndev = to_platform_device(dev); struct tegra_dc *dc = platform_get_drvdata(ndev); e = kstrtoint(buf, 10, &val); if (e) return e; tegra_dc_cmu_enable(dc, val); return count; } static ssize_t cmu_enable_show(struct device *dev, struct device_attribute *attr, char *buf) { struct platform_device *ndev = to_platform_device(dev); struct tegra_dc *dc = platform_get_drvdata(ndev); return snprintf(buf, PAGE_SIZE, "%d\n", dc->pdata->cmu_enable); } static DEVICE_ATTR(cmu_enable, 0644, cmu_enable_show, cmu_enable_store); #ifdef CONFIG_TEGRA_ISOMGR static ssize_t reserved_bw_show(struct device *dev, struct device_attribute *attr, char *buf) { struct platform_device *ndev = to_platform_device(dev); struct tegra_dc *dc = platform_get_drvdata(ndev); u32 reserved_bw = 0; if (tegra_dc_is_nvdisplay()) { if (dc->ihub_bw_info) reserved_bw = dc->ihub_bw_info->reserved_bw; } else { reserved_bw = dc->reserved_bw; } return snprintf(buf, PAGE_SIZE, "%d\n", reserved_bw); } static DEVICE_ATTR(reserved_bw, 0444, reserved_bw_show, NULL); #endif static ssize_t smart_panel_show(struct device *device, struct device_attribute *attr, char *buf) { return snprintf(buf, PAGE_SIZE, "1\n"); } static DEVICE_ATTR(smart_panel, 0444, smart_panel_show, NULL); static ssize_t panel_rotate_show(struct device *device, struct device_attribute *attr, char *buf) { struct platform_device *ndev = to_platform_device(device); struct tegra_dc *dc = platform_get_drvdata(ndev); return snprintf(buf, PAGE_SIZE, "%d\n", dc->out->rotation); } static DEVICE_ATTR(panel_rotation, 0444, panel_rotate_show, NULL); /* display current window assignment bitmask in * hexadecimal for the given dc device->dev */ static ssize_t win_mask_show(struct device *device, struct device_attribute *attr, char *buf) { struct platform_device *ndev = to_platform_device(device); struct tegra_dc *dc = platform_get_drvdata(ndev); ssize_t res; if ((!dc) || (!dc->ndev) || (!dc->pdata)) { pr_err("%s: dc|device err\n", __func__); res = -EINVAL; goto exit; } mutex_lock(&dc->lock); res = snprintf(buf, PAGE_SIZE, "0x%lx\n", dc->pdata->win_mask); mutex_unlock(&dc->lock); exit: return res; } static ssize_t win_mask_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct platform_device *ndev = to_platform_device(dev); struct tegra_dc *dc = platform_get_drvdata(ndev); unsigned long requested_winmask = 0; size_t ret; if (!tegra_dc_is_nvdisplay()) return -EINVAL; /* try first as decimal, then as hexadecimal */ if ((kstrtoul(buf, 10, &requested_winmask) < 0) && (kstrtoul(buf, 0, &requested_winmask) < 0)) return -EINVAL; ret = tegra_dc_update_winmask(dc, requested_winmask); if (!ret) return count; return ret; } static DEVICE_ATTR(win_mask, 0600, win_mask_show, win_mask_store); static ssize_t panel_connected_show(struct device *device, struct device_attribute *attr, char *buf) { struct platform_device *ndev = to_platform_device(device); struct tegra_dc *dc = platform_get_drvdata(ndev); return snprintf(buf, PAGE_SIZE, "%d\n", dc->connected); } static DEVICE_ATTR(panel_connected, 0444, panel_connected_show, NULL); void tegra_dc_remove_sysfs(struct device *dev) { struct platform_device *ndev = to_platform_device(dev); struct tegra_dc *dc = platform_get_drvdata(ndev); device_remove_file(dev, &dev_attr_mode); device_remove_file(dev, &dev_attr_enable); device_remove_file(dev, &dev_attr_stats_enable); device_remove_file(dev, &dev_attr_crc_checksum_latched); if ((dc->out->type == TEGRA_DC_OUT_HDMI) || (dc->out->type == TEGRA_DC_OUT_DP) || (dc->out->type == TEGRA_DC_OUT_FAKE_DP)) device_remove_file(dev, &dev_attr_out_crc); device_remove_file(dev, &dev_attr_win_mask); #ifdef CONFIG_TEGRA_DC_WIN_H device_remove_file(dev, &dev_attr_win_h); #endif device_remove_file(dev, &dev_attr_cmu_enable); #ifdef CONFIG_TEGRA_ISOMGR device_remove_file(dev, &dev_attr_reserved_bw); #endif device_remove_file(dev, &dev_attr_panel_connected); if (dc->out->stereo) { device_remove_file(dev, &dev_attr_stereo_orientation); device_remove_file(dev, &dev_attr_stereo_mode); } if (dc->fb) tegra_fb_remove_sysfs(dev); if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) device_remove_file(dev, &dev_attr_smart_panel); if (dc->out->type != TEGRA_DC_OUT_HDMI) device_remove_file(dev, &dev_attr_panel_rotation); } void tegra_dc_create_sysfs(struct device *dev) { struct platform_device *ndev = to_platform_device(dev); struct tegra_dc *dc = platform_get_drvdata(ndev); struct tegra_vrr *vrr = dc->out->vrr; int error = 0; error |= device_create_file(dev, &dev_attr_mode); error |= device_create_file(dev, &dev_attr_enable); error |= device_create_file(dev, &dev_attr_stats_enable); error |= device_create_file(dev, &dev_attr_crc_checksum_latched); if ((dc->out->type == TEGRA_DC_OUT_HDMI) || (dc->out->type == TEGRA_DC_OUT_DP) || (dc->out->type == TEGRA_DC_OUT_FAKE_DP)) error |= device_create_file(dev, &dev_attr_out_crc); error |= device_create_file(dev, &dev_attr_win_mask); #ifdef CONFIG_TEGRA_DC_WIN_H error |= device_create_file(dev, &dev_attr_win_h); #endif error |= device_create_file(dev, &dev_attr_cmu_enable); #ifdef CONFIG_TEGRA_ISOMGR error |= device_create_file(dev, &dev_attr_reserved_bw); #endif error |= device_create_file(dev, &dev_attr_scanline); if (dc->out->stereo) { error |= device_create_file(dev, &dev_attr_stereo_orientation); error |= device_create_file(dev, &dev_attr_stereo_mode); } error |= device_create_file(dev, &dev_attr_panel_connected); if (vrr) #ifdef CONFIG_TEGRA_VRR error |= vrr_create_sysfs(dev); #endif if (dc->fb) error |= tegra_fb_create_sysfs(dev); if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) error |= device_create_file(dev, &dev_attr_smart_panel); if (dc->out->type != TEGRA_DC_OUT_HDMI) error |= device_create_file(dev, &dev_attr_panel_rotation); if (error) dev_err(&ndev->dev, "Failed to create sysfs attributes!\n"); }