video: tegra: Added sysfs for dc/smartdimmer
[linux-2.6.git] / drivers / video / tegra / dc / nvsd.c
index 40ab9c1..75b9bf9 100644 (file)
 #include <linux/kernel.h>
 #include <mach/dc.h>
 #include <linux/types.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/backlight.h>
 
 #include "dc_reg.h"
 #include "dc_priv.h"
 #include "nvsd.h"
 
+/* Elements for sysfs access */
+#define NVSD_ATTR(__name) static struct kobj_attribute nvsd_attr_##__name = \
+       __ATTR(__name, S_IRUGO|S_IWUSR|S_IWGRP, nvsd_settings_show, nvsd_settings_store)
+#define NVSD_ATTRS_ENTRY(__name) (&nvsd_attr_##__name.attr)
+#define IS_NVSD_ATTR(__name) (attr == &nvsd_attr_##__name)
+
+static ssize_t nvsd_settings_show(struct kobject *kobj,
+       struct kobj_attribute *attr, char *buf);
+
+static ssize_t nvsd_settings_store(struct kobject *kobj,
+       struct kobj_attribute *attr, const char *buf, size_t count);
+
+static ssize_t nvsd_registers_show(struct kobject *kobj,
+       struct kobj_attribute *attr, char *buf);
+
+NVSD_ATTR(enable);
+NVSD_ATTR(aggressiveness);
+NVSD_ATTR(bin_width);
+NVSD_ATTR(hw_update_delay);
+NVSD_ATTR(use_vid_luma);
+NVSD_ATTR(coeff);
+NVSD_ATTR(blp_time_constant);
+NVSD_ATTR(blp_step);
+NVSD_ATTR(fc_time_limit);
+NVSD_ATTR(fc_threshold);
+NVSD_ATTR(lut);
+NVSD_ATTR(bltf);
+static struct kobj_attribute nvsd_attr_registers =
+       __ATTR(registers, S_IRUGO, nvsd_registers_show, NULL);
+
+static struct attribute *nvsd_attrs[] = {
+       NVSD_ATTRS_ENTRY(enable),
+       NVSD_ATTRS_ENTRY(aggressiveness),
+       NVSD_ATTRS_ENTRY(bin_width),
+       NVSD_ATTRS_ENTRY(hw_update_delay),
+       NVSD_ATTRS_ENTRY(use_vid_luma),
+       NVSD_ATTRS_ENTRY(coeff),
+       NVSD_ATTRS_ENTRY(blp_time_constant),
+       NVSD_ATTRS_ENTRY(blp_step),
+       NVSD_ATTRS_ENTRY(fc_time_limit),
+       NVSD_ATTRS_ENTRY(fc_threshold),
+       NVSD_ATTRS_ENTRY(lut),
+       NVSD_ATTRS_ENTRY(bltf),
+       NVSD_ATTRS_ENTRY(registers),
+       NULL,
+};
+
+static struct attribute_group nvsd_attr_group = {
+       .attrs = nvsd_attrs,
+};
+
+static struct kobject *nvsd_kobj;
+
+/* shared brightness variable */
 static atomic_t *sd_brightness = NULL;
+/* shared boolean for manual K workaround */
+static atomic_t man_k_until_blank = ATOMIC_INIT(0);
 
+/* Functional initialization */
 void nvsd_init(struct tegra_dc *dc, struct tegra_dc_sd_settings *settings) {
        u32 i = 0, val = 0;
        /* TODO: check if HW says SD's available */
@@ -39,20 +99,39 @@ void nvsd_init(struct tegra_dc *dc, struct tegra_dc_sd_settings *settings) {
                return;
        }
 
-       dev_dbg(&dc->ndev->dev, "===================\n");
-       dev_dbg(&dc->ndev->dev, "**** NVSD_INIT ****\n");
+       dev_dbg(&dc->ndev->dev, "NVSD Init:\n");
+
+       /* WAR: Settings will not be valid until the next flip.
+          Thus, set manual K to either HW's current value (if
+          we're already enabled) or a non-effective value (if
+          we're about to enable). */
+       val = tegra_dc_readl(dc, DC_DISP_SD_CONTROL);
+       if (val & SD_ENABLE_NORMAL) {
+               i = tegra_dc_readl(dc, DC_DISP_SD_HW_K_VALUES);
+       }
+       else {
+               /* 0 values for RGB = 1.0, i.e. non-affected */
+               i = 0;
+       }
+       tegra_dc_writel(dc, i, DC_DISP_SD_MAN_K_VALUES);
+       /* Enable manual correction mode here so that changing the
+          settings won't immediately impact display dehavior. */
+       val |= SD_CORRECTION_MODE_MAN;
+       tegra_dc_writel(dc, val, DC_DISP_SD_CONTROL);
 
        /* Write LUT */
+       dev_dbg(&dc->ndev->dev, "  LUT:\n");
        for (i = 0; i < DC_DISP_SD_LUT_NUM; i++) {
                val =   SD_LUT_R(settings->lut[i].r) |
                        SD_LUT_G(settings->lut[i].g) |
                        SD_LUT_B(settings->lut[i].b);
                tegra_dc_writel(dc, val, DC_DISP_SD_LUT(i));
 
-               dev_dbg(&dc->ndev->dev, "LUT(%d): 0x%08x\n", i, val);
+               dev_dbg(&dc->ndev->dev, "    %d: 0x%08x\n", i, val);
        }
 
        /* Write BL TF */
+       dev_dbg(&dc->ndev->dev, "  BL_TF:\n");
        for (i = 0; i < DC_DISP_SD_BL_TF_NUM; i++) {
                val =   SD_BL_TF_POINT_0(settings->bltf[i][0]) |
                        SD_BL_TF_POINT_1(settings->bltf[i][1]) |
@@ -60,7 +139,7 @@ void nvsd_init(struct tegra_dc *dc, struct tegra_dc_sd_settings *settings) {
                        SD_BL_TF_POINT_3(settings->bltf[i][3]);
                tegra_dc_writel(dc, val, DC_DISP_SD_BL_TF(i));
 
-               dev_dbg(&dc->ndev->dev, "BL_TF(%d): 0x%08x\n", i, val);
+               dev_dbg(&dc->ndev->dev, "    %d: 0x%08x\n", i, val);
        }
 
        /* Write Coeff */
@@ -68,27 +147,29 @@ void nvsd_init(struct tegra_dc *dc, struct tegra_dc_sd_settings *settings) {
                SD_CSC_COEFF_G(settings->coeff.g) |
                SD_CSC_COEFF_B(settings->coeff.b);
        tegra_dc_writel(dc, val, DC_DISP_SD_CSC_COEFF);
-       dev_dbg(&dc->ndev->dev, "COEFF: 0x%08x\n", val);
+       dev_dbg(&dc->ndev->dev, "  COEFF: 0x%08x\n", val);
 
        /* Write BL Params */
        val =   SD_BLP_TIME_CONSTANT(settings->blp.time_constant) |
                SD_BLP_STEP(settings->blp.step);
        tegra_dc_writel(dc, val, DC_DISP_SD_BL_PARAMETERS);
-       dev_dbg(&dc->ndev->dev, "BLP: 0x%08x\n", val);
+       dev_dbg(&dc->ndev->dev, "  BLP: 0x%08x\n", val);
 
        /* Write Auto/Manual PWM */
        val = (settings->use_auto_pwm) ? SD_BLC_MODE_AUTO : SD_BLC_MODE_MAN;
        tegra_dc_writel(dc, val, DC_DISP_SD_BL_CONTROL);
-       dev_dbg(&dc->ndev->dev, "BL_CONTROL: 0x%08x\n", val);
+       dev_dbg(&dc->ndev->dev, "  BL_CONTROL: 0x%08x\n", val);
 
        /* Write Flicker Control */
        val =   SD_FC_TIME_LIMIT(settings->fc.time_limit) |
                SD_FC_THRESHOLD(settings->fc.threshold);
        tegra_dc_writel(dc, val, DC_DISP_SD_FLICKER_CONTROL);
-       dev_dbg(&dc->ndev->dev, "FLICKER_CONTROL: 0x%08x\n", val);
+       dev_dbg(&dc->ndev->dev, "  FLICKER_CONTROL: 0x%08x\n", val);
 
        /* Manage SD Control */
        val = 0;
+       /* Stay in manual correction mode until the next flip. */
+       val |= SD_CORRECTION_MODE_MAN;
        /* Enable / One-Shot */
        val |= (settings->enable == 2) ?
                        (SD_ENABLE_ONESHOT | SD_ONESHOT_ENABLE) :
@@ -122,26 +203,29 @@ void nvsd_init(struct tegra_dc *dc, struct tegra_dc_sd_settings *settings) {
                case 8: val |= SD_BIN_WIDTH_EIGHT; break;
        }
 
-       /* TODO: histogram reset WAR? */
-
        /* Finally, Write SD Control */
        tegra_dc_writel(dc, val, DC_DISP_SD_CONTROL);
-       dev_dbg(&dc->ndev->dev, "SD_CONTROL: 0x%08x\n", val);
+       dev_dbg(&dc->ndev->dev, "  SD_CONTROL: 0x%08x\n", val);
 
        /* set the brightness pointer */
        sd_brightness = settings->sd_brightness;
-       dev_dbg(&dc->ndev->dev, "sd_brightness: 0x%08x\n", (u32)sd_brightness);
 
-       dev_dbg(&dc->ndev->dev, "*******************\n");
-       dev_dbg(&dc->ndev->dev, "===================\n");
+       /* note that we're in manual K until the next flip */
+       atomic_set(&man_k_until_blank, 1);
 }
 
+/* Periodic update */
 bool nvsd_update_brightness(struct tegra_dc *dc) {
        u32 val = 0;
        int cur_sd_brightness;
 
        if (sd_brightness) {
-               /* TODO: histogram reset WAR? */
+               if (atomic_read(&man_k_until_blank)) {
+                       val = tegra_dc_readl(dc, DC_DISP_SD_CONTROL);
+                       val &= ~SD_CORRECTION_MODE_MAN;
+                       tegra_dc_writel(dc, val, DC_DISP_SD_CONTROL);
+                       atomic_set(&man_k_until_blank, 0);
+               }
 
                cur_sd_brightness = atomic_read(sd_brightness);
 
@@ -155,9 +239,290 @@ bool nvsd_update_brightness(struct tegra_dc *dc) {
                        atomic_set(sd_brightness, (int)val);
                        return true;
                }
-               /* TODO: log? */
        }
 
        /* No update needed. */
        return false;
 }
+
+/* Sysfs accessors */
+static ssize_t nvsd_settings_show(struct kobject *kobj,
+       struct kobj_attribute *attr, char *buf)
+{
+       struct device *dev = container_of((kobj->parent), struct device, kobj);
+       struct nvhost_device *ndev = to_nvhost_device(dev);
+       struct tegra_dc *dc = nvhost_get_drvdata(ndev);
+       struct tegra_dc_sd_settings *sd_settings = dc->out->sd_settings;
+       ssize_t res = 0;
+
+       if(sd_settings) {
+               if(IS_NVSD_ATTR(enable)) {
+                       res = snprintf(buf, PAGE_SIZE, "%d\n",
+                               sd_settings->enable);
+               }
+               else if(IS_NVSD_ATTR(aggressiveness)) {
+                       res = snprintf(buf, PAGE_SIZE, "%d\n",
+                               sd_settings->aggressiveness);
+               }
+               else if(IS_NVSD_ATTR(bin_width)) {
+                       res = snprintf(buf, PAGE_SIZE, "%d\n",
+                               sd_settings->bin_width);
+               }
+               else if(IS_NVSD_ATTR(hw_update_delay)) {
+                       res = snprintf(buf, PAGE_SIZE, "%d\n",
+                               sd_settings->hw_update_delay);
+               }
+               else if(IS_NVSD_ATTR(use_vid_luma)) {
+                       res = snprintf(buf, PAGE_SIZE, "%d\n",
+                               sd_settings->use_vid_luma);
+               }
+               else if(IS_NVSD_ATTR(coeff)) {
+                       res = snprintf(buf, PAGE_SIZE, "R: %d / G: %d / B: %d\n",
+                               sd_settings->coeff.r,
+                               sd_settings->coeff.g,
+                               sd_settings->coeff.b);
+               }
+               else if(IS_NVSD_ATTR(blp_time_constant)) {
+                       res = snprintf(buf, PAGE_SIZE, "%d\n",
+                               sd_settings->blp.time_constant);
+               }
+               else if(IS_NVSD_ATTR(blp_step)) {
+                       res = snprintf(buf, PAGE_SIZE, "%d\n",
+                               sd_settings->blp.step);
+               }
+               else if(IS_NVSD_ATTR(fc_time_limit)) {
+                       res = snprintf(buf, PAGE_SIZE, "%d\n",
+                               sd_settings->fc.time_limit);
+               }
+               else if(IS_NVSD_ATTR(fc_threshold)) {
+                       res = snprintf(buf, PAGE_SIZE, "%d\n",
+                               sd_settings->fc.threshold);
+               }
+               else if(IS_NVSD_ATTR(lut)) {
+                       u32 i = 0;
+                       for (i = 0; i < DC_DISP_SD_LUT_NUM; i++) {
+                               res += snprintf(buf + res, PAGE_SIZE - res,
+                                       "%d: R: %3d / G: %3d / B: %3d\n",
+                                       i,
+                                       sd_settings->lut[i].r,
+                                       sd_settings->lut[i].g,
+                                       sd_settings->lut[i].b);
+                       }
+               }
+               else if(IS_NVSD_ATTR(bltf)) {
+                       u32 i = 0;
+                       for (i = 0; i < DC_DISP_SD_BL_TF_NUM; i++) {
+                               res += snprintf(buf + res, PAGE_SIZE - res,
+                                       "%d: 0: %3d / 1: %3d / 2: %3d / 3: %3d\n",
+                                       i,
+                                       sd_settings->bltf[i][0],
+                                       sd_settings->bltf[i][1],
+                                       sd_settings->bltf[i][2],
+                                       sd_settings->bltf[i][3]);
+                       }
+               }
+               else {
+                       res = -EINVAL;
+               }
+       }
+       else {
+               /* This shouldn't be reachable. But just in case... */
+               res = -EINVAL;
+       }
+
+       return res;
+}
+
+#define NVSD_CHECK_AND_UPDATE(_min, _max, _varname) { \
+       int val = simple_strtol(buf, NULL, 10); \
+       if (val >= _min && val <= _max) { \
+               sd_settings->_varname = val; \
+               settings_updated = true; \
+       } }
+#define NVSD_GET_MULTI(_ele, _num, _act, _min, _max) { \
+       char *b, *c, *orig_b; \
+       b = orig_b = kstrdup(buf, GFP_KERNEL); \
+       for (_act = 0; _act < _num; _act++) { \
+               if (!b) \
+                       break; \
+               b = strim(b); \
+               c = strsep(&b, " "); \
+               if (!strlen(c)) \
+                       break; \
+               _ele[_act] = simple_strtol(c, NULL, 10); \
+               if (_ele[_act] < _min || _ele[_act] > _max) \
+                       break; \
+       } \
+       if (orig_b) \
+               kfree(orig_b); \
+}
+static ssize_t nvsd_settings_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 nvhost_device *ndev = to_nvhost_device(dev);
+       struct tegra_dc *dc = nvhost_get_drvdata(ndev);
+       struct tegra_dc_sd_settings *sd_settings = dc->out->sd_settings;
+       ssize_t res = count;
+       bool settings_updated = false;
+
+       if(sd_settings) {
+               if(IS_NVSD_ATTR(enable)) {
+                       NVSD_CHECK_AND_UPDATE(0, 1, enable);
+               }
+               else if(IS_NVSD_ATTR(aggressiveness)) {
+                       NVSD_CHECK_AND_UPDATE(1, 5, aggressiveness);
+               }
+               else if(IS_NVSD_ATTR(bin_width)) {
+                       NVSD_CHECK_AND_UPDATE(0, 8, bin_width);
+               }
+               else if(IS_NVSD_ATTR(hw_update_delay)) {
+                       NVSD_CHECK_AND_UPDATE(0, 2, hw_update_delay);
+               }
+               else if(IS_NVSD_ATTR(use_vid_luma)) {
+                       NVSD_CHECK_AND_UPDATE(0, 1, use_vid_luma);
+               }
+               else if(IS_NVSD_ATTR(coeff)) {
+                       int ele[3], i = 0, num = 3;
+                       NVSD_GET_MULTI(ele, num, i, 0, 15);
+                       if (i == num) {
+                               sd_settings->coeff.r = ele[0];
+                               sd_settings->coeff.g = ele[1];
+                               sd_settings->coeff.b = ele[2];
+                               settings_updated = true;
+                       }
+                       else {
+                               res = -EINVAL;
+                       }
+               }
+               else if(IS_NVSD_ATTR(blp_time_constant)) {
+                       NVSD_CHECK_AND_UPDATE(0, 1024, blp.time_constant);
+               }
+               else if(IS_NVSD_ATTR(blp_step)) {
+                       NVSD_CHECK_AND_UPDATE(0, 255, blp.step);
+               }
+               else if(IS_NVSD_ATTR(fc_time_limit)) {
+                       NVSD_CHECK_AND_UPDATE(0, 255, fc.time_limit);
+               }
+               else if(IS_NVSD_ATTR(fc_threshold)) {
+                       NVSD_CHECK_AND_UPDATE(0, 255, fc.threshold);
+               }
+               else if(IS_NVSD_ATTR(lut)) {
+                       int ele[3 * DC_DISP_SD_LUT_NUM];
+                       int i = 0, num = 3 * DC_DISP_SD_LUT_NUM;
+                       NVSD_GET_MULTI(ele, num, i, 0, 255);
+                       if (i == num) {
+                               for (i = 0; i < DC_DISP_SD_LUT_NUM; i++) {
+                                       sd_settings->lut[i].r = ele[i * 3 + 0];
+                                       sd_settings->lut[i].g = ele[i * 3 + 1];
+                                       sd_settings->lut[i].b = ele[i * 3 + 2];
+                               }
+                               settings_updated = true;
+                       }
+                       else {
+                               res = -EINVAL;
+                       }
+               }
+               else if(IS_NVSD_ATTR(bltf)) {
+                       int ele[4 * DC_DISP_SD_BL_TF_NUM];
+                       int i = 0, num = 4 * DC_DISP_SD_BL_TF_NUM;
+                       NVSD_GET_MULTI(ele, num, i, 0, 255);
+                       if (i == num) {
+                               for (i = 0; i < DC_DISP_SD_BL_TF_NUM; i++) {
+                                       sd_settings->bltf[i][0] = ele[i * 4 + 0];
+                                       sd_settings->bltf[i][1] = ele[i * 4 + 1];
+                                       sd_settings->bltf[i][2] = ele[i * 4 + 2];
+                                       sd_settings->bltf[i][3] = ele[i * 4 + 3];
+                               }
+                               settings_updated = true;
+                       }
+                       else {
+                               res = -EINVAL;
+                       }
+               }
+               else {
+                       res = -EINVAL;
+               }
+
+               /* Re-init if our settings were updated. */
+               if (settings_updated) {
+                       nvsd_init(dc, sd_settings);
+                       /* Update backlight state IFF we're disabling! */
+                       if (!sd_settings->enable && sd_settings->bl_device) {
+                               /* Do the actual brightness update outside of the mutex */
+                               struct platform_device *pdev = sd_settings->bl_device;
+                               struct backlight_device *bl = platform_get_drvdata(pdev);
+                               if (bl)
+                                       backlight_update_status(bl);
+                       }
+               }
+       }
+       else {
+               /* This shouldn't be reachable. But just in case... */
+               res = -EINVAL;
+       }
+
+       return res;
+}
+
+#define NVSD_PRINT_REG(__name) { \
+       u32 val = tegra_dc_readl(dc, __name); \
+       res += snprintf(buf + res, PAGE_SIZE - res, #__name ": 0x%08x\n", val); \
+}
+#define NVSD_PRINT_REG_ARRAY(__name) { \
+       u32 val = 0, i = 0; \
+       res += snprintf(buf + res, PAGE_SIZE - res, #__name ":\n"); \
+       for (i = 0; i < __name##_NUM; i++) { \
+               val = tegra_dc_readl(dc, __name(i)); \
+               res += snprintf(buf + res, PAGE_SIZE - res, "  %d: 0x%08x\n", i, val); \
+       } \
+}
+static ssize_t nvsd_registers_show(struct kobject *kobj,
+       struct kobj_attribute *attr, char *buf)
+{
+       struct device *dev = container_of((kobj->parent), struct device, kobj);
+       struct nvhost_device *ndev = to_nvhost_device(dev);
+       struct tegra_dc *dc = nvhost_get_drvdata(ndev);
+       ssize_t res = 0;
+
+       NVSD_PRINT_REG(DC_DISP_SD_CONTROL);
+       NVSD_PRINT_REG(DC_DISP_SD_CSC_COEFF);
+       NVSD_PRINT_REG_ARRAY(DC_DISP_SD_LUT);
+       NVSD_PRINT_REG(DC_DISP_SD_FLICKER_CONTROL);
+       NVSD_PRINT_REG(DC_DISP_SD_PIXEL_COUNT);
+       NVSD_PRINT_REG_ARRAY(DC_DISP_SD_HISTOGRAM);
+       NVSD_PRINT_REG(DC_DISP_SD_BL_PARAMETERS);
+       NVSD_PRINT_REG_ARRAY(DC_DISP_SD_BL_TF);
+       NVSD_PRINT_REG(DC_DISP_SD_BL_CONTROL);
+       NVSD_PRINT_REG(DC_DISP_SD_HW_K_VALUES);
+       NVSD_PRINT_REG(DC_DISP_SD_MAN_K_VALUES);
+
+       return res;
+}
+
+/* Sysfs initializer */
+int nvsd_create_sysfs(struct device *dev)
+{
+       int retval = 0;
+
+       nvsd_kobj = kobject_create_and_add("smartdimmer", &dev->kobj);
+       if (!nvsd_kobj)
+               return -ENOMEM;
+
+       retval = sysfs_create_group(nvsd_kobj, &nvsd_attr_group);
+       if (retval) {
+               kobject_put(nvsd_kobj);
+               dev_err(dev, "%s: failed to create attributes\n", __func__);
+       }
+
+       return retval;
+}
+
+/* Sysfs destructor */
+void __devexit nvsd_remove_sysfs(struct device *dev)
+{
+       if (nvsd_kobj) {
+               sysfs_remove_group(nvsd_kobj, &nvsd_attr_group);
+               kobject_put(nvsd_kobj);
+       }
+}