1. I didn't send my patch to freescale because I thought it will only be a quick fix and there is a better/official way to handle this.
I had to extend the ldb driver in the linux kernel and disable some warnings in the kernel, because access GPIOs directly will cause a warning in kernel 3.10.53.
2. Our display uses 3 GPIOs (VCC, VCC_ENABLE and BACKLIGHT_ENABLE)
3. In our case the backlight-pwm has an extry pin on the display and you can switch on/off the backlight using the BACKLIGHT_ENABLE pin.
If you find a better way to handle this please let me know.
I attached you my patch, for kernel 3.10.53.
--- a/drivers/video/mxc/ldb.c
+++ b/drivers/video/mxc/ldb.c
@@ -29,8 +29,12 @@
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/types.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/of_gpio.h>
#include <video/of_videomode.h>
#include <video/videomode.h>
+#include <dt-bindings/gpio/gpio.h>
#include "mxc_dispdrv.h"
#define DRIVER_NAME "ldb"
@@ -57,6 +61,47 @@
#define INVALID_BUS_REG (~0UL)
+
+#define PWRUPSEQ_INIT 0
+#define PWRUPSEQ_PRE_FINISHED 1
+#define PWRUPSEQ_POST_FINISHED 2
+/**
+ * struct ldb_power_timing - config structure
+ * @gpios: Array containing the gpios needed to control
+ * the powersequence
+ * @nr_gpios: Number of gpios
+ * @startup_delay_times Array of delays between the configured gpios
+ * @nr_delays Number of delays, should be the same like nr_gpios if not the delay is 0
+ *
+ * This structure contains powersequence configuration
+ * information that must be passed by platform code to the
+ * ldb driver, if a display is used that needs a defined powersequence.
+ */
+struct ldb_gpio_timing{
+ struct gpio *gpios;
+ int nr_gpios;
+ unsigned *values;
+ int nr_values;
+ unsigned *delay_times;
+ int nr_delays;
+};
+
+/**
+ * struct ldb_power_timing - config structure
+ * @pre_timing: powersequence running befor lvds data will be initialised
+ * @post_timing: powersequence running after lvds data was initialised
+ * @lvds_delay_us: delay waiting befor LVDS data will be initialised
+ *
+ * This structure contains powersequence configuration
+ * information that must be passed by platform code to the
+ * ldb driver, if a display is used that needs a defined powersequence.
+ */
+struct ldb_power_timing{
+ struct ldb_gpio_timing pre_timing;
+ struct ldb_gpio_timing post_timing;
+ int lvds_delay_us;
+};
+
struct crtc_mux {
enum crtc crtc;
u32 val;
@@ -76,7 +121,12 @@ struct ldb_info {
bool ext_bgref_cap;
int ctrl_reg;
int bus_mux_num;
+ int PowerState;
const struct bus_mux *buses;
+ struct fb_info *fbi;
+ struct ldb_power_timing disp_powerup_timing;
+ struct ldb_power_timing disp_powerdown_timing;
+
};
struct ldb_data;
@@ -285,7 +335,7 @@ static const struct of_device_id ldb_dt_ids[] = {
};
MODULE_DEVICE_TABLE(of, ldb_dt_ids);
-static int ldb_init(struct mxc_dispdrv_handle *mddh,
+static int ldb_init_internal(struct mxc_dispdrv_handle *mddh,
struct mxc_dispdrv_setting *setting)
{
struct ldb_data *ldb = mxc_dispdrv_getdata(mddh);
@@ -324,6 +374,90 @@ static int ldb_init(struct mxc_dispdrv_handle *mddh,
return 0;
}
+static inline void ldb_delay(int delay_us) {
+ if (delay_us >= 1000) {
+ mdelay(delay_us / 1000);
+ udelay(delay_us % 1000);
+ } else {
+ udelay(delay_us);
+ }
+}
+
+static void ldb_init_gpio(const struct ldb_gpio_timing *gpio_timing)
+{
+ int i;
+ char label[15];
+ for(i = 0; i < gpio_timing->nr_gpios; ++i) {
+ if(gpio_is_output(gpio_timing->gpios[i].gpio) == 0)
+ {
+ memset(label, 0, sizeof(label));
+ sprintf(label, "%d", gpio_timing->gpios[i].gpio);
+ gpio_request(gpio_timing->gpios[i].gpio, label);
+
+ if(i < gpio_timing->nr_values) {
+ int value = gpio_timing->values[i];
+ //Invert value if pin is ACTIVE_LOW
+ if(gpio_timing->gpios[i].flags & GPIO_ACTIVE_LOW) {
+ value = ((~gpio_timing->values[i]) & 0x01);
+ }
+ //Initvalue is set the inverse of value
+ gpio_direction_output(gpio_timing->gpios[i].gpio, ~value);
+ } else {
+ gpio_direction_output(gpio_timing->gpios[i].gpio, 0);
+ }
+ }
+ }
+}
+
+static void ldb_gpio_timing(const struct ldb_gpio_timing *gpio_timing)
+{
+ int i;
+ for(i = 0; i < gpio_timing->nr_gpios; ++i) {
+ if(i < gpio_timing->nr_values) {
+ int value = gpio_timing->values[i];
+ //Invert value if pin is ACTIVE_LOW
+ if(gpio_timing->gpios[i].flags & GPIO_ACTIVE_LOW) {
+ value = ((~gpio_timing->values[i]) & 0x01);
+ }
+ gpio_set_value(gpio_timing->gpios[i].gpio, value);
+ } else {
+ gpio_set_value(gpio_timing->gpios[i].gpio, 0);
+ }
+
+ if(i < gpio_timing->nr_delays
+ && gpio_timing->delay_times[i] > 0) {
+ ldb_delay(gpio_timing->delay_times[i]);
+ }
+ }
+}
+
+static int ldb_init(struct mxc_dispdrv_handle *mddh,
+ struct mxc_dispdrv_setting *setting)
+{
+ struct ldb_data *ldb = mxc_dispdrv_getdata(mddh);
+ struct device *dev = ldb->dev;
+ const struct of_device_id *of_id = of_match_device(ldb_dt_ids, dev);
+ struct ldb_info *ldb_info = (struct ldb_info *)of_id->data;
+ int ret;
+
+ //Init GPIOs
+ ldb_init_gpio(&ldb_info->disp_powerup_timing.pre_timing);
+ ldb_init_gpio(&ldb_info->disp_powerup_timing.post_timing);
+ ldb_init_gpio(&ldb_info->disp_powerdown_timing.pre_timing);
+ ldb_init_gpio(&ldb_info->disp_powerdown_timing.post_timing);
+
+ //Set gpios needed befor LVDS init
+ ldb_gpio_timing(&ldb_info->disp_powerup_timing.pre_timing);
+
+ //init lvds data, the lvds lines will be activated by ldb_enable()
+ ret = ldb_init_internal(mddh, setting);
+
+ //Set PowerState to PWRUPSEQ_PRE_FINISHED because ldb_enable switches the post_powerup gpios
+ ldb_info->PowerState = PWRUPSEQ_PRE_FINISHED;
+
+ return ret;
+}
+
static int get_di_clk_id(struct ldb_chan chan, int *id)
{
struct ldb_data *ldb = chan.ldb;
@@ -484,6 +618,8 @@ static int ldb_enable(struct mxc_dispdrv_handle *mddh,
struct ldb_data *ldb = mxc_dispdrv_getdata(mddh);
struct ldb_chan chan;
struct device *dev = ldb->dev;
+ const struct of_device_id *of_id = of_match_device(ldb_dt_ids, dev);
+ struct ldb_info *ldb_info = (struct ldb_info *)of_id->data;
struct bus_mux bus_mux;
int ret = 0, id = 0, chno, other_chno;
@@ -523,6 +659,19 @@ static int ldb_enable(struct mxc_dispdrv_handle *mddh,
}
regmap_write(ldb->regmap, ldb->ctrl_reg, ldb->ctrl);
+
+ if(ldb_info->PowerState == PWRUPSEQ_PRE_FINISHED) {
+ ldb_info->fbi = fbi;
+
+ //Wait after LVDS init
+ ldb_delay(ldb_info->disp_powerup_timing.lvds_delay_us);
+
+ //Set gpios needed after LVDS init
+ ldb_gpio_timing(&ldb_info->disp_powerup_timing.post_timing);
+
+ ldb_info->PowerState = PWRUPSEQ_POST_FINISHED;
+ }
+
return 0;
}
@@ -549,9 +698,34 @@ static void ldb_disable(struct mxc_dispdrv_handle *mddh,
return;
}
+static void ldb_deinit(struct mxc_dispdrv_handle *mddh)
+{
+ struct ldb_data *ldb = mxc_dispdrv_getdata(mddh);
+ struct device *dev = ldb->dev;
+ const struct of_device_id *of_id = of_match_device(ldb_dt_ids, dev);
+ struct ldb_info *ldb_info = (struct ldb_info *)of_id->data;
+ int ret;
+ ret = 0;
+
+ //Set gpios needed befor disable LVDS data
+ ldb_gpio_timing(&ldb_info->disp_powerdown_timing.pre_timing);
+
+ //deactivate lvds data
+ if(mddh != NULL && ldb_info->fbi != NULL) {
+ ldb_disable(mddh, ldb_info->fbi);
+ }
+
+ //Set gpios needed after LVDS disabled
+ ldb_gpio_timing(&ldb_info->disp_powerdown_timing.post_timing);
+
+ //Set powerup sequence t
+ ldb_info->PowerState = PWRUPSEQ_INIT;
+}
+
static struct mxc_dispdrv_driver ldb_drv = {
.name = DRIVER_NAME,
.init = ldb_init,
+ .deinit = ldb_deinit,
.setup = ldb_setup,
.enable = ldb_enable,
.disable = ldb_disable
@@ -663,7 +837,7 @@ static bool is_valid_crtc(struct ldb_data *ldb, enum crtc crtc,
return false;
}
-static int ldb_probe(struct platform_device *pdev)
+static int ldb_probe_internal(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
const struct of_device_id *of_id =
@@ -864,12 +1038,157 @@ static int ldb_probe(struct platform_device *pdev)
static int ldb_remove(struct platform_device *pdev)
{
struct ldb_data *ldb = dev_get_drvdata(&pdev->dev);
+ struct device *dev = &pdev->dev;
+ const struct of_device_id *of_id = of_match_device(ldb_dt_ids, dev);
+ const struct ldb_info *ldb_info = (const struct ldb_info *)of_id->data;
mxc_dispdrv_puthandle(ldb->mddh);
mxc_dispdrv_unregister(ldb->mddh);
+
+ //Cleanup memory
+ kfree(ldb_info->disp_powerup_timing.pre_timing.gpios);
+ kfree(ldb_info->disp_powerup_timing.pre_timing.delay_times);
+ kfree(ldb_info->disp_powerup_timing.pre_timing.values);
+ kfree(ldb_info->disp_powerup_timing.post_timing.gpios);
+ kfree(ldb_info->disp_powerup_timing.post_timing.delay_times);
+ kfree(ldb_info->disp_powerup_timing.post_timing.values);
+ kfree(ldb_info->disp_powerdown_timing.pre_timing.gpios);
+ kfree(ldb_info->disp_powerdown_timing.pre_timing.delay_times);
+ kfree(ldb_info->disp_powerdown_timing.pre_timing.values);
+ kfree(ldb_info->disp_powerdown_timing.post_timing.gpios);
+ kfree(ldb_info->disp_powerdown_timing.post_timing.delay_times);
+ kfree(ldb_info->disp_powerdown_timing.post_timing.values);
return 0;
}
+static int ldb_parse_powertiming(struct platform_device *pdev, struct device_node *np, struct ldb_power_timing* ldb_power_timing)
+{
+ int gpio, length, i;
+ enum of_gpio_flags flags;
+ struct property *prop;
+
+ //Parse pre_timing
+ //Parse gpios
+ ldb_power_timing->pre_timing.nr_gpios = of_gpio_named_count(np, "pre_gpios");
+ ldb_power_timing->pre_timing.gpios = devm_kzalloc(&pdev->dev,
+ sizeof(struct gpio) * ldb_power_timing->pre_timing.nr_gpios,
+ GFP_KERNEL);
+ if (!ldb_power_timing->pre_timing.gpios)
+ return -ENOMEM;
+
+ for (i = 0; i < ldb_power_timing->pre_timing.nr_gpios; i++) {
+ gpio = of_get_named_gpio_flags(np, "pre_gpios", i, &flags);
+ if (gpio < 0)
+ break;
+ ldb_power_timing->pre_timing.gpios[i].gpio = gpio;
+ ldb_power_timing->pre_timing.gpios[i].flags = flags;
+ }
+
+ //Parse values
+ prop = of_find_property(np, "pre_values", &length);
+ if (prop) {
+ ldb_power_timing->pre_timing.nr_values = length / sizeof(u32);
+
+ ldb_power_timing->pre_timing.values = devm_kzalloc(&pdev->dev,
+ sizeof(u32) * ldb_power_timing->pre_timing.nr_values,
+ GFP_KERNEL);
+ if (!ldb_power_timing->pre_timing.values)
+ return -ENOMEM;
+
+ of_property_read_u32_array(np, "pre_values", ldb_power_timing->pre_timing.values, ldb_power_timing->pre_timing.nr_values);
+ }
+
+ //Parse delay times
+ prop = of_find_property(np, "pre_delays-us", &length);
+ if (prop) {
+ ldb_power_timing->pre_timing.nr_delays = length / sizeof(u32);
+
+ ldb_power_timing->pre_timing.delay_times = devm_kzalloc(&pdev->dev,
+ sizeof(u32) * ldb_power_timing->pre_timing.nr_delays,
+ GFP_KERNEL);
+ if (!ldb_power_timing->pre_timing.delay_times)
+ return -ENOMEM;
+
+ of_property_read_u32_array(np, "pre_delays-us", ldb_power_timing->pre_timing.delay_times, ldb_power_timing->pre_timing.nr_delays);
+ }
+
+ //Parse LVDS delay
+ of_property_read_u32(np, "init_delays-us", &ldb_power_timing->lvds_delay_us);
+
+ //Parse post_timing
+ //Parse gpios
+ ldb_power_timing->post_timing.nr_gpios = of_gpio_named_count(np, "post_gpios");
+ ldb_power_timing->post_timing.gpios = devm_kzalloc(&pdev->dev,
+ sizeof(struct gpio) * ldb_power_timing->post_timing.nr_gpios,
+ GFP_KERNEL);
+ if (!ldb_power_timing->post_timing.gpios)
+ return -ENOMEM;
+
+ for (i = 0; i < ldb_power_timing->post_timing.nr_gpios; i++) {
+ gpio = of_get_named_gpio_flags(np, "post_gpios", i, &flags);
+ if (gpio < 0)
+ break;
+ ldb_power_timing->post_timing.gpios[i].gpio = gpio;
+ ldb_power_timing->post_timing.gpios[i].flags = flags;
+ }
+
+ //Parse values
+ prop = of_find_property(np, "post_values", &length);
+ if (prop) {
+ ldb_power_timing->post_timing.nr_values = length / sizeof(u32);
+
+ ldb_power_timing->post_timing.values = devm_kzalloc(&pdev->dev,
+ sizeof(u32) * ldb_power_timing->post_timing.nr_values,
+ GFP_KERNEL);
+ if (!ldb_power_timing->post_timing.values)
+ return -ENOMEM;
+
+ of_property_read_u32_array(np, "post_values", ldb_power_timing->post_timing.values, ldb_power_timing->post_timing.nr_values);
+ }
+ //Parse delay times
+ prop = of_find_property(np, "post_delays-us", &length);
+ if (prop) {
+ ldb_power_timing->post_timing.nr_delays = length / sizeof(u32);
+
+ ldb_power_timing->post_timing.delay_times = devm_kzalloc(&pdev->dev,
+ sizeof(u32) * ldb_power_timing->post_timing.nr_delays,
+ GFP_KERNEL);
+ if (!ldb_power_timing->post_timing.delay_times)
+ return -ENOMEM;
+
+ of_property_read_u32_array(np, "post_delays-us", ldb_power_timing->post_timing.delay_times, ldb_power_timing->post_timing.nr_delays);
+ }
+
+ return 0;
+}
+
+static int ldb_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ const struct of_device_id *of_id = of_match_device(ldb_dt_ids, dev);
+ struct ldb_info *ldb_info = (struct ldb_info *)of_id->data;
+ struct device_node *np = dev->of_node, *child, *powerseq;
+ int ret;
+ ret = 0;
+
+ //Get display power sequence
+ for_each_child_of_node(np, child) {
+ powerseq = of_get_child_by_name(child, "display-powerup");
+ if(powerseq != NULL) {
+ ret = ldb_parse_powertiming(pdev, powerseq, &ldb_info->disp_powerup_timing);
+ }
+
+ powerseq = of_get_child_by_name(child, "display-powerdown");
+ if(powerseq != NULL) {
+ ret = ldb_parse_powertiming(pdev, powerseq, &ldb_info->disp_powerdown_timing);
+ }
+ }
+
+ ret = ldb_probe_internal(pdev);
+
+ return ret;
+}
+
static struct platform_driver ldb_driver = {
.driver = {
.name = DRIVER_NAME,
--- a/drivers/video/mxc/mxc_dispdrv.c
+++ b/drivers/video/mxc/mxc_dispdrv.c
@@ -50,6 +50,8 @@ struct mxc_dispdrv_entry {
struct list_head list;
};
+static struct mxc_dispdrv_entry *g_dispdrv_handle = NULL;
+
struct mxc_dispdrv_handle *mxc_dispdrv_register(struct mxc_dispdrv_driver *drv)
{
struct mxc_dispdrv_entry *new;
@@ -77,6 +79,9 @@ int mxc_dispdrv_unregister(struct mxc_dispdrv_handle *handle)
if (entry) {
mutex_lock(&dispdrv_lock);
+ if(entry->drv->deinit) {
+ entry->drv->deinit(handle);
+ }
list_del(&entry->list);
mutex_unlock(&dispdrv_lock);
kfree(entry);
@@ -86,6 +91,15 @@ int mxc_dispdrv_unregister(struct mxc_dispdrv_handle *handle)
}
EXPORT_SYMBOL_GPL(mxc_dispdrv_unregister);
+int mxc_dispdrv_powerfail(void)
+{
+ int ret = 0;
+ ret = mxc_dispdrv_unregister((struct mxc_dispdrv_handle *)g_dispdrv_handle);
+ g_dispdrv_handle = NULL;
+ return ret;
+}
+EXPORT_SYMBOL_GPL(mxc_dispdrv_powerfail);
+
struct mxc_dispdrv_handle *mxc_dispdrv_gethandle(char *name,
struct mxc_dispdrv_setting *setting)
{
@@ -99,6 +113,7 @@ struct mxc_dispdrv_handle *mxc_dispdrv_gethandle(char *name,
entry, setting);
if (ret >= 0) {
entry->active = true;
+ g_dispdrv_handle = entry;
found = 1;
break;
}
--- a/drivers/video/mxc/mxc_dispdrv.h
+++ b/drivers/video/mxc/mxc_dispdrv.h
@@ -44,6 +44,7 @@ struct mxc_dispdrv_driver {
struct mxc_dispdrv_handle *mxc_dispdrv_register(struct mxc_dispdrv_driver *drv);
int mxc_dispdrv_unregister(struct mxc_dispdrv_handle *handle);
+int mxc_dispdrv_powerfail(void);
struct mxc_dispdrv_handle *mxc_dispdrv_gethandle(char *name,
struct mxc_dispdrv_setting *setting);
void mxc_dispdrv_puthandle(struct mxc_dispdrv_handle *handle);