227 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Copyright (c) 2017 Sebastian Reichel <sre@kernel.org>4 */5 6#include <linux/leds.h>7#include <linux/mfd/motorola-cpcap.h>8#include <linux/module.h>9#include <linux/mutex.h>10#include <linux/of.h>11#include <linux/platform_device.h>12#include <linux/regmap.h>13#include <linux/regulator/consumer.h>14 15#define CPCAP_LED_NO_CURRENT 0x000116 17struct cpcap_led_info {18 u16 reg;19 u16 mask;20 u16 limit;21 u16 init_mask;22 u16 init_val;23};24 25static const struct cpcap_led_info cpcap_led_red = {26 .reg = CPCAP_REG_REDC,27 .mask = 0x03FF,28 .limit = 31,29};30 31static const struct cpcap_led_info cpcap_led_green = {32 .reg = CPCAP_REG_GREENC,33 .mask = 0x03FF,34 .limit = 31,35};36 37static const struct cpcap_led_info cpcap_led_blue = {38 .reg = CPCAP_REG_BLUEC,39 .mask = 0x03FF,40 .limit = 31,41};42 43/* aux display light */44static const struct cpcap_led_info cpcap_led_adl = {45 .reg = CPCAP_REG_ADLC,46 .mask = 0x000F,47 .limit = 1,48 .init_mask = 0x7FFF,49 .init_val = 0x5FF0,50};51 52/* camera privacy led */53static const struct cpcap_led_info cpcap_led_cp = {54 .reg = CPCAP_REG_CLEDC,55 .mask = 0x0007,56 .limit = 1,57 .init_mask = 0x03FF,58 .init_val = 0x0008,59};60 61struct cpcap_led {62 struct led_classdev led;63 const struct cpcap_led_info *info;64 struct device *dev;65 struct regmap *regmap;66 struct mutex update_lock;67 struct regulator *vdd;68 bool powered;69 70 u32 current_limit;71};72 73static u16 cpcap_led_val(u8 current_limit, u8 duty_cycle)74{75 current_limit &= 0x1f; /* 5 bit */76 duty_cycle &= 0x0f; /* 4 bit */77 78 return current_limit << 4 | duty_cycle;79}80 81static int cpcap_led_set_power(struct cpcap_led *led, bool status)82{83 int err;84 85 if (status == led->powered)86 return 0;87 88 if (status)89 err = regulator_enable(led->vdd);90 else91 err = regulator_disable(led->vdd);92 93 if (err) {94 dev_err(led->dev, "regulator failure: %d", err);95 return err;96 }97 98 led->powered = status;99 100 return 0;101}102 103static int cpcap_led_set(struct led_classdev *ledc, enum led_brightness value)104{105 struct cpcap_led *led = container_of(ledc, struct cpcap_led, led);106 int brightness;107 int err;108 109 mutex_lock(&led->update_lock);110 111 if (value > LED_OFF) {112 err = cpcap_led_set_power(led, true);113 if (err)114 goto exit;115 }116 117 if (value == LED_OFF) {118 /* Avoid HW issue by turning off current before duty cycle */119 err = regmap_update_bits(led->regmap,120 led->info->reg, led->info->mask, CPCAP_LED_NO_CURRENT);121 if (err) {122 dev_err(led->dev, "regmap failed: %d", err);123 goto exit;124 }125 126 brightness = cpcap_led_val(value, LED_OFF);127 } else {128 brightness = cpcap_led_val(value, LED_ON);129 }130 131 err = regmap_update_bits(led->regmap, led->info->reg, led->info->mask,132 brightness);133 if (err) {134 dev_err(led->dev, "regmap failed: %d", err);135 goto exit;136 }137 138 if (value == LED_OFF) {139 err = cpcap_led_set_power(led, false);140 if (err)141 goto exit;142 }143 144exit:145 mutex_unlock(&led->update_lock);146 return err;147}148 149static const struct of_device_id cpcap_led_of_match[] = {150 { .compatible = "motorola,cpcap-led-red", .data = &cpcap_led_red },151 { .compatible = "motorola,cpcap-led-green", .data = &cpcap_led_green },152 { .compatible = "motorola,cpcap-led-blue", .data = &cpcap_led_blue },153 { .compatible = "motorola,cpcap-led-adl", .data = &cpcap_led_adl },154 { .compatible = "motorola,cpcap-led-cp", .data = &cpcap_led_cp },155 {},156};157MODULE_DEVICE_TABLE(of, cpcap_led_of_match);158 159static int cpcap_led_probe(struct platform_device *pdev)160{161 struct cpcap_led *led;162 int err;163 164 led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL);165 if (!led)166 return -ENOMEM;167 platform_set_drvdata(pdev, led);168 led->info = device_get_match_data(&pdev->dev);169 led->dev = &pdev->dev;170 171 if (led->info->reg == 0x0000) {172 dev_err(led->dev, "Unsupported LED");173 return -ENODEV;174 }175 176 led->regmap = dev_get_regmap(pdev->dev.parent, NULL);177 if (!led->regmap)178 return -ENODEV;179 180 led->vdd = devm_regulator_get(&pdev->dev, "vdd");181 if (IS_ERR(led->vdd)) {182 err = PTR_ERR(led->vdd);183 dev_err(led->dev, "Couldn't get regulator: %d", err);184 return err;185 }186 187 err = device_property_read_string(&pdev->dev, "label", &led->led.name);188 if (err) {189 dev_err(led->dev, "Couldn't read LED label: %d", err);190 return err;191 }192 193 if (led->info->init_mask) {194 err = regmap_update_bits(led->regmap, led->info->reg,195 led->info->init_mask, led->info->init_val);196 if (err) {197 dev_err(led->dev, "regmap failed: %d", err);198 return err;199 }200 }201 202 mutex_init(&led->update_lock);203 204 led->led.max_brightness = led->info->limit;205 led->led.brightness_set_blocking = cpcap_led_set;206 err = devm_led_classdev_register(&pdev->dev, &led->led);207 if (err) {208 dev_err(led->dev, "Couldn't register LED: %d", err);209 return err;210 }211 212 return 0;213}214 215static struct platform_driver cpcap_led_driver = {216 .probe = cpcap_led_probe,217 .driver = {218 .name = "cpcap-led",219 .of_match_table = cpcap_led_of_match,220 },221};222module_platform_driver(cpcap_led_driver);223 224MODULE_DESCRIPTION("CPCAP LED driver");225MODULE_AUTHOR("Sebastian Reichel <sre@kernel.org>");226MODULE_LICENSE("GPL");227