411 lines
10 KiB
C
411 lines
10 KiB
C
/* Copyright (c) 2012, The Linux Foundation. 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");
|