Hi,

here's a driver for the National Semiconductor LP3944 FunLight Chip,
I wrote it for the OpenEZX project (http://www.openezx.org).

Can you please give some feedback about its status?

I2C driver for National Semiconductor LP3944 Funlight Chip
http://www.national.com/pf/LP/LP3944.html

This helper chip can drive up to 8 leds, with two programmable dim modes;
it could even be used as a gpio expander but this driver assumes it is used
as a led controller.

The dim modes are used to set _blink_ patterns for leds, the pattern is
specified supplying two parameters:
  - period: from 0s to 1.6s
  - duty cycle: percentage of the period the led is on, from 0 to 100

LP3944 can be found on Motorola A910 smartphone, where it drives the rgb
leds, the camera flash light and the displays backlights.

Signed-off-by: Antonio Ospite <[EMAIL PROTECTED]>

Index: linux-2.6.26-rc5/drivers/i2c/chips/lp3944.c
===================================================================
--- /dev/null
+++ linux-2.6.26-rc5/drivers/i2c/chips/lp3944.c
@@ -0,0 +1,447 @@
+/*
+ * lp3944.c - driver for National Semiconductor LP3944 Funlight Chip
+ *
+ * Copyright (C) 2008 Antonio Ospite <[EMAIL PROTECTED]>
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * I2C driver for National Semiconductor LP3944 Funlight Chip
+ * http://www.national.com/pf/LP/LP3944.html
+ *
+ * This helper chip can drive up to 8 leds, with two programmable DIM modes;
+ * it could even be used as a gpio expander but this driver assumes it is used
+ * as a led controller.
+ *
+ * The DIM modes are used to set _blink_ patterns for leds, the pattern is
+ * specified supplying two parameters:
+ *   - period: from 0s to 1.6s
+ *   - duty cycle: percentage of the period the led is on, from 0 to 100
+ *
+ * LP3944 can be found on Motorola A910 smartphone, where it drives the rgb
+ * leds, the camera flash light and the displays backlights.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/i2c/lp3944.h>
+
+#define LP3944_REG_INPUT1      0x00    /* LEDs 0-7 InputRegister (Read Only) */
+#define LP3944_REG_REGISTER1   0x01    /* None (Read Only) */
+#define LP3944_REG_PSC0                0x02    /* Frequency Prescaler 0 (R/W) 
*/
+#define LP3944_REG_PWM0                0x03    /* PWM Register 0 (R/W) */
+#define LP3944_REG_PSC1                0x04    /* Frequency Prescaler 1 (R/W) 
*/
+#define LP3944_REG_PWM1                0x05    /* PWM Register 1 (R/W) */
+#define LP3944_REG_LS0         0x06    /* LEDs 0-3 Selector (R/W) */
+#define LP3944_REG_LS1         0x07    /* LEDs 4-7 Selector (R/W) */
+#define LP3944_REG_REGISTER8   0x08    /* None (R/W) */
+#define LP3944_REG_REGISTER9   0x09    /* None (R/W) */
+
+#define LP3944_LED_STATUS_MASK 0x03
+
+/* Saved data */
+struct lp3944_data {
+       struct i2c_client *client;
+       struct mutex lock;
+
+       /* Only regs from 2 to 9 are R/W */
+       u8 LP3944_REGS[8];
+};
+
+static struct lp3944_data *lp3944;
+
+static int lp3944_reg_read(struct i2c_client *client, unsigned reg,
+                          unsigned *value);
+static int lp3944_reg_write(struct i2c_client *client, unsigned reg,
+                           unsigned value);
+
+/*
+ * Set the period in DIM status
+ *
+ * @dim: LP3944_DIM0 | LP3944_DIM1
+ * @period: period of a blink, that is a on/off cycle, in 1/10 sec
+ */
+int lp3944_dim_set_period(unsigned dim, unsigned period)
+{
+       unsigned psc_reg;
+       unsigned psc_value;
+       int err;
+
+       if (dim == LP3944_DIM0)
+               psc_reg = LP3944_REG_PSC0;
+       else if (dim == LP3944_DIM1)
+               psc_reg = LP3944_REG_PSC1;
+       else
+               return -EINVAL;
+
+       /* Convert period to Prescaler value */
+       if (period > LP3944_PERIOD_MAX)
+               return -EINVAL;
+
+       if (period == LP3944_PERIOD_MIN)
+               psc_value = 0;
+       else
+               psc_value = (period * LP3944_PERIOD_MAX) - 1;
+
+       mutex_lock(&lp3944->lock);
+       err = lp3944_reg_write(lp3944->client, psc_reg, psc_value);
+       mutex_unlock(&lp3944->lock);
+
+       return err;
+}
+EXPORT_SYMBOL_GPL(lp3944_dim_set_period);
+
+/*
+ * Set the duty cycle in DIM status
+ *
+ * @dim: LP3944_DIM0 | LP3944_DIM1
+ * @duty_cycle: percentage of a period in which a led is ON
+ */
+int lp3944_dim_set_dutycycle(unsigned dim, unsigned duty_cycle)
+{
+       unsigned pwm_reg;
+       unsigned pwm_value;
+       int err;
+
+       if (dim == LP3944_DIM0)
+               pwm_reg = LP3944_REG_PWM0;
+       else if (dim == LP3944_DIM1)
+               pwm_reg = LP3944_REG_PWM1;
+       else
+               return -EINVAL;
+
+       /* Convert duty cycle to PWM value */
+       if (duty_cycle > LP3944_DUTY_CYCLE_MAX)
+               return -EINVAL;
+
+       if (duty_cycle == LP3944_DUTY_CYCLE_MAX)
+               pwm_value = 255;
+       else
+               pwm_value = (duty_cycle * 256) / 100;
+
+       mutex_lock(&lp3944->lock);
+       err = lp3944_reg_write(lp3944->client, pwm_reg, pwm_value);
+       mutex_unlock(&lp3944->lock);
+
+       return err;
+}
+EXPORT_SYMBOL_GPL(lp3944_dim_set_dutycycle);
+
+/*
+ * Set the led status
+ *
+ * @led: LP3944_LED0-LP3944_LED7
+ * @status: off, on, dim0, dim1
+ */
+int lp3944_led_set(unsigned led, unsigned status)
+{
+       unsigned reg;
+       unsigned val;
+       int err;
+
+       switch (led) {
+       case LP3944_LED0:
+       case LP3944_LED1:
+       case LP3944_LED2:
+       case LP3944_LED3:
+               reg = LP3944_REG_LS0;
+               break;
+       case LP3944_LED4:
+       case LP3944_LED5:
+       case LP3944_LED6:
+       case LP3944_LED7:
+               led -= LP3944_LED4;
+               reg = LP3944_REG_LS1;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       if (status > LP3944_LED_STATUS_DIM1)
+               return -EINVAL;
+
+       mutex_lock(&lp3944->lock);
+       lp3944_reg_read(lp3944->client, reg, &val);
+
+       val &= ~(LP3944_LED_STATUS_MASK << (led << 1));
+       val |= (status << (led << 1));
+
+       pr_debug("lp3944_led_set: led %d, status %d, val: 0x%02x\n",
+                led, status, val);
+
+       /* set led status */
+       err = lp3944_reg_write(lp3944->client, reg, val);
+       mutex_unlock(&lp3944->lock);
+
+       return err;
+}
+EXPORT_SYMBOL_GPL(lp3944_led_set);
+
+/* low-level sysfs interface */
+static ssize_t lp3944_get_reg(struct device *dev,
+                             struct device_attribute *attr, char *buf)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       unsigned reg;
+       unsigned val;
+       int ret = 0;
+
+       /* line length = strlen("0x00 0x00\n") */
+       const int l = 10;
+
+       for (reg = 0; reg <= LP3944_REG_REGISTER9; reg++) {
+               /*
+                * we could even use lp3944 auto-increment feature here, but
+                * that's not very important.
+                */
+               lp3944_reg_read(client, reg, &val);
+               ret += sprintf(buf + l * reg, "0x%02x 0x%02x\n", reg, val);
+       }
+
+       return ret;
+}
+
+static ssize_t lp3944_set_reg(struct device *dev,
+                             struct device_attribute *attr, const char *buf,
+                             size_t count)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       unsigned reg;
+       unsigned val;
+
+       if (sscanf(buf, "0x%2x 0x%2x", &reg, &val) != 2)
+               return -EINVAL;
+
+       if (reg > LP3944_REG_REGISTER9)
+               return -EINVAL;
+
+       if (val > 0xff)
+               return -EINVAL;
+
+       lp3944_reg_write(client, reg, val);
+
+       return count;
+}
+
+static DEVICE_ATTR(reg, S_IWUSR | S_IRUGO, lp3944_get_reg, lp3944_set_reg);
+
+static struct attribute *lp3944_attributes[] = {
+       &dev_attr_reg.attr,
+       NULL
+};
+
+static const struct attribute_group lp3944_attr_group = {
+       .attrs = lp3944_attributes,
+};
+
+/* private stuff */
+
+static int lp3944_reg_read(struct i2c_client *client, unsigned reg,
+                          unsigned *value)
+{
+       int tmp;
+
+       tmp = i2c_smbus_read_byte_data(client, reg);
+       if (tmp < 0)
+               return -EINVAL;
+
+       *value = (unsigned) tmp;
+       pr_debug("lp3944_reg_read: reg: 0x%02x value: 0x%02x\n", reg, *value);
+
+       return 0;
+}
+
+static int lp3944_reg_write(struct i2c_client *client, unsigned reg,
+                           unsigned value)
+{
+       unsigned tmp;
+       int ret;
+
+       tmp = i2c_smbus_read_byte_data(client, reg);
+       pr_debug("lp3944_reg_write: before write reg: 0x%02x value: 0x%02x\n",
+                reg, tmp);
+
+       ret = i2c_smbus_write_byte_data(client, reg, value);
+
+       tmp = i2c_smbus_read_byte_data(client, reg);
+       pr_debug("lp3944_reg_write: after write reg: 0x%02x value: 0x%02x\n",
+                reg, tmp);
+
+       return ret;
+}
+
+static int lp3944_init_client(struct i2c_client *client)
+{
+       struct lp3944_data *data = i2c_get_clientdata(client);
+       int tmp;
+
+       /* Try to read a byte, if it fails, return an error */
+       tmp = i2c_smbus_read_byte_data(client, LP3944_REG_INPUT1);
+       if (tmp < 0)
+               return -ENODEV;
+
+       /* Default values, taken from official datasheet */
+       lp3944_reg_write(client, LP3944_REG_PSC0, 0x00);
+       lp3944_reg_write(client, LP3944_REG_PWM0, 0x80);
+       lp3944_reg_write(client, LP3944_REG_PSC1, 0x00);
+       lp3944_reg_write(client, LP3944_REG_PWM1, 0x80);
+       lp3944_reg_write(client, LP3944_REG_LS0, 0x00);
+       lp3944_reg_write(client, LP3944_REG_LS1, 0x00);
+
+       data->LP3944_REGS[0] = 0x00;
+       data->LP3944_REGS[1] = 0x80;
+       data->LP3944_REGS[2] = 0x00;
+       data->LP3944_REGS[3] = 0x80;
+       data->LP3944_REGS[4] = 0x00;
+       data->LP3944_REGS[5] = 0x00;
+
+       pr_debug("lp3944_init_client\n");
+
+       return 0;
+}
+
+static int __devinit lp3944_probe(struct i2c_client *client)
+{
+       struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+       struct lp3944_data *data;
+       int err;
+
+       dev_dbg(&client->dev, "adapter %s!\n", adapter->name);
+
+       if (lp3944) {
+               dev_dbg(&client->dev, "only one lp3944 chip allowed.\n");
+               return -ENODEV;
+       }
+
+       /* Let's see whether this adapter can support what we need. */
+       if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
+               printk(KERN_ERR "insufficient functionality!\n");
+               err = -EIO;
+               goto exit;
+       }
+
+       data = kzalloc(sizeof(struct lp3944_data), GFP_KERNEL);
+       if (!data) {
+               err = -ENOMEM;
+               goto exit;
+       }
+       data->client = client;
+       i2c_set_clientdata(client, data);
+
+       mutex_init(&data->lock);
+
+       /* Initialize the lp3944 chip */
+       err = lp3944_init_client(client);
+       if (err)
+               goto exit_kfree;
+
+       /* Register sysfs hooks */
+       err = sysfs_create_group(&client->dev.kobj, &lp3944_attr_group);
+       if (err)
+               goto exit_kfree;
+
+       lp3944 = data;
+       dev_info(&client->dev, "lp3944 enabled\n");
+
+       return 0;
+
+exit_kfree:
+       kfree(data);
+exit:
+       return err;
+}
+
+static int __devexit lp3944_remove(struct i2c_client *client)
+{
+       sysfs_remove_group(&client->dev.kobj, &lp3944_attr_group);
+
+       lp3944_reg_write(client, LP3944_REG_LS0, 0x00);
+       lp3944_reg_write(client, LP3944_REG_LS1, 0x00);
+
+       kfree(i2c_get_clientdata(client));
+       return 0;
+}
+
+#ifdef CONFIG_PM
+static int lp3944_suspend(struct i2c_client *client, pm_message_t state)
+{
+       struct lp3944_data *data = i2c_get_clientdata(client);
+       int i;
+
+       dev_dbg(&client->dev, "lp3944_suspend\n");
+
+       for (i = 2; i < LP3944_REG_REGISTER9; i++)
+               data->LP3944_REGS[i - 2] = i2c_smbus_read_byte_data(client, i);
+
+       return 0;
+}
+
+static int lp3944_resume(struct i2c_client *client)
+{
+       struct lp3944_data *data = i2c_get_clientdata(client);
+       int i;
+
+       dev_dbg(&client->dev, "lp3944_resume\n");
+
+       for (i = 2; i < LP3944_REG_REGISTER9; i++)
+               i2c_smbus_write_byte_data(client, i, data->LP3944_REGS[i - 2]);
+
+       return 0;
+}
+#else
+
+#define lp3944_suspend NULL
+#define lp3944_resume NULL
+
+#endif /* CONFIG_PM */
+
+/* lp3944 i2c driver struct */
+static struct i2c_driver lp3944_driver = {
+       .driver = {
+                  .name = "lp3944",
+                  .owner = THIS_MODULE,
+                  },
+       .probe = lp3944_probe,
+       .remove = lp3944_remove,
+       .suspend = lp3944_suspend,
+       .resume = lp3944_resume,
+};
+
+static int __init lp3944_module_init(void)
+{
+       int err;
+
+       err = i2c_add_driver(&lp3944_driver);
+       if (err)
+               printk(KERN_ERR "lp3944_module_init: can't add i2c driver\n");
+
+       return err;
+}
+
+static void __exit lp3944_module_exit(void)
+{
+       i2c_del_driver(&lp3944_driver);
+}
+
+module_init(lp3944_module_init);
+module_exit(lp3944_module_exit);
+
+/* Module information */
+MODULE_AUTHOR("Antonio Ospite <[EMAIL PROTECTED]>");
+MODULE_DESCRIPTION("LP3944 Fun Light Chip");
+MODULE_LICENSE("GPL");
Index: linux-2.6.26-rc5/drivers/i2c/chips/Kconfig
===================================================================
--- linux-2.6.26-rc5.orig/drivers/i2c/chips/Kconfig
+++ linux-2.6.26-rc5/drivers/i2c/chips/Kconfig
@@ -129,6 +129,16 @@
          This driver can also be built as a module.  If so, the module
          will be called tsl2550.
 
+config LP3944
+       tristate "National Semiconductor LP3944 Fun Light Chip"
+       depends on EXPERIMENTAL
+       help
+    If you say yes here you get support for the National Semiconductor LP3944
+    Lighting Management Unit (LMU) also known as a Fun Light Chip.
+
+         This driver can also be built as a module.  If so, the module
+         will be called lp3944.
+
 config MENELAUS
        bool "TWL92330/Menelaus PM chip"
        depends on I2C=y && ARCH_OMAP24XX
Index: linux-2.6.26-rc5/drivers/i2c/chips/Makefile
===================================================================
--- linux-2.6.26-rc5.orig/drivers/i2c/chips/Makefile
+++ linux-2.6.26-rc5/drivers/i2c/chips/Makefile
@@ -20,6 +20,7 @@
 obj-$(CONFIG_TPS65010)         += tps65010.o
 obj-$(CONFIG_MENELAUS)         += menelaus.o
 obj-$(CONFIG_SENSORS_TSL2550)  += tsl2550.o
+obj-$(CONFIG_LP3944)           += lp3944.o
 
 ifeq ($(CONFIG_I2C_DEBUG_CHIP),y)
 EXTRA_CFLAGS += -DDEBUG
Index: linux-2.6.26-rc5/include/linux/i2c/lp3944.h
===================================================================
--- /dev/null
+++ linux-2.6.26-rc5/include/linux/i2c/lp3944.h
@@ -0,0 +1,54 @@
+/*
+ * lp3944.h - public interface for National Semiconductor LP3944 Funlight Chip
+ *
+ * Copyright (C) 2008 Antonio Ospite <[EMAIL PROTECTED]>
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef __LINUX_I2C_LP3944_H
+#define __LINUX_I2C_LP3944_H
+
+#define LP3944_LED0 0
+#define LP3944_LED1 1
+#define LP3944_LED2 2
+#define LP3944_LED3 3
+#define LP3944_LED4 4
+#define LP3944_LED5 5
+#define LP3944_LED6 6
+#define LP3944_LED7 7
+
+#define LP3944_DIM0 0
+#define LP3944_DIM1 1
+
+/* period in 1/10 sec */
+#define LP3944_PERIOD_MIN 0
+#define LP3944_PERIOD_MAX 16
+
+/* duty cycle is a percentage */
+#define LP3944_DUTY_CYCLE_MIN 0
+#define LP3944_DUTY_CYCLE_MAX 100
+
+/* led statuses */
+#define LP3944_LED_STATUS_OFF  0x0
+#define LP3944_LED_STATUS_ON   0x1
+#define LP3944_LED_STATUS_DIM0 0x2
+#define LP3944_LED_STATUS_DIM1 0x3
+
+extern int lp3944_dim_set_period(unsigned dim, unsigned period);
+extern int lp3944_dim_set_dutycycle(unsigned int dim, unsigned duty_cycle);
+extern int lp3944_led_set(unsigned led, unsigned status);
+
+#endif /* __LINUX_I2C_LP3944_H */
Index: linux-2.6.26-rc5/Documentation/i2c/chips/lp3944
===================================================================
--- /dev/null
+++ linux-2.6.26-rc5/Documentation/i2c/chips/lp3944
@@ -0,0 +1,101 @@
+Kernel driver lp3944
+====================
+
+  * National Semiconductor LP3944 Fun-light Chip
+    Prefix: 'lp3944'
+    Addresses scanned: None (see the Notes section below)
+    Datasheet: Publicly available at the National Semiconductor website
+               http://www.national.com/pf/LP/LP3944.html
+
+Authors:
+        Antonio Ospite <[EMAIL PROTECTED]>
+
+
+Description
+-----------
+The LP3944 is a helper chip that can drive up to 8 leds, with two programmable
+DIM modes; it could even be used as a gpio expander but this driver assumes it
+is used as a led controller.
+
+The DIM modes are used to set _blink_ patterns for leds, the pattern is
+specified supplying two parameters:
+  - period: from 0s to 1.6s
+  - duty cycle: percentage of the period the led is on, from 0 to 100
+
+Setting a led in DIM0 or DIM1 mode makes it blink according to the pattern.
+
+See the datasheet for details.
+
+LP3944 can be found on Motorola A910 smartphone, where it drives the rgb
+leds, the camera flash light and the displays backlights.
+
+
+Notes
+-----
+The chip is used mainly in embedded contextes, so this driver expects it is
+registered using the i2c_board_info mechanism.
+
+To register the chip at address 0x60 on adapter 0, set the info:
+
+       static struct i2c_board_info __initdata a910_i2c_board_info[] = {
+               {
+                       I2C_BOARD_INFO("lp3944", 0x60),
+                       .type = "lp3944",
+               },
+       };
+
+and register it in the platform init function
+
+       i2c_register_board_info(0, a910_i2c_board_info,
+                       ARRAY_SIZE(a910_i2c_board_info));
+
+
+Accessing LP3944 via exported interface
+---------------------------------------
+The driver exposes a friendly interface to abstract the chip functionalities
+for other drivers to use.
+
+Including include/linux/i2c/lp3944.h one can use these calls:
+
+extern int lp3944_dim_set_period(unsigned dim, unsigned period);
+extern int lp3944_dim_set_dutycycle(unsigned int dim, unsigned duty_cycle);
+extern int lp3944_led_set(unsigned led, unsigned status);
+
+in a leds-class driver.
+
+
+Accessing LP3944 via /sys interface
+-----------------------------------
+Standard i2c sysfs interface can be found at
+/sys/bus/i2c/devices/<0>-<1>/
+or
+/sys/bus/i2c/drivers/lp3944/<0>-<1>/
+
+where <0> is the bus the chip was registered to (e. g. i2c-0)
+and <1> the chip address.
+
+Additionally, there is a R/W 'reg' file which can be used to dump and set
+register values.
+  $ cat reg
+  0x00 0xd7
+  0x01 0x00
+  0x02 0x4f
+  0x03 0x0c
+  0x04 0x9f
+  0x05 0x80
+  0x06 0x40
+  0x07 0x00
+  0x08 0x00
+  0x09 0x00
+
+Set period and duty_cycle for DIM0 mode:
+  $ echo 0x02 0x4f > reg
+  $ echo 0x03 0x0c > reg
+
+Set LED0 mode to DIM0
+  $ echo 0x06 0x02 > reg
+
+Registers 0x08 and 0x09 are not used for led control but are still valid to
+store arbitrary bytes :)
+  $ echo 0x08 0xde > reg
+  $ echo 0x09 0xad > reg

_______________________________________________
i2c mailing list
[email protected]
http://lists.lm-sensors.org/mailman/listinfo/i2c

Reply via email to