Linux Framebuffer Layer development
 help / color / mirror / Atom feed
* RE: [PATCH v1] ARM: i.mx: mx3fb: add overlay support
From: Alex Gershgorin @ 2012-04-20 15:38 UTC (permalink / raw)
  To: Guennadi Liakhovetski
  Cc: Florian Tobias Schandinat, s.hauer@pengutronix.de,
	laurent.pinchart@ideasonboard.com, linux-fbdev@vger.kernel.org,
	linux-media@vger.kernel.org
In-Reply-To: <Pine.LNX.4.64.1204181952580.30514@axis700.grange>

Hi Guennadi,

Thanks for your review.

> > Thanks for reviving, fixing and submitting this code!

I think that this code is beneficial  :-)

> > On Wed, 18 Apr 2012, Alex Gershgorin wrote:

> This patch is based on the original version submitted by Guennadi Liakhovetski,
> the patch initializes overlay channel, adds ioctl for configuring
> transparency of the overlay and graphics planes, CONFIG_FB_MX3_OVERLAY
> is also supported.
>
> In case that CONFIG_FB_MX3_OVERLAY is not defined, mx3fb is completely
> backward compatible.
>
> Blend mode, only global alpha blending has been tested.
>
> Signed-off-by: Alex Gershgorin <alexg@meprolight.com>
> Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>

> > Thanks for the credit (;-)), but no, putting my Sob after yours means,
> > that I took your patch and forwarded it on to the next maintainer, which
> > is clearly not the case here:-) The original i.MX31 framebuffer overlay
> > code from my old patches also clearly wasn't written by me, since I didn't
> > have a chance to test it. So, if you like, you can try to trace back
> > original authors of that code and ask them, how they want to be credited
> > here,

I would like to thank all the authors of original code.
unfortunately I can't thank for each one of you separately by name, i hope
that you understand and accept it.

>>  otherwise just mentioning, that this work is based on some earlier
> > patch series "i.MX31: dmaengine and framebuffer drivers" from 2008 by ...
> > should be enough.

This option is more suitable, I just correct the description of the patch,
and leave your signature (if you have any objections?) since 2008 patch version.

> > I don't think I can review this patch in sufficient depth, just a couple
> > of minor comments below

> ---
>
> Applies to v3.4-rc3
> ---
>  drivers/video/Kconfig |    7 +
>  drivers/video/mx3fb.c |  318 ++++++++++++++++++++++++++++++++++++++++++++-----
>  include/linux/mxcfb.h |   93 ++++++++++++++
>  3 files changed, 388 insertions(+), 30 deletions(-)
>  create mode 100644 include/linux/mxcfb.h
>
> diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
> index a290be5..acbfccc 100644
> --- a/drivers/video/Kconfig
> +++ b/drivers/video/Kconfig
> @@ -2368,6 +2368,13 @@ config FB_MX3
>         far only synchronous displays are supported. If you plan to use
>         an LCD display with your i.MX31 system, say Y here.
>
> +config FB_MX3_OVERLAY
> +     bool "MX3 Overlay support"
> +     default n
> +     depends on FB_MX3
> +     ---help---
> +       Say Y here to enable overlay support
> +
>  config FB_BROADSHEET
>       tristate "E-Ink Broadsheet/Epson S1D13521 controller support"
>       depends on FB
> diff --git a/drivers/video/mx3fb.c b/drivers/video/mx3fb.c
> index eec0d7b..0fb8a72 100644
> --- a/drivers/video/mx3fb.c
> +++ b/drivers/video/mx3fb.c
> @@ -26,6 +26,7 @@
>  #include <linux/console.h>
>  #include <linux/clk.h>
>  #include <linux/mutex.h>
> +#include <linux/mxcfb.h>
>
>  #include <mach/dma.h>
>  #include <mach/hardware.h>
> @@ -238,6 +239,7 @@ static const struct fb_videomode mx3fb_modedb[] = {
>
>  struct mx3fb_data {
>       struct fb_info          *fbi;
> +     struct fb_info          *fbi_ovl;
>       int                     backlight_level;
>       void __iomem            *reg_base;
>       spinlock_t              lock;
> @@ -246,6 +248,9 @@ struct mx3fb_data {
>       uint32_t                h_start_width;
>       uint32_t                v_start_width;
>       enum disp_data_mapping  disp_data_fmt;
> +
> +     /* IDMAC / dmaengine interface */
> +     struct idmac_channel    *idmac_channel[2];      /* We need 2 channels */
>  };
>
>  struct dma_chan_request {
> @@ -272,6 +277,17 @@ struct mx3fb_info {
>       u32                             sync;   /* preserve var->sync flags */
>  };
>
> +/* Allocated overlay buffer */
> +struct mx3fb_alloc_list {
> +     struct list_head        list;
> +     dma_addr_t              phy_addr;
> +     void                    *cpu_addr;
> +     size_t                  size;
> +};
> +
> +/* A list of overlay buffers */
> +static LIST_HEAD(fb_alloc_list);

> > Static variables are evil:-) Which you prove below by protecting this
> > global list-head by a per-device mutex. No, I have no idea whether anyone
> > ever comes up with an SoC with multiple instances of this device, but at
> > least this seems inconsistent to me.

 This is descibed bellow... it can will move into struct mx3fb_info ...

> +
>  static void mx3fb_dma_done(void *);
>
>  /* Used fb-mode and bpp. Can be set on kernel command line, therefore file-static. */
> @@ -303,7 +319,11 @@ static void sdc_fb_init(struct mx3fb_info *fbi)
>       struct mx3fb_data *mx3fb = fbi->mx3fb;
>       uint32_t reg;
>
> -     reg = mx3fb_read_reg(mx3fb, SDC_COM_CONF);
> +     reg = mx3fb_read_reg(mx3fb, SDC_COM_CONF) & ~SDC_COM_GWSEL;
> +
> +     /* Also enable foreground for overlay graphic window is foreground */
> +     if (mx3fb->fbi_ovl && fbi = mx3fb->fbi_ovl->par)
> +             reg |= (SDC_COM_FG_EN | SDC_COM_GWSEL);

> > Superfluous parenthesis.

     Already fixed
>
>       mx3fb_write_reg(mx3fb, reg | SDC_COM_BG_EN, SDC_COM_CONF);
>  }
> @@ -312,13 +332,24 @@ static void sdc_fb_init(struct mx3fb_info *fbi)
>  static uint32_t sdc_fb_uninit(struct mx3fb_info *fbi)
>  {
>       struct mx3fb_data *mx3fb = fbi->mx3fb;
> -     uint32_t reg;
> +     uint32_t reg, chan_mask;
>
>       reg = mx3fb_read_reg(mx3fb, SDC_COM_CONF);
>
> -     mx3fb_write_reg(mx3fb, reg & ~SDC_COM_BG_EN, SDC_COM_CONF);
> +     /*
> +      * Don't we have to automatically disable overlay when disabling
> +      * background? Attention: cannot test mx3fb->fbi_ovl->par, must
> +      * test mx3fb->fbi->par, because at the time this function is
> +      * called for the first time fbi_ovl is not assigned yet.
> +      */
> +     if (fbi = mx3fb->fbi->par)
> +             chan_mask = SDC_COM_BG_EN;
> +     else
> +             chan_mask = SDC_COM_FG_EN | SDC_COM_GWSEL;
> +
> +     mx3fb_write_reg(mx3fb, reg & ~chan_mask, SDC_COM_CONF);
>
> -     return reg & SDC_COM_BG_EN;
> +     return reg & chan_mask;
>  }
>
>  static void sdc_enable_channel(struct mx3fb_info *mx3_fbi)
> @@ -412,13 +443,20 @@ static void sdc_disable_channel(struct mx3fb_info *mx3_fbi)
>  static int sdc_set_window_pos(struct mx3fb_data *mx3fb, enum ipu_channel channel,
>                             int16_t x_pos, int16_t y_pos)
>  {
> -     if (channel != IDMAC_SDC_0)
> -             return -EINVAL;
> -
>       x_pos += mx3fb->h_start_width;
>       y_pos += mx3fb->v_start_width;
>
> -     mx3fb_write_reg(mx3fb, (x_pos << 16) | y_pos, SDC_BG_POS);
> +     switch (channel) {
> +     case IDMAC_SDC_0:
> +             mx3fb_write_reg(mx3fb, (x_pos << 16) | y_pos, SDC_BG_POS);
> +             break;
> +     case IDMAC_SDC_1:
> +             mx3fb_write_reg(mx3fb, (x_pos << 16) | y_pos, SDC_FG_POS);
> +             break;
> +     default:
> +             return -EINVAL;
> +     }
> +
>       return 0;
>  }
>
> @@ -482,14 +520,17 @@ static int sdc_init_panel(struct mx3fb_data *mx3fb, enum ipu_panel panel,
>       mx3fb->h_start_width = h_start_width;
>       mx3fb->v_start_width = v_start_width;
>
> +     reg = mx3fb_read_reg(mx3fb, SDC_COM_CONF);
> +
>       switch (panel) {
>       case IPU_PANEL_SHARP_TFT:
>               mx3fb_write_reg(mx3fb, 0x00FD0102L, SDC_SHARP_CONF_1);
>               mx3fb_write_reg(mx3fb, 0x00F500F4L, SDC_SHARP_CONF_2);
> -             mx3fb_write_reg(mx3fb, SDC_COM_SHARP | SDC_COM_TFT_COLOR, SDC_COM_CONF);
> +             mx3fb_write_reg(mx3fb, reg | SDC_COM_SHARP |
> +                             SDC_COM_TFT_COLOR, SDC_COM_CONF);
>               break;
>       case IPU_PANEL_TFT:
> -             mx3fb_write_reg(mx3fb, SDC_COM_TFT_COLOR, SDC_COM_CONF);
> +             mx3fb_write_reg(mx3fb, reg | SDC_COM_TFT_COLOR, SDC_COM_CONF);
>               break;
>       default:
>               return -EINVAL;
> @@ -563,13 +604,12 @@ static int sdc_init_panel(struct mx3fb_data *mx3fb, enum ipu_panel panel,
>  /**
>   * sdc_set_color_key() - set the transparent color key for SDC graphic plane.
>   * @mx3fb:   mx3fb context.
> - * @channel: IPU DMAC channel ID.
>   * @enable:  boolean to enable or disable color keyl.
>   * @color_key:       24-bit RGB color to use as transparent color key.
>   * @return:  0 on success or negative error code on failure.
>   */
> -static int sdc_set_color_key(struct mx3fb_data *mx3fb, enum ipu_channel channel,
> -                          bool enable, uint32_t color_key)
> +static int sdc_set_color_key(struct mx3fb_data *mx3fb, bool enable,
> +                             uint32_t color_key)
>  {
>       uint32_t reg, sdc_conf;
>       unsigned long lock_flags;
> @@ -577,10 +617,6 @@ static int sdc_set_color_key(struct mx3fb_data *mx3fb, enum ipu_channel channel,
>       spin_lock_irqsave(&mx3fb->lock, lock_flags);
>
>       sdc_conf = mx3fb_read_reg(mx3fb, SDC_COM_CONF);
> -     if (channel = IDMAC_SDC_0)
> -             sdc_conf &= ~SDC_COM_GWSEL;
> -     else
> -             sdc_conf |= SDC_COM_GWSEL;
>
>       if (enable) {
>               reg = mx3fb_read_reg(mx3fb, SDC_GW_CTRL) & 0xFF000000L;
> @@ -668,8 +704,12 @@ static int mx3fb_set_fix(struct fb_info *fbi)
>  {
>       struct fb_fix_screeninfo *fix = &fbi->fix;
>       struct fb_var_screeninfo *var = &fbi->var;
> +     struct mx3fb_info *mx3_fbi = fbi->par;
>
> -     strncpy(fix->id, "DISP3 BG", 8);
> +     if (mx3_fbi->ipu_ch = IDMAC_SDC_1)
> +             strncpy(fix->id, "DISP3 FG", 8);
> +     else
> +             strncpy(fix->id, "DISP3 BG", 8);
>
>       fix->line_length = var->xres_virtual * var->bits_per_pixel / 8;
>
> @@ -689,13 +729,25 @@ static void mx3fb_dma_done(void *arg)
>       struct idmac_channel *ichannel = to_idmac_chan(chan);
>       struct mx3fb_data *mx3fb = ichannel->client;
>       struct mx3fb_info *mx3_fbi = mx3fb->fbi->par;
> +     struct mx3fb_info *mx3_fbi_cur;
> +     struct mx3fb_info *mx3_fbi_ovl = mx3fb->fbi_ovl ? mx3fb->fbi_ovl->par :
> +             NULL;
>
>       dev_dbg(mx3fb->dev, "irq %d callback\n", ichannel->eof_irq);
>
> +     if (ichannel = mx3_fbi->idmac_channel) {
> +             mx3_fbi_cur = mx3_fbi;
> +     } else if (mx3_fbi_ovl && ichannel = mx3_fbi_ovl->idmac_channel) {
> +             mx3_fbi_cur = mx3_fbi_ovl;
> +     } else {
> +             WARN(1, "Cannot identify channel!\n");
> +             return;
> +     }
> +
>       /* We only need one interrupt, it will be re-enabled as needed */
>       disable_irq_nosync(ichannel->eof_irq);
>
> -     complete(&mx3_fbi->flip_cmpl);
> +     complete(&mx3_fbi_cur->flip_cmpl);
>  }
>
>  static int __set_par(struct fb_info *fbi, bool lock)
> @@ -1151,6 +1203,145 @@ static struct fb_ops mx3fb_ops = {
>       .fb_blank = mx3fb_blank,
>  };
>
> +#ifdef CONFIG_FB_MX3_OVERLAY
> +static int mx3fb_blank_ovl(int blank, struct fb_info *fbi)
> +{
> +     struct mx3fb_info *mx3_fbi = fbi->par;
> +
> +     dev_dbg(fbi->device, "ovl blank = %d\n", blank);
> +
> +     if (mx3_fbi->blank = blank)
> +             return 0;
> +
> +     mutex_lock(&mx3_fbi->mutex);
> +     mx3_fbi->blank = blank;
> +
> +     switch (blank) {
> +     case FB_BLANK_POWERDOWN:
> +     case FB_BLANK_VSYNC_SUSPEND:
> +     case FB_BLANK_HSYNC_SUSPEND:
> +     case FB_BLANK_NORMAL:
> +             sdc_disable_channel(mx3_fbi);
> +             break;
> +     case FB_BLANK_UNBLANK:
> +             sdc_enable_channel(mx3_fbi);
> +             break;
> +     }
> +     mutex_unlock(&mx3_fbi->mutex);
> +
> +     return 0;
> +}
> +
> +/*
> + * Function to handle custom ioctls for MX3 framebuffer.
> + *
> + *  @inode   inode struct
> + *  @file    file struct
> + *  @cmd     Ioctl command to handle
> + *  @arg     User pointer to command arguments
> + *  @fbi     framebuffer information pointer
> + */
> +static int mx3fb_ioctl_ovl(struct fb_info *fbi, unsigned int cmd,
> +                        unsigned long arg)
> +{
> +     struct mx3fb_info *mx3_fbi = fbi->par;
> +     struct mx3fb_data *mx3fb = mx3_fbi->mx3fb;
> +     struct mxcfb_gbl_alpha ga;
> +     struct mxcfb_color_key key;
> +     int retval = 0;
> +     int __user *argp = (void __user *)arg;
> +     struct mx3fb_alloc_list *mem;
> +     int size;
> +     unsigned long offset;
> +
> +     switch (cmd) {
> +     case FBIO_ALLOC:
> +             if (get_user(size, argp))
> +                     return -EFAULT;
> +
> +             mem = kzalloc(sizeof(*mem), GFP_KERNEL);
> +             if (mem = NULL)
> +                     return -ENOMEM;
> +
> +             mem->size = PAGE_ALIGN(size);
> +
> +             mem->cpu_addr = dma_alloc_coherent(fbi->device, size,
> +                                                &mem->phy_addr,
> +                                                GFP_DMA);
> +             if (mem->cpu_addr = NULL) {
> +                     kfree(mem);
> +                     return -ENOMEM;
> +             }
> +
> +             mutex_lock(&mx3_fbi->mutex);
> +             list_add(&mem->list, &fb_alloc_list);
> +             mutex_unlock(&mx3_fbi->mutex);

> > Here. At the very least you'd need a global mutex. Or put the list-head in
> > struct mx3fb_info.

I will do it, list-head will go inoto struct mx3fb_info

> +
> +             dev_dbg(fbi->device, "allocated %d bytes  <at>  0x%08X\n",
> +                     mem->size, mem->phy_addr);
> +
> +             if (put_user(mem->phy_addr, argp))
> +                     return -EFAULT;
> +
> +             break;
> +     case FBIO_FREE:
> +             if (get_user(offset, argp))
> +                     return -EFAULT;
> +
> +             retval = -EINVAL;
> +             mutex_lock(&mx3_fbi->mutex);
> +             list_for_each_entry(mem, &fb_alloc_list, list) {
> +                     if (mem->phy_addr = offset) {
> +                             list_del(&mem->list);
> +                             dma_free_coherent(fbi->device,
> +                                               mem->size,
> +                                               mem->cpu_addr,
> +                                               mem->phy_addr);
> +                             kfree(mem);
> +                             retval = 0;
> +                             break;
> +                     }
> +             }
> +             mutex_unlock(&mx3_fbi->mutex);
> +
> +             break;
> +     case MXCFB_SET_GBL_ALPHA:

> > Are you using these proprietary ioctl()s?

 Unfortunately yes ...

>> If not, I wouldn't implement
> > them. If you do need them, maybe it would make sense to add such ioctl()s
> > globally for fbdev?

It wiil be nice... but I think for this will need a separate patch?


> +             if (copy_from_user(&ga, (void *)arg, sizeof(ga)))
> +                     retval = -EFAULT;
> +
> +             sdc_set_global_alpha(mx3fb, (bool)ga.enable, ga.alpha);
> +             dev_dbg(fbi->device, "Set global alpha to %d\n", ga.alpha);
> +
> +             break;
> +     case MXCFB_SET_CLR_KEY:
> +             if (copy_from_user(&key, (void *)arg, sizeof(key)))
> +                     retval = -EFAULT;
> +
> +             sdc_set_color_key(mx3fb, (bool)key.enable, key.color_key);
> +             dev_dbg(fbi->device, "Set color key to %d\n", key.color_key);
> +
> +             break;
> +     default:
> +             retval = -EINVAL;
> +     }
> +
> +     return retval;
> +}
> +
> +static struct fb_ops mx3fb_ovl_ops = {
> +     .owner = THIS_MODULE,
> +     .fb_set_par = mx3fb_set_par,
> +     .fb_check_var = mx3fb_check_var,
> +     .fb_setcolreg = mx3fb_setcolreg,
> +     .fb_pan_display = mx3fb_pan_display,
> +     .fb_ioctl = mx3fb_ioctl_ovl,
> +     .fb_fillrect = cfb_fillrect,
> +     .fb_copyarea = cfb_copyarea,
> +     .fb_imageblit = cfb_imageblit,
> +     .fb_blank = mx3fb_blank_ovl,
> +};
> +#endif
> +
>  #ifdef CONFIG_PM
>  /*
>   * Power management hooks.      Note that we won't be called from IRQ context,
> @@ -1164,11 +1355,16 @@ static int mx3fb_suspend(struct platform_device *pdev, pm_message_t state)
>  {
>       struct mx3fb_data *mx3fb = platform_get_drvdata(pdev);
>       struct mx3fb_info *mx3_fbi = mx3fb->fbi->par;
> +     struct mx3fb_info *mx3_fbi_ovl = mx3fb->fbi_ovl->par;
>
>       console_lock();
>       fb_set_suspend(mx3fb->fbi, 1);
> +     fb_set_suspend(mx3fb->fbi_ovl, 1);
>       console_unlock();
>
> +     if (mx3_fbi_ovl->blank = FB_BLANK_UNBLANK)
> +             sdc_disable_channel(mx3_fbi_ovl);
> +
>       if (mx3_fbi->blank = FB_BLANK_UNBLANK) {
>               sdc_disable_channel(mx3_fbi);
>               sdc_set_brightness(mx3fb, 0);
> @@ -1184,14 +1380,19 @@ static int mx3fb_resume(struct platform_device *pdev)
>  {
>       struct mx3fb_data *mx3fb = platform_get_drvdata(pdev);
>       struct mx3fb_info *mx3_fbi = mx3fb->fbi->par;
> +     struct mx3fb_info *mx3_fbi_ovl = mx3fb->fbi_ovl->par;
>
>       if (mx3_fbi->blank = FB_BLANK_UNBLANK) {
>               sdc_enable_channel(mx3_fbi);
>               sdc_set_brightness(mx3fb, mx3fb->backlight_level);
>       }
>
> +     if (mx3_fbi_ovl->blank = FB_BLANK_UNBLANK)
> +             sdc_enable_channel(mx3_fbi_ovl);
> +
>       console_lock();
>       fb_set_suspend(mx3fb->fbi, 0);
> +     fb_set_suspend(mx3fb->fbi_ovl, 0);
>       console_unlock();
>
>       return 0;
> @@ -1333,8 +1534,8 @@ static int init_fb_chan(struct mx3fb_data *mx3fb, struct idmac_channel *ichan)
>       ichan->client = mx3fb;
>       irq = ichan->eof_irq;
>
> -     if (ichan->dma_chan.chan_id != IDMAC_SDC_0)
> -             return -EINVAL;
> +     switch (ichan->dma_chan.chan_id) {
> +     case IDMAC_SDC_0:
>
>       fbi = mx3fb_init_fbinfo(dev, &mx3fb_ops);

> > I would bite the bullet and indent this case block...

This makes a clear separation between the framebuffer and overlay
channels during initializing, but if you have any ideas welcome, please
send, I could do a test on my hardware :-)

>       if (!fbi)
> @@ -1375,7 +1576,29 @@ static int init_fb_chan(struct mx3fb_data *mx3fb, struct idmac_channel *ichan)
>
>       sdc_set_brightness(mx3fb, 255);
>       sdc_set_global_alpha(mx3fb, true, 0xFF);
> -     sdc_set_color_key(mx3fb, IDMAC_SDC_0, false, 0);
> +     sdc_set_color_key(mx3fb, false, 0);
> +
> +     break;
> +#ifdef CONFIG_FB_MX3_OVERLAY
> +     case IDMAC_SDC_1:
> +
> +             /* We know, that background has been allocated already! */
> +             fbi = mx3fb_init_fbinfo(dev, &mx3fb_ovl_ops);
> +             if (!fbi)
> +                     return -ENOMEM;
> +
> +             /* Default Y virtual size is 2x panel size */
> +             fbi->var = mx3fb->fbi->var;
> +             /* This shouldn't be necessary, it is already set up above */
> +             fbi->var.yres_virtual = mx3fb->fbi->var.yres * 2;
> +
> +             mx3fb->fbi_ovl = fbi;
> +
> +             break;
> +#endif
> +     default:
> +             return -EINVAL;
> +     }
>
>       mx3fbi                  = fbi->par;
>       mx3fbi->idmac_channel   = ichan;
> @@ -1392,9 +1615,13 @@ static int init_fb_chan(struct mx3fb_data *mx3fb, struct idmac_channel *ichan)
>       if (ret < 0)
>               goto esetpar;
>
> -     __blank(FB_BLANK_UNBLANK, fbi);
> +     /* Overlay stays blanked by default */
> +     if (ichan->dma_chan.chan_id = IDMAC_SDC_0) {
> +             mx3fb_blank(FB_BLANK_UNBLANK, fbi);
>
> -     dev_info(dev, "registered, using mode %s\n", fb_mode);
> +             dev_info(dev, "mx3fb: fb registered, using mode %s [%c]\n",
> +             fb_mode, list_empty(&ichan->queue) ? '-' : '+');
> +     }
>
>       ret = register_framebuffer(fbi);
>       if (ret < 0)
> @@ -1492,14 +1719,42 @@ static int mx3fb_probe(struct platform_device *pdev)
>
>       mx3fb->backlight_level = 255;
>
> +     mx3fb->idmac_channel[0] = to_idmac_chan(chan);
> +     mx3fb->idmac_channel[0]->client = mx3fb;
> +
>       ret = init_fb_chan(mx3fb, to_idmac_chan(chan));
>       if (ret < 0)
>               goto eisdc0;
>
> +#ifdef CONFIG_FB_MX3_OVERLAY
> +     dma_cap_zero(mask);
> +     dma_cap_set(DMA_SLAVE, mask);
> +     dma_cap_set(DMA_PRIVATE, mask);
> +     rq.id = IDMAC_SDC_1;
> +     chan = dma_request_channel(mask, chan_filter, &rq);
> +     if (!chan) {
> +             ret = -EBUSY;
> +             goto ersdc1;
> +     }
> +
> +     mx3fb->idmac_channel[1] = to_idmac_chan(chan);
> +     mx3fb->idmac_channel[1]->client = mx3fb;
> +
> +     ret = init_fb_chan(mx3fb, to_idmac_chan(chan));
> +     if (ret < 0)
> +             goto eisdc1;
> +#endif
> +
>       return 0;
>
> +#ifdef CONFIG_FB_MX3_OVERLAY
> +eisdc1:
> +     dma_release_channel(&mx3fb->idmac_channel[1]->dma_chan);
> +ersdc1:
> +     release_fbi(mx3fb->fbi);
> +#endif
>  eisdc0:
> -     dma_release_channel(chan);
> +     dma_release_channel(&mx3fb->idmac_channel[0]->dma_chan);
>  ersdc0:
>       dmaengine_put();
>       iounmap(mx3fb->reg_base);
> @@ -1513,13 +1768,16 @@ static int mx3fb_remove(struct platform_device *dev)
>  {
>       struct mx3fb_data *mx3fb = platform_get_drvdata(dev);
>       struct fb_info *fbi = mx3fb->fbi;
> -     struct mx3fb_info *mx3_fbi = fbi->par;
> -     struct dma_chan *chan;
>
> -     chan = &mx3_fbi->idmac_channel->dma_chan;
> -     release_fbi(fbi);
> +     if (fbi)
> +             release_fbi(fbi);
> +
> +     fbi = mx3fb->fbi_ovl;
> +     if (fbi)
> +             release_fbi(fbi);
>
> -     dma_release_channel(chan);
> +     dma_release_channel(&mx3fb->idmac_channel[1]->dma_chan);
> +     dma_release_channel(&mx3fb->idmac_channel[0]->dma_chan);
>       dmaengine_put();
>
>       iounmap(mx3fb->reg_base);
> diff --git a/include/linux/mxcfb.h b/include/linux/mxcfb.h
> new file mode 100644
> index 0000000..54b720d
> --- /dev/null
> +++ b/include/linux/mxcfb.h
> @@ -0,0 +1,93 @@
> +/*
> + * File: include/linux/mxcfb.h
> + * Global header file for the MXC Framebuffer
> + *
> + * Copyright 2004-2012 Freescale Semiconductor, Inc. All Rights Reserved.
> + *
> + * The code contained herein is licensed under the GNU Lesser General
> + * Public License.  You may obtain a copy of the GNU Lesser General
> + * Public License Version 2.1 or later at the following locations:
> + *
> + * http://www.opensource.org/licenses/lgpl-license.html
> + * http://www.gnu.org/copyleft/lgpl.html
> + */
> +
> +#ifndef __LINUX_MXCFB_H__
> +#define __LINUX_MXCFB_H__
> +
> +#include <linux/fb.h>

> > Why is this needed here?

I checked, it is not necessary

> +
> +#define FB_SYNC_OE_LOW_ACT   0x80000000
> +#define FB_SYNC_CLK_LAT_FALL 0x40000000
> +#define FB_SYNC_DATA_INVERT  0x20000000
> +#define FB_SYNC_CLK_IDLE_EN  0x10000000
> +#define FB_SYNC_SHARP_MODE   0x08000000
> +#define FB_SYNC_SWAP_RGB     0x04000000
> +
> +struct mxcfb_gbl_alpha {
> +     int enable;
> +     int alpha;
> +};
> +
> +struct mxcfb_color_key {
> +     int enable;
> +     __u32 color_key;
> +};
> +
> +struct mxcfb_pos {
> +     __u16 x;
> +     __u16 y;
> +};
> +
> +struct mxcfb_gamma {
> +     int enable;
> +     int constk[16];
> +     int slopek[16];
> +};
> +
> +struct mxcfb_rect {
> +     __u32 top;
> +     __u32 left;
> +     __u32 width;
> +     __u32 height;
> +};
> +
> +/*
> + * Structure used to define waveform modes for driver
> + * Needed for driver to perform auto-waveform selection
> + */
> +struct mxcfb_waveform_modes {
> +     int mode_init;
> +     int mode_du;
> +     int mode_gc4;
> +     int mode_gc8;
> +     int mode_gc16;
> +     int mode_gc32;
> +};
> +
> +/* IOCTL commands. */
> +
> +#define MXCFB_WAIT_FOR_VSYNC         _IOW('F', 0x20, u_int32_t)
> +#define MXCFB_SET_GBL_ALPHA          _IOW('F', 0x21, struct mxcfb_gbl_alpha)
> +#define MXCFB_SET_CLR_KEY            _IOW('F', 0x22, struct mxcfb_color_key)
> +#define MXCFB_SET_OVERLAY_POS                _IOWR('F', 0x24, struct mxcfb_pos)
> +#define MXCFB_GET_FB_IPU_CHAN                _IOR('F', 0x25, u_int32_t)
> +#define MXCFB_SET_LOC_ALPHA          _IOWR('F', 0x26, struct mxcfb_loc_alpha)
> +#define MXCFB_SET_LOC_ALP_BUF                _IOW('F', 0x27, unsigned long)
> +#define MXCFB_SET_GAMMA                      _IOW('F', 0x28, struct mxcfb_gamma)
> +#define MXCFB_GET_FB_IPU_DI          _IOR('F', 0x29, u_int32_t)
> +#define MXCFB_GET_DIFMT                      _IOR('F', 0x2A, u_int32_t)
> +#define MXCFB_GET_FB_BLANK           _IOR('F', 0x2B, u_int32_t)

> > Please, don't add unused identifiers.

Yes, I can remove something.

> +
> +#ifdef __KERNEL__
> +
> +enum {
> +     MXCFB_REFRESH_OFF,
> +     MXCFB_REFRESH_AUTO,
> +     MXCFB_REFRESH_PARTIAL,
> +};
> +
> +#endif
> +
> +#endif /* _MXCFB_H */
> +
> --
> 1.7.0.4
>

Thanks
Alex

^ permalink raw reply

* [PATCH 4/4] backlight: add LM3533 backlight driver
From: Johan Hovold @ 2012-04-20 15:30 UTC (permalink / raw)
  To: Richard Purdie, Samuel Ortiz, Arnd Bergmann, Greg Kroah-Hartman,
	Florian Tobias Schandinat
  Cc: Andrew Morton, linux-kernel, linux-fbdev, Johan Hovold
In-Reply-To: <1334935826-12527-1-git-send-email-jhovold@gmail.com>

Add sub-driver for the backlights in National Semiconductor / TI LM3533
lighting power chips.

The chip provides 256 brightness levels and ambient-light-sensor and pwm
input control.

Signed-off-by: Johan Hovold <jhovold@gmail.com>
---
 drivers/video/backlight/Kconfig     |   12 +
 drivers/video/backlight/Makefile    |    1 +
 drivers/video/backlight/lm3533_bl.c |  432 +++++++++++++++++++++++++++++++++++
 3 files changed, 445 insertions(+), 0 deletions(-)
 create mode 100644 drivers/video/backlight/lm3533_bl.c

diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
index af16884..45455e7 100644
--- a/drivers/video/backlight/Kconfig
+++ b/drivers/video/backlight/Kconfig
@@ -184,6 +184,18 @@ config BACKLIGHT_GENERIC
 	  known as the Corgi backlight driver. If you have a Sharp Zaurus
 	  SL-C7xx, SL-Cxx00 or SL-6000x say y.
 
+config BACKLIGHT_LM3533
+	tristate "Backlight Driver for LM3533"
+	depends on BACKLIGHT_CLASS_DEVICE
+	depends on MFD_LM3533
+	help
+	  Say Y to enable the backlight driver for National Semiconductor / TI
+	  LM3533 Lighting Power chips.
+
+	  The backlights can be controlled directly, through PWM input, or by
+	  the on-chip ambient light sensor. The chip supports 256 brightness
+	  levels.
+
 config BACKLIGHT_LOCOMO
 	tristate "Sharp LOCOMO LCD/Backlight Driver"
 	depends on SHARP_LOCOMO
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
index 36855ae..a2ac9cf 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -21,6 +21,7 @@ obj-$(CONFIG_BACKLIGHT_EP93XX)	+= ep93xx_bl.o
 obj-$(CONFIG_BACKLIGHT_GENERIC)	+= generic_bl.o
 obj-$(CONFIG_BACKLIGHT_HP700)	+= jornada720_bl.o
 obj-$(CONFIG_BACKLIGHT_HP680)	+= hp680_bl.o
+obj-$(CONFIG_BACKLIGHT_LM3533)	+= lm3533_bl.o
 obj-$(CONFIG_BACKLIGHT_LOCOMO)	+= locomolcd.o
 obj-$(CONFIG_BACKLIGHT_LP855X)	+= lp855x_bl.o
 obj-$(CONFIG_BACKLIGHT_OMAP1)	+= omap1_bl.o
diff --git a/drivers/video/backlight/lm3533_bl.c b/drivers/video/backlight/lm3533_bl.c
new file mode 100644
index 0000000..4217e1f
--- /dev/null
+++ b/drivers/video/backlight/lm3533_bl.c
@@ -0,0 +1,432 @@
+/*
+ * lm3533-bl.c -- LM3533 Backlight driver
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/backlight.h>
+#include <linux/fb.h>
+#include <linux/slab.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+#define LM3533_HVCTRLBANK_COUNT		2
+#define LM3533_BL_MAX_BRIGHTNESS	255
+
+#define LM3533_REG_CTRLBANK_AB_BCONF	0x1a
+
+
+struct lm3533_bl {
+	struct lm3533 *lm3533;
+	struct lm3533_ctrlbank cb;
+	struct backlight_device *bd;
+	int id;
+};
+
+
+static inline int lm3533_bl_get_ctrlbank_id(struct lm3533_bl *bl)
+{
+	return bl->id;
+}
+
+static int lm3533_bl_update_status(struct backlight_device *bd)
+{
+	struct lm3533_bl *bl = bl_get_data(bd);
+	int brightness = bd->props.brightness;
+
+	if (bd->props.power != FB_BLANK_UNBLANK)
+		brightness = 0;
+	if (bd->props.fb_blank != FB_BLANK_UNBLANK)
+		brightness = 0;
+
+	return lm3533_ctrlbank_set_brightness(&bl->cb, (u8)brightness);
+}
+
+static int lm3533_bl_get_brightness(struct backlight_device *bd)
+{
+	struct lm3533_bl *bl = bl_get_data(bd);
+	u8 val;
+	int ret;
+
+	ret = lm3533_ctrlbank_get_brightness(&bl->cb, &val);
+	if (ret)
+		return ret;
+
+	return val;
+}
+
+static const struct backlight_ops lm3533_bl_ops = {
+	.get_brightness	= lm3533_bl_get_brightness,
+	.update_status	= lm3533_bl_update_status,
+};
+
+static ssize_t show_id(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", bl->id);
+}
+
+/*
+ * ALS settings:
+ *
+ *   0 - ALS disabled
+ *   1 - ALS mapper 1 (backlight 0)
+ *   2 - ALS mapper 2 (backlight 1)
+ */
+static ssize_t show_als(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	int ctrlbank = lm3533_bl_get_ctrlbank_id(bl);
+	u8 val;
+	u8 mask;
+	int als;
+	int ret;
+
+	ret = lm3533_read(bl->lm3533, LM3533_REG_CTRLBANK_AB_BCONF, &val);
+	if (ret)
+		return ret;
+
+	mask = 2 * ctrlbank;
+	als = val & mask;
+	if (als)
+		als = ctrlbank + 1;
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", als);
+}
+
+static ssize_t store_als(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	int ctrlbank = lm3533_bl_get_ctrlbank_id(bl);
+	int als;
+	u8 val;
+	u8 mask;
+	int ret;
+
+	if (kstrtoint(buf, 0, &als))
+		return -EINVAL;
+
+	if (als != 0 && (als != ctrlbank + 1))
+		return -EINVAL;
+
+	mask = 1 << (2 * ctrlbank);
+
+	if (als)
+		val = mask;
+	else
+		val = 0;
+
+	ret = lm3533_update(bl->lm3533, LM3533_REG_CTRLBANK_AB_BCONF, val,
+									mask);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t show_linear(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	u8 val;
+	u8 mask;
+	int linear;
+	int ret;
+
+	ret = lm3533_read(bl->lm3533, LM3533_REG_CTRLBANK_AB_BCONF, &val);
+	if (ret)
+		return ret;
+
+	mask = 1 << (2 * lm3533_bl_get_ctrlbank_id(bl) + 1);
+
+	if (val & mask)
+		linear = 1;
+	else
+		linear = 0;
+
+	return scnprintf(buf, PAGE_SIZE, "%x\n", linear);
+}
+
+static ssize_t store_linear(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	unsigned long linear;
+	u8 mask;
+	u8 val;
+	int ret;
+
+	if (kstrtoul(buf, 0, &linear))
+		return -EINVAL;
+
+	mask = 1 << (2 * lm3533_bl_get_ctrlbank_id(bl) + 1);
+
+	if (linear)
+		val = mask;
+	else
+		val = 0;
+
+	ret = lm3533_update(bl->lm3533, LM3533_REG_CTRLBANK_AB_BCONF, val,
+									mask);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+#define show_ctrlbank_attr(_name)					\
+static ssize_t show_##_name(struct device *dev,				\
+				struct device_attribute *attr,		\
+				char *buf)				\
+{									\
+	struct lm3533_bl *bl = dev_get_drvdata(dev);			\
+	u8 val;								\
+	int ret;							\
+									\
+	ret = lm3533_ctrlbank_get_##_name(&bl->cb, &val);		\
+	if (ret)							\
+		return ret;						\
+									\
+	return scnprintf(buf, PAGE_SIZE, "%d\n", val);			\
+}
+
+#define store_ctrlbank_attr(_name)					\
+static ssize_t store_##_name(struct device *dev,			\
+				struct device_attribute *attr,		\
+				const char *buf, size_t len)		\
+{									\
+	struct lm3533_bl *bl = dev_get_drvdata(dev);			\
+	u8 val;								\
+	int ret;							\
+									\
+	if (kstrtou8(buf, 0, &val))					\
+		return -EINVAL;						\
+									\
+	ret = lm3533_ctrlbank_set_##_name(&bl->cb, val);		\
+	if (ret)							\
+		return ret;						\
+									\
+	return len;							\
+}
+
+show_ctrlbank_attr(max_current);
+store_ctrlbank_attr(max_current);
+show_ctrlbank_attr(pwm);
+store_ctrlbank_attr(pwm);
+
+static LM3533_ATTR_RW(als);
+static LM3533_ATTR_RO(id);
+static LM3533_ATTR_RW(linear);
+static LM3533_ATTR_RW(max_current);
+static LM3533_ATTR_RW(pwm);
+
+static struct attribute *lm3533_bl_attributes[] = {
+	&dev_attr_als.attr,
+	&dev_attr_id.attr,
+	&dev_attr_linear.attr,
+	&dev_attr_max_current.attr,
+	&dev_attr_pwm.attr,
+	NULL,
+};
+
+static mode_t lm3533_bl_attr_is_visible(struct kobject *kobj,
+					     struct attribute *attr, int n)
+{
+	struct device *dev = container_of(kobj, struct device, kobj);
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	mode_t mode = attr->mode;
+
+	if (attr = &dev_attr_als.attr) {
+		if (!bl->lm3533->have_als)
+			mode = 0;
+	}
+
+	return mode;
+};
+
+static struct attribute_group lm3533_bl_attribute_group = {
+	.is_visible	= lm3533_bl_attr_is_visible,
+	.attrs		= lm3533_bl_attributes
+};
+
+static int __devinit lm3533_bl_setup(struct lm3533_bl *bl,
+					struct lm3533_bl_platform_data *pdata)
+{
+	int ret;
+
+	ret = lm3533_ctrlbank_set_max_current(&bl->cb, pdata->max_current);
+	if (ret)
+		return ret;
+
+	return lm3533_ctrlbank_set_pwm(&bl->cb, pdata->pwm);
+}
+
+static int __devinit lm3533_bl_probe(struct platform_device *pdev)
+{
+	struct lm3533 *lm3533;
+	struct lm3533_bl_platform_data *pdata;
+	struct lm3533_bl *bl;
+	struct backlight_device *bd;
+	struct backlight_properties props;
+	int ret;
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533 = dev_get_drvdata(pdev->dev.parent);
+	if (!lm3533)
+		return -EINVAL;
+
+	pdata = pdev->dev.platform_data;
+	if (!pdata) {
+		dev_err(&pdev->dev, "no platform data\n");
+		return -EINVAL;
+	}
+
+	if (pdev->id < 0 || pdev->id >= LM3533_HVCTRLBANK_COUNT) {
+		dev_err(&pdev->dev, "illegal backlight id %d\n", pdev->id);
+		return -EINVAL;
+	}
+
+	bl = kzalloc(sizeof(*bl), GFP_KERNEL);
+	if (!bl) {
+		dev_err(&pdev->dev,
+				"failed to allocate memory for backlight\n");
+		return -ENOMEM;
+	}
+
+	bl->lm3533 = lm3533;
+	bl->id = pdev->id;
+
+	bl->cb.lm3533 = lm3533;
+	bl->cb.id = lm3533_bl_get_ctrlbank_id(bl);
+	bl->cb.dev = NULL;			/* until registered */
+
+	memset(&props, 0, sizeof(props));
+	props.type = BACKLIGHT_RAW;
+	props.max_brightness = LM3533_BL_MAX_BRIGHTNESS;
+	props.brightness = pdata->default_brightness;
+	bd = backlight_device_register(pdata->name, pdev->dev.parent, bl,
+						&lm3533_bl_ops, &props);
+	if (IS_ERR(bd)) {
+		dev_err(&pdev->dev, "failed to register backlight device\n");
+		ret = PTR_ERR(bd);
+		goto err_free;
+	}
+
+	bl->bd = bd;
+	bl->cb.dev = &bl->bd->dev;
+
+	platform_set_drvdata(pdev, bl);
+
+	ret = sysfs_create_group(&bd->dev.kobj, &lm3533_bl_attribute_group);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed to create sysfs attributes\n");
+		goto err_unregister;
+	}
+
+	backlight_update_status(bd);
+
+	ret = lm3533_bl_setup(bl, pdata);
+	if (ret)
+		goto err_sysfs_remove;
+
+	ret = lm3533_ctrlbank_enable(&bl->cb);
+	if (ret)
+		goto err_sysfs_remove;
+
+	return 0;
+
+err_sysfs_remove:
+	sysfs_remove_group(&bd->dev.kobj, &lm3533_bl_attribute_group);
+err_unregister:
+	backlight_device_unregister(bd);
+err_free:
+	kfree(bl);
+
+	return ret;
+}
+
+static int __devexit lm3533_bl_remove(struct platform_device *pdev)
+{
+	struct lm3533_bl *bl = platform_get_drvdata(pdev);
+	struct backlight_device *bd = bl->bd;
+
+	dev_dbg(&bd->dev, "%s\n", __func__);
+
+	bd->props.power = FB_BLANK_POWERDOWN;
+	bd->props.brightness = 0;
+
+	lm3533_ctrlbank_disable(&bl->cb);
+	sysfs_remove_group(&bd->dev.kobj, &lm3533_bl_attribute_group);
+	backlight_device_unregister(bd);
+	kfree(bl);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int lm3533_bl_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	struct lm3533_bl *bl = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	return lm3533_ctrlbank_disable(&bl->cb);
+}
+
+static int lm3533_bl_resume(struct platform_device *pdev)
+{
+	struct lm3533_bl *bl = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	return lm3533_ctrlbank_enable(&bl->cb);
+}
+#else
+#define lm3533_bl_suspend	NULL
+#define lm3533_bl_resume	NULL
+#endif
+
+static void lm3533_bl_shutdown(struct platform_device *pdev)
+{
+	struct lm3533_bl *bl = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533_ctrlbank_disable(&bl->cb);
+}
+
+static struct platform_driver lm3533_bl_driver = {
+	.driver = {
+		.name	= "lm3533-backlight",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= lm3533_bl_probe,
+	.remove		= __devexit_p(lm3533_bl_remove),
+	.shutdown	= lm3533_bl_shutdown,
+	.suspend	= lm3533_bl_suspend,
+	.resume		= lm3533_bl_resume,
+};
+module_platform_driver(lm3533_bl_driver);
+
+MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
+MODULE_DESCRIPTION("LM3533 Backlight driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lm3533-backlight");
-- 
1.7.8.5


^ permalink raw reply related

* [PATCH 3/4] leds: add LM3533 LED driver
From: Johan Hovold @ 2012-04-20 15:30 UTC (permalink / raw)
  To: Richard Purdie, Samuel Ortiz, Arnd Bergmann, Greg Kroah-Hartman,
	Florian Tobias Schandinat
  Cc: Andrew Morton, linux-kernel, linux-fbdev, Johan Hovold
In-Reply-To: <1334935826-12527-1-git-send-email-jhovold@gmail.com>

Add sub-driver for the LEDs in National Semiconductor / TI LM3533
lighting power chips.

The chip provides 256 brightness levels, hardware accelerated blinking
as well as ambient-light-sensor and pwm input control.

Signed-off-by: Johan Hovold <jhovold@gmail.com>
---
 drivers/leds/Kconfig       |   13 +
 drivers/leds/Makefile      |    1 +
 drivers/leds/leds-lm3533.c |  713 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 727 insertions(+), 0 deletions(-)
 create mode 100644 drivers/leds/leds-lm3533.c

diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index ff4b8cf..22a4e62 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -50,6 +50,19 @@ config LEDS_LM3530
 	  controlled manually or using PWM input or using ambient
 	  light automatically.
 
+config LEDS_LM3533
+	tristate "LED support for LM3533"
+	depends on LEDS_CLASS
+	depends on MFD_LM3533
+	help
+	  This option enables support for the LEDs on National Semiconductor /
+	  TI LM3533 Lighting Power chips.
+
+	  The LEDs can be controlled directly, through PWM input, or by the
+	  on-chip ambient light sensor. The chip supports hardware-accelerated
+	  blinking with maximum on and off periods of 9.8 and 77 seconds
+	  respectively.
+
 config LEDS_LOCOMO
 	tristate "LED Support for Locomo device"
 	depends on LEDS_CLASS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 890481c..f39a526 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -10,6 +10,7 @@ obj-$(CONFIG_LEDS_ATMEL_PWM)		+= leds-atmel-pwm.o
 obj-$(CONFIG_LEDS_BD2802)		+= leds-bd2802.o
 obj-$(CONFIG_LEDS_LOCOMO)		+= leds-locomo.o
 obj-$(CONFIG_LEDS_LM3530)		+= leds-lm3530.o
+obj-$(CONFIG_LEDS_LM3533)		+= leds-lm3533.o
 obj-$(CONFIG_LEDS_MIKROTIK_RB532)	+= leds-rb532.o
 obj-$(CONFIG_LEDS_S3C24XX)		+= leds-s3c24xx.o
 obj-$(CONFIG_LEDS_NET48XX)		+= leds-net48xx.o
diff --git a/drivers/leds/leds-lm3533.c b/drivers/leds/leds-lm3533.c
new file mode 100644
index 0000000..8e34b7a
--- /dev/null
+++ b/drivers/leds/leds-lm3533.c
@@ -0,0 +1,713 @@
+/*
+ * leds-lm3533.c -- LM3533 LED driver
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/leds.h>
+#include <linux/mfd/core.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+#define LM3533_LVCTRLBANK_MIN		2
+#define LM3533_LVCTRLBANK_MAX		5
+#define LM3533_LVCTRLBANK_COUNT		4
+#define LM3533_RISEFALLTIME_MAX		7
+#define LM3533_ALS_LV_MIN		2
+#define LM3533_ALS_LV_MAX		3
+
+#define LM3533_REG_CTRLBANK_BCONF_BASE		0x1b
+#define LM3533_REG_PATTERN_ENABLE		0x28
+#define LM3533_REG_PATTERN_LOW_TIME_BASE	0x71
+#define LM3533_REG_PATTERN_HIGH_TIME_BASE	0x72
+#define LM3533_REG_PATTERN_RISETIME_BASE	0x74
+#define LM3533_REG_PATTERN_FALLTIME_BASE	0x75
+
+#define LM3533_REG_PATTERN_STEP			0x10
+
+#define LM3533_REG_CTRLBANK_BCONF_MAPPING_MASK	0x04
+#define LM3533_REG_CTRLBANK_BCONF_ALS_MASK	0x03
+
+#define LM3533_LED_FLAG_PATTERN_ENABLE		1
+
+
+struct lm3533_led {
+	struct lm3533 *lm3533;
+	struct lm3533_ctrlbank cb;
+	struct led_classdev cdev;
+	int id;
+
+	struct mutex mutex;
+	unsigned long flags;
+
+	struct work_struct work;
+	u8 new_brightness;
+};
+
+#define to_lm3533_led(_cdev) \
+	container_of(_cdev, struct lm3533_led, cdev)
+
+
+static inline int lm3533_led_get_ctrlbank_id(struct lm3533_led *led)
+{
+	return led->id + 2;
+}
+
+static inline u8 lm3533_led_get_lv_reg(struct lm3533_led *led, u8 base)
+{
+	return base + led->id;
+}
+
+static inline u8 lm3533_led_get_pattern(struct lm3533_led *led)
+{
+	return led->id;
+}
+
+static inline u8 lm3533_led_get_pattern_reg(struct lm3533_led *led,
+								u8 base)
+{
+	return base + lm3533_led_get_pattern(led) * LM3533_REG_PATTERN_STEP;
+}
+
+static int lm3533_led_pattern_enable(struct lm3533_led *led, int enable)
+{
+	u8 mask;
+	u8 val;
+	int pattern;
+	int state;
+	int ret = 0;
+
+	dev_dbg(led->cdev.dev, "%s - %d\n", __func__, enable);
+
+	mutex_lock(&led->mutex);
+
+	state = test_bit(LM3533_LED_FLAG_PATTERN_ENABLE, &led->flags);
+	if ((enable && state) || (!enable && !state))
+		goto out;
+
+	pattern = lm3533_led_get_pattern(led);
+	mask = 1 << (2 * pattern);
+
+	if (enable)
+		val = mask;
+	else
+		val = 0;
+
+	ret = lm3533_update(led->lm3533, LM3533_REG_PATTERN_ENABLE, val, mask);
+	if (ret) {
+		dev_err(led->cdev.dev, "failed to enable pattern %d (%d)\n",
+							pattern, enable);
+		goto out;
+	}
+
+	__change_bit(LM3533_LED_FLAG_PATTERN_ENABLE, &led->flags);
+out:
+	mutex_unlock(&led->mutex);
+
+	return ret;
+}
+
+static void lm3533_led_work(struct work_struct *work)
+{
+	struct lm3533_led *led = container_of(work, struct lm3533_led, work);
+
+	dev_dbg(led->cdev.dev, "%s - %u\n", __func__, led->new_brightness);
+
+	if (led->new_brightness = 0)
+		lm3533_led_pattern_enable(led, 0);	/* disable blink */
+
+	lm3533_ctrlbank_set_brightness(&led->cb, led->new_brightness);
+}
+
+static void lm3533_led_set(struct led_classdev *cdev,
+						enum led_brightness value)
+{
+	struct lm3533_led *led = to_lm3533_led(cdev);
+
+	dev_dbg(led->cdev.dev, "%s - %d\n", __func__, value);
+
+	led->new_brightness = value;
+	schedule_work(&led->work);
+}
+
+static enum led_brightness lm3533_led_get(struct led_classdev *cdev)
+{
+	struct lm3533_led *led = to_lm3533_led(cdev);
+	u8 val;
+	int ret;
+
+	ret = lm3533_ctrlbank_get_brightness(&led->cb, &val);
+	if (ret)
+		return ret;
+
+	dev_dbg(led->cdev.dev, "%s - %u\n", __func__, val);
+
+	return val;
+}
+
+/* Pattern generator defines -- delays in us */
+#define LM3533_LED_DELAY_GROUP1_BASE	0x00
+#define LM3533_LED_DELAY_GROUP2_BASE	0x3d
+#define LM3533_LED_DELAY_GROUP3_BASE	0x80
+#define LM3533_LED_DELAY_MAX		0xff
+
+#define LM3533_LED_DELAY_GROUP1_STEP	16384
+#define LM3533_LED_DELAY_GROUP2_STEP	131072
+#define LM3533_LED_DELAY_GROUP3_STEP	524288
+#define LM3533_LED_DELAY_GROUP1_MIN	16384
+#define LM3533_LED_DELAY_GROUP2_MIN	1130496
+#define LM3533_LED_DELAY_GROUP3_MIN	10305536
+#define LM3533_LED_DELAY_GROUP1_MAX	999424
+#define LM3533_LED_DELAY_GROUP2_MAX	9781248
+#define LM3533_LED_DELAY_GROUP3_MAX	76890112
+
+/* Delay limits in ms */
+#define LM3533_LED_DELAY_ON_MAX		9845
+#define LM3533_LED_DELAY_OFF_MAX	77140
+
+static int time_to_val(long *t, long t_min, long t_max, long t_step,
+							int v_min, int v_max)
+{
+	int val;
+
+	*t += t_step / 2;
+	val = (*t - t_min) / t_step + v_min;
+	val = clamp(val, v_min, v_max);
+	*t = t_step * (val - v_min) + t_min;
+
+	return val;
+}
+
+static int lm3533_led_get_delay(long *delay)
+{
+	int val;
+
+	*delay *= 1000;
+
+	if (*delay >= LM3533_LED_DELAY_GROUP3_MIN -
+					LM3533_LED_DELAY_GROUP3_STEP / 2) {
+		val = time_to_val(delay, LM3533_LED_DELAY_GROUP3_MIN,
+					LM3533_LED_DELAY_GROUP3_MAX,
+					LM3533_LED_DELAY_GROUP3_STEP,
+					LM3533_LED_DELAY_GROUP3_BASE,
+					0xff);
+	} else if (*delay >= LM3533_LED_DELAY_GROUP2_MIN -
+					LM3533_LED_DELAY_GROUP2_STEP / 2) {
+		val = time_to_val(delay, LM3533_LED_DELAY_GROUP2_MIN,
+					LM3533_LED_DELAY_GROUP2_MAX,
+					LM3533_LED_DELAY_GROUP2_STEP,
+					LM3533_LED_DELAY_GROUP2_BASE,
+					LM3533_LED_DELAY_GROUP3_BASE - 1);
+	} else {
+		val = time_to_val(delay, LM3533_LED_DELAY_GROUP1_MIN,
+					LM3533_LED_DELAY_GROUP1_MAX,
+					LM3533_LED_DELAY_GROUP1_STEP,
+					LM3533_LED_DELAY_GROUP1_BASE,
+					LM3533_LED_DELAY_GROUP2_BASE - 1);
+	}
+
+	*delay /= 1000;
+
+	return val;
+}
+
+static int lm3533_led_delay_set(struct lm3533_led *led, u8 base,
+							unsigned long *delay)
+{
+	u8 val;
+	u8 reg;
+	long t;
+	int ret;
+
+	t = *delay;
+	val = lm3533_led_get_delay(&t);
+
+	dev_dbg(led->cdev.dev, "%s - %lu: %ld (0x%02x)\n", __func__,
+							*delay, t, val);
+	reg = lm3533_led_get_pattern_reg(led, base);
+	ret = lm3533_write(led->lm3533, reg, val);
+	if (ret)
+		dev_err(led->cdev.dev, "failed to set delay (%02x)\n", reg);
+
+	*delay = t;
+
+	return ret;
+}
+
+static int lm3533_led_delay_on_set(struct lm3533_led *led, unsigned long *t)
+{
+	*t = min_t(long, *t, LM3533_LED_DELAY_GROUP2_MAX / 1000);
+
+	return lm3533_led_delay_set(led, LM3533_REG_PATTERN_HIGH_TIME_BASE, t);
+}
+
+static int lm3533_led_delay_off_set(struct lm3533_led *led, unsigned long *t)
+{
+	*t = min_t(long, *t, LM3533_LED_DELAY_GROUP3_MAX / 1000);
+
+	return lm3533_led_delay_set(led, LM3533_REG_PATTERN_LOW_TIME_BASE, t);
+}
+
+static int lm3533_led_blink_set(struct led_classdev *cdev,
+				unsigned long *delay_on,
+				unsigned long *delay_off)
+{
+	struct lm3533_led *led = to_lm3533_led(cdev);
+	int ret;
+
+	dev_dbg(led->cdev.dev, "%s - on = %lu, off = %lu\n", __func__,
+							*delay_on, *delay_off);
+
+	if (*delay_on > LM3533_LED_DELAY_ON_MAX ||
+					*delay_off > LM3533_LED_DELAY_OFF_MAX)
+		return -EINVAL;
+
+	if (*delay_on = 0 && *delay_off = 0) {
+		*delay_on = 500;
+		*delay_off = 500;
+	}
+
+	ret = lm3533_led_delay_on_set(led, delay_on);
+	if (ret)
+		return ret;
+
+	ret = lm3533_led_delay_off_set(led, delay_off);
+	if (ret)
+		return ret;
+
+	return lm3533_led_pattern_enable(led, 1);
+}
+
+static ssize_t show_id(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", led->id);
+}
+
+/*
+ * Pattern generator rise/fall times:
+ *
+ *   0 - 2048 us (default)
+ *   1 - 262 ms
+ *   2 - 524 ms
+ *   3 - 1.049 s
+ *   4 - 2.097 s
+ *   5 - 4.194 s
+ *   6 - 8.389 s
+ *   7 - 16.78 s
+ */
+static ssize_t show_risefalltime(struct device *dev,
+					struct device_attribute *attr,
+					char *buf, u8 base)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	ssize_t ret;
+	u8 reg;
+	u8 val;
+
+	reg = lm3533_led_get_pattern_reg(led, base);
+	ret = lm3533_read(led->lm3533, reg, &val);
+	if (ret)
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%x\n", val);
+}
+
+static ssize_t show_risetime(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	return show_risefalltime(dev, attr, buf,
+					LM3533_REG_PATTERN_RISETIME_BASE);
+}
+
+static ssize_t show_falltime(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	return show_risefalltime(dev, attr, buf,
+					LM3533_REG_PATTERN_FALLTIME_BASE);
+}
+
+static ssize_t store_risefalltime(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len, u8 base)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	u8 val;
+	u8 reg;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val) || val > LM3533_RISEFALLTIME_MAX)
+		return -EINVAL;
+
+	reg = lm3533_led_get_pattern_reg(led, base);
+	ret = lm3533_write(led->lm3533, reg, val);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t store_risetime(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	return store_risefalltime(dev, attr, buf, len,
+					LM3533_REG_PATTERN_RISETIME_BASE);
+}
+
+static ssize_t store_falltime(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	return store_risefalltime(dev, attr, buf, len,
+					LM3533_REG_PATTERN_FALLTIME_BASE);
+}
+
+/*
+ * ALS settings:
+ *
+ *   0 - ALS disabled
+ *   2 - ALS mapper 2
+ *   3 - ALS mapper 3
+ */
+static ssize_t show_als(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	u8 reg;
+	u8 val;
+	int als;
+	int ret;
+
+	reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+	ret = lm3533_read(led->lm3533, reg, &val);
+	if (ret)
+		return ret;
+
+	als = val & LM3533_REG_CTRLBANK_BCONF_ALS_MASK;
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", als);
+}
+
+static ssize_t store_als(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	u8 als;
+	u8 reg;
+	u8 mask;
+	int ret;
+
+	if (kstrtou8(buf, 0, &als))
+		return -EINVAL;
+
+	if (als != 0 && (als < LM3533_ALS_LV_MIN || als > LM3533_ALS_LV_MAX))
+		return -EINVAL;
+
+	reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+	mask = LM3533_REG_CTRLBANK_BCONF_ALS_MASK;
+
+	ret = lm3533_update(led->lm3533, reg, als, mask);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t show_linear(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	u8 reg;
+	u8 val;
+	int linear;
+	int ret;
+
+	reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+	ret = lm3533_read(led->lm3533, reg, &val);
+	if (ret)
+		return ret;
+
+	if (val & LM3533_REG_CTRLBANK_BCONF_MAPPING_MASK)
+		linear = 1;
+	else
+		linear = 0;
+
+	return scnprintf(buf, PAGE_SIZE, "%x\n", linear);
+}
+
+static ssize_t store_linear(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	unsigned long linear;
+	u8 reg;
+	u8 mask;
+	u8 val;
+	int ret;
+
+	if (kstrtoul(buf, 0, &linear))
+		return -EINVAL;
+
+	reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+	mask = LM3533_REG_CTRLBANK_BCONF_MAPPING_MASK;
+
+	if (linear)
+		val = mask;
+	else
+		val = 0;
+
+	ret = lm3533_update(led->lm3533, reg, val, mask);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+#define show_ctrlbank_attr(_name)					\
+static ssize_t show_##_name(struct device *dev,				\
+				struct device_attribute *attr,		\
+				char *buf)				\
+{									\
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);		\
+	struct lm3533_led *led = to_lm3533_led(led_cdev);		\
+	u8 val;								\
+	int ret;							\
+									\
+	ret = lm3533_ctrlbank_get_##_name(&led->cb, &val);		\
+	if (ret)							\
+		return ret;						\
+									\
+	return scnprintf(buf, PAGE_SIZE, "%d\n", val);			\
+}
+
+#define store_ctrlbank_attr(_name)					\
+static ssize_t store_##_name(struct device *dev,			\
+				struct device_attribute *attr,		\
+				const char *buf, size_t len)		\
+{									\
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);		\
+	struct lm3533_led *led = to_lm3533_led(led_cdev);		\
+	u8 val;								\
+	int ret;							\
+									\
+	if (kstrtou8(buf, 0, &val))					\
+		return -EINVAL;						\
+									\
+	ret = lm3533_ctrlbank_set_##_name(&led->cb, val);		\
+	if (ret)							\
+		return ret;						\
+									\
+	return len;							\
+}
+
+show_ctrlbank_attr(max_current);
+store_ctrlbank_attr(max_current);
+show_ctrlbank_attr(pwm);
+store_ctrlbank_attr(pwm);
+
+static LM3533_ATTR_RW(als);
+static LM3533_ATTR_RW(falltime);
+static LM3533_ATTR_RO(id);
+static LM3533_ATTR_RW(linear);
+static LM3533_ATTR_RW(max_current);
+static LM3533_ATTR_RW(pwm);
+static LM3533_ATTR_RW(risetime);
+
+static struct attribute *lm3533_led_attributes[] = {
+	&dev_attr_als.attr,
+	&dev_attr_falltime.attr,
+	&dev_attr_id.attr,
+	&dev_attr_linear.attr,
+	&dev_attr_max_current.attr,
+	&dev_attr_pwm.attr,
+	&dev_attr_risetime.attr,
+	NULL,
+};
+
+static mode_t lm3533_led_attr_is_visible(struct kobject *kobj,
+					     struct attribute *attr, int n)
+{
+	struct device *dev = container_of(kobj, struct device, kobj);
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	mode_t mode = attr->mode;
+
+	if (attr = &dev_attr_als.attr) {
+		if (!led->lm3533->have_als)
+			mode = 0;
+	}
+
+	return mode;
+};
+
+static struct attribute_group lm3533_led_attribute_group = {
+	.is_visible	= lm3533_led_attr_is_visible,
+	.attrs		= lm3533_led_attributes
+};
+
+static int __devinit lm3533_led_setup(struct lm3533_led *led,
+					struct lm3533_led_platform_data *pdata)
+{
+	int ret;
+
+	ret = lm3533_ctrlbank_set_max_current(&led->cb, pdata->max_current);
+	if (ret)
+		return ret;
+
+	return lm3533_ctrlbank_set_pwm(&led->cb, pdata->pwm);
+}
+
+static int __devinit lm3533_led_probe(struct platform_device *pdev)
+{
+	struct lm3533 *lm3533;
+	struct lm3533_led_platform_data *pdata;
+	struct lm3533_led *led;
+	int ret;
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533 = dev_get_drvdata(pdev->dev.parent);
+	if (!lm3533)
+		return -EINVAL;
+
+	pdata = pdev->dev.platform_data;
+	if (!pdata) {
+		dev_err(&pdev->dev, "no platform data\n");
+		return -EINVAL;
+	}
+
+	if (pdev->id < 0 || pdev->id >= LM3533_LVCTRLBANK_COUNT) {
+		dev_err(&pdev->dev, "illegal LED id %d\n", pdev->id);
+		return -EINVAL;
+	}
+
+	led = kzalloc(sizeof(*led), GFP_KERNEL);
+	if (!led)
+		return -ENOMEM;
+
+	led->lm3533 = lm3533;
+	led->cdev.name = pdata->name;
+	led->cdev.default_trigger = pdata->default_trigger;
+	led->cdev.brightness_set = lm3533_led_set;
+	led->cdev.brightness_get = lm3533_led_get;
+	led->cdev.blink_set = lm3533_led_blink_set;
+	led->cdev.brightness = LED_OFF;
+	led->id = pdev->id;
+
+	mutex_init(&led->mutex);
+	INIT_WORK(&led->work, lm3533_led_work);
+
+	/* The class framework makes a callback to get brightness during
+	 * registration so use parent device (for error reporting) until
+	 * registered.
+	 */
+	led->cb.lm3533 = lm3533;
+	led->cb.id = lm3533_led_get_ctrlbank_id(led);
+	led->cb.dev = lm3533->dev;
+
+	platform_set_drvdata(pdev, led);
+
+	ret = led_classdev_register(pdev->dev.parent, &led->cdev);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to register LED %d\n", pdev->id);
+		goto err_free;
+	}
+
+	led->cb.dev = led->cdev.dev;
+
+	ret = sysfs_create_group(&led->cdev.dev->kobj,
+						&lm3533_led_attribute_group);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed to create sysfs attributes\n");
+		goto err_unregister;
+	}
+
+	ret = lm3533_led_setup(led, pdata);
+	if (ret)
+		goto err_sysfs_remove;
+
+	ret = lm3533_ctrlbank_enable(&led->cb);
+	if (ret)
+		goto err_sysfs_remove;
+
+	return 0;
+
+err_sysfs_remove:
+	sysfs_remove_group(&led->cdev.dev->kobj, &lm3533_led_attribute_group);
+err_unregister:
+	led_classdev_unregister(&led->cdev);
+	flush_work_sync(&led->work);
+err_free:
+	kfree(led);
+
+	return ret;
+}
+
+static int __devexit lm3533_led_remove(struct platform_device *pdev)
+{
+	struct lm3533_led *led = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533_ctrlbank_disable(&led->cb);
+	sysfs_remove_group(&led->cdev.dev->kobj, &lm3533_led_attribute_group);
+	led_classdev_unregister(&led->cdev);
+	flush_work_sync(&led->work);
+	kfree(led);
+
+	return 0;
+}
+
+static void lm3533_led_shutdown(struct platform_device *pdev)
+{
+
+	struct lm3533_led *led = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533_ctrlbank_disable(&led->cb);
+	lm3533_led_set(&led->cdev, LED_OFF);		/* disable blink */
+	flush_work_sync(&led->work);
+}
+
+static struct platform_driver lm3533_led_driver = {
+	.driver = {
+		.name = "lm3533-leds",
+		.owner = THIS_MODULE,
+	},
+	.probe		= lm3533_led_probe,
+	.remove		= __devexit_p(lm3533_led_remove),
+	.shutdown	= lm3533_led_shutdown,
+};
+module_platform_driver(lm3533_led_driver);
+
+MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
+MODULE_DESCRIPTION("LM3533 LED driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lm3533-leds");
-- 
1.7.8.5


^ permalink raw reply related

* [PATCH 2/4] misc: add LM3533 ambient light sensor driver
From: Johan Hovold @ 2012-04-20 15:30 UTC (permalink / raw)
  To: Richard Purdie, Samuel Ortiz, Arnd Bergmann, Greg Kroah-Hartman,
	Florian Tobias Schandinat
  Cc: Andrew Morton, linux-kernel, linux-fbdev, Johan Hovold
In-Reply-To: <1334935826-12527-1-git-send-email-jhovold@gmail.com>

Add sub-driver for the ambient light sensor in National Semiconductor /
TI LM3533 lighting power chips.

Raw ADC values as well as current ALS zone can be retrieved through
sysfs. The ALS zone can also be read using a character device
(/dev/lm3533-als) which is updated on zone changes (interrupt driven or
polled).

The driver provides a configuration interface through sysfs.

Signed-off-by: Johan Hovold <jhovold@gmail.com>
---
 drivers/misc/Kconfig      |   13 +
 drivers/misc/Makefile     |    1 +
 drivers/misc/lm3533-als.c |  662 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 676 insertions(+), 0 deletions(-)
 create mode 100644 drivers/misc/lm3533-als.c

diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index c779509..cc8cbf0 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -314,6 +314,19 @@ config APDS9802ALS
 	  This driver can also be built as a module.  If so, the module
 	  will be called apds9802als.
 
+config ALS_LM3533
+	tristate "LM3533 Ambient Light Sensor"
+	depends on MFD_LM3533
+	help
+	  If you say yes here you get support for the ambient light sensor on
+	  National Semiconductor / TI LM3533 Lighting Power chips.
+
+	  The sensor can be used to control the LEDs and backlights of the chip
+	  through defining five light zones and three sets of corresponding
+	  brightness target levels. The driver presents a character device
+	  (/dev/lm3533-als) which can be used to retrieve the current light
+	  zone or to poll for zone changes.
+
 config ISL29003
 	tristate "Intersil ISL29003 ambient light sensor"
 	depends on I2C && SYSFS
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 3e1d8010..122a168 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -27,6 +27,7 @@ obj-$(CONFIG_SGI_GRU)		+= sgi-gru/
 obj-$(CONFIG_CS5535_MFGPT)	+= cs5535-mfgpt.o
 obj-$(CONFIG_HP_ILO)		+= hpilo.o
 obj-$(CONFIG_APDS9802ALS)	+= apds9802als.o
+obj-$(CONFIG_ALS_LM3533)	+= lm3533-als.o
 obj-$(CONFIG_ISL29003)		+= isl29003.o
 obj-$(CONFIG_ISL29020)		+= isl29020.o
 obj-$(CONFIG_SENSORS_TSL2550)	+= tsl2550.o
diff --git a/drivers/misc/lm3533-als.c b/drivers/misc/lm3533-als.c
new file mode 100644
index 0000000..0348c6d
--- /dev/null
+++ b/drivers/misc/lm3533-als.c
@@ -0,0 +1,662 @@
+/*
+ * lm3533-als.c -- LM3533 Ambient Light Sensor driver
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/atomic.h>
+#include <linux/fs.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/mfd/core.h>
+#include <linux/platform_device.h>
+#include <linux/poll.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+/* Default poll intervall for zone changes in polled mode in ms. */
+#define LM3533_ALS_POLL_INTERVAL		1000
+
+#define LM3533_ALS_RESISTOR_MAX			0x7f
+#define LM3533_ALS_BOUNDARY_MAX			0xff
+#define LM3533_ALS_TARGET_MAX			0xff
+#define LM3533_ALS_ZONE_MAX			4
+
+#define LM3533_REG_ALS_RESISTOR_SELECT		0x30
+#define LM3533_REG_ALS_CONF			0x31
+#define LM3533_REG_ALS_ZONE_INFO		0x34
+#define LM3533_REG_ALS_READ_ADC_AVERAGE		0x37
+#define LM3533_REG_ALS_READ_ADC_RAW		0x38
+#define LM3533_REG_ALS_BOUNDARY0_HIGH		0x50
+#define LM3533_REG_ALS_BOUNDARY0_LOW		0x51
+#define LM3533_REG_ALS_BOUNDARY1_HIGH		0x52
+#define LM3533_REG_ALS_BOUNDARY1_LOW		0x53
+#define LM3533_REG_ALS_BOUNDARY2_HIGH		0x54
+#define LM3533_REG_ALS_BOUNDARY2_LOW		0x55
+#define LM3533_REG_ALS_BOUNDARY3_HIGH		0x56
+#define LM3533_REG_ALS_BOUNDARY3_LOW		0x57
+#define LM3533_REG_ALS_M1_TARGET_0		0x60
+#define LM3533_REG_ALS_M1_TARGET_1		0x61
+#define LM3533_REG_ALS_M1_TARGET_2		0x62
+#define LM3533_REG_ALS_M1_TARGET_3		0x63
+#define LM3533_REG_ALS_M1_TARGET_4		0x64
+#define LM3533_REG_ALS_M2_TARGET_0		0x65
+#define LM3533_REG_ALS_M2_TARGET_1		0x66
+#define LM3533_REG_ALS_M2_TARGET_2		0x67
+#define LM3533_REG_ALS_M2_TARGET_3		0x68
+#define LM3533_REG_ALS_M2_TARGET_4		0x69
+#define LM3533_REG_ALS_M3_TARGET_0		0x6a
+#define LM3533_REG_ALS_M3_TARGET_1		0x6b
+#define LM3533_REG_ALS_M3_TARGET_2		0x6c
+#define LM3533_REG_ALS_M3_TARGET_3		0x6d
+#define LM3533_REG_ALS_M3_TARGET_4		0x6e
+
+#define LM3533_ALS_ENABLE_MASK			0x01
+#define LM3533_ALS_INPUT_MODE_MASK		0x02
+#define LM3533_ALS_INT_ENABLE_MASK		0x01
+
+#define LM3533_ALS_ZONE_SHIFT			2
+#define LM3533_ALS_ZONE_MASK			0x1c
+
+#define LM3533_ALS_FLAG_ZONE_CHANGED		1
+
+
+struct lm3533_als {
+	struct lm3533 *lm3533;
+	struct miscdevice cdev;
+
+	int irq;
+
+	atomic_t open_ref;
+
+	unsigned long poll_interval;
+	struct delayed_work dwork;
+
+	wait_queue_head_t read_wait;
+
+	struct mutex mutex;
+	unsigned long flags;
+	u8 zone;
+};
+
+#define to_lm3533_als(_cdev) \
+	container_of(_cdev, struct lm3533_als, cdev)
+
+
+static int lm3533_als_get_zone(struct lm3533_als *als, u8 *zone)
+{
+	u8 val;
+	int ret;
+
+	ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val);
+	if (ret) {
+		dev_err(als->cdev.this_device, "failed to read zone\n");
+		return ret;
+	}
+
+	*zone = (val & LM3533_ALS_ZONE_MASK) >> LM3533_ALS_ZONE_SHIFT;
+	*zone = min_t(u8, *zone, LM3533_ALS_ZONE_MAX);
+
+	return 0;
+}
+
+static void lm3533_als_work(struct work_struct *work)
+{
+	struct delayed_work *dwork = to_delayed_work(work);
+	struct lm3533_als *als = container_of(dwork, struct lm3533_als, dwork);
+	u8 zone;
+	int ret;
+
+	ret = lm3533_als_get_zone(als, &zone);
+	if (ret)
+		goto out;
+
+	mutex_lock(&als->mutex);
+	if (als->zone != zone) {
+		als->zone = zone;
+		set_bit(LM3533_ALS_FLAG_ZONE_CHANGED, &als->flags);
+	}
+	mutex_unlock(&als->mutex);
+
+	if (test_bit(LM3533_ALS_FLAG_ZONE_CHANGED, &als->flags))
+		wake_up_interruptible(&als->read_wait);
+out:
+	if (als->irq < 0)
+		schedule_delayed_work(dwork, als->poll_interval);
+}
+
+static irqreturn_t lm3533_als_isr(int irq, void *dev_id)
+{
+	struct lm3533_als *als = dev_id;
+	u8 zone;
+	int ret;
+
+	ret = lm3533_als_get_zone(als, &zone);
+	if (ret)
+		goto out;
+
+	mutex_lock(&als->mutex);
+	als->zone = zone;
+	set_bit(LM3533_ALS_FLAG_ZONE_CHANGED, &als->flags);
+	mutex_unlock(&als->mutex);
+
+	wake_up_interruptible(&als->read_wait);
+out:
+	return IRQ_HANDLED;
+}
+
+static int lm3533_als_set_int_mode(struct lm3533_als *als, int enable)
+{
+	u8 mask = LM3533_ALS_INT_ENABLE_MASK;
+	u8 val;
+
+	if (enable)
+		val = mask;
+	else
+		val = 0;
+
+	return lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, val, mask);
+}
+
+static int lm3533_als_int_enable(struct lm3533_als *als)
+{
+	int ret;
+
+	dev_dbg(als->cdev.this_device, "%s\n", __func__);
+
+	ret = lm3533_als_set_int_mode(als, 1);
+	if (ret) {
+		dev_warn(als->cdev.this_device, "could not enable int mode\n");
+		goto err;
+	}
+
+	ret = request_threaded_irq(als->irq, NULL, lm3533_als_isr,
+					IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+					als->cdev.name, als);
+	if (ret) {
+		dev_warn(als->cdev.this_device, "could not get irq %d\n",
+								als->irq);
+		goto err;
+	}
+
+	return ret;
+err:
+	als->irq = -EINVAL;
+
+	return ret;
+}
+
+static int lm3533_als_int_disable(struct lm3533_als *als)
+{
+	int ret;
+
+	dev_dbg(als->cdev.this_device, "%s\n", __func__);
+
+	free_irq(als->irq, als);
+
+	ret = lm3533_als_set_int_mode(als, 0);
+	if (ret) {
+		dev_warn(als->cdev.this_device,
+					"could not disable int mode\n");
+	}
+
+	return ret;
+}
+
+static int lm3533_als_open(struct inode *inode, struct file *file)
+{
+	struct lm3533_als *als = to_lm3533_als(file->private_data);
+
+	dev_dbg(als->cdev.this_device, "%s\n", __func__);
+
+	file->private_data = als;
+
+	if (atomic_inc_return(&als->open_ref) != 1)
+		goto out;
+
+	/* Enable interrupt mode if requested, but fall back to polled mode on
+	 * errors.
+	 */
+	if (als->irq >= 0)
+		lm3533_als_int_enable(als);
+
+	/* Make sure first read returns current zone. */
+	mutex_lock(&als->mutex);
+	clear_bit(LM3533_ALS_FLAG_ZONE_CHANGED, &als->flags);
+	als->zone = (u8)-1;
+	mutex_unlock(&als->mutex);
+
+	schedule_delayed_work(&als->dwork, 0);
+out:
+	return nonseekable_open(inode, file);
+}
+
+static int lm3533_als_release(struct inode *inode, struct file *file)
+{
+	struct lm3533_als *als = file->private_data;
+
+	dev_dbg(als->cdev.this_device, "%s\n", __func__);
+
+	if (atomic_dec_return(&als->open_ref))
+		return 0;
+
+	if (als->irq >= 0)
+		lm3533_als_int_disable(als);
+
+	cancel_delayed_work_sync(&als->dwork);
+
+	return 0;
+}
+
+static unsigned int lm3533_als_poll(struct file *file,
+						struct poll_table_struct *pt)
+{
+	struct lm3533_als *als = file->private_data;
+	unsigned mask = 0;
+
+	poll_wait(file, &als->read_wait, pt);
+
+	if (test_bit(LM3533_ALS_FLAG_ZONE_CHANGED, &als->flags))
+		mask |= POLLIN | POLLRDNORM;
+
+	return mask;
+}
+
+static ssize_t lm3533_als_read(struct file *file, char __user *buf,
+						size_t count, loff_t *f_pos)
+{
+	struct lm3533_als *als = file->private_data;
+	int ret;
+
+	dev_dbg(als->cdev.this_device, "%s\n", __func__);
+
+	if (!count)
+		return 0;
+
+	mutex_lock(&als->mutex);
+	while (!test_bit(LM3533_ALS_FLAG_ZONE_CHANGED, &als->flags)) {
+		mutex_unlock(&als->mutex);
+
+		if (file->f_flags & O_NONBLOCK)
+			return -EAGAIN;
+
+		ret = wait_event_interruptible(als->read_wait,
+					test_bit(LM3533_ALS_FLAG_ZONE_CHANGED,
+								&als->flags));
+		if (ret)
+			return -ERESTARTSYS;
+
+		mutex_lock(&als->mutex);
+	}
+
+	count = min(count, sizeof(als->zone));
+	if (copy_to_user(buf, &als->zone, count)) {
+		ret = -EFAULT;
+		goto out;
+	}
+	*f_pos += count;
+	ret = count;
+
+	clear_bit(LM3533_ALS_FLAG_ZONE_CHANGED, &als->flags);
+out:
+	mutex_unlock(&als->mutex);
+
+	return ret;
+}
+
+static const struct file_operations lm3533_als_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.open		= lm3533_als_open,
+	.release	= lm3533_als_release,
+	.poll		= lm3533_als_poll,
+	.read		= lm3533_als_read,
+};
+
+struct lm3533_device_attribute {
+	struct device_attribute dev_attr;
+	u8 reg;
+	u8 max;
+};
+
+#define to_lm3533_dev_attr(_dev_attr) \
+	container_of(_dev_attr, struct lm3533_device_attribute, dev_attr)
+
+static ssize_t show_zone(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct miscdevice *cdev = dev_get_drvdata(dev);
+	struct lm3533_als *als = to_lm3533_als(cdev);
+	u8 zone;
+	int ret;
+
+	ret = lm3533_als_get_zone(als, &zone);
+	if (ret)
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", zone);
+}
+
+static ssize_t show_lm3533_als_reg(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct miscdevice *cdev = dev_get_drvdata(dev);
+	struct lm3533_als *als = to_lm3533_als(cdev);
+	struct lm3533_device_attribute *lm3533_attr = to_lm3533_dev_attr(attr);
+	u8 val;
+	int ret;
+
+	ret = lm3533_read(als->lm3533, lm3533_attr->reg, &val);
+	if (ret)
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_lm3533_als_reg(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct miscdevice *cdev = dev_get_drvdata(dev);
+	struct lm3533_als *als = to_lm3533_als(cdev);
+	struct lm3533_device_attribute *lm3533_attr = to_lm3533_dev_attr(attr);
+	u8 val;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val) || val > lm3533_attr->max)
+		return -EINVAL;
+
+	ret = lm3533_write(als->lm3533, lm3533_attr->reg, val);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+#define REG_ATTR(_name, _mode, _show, _store, _reg, _max) \
+	{ .dev_attr = __ATTR(_name, _mode, _show, _store), \
+	  .reg = _reg, \
+	  .max = _max }
+
+#define LM3533_REG_ATTR(_name, _mode, _show, _store, _reg, _max) \
+	struct lm3533_device_attribute lm3533_dev_attr_##_name \
+		= REG_ATTR(_name, _mode, _show, _store, _reg, _max)
+
+#define LM3533_REG_ATTR_RO(_name, _reg) \
+	LM3533_REG_ATTR(_name, S_IRUGO, show_lm3533_als_reg, NULL, _reg, 0)
+#define LM3533_REG_ATTR_RW(_name, _reg, _max) \
+	LM3533_REG_ATTR(_name, S_IRUGO | S_IWUSR, show_lm3533_als_reg, \
+					store_lm3533_als_reg, _reg, _max)
+
+#define ALS_BOUNDARY_LOW_ATTR_RW(_nr) \
+	LM3533_REG_ATTR_RW(boundary##_nr##_low, \
+		LM3533_REG_ALS_BOUNDARY##_nr##_LOW, LM3533_ALS_BOUNDARY_MAX)
+
+#define ALS_BOUNDARY_HIGH_ATTR_RW(_nr) \
+	LM3533_REG_ATTR_RW(boundary##_nr##_high, \
+		LM3533_REG_ALS_BOUNDARY##_nr##_HIGH, LM3533_ALS_BOUNDARY_MAX)
+
+/* ALS Zone boundaries
+ *
+ * boundary[0-3]_low	0-255
+ * boundary[0-3]_high	0-255
+ */
+static ALS_BOUNDARY_LOW_ATTR_RW(0);
+static ALS_BOUNDARY_LOW_ATTR_RW(1);
+static ALS_BOUNDARY_LOW_ATTR_RW(2);
+static ALS_BOUNDARY_LOW_ATTR_RW(3);
+
+static ALS_BOUNDARY_HIGH_ATTR_RW(0);
+static ALS_BOUNDARY_HIGH_ATTR_RW(1);
+static ALS_BOUNDARY_HIGH_ATTR_RW(2);
+static ALS_BOUNDARY_HIGH_ATTR_RW(3);
+
+#define ALS_TARGET_ATTR_RW(_mapper, _nr) \
+	LM3533_REG_ATTR_RW(target##_mapper##_##_nr, \
+		LM3533_REG_ALS_M##_mapper##_TARGET_##_nr, LM3533_ALS_TARGET_MAX)
+
+/* ALS Mapper targets
+ *
+ * target[1-3]_[0-4]		0-255
+ */
+static ALS_TARGET_ATTR_RW(1, 0);
+static ALS_TARGET_ATTR_RW(1, 1);
+static ALS_TARGET_ATTR_RW(1, 2);
+static ALS_TARGET_ATTR_RW(1, 3);
+static ALS_TARGET_ATTR_RW(1, 4);
+
+static ALS_TARGET_ATTR_RW(2, 0);
+static ALS_TARGET_ATTR_RW(2, 1);
+static ALS_TARGET_ATTR_RW(2, 2);
+static ALS_TARGET_ATTR_RW(2, 3);
+static ALS_TARGET_ATTR_RW(2, 4);
+
+static ALS_TARGET_ATTR_RW(3, 0);
+static ALS_TARGET_ATTR_RW(3, 1);
+static ALS_TARGET_ATTR_RW(3, 2);
+static ALS_TARGET_ATTR_RW(3, 3);
+static ALS_TARGET_ATTR_RW(3, 4);
+
+/* ALS ADC
+ *
+ * adc_average	0-255
+ * adc_raw	0-255
+ */
+static LM3533_REG_ATTR_RO(adc_average, LM3533_REG_ALS_READ_ADC_AVERAGE);
+static LM3533_REG_ATTR_RO(adc_raw, LM3533_REG_ALS_READ_ADC_RAW);
+
+/* ALS Gain resistor setting
+ *
+ * gain		0-31
+ */
+static LM3533_REG_ATTR_RW(gain, LM3533_REG_ALS_RESISTOR_SELECT,
+						LM3533_ALS_RESISTOR_MAX);
+/* ALS Current Zone
+ *
+ * zone		0-4
+ */
+static LM3533_ATTR_RO(zone);
+
+static struct attribute *lm3533_als_attributes[] = {
+	&lm3533_dev_attr_adc_average.dev_attr.attr,
+	&lm3533_dev_attr_adc_raw.dev_attr.attr,
+	&lm3533_dev_attr_boundary0_high.dev_attr.attr,
+	&lm3533_dev_attr_boundary0_low.dev_attr.attr,
+	&lm3533_dev_attr_boundary1_high.dev_attr.attr,
+	&lm3533_dev_attr_boundary1_low.dev_attr.attr,
+	&lm3533_dev_attr_boundary2_high.dev_attr.attr,
+	&lm3533_dev_attr_boundary2_low.dev_attr.attr,
+	&lm3533_dev_attr_boundary3_high.dev_attr.attr,
+	&lm3533_dev_attr_boundary3_low.dev_attr.attr,
+	&lm3533_dev_attr_target1_0.dev_attr.attr,
+	&lm3533_dev_attr_target1_1.dev_attr.attr,
+	&lm3533_dev_attr_target1_2.dev_attr.attr,
+	&lm3533_dev_attr_target1_3.dev_attr.attr,
+	&lm3533_dev_attr_target1_4.dev_attr.attr,
+	&lm3533_dev_attr_target2_0.dev_attr.attr,
+	&lm3533_dev_attr_target2_1.dev_attr.attr,
+	&lm3533_dev_attr_target2_2.dev_attr.attr,
+	&lm3533_dev_attr_target2_3.dev_attr.attr,
+	&lm3533_dev_attr_target2_4.dev_attr.attr,
+	&lm3533_dev_attr_target3_0.dev_attr.attr,
+	&lm3533_dev_attr_target3_1.dev_attr.attr,
+	&lm3533_dev_attr_target3_2.dev_attr.attr,
+	&lm3533_dev_attr_target3_3.dev_attr.attr,
+	&lm3533_dev_attr_target3_4.dev_attr.attr,
+	&lm3533_dev_attr_gain.dev_attr.attr,
+	&dev_attr_zone.attr,
+	NULL,
+};
+
+static struct attribute_group lm3533_als_attribute_group = {
+	.attrs = lm3533_als_attributes
+};
+
+static int __devinit lm3533_als_set_input_mode(struct lm3533 *lm3533,
+								int pwm_mode)
+{
+	u8 mask = LM3533_ALS_INPUT_MODE_MASK;
+	u8 val;
+	int ret;
+
+	if (pwm_mode)
+		val = mask;	/* pwm input */
+	else
+		val = 0;	/* analog input */
+
+	ret = lm3533_update(lm3533, LM3533_REG_ALS_CONF, mask, mask);
+	if (ret) {
+		dev_err(lm3533->dev,
+				"failed to set input mode %d\n", pwm_mode);
+	}
+
+	return ret;
+}
+
+static int __devinit lm3533_als_enable(struct lm3533 *lm3533)
+{
+	u8 mask = LM3533_ALS_ENABLE_MASK;
+	int ret;
+
+	ret = lm3533_update(lm3533, LM3533_REG_ALS_CONF, mask, mask);
+	if (ret)
+		dev_err(lm3533->dev, "failed to enable ALS\n");
+
+	return ret;
+}
+
+static int lm3533_als_disable(struct lm3533 *lm3533)
+{
+	u8 mask = LM3533_ALS_ENABLE_MASK;
+	int ret;
+
+	ret = lm3533_update(lm3533, LM3533_REG_ALS_CONF, 0, mask);
+	if (ret)
+		dev_err(lm3533->dev, "failed to disable ALS\n");
+
+	return ret;
+}
+
+static int __devinit lm3533_als_probe(struct platform_device *pdev)
+{
+	struct lm3533 *lm3533;
+	struct lm3533_als_platform_data *pdata;
+	struct lm3533_als *als;
+	int ret;
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533 = dev_get_drvdata(pdev->dev.parent);
+	if (!lm3533)
+		return -EINVAL;
+
+	pdata = pdev->dev.platform_data;
+	if (!pdata) {
+		dev_err(&pdev->dev, "no platform data\n");
+		return -EINVAL;
+	}
+
+	als = kzalloc(sizeof(*als), GFP_KERNEL);
+	if (!als)
+		return -ENOMEM;
+
+	als->lm3533 = lm3533;
+	if (pdata->int_mode)
+		als->irq = lm3533->irq;
+	else
+		als->irq = -EINVAL;
+
+	if (!pdata->poll_interval)
+		pdata->poll_interval = LM3533_ALS_POLL_INTERVAL;
+	als->poll_interval = msecs_to_jiffies(pdata->poll_interval);
+
+	als->cdev.name = "lm3533-als";
+	als->cdev.parent = pdev->dev.parent;
+	als->cdev.minor = MISC_DYNAMIC_MINOR;
+	als->cdev.fops = &lm3533_als_fops;
+
+	mutex_init(&als->mutex);
+	INIT_DELAYED_WORK(&als->dwork, lm3533_als_work);
+	init_waitqueue_head(&als->read_wait);
+
+	platform_set_drvdata(pdev, als);
+
+	ret = lm3533_als_set_input_mode(lm3533, pdata->pwm_mode);
+	if (ret)
+		goto err;
+
+	ret = lm3533_als_enable(lm3533);
+	if (ret)
+		goto err;
+
+	ret = misc_register(&als->cdev);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to register ALS\n");
+		goto err_disable;
+	}
+
+	ret = sysfs_create_group(&als->cdev.this_device->kobj,
+						&lm3533_als_attribute_group);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed to create sysfs attributes\n");
+		goto err_unregister;
+	}
+
+	return 0;
+
+err_unregister:
+	misc_deregister(&als->cdev);
+err_disable:
+	lm3533_als_disable(lm3533);
+err:
+	kfree(als);
+
+	return ret;
+}
+
+static int __devexit lm3533_als_remove(struct platform_device *pdev)
+{
+	struct lm3533_als *als = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	sysfs_remove_group(&als->cdev.this_device->kobj,
+						&lm3533_als_attribute_group);
+	misc_deregister(&als->cdev);
+	lm3533_als_disable(als->lm3533);
+	kfree(als);
+
+	return 0;
+}
+
+static struct platform_driver lm3533_als_driver = {
+	.driver = {
+		.name = "lm3533-als",
+		.owner = THIS_MODULE,
+	},
+	.probe		= lm3533_als_probe,
+	.remove		= __devexit_p(lm3533_als_remove),
+};
+module_platform_driver(lm3533_als_driver);
+
+MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
+MODULE_DESCRIPTION("LM3533 Ambient Light Sensor driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lm3533-als");
-- 
1.7.8.5


^ permalink raw reply related

* [PATCH 1/4] mfd: add LM3533 lighting-power core driver
From: Johan Hovold @ 2012-04-20 15:30 UTC (permalink / raw)
  To: Richard Purdie, Samuel Ortiz, Arnd Bergmann, Greg Kroah-Hartman,
	Florian Tobias Schandinat
  Cc: Andrew Morton, linux-kernel, linux-fbdev, Johan Hovold
In-Reply-To: <1334935826-12527-1-git-send-email-jhovold@gmail.com>

Add support for National Semiconductor / TI LM3533 lighting power chips.

This is the core driver which provides register access over I2C and
registers the ambient light sensor, LED and backlight sub-drivers.

Signed-off-by: Johan Hovold <jhovold@gmail.com>
---
 drivers/mfd/Kconfig           |   12 +
 drivers/mfd/Makefile          |    3 +
 drivers/mfd/lm3533-core.c     |  738 +++++++++++++++++++++++++++++++++++++++++
 drivers/mfd/lm3533-ctrlbank.c |  134 ++++++++
 drivers/mfd/lm3533-i2c.c      |  115 +++++++
 include/linux/mfd/lm3533.h    |  106 ++++++
 6 files changed, 1108 insertions(+), 0 deletions(-)
 create mode 100644 drivers/mfd/lm3533-core.c
 create mode 100644 drivers/mfd/lm3533-ctrlbank.c
 create mode 100644 drivers/mfd/lm3533-i2c.c
 create mode 100644 include/linux/mfd/lm3533.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 29f463c..e2760d9 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -106,6 +106,18 @@ config UCB1400_CORE
 	  To compile this driver as a module, choose M here: the
 	  module will be called ucb1400_core.
 
+config MFD_LM3533
+	tristate "LM3533 Lighting Power chip"
+	depends on I2C
+	select MFD_CORE
+	help
+	  Say yes here to enable support for National Semiconductor / TI
+	  LM3533 Lighting Power chips.
+
+	  This driver provides common support for accessing the device;
+	  additional drivers must be enabled in order to use the LED,
+	  backlight or ambient-light-sensor functionality of the device.
+
 config TPS6105X
 	tristate "TPS61050/61052 Boost Converters"
 	depends on I2C
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 05fa538..563fffa 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -15,6 +15,9 @@ obj-$(CONFIG_MFD_DAVINCI_VOICECODEC)	+= davinci_voicecodec.o
 obj-$(CONFIG_MFD_DM355EVM_MSP)	+= dm355evm_msp.o
 obj-$(CONFIG_MFD_TI_SSP)	+= ti-ssp.o
 
+lm3533-objs			:= lm3533-core.o lm3533-i2c.o
+obj-$(CONFIG_MFD_LM3533)	+= lm3533.o lm3533-ctrlbank.o
+
 obj-$(CONFIG_MFD_STMPE)		+= stmpe.o
 obj-$(CONFIG_STMPE_I2C)		+= stmpe-i2c.o
 obj-$(CONFIG_STMPE_SPI)		+= stmpe-spi.o
diff --git a/drivers/mfd/lm3533-core.c b/drivers/mfd/lm3533-core.c
new file mode 100644
index 0000000..7a2d174
--- /dev/null
+++ b/drivers/mfd/lm3533-core.c
@@ -0,0 +1,738 @@
+/*
+ * lm3533-core.c -- LM3533 Core
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/debugfs.h>
+#include <linux/gpio.h>
+#include <linux/mfd/core.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+#define LM3533_BOOST_OVP_MAX		0x03
+#define LM3533_BOOST_OVP_MASK		0x06
+#define LM3533_BOOST_OVP_SHIFT		1
+
+#define LM3533_BOOST_FREQ_MAX		0x01
+#define LM3533_BOOST_FREQ_MASK		0x01
+#define LM3533_BOOST_FREQ_SHIFT		0
+
+#define LM3533_BL_ID_MASK		1
+#define LM3533_LED_ID_MASK		3
+#define LM3533_BL_ID_MAX		1
+#define LM3533_LED_ID_MAX		3
+
+#define LM3533_HVLED_ID_MAX		2
+#define LM3533_LVLED_ID_MAX		5
+
+#define LM3533_REG_OUTPUT_CONF1		0x10
+#define LM3533_REG_OUTPUT_CONF2		0x11
+#define LM3533_REG_BOOST_PWM		0x2c
+
+
+static struct mfd_cell lm3533_als_devs[] = {
+	{
+		.name	= "lm3533-als",
+		.id	= -1,
+	},
+};
+
+static struct mfd_cell lm3533_bl_devs[] = {
+	{
+		.name	= "lm3533-backlight",
+		.id	= 0,
+	},
+	{
+		.name	= "lm3533-backlight",
+		.id	= 1,
+	},
+};
+
+static struct mfd_cell lm3533_led_devs[] = {
+	{
+		.name	= "lm3533-leds",
+		.id	= 0,
+	},
+	{
+		.name	= "lm3533-leds",
+		.id	= 1,
+	},
+	{
+		.name	= "lm3533-leds",
+		.id	= 2,
+	},
+	{
+		.name	= "lm3533-leds",
+		.id	= 3,
+	},
+};
+
+static int __lm3533_read(struct lm3533 *lm3533, u8 reg, u8 *val)
+{
+	int ret;
+
+	ret = lm3533->read(lm3533, reg, val);
+	if (ret < 0) {
+		dev_err(lm3533->dev, "failed to read register %02x: %d\n",
+								reg, ret);
+		return ret;
+	}
+
+	dev_dbg(lm3533->dev, "read [%02x]: %02x\n", reg, *val);
+
+	return 0;
+}
+
+static int __lm3533_write(struct lm3533 *lm3533, u8 reg, u8 val)
+{
+	int ret;
+
+	dev_dbg(lm3533->dev, "write [%02x]: %02x\n", reg, val);
+
+	ret = lm3533->write(lm3533, reg, val);
+	if (ret < 0) {
+		dev_err(lm3533->dev, "failed to write register %02x: %d\n",
+								reg, ret);
+	}
+
+	return ret;
+}
+
+int lm3533_read(struct lm3533 *lm3533, u8 reg, u8 *val)
+{
+	int ret;
+
+	mutex_lock(&lm3533->io_mutex);
+	ret = __lm3533_read(lm3533, reg, val);
+	mutex_unlock(&lm3533->io_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(lm3533_read);
+
+int lm3533_write(struct lm3533 *lm3533, u8 reg, u8 val)
+{
+	int ret;
+
+	mutex_lock(&lm3533->io_mutex);
+	ret = __lm3533_write(lm3533, reg, val);
+	mutex_unlock(&lm3533->io_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(lm3533_write);
+
+int lm3533_update(struct lm3533 *lm3533, u8 reg, u8 val, u8 mask)
+{
+	u8 old_val;
+	u8 new_val;
+	int ret;
+
+	mutex_lock(&lm3533->io_mutex);
+	ret = __lm3533_read(lm3533, reg, &old_val);
+	if (ret)
+		goto out;
+	new_val = (old_val & ~mask) | (val & mask);
+	if (new_val != old_val)
+		ret = __lm3533_write(lm3533, reg, new_val);
+out:
+	mutex_unlock(&lm3533->io_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(lm3533_update);
+
+/*
+ * HVLED output config -- output hvled controlled by backlight bl
+ */
+static int lm3533_set_hvled_config(struct lm3533 *lm3533, u8 hvled, u8 bl)
+{
+	u8 val;
+	u8 mask;
+	int shift;
+	int ret;
+
+	if (hvled = 0 || hvled > LM3533_HVLED_ID_MAX)
+		return -EINVAL;
+
+	if (bl > LM3533_BL_ID_MAX)
+		return -EINVAL;
+
+	shift = hvled - 1;
+	mask = LM3533_BL_ID_MASK << shift;
+	val = bl << shift;
+
+	ret = lm3533_update(lm3533, LM3533_REG_OUTPUT_CONF1, val, mask);
+	if (ret)
+		dev_err(lm3533->dev, "failed to set hvled config\n");
+
+	return ret;
+}
+
+/*
+ * LVLED output config -- output lvled controlled by LED led
+ */
+static int lm3533_set_lvled_config(struct lm3533 *lm3533, u8 lvled, u8 led)
+{
+	u8 reg;
+	u8 val;
+	u8 mask;
+	int shift;
+	int ret;
+
+	if (lvled = 0 || lvled > LM3533_LVLED_ID_MAX)
+		return -EINVAL;
+
+	if (led > LM3533_LED_ID_MAX)
+		return -EINVAL;
+
+	if (lvled < 4) {
+		reg = LM3533_REG_OUTPUT_CONF1;
+		shift = 2 * lvled;
+	} else {
+		reg = LM3533_REG_OUTPUT_CONF2;
+		shift = 2 * (lvled - 4);
+	}
+
+	mask = LM3533_LED_ID_MASK << shift;
+	val = led << shift;
+
+	ret = lm3533_update(lm3533, reg, val, mask);
+	if (ret)
+		dev_err(lm3533->dev, "failed to set lvled config\n");
+
+	return ret;
+}
+
+static void lm3533_enable(struct lm3533 *lm3533)
+{
+	if (gpio_is_valid(lm3533->gpio_hwen))
+		gpio_set_value(lm3533->gpio_hwen, 1);
+}
+
+static void lm3533_disable(struct lm3533 *lm3533)
+{
+	if (gpio_is_valid(lm3533->gpio_hwen))
+		gpio_set_value(lm3533->gpio_hwen, 0);
+}
+
+#ifdef CONFIG_DEBUG_FS
+
+const struct {
+	u8 min;
+	u8 max;
+} lm3533_regs[] = {
+	{ 0x10, 0x2c },
+	{ 0x30, 0x38 },
+	{ 0x40, 0x45 },
+	{ 0x50, 0x57 },
+	{ 0x60, 0x6e },
+	{ 0x70, 0x75 },
+	{ 0x80, 0x85 },
+	{ 0x90, 0x95 },
+	{ 0xa0, 0xa5 },
+	{ 0xb0, 0xb2 },
+};
+
+static int lm3533_regs_show(struct seq_file *s, void *__unused)
+{
+	struct lm3533 *lm3533 = s->private;
+	u8 reg;
+	u8 val;
+	int ret;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(lm3533_regs); ++i) {
+		for (reg = lm3533_regs[i].min;
+					reg <= lm3533_regs[i].max; ++reg) {
+			ret = lm3533_read(lm3533, reg, &val);
+			if (ret)
+				return ret;
+
+			seq_printf(s, "[%02x]: %02x\n", reg, val);
+		}
+	}
+
+	return 0;
+}
+
+static int lm3533_regs_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, lm3533_regs_show, inode->i_private);
+}
+
+static int lm3533_set_reg_open(struct inode *inode, struct file *file)
+{
+	file->private_data = inode->i_private;
+
+	return 0;
+}
+
+static ssize_t lm3533_set_reg_write(struct file *file,
+					const char __user *user_buf,
+					size_t count, loff_t *ppos)
+{
+	struct lm3533 *lm3533 = file->private_data;
+	char buf[32];
+	int len;
+	unsigned reg;
+	unsigned val;
+	int ret;
+
+	len = min(count, sizeof(buf) - 1);
+	if (copy_from_user(buf, user_buf, len))
+		return -EFAULT;
+	buf[len] = 0;
+
+	if (sscanf(buf, "%x %x", &reg, &val) != 2)
+		return -EINVAL;
+
+	if (reg > 0xff || val > 0xff)
+		return -EINVAL;
+
+	ret = lm3533_write(lm3533, (u8)reg, (u8)val);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static const struct file_operations lm3533_regs_fops = {
+	.open		= lm3533_regs_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+static const struct file_operations lm3533_set_reg_fops = {
+	.open		= lm3533_set_reg_open,
+	.write		= lm3533_set_reg_write,
+};
+
+static void __devinit lm3533_debugfs_init(struct lm3533 *lm3533)
+{
+	struct dentry *d;
+
+	lm3533->debugfs_root = debugfs_create_dir("lm3533", NULL);
+	if (!lm3533->debugfs_root) {
+		dev_err(lm3533->dev, "failed to create debugfs root\n");
+		return;
+	}
+
+	d = debugfs_create_file("regs", 0444, lm3533->debugfs_root, lm3533,
+							&lm3533_regs_fops);
+	if (!d)
+		dev_err(lm3533->dev, "failed to create debugfs regs file\n");
+
+	d = debugfs_create_file("set_reg", 0644, lm3533->debugfs_root, lm3533,
+							&lm3533_set_reg_fops);
+	if (!d) {
+		dev_err(lm3533->dev,
+				"failed to create debugfs set_reg file\n");
+	}
+}
+
+static void __devexit lm3533_debugfs_cleanup(struct lm3533 *lm3533)
+{
+	debugfs_remove_recursive(lm3533->debugfs_root);
+}
+
+#else	/* CONFIG_DEBUG_FS */
+static void lm3533_debugfs_init(struct lm3533 *lm3533) { }
+static void lm3533_debugfs_cleanup(struct lm3533 *lm3533) { }
+#endif	/* CONFIG_DEBUG_FS */
+
+enum lm3533_attribute_type {
+	LM3533_ATTR_TYPE_BACKLIGHT,
+	LM3533_ATTR_TYPE_LED,
+};
+
+struct lm3533_device_attribute {
+	struct device_attribute dev_attr;
+	enum lm3533_attribute_type type;
+	union {
+		struct {
+			u8 id;
+		} output;
+		struct {
+			u8 reg;
+			u8 shift;
+			u8 mask;
+			u8 max;
+		} generic;
+	} u;
+};
+
+#define to_lm3533_dev_attr(_attr) \
+	container_of(_attr, struct lm3533_device_attribute, dev_attr)
+
+static ssize_t show_lm3533_reg(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct lm3533 *lm3533 = dev_get_drvdata(dev);
+	struct lm3533_device_attribute *lattr = to_lm3533_dev_attr(attr);
+	u8 val;
+	int ret;
+
+	ret = lm3533_read(lm3533, lattr->u.generic.reg, &val);
+	if (ret)
+		return ret;
+
+	val = (val & lattr->u.generic.mask) >> lattr->u.generic.shift;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_lm3533_reg(struct device *dev,
+						struct device_attribute *attr,
+						const char *buf, size_t len)
+{
+	struct lm3533 *lm3533 = dev_get_drvdata(dev);
+	struct lm3533_device_attribute *lattr = to_lm3533_dev_attr(attr);
+	u8 val;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val) || val > lattr->u.generic.max)
+		return -EINVAL;
+
+	val = val << lattr->u.generic.shift;
+	ret = lm3533_update(lm3533, lattr->u.generic.reg, val,
+							lattr->u.generic.mask);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+#define GENERIC_ATTR(_reg, _max, _mask, _shift) \
+	{ .reg		= _reg, \
+	  .max		= _max, \
+	  .mask		= _mask, \
+	  .shift	= _shift }
+
+#define LM3533_GENERIC_ATTR(_name, _mode, _show, _store, _type,	\
+						_reg, _max, _mask, _shift) \
+	struct lm3533_device_attribute lm3533_dev_attr_##_name = { \
+		.dev_attr	= __ATTR(_name, _mode, _show, _store), \
+		.type		= _type, \
+		.u.generic	= GENERIC_ATTR(_reg, _max, _mask, _shift) }
+
+#define LM3533_GENERIC_ATTR_RW(_name, _type, _reg, _max, _mask, _shift) \
+	LM3533_GENERIC_ATTR(_name, S_IRUGO | S_IWUSR, \
+					show_lm3533_reg, store_lm3533_reg, \
+					_type, _reg, _max, _mask, _shift)
+
+#define LM3533_BOOST_ATTR_RW(_name, _NAME) \
+	LM3533_GENERIC_ATTR_RW(_name, LM3533_ATTR_TYPE_BACKLIGHT, \
+				LM3533_REG_BOOST_PWM, LM3533_##_NAME##_MAX, \
+				LM3533_##_NAME##_MASK, LM3533_##_NAME##_SHIFT)
+/*
+ * Boost Over Voltage Protection Select
+ *
+ *   0 -- 16 V (default)
+ *   1 -- 24 V
+ *   2 -- 32 V
+ *   3 -- 40 V
+ */
+static LM3533_BOOST_ATTR_RW(boost_ovp, BOOST_OVP);
+
+/*
+ * Boost Frequency Select
+ *
+ *   0 -- 500 kHz (default)
+ *   1 -- 1 MHz
+ */
+static LM3533_BOOST_ATTR_RW(boost_freq, BOOST_FREQ);
+
+static ssize_t show_output(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct lm3533 *lm3533 = dev_get_drvdata(dev);
+	struct lm3533_device_attribute *lattr = to_lm3533_dev_attr(attr);
+	int id = lattr->u.output.id;
+	u8 reg;
+	u8 val;
+	u8 mask;
+	int shift;
+	int ret;
+
+	if (lattr->type = LM3533_ATTR_TYPE_BACKLIGHT) {
+		reg = LM3533_REG_OUTPUT_CONF1;
+		shift = id - 1;
+		mask = LM3533_BL_ID_MASK << shift;
+	} else {
+		if (id < 4) {
+			reg = LM3533_REG_OUTPUT_CONF1;
+			shift = 2 * id;
+		} else {
+			reg = LM3533_REG_OUTPUT_CONF2;
+			shift = 2 * (id - 4);
+		}
+		mask = LM3533_LED_ID_MASK << shift;
+	}
+
+	ret = lm3533_read(lm3533, reg, &val);
+	if (ret)
+		return ret;
+
+	val = (val & mask) >> shift;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_output(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct lm3533 *lm3533 = dev_get_drvdata(dev);
+	struct lm3533_device_attribute *lattr = to_lm3533_dev_attr(attr);
+	int id = lattr->u.output.id;
+	u8 val;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val))
+		return -EINVAL;
+
+	if (lattr->type = LM3533_ATTR_TYPE_BACKLIGHT)
+		ret = lm3533_set_hvled_config(lm3533, id, val);
+	else
+		ret = lm3533_set_lvled_config(lm3533, id, val);
+
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+#define LM3533_OUTPUT_ATTR(_name, _mode, _show, _store, _type, _id) \
+	struct lm3533_device_attribute lm3533_dev_attr_##_name = \
+		{ .dev_attr	= __ATTR(_name, _mode, _show, _store), \
+		  .type		= _type, \
+		  .u.output	= { .id = _id }, }
+
+#define LM3533_OUTPUT_ATTR_RW(_name, _type, _id) \
+	LM3533_OUTPUT_ATTR(output_##_name, S_IRUGO | S_IWUSR, \
+					show_output, store_output, _type, _id)
+
+#define LM3533_OUTPUT_HVLED_ATTR_RW(_nr) \
+	LM3533_OUTPUT_ATTR_RW(hvled##_nr, LM3533_ATTR_TYPE_BACKLIGHT, _nr)
+#define LM3533_OUTPUT_LVLED_ATTR_RW(_nr) \
+	LM3533_OUTPUT_ATTR_RW(lvled##_nr, LM3533_ATTR_TYPE_LED, _nr)
+/*
+ * Output config:
+ *
+ * output_hvled<nr> -- 0-1
+ * output_lvled<nr> -- 0-3
+ */
+static LM3533_OUTPUT_HVLED_ATTR_RW(1);
+static LM3533_OUTPUT_HVLED_ATTR_RW(2);
+static LM3533_OUTPUT_LVLED_ATTR_RW(1);
+static LM3533_OUTPUT_LVLED_ATTR_RW(2);
+static LM3533_OUTPUT_LVLED_ATTR_RW(3);
+static LM3533_OUTPUT_LVLED_ATTR_RW(4);
+static LM3533_OUTPUT_LVLED_ATTR_RW(5);
+
+static struct attribute *lm3533_attributes[] = {
+	&lm3533_dev_attr_boost_freq.dev_attr.attr,
+	&lm3533_dev_attr_boost_ovp.dev_attr.attr,
+	&lm3533_dev_attr_output_hvled1.dev_attr.attr,
+	&lm3533_dev_attr_output_hvled2.dev_attr.attr,
+	&lm3533_dev_attr_output_lvled1.dev_attr.attr,
+	&lm3533_dev_attr_output_lvled2.dev_attr.attr,
+	&lm3533_dev_attr_output_lvled3.dev_attr.attr,
+	&lm3533_dev_attr_output_lvled4.dev_attr.attr,
+	&lm3533_dev_attr_output_lvled5.dev_attr.attr,
+	NULL,
+};
+
+#define to_dev_attr(_attr) \
+	container_of(_attr, struct device_attribute, attr)
+
+static mode_t lm3533_attr_is_visible(struct kobject *kobj,
+					     struct attribute *attr, int n)
+{
+	struct device *dev = container_of(kobj, struct device, kobj);
+	struct lm3533 *lm3533 = dev_get_drvdata(dev);
+	struct device_attribute *dattr = to_dev_attr(attr);
+	struct lm3533_device_attribute *lattr = to_lm3533_dev_attr(dattr);
+	enum lm3533_attribute_type type = lattr->type;
+	mode_t mode = attr->mode;
+
+	if (!lm3533->have_backlights && type = LM3533_ATTR_TYPE_BACKLIGHT)
+		mode = 0;
+	else if (!lm3533->have_leds && type = LM3533_ATTR_TYPE_LED)
+		mode = 0;
+
+	return mode;
+};
+
+static struct attribute_group lm3533_attribute_group = {
+	.is_visible	= lm3533_attr_is_visible,
+	.attrs		= lm3533_attributes
+};
+
+static int __devinit lm3533_device_als_init(struct lm3533 *lm3533)
+{
+	struct lm3533_platform_data *pdata = lm3533->dev->platform_data;
+	int ret;
+
+	if (!pdata->als)
+		return 0;
+
+	lm3533_als_devs[0].platform_data = pdata->als;
+	lm3533_als_devs[0].pdata_size = sizeof(*pdata->als);
+
+	ret = mfd_add_devices(lm3533->dev, 0, lm3533_als_devs, 1, NULL, 0);
+	if (ret) {
+		dev_err(lm3533->dev, "failed to add ALS device\n");
+		return ret;
+	}
+
+	lm3533->have_als = 1;
+
+	return 0;
+}
+
+static int __devinit lm3533_device_bl_init(struct lm3533 *lm3533)
+{
+	struct lm3533_platform_data *pdata = lm3533->dev->platform_data;
+	int i;
+	int ret;
+
+	if (!pdata->backlights || pdata->num_backlights = 0)
+		return 0;
+
+	if (pdata->num_backlights > ARRAY_SIZE(lm3533_bl_devs))
+		pdata->num_backlights = ARRAY_SIZE(lm3533_bl_devs);
+
+	for (i = 0; i < pdata->num_backlights; ++i) {
+		lm3533_bl_devs[i].platform_data = &pdata->backlights[i];
+		lm3533_bl_devs[i].pdata_size = sizeof(pdata->backlights[i]);
+	}
+
+	ret = mfd_add_devices(lm3533->dev, 0, lm3533_bl_devs,
+					pdata->num_backlights, NULL, 0);
+	if (ret) {
+		dev_err(lm3533->dev, "failed to add backlight devices\n");
+		return ret;
+	}
+
+	lm3533->have_backlights = 1;
+
+	return 0;
+}
+
+static int __devinit lm3533_device_led_init(struct lm3533 *lm3533)
+{
+	struct lm3533_platform_data *pdata = lm3533->dev->platform_data;
+	int i;
+	int ret;
+
+	if (!pdata->leds || pdata->num_leds = 0)
+		return 0;
+
+	if (pdata->num_leds > ARRAY_SIZE(lm3533_led_devs))
+		pdata->num_leds = ARRAY_SIZE(lm3533_led_devs);
+
+	for (i = 0; i < pdata->num_leds; ++i) {
+		lm3533_led_devs[i].platform_data = &pdata->leds[i];
+		lm3533_led_devs[i].pdata_size = sizeof(pdata->leds[i]);
+	}
+
+	ret = mfd_add_devices(lm3533->dev, 0, lm3533_led_devs,
+						pdata->num_leds, NULL, 0);
+	if (ret) {
+		dev_err(lm3533->dev, "failed to add LED devices\n");
+		return ret;
+	}
+
+	lm3533->have_leds = 1;
+
+	return 0;
+}
+
+int __devinit lm3533_device_init(struct lm3533 *lm3533)
+{
+	struct lm3533_platform_data *pdata = lm3533->dev->platform_data;
+	int ret;
+
+	dev_dbg(lm3533->dev, "%s\n", __func__);
+
+	if (!pdata) {
+		dev_err(lm3533->dev, "no platform data\n");
+		return -EINVAL;
+	}
+
+	mutex_init(&lm3533->io_mutex);
+	lm3533->gpio_hwen = pdata->gpio_hwen;
+
+	dev_set_drvdata(lm3533->dev, lm3533);
+
+	if (gpio_is_valid(lm3533->gpio_hwen)) {
+		ret = gpio_request_one(lm3533->gpio_hwen, GPIOF_OUT_INIT_LOW,
+								"lm3533-hwen");
+		if (ret < 0) {
+			dev_err(lm3533->dev,
+				"failed to request HWEN GPIO %d\n",
+				lm3533->gpio_hwen);
+			return ret;
+		}
+	}
+
+	lm3533_enable(lm3533);
+
+	lm3533_device_als_init(lm3533);
+	lm3533_device_bl_init(lm3533);
+	lm3533_device_led_init(lm3533);
+
+	ret = sysfs_create_group(&lm3533->dev->kobj, &lm3533_attribute_group);
+	if (ret < 0) {
+		dev_err(lm3533->dev, "failed to create sysfs attributes\n");
+		goto err_unregister;
+	}
+
+	lm3533_debugfs_init(lm3533);
+
+	return 0;
+
+err_unregister:
+	mfd_remove_devices(lm3533->dev);
+	lm3533_disable(lm3533);
+	if (gpio_is_valid(lm3533->gpio_hwen))
+		gpio_free(lm3533->gpio_hwen);
+
+	return ret;
+}
+
+void __devexit lm3533_device_exit(struct lm3533 *lm3533)
+{
+	dev_dbg(lm3533->dev, "%s\n", __func__);
+
+	lm3533_debugfs_cleanup(lm3533);
+
+	sysfs_remove_group(&lm3533->dev->kobj, &lm3533_attribute_group);
+
+	mfd_remove_devices(lm3533->dev);
+	lm3533_disable(lm3533);
+	if (gpio_is_valid(lm3533->gpio_hwen))
+		gpio_free(lm3533->gpio_hwen);
+}
+
+MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
+MODULE_DESCRIPTION("LM3533 Core");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/lm3533-ctrlbank.c b/drivers/mfd/lm3533-ctrlbank.c
new file mode 100644
index 0000000..2feb4e8
--- /dev/null
+++ b/drivers/mfd/lm3533-ctrlbank.c
@@ -0,0 +1,134 @@
+/*
+ * lm3533-ctrlbank.c -- LM3533 Generic Control Bank interface
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+#define LM3533_BRIGHTNESS_MAX		255
+#define LM3533_MAX_CURRENT_MAX		31
+#define LM3533_PWM_MAX			0x3f
+
+#define LM3533_REG_PWM_BASE		0x14
+#define LM3533_REG_MAX_CURRENT_BASE	0x1f
+#define LM3533_REG_CTRLBANK_ENABLE	0x27
+#define LM3533_REG_BRIGHTNESS_BASE	0x40
+
+
+static inline u8 lm3533_ctrlbank_get_reg(struct lm3533_ctrlbank *cb, u8 base)
+{
+	return base + cb->id;
+}
+
+int lm3533_ctrlbank_enable(struct lm3533_ctrlbank *cb)
+{
+	u8 mask;
+	int ret;
+
+	dev_dbg(cb->dev, "%s - %d\n", __func__, cb->id);
+
+	mask = 1 << cb->id;
+	ret = lm3533_update(cb->lm3533, LM3533_REG_CTRLBANK_ENABLE,
+								mask, mask);
+	if (ret)
+		dev_err(cb->dev, "failed to enable ctrlbank %d\n", cb->id);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(lm3533_ctrlbank_enable);
+
+int lm3533_ctrlbank_disable(struct lm3533_ctrlbank *cb)
+{
+	u8 mask;
+	int ret;
+
+	dev_dbg(cb->dev, "%s - %d\n", __func__, cb->id);
+
+	mask = 1 << cb->id;
+	ret = lm3533_update(cb->lm3533, LM3533_REG_CTRLBANK_ENABLE, 0, mask);
+	if (ret)
+		dev_err(cb->dev, "failed to disable ctrlbank %d\n", cb->id);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(lm3533_ctrlbank_disable);
+
+#define lm3533_ctrlbank_set(_name, _NAME)				\
+int lm3533_ctrlbank_set_##_name(struct lm3533_ctrlbank *cb, u8 val)	\
+{									\
+	u8 reg;								\
+	int ret;							\
+									\
+	if (val > LM3533_##_NAME##_MAX)					\
+		return -EINVAL;						\
+									\
+	reg = lm3533_ctrlbank_get_reg(cb, LM3533_REG_##_NAME##_BASE);	\
+	ret = lm3533_write(cb->lm3533, reg, val);			\
+	if (ret)							\
+		dev_err(cb->dev, "failed to set " #_name "\n");		\
+									\
+	return ret;							\
+}									\
+EXPORT_SYMBOL_GPL(lm3533_ctrlbank_set_##_name);
+
+#define lm3533_ctrlbank_get(_name, _NAME)				\
+int lm3533_ctrlbank_get_##_name(struct lm3533_ctrlbank *cb, u8 *val)	\
+{									\
+	u8 reg;								\
+	int ret;							\
+									\
+	reg = lm3533_ctrlbank_get_reg(cb, LM3533_REG_##_NAME##_BASE);	\
+	ret = lm3533_read(cb->lm3533, reg, val);			\
+	if (ret)							\
+		dev_err(cb->dev, "failed to get " #_name "\n");		\
+									\
+	return ret;							\
+}									\
+EXPORT_SYMBOL_GPL(lm3533_ctrlbank_get_##_name);
+
+lm3533_ctrlbank_set(brightness, BRIGHTNESS);
+lm3533_ctrlbank_get(brightness, BRIGHTNESS);
+
+/*
+ * Full scale current.
+ *
+ * Imax = 5 + val * 0.8 mA, e.g.:
+ *
+ *    0 - 5 mA
+ *     ...
+ *   19 - 20.2 mA (default)
+ *     ...
+ *   31 - 29.8 mA
+ */
+lm3533_ctrlbank_set(max_current, MAX_CURRENT);
+lm3533_ctrlbank_get(max_current, MAX_CURRENT);
+
+/*
+ * PWM control:
+ *
+ *   bit 5 - PWM enabled in Zone 4
+ *   bit 4 - PWM enabled in Zone 3
+ *   bit 3 - PWM enabled in Zone 2
+ *   bit 2 - PWM enabled in Zone 1
+ *   bit 1 - PWM enabled in Zone 0
+ *   bit 0 - PWM enabled
+ */
+lm3533_ctrlbank_set(pwm, PWM);
+lm3533_ctrlbank_get(pwm, PWM);
+
+
+MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
+MODULE_DESCRIPTION("LM3533 Control Bank interface");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/lm3533-i2c.c b/drivers/mfd/lm3533-i2c.c
new file mode 100644
index 0000000..d479b62
--- /dev/null
+++ b/drivers/mfd/lm3533-i2c.c
@@ -0,0 +1,115 @@
+/*
+ * lm3533-i2c.c -- LM3533 I2C interface
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+static int lm3533_i2c_read(struct lm3533 *lm3533, u8 reg, u8 *val)
+{
+	int ret;
+
+	ret = i2c_smbus_read_byte_data(lm3533->i2c, reg);
+	if (ret < 0)
+		return ret;
+
+	*val = (u8)ret;
+
+	return 0;
+}
+
+static int lm3533_i2c_write(struct lm3533 *lm3533, u8 reg, u8 val)
+{
+	return i2c_smbus_write_byte_data(lm3533->i2c, reg, val);
+}
+
+static int __devinit lm3533_i2c_probe(struct i2c_client *i2c,
+					const struct i2c_device_id *id)
+{
+	struct lm3533 *lm3533;
+	int ret;
+
+	dev_dbg(&i2c->dev, "%s\n", __func__);
+
+	if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
+		return -EINVAL;
+
+	lm3533 = kzalloc(sizeof(*lm3533), GFP_KERNEL);
+	if (!lm3533)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, lm3533);
+
+	lm3533->dev = &i2c->dev;
+	lm3533->i2c = i2c;
+	lm3533->irq = i2c->irq;
+	lm3533->read = lm3533_i2c_read;
+	lm3533->write = lm3533_i2c_write;
+
+	ret = lm3533_device_init(lm3533);
+	if (ret) {
+		kfree(lm3533);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int __devexit lm3533_i2c_remove(struct i2c_client *i2c)
+{
+	struct lm3533 *lm3533 = i2c_get_clientdata(i2c);
+
+	dev_dbg(&i2c->dev, "%s\n", __func__);
+
+	lm3533_device_exit(lm3533);
+
+	kfree(lm3533);
+
+	return 0;
+}
+
+static const struct i2c_device_id lm3533_i2c_ids[] = {
+	{ "lm3533", 0 },
+	{ },
+};
+MODULE_DEVICE_TABLE(i2c, lm3533_i2c_ids);
+
+static struct i2c_driver lm3533_i2c_driver = {
+	.driver = {
+		   .name = "lm3533",
+		   .owner = THIS_MODULE,
+	},
+	.id_table	= lm3533_i2c_ids,
+	.probe		= lm3533_i2c_probe,
+	.remove		= __devexit_p(lm3533_i2c_remove),
+};
+
+static int __init lm3533_i2c_init(void)
+{
+	return i2c_add_driver(&lm3533_i2c_driver);
+}
+subsys_initcall(lm3533_i2c_init);
+
+static void __exit lm3533_i2c_exit(void)
+{
+	i2c_del_driver(&lm3533_i2c_driver);
+}
+module_exit(lm3533_i2c_exit);
+
+MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
+MODULE_DESCRIPTION("LM3533 I2C interface");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/lm3533.h b/include/linux/mfd/lm3533.h
new file mode 100644
index 0000000..cb8bf33
--- /dev/null
+++ b/include/linux/mfd/lm3533.h
@@ -0,0 +1,106 @@
+/*
+ * lm3533.h -- LM3533 interface
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef __LINUX_MFD_LM3533_H
+#define __LINUX_MFD_LM3533_H
+
+#include <linux/mutex.h>
+
+
+#define LM3533_ATTR_RO(_name) \
+	DEVICE_ATTR(_name, S_IRUGO, show_##_name, NULL)
+#define LM3533_ATTR_RW(_name) \
+	DEVICE_ATTR(_name, S_IRUGO | S_IWUSR , show_##_name, store_##_name)
+
+struct device;
+struct dentry;
+struct i2c_client;
+
+struct lm3533 {
+	struct device *dev;
+	struct i2c_client *i2c;
+
+	struct mutex io_mutex;
+
+	int (*read)(struct lm3533 *lm3533, u8 reg, u8 *val);
+	int (*write)(struct lm3533 *lm3533, u8 reg, u8 val);
+
+	int gpio_hwen;
+	int irq;
+
+	unsigned have_als:1;
+	unsigned have_backlights:1;
+	unsigned have_leds:1;
+
+#ifdef CONFIG_DEBUG_FS
+	struct dentry *debugfs_root;
+#endif
+};
+
+struct lm3533_ctrlbank {
+	struct lm3533 *lm3533;
+	struct device *dev;
+	int id;
+};
+
+struct lm3533_als_platform_data {
+	unsigned pwm_mode:1;		/* PWM input mode (default analog) */
+	unsigned int_mode:1;		/* interrupt mode (default polled) */
+	unsigned poll_interval;		/* in polled mode (in ms) */
+};
+
+struct lm3533_bl_platform_data {
+	char *name;
+	u8 default_brightness;		/* 0 - 255 */
+	u8 max_current;			/* 0 - 31 */
+	u8 pwm;				/* 0 - 0x3f */
+};
+
+struct lm3533_led_platform_data {
+	char *name;
+	const char *default_trigger;
+	u8 max_current;			/* 0 - 31 */
+	u8 pwm;				/* 0 - 0x3f */
+};
+
+struct lm3533_platform_data {
+	int gpio_hwen;
+
+	struct lm3533_als_platform_data *als;
+
+	struct lm3533_bl_platform_data *backlights;
+	int num_backlights;
+
+	struct lm3533_led_platform_data *leds;
+	int num_leds;
+};
+
+extern int lm3533_ctrlbank_enable(struct lm3533_ctrlbank *cb);
+extern int lm3533_ctrlbank_disable(struct lm3533_ctrlbank *cb);
+
+extern int lm3533_ctrlbank_set_brightness(struct lm3533_ctrlbank *cb, u8 val);
+extern int lm3533_ctrlbank_get_brightness(struct lm3533_ctrlbank *cb, u8 *val);
+extern int lm3533_ctrlbank_set_max_current(struct lm3533_ctrlbank *cb, u8 val);
+extern int lm3533_ctrlbank_get_max_current(struct lm3533_ctrlbank *cb,
+								u8 *val);
+extern int lm3533_ctrlbank_set_pwm(struct lm3533_ctrlbank *cb, u8 val);
+extern int lm3533_ctrlbank_get_pwm(struct lm3533_ctrlbank *cb, u8 *val);
+
+extern int lm3533_read(struct lm3533 *lm3533, u8 reg, u8 *val);
+extern int lm3533_write(struct lm3533 *lm3533, u8 reg, u8 val);
+extern int lm3533_update(struct lm3533 *lm3533, u8 reg, u8 val, u8 mask);
+
+extern int __devinit lm3533_device_init(struct lm3533 *lm3533);
+extern void __devinit lm3533_device_exit(struct lm3533 *lm3533);
+
+#endif	/* __LINUX_MFD_LM3533_H */
-- 
1.7.8.5


^ permalink raw reply related

* [PATCH 0/4] mfd: add LM3533 lighting-power chip driver
From: Johan Hovold @ 2012-04-20 15:30 UTC (permalink / raw)
  To: Richard Purdie, Samuel Ortiz, Arnd Bergmann, Greg Kroah-Hartman,
	Florian Tobias Schandinat
  Cc: Andrew Morton, linux-kernel, linux-fbdev, Johan Hovold

These patches (against v3.4-rc3) add support for the National Semiconductor /
Texas Instruments LM3533 lighting-power chip.

This multi-function device has four LEDs, two backlights and an ambient-light
sensor.

The LEDs and backlights can be controlled directly, through PWM input,
or by the on-chip ambient light sensor. Hardware-accelerated blinking is
provided for the LEDs.

ALS control is done through defining five light zones and three sets of
corresponding brightness target levels. The ALS driver presents a character
device (/dev/lm3533-als) which can be used to retrieve the current light zone
or to poll for zone changes.

Further details and specifications will soon be available from

	http://www.ti.com/product/lm3533

This work has been done on behalf of National Semiconductor / Texas
Instruments.

Thanks,
Johan


Johan Hovold (4):
  mfd: add LM3533 lighting-power core driver
  misc: add LM3533 ambient light sensor driver
  leds: add LM3533 LED driver
  backlight: add LM3533 backlight driver

 drivers/leds/Kconfig                |   13 +
 drivers/leds/Makefile               |    1 +
 drivers/leds/leds-lm3533.c          |  713 +++++++++++++++++++++++++++++++++
 drivers/mfd/Kconfig                 |   12 +
 drivers/mfd/Makefile                |    3 +
 drivers/mfd/lm3533-core.c           |  738 +++++++++++++++++++++++++++++++++++
 drivers/mfd/lm3533-ctrlbank.c       |  134 +++++++
 drivers/mfd/lm3533-i2c.c            |  115 ++++++
 drivers/misc/Kconfig                |   13 +
 drivers/misc/Makefile               |    1 +
 drivers/misc/lm3533-als.c           |  662 +++++++++++++++++++++++++++++++
 drivers/video/backlight/Kconfig     |   12 +
 drivers/video/backlight/Makefile    |    1 +
 drivers/video/backlight/lm3533_bl.c |  432 ++++++++++++++++++++
 include/linux/mfd/lm3533.h          |  106 +++++
 15 files changed, 2956 insertions(+), 0 deletions(-)
 create mode 100644 drivers/leds/leds-lm3533.c
 create mode 100644 drivers/mfd/lm3533-core.c
 create mode 100644 drivers/mfd/lm3533-ctrlbank.c
 create mode 100644 drivers/mfd/lm3533-i2c.c
 create mode 100644 drivers/misc/lm3533-als.c
 create mode 100644 drivers/video/backlight/lm3533_bl.c
 create mode 100644 include/linux/mfd/lm3533.h

-- 
1.7.8.5


^ permalink raw reply

* Re: [PATCH V3 3/3] OMAPDSS: DISPC: Correct DISPC functional clock usage
From: Tomi Valkeinen @ 2012-04-20 14:29 UTC (permalink / raw)
  To: Chandrabhanu Mahapatra; +Cc: linux-omap, linux-fbdev
In-Reply-To: <1334928673-27943-1-git-send-email-cmahapatra@ti.com>

[-- Attachment #1: Type: text/plain, Size: 1327 bytes --]

On Fri, 2012-04-20 at 19:01 +0530, Chandrabhanu Mahapatra wrote:
> DISPC_FCLK is incorrectly used as functional clock of DISPC in scaling
> calculations. So, DISPC_CORE_CLK replaces as functional clock of DISPC.
> DISPC_CORE_CLK is derived from DISPC_FCLK divided by an independent DISPC
> divisor LCD.
> 
> Signed-off-by: Chandrabhanu Mahapatra <cmahapatra@ti.com>
> ---
>  drivers/video/omap2/dss/dispc.c |   25 +++++++++++++++++++------
>  drivers/video/omap2/dss/dss.h   |    1 +
>  2 files changed, 20 insertions(+), 6 deletions(-)
> 
> diff --git a/drivers/video/omap2/dss/dispc.c b/drivers/video/omap2/dss/dispc.c
> index 17ffa71..0417423 100644
> --- a/drivers/video/omap2/dss/dispc.c
> +++ b/drivers/video/omap2/dss/dispc.c
> @@ -1855,7 +1855,7 @@ static int dispc_ovl_calc_scaling(enum omap_plane plane,
>  			fclk = calc_fclk(channel, in_width, in_height,
>  					out_width, out_height);
>  			error = (in_width > maxsinglelinewidth || !fclk ||
> -				fclk > dispc_fclk_rate());
> +				fclk > dispc_core_clk_rate());

The patch is now otherwise fine, but I think it needs some more
renaming. Now the code mixes fclk and core-clk names, which is rather
confusing. I guess the calc_fclk should actually be calc_req_core_clk?
And the fclk variable core_clk (or cclk or something)?

 Tomi


[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

^ permalink raw reply

* [PATCH V3 3/3] OMAPDSS: DISPC: Correct DISPC functional clock usage
From: Chandrabhanu Mahapatra @ 2012-04-20 13:43 UTC (permalink / raw)
  To: tomi.valkeinen; +Cc: linux-omap, linux-fbdev, Chandrabhanu Mahapatra
In-Reply-To: <1333379598-11544-4-git-send-email-cmahapatra@ti.com>

DISPC_FCLK is incorrectly used as functional clock of DISPC in scaling
calculations. So, DISPC_CORE_CLK replaces as functional clock of DISPC.
DISPC_CORE_CLK is derived from DISPC_FCLK divided by an independent DISPC
divisor LCD.

Signed-off-by: Chandrabhanu Mahapatra <cmahapatra@ti.com>
---
 drivers/video/omap2/dss/dispc.c |   25 +++++++++++++++++++------
 drivers/video/omap2/dss/dss.h   |    1 +
 2 files changed, 20 insertions(+), 6 deletions(-)

diff --git a/drivers/video/omap2/dss/dispc.c b/drivers/video/omap2/dss/dispc.c
index 17ffa71..0417423 100644
--- a/drivers/video/omap2/dss/dispc.c
+++ b/drivers/video/omap2/dss/dispc.c
@@ -1855,7 +1855,7 @@ static int dispc_ovl_calc_scaling(enum omap_plane plane,
 			fclk = calc_fclk(channel, in_width, in_height,
 					out_width, out_height);
 			error = (in_width > maxsinglelinewidth || !fclk ||
-				fclk > dispc_fclk_rate());
+				fclk > dispc_core_clk_rate());
 			if (error) {
 				if (decim_x = decim_y) {
 					decim_x = min_factor;
@@ -1893,7 +1893,7 @@ static int dispc_ovl_calc_scaling(enum omap_plane plane,
 					out_width, out_height);
 			error = (error || in_width > maxsinglelinewidth * 2 ||
 				(in_width > maxsinglelinewidth && *five_taps) ||
-				!fclk || fclk > dispc_fclk_rate());
+				!fclk || fclk > dispc_core_clk_rate());
 			if (error) {
 				if (decim_x = decim_y) {
 					decim_x = min_factor;
@@ -1926,7 +1926,7 @@ static int dispc_ovl_calc_scaling(enum omap_plane plane,
 	} else {
 		int decim_x_min = decim_x;
 		in_height = DIV_ROUND_UP(height, decim_y);
-		in_width_max = dispc_fclk_rate() /
+		in_width_max = dispc_core_clk_rate() /
 				DIV_ROUND_UP(dispc_mgr_pclk_rate(channel),
 						out_width);
 		decim_x = DIV_ROUND_UP(width, in_width_max);
@@ -1950,13 +1950,13 @@ static int dispc_ovl_calc_scaling(enum omap_plane plane,
 	}
 
 	DSSDBG("required fclk rate = %lu Hz\n", fclk);
-	DSSDBG("current fclk rate = %lu Hz\n", dispc_fclk_rate());
+	DSSDBG("current fclk rate = %lu Hz\n", dispc_core_clk_rate());
 
-	if (!fclk || fclk > dispc_fclk_rate()) {
+	if (!fclk || fclk > dispc_core_clk_rate()) {
 		DSSERR("failed to set up scaling, "
 			"required fclk rate = %lu Hz, "
 			"current fclk rate = %lu Hz\n",
-			fclk, dispc_fclk_rate());
+			fclk, dispc_core_clk_rate());
 		return -EINVAL;
 	}
 
@@ -2646,6 +2646,19 @@ unsigned long dispc_mgr_pclk_rate(enum omap_channel channel)
 	}
 }
 
+unsigned long dispc_core_clk_rate(void)
+{
+	int lcd;
+	unsigned long fclk = dispc_fclk_rate();
+
+	if (dss_has_feature(FEAT_CORE_CLK_DIV))
+		lcd = REG_GET(DISPC_DIVISOR, 23, 16);
+	else
+		lcd = REG_GET(DISPC_DIVISORo(OMAP_DSS_CHANNEL_LCD), 23, 16);
+
+	return fclk / lcd;
+}
+
 void dispc_dump_clocks(struct seq_file *s)
 {
 	int lcd, pcd;
diff --git a/drivers/video/omap2/dss/dss.h b/drivers/video/omap2/dss/dss.h
index d4b3dff..1bde34c 100644
--- a/drivers/video/omap2/dss/dss.h
+++ b/drivers/video/omap2/dss/dss.h
@@ -451,6 +451,7 @@ void dispc_mgr_set_pol_freq(enum omap_channel channel,
 		enum omap_panel_config config, u8 acbi, u8 acb);
 unsigned long dispc_mgr_lclk_rate(enum omap_channel channel);
 unsigned long dispc_mgr_pclk_rate(enum omap_channel channel);
+unsigned long dispc_core_clk_rate(void);
 int dispc_mgr_set_clock_div(enum omap_channel channel,
 		struct dispc_clock_info *cinfo);
 int dispc_mgr_get_clock_div(enum omap_channel channel,
-- 
1.7.1


^ permalink raw reply related

* Re: [PATCH] OMAPDSS: VENC: allow switching venc type at runtime
From: Tomi Valkeinen @ 2012-04-20 11:34 UTC (permalink / raw)
  To: Grazvydas Ignotas; +Cc: linux-fbdev, linux-omap
In-Reply-To: <CANOLnOP9ciznRxddjFSLuWALme57Woq0p6zP93U3tOFvd9em5w@mail.gmail.com>

[-- Attachment #1: Type: text/plain, Size: 2208 bytes --]

On Fri, 2012-04-20 at 13:49 +0300, Grazvydas Ignotas wrote:
> On Fri, Apr 20, 2012 at 11:38 AM, Tomi Valkeinen <tomi.valkeinen@ti.com> wrote:
> > On Thu, 2012-03-29 at 02:45 +0300, Grazvydas Ignotas wrote:
> >> VENC type (composite/svideo) doesn't have to be fixed by board wiring,
> >> it is possible to provide both connectors, which is what pandora does.
> >> Having to recompile the kernel for users who have TV connector types
> >> that's don't match default board setting is very inconvenient, especially
> >
> > You don't have to recompile the kernel, you could just set the venc type
> > in the board file depending on a boot parameter.
> >
> >> for users of a consumer device, so add support for switching VENC type
> >> at runtime over a new sysfs file venc_type.
> >
> > I really dislike adding new custom sysfs entries for omapdss, and I'd
> > like to avoid them if at all possible.
> 
> Well some panels already have custom attributes, and venc could be
> considered as special panel type, so if it's allowed for panels, why
> not allow it for venc?

It's not really about "allowing". It's just that each new sysfs file is
a new non-standard custom API to userspace which we need to support
until the end of time. Adding new sysfs files carelessly will cause a
nightmare for me in the future, so by default I'm against new sysfs
files =).

> > Do you need to change the venc
> > type during runtime, or is it enough that it can be set during boot?
> 
> We need this on runtime, otherwise it causes several issues:
> - reboot is required to change the setting, although there is no
> technical reason to really require it. This punishes users who want to
> try both settings or have both TV types (with a portable device this
> may sometimes happen).
> - having to provide a way for users to change this in kernel boot
> arguments. Note that many pandora users don't know how to handle boot
> scripts, so a bootloader menu of some sort would be needed or ability
> to edit u-boot environment from Linux, both of which would be
> needlessly complicated solutions.

Ok. Sounds like we need to have dynamic configuration. I'll review the
patch.

 Tomi


[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

^ permalink raw reply

* Re: VESA and VGA16 framebuffer drivers clash sometimes
From: Geert Uytterhoeven @ 2012-04-20 11:12 UTC (permalink / raw)
  To: Boszormenyi Zoltan; +Cc: linux-kernel, Linux Fbdev development list
In-Reply-To: <4F911568.7050601@pr.hu>

2012/4/20 Boszormenyi Zoltan <zboszor@pr.hu>:
> on an embedded x86 board with Intel Atom, text on vesafb
> gets distorted if both vesafb and vga16fb are compiled in.
> The strange thing is that it doesn't happen all the time but
> about once out of 15 boots.
>
> The distortion looks like this:
> http://www.postgresql-support.de/private/IMG_7221.jpg
>
> The relevant kernel messages:
>
> Apr 13 11:14:30 term002 kernel: vesafb: mode is 800x600x16, linelength\x1600,
> pages=7
> Apr 13 11:14:30 term002 kernel: vesafb: scrolling: redraw
> Apr 13 11:14:30 term002 kernel: vesafb: Truecolor: size=0:5:6:5,
> shift=0:11:5:0
> Apr 13 11:14:30 term002 kernel: vesafb: framebuffer at 0xa0000000, mapped to
> 0xdf580000, using 1875k, total 8128k
> Apr 13 11:14:30 term002 kernel: Console: switching to colour frame buffer
> device 100x37
> Apr 13 11:14:30 term002 kernel: fb0: VESA VGA frame buffer device
> Apr 13 11:14:30 term002 BOOTLOG: Terminal startup
> Apr 13 11:14:30 term002 kernel: vga16fb: mapped to 0xc00a0000
> Apr 13 11:14:30 term002 kernel: fbcon: VGA16 VGA (fb1) is primary device
> Apr 13 11:14:30 term002 kernel: fbcon: Remapping primary device, fb1, to tty
> 1-63
> Apr 13 11:14:30 term002 kernel: fb1: VGA16 VGA frame buffer device
>
> It's strange that vga16fb is always detected as primary
> and vesafb gets remapped to fb1 even when there is no
> screen distortion. The kernel is compiled for SMP.

IIRC, the request_region()/request_mem_region() calls in vga16fb.c that would
avoid this were removed several years ago. Unfortunately I don't remember
anymore why. Google or full-history-linux may help you...

Gr{oetje,eeting}s,

                        Geert

--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org

In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
                                -- Linus Torvalds

^ permalink raw reply

* Re: [PATCH] OMAPDSS: VENC: allow switching venc type at runtime
From: Grazvydas Ignotas @ 2012-04-20 10:49 UTC (permalink / raw)
  To: Tomi Valkeinen; +Cc: linux-fbdev, linux-omap
In-Reply-To: <1334911137.2058.8.camel@deskari>

On Fri, Apr 20, 2012 at 11:38 AM, Tomi Valkeinen <tomi.valkeinen@ti.com> wrote:
> On Thu, 2012-03-29 at 02:45 +0300, Grazvydas Ignotas wrote:
>> VENC type (composite/svideo) doesn't have to be fixed by board wiring,
>> it is possible to provide both connectors, which is what pandora does.
>> Having to recompile the kernel for users who have TV connector types
>> that's don't match default board setting is very inconvenient, especially
>
> You don't have to recompile the kernel, you could just set the venc type
> in the board file depending on a boot parameter.
>
>> for users of a consumer device, so add support for switching VENC type
>> at runtime over a new sysfs file venc_type.
>
> I really dislike adding new custom sysfs entries for omapdss, and I'd
> like to avoid them if at all possible.

Well some panels already have custom attributes, and venc could be
considered as special panel type, so if it's allowed for panels, why
not allow it for venc?

> Do you need to change the venc
> type during runtime, or is it enough that it can be set during boot?

We need this on runtime, otherwise it causes several issues:
- reboot is required to change the setting, although there is no
technical reason to really require it. This punishes users who want to
try both settings or have both TV types (with a portable device this
may sometimes happen).
- having to provide a way for users to change this in kernel boot
arguments. Note that many pandora users don't know how to handle boot
scripts, so a bootloader menu of some sort would be needed or ability
to edit u-boot environment from Linux, both of which would be
needlessly complicated solutions.


-- 
Gražvydas

^ permalink raw reply

* Re: [PATCH] OMAPDSS: VENC: allow switching venc type at runtime
From: Tomi Valkeinen @ 2012-04-20  8:38 UTC (permalink / raw)
  To: Grazvydas Ignotas; +Cc: linux-fbdev, linux-omap
In-Reply-To: <1332978312-11959-1-git-send-email-notasas@gmail.com>

[-- Attachment #1: Type: text/plain, Size: 832 bytes --]

On Thu, 2012-03-29 at 02:45 +0300, Grazvydas Ignotas wrote:
> VENC type (composite/svideo) doesn't have to be fixed by board wiring,
> it is possible to provide both connectors, which is what pandora does.
> Having to recompile the kernel for users who have TV connector types
> that's don't match default board setting is very inconvenient, especially

You don't have to recompile the kernel, you could just set the venc type
in the board file depending on a boot parameter.

> for users of a consumer device, so add support for switching VENC type
> at runtime over a new sysfs file venc_type.

I really dislike adding new custom sysfs entries for omapdss, and I'd
like to avoid them if at all possible. Do you need to change the venc
type during runtime, or is it enough that it can be set during boot?

 Tomi


[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

^ permalink raw reply

* Re: [PATCH V3 3/3] OMAPDSS: DISPC: Correct DISPC functional clock usage
From: Mahapatra, Chandrabhanu @ 2012-04-20  6:42 UTC (permalink / raw)
  To: Tomi Valkeinen; +Cc: Archit Taneja, linux-omap, linux-fbdev
In-Reply-To: <1334841071.1911.85.camel@deskari>

On Thu, Apr 19, 2012 at 6:41 PM, Tomi Valkeinen <tomi.valkeinen@ti.com> wrote:
> On Mon, 2012-04-02 at 20:43 +0530, Chandrabhanu Mahapatra wrote:
>> DISPC_FCLK is incorrectly used as functional clock of DISPC in scaling
>> calculations. So, DISPC_CORE_CLK replaces as functional clock of DISPC.
>> DISPC_CORE_CLK is derived from DISPC_FCLK divided by an independent DISPC
>> divisor LCD.
>>
>> Signed-off-by: Chandrabhanu Mahapatra <cmahapatra@ti.com>
>> ---
>>  drivers/video/omap2/dss/dispc.c |   28 ++++++++++++++++++++++------
>>  drivers/video/omap2/dss/dss.h   |    1 +
>>  2 files changed, 23 insertions(+), 6 deletions(-)
>>
>> diff --git a/drivers/video/omap2/dss/dispc.c b/drivers/video/omap2/dss/dispc.c
>> index 17ffa71..cfde674 100644
>> --- a/drivers/video/omap2/dss/dispc.c
>> +++ b/drivers/video/omap2/dss/dispc.c
>> @@ -1813,6 +1813,7 @@ static int dispc_ovl_calc_scaling(enum omap_plane plane,
>>                               dss_feat_get_param_max(FEAT_PARAM_LINEWIDTH);
>>       const int max_decim_limit = 16;
>>       unsigned long fclk = 0;
>> +     unsigned long dispc_core_clk = dispc_core_clk_rate(channel);
>>       int decim_x, decim_y, error, min_factor;
>>       u16 in_width, in_height, in_width_max = 0;
>>
>> @@ -1855,7 +1856,7 @@ static int dispc_ovl_calc_scaling(enum omap_plane plane,
>>                       fclk = calc_fclk(channel, in_width, in_height,
>>                                       out_width, out_height);
>>                       error = (in_width > maxsinglelinewidth || !fclk ||
>> -                             fclk > dispc_fclk_rate());
>> +                             fclk > dispc_core_clk);
>>                       if (error) {
>>                               if (decim_x = decim_y) {
>>                                       decim_x = min_factor;
>> @@ -1893,7 +1894,7 @@ static int dispc_ovl_calc_scaling(enum omap_plane plane,
>>                                       out_width, out_height);
>>                       error = (error || in_width > maxsinglelinewidth * 2 ||
>>                               (in_width > maxsinglelinewidth && *five_taps) ||
>> -                             !fclk || fclk > dispc_fclk_rate());
>> +                             !fclk || fclk > dispc_core_clk);
>>                       if (error) {
>>                               if (decim_x = decim_y) {
>>                                       decim_x = min_factor;
>> @@ -1926,7 +1927,7 @@ static int dispc_ovl_calc_scaling(enum omap_plane plane,
>>       } else {
>>               int decim_x_min = decim_x;
>>               in_height = DIV_ROUND_UP(height, decim_y);
>> -             in_width_max = dispc_fclk_rate() /
>> +             in_width_max = dispc_core_clk /
>>                               DIV_ROUND_UP(dispc_mgr_pclk_rate(channel),
>>                                               out_width);
>>               decim_x = DIV_ROUND_UP(width, in_width_max);
>> @@ -1950,13 +1951,13 @@ static int dispc_ovl_calc_scaling(enum omap_plane plane,
>>       }
>>
>>       DSSDBG("required fclk rate = %lu Hz\n", fclk);
>> -     DSSDBG("current fclk rate = %lu Hz\n", dispc_fclk_rate());
>> +     DSSDBG("current fclk rate = %lu Hz\n", dispc_core_clk);
>>
>> -     if (!fclk || fclk > dispc_fclk_rate()) {
>> +     if (!fclk || fclk > dispc_core_clk) {
>>               DSSERR("failed to set up scaling, "
>>                       "required fclk rate = %lu Hz, "
>>                       "current fclk rate = %lu Hz\n",
>> -                     fclk, dispc_fclk_rate());
>> +                     fclk, dispc_core_clk);
>>               return -EINVAL;
>>       }
>>
>> @@ -2646,6 +2647,21 @@ unsigned long dispc_mgr_pclk_rate(enum omap_channel channel)
>>       }
>>  }
>>
>> +unsigned long dispc_core_clk_rate(enum omap_channel channel)
>> +{
>> +     int lcd = 1;
>> +     unsigned long r = dispc_fclk_rate();
>> +
>> +     if (dss_has_feature(FEAT_CORE_CLK_DIV)) {
>> +             lcd = REG_GET(DISPC_DIVISOR, 23, 16);
>> +     } else {
>> +             if (dispc_mgr_is_lcd(channel))
>> +                     lcd = REG_GET(DISPC_DIVISORo(channel), 23, 16);
>> +     }
>> +
>> +     return r / lcd ;
>> +}
>> +
>
> I wonder if this is correct. "channel" for dispc core clock doesn't make
> sense, there's no channel related to that. At least on OMAP4.
>
> If I'm not mistaken, in omap2/3 case (i.e.
> dss_has_feature(FEAT_CORE_CLK_DIV) = false) we can just use channel 0
> to get the lcd divisor. Although that would mean that LCD output's
> divisor affects the tv-out's scaling calculations, which feels a bit
> strange...
>

Yes, you are right. I had failed to look at channel 0 or
OMAP_DSS_CHANNEL_LCD can simply be used.

> So... I don't know... Do we really have two different dispc core clocks
> int omap2/3 (for lcd output and for tv output), but only one in omap4?
> If so, then the above code is ok. Have you found explanations from TRM
> which say what clock is required and where?
>

Though the term Dispc Core Clock is never used in OMAP2/3 TRM, it is
actually the logic clock that drives the various logics of DSS
subsystem. So we can say that we have one dispc core clock for OMAP2/3
and that being the logic clock. I took two different logics as LCD
factor returned zero for TV.  However, LCD factor of DISPC_DIVISOR of
channel 0 can be used always.

> In any case, please remove the initialization of lcd variable, and add:
>
> else
>        lcd = 1;
>
> I think that's much clearer. And "r" variable is commonly used as a
> return value. I would rename the variable to something else, say, "fck".
>
>  Tomi
>

Yes, sure it makes code more understandable and clean.


-- 
Chandrabhanu Mahapatra
Texas Instruments India Pvt. Ltd.

^ permalink raw reply

* RE: [PATCH] blackfin: fix compile error in bfin-lq035q1-fb.c
From: Jingoo Han @ 2012-04-20  4:52 UTC (permalink / raw)
  To: linux-fbdev
In-Reply-To: <1334803401-26780-1-git-send-email-paul.gortmaker@windriver.com>



> -----Original Message-----
> From: linux-fbdev-owner@vger.kernel.org [mailto:linux-fbdev-owner@vger.kernel.org] On Behalf Of Paul
> Gortmaker
> Sent: Thursday, April 19, 2012 11:43 AM
> To: linux-fbdev@vger.kernel.org
> Cc: uclinux-dist-devel@blackfin.uclinux.org; Paul Gortmaker; Florian Tobias Schandinat; Mike Frysinger
> Subject: [PATCH] blackfin: fix compile error in bfin-lq035q1-fb.c
> 
> This file has an implicit dependency on GPIO stuff, showing
> up as the following build failure:
> 
> drivers/video/bfin-lq035q1-fb.c:369:6: error: 'GPIOF_OUT_INIT_LOW' undeclared
> 
> Other more global bfin build issues prevent an automated bisect, but
> it really doesn't matter - simply add in the appropriate header.
> 
> Cc: Florian Tobias Schandinat <FlorianSchandinat@gmx.de>
> Cc: Mike Frysinger <vapier@gentoo.org>
> Signed-off-by: Paul Gortmaker <paul.gortmaker@windriver.com>

Acked-by: Jingoo Han <jg1.han@samsung.com>

It looks good.
This header file 'linux/gpio.h' is necessary for using 'GPIOF_OUT_INIT_LOW'.

> 
> diff --git a/drivers/video/bfin-lq035q1-fb.c b/drivers/video/bfin-lq035q1-fb.c
> index 86922ac..353c02f 100644
> --- a/drivers/video/bfin-lq035q1-fb.c
> +++ b/drivers/video/bfin-lq035q1-fb.c
> @@ -13,6 +13,7 @@
>  #include <linux/errno.h>
>  #include <linux/string.h>
>  #include <linux/fb.h>
> +#include <linux/gpio.h>
>  #include <linux/slab.h>
>  #include <linux/init.h>
>  #include <linux/types.h>
> --
> 1.7.9.1
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-fbdev" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html


^ permalink raw reply

* [PATCH V2] i.MX28: Shut down the LCD controller to avoid BootROM sampling bug
From: Marek Vasut @ 2012-04-19 18:31 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1334767680-12688-1-git-send-email-marex@denx.de>

From: Marek Vasut <marek.vasut@gmail.com>

If there's some traffic on the LCD controller pads, the BootROM has trouble with
sampling the bootmode from these pads. The BootROM usually ends in a loop.

Signed-off-by: Marek Vasut <marek.vasut@gmail.com>
Cc: Chen Peter-B29397 <B29397@freescale.com>
Cc: Detlev Zundel <dzu@denx.de>
Cc: Fabio Estevam <festevam@gmail.com>
Cc: Florian Tobias Schandinat <FlorianSchandinat@gmx.de>
Cc: Li Frank-B20596 <B20596@freescale.com>
Cc: Lin Tony-B19295 <B19295@freescale.com>
Cc: Linux FBDEV <linux-fbdev@vger.kernel.org>
Cc: Sascha Hauer <s.hauer@pengutronix.de>
Cc: Shawn Guo <shawn.guo@freescale.com>
Cc: Shawn Guo <shawn.guo@linaro.org>
Cc: Stefano Babic <sbabic@denx.de>
Cc: Subodh Nijsure <snijsure@grid-net.com>
Cc: Tony Lin <tony.lin@freescale.com>
Cc: Wolfgang Denk <wd@denx.de>
Acked-by: Shawn Guo <shawn.guo@linaro.org>
Acked-by: Wolfgang Denk <wd@denx.de>
Acked-by: Wolfram Sang <w.sang@pengutronix.de>
Tested-by: Wolfgang Denk <wd@denx.de>
---
 drivers/video/mxsfb.c |   13 +++++++++++++
 1 file changed, 13 insertions(+)

V2: Make shutdown() call static

diff --git a/drivers/video/mxsfb.c b/drivers/video/mxsfb.c
index 4a89f88..dcf29bf 100644
--- a/drivers/video/mxsfb.c
+++ b/drivers/video/mxsfb.c
@@ -880,6 +880,18 @@ static int __devexit mxsfb_remove(struct platform_device *pdev)
 	return 0;
 }
 
+static void mxsfb_shutdown(struct platform_device *pdev)
+{
+	struct fb_info *fb_info = platform_get_drvdata(pdev);
+	struct mxsfb_info *host = to_imxfb_host(fb_info);
+
+	/*
+	 * Force stop the LCD controller as keeping it running during reboot
+	 * might interfere with the BootROM's boot mode pads sampling.
+	 */
+	writel(CTRL_RUN, host->base + LCDC_CTRL + REG_CLR);
+}
+
 static struct platform_device_id mxsfb_devtype[] = {
 	{
 		.name = "imx23-fb",
@@ -896,6 +908,7 @@ MODULE_DEVICE_TABLE(platform, mxsfb_devtype);
 static struct platform_driver mxsfb_driver = {
 	.probe = mxsfb_probe,
 	.remove = __devexit_p(mxsfb_remove),
+	.shutdown = mxsfb_shutdown,
 	.id_table = mxsfb_devtype,
 	.driver = {
 		   .name = DRIVER_NAME,
-- 
1.7.9.5


^ permalink raw reply related

* [PATCH] drivers/video/intelfb/intelfbdrv.c: add missing agp_backend_release
From: Julia Lawall @ 2012-04-19 16:55 UTC (permalink / raw)
  To: Maik Broemme
  Cc: kernel-janitors, Florian Tobias Schandinat, linux-fbdev,
	linux-kernel

From: Julia Lawall <Julia.Lawall@lip6.fr>

Release bridge, as done on all other paths out of the function.

Signed-off-by: Julia Lawall <Julia.Lawall@lip6.fr>

---
 drivers/video/intelfb/intelfbdrv.c |    2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/video/intelfb/intelfbdrv.c b/drivers/video/intelfb/intelfbdrv.c
index 02fd226..bdcbfba 100644
--- a/drivers/video/intelfb/intelfbdrv.c
+++ b/drivers/video/intelfb/intelfbdrv.c
@@ -680,6 +680,7 @@ static int __devinit intelfb_pci_register(struct pci_dev *pdev,
 		 + dinfo->fb.size);
 	if (!dinfo->aperture.virtual) {
 		ERR_MSG("Cannot remap FB region.\n");
+		agp_backend_release(bridge);
 		cleanup(dinfo);
 		return -ENODEV;
 	}
@@ -689,6 +690,7 @@ static int __devinit intelfb_pci_register(struct pci_dev *pdev,
 					      INTEL_REG_SIZE);
 	if (!dinfo->mmio_base) {
 		ERR_MSG("Cannot remap MMIO region.\n");
+		agp_backend_release(bridge);
 		cleanup(dinfo);
 		return -ENODEV;
 	}


^ permalink raw reply related

* Re: [PATCH] i.MX28: Shut down the LCD controller to avoid BootROM sampling bug
From: Shawn Guo @ 2012-04-19 14:41 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1334767680-12688-1-git-send-email-marex@denx.de>

On Wed, Apr 18, 2012 at 06:48:00PM +0200, Marek Vasut wrote:
...
> +void mxsfb_shutdown(struct platform_device *pdev)

static?

Otherwise,

Acked-by: Shawn Guo <shawn.guo@linaro.org>

> +{
> +	struct fb_info *fb_info = platform_get_drvdata(pdev);
> +	struct mxsfb_info *host = to_imxfb_host(fb_info);
> +
> +	/*
> +	 * Force stop the LCD controller as keeping it running during reboot
> +	 * might interfere with the BootROM's boot mode pads sampling.
> +	 */
> +	writel(CTRL_RUN, host->base + LCDC_CTRL + REG_CLR);
> +}
> +
>  static struct platform_device_id mxsfb_devtype[] = {
>  	{
>  		.name = "imx23-fb",
> @@ -896,6 +908,7 @@ MODULE_DEVICE_TABLE(platform, mxsfb_devtype);
>  static struct platform_driver mxsfb_driver = {
>  	.probe = mxsfb_probe,
>  	.remove = __devexit_p(mxsfb_remove),
> +	.shutdown = mxsfb_shutdown,
>  	.id_table = mxsfb_devtype,
>  	.driver = {
>  		   .name = DRIVER_NAME,
> -- 
> 1.7.9.5
> 

^ permalink raw reply

* Re: [PATCH V3 3/3] OMAPDSS: DISPC: Correct DISPC functional clock usage
From: Tomi Valkeinen @ 2012-04-19 13:11 UTC (permalink / raw)
  To: Chandrabhanu Mahapatra, Archit Taneja; +Cc: linux-omap, linux-fbdev
In-Reply-To: <1333379598-11544-4-git-send-email-cmahapatra@ti.com>

[-- Attachment #1: Type: text/plain, Size: 4271 bytes --]

On Mon, 2012-04-02 at 20:43 +0530, Chandrabhanu Mahapatra wrote:
> DISPC_FCLK is incorrectly used as functional clock of DISPC in scaling
> calculations. So, DISPC_CORE_CLK replaces as functional clock of DISPC.
> DISPC_CORE_CLK is derived from DISPC_FCLK divided by an independent DISPC
> divisor LCD.
> 
> Signed-off-by: Chandrabhanu Mahapatra <cmahapatra@ti.com>
> ---
>  drivers/video/omap2/dss/dispc.c |   28 ++++++++++++++++++++++------
>  drivers/video/omap2/dss/dss.h   |    1 +
>  2 files changed, 23 insertions(+), 6 deletions(-)
> 
> diff --git a/drivers/video/omap2/dss/dispc.c b/drivers/video/omap2/dss/dispc.c
> index 17ffa71..cfde674 100644
> --- a/drivers/video/omap2/dss/dispc.c
> +++ b/drivers/video/omap2/dss/dispc.c
> @@ -1813,6 +1813,7 @@ static int dispc_ovl_calc_scaling(enum omap_plane plane,
>  				dss_feat_get_param_max(FEAT_PARAM_LINEWIDTH);
>  	const int max_decim_limit = 16;
>  	unsigned long fclk = 0;
> +	unsigned long dispc_core_clk = dispc_core_clk_rate(channel);
>  	int decim_x, decim_y, error, min_factor;
>  	u16 in_width, in_height, in_width_max = 0;
>  
> @@ -1855,7 +1856,7 @@ static int dispc_ovl_calc_scaling(enum omap_plane plane,
>  			fclk = calc_fclk(channel, in_width, in_height,
>  					out_width, out_height);
>  			error = (in_width > maxsinglelinewidth || !fclk ||
> -				fclk > dispc_fclk_rate());
> +				fclk > dispc_core_clk);
>  			if (error) {
>  				if (decim_x == decim_y) {
>  					decim_x = min_factor;
> @@ -1893,7 +1894,7 @@ static int dispc_ovl_calc_scaling(enum omap_plane plane,
>  					out_width, out_height);
>  			error = (error || in_width > maxsinglelinewidth * 2 ||
>  				(in_width > maxsinglelinewidth && *five_taps) ||
> -				!fclk || fclk > dispc_fclk_rate());
> +				!fclk || fclk > dispc_core_clk);
>  			if (error) {
>  				if (decim_x == decim_y) {
>  					decim_x = min_factor;
> @@ -1926,7 +1927,7 @@ static int dispc_ovl_calc_scaling(enum omap_plane plane,
>  	} else {
>  		int decim_x_min = decim_x;
>  		in_height = DIV_ROUND_UP(height, decim_y);
> -		in_width_max = dispc_fclk_rate() /
> +		in_width_max = dispc_core_clk /
>  				DIV_ROUND_UP(dispc_mgr_pclk_rate(channel),
>  						out_width);
>  		decim_x = DIV_ROUND_UP(width, in_width_max);
> @@ -1950,13 +1951,13 @@ static int dispc_ovl_calc_scaling(enum omap_plane plane,
>  	}
>  
>  	DSSDBG("required fclk rate = %lu Hz\n", fclk);
> -	DSSDBG("current fclk rate = %lu Hz\n", dispc_fclk_rate());
> +	DSSDBG("current fclk rate = %lu Hz\n", dispc_core_clk);
>  
> -	if (!fclk || fclk > dispc_fclk_rate()) {
> +	if (!fclk || fclk > dispc_core_clk) {
>  		DSSERR("failed to set up scaling, "
>  			"required fclk rate = %lu Hz, "
>  			"current fclk rate = %lu Hz\n",
> -			fclk, dispc_fclk_rate());
> +			fclk, dispc_core_clk);
>  		return -EINVAL;
>  	}
>  
> @@ -2646,6 +2647,21 @@ unsigned long dispc_mgr_pclk_rate(enum omap_channel channel)
>  	}
>  }
>  
> +unsigned long dispc_core_clk_rate(enum omap_channel channel)
> +{
> +	int lcd = 1;
> +	unsigned long r = dispc_fclk_rate();
> +
> +	if (dss_has_feature(FEAT_CORE_CLK_DIV)) {
> +		lcd = REG_GET(DISPC_DIVISOR, 23, 16);
> +	} else {
> +		if (dispc_mgr_is_lcd(channel))
> +			lcd = REG_GET(DISPC_DIVISORo(channel), 23, 16);
> +	}
> +
> +	return r / lcd ;
> +}
> +

I wonder if this is correct. "channel" for dispc core clock doesn't make
sense, there's no channel related to that. At least on OMAP4.

If I'm not mistaken, in omap2/3 case (i.e.
dss_has_feature(FEAT_CORE_CLK_DIV) == false) we can just use channel 0
to get the lcd divisor. Although that would mean that LCD output's
divisor affects the tv-out's scaling calculations, which feels a bit
strange...

So... I don't know... Do we really have two different dispc core clocks
int omap2/3 (for lcd output and for tv output), but only one in omap4?
If so, then the above code is ok. Have you found explanations from TRM
which say what clock is required and where?

In any case, please remove the initialization of lcd variable, and add:

else
	lcd = 1;

I think that's much clearer. And "r" variable is commonly used as a
return value. I would rename the variable to something else, say, "fck".

 Tomi


[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

^ permalink raw reply

* Re: [PATCH 0/6] OMAPDSS: APPLY: Treat overlay manager timings as shadow registers
From: Archit Taneja @ 2012-04-19 12:10 UTC (permalink / raw)
  To: Tomi Valkeinen; +Cc: linux-omap, linux-fbdev
In-Reply-To: <1334836135.1911.40.camel@deskari>

On Thursday 19 April 2012 05:18 PM, Tomi Valkeinen wrote:
> On Mon, 2012-04-16 at 12:53 +0530, Archit Taneja wrote:
>> An overlay manager's timings (the manager size, and blanking parameters if an
>> LCD manager) are DISPC shadow registers, and they should hence follow the
>> correct programming model.
>>
>> This set makes the timings a manager_info parameter. The interface drivers now
>> set the timings in manager_info instead of directly writing to registers.
>>
>> This change also prevents the need to use display resolution for overlay
>> checks, hence making some of the APPLY functions less dependent on the display.
>>
>> These patches apply over:
>>
>> git://gitorious.org/linux-omap-dss2/linux.git dev
>>
>> Archit Taneja (6):
>>    OMAPDSS: DISPC/RFBI: Use dispc_mgr_set_lcd_timings() for setting lcd
>>      size
>>    OMAPDSS: DISPC: Use a common function to set manager timings
>>    OMAPDSS: DISPC: Clean up manager timing/size functions
>>    OMAPDSS: MANAGER: Make DISPC timings a manager_info parameter
>>    OMAPDSS: MANAGER: Check validity of manager timings
>>    OMAPDSS: APPLY: Remove display dependency from overlay and manager
>>      checks
>
> The patches 1, 2 and 3 in this series look like independent cleanups.
> Should I apply those, or do you think there may be changes required?

I think you can pull these 3, they were sort of a prerequisite for the 
apply-manager-timings stuff. I don't think they need to change.

I'll post another series for applying the manager timings correctly(i.e, 
the fixed version of the last 3 patches).

Archit

>
>   Tomi
>


^ permalink raw reply

* Re: [PATCH 0/6] OMAPDSS: APPLY: Treat overlay manager timings as shadow registers
From: Tomi Valkeinen @ 2012-04-19 12:00 UTC (permalink / raw)
  To: Archit Taneja; +Cc: linux-omap, linux-fbdev
In-Reply-To: <4F8FFDD5.5030302@ti.com>

[-- Attachment #1: Type: text/plain, Size: 1799 bytes --]

On Thu, 2012-04-19 at 17:28 +0530, Archit Taneja wrote:
> On Thursday 19 April 2012 05:18 PM, Tomi Valkeinen wrote:
> > On Mon, 2012-04-16 at 12:53 +0530, Archit Taneja wrote:
> >> An overlay manager's timings (the manager size, and blanking parameters if an
> >> LCD manager) are DISPC shadow registers, and they should hence follow the
> >> correct programming model.
> >>
> >> This set makes the timings a manager_info parameter. The interface drivers now
> >> set the timings in manager_info instead of directly writing to registers.
> >>
> >> This change also prevents the need to use display resolution for overlay
> >> checks, hence making some of the APPLY functions less dependent on the display.
> >>
> >> These patches apply over:
> >>
> >> git://gitorious.org/linux-omap-dss2/linux.git dev
> >>
> >> Archit Taneja (6):
> >>    OMAPDSS: DISPC/RFBI: Use dispc_mgr_set_lcd_timings() for setting lcd
> >>      size
> >>    OMAPDSS: DISPC: Use a common function to set manager timings
> >>    OMAPDSS: DISPC: Clean up manager timing/size functions
> >>    OMAPDSS: MANAGER: Make DISPC timings a manager_info parameter
> >>    OMAPDSS: MANAGER: Check validity of manager timings
> >>    OMAPDSS: APPLY: Remove display dependency from overlay and manager
> >>      checks
> >
> > The patches 1, 2 and 3 in this series look like independent cleanups.
> > Should I apply those, or do you think there may be changes required?
> 
> I think you can pull these 3, they were sort of a prerequisite for the 
> apply-manager-timings stuff. I don't think they need to change.
> 
> I'll post another series for applying the manager timings correctly(i.e, 
> the fixed version of the last 3 patches).

Ok, I applied the first three patches to my dev branch.

 Tomi


[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

^ permalink raw reply

* Re: [PATCH 0/6] OMAPDSS: APPLY: Treat overlay manager timings as shadow registers
From: Tomi Valkeinen @ 2012-04-19 11:48 UTC (permalink / raw)
  To: Archit Taneja; +Cc: linux-omap, linux-fbdev
In-Reply-To: <1334561027-28569-1-git-send-email-archit@ti.com>

[-- Attachment #1: Type: text/plain, Size: 1263 bytes --]

On Mon, 2012-04-16 at 12:53 +0530, Archit Taneja wrote:
> An overlay manager's timings (the manager size, and blanking parameters if an
> LCD manager) are DISPC shadow registers, and they should hence follow the
> correct programming model.
> 
> This set makes the timings a manager_info parameter. The interface drivers now
> set the timings in manager_info instead of directly writing to registers.
> 
> This change also prevents the need to use display resolution for overlay
> checks, hence making some of the APPLY functions less dependent on the display.
> 
> These patches apply over:
> 
> git://gitorious.org/linux-omap-dss2/linux.git dev
> 
> Archit Taneja (6):
>   OMAPDSS: DISPC/RFBI: Use dispc_mgr_set_lcd_timings() for setting lcd
>     size
>   OMAPDSS: DISPC: Use a common function to set manager timings
>   OMAPDSS: DISPC: Clean up manager timing/size functions
>   OMAPDSS: MANAGER: Make DISPC timings a manager_info parameter
>   OMAPDSS: MANAGER: Check validity of manager timings
>   OMAPDSS: APPLY: Remove display dependency from overlay and manager
>     checks

The patches 1, 2 and 3 in this series look like independent cleanups.
Should I apply those, or do you think there may be changes required?

 Tomi


[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

^ permalink raw reply

* [PATCH v2] backlight: Add LMS501KF03 LCD panel driver
From: Sachin Kamat @ 2012-04-19 11:45 UTC (permalink / raw)
  To: linux-fbdev

LMS501KF03 is a 480x800 LCD module with brightness control.
The driver uses 3-wired SPI inteface.

Signed-off-by: Ilho Lee <Ilho215.lee@samsung.com>
Signed-off-by: Sachin Kamat <sachin.kamat@linaro.org>
---
Changes since v1:
Incorporated review comments from Florian Tobias Schandinat
<FlorianSchandinat@gmx.de> - Simplied lms501kf03_ldi_enable and
lms501kf03_ldi_disable function implementations.
---
 drivers/video/backlight/Kconfig      |    8 +
 drivers/video/backlight/Makefile     |    1 +
 drivers/video/backlight/lms501kf03.c |  525 ++++++++++++++++++++++++++++++++++
 3 files changed, 534 insertions(+), 0 deletions(-)
 create mode 100644 drivers/video/backlight/lms501kf03.c

diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
index af16884..e216f77 100644
--- a/drivers/video/backlight/Kconfig
+++ b/drivers/video/backlight/Kconfig
@@ -125,6 +125,14 @@ config LCD_AMS369FG06
 	  If you have an AMS369FG06 AMOLED Panel, say Y to enable its
 	  LCD control driver.
 
+config LCD_LMS501KF03
+	tristate "LMS501KF03 AMOLED LCD Driver"
+	depends on SPI_GPIO && BACKLIGHT_CLASS_DEVICE
+	default n
+	help
+	  If you have an 5.01" LMS501KF03 AMOLED Panel, say Y to enable its
+	  LCD control driver.
+
 endif # LCD_CLASS_DEVICE
 
 #
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
index 36855ae..1b1e62a 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -14,6 +14,7 @@ obj-$(CONFIG_LCD_TOSA)		   += tosa_lcd.o
 obj-$(CONFIG_LCD_S6E63M0)	+= s6e63m0.o
 obj-$(CONFIG_LCD_LD9040)	+= ld9040.o
 obj-$(CONFIG_LCD_AMS369FG06)	+= ams369fg06.o
+obj-$(CONFIG_LCD_LMS501KF03)	+= lms501kf03.o
 
 obj-$(CONFIG_BACKLIGHT_CLASS_DEVICE) += backlight.o
 obj-$(CONFIG_BACKLIGHT_ATMEL_PWM)    += atmel-pwm-bl.o
diff --git a/drivers/video/backlight/lms501kf03.c b/drivers/video/backlight/lms501kf03.c
new file mode 100644
index 0000000..af25532
--- /dev/null
+++ b/drivers/video/backlight/lms501kf03.c
@@ -0,0 +1,525 @@
+/*
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ *		http://www.samsung.com/
+ *
+ * LMS501KF03 5.01" LCD module driver
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+#include <linux/backlight.h>
+#include <linux/delay.h>
+#include <linux/fb.h>
+#include <linux/gpio.h>
+#include <linux/lcd.h>
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+#include <linux/wait.h>
+
+#define ENDDEF			0xFF00
+#define COMMAND_ONLY		0x00
+#define DATA_ONLY		0x01
+
+#define MIN_BRIGHTNESS		0
+#define MAX_BRIGHTNESS		255
+#define DEFAULT_BRIGHTNESS	150
+
+#define POWER_IS_ON(power)	((power) <= FB_BLANK_NORMAL)
+
+struct lms501kf03 {
+	struct device			*dev;
+	struct spi_device		*spi;
+	unsigned int			power;
+	struct lcd_device		*ld;
+	struct backlight_device		*bd;
+	struct lcd_platform_data	*lcd_pd;
+};
+
+const unsigned short SEQ_PASSWORD[] = {
+	0xb9, 0xff, 0x83, 0x69,
+	ENDDEF
+};
+
+const unsigned short SEQ_POWER[] = {
+	0xb1, 0x01, 0x00, 0x34, 0x06, 0x00, 0x14, 0x14, 0x20, 0x28,
+	0x12, 0x12, 0x17, 0x0a, 0x01, 0xe6, 0xe6, 0xe6, 0xe6, 0xe6,
+	ENDDEF
+};
+
+const unsigned short SEQ_DISPLAY[] = {
+	0xb2, 0x00, 0x2b, 0x03, 0x03, 0x70, 0x00, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x03, 0x03, 0x00, 0x01,
+	ENDDEF
+};
+
+const unsigned short SEQ_RGB_IF[] = {
+	0xb3, 0x09,
+	ENDDEF
+};
+
+const unsigned short SEQ_DISPLAY_INV[] = {
+	0xb4, 0x01, 0x08, 0x77, 0x0e, 0x06,
+	ENDDEF
+};
+
+const unsigned short SEQ_VCOM[] = {
+	0xb6, 0x4c, 0x2e,
+	ENDDEF
+};
+
+const unsigned short SEQ_GATE[] = {
+	0xd5, 0x00, 0x05, 0x03, 0x29, 0x01, 0x07, 0x17, 0x68, 0x13,
+	0x37, 0x20, 0x31, 0x8a, 0x46, 0x9b, 0x57, 0x13, 0x02, 0x75,
+	0xb9, 0x64, 0xa8, 0x07, 0x0f, 0x04, 0x07,
+	ENDDEF
+};
+
+const unsigned short SEQ_PANEL[] = {
+	0xcc, 0x02,
+	ENDDEF
+};
+
+const unsigned short SEQ_COL_MOD[] = {
+	0x3a, 0x77,
+	ENDDEF
+};
+
+const unsigned short SEQ_W_GAMMA[] = {
+	0xe0, 0x00, 0x04, 0x09, 0x0f, 0x1f, 0x3f, 0x1f, 0x2f, 0x0a,
+	0x0f, 0x10, 0x16, 0x18, 0x16, 0x17, 0x0d, 0x15, 0x00, 0x04,
+	0x09, 0x0f, 0x38, 0x3f, 0x20, 0x39, 0x0a, 0x0f, 0x10, 0x16,
+	0x18, 0x16, 0x17, 0x0d, 0x15,
+	ENDDEF
+};
+
+const unsigned short SEQ_RGB_GAMMA[] = {
+	0xc1, 0x01, 0x03, 0x07, 0x0f, 0x1a, 0x22, 0x2c, 0x33, 0x3c,
+	0x46, 0x4f, 0x58, 0x60, 0x69, 0x71, 0x79, 0x82, 0x89, 0x92,
+	0x9a, 0xa1, 0xa9, 0xb1, 0xb9, 0xc1, 0xc9, 0xcf, 0xd6, 0xde,
+	0xe5, 0xec, 0xf3, 0xf9, 0xff, 0xdd, 0x39, 0x07, 0x1c, 0xcb,
+	0xab, 0x5f, 0x49, 0x80, 0x03, 0x07, 0x0f, 0x19, 0x20, 0x2a,
+	0x31, 0x39, 0x42, 0x4b, 0x53, 0x5b, 0x63, 0x6b, 0x73, 0x7b,
+	0x83, 0x8a, 0x92, 0x9b, 0xa2, 0xaa, 0xb2, 0xba, 0xc2, 0xca,
+	0xd0, 0xd8, 0xe1, 0xe8, 0xf0, 0xf8, 0xff, 0xf7, 0xd8, 0xbe,
+	0xa7, 0x39, 0x40, 0x85, 0x8c, 0xc0, 0x04, 0x07, 0x0c, 0x17,
+	0x1c, 0x23, 0x2b, 0x34, 0x3b, 0x43, 0x4c, 0x54, 0x5b, 0x63,
+	0x6a, 0x73, 0x7a, 0x82, 0x8a, 0x91, 0x98, 0xa1, 0xa8, 0xb0,
+	0xb7, 0xc1, 0xc9, 0xcf, 0xd9, 0xe3, 0xea, 0xf4, 0xff, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	ENDDEF
+};
+
+const unsigned short SEQ_UP_DN[] = {
+	0x36, 0x10,
+	ENDDEF
+};
+
+const unsigned short SEQ_SLEEP_IN[] = {
+	0x10,
+	ENDDEF
+};
+
+const unsigned short SEQ_SLEEP_OUT[] = {
+	0x11,
+	ENDDEF
+};
+
+const unsigned short SEQ_DISPLAY_ON[] = {
+	0x29,
+	ENDDEF
+};
+
+const unsigned short SEQ_DISPLAY_OFF[] = {
+	0x10,
+	ENDDEF
+};
+
+static int lms501kf03_spi_write_byte(struct lms501kf03 *lcd, int addr, int data)
+{
+	u16 buf[1];
+	struct spi_message msg;
+
+	struct spi_transfer xfer = {
+		.len	= 2,
+		.tx_buf	= buf,
+	};
+
+	buf[0] = (addr << 8) | data;
+
+	spi_message_init(&msg);
+	spi_message_add_tail(&xfer, &msg);
+
+	return spi_sync(lcd->spi, &msg);
+}
+
+static int lms501kf03_spi_write(struct lms501kf03 *lcd, unsigned char address,
+	unsigned char command)
+{
+	int ret = 0;
+
+	ret = lms501kf03_spi_write_byte(lcd, address, command);
+
+	return ret;
+}
+
+static int lms501kf03_panel_send_sequence(struct lms501kf03 *lcd,
+	const unsigned short *wbuf)
+{
+	int ret = 0, i = 0;
+
+	while (wbuf[i] != ENDDEF) {
+		if (i = 0)
+			ret = lms501kf03_spi_write(lcd, COMMAND_ONLY, wbuf[i]);
+		else
+			ret = lms501kf03_spi_write(lcd, DATA_ONLY, wbuf[i]);
+		if (ret)
+			break;
+
+		udelay(100);
+		i += 1;
+	}
+	return ret;
+}
+
+static int lms501kf03_ldi_init(struct lms501kf03 *lcd)
+{
+	int ret, i;
+	const unsigned short *init_seq[] = {
+		SEQ_PASSWORD,
+		SEQ_POWER,
+		SEQ_DISPLAY,
+		SEQ_RGB_IF,
+		SEQ_DISPLAY_INV,
+		SEQ_VCOM,
+		SEQ_GATE,
+		SEQ_PANEL,
+		SEQ_COL_MOD,
+		SEQ_W_GAMMA,
+		SEQ_RGB_GAMMA,
+		SEQ_SLEEP_OUT,
+	};
+
+	for (i = 0; i < ARRAY_SIZE(init_seq); i++) {
+		ret = lms501kf03_panel_send_sequence(lcd, init_seq[i]);
+		if (ret)
+			break;
+	}
+	mdelay(120);
+
+	return ret;
+}
+
+static int lms501kf03_ldi_enable(struct lms501kf03 *lcd)
+{
+	return lms501kf03_panel_send_sequence(lcd, SEQ_DISPLAY_ON);
+}
+
+static int lms501kf03_ldi_disable(struct lms501kf03 *lcd)
+{
+	return lms501kf03_panel_send_sequence(lcd, SEQ_DISPLAY_OFF);
+}
+
+static int lms501kf03_power_on(struct lms501kf03 *lcd)
+{
+	int ret = 0;
+	struct lcd_platform_data *pd = NULL;
+	struct backlight_device *bd = NULL;
+
+	pd = lcd->lcd_pd;
+	if (!pd) {
+		dev_err(lcd->dev, "platform data is NULL.\n");
+		return -EFAULT;
+	}
+
+	bd = lcd->bd;
+	if (!bd) {
+		dev_err(lcd->dev, "backlight device is NULL.\n");
+		return -EFAULT;
+	}
+
+	if (!pd->power_on) {
+		dev_err(lcd->dev, "power_on is NULL.\n");
+		return -EFAULT;
+	} else {
+		pd->power_on(lcd->ld, 1);
+		mdelay(pd->power_on_delay);
+	}
+
+	if (!pd->reset) {
+		dev_err(lcd->dev, "reset is NULL.\n");
+		return -EFAULT;
+	} else {
+		pd->reset(lcd->ld);
+		mdelay(pd->reset_delay);
+	}
+
+	ret = lms501kf03_ldi_init(lcd);
+	if (ret) {
+		dev_err(lcd->dev, "failed to initialize ldi.\n");
+		return ret;
+	}
+
+	ret = lms501kf03_ldi_enable(lcd);
+	if (ret) {
+		dev_err(lcd->dev, "failed to enable ldi.\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int lms501kf03_power_off(struct lms501kf03 *lcd)
+{
+	int ret = 0;
+	struct lcd_platform_data *pd = NULL;
+
+	pd = lcd->lcd_pd;
+	if (!pd) {
+		dev_err(lcd->dev, "platform data is NULL\n");
+		return -EFAULT;
+	}
+
+	ret = lms501kf03_ldi_disable(lcd);
+	if (ret) {
+		dev_err(lcd->dev, "lcd setting failed.\n");
+		return -EIO;
+	}
+
+	mdelay(pd->power_off_delay);
+
+	if (!pd->power_on) {
+		dev_err(lcd->dev, "power_on is NULL.\n");
+		return -EFAULT;
+	} else
+		pd->power_on(lcd->ld, 0);
+
+	return 0;
+}
+
+static int lms501kf03_power(struct lms501kf03 *lcd, int power)
+{
+	int ret = 0;
+
+	if (POWER_IS_ON(power) && !POWER_IS_ON(lcd->power))
+		ret = lms501kf03_power_on(lcd);
+	else if (!POWER_IS_ON(power) && POWER_IS_ON(lcd->power))
+		ret = lms501kf03_power_off(lcd);
+
+	if (!ret)
+		lcd->power = power;
+
+	return ret;
+}
+
+static int lms501kf03_get_power(struct lcd_device *ld)
+{
+	struct lms501kf03 *lcd = lcd_get_data(ld);
+
+	return lcd->power;
+}
+
+static int lms501kf03_set_power(struct lcd_device *ld, int power)
+{
+	struct lms501kf03 *lcd = lcd_get_data(ld);
+
+	if (power != FB_BLANK_UNBLANK && power != FB_BLANK_POWERDOWN &&
+		power != FB_BLANK_NORMAL) {
+		dev_err(lcd->dev, "power value should be 0, 1 or 4.\n");
+		return -EINVAL;
+	}
+
+	return lms501kf03_power(lcd, power);
+}
+
+static int lms501kf03_get_brightness(struct backlight_device *bd)
+{
+	return bd->props.brightness;
+}
+
+static int lms501kf03_set_brightness(struct backlight_device *bd)
+{
+	int ret = 0;
+	int brightness = bd->props.brightness;
+
+	if (brightness < MIN_BRIGHTNESS ||
+		brightness > bd->props.max_brightness) {
+		dev_err(&bd->dev, "lcd brightness should be %d to %d.\n",
+			MIN_BRIGHTNESS, MAX_BRIGHTNESS);
+		return -EINVAL;
+	}
+
+	return ret;
+}
+
+static struct lcd_ops lms501kf03_lcd_ops = {
+	.get_power = lms501kf03_get_power,
+	.set_power = lms501kf03_set_power,
+};
+
+static const struct backlight_ops lms501kf03_backlight_ops = {
+	.get_brightness = lms501kf03_get_brightness,
+	.update_status = lms501kf03_set_brightness,
+};
+
+static int __devinit lms501kf03_probe(struct spi_device *spi)
+{
+	struct lms501kf03 *lcd = NULL;
+	struct lcd_device *ld = NULL;
+	struct backlight_device *bd = NULL;
+	struct backlight_properties props;
+	int ret = 0;
+
+	lcd = kzalloc(sizeof(struct lms501kf03), GFP_KERNEL);
+	if (!lcd)
+		return -ENOMEM;
+
+	/* lms501kf03 lcd panel uses 3-wire 9-bit SPI Mode. */
+	spi->bits_per_word = 9;
+
+	ret = spi_setup(spi);
+	if (ret < 0) {
+		dev_err(&spi->dev, "spi setup failed.\n");
+		goto out_free_lcd;
+	}
+
+	lcd->spi = spi;
+	lcd->dev = &spi->dev;
+
+	lcd->lcd_pd = (struct lcd_platform_data *)spi->dev.platform_data;
+	if (!lcd->lcd_pd) {
+		dev_err(&spi->dev, "platform data is NULL\n");
+		goto out_free_lcd;
+	}
+
+	ld = lcd_device_register("lms501kf03", &spi->dev, lcd,
+		&lms501kf03_lcd_ops);
+	if (IS_ERR(ld)) {
+		ret = PTR_ERR(ld);
+		goto out_free_lcd;
+	}
+
+	lcd->ld = ld;
+
+	memset(&props, 0, sizeof(struct backlight_properties));
+	props.type = BACKLIGHT_RAW;
+	props.max_brightness = MAX_BRIGHTNESS;
+
+	bd = backlight_device_register("lms501kf03-bl", &spi->dev, lcd,
+		&lms501kf03_backlight_ops, &props);
+	if (IS_ERR(bd)) {
+		ret = PTR_ERR(bd);
+		goto out_lcd_unregister;
+	}
+
+	bd->props.brightness = DEFAULT_BRIGHTNESS;
+	lcd->bd = bd;
+
+	if (!lcd->lcd_pd->lcd_enabled) {
+		/*
+		 * if lcd panel was off from bootloader then
+		 * current lcd status is powerdown and then
+		 * it enables lcd panel.
+		 */
+		lcd->power = FB_BLANK_POWERDOWN;
+
+		lms501kf03_power(lcd, FB_BLANK_UNBLANK);
+	} else
+		lcd->power = FB_BLANK_UNBLANK;
+
+	dev_set_drvdata(&spi->dev, lcd);
+
+	dev_info(&spi->dev, "lms501kf03 panel driver has been probed.\n");
+
+	return 0;
+
+out_lcd_unregister:
+	lcd_device_unregister(ld);
+out_free_lcd:
+	kfree(lcd);
+	return ret;
+}
+
+static int __devexit lms501kf03_remove(struct spi_device *spi)
+{
+	struct lms501kf03 *lcd = dev_get_drvdata(&spi->dev);
+
+	lms501kf03_power(lcd, FB_BLANK_POWERDOWN);
+	lcd_device_unregister(lcd->ld);
+	kfree(lcd);
+
+	return 0;
+}
+
+#if defined(CONFIG_PM)
+unsigned int before_power;
+
+static int lms501kf03_suspend(struct spi_device *spi, pm_message_t mesg)
+{
+	int ret = 0;
+	struct lms501kf03 *lcd = dev_get_drvdata(&spi->dev);
+
+	dev_dbg(&spi->dev, "lcd->power = %d\n", lcd->power);
+
+	before_power = lcd->power;
+
+	/*
+	 * when lcd panel is suspend, lcd panel becomes off
+	 * regardless of status.
+	 */
+	ret = lms501kf03_power(lcd, FB_BLANK_POWERDOWN);
+
+	return ret;
+}
+
+static int lms501kf03_resume(struct spi_device *spi)
+{
+	int ret = 0;
+	struct lms501kf03 *lcd = dev_get_drvdata(&spi->dev);
+
+	/*
+	 * after suspended, if lcd panel status is FB_BLANK_UNBLANK
+	 * (at that time, before_power is FB_BLANK_UNBLANK) then
+	 * it changes that status to FB_BLANK_POWERDOWN to get lcd on.
+	 */
+	if (before_power = FB_BLANK_UNBLANK)
+		lcd->power = FB_BLANK_POWERDOWN;
+
+	dev_dbg(&spi->dev, "before_power = %d\n", before_power);
+
+	ret = lms501kf03_power(lcd, before_power);
+
+	return ret;
+}
+#else
+#define lms501kf03_suspend	NULL
+#define lms501kf03_resume	NULL
+#endif
+
+void lms501kf03_shutdown(struct spi_device *spi)
+{
+	struct lms501kf03 *lcd = dev_get_drvdata(&spi->dev);
+
+	lms501kf03_power(lcd, FB_BLANK_POWERDOWN);
+}
+
+static struct spi_driver lms501kf03_driver = {
+	.driver = {
+		.name	= "lms501kf03",
+		.bus	= &spi_bus_type,
+		.owner	= THIS_MODULE,
+	},
+	.probe		= lms501kf03_probe,
+	.remove		= __devexit_p(lms501kf03_remove),
+	.shutdown	= lms501kf03_shutdown,
+	.suspend	= lms501kf03_suspend,
+	.resume		= lms501kf03_resume,
+};
+
+module_spi_driver(lms501kf03_driver);
+
+MODULE_AUTHOR("Ilho Lee <Ilho215.lee@samsung.com>");
+MODULE_AUTHOR("Sachin Kamat <sachin.kamat@samsung.com>");
+MODULE_DESCRIPTION("LMS501KF03 LCD Driver");
+MODULE_LICENSE("GPL");
-- 
1.7.4.1


^ permalink raw reply related

* Re: [PATCH 4/6] OMAPDSS: MANAGER: Make DISPC timings a manager_info parameter
From: Tomi Valkeinen @ 2012-04-19 11:37 UTC (permalink / raw)
  To: Archit Taneja; +Cc: linux-omap, linux-fbdev
In-Reply-To: <4F8FE42D.6030105@ti.com>

[-- Attachment #1: Type: text/plain, Size: 3861 bytes --]

On Thu, 2012-04-19 at 15:38 +0530, Archit Taneja wrote:
> On Thursday 19 April 2012 12:07 PM, Tomi Valkeinen wrote:
> > On Thu, 2012-04-19 at 11:43 +0530, Archit Taneja wrote:
> >> On Wednesday 18 April 2012 08:28 PM, Tomi Valkeinen wrote:
> >>> On Mon, 2012-04-16 at 12:53 +0530, Archit Taneja wrote:
> >>>> DISPC manager size and DISPC manager blanking parameters(for LCD managers)
> >>>> follow the shadow register programming model. Currently, they are programmed
> >>>> directly by the interface drivers.
> >>>>
> >>>> Make timings(omap_video_timing struct) an overlay_manager_info member, they are
> >>>> now programmed via the apply mechanism used for programming shadow registers.
> >>>>
> >>>> The interface driver now call the function dss_mgr_set_timings() which applies
> >>>> the new timing parameters, rather than directly writing to DISPC registers.
> >>>
> >>> I don't think that works correctly. The omap_overlay_manager_info is
> >>> supposed to be set with set_manager_info() by the user of omapdss, to
> >>> configure the manager's features. The timings are not supposed to be set
> >>> via that mechanism, but with dssdev->set_timings().
> >>>
> >>> This is similar to the info and extra_info for overlay. info has stuff
> >>> that omapdss doesn't change, it just uses what the user gives.
> >>> extra_info, on the other hand, has omapdss private stuff that the user
> >>> does not see. Timings are clearly private stuff in this sense, because
> >>> they are set via dssdev->set_timings().
> >>>
> >>> One reason for this is the programming model we use. If the user of
> >>> omapdss does get_info() for two overlays, changes the infos, and then
> >>> calls set_info() for both overlays and finally apply() for the manager,
> >>> we don't do any locking there because omapdss presumes the info is
> >>> handled by one user. If, say, the dpi.c would change the info and call
> >>> apply at the same time, the configuration could go badly wrong.
> >>
> >> I think I get your point. So even though get_info() and set_info() fn's
> >> are spinlock protected, if there are 2 users setting the info, it
> >> doesn't mean that the info they finally written is correct. Is this
> >> example the same thing as what you mean ? :
> >>
> >> In order of time:
> >>
> >> -user 1 gets an overlay's info
> >>
> >> -user 2 gets an overlay's info
> >>
> >> -user 1 modifies and sets overlay info
> >>
> >> -user 2 sets overlay info without the knowledge of what user 1 did.
> >>
> >> So even though we ensure these events happen sequentially, we don't
> >> protect the info across multiple users.
> >
> > Yes. The spinlocks ensure that the info is "whole", so we don't get a
> > few fields from user1 and a few fields from user2. But they don't
> > protect us from the case you described above.
> >
> > For that we would need a "dss lock" that the user would acquire before
> > using get_info and set_info. But I don't want to go to that direction,
> > because we really only support one user anyway.
> >
> > The problem in this particular case is that omapdss itself becomes
> > another user if it uses get_info&  set_info. And that can be easily
> > avoided by splitting the configuration into public (the "info") and
> > internal ("extra_info"). The users of omapdss never touch the
> > extra_info, and omapdss never touches the info.
> 
> omapdss touches info via sysfs, so if we use sysfs(in a fast way, using 
> a it in scripts, for example) while fb uses DSS2, then we might hit this 
> issue.

The sysfs calls come from the user, so in that sense it's not omapdss
that is doing the change.

But it's true that with sysfs we may have two users for omapdss, for
example the scripts using sysfs and omapfb. Hopefully we can deprecate
the sysfs files at some point...

 Tomi


[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

^ permalink raw reply

* Re: [PATCH 4/6] OMAPDSS: MANAGER: Make DISPC timings a manager_info parameter
From: Archit Taneja @ 2012-04-19 10:20 UTC (permalink / raw)
  To: Tomi Valkeinen; +Cc: linux-omap, linux-fbdev
In-Reply-To: <1334817446.1521.57.camel@lappy>

On Thursday 19 April 2012 12:07 PM, Tomi Valkeinen wrote:
> On Thu, 2012-04-19 at 11:43 +0530, Archit Taneja wrote:
>> On Wednesday 18 April 2012 08:28 PM, Tomi Valkeinen wrote:
>>> On Mon, 2012-04-16 at 12:53 +0530, Archit Taneja wrote:
>>>> DISPC manager size and DISPC manager blanking parameters(for LCD managers)
>>>> follow the shadow register programming model. Currently, they are programmed
>>>> directly by the interface drivers.
>>>>
>>>> Make timings(omap_video_timing struct) an overlay_manager_info member, they are
>>>> now programmed via the apply mechanism used for programming shadow registers.
>>>>
>>>> The interface driver now call the function dss_mgr_set_timings() which applies
>>>> the new timing parameters, rather than directly writing to DISPC registers.
>>>
>>> I don't think that works correctly. The omap_overlay_manager_info is
>>> supposed to be set with set_manager_info() by the user of omapdss, to
>>> configure the manager's features. The timings are not supposed to be set
>>> via that mechanism, but with dssdev->set_timings().
>>>
>>> This is similar to the info and extra_info for overlay. info has stuff
>>> that omapdss doesn't change, it just uses what the user gives.
>>> extra_info, on the other hand, has omapdss private stuff that the user
>>> does not see. Timings are clearly private stuff in this sense, because
>>> they are set via dssdev->set_timings().
>>>
>>> One reason for this is the programming model we use. If the user of
>>> omapdss does get_info() for two overlays, changes the infos, and then
>>> calls set_info() for both overlays and finally apply() for the manager,
>>> we don't do any locking there because omapdss presumes the info is
>>> handled by one user. If, say, the dpi.c would change the info and call
>>> apply at the same time, the configuration could go badly wrong.
>>
>> I think I get your point. So even though get_info() and set_info() fn's
>> are spinlock protected, if there are 2 users setting the info, it
>> doesn't mean that the info they finally written is correct. Is this
>> example the same thing as what you mean ? :
>>
>> In order of time:
>>
>> -user 1 gets an overlay's info
>>
>> -user 2 gets an overlay's info
>>
>> -user 1 modifies and sets overlay info
>>
>> -user 2 sets overlay info without the knowledge of what user 1 did.
>>
>> So even though we ensure these events happen sequentially, we don't
>> protect the info across multiple users.
>
> Yes. The spinlocks ensure that the info is "whole", so we don't get a
> few fields from user1 and a few fields from user2. But they don't
> protect us from the case you described above.
>
> For that we would need a "dss lock" that the user would acquire before
> using get_info and set_info. But I don't want to go to that direction,
> because we really only support one user anyway.
>
> The problem in this particular case is that omapdss itself becomes
> another user if it uses get_info&  set_info. And that can be easily
> avoided by splitting the configuration into public (the "info") and
> internal ("extra_info"). The users of omapdss never touch the
> extra_info, and omapdss never touches the info.

omapdss touches info via sysfs, so if we use sysfs(in a fast way, using 
a it in scripts, for example) while fb uses DSS2, then we might hit this 
issue.

Archit

>
>   Tomi
>


^ permalink raw reply

* Re: [PATCH] backlight: Add LMS501KF03 LCD panel driver
From: Sachin Kamat @ 2012-04-19  7:52 UTC (permalink / raw)
  To: linux-fbdev
In-Reply-To: <1334558406-22576-1-git-send-email-sachin.kamat@linaro.org>

On 18/04/2012, Florian Tobias Schandinat <FlorianSchandinat@gmx.de> wrote:
> On 04/16/2012 06:40 AM, Sachin Kamat wrote:
>> LMS501KF03 is a 480x800 LCD module with brightness control.
>> The driver uses 3-wired SPI inteface.
>>
>> Signed-off-by: Ilho Lee <Ilho215.lee@samsung.com>
>> Signed-off-by: Sachin Kamat <sachin.kamat@linaro.org>
>> ---
>
>> diff --git a/drivers/video/backlight/lms501kf03.c
>> b/drivers/video/backlight/lms501kf03.c
>> new file mode 100644
>> index 0000000..3dc85d4
>> --- /dev/null
>> +++ b/drivers/video/backlight/lms501kf03.c
>> +static int lms501kf03_ldi_enable(struct lms501kf03 *lcd)
>> +{
>> +	int ret, i;
>> +	const unsigned short *init_seq[] = {
>> +		SEQ_DISPLAY_ON,
>> +	};
>
> Is this array expected to grow at some point?
> Otherwise I'd suggest to get rid of it and simplify the code below.

OK. I will simplify the code.

>
>> +
>> +	for (i = 0; i < ARRAY_SIZE(init_seq); i++) {
>> +		ret = lms501kf03_panel_send_sequence(lcd, init_seq[i]);
>> +		if (ret)
>> +			break;
>> +	}
>> +
>> +	return ret;
>> +}
>> +
>> +static int lms501kf03_ldi_disable(struct lms501kf03 *lcd)
>> +{
>> +	int ret, i;
>> +
>> +	const unsigned short *init_seq[] = {
>> +		SEQ_DISPLAY_OFF,
>> +	};
>
> dito

OK.

>
>> +
>> +	for (i = 0; i < ARRAY_SIZE(init_seq); i++) {
>> +		ret = lms501kf03_panel_send_sequence(lcd, init_seq[i]);
>> +		if (ret)
>> +			break;
>> +	}
>> +
>> +	return ret;
>> +}
>
>

Thank you for reviewing the code. I will re-send the patch with above
suggested changes.

> Best regards,
>
> Florian Tobias Schandinat
>


-- 
With warm regards,
Sachin

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox