summaryrefslogtreecommitdiff
path: root/drivers/video/sprdfb/sprdfb_attr.c
diff options
context:
space:
mode:
authorYuval Adam <_@yuv.al>2017-08-30 08:25:59 +0000
committerYuval Adam <_@yuv.al>2017-08-30 08:25:59 +0000
commit8e4bff4cab0ddac6060645b0715210484d02ff40 (patch)
tree3a5c3024371a04692a3a6d9974d001cdff8ebf84 /drivers/video/sprdfb/sprdfb_attr.c
Initial file dump from open source releaseHEADmaster
Diffstat (limited to 'drivers/video/sprdfb/sprdfb_attr.c')
-rw-r--r--drivers/video/sprdfb/sprdfb_attr.c429
1 files changed, 429 insertions, 0 deletions
diff --git a/drivers/video/sprdfb/sprdfb_attr.c b/drivers/video/sprdfb/sprdfb_attr.c
new file mode 100644
index 00000000..a5d53581
--- /dev/null
+++ b/drivers/video/sprdfb/sprdfb_attr.c
@@ -0,0 +1,429 @@
+/*
+ * Copyright (C) 2014 Spreadtrum Communications Inc.
+ *
+ * Author: Haibing.Yang <haibing.yang@spreadtrum.com>
+ *
+ * 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.
+ */
+
+#define pr_fmt(fmt) "sprdfb: " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/fb.h>
+#include "sprdfb.h"
+#include "sprdfb_panel.h"
+
+
+struct attr_info {
+ struct sprdfb_device *fb_dev;
+ /* clock management */
+ u32 origin_pclk;
+ u32 curr_pclk;
+ u32 origin_fps;
+ u32 curr_fps;
+ u32 origin_mipi_clk;
+ u32 curr_mipi_clk;
+
+ struct semaphore sem;
+};
+
+static ssize_t sysfs_rd_current_pclk(struct device *dev,
+ struct device_attribute *attr, char *buf);
+static ssize_t sysfs_write_pclk(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count);
+static ssize_t sysfs_rd_current_fps(struct device *dev,
+ struct device_attribute *attr, char *buf);
+static ssize_t sysfs_write_fps(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count);
+static ssize_t sysfs_rd_current_mipi_clk(struct device *dev,
+ struct device_attribute *attr, char *buf);
+static ssize_t sysfs_write_mipi_clk(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count);
+static ssize_t sysfs_rd_current_frame_count(struct device *dev,
+ struct device_attribute *attr, char *buf);
+
+#ifdef CONFIG_FB_ESD_SUPPORT
+static ssize_t sysfs_rd_current_esd(struct device *dev,
+ struct device_attribute *attr, char *buf);
+static ssize_t sysfs_write_esd(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count);
+#endif
+
+static DEVICE_ATTR(dynamic_pclk, S_IRUGO | S_IWUSR, sysfs_rd_current_pclk,
+ sysfs_write_pclk);
+static DEVICE_ATTR(dynamic_fps, S_IRUGO | S_IWUSR, sysfs_rd_current_fps,
+ sysfs_write_fps);
+static DEVICE_ATTR(dynamic_mipi_clk, S_IRUGO | S_IWUSR,
+ sysfs_rd_current_mipi_clk, sysfs_write_mipi_clk);
+static DEVICE_ATTR(dynamic_frame_count, S_IRUGO,
+ sysfs_rd_current_frame_count, NULL);
+
+#ifdef CONFIG_FB_ESD_SUPPORT
+static DEVICE_ATTR(dynamic_esd, S_IRUGO | S_IWUSR,
+ sysfs_rd_current_esd, sysfs_write_esd);
+#endif
+
+static struct attribute *sprdfb_fs_attrs[] = {
+ &dev_attr_dynamic_pclk.attr,
+ &dev_attr_dynamic_fps.attr,
+ &dev_attr_dynamic_mipi_clk.attr,
+ &dev_attr_dynamic_frame_count.attr,
+
+#ifdef CONFIG_FB_ESD_SUPPORT
+ &dev_attr_dynamic_esd.attr,
+#endif
+ NULL,
+};
+
+static struct attribute_group sprdfb_attrs_group = {
+ .attrs = sprdfb_fs_attrs,
+};
+
+/**
+ * @fb_dev - sprdfb specific device
+ * @type: SPRDFB_DYNAMIC_PCLK, SPRDFB_DYNAMIC_FPS, SPRDFB_DYNAMIC_MIPI_CLK
+ * @new_val: new required fps, dpi clock or mipi clock
+ *
+ * If clk is set unsuccessfully, this function returns minus.
+ * It returns 0 if clk is set successfully.
+ */
+int sprdfb_chg_clk_intf(struct sprdfb_device *fb_dev,
+ int type, u32 new_val)
+{
+ int ret;
+ struct attr_info *attr = fb_dev->priv1;
+
+ down(&attr->sem);
+ ret = sprdfb_dispc_chg_clk(fb_dev, type, new_val);
+ up(&attr->sem);
+
+ return ret;
+}
+
+static ssize_t sysfs_rd_current_pclk(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int ret;
+ struct fb_info *fbi = dev_get_drvdata(dev);
+ struct sprdfb_device *fb_dev = (struct sprdfb_device *)fbi->par;
+ struct attr_info *attr_info = fb_dev->priv1;
+
+ if (!fb_dev) {
+ pr_err("fb_dev can't be found\n");
+ return -ENXIO;
+ }
+ ret = snprintf(buf, PAGE_SIZE,
+ "current dpi_clk: %u\nnew dpi_clk: %u\norigin dpi_clk: %u\n",
+ fb_dev->dpi_clock, attr_info->curr_pclk,
+ attr_info->origin_pclk);
+
+ return ret;
+}
+
+static ssize_t sysfs_write_pclk(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+ int divider;
+ u32 dpi_clk_src, new_pclk;
+ struct fb_info *fbi = dev_get_drvdata(dev);
+ struct sprdfb_device *fb_dev = (struct sprdfb_device *)fbi->par;
+ struct attr_info *attr_info = fb_dev->priv1;
+
+ sscanf(buf, "%u,%d\n", &dpi_clk_src, &divider);
+
+ if (divider == 0 && dpi_clk_src == 0) {
+ new_pclk = attr_info->origin_pclk;
+ goto DIRECT_GO;
+ }
+
+ if (divider < 1 || divider > 0xff || dpi_clk_src < 1) {
+ pr_err("divider:[%d], clk_src:[%d] is invalid\n",
+ divider, dpi_clk_src);
+ return count;
+ }
+
+ if (dpi_clk_src < 1000)
+ dpi_clk_src *= 1000000; /* MHz */
+
+ new_pclk = dpi_clk_src / divider;
+
+DIRECT_GO:
+ if (new_pclk == fb_dev->dpi_clock) {
+ /* Do nothing */
+ pr_warn("new pclk is the same as current pclk\n");
+ return count;
+ }
+
+ ret = sprdfb_chg_clk_intf(fb_dev, SPRDFB_DYNAMIC_PCLK, new_pclk);
+ if (ret) {
+ pr_err("%s: failed to change dpi clock. ret=%d\n",
+ __func__, ret);
+ return ret;
+ }
+ attr_info->curr_pclk = new_pclk;
+
+ return count;
+}
+
+static ssize_t sysfs_rd_current_fps(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int ret;
+ struct fb_info *fbi = dev_get_drvdata(dev);
+ struct sprdfb_device *fb_dev = (struct sprdfb_device *)fbi->par;
+ struct attr_info *attr_info = fb_dev->priv1;
+ struct panel_spec* panel = fb_dev->panel;
+
+ if (!panel) {
+ pr_err("panel doesn't exist.");
+ return -ENXIO;
+ }
+ ret = snprintf(buf, PAGE_SIZE,
+ "current fps: %u\nnew fps: %u\norigin fps: %u\n",
+ panel->fps, attr_info->curr_fps, attr_info->origin_fps);
+
+ return ret;
+}
+
+static ssize_t sysfs_write_fps(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret, fps;
+ struct fb_info *fbi = dev_get_drvdata(dev);
+ struct sprdfb_device *fb_dev = (struct sprdfb_device *)fbi->par;
+ struct attr_info *attr_info = fb_dev->priv1;
+ struct panel_spec* panel = fb_dev->panel;
+
+ if (!panel) {
+ pr_err("panel doesn't exist.");
+ return -ENXIO;
+ }
+ ret = kstrtoint(buf, 10, &fps);
+
+ fps = fps > 0 ? fps : attr_info->origin_fps;
+
+ if (panel->fps == fps) {
+ /* Do nothing */
+ pr_warn("new fps is the same as current fps\n");
+ return count;
+ }
+
+ ret = sprdfb_chg_clk_intf(fb_dev, SPRDFB_DYNAMIC_FPS, (u32)fps);
+ if (ret) {
+ pr_err("%s: failed to change dpi clock. ret=%d\n",
+ __func__, ret);
+ return ret;
+ }
+ attr_info->curr_fps = fps;
+
+ return count;
+}
+
+static ssize_t sysfs_rd_current_mipi_clk(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int ret = 0;
+ struct fb_info *fbi = dev_get_drvdata(dev);
+ struct sprdfb_device *fb_dev = (struct sprdfb_device *)fbi->par;
+ struct attr_info *attr_info = fb_dev->priv1;
+ struct panel_spec* panel = fb_dev->panel;
+
+ if (!panel) {
+ pr_err("panel doesn't exist.");
+ return -ENXIO;
+ }
+ if (panel->type != LCD_MODE_DSI) {
+ pr_err("Current panel is not mipi dsi\n");
+ ret = snprintf(buf, PAGE_SIZE,
+ "Current panel is not mipi dsi\n");
+ return ret;
+ }
+
+ ret = snprintf(buf, PAGE_SIZE,
+ "current mipi d-phy frequency: %u\n"
+ "new mipi d-phy frequency: %u\n"
+ "origin mipi d-phy frequency: %u\n",
+ panel->info.mipi->phy_feq,
+ attr_info->curr_mipi_clk,
+ attr_info->origin_mipi_clk);
+ return ret;
+}
+
+static ssize_t sysfs_write_mipi_clk(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret = 0;
+ u32 dphy_freq;
+ struct fb_info *fbi = dev_get_drvdata(dev);
+ struct sprdfb_device *fb_dev = (struct sprdfb_device *)fbi->par;
+ struct attr_info *attr_info = fb_dev->priv1;
+ struct panel_spec* panel = fb_dev->panel;
+
+ if (!panel) {
+ pr_err("panel doesn't exist.");
+ return -ENXIO;
+ }
+ if (panel->type != LCD_MODE_DSI) {
+ pr_err("sys write failure. Current panel is not mipi dsi\n");
+ return count;
+ }
+ ret = kstrtoint(buf, 10, &dphy_freq);
+ if (ret) {
+ pr_err("Invalid input for dphy_freq\n");
+ return -EINVAL;
+ }
+
+ if (dphy_freq > 0) {
+ /*
+ * because of double edge trigger,
+ * the rule is actual freq * 10 / 2,
+ * Eg: Required freq is 500M
+ * Equation: 2500*2*1000/10=500*1000=2500*200=500M
+ */
+ dphy_freq *= 200;
+ } else
+ dphy_freq = attr_info->origin_mipi_clk;
+
+ /* dphy supported freq ranges is 90M-1500M*/
+ pr_debug("input dphy_freq is %d\n", dphy_freq);
+
+ if (dphy_freq == attr_info->curr_mipi_clk) {
+ /* Do nothing */
+ pr_warn("new dphy_freq is the same as current freq\n");
+ return count;
+ }
+
+ if (dphy_freq <= 1500000 && dphy_freq >= 90000) {
+ ret = sprdfb_chg_clk_intf(fb_dev, SPRDFB_DYNAMIC_MIPI_CLK, dphy_freq);
+ if (ret) {
+ pr_err("sprdfb_chg_clk_intf change d-phy freq fail.\n");
+ return count;
+ }
+ attr_info->curr_mipi_clk = dphy_freq;
+ } else {
+ pr_warn("input mipi frequency:%d is out of range.\n",
+ dphy_freq);
+ }
+
+ return count;
+}
+
+static ssize_t sysfs_rd_current_frame_count(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int ret;
+ struct fb_info *fbi = dev_get_drvdata(dev);
+ struct sprdfb_device *fb_dev = (struct sprdfb_device *)fbi->par;
+
+ if (!fb_dev) {
+ pr_err("fb_dev can't be found\n");
+ return -ENXIO;
+ }
+ ret = snprintf(buf, PAGE_SIZE,
+ "current frame_count: %lld\n", fb_dev->frame_count);
+
+ return ret;
+}
+
+#ifdef CONFIG_FB_ESD_SUPPORT
+static ssize_t sysfs_rd_current_esd(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int ret;
+ struct fb_info *fbi = dev_get_drvdata(dev);
+ struct sprdfb_device *fb_dev = (struct sprdfb_device *)fbi->par;
+// struct attr_info *attr_info = fb_dev->priv1;
+
+ if (!fb_dev) {
+ pr_err("fb_dev can't be found\n");
+ return -ENXIO;
+ }
+ ret = snprintf(buf, PAGE_SIZE,
+ "current esd: %u\n",fb_dev->ESD_work_start);
+
+ return ret;
+}
+
+static ssize_t sysfs_write_esd(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret, esd;
+ struct fb_info *fbi = dev_get_drvdata(dev);
+ struct sprdfb_device *fb_dev = (struct sprdfb_device *)fbi->par;
+// struct attr_info *attr_info = fb_dev->priv1;
+
+ ret = kstrtoint(buf, 10, &esd);
+
+ esd = (esd == 1) ? 1:0;
+
+ if ((1 == esd) && (fb_dev->enable == 1)) {
+ if (!fb_dev->ESD_work_start) {
+ printk("sprdfb: schedule ESD work queue!\n");
+ schedule_delayed_work(&fb_dev->ESD_work, msecs_to_jiffies(fb_dev->ESD_timeout_val));
+ fb_dev->ESD_work_start = true;
+ }
+ } else {
+ if (fb_dev->ESD_work_start == true) {
+ printk("sprdfb: cancel ESD work queue\n");
+ cancel_delayed_work_sync(&fb_dev->ESD_work);
+ fb_dev->ESD_work_start = false;
+ }
+ }
+ return count;
+}
+#endif
+
+int sprdfb_create_sysfs(struct sprdfb_device *fb_dev)
+{
+ int rc;
+ struct panel_spec* panel = fb_dev->panel;
+ struct attr_info *attr;
+
+ fb_dev->priv1 = kzalloc(sizeof(struct attr_info), GFP_KERNEL);
+ if (!fb_dev->priv1) {
+ pr_err("shortage of memory\n");
+ return -ENOMEM;
+ }
+ attr = fb_dev->priv1;
+
+ rc = sysfs_create_group(&fb_dev->fb->dev->kobj, &sprdfb_attrs_group);
+ if (rc)
+ pr_err("sysfs group creation failed, rc=%d\n", rc);
+
+ attr->origin_pclk = fb_dev->dpi_clock;
+ if (panel) {
+ attr->origin_fps = panel->fps;
+ if (panel->type == LCD_MODE_DSI)
+ attr->origin_mipi_clk = panel->info.mipi->phy_feq;
+ }
+ sema_init(&attr->sem, 1);
+
+ return rc;
+}
+
+void sprdfb_remove_sysfs(struct sprdfb_device *fb_dev)
+{
+ struct attr_info *attr = fb_dev->priv1;
+ sysfs_remove_group(&fb_dev->fb->dev->kobj, &sprdfb_attrs_group);
+
+ if (attr)
+ kfree(attr);
+}