// SPDX-License-Identifier: (GPL-2.0+ OR MIT) /* * Copyright (c) 2025 Amlogic, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #define addr_led_reg0 0 //input data #define addr_led_reg1 1 //output data #define addr_led_reg2 2 //inversion #define addr_led_reg3 3 //direction #define MUTE_KEY 0x04 #define VOLUP_KEY 0x08 #define VOLDOWN_KEY 0x10 #define PAULSE_KEY 0x20 #define RELEASE_KEY 0x00 #define DEFAULT_SPEED 230 //default speed 230ms #define LED_DEVICE_NAME "ledring" #define LED_CHAR_DEV_NAME "aml-led" #define CMD_LEDRING_ARG 0x100001 #define CMD_LED_MUTE_ARG 0x100002 #define CMD_LED_LISTENING_ARG 0x100003 #define LED_OFF 0 #define LED_ON 1 #define MAX_NAME_LEN 50 static struct kobject *ledring_kobj; static struct i2c_client *g_client; static int m_temp = -1; static int m_index; static int timeout_flag; static int timeout; static struct _key_led { struct input_dev *pca_input_dev; char dev_name[MAX_NAME_LEN]; struct timer_list mtimer; struct work_struct list_work; int run_time; int key_num; int key_tmp_val; int key_last_val; int action_times; struct class *cls; int major; int mode; } *key_led_des; static struct _key_des { char name[MAX_NAME_LEN]; unsigned int key_val; int pin; int irq; } *exp_key; static struct _style { int num; /*the number of led*/ int times; /*1: stop after action once, 0: cycle action*/ int speed; /*speed. N(ms)*/ int timeout; /*timeout N(s)*/ int style[12][12]; /*style data*/ } style_data = { 6, 0, DEFAULT_SPEED, 0, { /*led1 led2 led3 led4 led5 led6*/ {0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, /*state1*/ {0x00, 0x00, 0x00, 0x00, 0x01, 0x00}, /*state2*/ {0x00, 0x00, 0x00, 0x01, 0x00, 0x00}, /*state3*/ {0x00, 0x00, 0x01, 0x00, 0x00, 0x00}, /*state4*/ {0x00, 0x01, 0x00, 0x00, 0x00, 0x00}, /*state5*/ {0x01, 0x00, 0x00, 0x00, 0x00, 0x00}, /*state6*/ }, }; static int leds_light(void); static int leds_init(int mode); static const struct i2c_device_id ledring_id[] = { {"aml_pca9557"}, {} }; MODULE_DEVICE_TABLE(i2c, ledring_id); static const struct of_device_id ledring_dt_ids[] = { { .compatible = "aml, ledring", .data = (void *)NULL }, {}, }; MODULE_DEVICE_TABLE(of, ledring_dt_ids); static ssize_t speed_level_write(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int ret; int m_speed; ret = kstrtouint(buf, 10, &m_speed); if (ret == 0) { pr_info("set speed level: %d ms\n", m_speed); } else { pr_info("set speed level fail, use default speed!!\n"); m_speed = DEFAULT_SPEED; } key_led_des->run_time = m_speed; schedule_work(&key_led_des->list_work); mod_timer(&key_led_des->mtimer, jiffies + key_led_des->run_time * HZ / 1000); return count; } static ssize_t run_state_read(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { pr_info("close ledring: echo 0 > runflag\n"); pr_info("open ledring: echo 1 > runflag\n"); return 0; } static ssize_t run_state_write(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int ret; int runflag; ret = kstrtouint(buf, 10, &runflag); if (ret == 0) { if (runflag == 0) { del_timer(&key_led_des->mtimer); pr_info("stop running ledring.\n"); } else if (runflag == 1) { pr_info("start running ledring.\n"); schedule_work(&key_led_des->list_work); mod_timer(&key_led_des->mtimer, jiffies + key_led_des->run_time * HZ / 1000); } } else { pr_info("set run state fail!\n"); } return count; } static ssize_t speed_level_read(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { pr_info("current speed level: %d ms\n", key_led_des->run_time); return 0; } static struct kobj_attribute running_attribute = __ATTR(runflag, 0664, run_state_read, run_state_write); static struct kobj_attribute speed_attribute = __ATTR(speed, 0664, speed_level_read, speed_level_write); static struct attribute *attrs[] = { &speed_attribute.attr, &running_attribute.attr, NULL, }; static struct attribute_group attr_group = { .attrs = attrs, }; static void mtimer_function(struct timer_list *timer) { schedule_work(&key_led_des->list_work); mod_timer(&key_led_des->mtimer, jiffies + key_led_des->run_time * HZ / 1000); } static void list_work_func(struct work_struct *work) { int ret; int key_val = 0; if (key_led_des->mode == 0) { if (timeout_flag == 0) { leds_light(); } else { timeout--; if (timeout <= 0) { timeout = 0; del_timer(&key_led_des->mtimer); ret = i2c_smbus_write_byte_data(g_client, addr_led_reg1, 0); if (ret < 0) pr_err("led set reg1 fail!\n"); } else { leds_light(); } } } else if (key_led_des->mode == 1) { key_val = i2c_smbus_read_byte_data(g_client, addr_led_reg0) & 0x3C; if (key_led_des->key_tmp_val != key_val) { key_led_des->key_tmp_val = key_val; if (key_val != 0) key_led_des->key_last_val = key_val; switch (key_led_des->key_last_val) { case MUTE_KEY: if (key_val != 0) { pr_info("key \"%s\" down\n", exp_key[0].name); input_event(key_led_des->pca_input_dev, EV_KEY, exp_key[0].key_val, 1); } else { pr_info("key \"%s\" up\n", exp_key[0].name); input_event(key_led_des->pca_input_dev, EV_KEY, exp_key[0].key_val, 0); } input_event(key_led_des->pca_input_dev, EV_SYN, 0, 0); break; case VOLUP_KEY: if (key_val != 0) { pr_info("key \"%s\" down\n", exp_key[2].name); input_event(key_led_des->pca_input_dev, EV_KEY, exp_key[2].key_val, 1); } else { pr_info("key \"%s\" up\n", exp_key[2].name); input_event(key_led_des->pca_input_dev, EV_KEY, exp_key[2].key_val, 0); } input_event(key_led_des->pca_input_dev, EV_SYN, 0, 0); break; case VOLDOWN_KEY: if (key_val != 0) { pr_info("key \"%s\" down\n", exp_key[3].name); input_event(key_led_des->pca_input_dev, EV_KEY, exp_key[3].key_val, 1); } else { pr_info("key \"%s\" up\n", exp_key[3].name); input_event(key_led_des->pca_input_dev, EV_KEY, exp_key[3].key_val, 0); } input_event(key_led_des->pca_input_dev, EV_SYN, 0, 0); break; case PAULSE_KEY: if (key_val != 0) { pr_info("key \"%s\" down\n", exp_key[1].name); input_event(key_led_des->pca_input_dev, EV_KEY, exp_key[1].key_val, 1); } else { pr_info("key \"%s\" up\n", exp_key[1].name); input_event(key_led_des->pca_input_dev, EV_KEY, exp_key[1].key_val, 0); } input_event(key_led_des->pca_input_dev, EV_SYN, 0, 0); break; default: pr_info("invalid key, please check!\n"); break; } } } } static int setup_timer_task(int mode) { timer_setup(&key_led_des->mtimer, mtimer_function, mode); mod_timer(&key_led_des->mtimer, jiffies + key_led_des->run_time * HZ / 1000); INIT_WORK(&key_led_des->list_work, list_work_func); return 0; } static int leds_init(int mode) { int ret; if (mode == 0) { ret = i2c_smbus_write_byte_data(g_client, addr_led_reg3, 0); if (ret < 0) { pr_err("led init reg3 fail!\n"); return -1; } ret = i2c_smbus_write_byte_data(g_client, addr_led_reg1, 0); if (ret < 0) { pr_err("led init reg1 fail!\n"); return -1; } } else if (mode == 1) { ret = i2c_smbus_write_byte_data(g_client, addr_led_reg3, 0x3F); if (ret < 0) { pr_err("led init reg3 fail!\n"); return -1; } ret = i2c_smbus_write_byte_data(g_client, addr_led_reg1, 0xC0); if (ret < 0) { pr_err("led init reg1 fail!\n"); return -1; } } else { pr_info("%s fail,%d mode is not support!\n", __func__, mode); } return 0; } static int leds_light(void) { int ret = 0; int i = 0; int temp = 0; for (i = 0; i < style_data.num; i++) { temp |= style_data.style[m_index][i] << (style_data.num - i + 1); } if (m_temp != temp) { ret = i2c_smbus_write_byte_data(g_client, addr_led_reg1, temp); if (ret < 0) pr_err("led lit fail!\n"); m_temp = temp; } if (++m_index > style_data.num - 1) { if (style_data.times != 0) { if (--key_led_des->action_times <= 0) del_timer(&key_led_des->mtimer); } m_index = 0; } return 0; } static long leds_ioctl(struct file *file, unsigned int cmd, unsigned long args) { int ret, val; switch (cmd) { case CMD_LEDRING_ARG: m_index = 0; del_timer(&key_led_des->mtimer); ret = i2c_smbus_write_byte_data(g_client, addr_led_reg1, 0); if (ret < 0) pr_err("led set reg1 fail!\n"); ret = copy_from_user(&style_data, (int *)args, sizeof(style_data)); if (style_data.speed < 0) { pr_info("set speed level fail,use default speed!!\n"); key_led_des->run_time = DEFAULT_SPEED; } else if (key_led_des->run_time != style_data.speed) { key_led_des->run_time = style_data.speed; } if (style_data.timeout != 0) { timeout_flag = 1; timeout = style_data.timeout * 1000 / key_led_des->run_time; } else { timeout_flag = 0; } if (style_data.times < 0) style_data.times = 0; key_led_des->action_times = style_data.times; schedule_work(&key_led_des->list_work); mod_timer(&key_led_des->mtimer, jiffies + key_led_des->run_time * HZ / 1000); break; case CMD_LED_MUTE_ARG: val = i2c_smbus_read_byte_data(g_client, addr_led_reg1); if (args == LED_OFF) { ret = i2c_smbus_write_byte_data(g_client, addr_led_reg1, (1 << 7) | val); if (ret < 0) pr_err("led init reg1 fail!\n"); } else if (args == LED_ON) { ret = i2c_smbus_write_byte_data(g_client, addr_led_reg1, ~(1 << 7) & val); if (ret < 0) pr_err("led init reg1 fail!\n"); } break; case CMD_LED_LISTENING_ARG: val = i2c_smbus_read_byte_data(g_client, addr_led_reg1); if (args == LED_OFF) { ret = i2c_smbus_write_byte_data(g_client, addr_led_reg1, (1 << 6) | val); if (ret < 0) pr_err("led init reg1 fail!\n"); } else if (args == LED_ON) { ret = i2c_smbus_write_byte_data(g_client, addr_led_reg1, ~(1 << 6) & val); if (ret < 0) pr_err("led init reg1 fail!\n"); } break; default: break; } return 0; } static ssize_t leds_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) { unsigned long ret; ret = copy_to_user(buf, &key_led_des->mode, sizeof(key_led_des->mode)); return count; } static const struct file_operations led_fops = { .owner = THIS_MODULE, .read = leds_read, .compat_ioctl = leds_ioctl, .unlocked_ioctl = leds_ioctl, }; static int ledring_parse_child_dt(const struct device *dev, int *mode) { int ret, cnt; int m_mode = 0; const char *uname; if (dev->of_node) { ret = of_property_read_u32(dev->of_node, "mode", &m_mode); if (ret) { pr_err("does not have a valid mode property\n"); return -EINVAL; } *mode = m_mode; ret = of_property_read_string(dev->of_node, "key_dev_name", &uname); if (ret) { pr_err("does not have a valid key_dev_name property\n"); return -EINVAL; } strncpy(key_led_des->dev_name, uname, MAX_NAME_LEN); if (m_mode == 1) { ret = of_property_read_u32(dev->of_node, "key_num", &key_led_des->key_num); if (ret) { pr_err("failed to get key_num!\n"); return -EINVAL; } exp_key = kzalloc(sizeof(*exp_key) * key_led_des->key_num, GFP_KERNEL); if (!exp_key) return -ENOMEM; for (cnt = 0; cnt < key_led_des->key_num; cnt++) { ret = of_property_read_string_index(dev->of_node, "key_name", cnt, &uname); if (ret < 0) { pr_err("invalid key name index[%d]\n", cnt); return -EINVAL; } strncpy(exp_key[cnt].name, uname, MAX_NAME_LEN); ret = of_property_read_u32_index(dev->of_node, "key_value", cnt, &exp_key[cnt].key_val); if (ret < 0) { pr_err("invalid key code index[%d]\n", cnt); return -EINVAL; } } } return 0; } return -EINVAL; } static int ledring_probe(struct i2c_client *client) { int ret, i; int try_times = 0; struct device *dev = &client->dev; g_client = client; pr_info("%s\n", __func__); key_led_des = devm_kzalloc(dev, sizeof(struct _key_led), GFP_KERNEL); if (!key_led_des) return -ENOMEM; ret = ledring_parse_child_dt(dev, &key_led_des->mode); if (ret < 0) { pr_err("%s,ledring_parse_child_dt fail!\n", __func__); return -EINVAL; } if (key_led_des->mode == 0) { ledring_kobj = kobject_create_and_add("ledring", kernel_kobj); if (!ledring_kobj) return -ENOMEM; ret = sysfs_create_group(ledring_kobj, &attr_group); if (ret) goto err1; key_led_des->run_time = DEFAULT_SPEED; for (i = 0; i < 3; i++) { ret = leds_init(key_led_des->mode); if (ret != 0) { if (++try_times >= 3) goto err1; } else { break; } } } else { for (i = 0; i < 3; i++) { ret = leds_init(key_led_des->mode); if (ret != 0) { if (++try_times >= 3) goto err3; } else { break; } } key_led_des->pca_input_dev = input_allocate_device(); if (!key_led_des->pca_input_dev) { pr_err("input_allocate_device err!\n"); goto err3; } set_bit(EV_SYN, key_led_des->pca_input_dev->evbit); set_bit(EV_KEY, key_led_des->pca_input_dev->evbit); for (i = 0; i < key_led_des->key_num; i++) set_bit(exp_key[i].key_val, key_led_des->pca_input_dev->keybit); key_led_des->pca_input_dev->name = key_led_des->dev_name; ret = input_register_device(key_led_des->pca_input_dev); if (ret != 0) { pr_err("input_register_device err!\n"); goto err2; } key_led_des->run_time = 100; //200ms key_led_des->key_tmp_val = 0; } setup_timer_task(key_led_des->mode); key_led_des->major = register_chrdev(0, LED_CHAR_DEV_NAME, &led_fops); key_led_des->cls = class_create(LED_DEVICE_NAME); device_create(key_led_des->cls, NULL, MKDEV(key_led_des->major, 0), NULL, LED_DEVICE_NAME); return 0; err1: kobject_put(ledring_kobj); return -ENOMEM; err2: input_free_device(key_led_des->pca_input_dev); return -ENOMEM; err3: return -ENOMEM; } static void ledring_remove(struct i2c_client *client) { pr_info("%s\n", __func__); device_destroy(key_led_des->cls, MKDEV(key_led_des->major, 0)); class_destroy(key_led_des->cls); unregister_chrdev(key_led_des->major, LED_CHAR_DEV_NAME); flush_work(&key_led_des->list_work); del_timer(&key_led_des->mtimer); if (key_led_des->mode == 0) { kobject_put(ledring_kobj); } else { input_unregister_device(key_led_des->pca_input_dev); input_free_device(key_led_des->pca_input_dev); } } static int ledring_resume(struct device *dev) { pr_info("enter %s\n", __func__); schedule_work(&key_led_des->list_work); mod_timer(&key_led_des->mtimer, jiffies + key_led_des->run_time * HZ / 1000); return 0; } static int ledring_suspend(struct device *dev) { int ret; pr_info("enter %s\n", __func__); del_timer(&key_led_des->mtimer); ret = i2c_smbus_write_byte_data(g_client, addr_led_reg1, 0); if (ret < 0) pr_err("led set reg1 fail!\n"); return 0; } static const struct dev_pm_ops ledring_pm = { .suspend = ledring_suspend, .resume = ledring_resume, }; static struct i2c_driver ledring_drv = { .driver = { .name = "aml_ledring", .owner = THIS_MODULE, .of_match_table = ledring_dt_ids, .pm = &ledring_pm, }, .probe = ledring_probe, .remove = ledring_remove, .id_table = ledring_id, }; int aml_pca9557_init(void) { return i2c_add_driver(&ledring_drv); } void __exit aml_pca9557_exit(void) { i2c_del_driver(&ledring_drv); }