/* 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 #include #include #include #include #include #include #include #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 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 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 = 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");