video: tegra: Added sysfs for dc/smartdimmer
Laurence Harrison [Thu, 7 Apr 2011 01:26:21 +0000 (18:26 -0700)]
Includes:
1.) Added basic DC sysfs objects.
2.) Sysfs objects and functions for smartdimmer settings.
3.) Register dump access for smartdimmer.
4.) Improvements to the behavior of smartdimmer (now updates
    at the end of vblank instead of the beginning).
5.) Rename v_blank_complete to vblank_complete to keep in
    same effective style as the rest of the code.

Original-Change-Id: I59addcc479880322d49b24d1206009def3c4b392
Reviewed-on: http://git-master/r/29893
Reviewed-by: Varun Colbert <vcolbert@nvidia.com>
Tested-by: Varun Colbert <vcolbert@nvidia.com>

Rebase-Id: R3a65726e3644d01b374f9774e966d635234567b4

drivers/video/tegra/dc/Makefile
drivers/video/tegra/dc/dc.c
drivers/video/tegra/dc/dc_priv.h
drivers/video/tegra/dc/dc_sysfs.c [new file with mode: 0644]
drivers/video/tegra/dc/dsi.c
drivers/video/tegra/dc/nvsd.c
drivers/video/tegra/dc/nvsd.h

index 64ab35f..63044ca 100644 (file)
@@ -5,4 +5,5 @@ obj-y += nvhdcp.o
 obj-y += edid.o
 obj-y += nvsd.o
 obj-y += dsi.o
+obj-y += dc_sysfs.o
 obj-$(CONFIG_TEGRA_OVERLAY) += overlay.o
index 1ffa168..55a93be 100644 (file)
@@ -486,7 +486,6 @@ int tegra_dc_update_windows(struct tegra_dc_win *windows[], int n)
        unsigned long update_mask = GENERAL_ACT_REQ;
        unsigned long val;
        bool update_blend = false;
-       bool nvsd_updated = false;
        int i;
 
        dc = windows[0]->dc;
@@ -649,21 +648,8 @@ int tegra_dc_update_windows(struct tegra_dc_win *windows[], int n)
 
        tegra_dc_writel(dc, update_mask, DC_CMD_STATE_CONTROL);
 
-       /* Update the SD brightness */
-       nvsd_updated = nvsd_update_brightness(dc);
-
        mutex_unlock(&dc->lock);
 
-       /* Do the actual brightness update outside of the mutex */
-       if (nvsd_updated && dc->out->sd_settings &&
-           dc->out->sd_settings->bl_device) {
-
-               struct platform_device *pdev = dc->out->sd_settings->bl_device;
-               struct backlight_device *bl = platform_get_drvdata(pdev);
-               if (bl)
-                       backlight_update_status(bl);
-       }
-
        return 0;
 }
 EXPORT_SYMBOL(tegra_dc_update_windows);
@@ -1190,6 +1176,29 @@ unsigned tegra_dc_get_out_width(struct tegra_dc *dc)
 }
 EXPORT_SYMBOL(tegra_dc_get_out_width);
 
+static void tegra_dc_vblank(struct work_struct *work)
+{
+       struct tegra_dc *dc = container_of(work, struct tegra_dc, vblank_work);
+       bool nvsd_updated = false;
+
+       mutex_lock(&dc->lock);
+
+       /* Update the SD brightness */
+       nvsd_updated = nvsd_update_brightness(dc);
+
+       mutex_unlock(&dc->lock);
+
+       /* Do the actual brightness update outside of the mutex */
+       if (nvsd_updated && dc->out->sd_settings &&
+           dc->out->sd_settings->bl_device) {
+
+               struct platform_device *pdev = dc->out->sd_settings->bl_device;
+               struct backlight_device *bl = platform_get_drvdata(pdev);
+               if (bl)
+                       backlight_update_status(bl);
+       }
+}
+
 static irqreturn_t tegra_dc_irq(int irq, void *ptr)
 {
 #ifndef CONFIG_TEGRA_FPGA_PLATFORM
@@ -1202,34 +1211,6 @@ static irqreturn_t tegra_dc_irq(int irq, void *ptr)
        status = tegra_dc_readl(dc, DC_CMD_INT_STATUS);
        tegra_dc_writel(dc, status, DC_CMD_INT_STATUS);
 
-       if (status & V_BLANK_INT)
-               complete(&dc->v_blank_complete);
-
-       if (status & FRAME_END_INT) {
-               int completed = 0;
-               int dirty = 0;
-
-               val = tegra_dc_readl(dc, DC_CMD_STATE_CONTROL);
-               for (i = 0; i < DC_N_WINDOWS; i++) {
-                       if (!(val & (WIN_A_UPDATE << i))) {
-                               dc->windows[i].dirty = 0;
-                               completed = 1;
-                       } else {
-                               dirty = 1;
-                       }
-               }
-
-               if (!dirty) {
-                       val = tegra_dc_readl(dc, DC_CMD_INT_ENABLE);
-                       val &= ~FRAME_END_INT;
-                       tegra_dc_writel(dc, val, DC_CMD_INT_ENABLE);
-               }
-
-               if (completed)
-                       wake_up(&dc->wq);
-       }
-
-
        /*
         * Overlays can get thier internal state corrupted during and underflow
         * condition.  The only way to fix this state is to reset the DC.
@@ -1247,6 +1228,7 @@ static irqreturn_t tegra_dc_irq(int irq, void *ptr)
        if (status & V_BLANK_INT) {
                int i;
 
+               /* Check for any underflow reset conditions */
                for (i = 0; i< DC_N_WINDOWS; i++) {
                        if (dc->underflow_mask & (WIN_A_UF_INT <<i)) {
                                dc->windows[i].underflows++;
@@ -1259,14 +1241,60 @@ static irqreturn_t tegra_dc_irq(int irq, void *ptr)
                }
 
                if (!dc->underflow_mask) {
+                       /* If we have no underflow to check, go ahead
+                          and disable the interrupt */
                        val = tegra_dc_readl(dc, DC_CMD_INT_ENABLE);
                        val &= ~V_BLANK_INT;
                        tegra_dc_writel(dc, val, DC_CMD_INT_ENABLE);
                }
 
+               /* Clear the underflow mask now that we've checked it. */
                dc->underflow_mask = 0;
+
+               /* Schedule any additional bottom-half vblank actvities. */
+               schedule_work(&dc->vblank_work);
+
+               /* Mark the vblank as complete. */
+               complete(&dc->vblank_complete);
        }
 
+       if (status & FRAME_END_INT) {
+               int completed = 0;
+               int dirty = 0;
+
+               val = tegra_dc_readl(dc, DC_CMD_STATE_CONTROL);
+               for (i = 0; i < DC_N_WINDOWS; i++) {
+                       if (!(val & (WIN_A_UPDATE << i))) {
+                               dc->windows[i].dirty = 0;
+                               completed = 1;
+                       } else {
+                               dirty = 1;
+                       }
+               }
+
+               if (!dirty) {
+                       val = tegra_dc_readl(dc, DC_CMD_INT_ENABLE);
+                       val &= ~FRAME_END_INT;
+                       tegra_dc_writel(dc, val, DC_CMD_INT_ENABLE);
+               }
+
+               if (completed) {
+                       if (!dirty) {
+                               /* With the last completed window, go ahead
+                                  and enable the vblank interrupt for nvsd. */
+                               val = tegra_dc_readl(dc, DC_CMD_INT_ENABLE);
+                               val |= V_BLANK_INT;
+                               tegra_dc_writel(dc, val, DC_CMD_INT_ENABLE);
+
+                               val = tegra_dc_readl(dc, DC_CMD_INT_MASK);
+                               val |= V_BLANK_INT;
+                               tegra_dc_writel(dc, val, DC_CMD_INT_MASK);
+                       }
+
+                       /* Wake up the workqueue regardless. */
+                       wake_up(&dc->wq);
+               }
+       }
 
        return IRQ_HANDLED;
 #else
@@ -1665,9 +1693,10 @@ static int tegra_dc_probe(struct nvhost_device *ndev)
                dc->enabled = true;
 
        mutex_init(&dc->lock);
-       init_completion(&dc->v_blank_complete);
+       init_completion(&dc->vblank_complete);
        init_waitqueue_head(&dc->wq);
        INIT_WORK(&dc->reset_work, tegra_dc_reset_worker);
+       INIT_WORK(&dc->vblank_work, tegra_dc_vblank);
 
        dc->n_windows = DC_N_WINDOWS;
        for (i = 0; i < dc->n_windows; i++) {
@@ -1736,6 +1765,8 @@ static int tegra_dc_probe(struct nvhost_device *ndev)
        if (dc->out_ops && dc->out_ops->detect)
                dc->out_ops->detect(dc);
 
+       tegra_dc_create_sysfs(&dc->ndev->dev);
+
        return 0;
 
 err_free_irq:
@@ -1760,6 +1791,8 @@ static int tegra_dc_remove(struct nvhost_device *ndev)
 {
        struct tegra_dc *dc = nvhost_get_drvdata(ndev);
 
+       tegra_dc_remove_sysfs(&dc->ndev->dev);
+
        if (dc->overlay) {
                tegra_overlay_unregister(dc->overlay);
        }
index b01f569..f5a5f24 100644 (file)
@@ -93,7 +93,9 @@ struct tegra_dc {
        unsigned long                   underflow_mask;
        struct work_struct              reset_work;
 
-       struct completion               v_blank_complete;
+       struct completion               vblank_complete;
+
+       struct work_struct              vblank_work;
 };
 
 static inline void tegra_dc_io_start(struct tegra_dc *dc)
@@ -148,4 +150,7 @@ extern struct tegra_dc_out_ops tegra_dc_rgb_ops;
 extern struct tegra_dc_out_ops tegra_dc_hdmi_ops;
 extern struct tegra_dc_out_ops tegra_dc_dsi_ops;
 
+void __devexit tegra_dc_remove_sysfs(struct device *dev);
+void tegra_dc_create_sysfs(struct device *dev);
 #endif
+
diff --git a/drivers/video/tegra/dc/dc_sysfs.c b/drivers/video/tegra/dc/dc_sysfs.c
new file mode 100644 (file)
index 0000000..4afc864
--- /dev/null
@@ -0,0 +1,121 @@
+#include <linux/platform_device.h>
+#include <linux/kernel.h>
+
+#include <mach/dc.h>
+#include <mach/fb.h>
+
+#include "dc_reg.h"
+#include "dc_priv.h"
+#include "nvsd.h"
+
+/****************
+ * Current mode *
+ ****************/
+static ssize_t mode_show(struct device *device,
+       struct device_attribute *attr, char *buf)
+{
+       struct nvhost_device *ndev = to_nvhost_device(device);
+       struct tegra_dc *dc = nvhost_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, S_IRUGO, mode_show, NULL);
+
+/**************
+ * DC Enabled *
+ **************/
+static ssize_t enable_show(struct device *device,
+       struct device_attribute *attr, char *buf)
+{
+       struct nvhost_device *ndev = to_nvhost_device(device);
+       struct tegra_dc *dc = nvhost_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 nvhost_device *ndev = to_nvhost_device(dev);
+       struct tegra_dc *dc = nvhost_get_drvdata(ndev);
+       int enabled;
+
+       enabled = simple_strtoul(buf, NULL, 10);
+
+       if (enabled) {
+               tegra_dc_enable(dc);
+       } else {
+               tegra_dc_disable(dc);
+       }
+
+       return count;
+}
+
+static DEVICE_ATTR(enable, S_IRUGO|S_IWUSR|S_IWGRP, enable_show, enable_store);
+
+/********
+ * Init *
+ ********/
+void __devexit tegra_dc_remove_sysfs(struct device *dev)
+{
+       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;
+
+       device_remove_file(dev, &dev_attr_mode);
+       device_remove_file(dev, &dev_attr_enable);
+
+       if(sd_settings) {
+               nvsd_remove_sysfs(dev);
+       }
+}
+
+void tegra_dc_create_sysfs(struct device *dev)
+{
+       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;
+       int error = 0;
+
+       error |= device_create_file(dev, &dev_attr_mode);
+       error |= device_create_file(dev, &dev_attr_enable);
+
+       if(sd_settings) {
+               error |= nvsd_create_sysfs(dev);
+       }
+
+       if(error) {
+               printk("Failed to create sysfs attributes!\n");
+       }
+}
+
index a98ef99..3794058 100755 (executable)
@@ -729,7 +729,7 @@ void tegra_dsi_stop_dc_stream_at_frame_end(struct tegra_dc *dc, struct tegra_dc_
 
        /* wait for vblank completion */
        timeout = wait_for_completion_interruptible_timeout(
-               &dc->v_blank_complete, DSI_STOP_DC_DURATION_MSEC);
+               &dc->vblank_complete, DSI_STOP_DC_DURATION_MSEC);
 
        /* disable vblank interrupt */
        val = tegra_dc_readl(dc, DC_CMD_INT_ENABLE);
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);
+       }
+}
index 6d08226..f7fc4a1 100644 (file)
@@ -19,5 +19,7 @@
 
 void nvsd_init(struct tegra_dc *dc, struct tegra_dc_sd_settings *settings);
 bool nvsd_update_brightness(struct tegra_dc *dc);
+int nvsd_create_sysfs(struct device *dev);
+void __devexit nvsd_remove_sysfs(struct device *dev);
 
 #endif