brintos

brintos / linux-shallow public Read only

0
0
Text · 4.9 KiB · 87354f1 Raw
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