mirror of
https://github.com/followmsi/android_kernel_google_msm.git
synced 2024-11-06 23:17:41 +00:00
leds: msm-tricolor: Add support for tricolor leds
Add support for tricolor leds on Qualcomm's MSM boards. Change-Id: Ib05d6129f98cf67e7d208465a13cc6b1300c6277 Signed-off-by: Mohan Pallaka <mpallaka@codeaurora.org>
This commit is contained in:
parent
df96902bc2
commit
f1d59b971e
4 changed files with 445 additions and 0 deletions
|
@ -181,6 +181,20 @@ config LEDS_PMIC_MPP
|
|||
To compile this driver as a module, choose M here: the
|
||||
module will be called leds-pmic-mpp.
|
||||
|
||||
config LEDS_MSM_TRICOLOR
|
||||
tristate "LED Support for Qualcomm tricolor LEDs"
|
||||
depends on LEDS_CLASS && MSM_SMD
|
||||
help
|
||||
This option enables support for tricolor LEDs connected to
|
||||
to Qualcomm reference boards. Red, green and blue color leds
|
||||
are supported. These leds are turned on/off, blink on/off
|
||||
by Modem upon receiving command through rpc from this driver.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called leds-msm-tricolor.
|
||||
|
||||
config LEDS_LP3944
|
||||
tristate "LED Support for N.S. LP3944 (Fun Light) I2C chip"
|
||||
depends on LEDS_CLASS
|
||||
|
|
|
@ -52,6 +52,7 @@ obj-$(CONFIG_LEDS_PMIC8058) += leds-pmic8058.o
|
|||
obj-$(CONFIG_LEDS_PMIC_MPP) += leds-pmic-mpp.o
|
||||
obj-$(CONFIG_LEDS_QCIBL) += leds-qci-backlight.o
|
||||
obj-$(CONFIG_LEDS_MSM_PDM) += leds-msm-pdm.o
|
||||
obj-$(CONFIG_LEDS_MSM_TRICOLOR) += leds-msm-tricolor.o
|
||||
|
||||
# LED SPI Drivers
|
||||
obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o
|
||||
|
|
410
drivers/leds/leds-msm-tricolor.c
Normal file
410
drivers/leds/leds-msm-tricolor.c
Normal file
|
@ -0,0 +1,410 @@
|
|||
/* Copyright (c) 2012, Code Aurora Forum. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/err.h>
|
||||
|
||||
#include <linux/leds-msm-tricolor.h>
|
||||
#include <mach/msm_rpcrouter.h>
|
||||
|
||||
#define LED_RPC_PROG 0x30000091
|
||||
#define LED_RPC_VER 0x00030001
|
||||
|
||||
#define LED_SUBSCRIBE_PROC 0x03
|
||||
#define LED_SUBS_RCV_EVNT 0x01
|
||||
#define LED_SUBS_REGISTER 0x00
|
||||
#define LED_EVNT_CLASS_ALL 0x00
|
||||
#define LINUX_HOST 0x04
|
||||
#define LED_CMD_PROC 0x02
|
||||
#define TRICOLOR_LED_ID 0x0A
|
||||
|
||||
enum tricolor_led_status {
|
||||
ALL_OFF,
|
||||
ALL_ON,
|
||||
BLUE_ON,
|
||||
BLUE_OFF,
|
||||
RED_ON,
|
||||
RED_OFF,
|
||||
GREEN_ON,
|
||||
GREEN_OFF,
|
||||
BLUE_BLINK,
|
||||
RED_BLINK,
|
||||
GREEN_BLINK,
|
||||
BLUE_BLINK_OFF,
|
||||
RED_BLINK_OFF,
|
||||
GREEN_BLINK_OFF,
|
||||
LED_MAX,
|
||||
};
|
||||
|
||||
struct led_cmd_data_type {
|
||||
u32 cmd_data_type_ptr; /* cmd_data_type ptr */
|
||||
u32 ver; /* version */
|
||||
u32 id; /* command id */
|
||||
u32 handle; /* handle returned from subscribe proc */
|
||||
u32 disc_id1; /* discriminator id */
|
||||
u32 input_ptr; /* input ptr length */
|
||||
u32 input_val; /* command specific data */
|
||||
u32 input_len; /* length of command input */
|
||||
u32 disc_id2; /* discriminator id */
|
||||
u32 output_len; /* length of output data */
|
||||
u32 delayed; /* execution context for modem */
|
||||
};
|
||||
|
||||
struct led_subscribe_req {
|
||||
u32 subs_ptr; /* subscribe ptr */
|
||||
u32 ver; /* version */
|
||||
u32 srvc; /* command or event */
|
||||
u32 req; /* subscribe or unsubscribe */
|
||||
u32 host_os; /* host operating system */
|
||||
u32 disc_id; /* discriminator id */
|
||||
u32 event; /* event */
|
||||
u32 cb_id; /* callback id */
|
||||
u32 handle_ptr; /* handle ptr */
|
||||
u32 handle_data; /* handle data */
|
||||
};
|
||||
|
||||
struct tricolor_led_data {
|
||||
struct led_classdev cdev;
|
||||
struct msm_rpc_client *rpc_client;
|
||||
bool blink_status;
|
||||
struct mutex lock;
|
||||
u8 color;
|
||||
};
|
||||
|
||||
static struct led_subscribe_req *led_subs_req;
|
||||
|
||||
static int led_send_cmd_arg(struct msm_rpc_client *client,
|
||||
void *buffer, void *data)
|
||||
{
|
||||
struct led_cmd_data_type *led_cmd = buffer;
|
||||
enum tricolor_led_status status = *(enum tricolor_led_status *) data;
|
||||
|
||||
led_cmd->cmd_data_type_ptr = cpu_to_be32(0x01);
|
||||
led_cmd->ver = cpu_to_be32(0x03);
|
||||
led_cmd->id = cpu_to_be32(TRICOLOR_LED_ID);
|
||||
led_cmd->handle = cpu_to_be32(led_subs_req->handle_data);
|
||||
led_cmd->disc_id1 = cpu_to_be32(TRICOLOR_LED_ID);
|
||||
led_cmd->input_ptr = cpu_to_be32(0x01);
|
||||
led_cmd->input_val = cpu_to_be32(status);
|
||||
led_cmd->input_len = cpu_to_be32(0x01);
|
||||
led_cmd->disc_id2 = cpu_to_be32(TRICOLOR_LED_ID);
|
||||
led_cmd->output_len = cpu_to_be32(0x00);
|
||||
led_cmd->delayed = cpu_to_be32(0x00);
|
||||
|
||||
return sizeof(*led_cmd);
|
||||
}
|
||||
|
||||
static int led_rpc_res(struct msm_rpc_client *client,
|
||||
void *buffer, void *data)
|
||||
{
|
||||
uint32_t result;
|
||||
|
||||
result = be32_to_cpu(*((uint32_t *)buffer));
|
||||
pr_debug("%s: request completed: 0x%x\n", __func__, result);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void led_rpc_set_status(struct msm_rpc_client *client,
|
||||
enum tricolor_led_status status)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = msm_rpc_client_req(client, LED_CMD_PROC,
|
||||
led_send_cmd_arg, &status, led_rpc_res, NULL, -1);
|
||||
if (rc)
|
||||
pr_err("%s: RPC client request for led failed", __func__);
|
||||
|
||||
}
|
||||
|
||||
static ssize_t led_blink_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct tricolor_led_data *led = dev_get_drvdata(dev);
|
||||
|
||||
return snprintf(buf, 2, "%d\n", led->blink_status);
|
||||
}
|
||||
|
||||
static ssize_t led_blink_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t size)
|
||||
{
|
||||
struct tricolor_led_data *led = dev_get_drvdata(dev);
|
||||
enum tricolor_led_status status;
|
||||
unsigned long value;
|
||||
int rc;
|
||||
|
||||
if (size > 2)
|
||||
return -EINVAL;
|
||||
|
||||
rc = kstrtoul(buf, 10, &value);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
|
||||
if (value < LED_OFF || value > led->cdev.max_brightness) {
|
||||
dev_err(dev, "invalid brightness\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (led->color) {
|
||||
case LED_COLOR_RED:
|
||||
status = value ? RED_BLINK : RED_BLINK_OFF;
|
||||
break;
|
||||
case LED_COLOR_GREEN:
|
||||
status = value ? GREEN_BLINK : GREEN_BLINK_OFF;
|
||||
break;
|
||||
case LED_COLOR_BLUE:
|
||||
status = value ? BLUE_BLINK : BLUE_BLINK_OFF;
|
||||
break;
|
||||
default:
|
||||
dev_err(dev, "unknown led device\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&led->lock);
|
||||
led->blink_status = !!value;
|
||||
led->cdev.brightness = 0;
|
||||
|
||||
/* program the led blink */
|
||||
led_rpc_set_status(led->rpc_client, status);
|
||||
mutex_unlock(&led->lock);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(blink, 0644, led_blink_show, led_blink_store);
|
||||
|
||||
static void tricolor_led_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
struct tricolor_led_data *led;
|
||||
enum tricolor_led_status status;
|
||||
|
||||
led = container_of(led_cdev, struct tricolor_led_data, cdev);
|
||||
|
||||
if (value < LED_OFF || value > led->cdev.max_brightness) {
|
||||
dev_err(led->cdev.dev, "invalid brightness\n");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (led->color) {
|
||||
case LED_COLOR_RED:
|
||||
status = value ? RED_ON : RED_OFF;
|
||||
break;
|
||||
case LED_COLOR_GREEN:
|
||||
status = value ? GREEN_ON : GREEN_OFF;
|
||||
break;
|
||||
case LED_COLOR_BLUE:
|
||||
status = value ? BLUE_ON : BLUE_OFF;
|
||||
break;
|
||||
default:
|
||||
dev_err(led->cdev.dev, "unknown led device\n");
|
||||
return;
|
||||
}
|
||||
|
||||
mutex_lock(&led->lock);
|
||||
led->blink_status = 0;
|
||||
led->cdev.brightness = value;
|
||||
|
||||
/* program the led brightness */
|
||||
led_rpc_set_status(led->rpc_client, status);
|
||||
mutex_unlock(&led->lock);
|
||||
}
|
||||
|
||||
static enum led_brightness tricolor_led_get(struct led_classdev *led_cdev)
|
||||
{
|
||||
struct tricolor_led_data *led;
|
||||
|
||||
led = container_of(led_cdev, struct tricolor_led_data, cdev);
|
||||
|
||||
return led->cdev.brightness;
|
||||
}
|
||||
|
||||
static int led_rpc_register_subs_arg(struct msm_rpc_client *client,
|
||||
void *buffer, void *data)
|
||||
{
|
||||
led_subs_req = buffer;
|
||||
|
||||
led_subs_req->subs_ptr = cpu_to_be32(0x1);
|
||||
led_subs_req->ver = cpu_to_be32(0x1);
|
||||
led_subs_req->srvc = cpu_to_be32(LED_SUBS_RCV_EVNT);
|
||||
led_subs_req->req = cpu_to_be32(LED_SUBS_REGISTER);
|
||||
led_subs_req->host_os = cpu_to_be32(LINUX_HOST);
|
||||
led_subs_req->disc_id = cpu_to_be32(LED_SUBS_RCV_EVNT);
|
||||
led_subs_req->event = cpu_to_be32(LED_EVNT_CLASS_ALL);
|
||||
led_subs_req->cb_id = cpu_to_be32(0x1);
|
||||
led_subs_req->handle_ptr = cpu_to_be32(0x1);
|
||||
led_subs_req->handle_data = cpu_to_be32(0x0);
|
||||
|
||||
return sizeof(*led_subs_req);
|
||||
}
|
||||
|
||||
static int led_cb_func(struct msm_rpc_client *client, void *buffer, int in_size)
|
||||
{
|
||||
struct rpc_request_hdr *hdr = buffer;
|
||||
int rc;
|
||||
|
||||
hdr->type = be32_to_cpu(hdr->type);
|
||||
hdr->xid = be32_to_cpu(hdr->xid);
|
||||
hdr->rpc_vers = be32_to_cpu(hdr->rpc_vers);
|
||||
hdr->prog = be32_to_cpu(hdr->prog);
|
||||
hdr->vers = be32_to_cpu(hdr->vers);
|
||||
hdr->procedure = be32_to_cpu(hdr->procedure);
|
||||
|
||||
msm_rpc_start_accepted_reply(client, hdr->xid,
|
||||
RPC_ACCEPTSTAT_SUCCESS);
|
||||
rc = msm_rpc_send_accepted_reply(client, 0);
|
||||
if (rc)
|
||||
pr_err("%s: sending reply failed: %d\n", __func__, rc);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int __devinit tricolor_led_probe(struct platform_device *pdev)
|
||||
{
|
||||
const struct led_platform_data *pdata = pdev->dev.platform_data;
|
||||
struct msm_rpc_client *rpc_client;
|
||||
struct led_info *curr_led;
|
||||
struct tricolor_led_data *led, *tmp_led;
|
||||
int rc, i, j;
|
||||
|
||||
if (!pdata) {
|
||||
dev_err(&pdev->dev, "platform data not supplied\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* initialize rpc client */
|
||||
rpc_client = msm_rpc_register_client("led", LED_RPC_PROG,
|
||||
LED_RPC_VER, 0, led_cb_func);
|
||||
rc = IS_ERR(rpc_client);
|
||||
if (rc) {
|
||||
dev_err(&pdev->dev, "failed to initialize rpc_client\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* subscribe */
|
||||
rc = msm_rpc_client_req(rpc_client, LED_SUBSCRIBE_PROC,
|
||||
led_rpc_register_subs_arg, NULL,
|
||||
led_rpc_res, NULL, -1);
|
||||
if (rc) {
|
||||
pr_err("%s: RPC client request failed for subscribe services\n",
|
||||
__func__);
|
||||
goto fail_mem_alloc;
|
||||
}
|
||||
|
||||
led = devm_kzalloc(&pdev->dev, pdata->num_leds * sizeof(*led),
|
||||
GFP_KERNEL);
|
||||
if (!led) {
|
||||
dev_err(&pdev->dev, "failed to alloc memory\n");
|
||||
rc = -ENOMEM;
|
||||
goto fail_mem_alloc;
|
||||
}
|
||||
|
||||
for (i = 0; i < pdata->num_leds; i++) {
|
||||
curr_led = &pdata->leds[i];
|
||||
tmp_led = &led[i];
|
||||
|
||||
tmp_led->cdev.name = curr_led->name;
|
||||
tmp_led->cdev.default_trigger = curr_led->default_trigger;
|
||||
tmp_led->cdev.brightness_set = tricolor_led_set;
|
||||
tmp_led->cdev.brightness_get = tricolor_led_get;
|
||||
tmp_led->cdev.brightness = LED_OFF;
|
||||
tmp_led->cdev.max_brightness = LED_FULL;
|
||||
tmp_led->color = curr_led->flags;
|
||||
tmp_led->rpc_client = rpc_client;
|
||||
tmp_led->blink_status = false;
|
||||
|
||||
mutex_init(&tmp_led->lock);
|
||||
|
||||
rc = led_classdev_register(&pdev->dev, &tmp_led->cdev);
|
||||
if (rc) {
|
||||
dev_err(&pdev->dev, "failed to register led %s(%d)\n",
|
||||
tmp_led->cdev.name, rc);
|
||||
goto fail_led_reg;
|
||||
}
|
||||
|
||||
/* Add blink attributes */
|
||||
rc = device_create_file(tmp_led->cdev.dev, &dev_attr_blink);
|
||||
if (rc) {
|
||||
dev_err(&pdev->dev, "failed to create blink attr\n");
|
||||
goto fail_blink_attr;
|
||||
}
|
||||
dev_set_drvdata(tmp_led->cdev.dev, tmp_led);
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, led);
|
||||
|
||||
return 0;
|
||||
|
||||
fail_blink_attr:
|
||||
j = i;
|
||||
while (j)
|
||||
device_remove_file(led[--j].cdev.dev, &dev_attr_blink);
|
||||
i++;
|
||||
fail_led_reg:
|
||||
while (i) {
|
||||
led_classdev_unregister(&led[--i].cdev);
|
||||
mutex_destroy(&led[i].lock);
|
||||
}
|
||||
fail_mem_alloc:
|
||||
msm_rpc_unregister_client(rpc_client);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int __devexit tricolor_led_remove(struct platform_device *pdev)
|
||||
{
|
||||
const struct led_platform_data *pdata = pdev->dev.platform_data;
|
||||
struct tricolor_led_data *led = platform_get_drvdata(pdev);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < pdata->num_leds; i++) {
|
||||
led_classdev_unregister(&led[i].cdev);
|
||||
device_remove_file(led[i].cdev.dev, &dev_attr_blink);
|
||||
mutex_destroy(&led[i].lock);
|
||||
}
|
||||
|
||||
msm_rpc_unregister_client(led->rpc_client);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver tricolor_led_driver = {
|
||||
.probe = tricolor_led_probe,
|
||||
.remove = __devexit_p(tricolor_led_remove),
|
||||
.driver = {
|
||||
.name = "msm-tricolor-leds",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init tricolor_led_init(void)
|
||||
{
|
||||
return platform_driver_register(&tricolor_led_driver);
|
||||
}
|
||||
late_initcall(tricolor_led_init);
|
||||
|
||||
static void __exit tricolor_led_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&tricolor_led_driver);
|
||||
}
|
||||
module_exit(tricolor_led_exit);
|
||||
|
||||
MODULE_DESCRIPTION("MSM Tri-color LEDs driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_VERSION("1.0");
|
||||
MODULE_ALIAS("platform:tricolor-led");
|
20
include/linux/leds-msm-tricolor.h
Normal file
20
include/linux/leds-msm-tricolor.h
Normal file
|
@ -0,0 +1,20 @@
|
|||
/* Copyright (c) 2012, Code Aurora Forum. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __LEDS_MSM_TRICOLOR__
|
||||
enum tri_color_led_color {
|
||||
LED_COLOR_RED,
|
||||
LED_COLOR_GREEN,
|
||||
LED_COLOR_BLUE,
|
||||
LED_COLOR_MAX
|
||||
};
|
||||
#endif
|
Loading…
Reference in a new issue