Jetpack/kernel/kernel-4.9/drivers/leds/leds-thine350x.c

175 lines
5.2 KiB
C

/*
* Copyright (c) 2017, NVIDIA CORPORATION. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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 <linux/leds.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/spi/spi.h>
#include <linux/of.h>
#define THINE_350X_LEDS_COUNT 24
/*bit6-output enable, bit[5:0]-base brightness*/
#define THINE_350X_DEF_INIT_BRIGHT 127
/*slave address*/
#define THINE_350X_DEF_ADDRESS 0
/*Value of register 0: enable output, group brightness 0x3f*/
#define THINE_350X_MAX_BASE_BRIGHT 0x7f
/*No. of bytes required to control individual led*/
#define THINE_350x_LED_COM_BYTE_COUNT 3
/*No. of bytes of control information before LED brightness data for all LEDs*/
#define THINE_350x_LED_CTRL_BYTE_COUNT 3
struct thine350x_led {
struct led_classdev led_cdev;
struct spi_device *spidev;
char name[sizeof("thine350x_addre_id")];
int id;
struct work_struct work;
};
struct thine350xleds {
struct thine350x_led led[THINE_350X_LEDS_COUNT];
struct mutex mutex;
unsigned int address;
};
static enum led_brightness thine350x_get_brightness
(struct led_classdev *ledcdev)
{
return ledcdev->brightness;
}
static void thine350x_work(struct work_struct *work)
{
struct thine350x_led *led = container_of(work,
struct thine350x_led, work);
struct spi_device *spi =
(struct spi_device *)led->led_cdev.dev->parent;
struct thine350xleds *leds = spi_get_drvdata(spi);
unsigned char data[THINE_350x_LED_COM_BYTE_COUNT];
int ret;
/*Individual LEDs are addressed starting with index 1 in 350x chip,
* but data structure uses LED index starting from 0.
* So +1 is added while referring to LED index
*/
data[0] = (unsigned char)leds->address;
data[1] = (unsigned char)led->id+1;
data[2] = (unsigned char)led->led_cdev.brightness;
ret = spi_write(spi, data, THINE_350x_LED_COM_BYTE_COUNT);
if (ret != 0)
dev_err(led->led_cdev.dev, "Error writing to SPI slave\n");
}
static void thine350x_set_brightness(struct led_classdev *ledcdev,
enum led_brightness brightness)
{
struct thine350x_led *led = container_of(ledcdev,
struct thine350x_led, led_cdev);
schedule_work(&led->work);
}
static int thine350x_probe(struct spi_device *spi)
{
struct thine350xleds *leds;
int led_i, ret, init_bright;
unsigned char data[3+THINE_350X_LEDS_COUNT];
struct device_node *np = spi->dev.of_node;
leds = devm_kzalloc(&spi->dev, sizeof(struct thine350xleds),
GFP_KERNEL);
if (!leds)
return -ENOMEM;
mutex_init(&leds->mutex);
/*If dev_address node is not present use default address*/
if (of_property_read_u32(np, "dev_address", &leds->address))
leds->address = THINE_350X_DEF_ADDRESS;
if (of_property_read_u32(np, "init_brightness", &init_bright))
init_bright = THINE_350X_DEF_INIT_BRIGHT;
for (led_i = 0; led_i < THINE_350X_LEDS_COUNT; led_i++) {
leds->led[led_i].id = led_i;
leds->led[led_i].spidev = spi;
INIT_WORK(&leds->led[led_i].work, thine350x_work);
/*Individual LEDs are named starting with index 1,
* but data structure uses LED index starting from 0.
* So +1 is added while referring to LED index
*/
snprintf(leds->led[led_i].name,
sizeof("thine350x_addre_id"), "thine350x_%d_%d",
leds->address, led_i+1);
leds->led[led_i].led_cdev.name = leds->led[led_i].name;
leds->led[led_i].led_cdev.max_brightness = 0xff;
leds->led[led_i].led_cdev.brightness = LED_OFF;
leds->led[led_i].led_cdev.brightness_get =
thine350x_get_brightness;
leds->led[led_i].led_cdev.brightness_set =
thine350x_set_brightness;
ret = led_classdev_register(&spi->dev,
&leds->led[led_i].led_cdev);
if (ret < 0)
goto err;
}
spi_set_drvdata(spi, leds);
/*Setup SPI bus*/
spi->bits_per_word = 8;
spi->mode = SPI_MODE_0;
ret = spi_setup(spi);
if (ret)
return ret;
/*Configure LED output enable without initial brightness*/
data[0] = leds->address;/*slave address*/
data[1] = 0;/*register address*/
data[2] = THINE_350X_MAX_BASE_BRIGHT;
for (led_i = 0; led_i < THINE_350X_LEDS_COUNT; led_i++)
data[THINE_350x_LED_CTRL_BYTE_COUNT+led_i] =
(unsigned char)init_bright;
ret = spi_write(spi, data,
THINE_350X_LEDS_COUNT + THINE_350x_LED_CTRL_BYTE_COUNT);
return ret;
err:
while (led_i--)
led_classdev_unregister(&leds->led[led_i].led_cdev);
return ret;
}
static int thine350x_remove(struct spi_device *spi)
{
struct thine350xleds *leds = spi_get_drvdata(spi);
int led_i;
for (led_i = 0; led_i < THINE_350X_LEDS_COUNT; led_i++)
led_classdev_unregister(&leds->led[led_i].led_cdev);
return 0;
}
static struct spi_driver thine350x_driver = {
.probe = thine350x_probe,
.remove = thine350x_remove,
.driver = {
.name = "thine350x",
},
};
module_spi_driver(thine350x_driver);
MODULE_AUTHOR("Vishruth Jain <vishruthj@nvidia.com");
MODULE_DESCRIPTION("THL350X LED driver");
MODULE_LICENSE("GPL v2");