linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 10/28] leds-lp55xx: support firmware interface for LED patterns
@ 2012-10-05  8:15 Kim, Milo
  0 siblings, 0 replies; only message in thread
From: Kim, Milo @ 2012-10-05  8:15 UTC (permalink / raw)
  To: Bryan Wu; +Cc: Richard Purdie, linux-leds, linux-kernel

 This patch provides additional device attributes which enable
 loading the firmware. ('select_engine' and 'run_engine')

 LP55xx family chips have internal program memory which run various patterns.
 Using this memory, LEDs continue on blinking/dimming without continuous I2C
 commands. That means the I2C HOST can be entered into sleep once the memory
 is updated.

 An application can get program data from a file or raw hex data in code,
 and write data into the program memory through the I2C.

 LP55xx chips have three program engines.
 To load and run the pattern, the programming sequence is as follows.
 (1) Select an engine number (1/2/3)
 (2) Set engine mode to load
 (3) Write pattern data into selected area
 (4) Set engine mode to run

 This sequence is almost same as the firmware interface.
 (1) Select an engine number               : 'select_engine' dev attribute
 (2) Mode change to load                   : 'loading' of firmware class
 (3) Write pattern data into selected area : 'data' of firmware class
 (4) Mode change to run                    : 'run_engine' dev attribute

 For example,
 echo 1 or 2 or 3 > /sys/class/leds/<name>/device/select_engine
 echo 1 > /sys/class/firmware/<name>/loading
 echo HEX STREAM DATA > /sys/class/firmware/<name>/data
 echo 0 > /sys/class/firmware/<name>/loading
 echo 1 > /sys/class/leds/<name>/device/run_engine

 As soon as 'loading' is set to 0, registered callback is called.
 Inside the callback, the selected engine is loaded and memory is updated.
 To run programmed pattern, 'run_engine' attribute should be enabled.

 ( Chip specific features )
 o Firmware callback : load selected engine and update program memory
 o Run engine        : change the engine mode
 Both operations depend on the device - different register address.
 Those are configurable in each driver side.

Signed-off-by: Milo(Woogyom) Kim <milo.kim@ti.com>
---
 drivers/leds/leds-lp55xx-common.c |  141 +++++++++++++++++++++++++++++++++++++
 drivers/leds/leds-lp55xx-common.h |    4 ++
 2 files changed, 145 insertions(+)

diff --git a/drivers/leds/leds-lp55xx-common.c b/drivers/leds/leds-lp55xx-common.c
index 987c699..af3fb49 100644
--- a/drivers/leds/leds-lp55xx-common.c
+++ b/drivers/leds/leds-lp55xx-common.c
@@ -11,6 +11,7 @@
  */
 
 #include <linux/delay.h>
+#include <linux/firmware.h>
 #include <linux/i2c.h>
 #include <linux/leds.h>
 #include <linux/module.h>
@@ -184,6 +185,130 @@ static int lp55xx_init_led(struct lp55xx_led *led,
 	return 0;
 }
 
+static void lp55xx_firmware_loaded(const struct firmware *fw, void *context)
+{
+	struct lp55xx_chip *chip = context;
+	struct device *dev = &chip->cl->dev;
+
+	if (!fw) {
+		dev_err(dev, "firmware request failed\n");
+		goto out;
+	}
+
+	/* handling firmware data is chip dependent */
+	mutex_lock(&chip->lock);
+
+	chip->fw = fw;
+	if (chip->cfg->firmware_cb)
+		chip->cfg->firmware_cb(chip);
+
+	mutex_unlock(&chip->lock);
+
+out:
+	/* firmware should be released for other channel use */
+	release_firmware(chip->fw);
+}
+
+static int lp55xx_request_firmware(struct lp55xx_chip *chip)
+{
+	const char *name = chip->cl->name;
+	struct device *dev = &chip->cl->dev;
+
+	return request_firmware_nowait(THIS_MODULE, true, name, dev,
+				GFP_KERNEL, chip, lp55xx_firmware_loaded);
+}
+
+static ssize_t show_engine_select(struct device *dev,
+			    struct device_attribute *attr,
+			    char *buf)
+{
+	struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
+	struct lp55xx_chip *chip = led->chip;
+
+	return sprintf(buf, "%d\n", chip->engine_idx);
+}
+
+static ssize_t store_engine_select(struct device *dev,
+			     struct device_attribute *attr,
+			     const char *buf, size_t len)
+{
+	struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
+	struct lp55xx_chip *chip = led->chip;
+	unsigned long val;
+	int ret;
+
+	if (kstrtoul(buf, 0, &val))
+		return -EINVAL;
+
+	/* select the engine to be run */
+
+	switch (val) {
+	case LP55XX_ENGINE_1:
+	case LP55XX_ENGINE_2:
+	case LP55XX_ENGINE_3:
+		mutex_lock(&chip->lock);
+		chip->engine_idx = val;
+		ret = lp55xx_request_firmware(chip);
+		mutex_unlock(&chip->lock);
+		break;
+	default:
+		dev_err(dev, "%lu: invalid engine index. (1, 2, 3)\n", val);
+		return -EINVAL;
+	}
+
+	if (ret) {
+		dev_err(dev, "request firmware err: %d\n", ret);
+		return ret;
+	}
+
+	return len;
+}
+
+static inline void lp55xx_run_engine(struct lp55xx_chip *chip, bool start)
+{
+	if (chip->cfg->run_engine)
+		chip->cfg->run_engine(chip, start);
+}
+
+static ssize_t store_engine_run(struct device *dev,
+			     struct device_attribute *attr,
+			     const char *buf, size_t len)
+{
+	struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
+	struct lp55xx_chip *chip = led->chip;
+	unsigned long val;
+
+	if (kstrtoul(buf, 0, &val))
+		return -EINVAL;
+
+	/* run or stop the selected engine */
+
+	if (val <= 0) {
+		lp55xx_run_engine(chip, false);
+		return len;
+	}
+
+	mutex_lock(&chip->lock);
+	lp55xx_run_engine(chip, true);
+	mutex_unlock(&chip->lock);
+
+	return len;
+}
+
+static DEVICE_ATTR(select_engine, S_IRUGO | S_IWUSR,
+		   show_engine_select, store_engine_select);
+static DEVICE_ATTR(run_engine, S_IWUSR, NULL, store_engine_run);
+
+static struct attribute *lp55xx_dev_attributes[] = {
+	&dev_attr_select_engine.attr,
+	&dev_attr_run_engine.attr,
+	NULL,
+};
+
+static const struct attribute_group lp55xx_dev_attr_group = {
+	.attrs = lp55xx_dev_attributes,
+};
+
 int lp55xx_write(struct lp55xx_chip *chip, u8 reg, u8 val)
 {
 	return i2c_smbus_write_byte_data(chip->cl, reg, val);
@@ -360,3 +485,19 @@ void lp55xx_unregister_leds(struct lp55xx_led *led, struct lp55xx_chip *chip)
 	}
 }
 EXPORT_SYMBOL_GPL(lp55xx_unregister_leds);
+
+int lp55xx_register_sysfs(struct lp55xx_chip *chip)
+{
+	struct device *dev = &chip->cl->dev;
+
+	return sysfs_create_group(&dev->kobj, &lp55xx_dev_attr_group);
+}
+EXPORT_SYMBOL_GPL(lp55xx_register_sysfs);
+
+void lp55xx_unregister_sysfs(struct lp55xx_chip *chip)
+{
+	struct device *dev = &chip->cl->dev;
+
+	sysfs_remove_group(&dev->kobj, &lp55xx_dev_attr_group);
+}
+EXPORT_SYMBOL_GPL(lp55xx_unregister_sysfs);
diff --git a/drivers/leds/leds-lp55xx-common.h b/drivers/leds/leds-lp55xx-common.h
index 66d7039..b5b7b3e 100644
--- a/drivers/leds/leds-lp55xx-common.h
+++ b/drivers/leds/leds-lp55xx-common.h
@@ -131,4 +131,8 @@ extern int lp55xx_register_leds(struct lp55xx_led *led,
 extern void lp55xx_unregister_leds(struct lp55xx_led *led,
 				struct lp55xx_chip *chip);
 
+/* common device attributes functions */
+extern int lp55xx_register_sysfs(struct lp55xx_chip *chip);
+extern void lp55xx_unregister_sysfs(struct lp55xx_chip *chip);
+
 #endif /* __LINUX_LP55XX_COMMON_H */
-- 
1.7.9.5


Best Regards,
Milo



^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2012-10-05  8:15 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2012-10-05  8:15 [PATCH 10/28] leds-lp55xx: support firmware interface for LED patterns Kim, Milo

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).