/* * Copyright (c) 2017 Sebastian Reichel * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 or * later 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 CPCAP_LED_NO_CURRENT 0x0001 struct cpcap_led_info { u16 reg; u16 mask; u16 limit; u16 init_mask; u16 init_val; }; static const struct cpcap_led_info cpcap_led_red = { .reg = CPCAP_REG_REDC, .mask = 0x03FF, .limit = 31, }; static const struct cpcap_led_info cpcap_led_green = { .reg = CPCAP_REG_GREENC, .mask = 0x03FF, .limit = 31, }; static const struct cpcap_led_info cpcap_led_blue = { .reg = CPCAP_REG_BLUEC, .mask = 0x03FF, .limit = 31, }; /* aux display light */ static const struct cpcap_led_info cpcap_led_adl = { .reg = CPCAP_REG_ADLC, .mask = 0x000F, .limit = 1, .init_mask = 0x7FFF, .init_val = 0x5FF0, }; /* camera privacy led */ static const struct cpcap_led_info cpcap_led_cp = { .reg = CPCAP_REG_CLEDC, .mask = 0x0007, .limit = 1, .init_mask = 0x03FF, .init_val = 0x0008, }; struct cpcap_led { struct led_classdev led; const struct cpcap_led_info *info; struct device *dev; struct regmap *regmap; struct mutex update_lock; struct regulator *vdd; bool powered; u32 current_limit; }; static u16 cpcap_led_val(u8 current_limit, u8 duty_cycle) { current_limit &= 0x1f; /* 5 bit */ duty_cycle &= 0x0f; /* 4 bit */ return current_limit << 4 | duty_cycle; } static int cpcap_led_set_power(struct cpcap_led *led, bool status) { int err; if (status == led->powered) return 0; if (status) err = regulator_enable(led->vdd); else err = regulator_disable(led->vdd); if (err) { dev_err(led->dev, "regulator failure: %d", err); return err; } led->powered = status; return 0; } static int cpcap_led_set(struct led_classdev *ledc, enum led_brightness value) { struct cpcap_led *led = container_of(ledc, struct cpcap_led, led); int brightness; int err; mutex_lock(&led->update_lock); if (value > LED_OFF) { err = cpcap_led_set_power(led, true); if (err) goto exit; } if (value == LED_OFF) { /* Avoid HW issue by turning off current before duty cycle */ err = regmap_update_bits(led->regmap, led->info->reg, led->info->mask, CPCAP_LED_NO_CURRENT); if (err) { dev_err(led->dev, "regmap failed: %d", err); goto exit; } brightness = cpcap_led_val(value, LED_OFF); } else { brightness = cpcap_led_val(value, LED_ON); } err = regmap_update_bits(led->regmap, led->info->reg, led->info->mask, brightness); if (err) { dev_err(led->dev, "regmap failed: %d", err); goto exit; } if (value == LED_OFF) { err = cpcap_led_set_power(led, false); if (err) goto exit; } exit: mutex_unlock(&led->update_lock); return err; } static const struct of_device_id cpcap_led_of_match[] = { { .compatible = "motorola,cpcap-led-red", .data = &cpcap_led_red }, { .compatible = "motorola,cpcap-led-green", .data = &cpcap_led_green }, { .compatible = "motorola,cpcap-led-blue", .data = &cpcap_led_blue }, { .compatible = "motorola,cpcap-led-adl", .data = &cpcap_led_adl }, { .compatible = "motorola,cpcap-led-cp", .data = &cpcap_led_cp }, {}, }; MODULE_DEVICE_TABLE(of, cpcap_led_of_match); static int cpcap_led_probe(struct platform_device *pdev) { const struct of_device_id *match; struct cpcap_led *led; int err; match = of_match_device(of_match_ptr(cpcap_led_of_match), &pdev->dev); if (!match || !match->data) return -EINVAL; led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL); if (!led) return -ENOMEM; platform_set_drvdata(pdev, led); led->info = match->data; led->dev = &pdev->dev; if (led->info->reg == 0x0000) { dev_err(led->dev, "Unsupported LED"); return -ENODEV; } led->regmap = dev_get_regmap(pdev->dev.parent, NULL); if (!led->regmap) return -ENODEV; led->vdd = devm_regulator_get(&pdev->dev, "vdd"); if (IS_ERR(led->vdd)) { err = PTR_ERR(led->vdd); dev_err(led->dev, "Couldn't get regulator: %d", err); return err; } err = device_property_read_string(&pdev->dev, "label", &led->led.name); if (err) { dev_err(led->dev, "Couldn't read LED label: %d", err); return err; } if (led->info->init_mask) { err = regmap_update_bits(led->regmap, led->info->reg, led->info->init_mask, led->info->init_val); if (err) { dev_err(led->dev, "regmap failed: %d", err); return err; } } mutex_init(&led->update_lock); led->led.max_brightness = led->info->limit; led->led.brightness_set_blocking = cpcap_led_set; err = devm_led_classdev_register(&pdev->dev, &led->led); if (err) { dev_err(led->dev, "Couldn't register LED: %d", err); return err; } return 0; } static struct platform_driver cpcap_led_driver = { .probe = cpcap_led_probe, .driver = { .name = "cpcap-led", .of_match_table = cpcap_led_of_match, }, }; module_platform_driver(cpcap_led_driver); MODULE_DESCRIPTION("CPCAP LED driver"); MODULE_AUTHOR("Sebastian Reichel "); MODULE_LICENSE("GPL");