mirror of
https://github.com/apache/nuttx.git
synced 2026-08-27 20:30:38 +00:00
Permissions (Part 2) Description: In kernel builds, any unprivileged process running on the NuttX device can open /dev/efuse and attempt to read/write fuse content. Reading the fuses may provide valuable information to an attacker controlling the user process. The write operation, in extreme cases where the fuse blocks are not locked, may brick the device. DISCLAIMER: I tried to be strict with the settings, better to relax them later if it's needed. This is part of https://github.com/apache/nuttx/issues/19410 See https://github.com/apache/nuttx/issues/19410 Compiles ok. Signed-off-by: Catalin Visinescu <catalin_visinescu@yahoo.com>
972 lines
29 KiB
C
972 lines
29 KiB
C
/****************************************************************************
|
|
* drivers/leds/lp503x.c
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership. The
|
|
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance with the
|
|
* License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <nuttx/config.h>
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <nuttx/debug.h>
|
|
|
|
#include <nuttx/kmalloc.h>
|
|
#include <nuttx/signal.h>
|
|
#include <nuttx/i2c/i2c_master.h>
|
|
#include <nuttx/leds/lp503x.h>
|
|
|
|
#if defined(CONFIG_I2C) && defined(CONFIG_LP503X)
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_DEBUG_LP503X
|
|
# define lp503x_err(x, ...) _err(x, ##__VA_ARGS__)
|
|
# define lp503x_info(x, ...) _info(x, ##__VA_ARGS__)
|
|
#else
|
|
# define lp503x_err(x, ...) uerr(x, ##__VA_ARGS__)
|
|
# define lp503x_info(x, ...) uinfo(x, ##__VA_ARGS__)
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
enum lp503x_state
|
|
{
|
|
LP503X_STATE_UNINIT = 0,
|
|
LP503X_STATE_RESET,
|
|
LP503X_STATE_CONFIGURED,
|
|
};
|
|
|
|
struct lp503x_dev_s
|
|
{
|
|
struct i2c_master_s *i2c;
|
|
uint8_t i2c_addr;
|
|
int i2c_freq;
|
|
int count;
|
|
|
|
/* device configuration/setup data */
|
|
|
|
struct lp503x_config_s *lp503x_config;
|
|
|
|
/* current state of the lp503x device */
|
|
|
|
enum lp503x_state state;
|
|
};
|
|
|
|
/* A set of default config parameters as set in LED driver Kconfig */
|
|
|
|
struct lp503x_config_s config_default =
|
|
{
|
|
#ifdef CONFIG_LP503X_LOG_MODE
|
|
.enable_log_mode = 1,
|
|
#else
|
|
.enable_log_mode = 0,
|
|
#endif
|
|
#ifdef CONFIG_LP503X_POWER_SAVE
|
|
.enable_power_save = 1,
|
|
#else
|
|
.enable_power_save = 0,
|
|
#endif
|
|
#ifdef CONFIG_LP503X_DITHER_MODE
|
|
.enable_pwm_dithering = 1,
|
|
#else
|
|
.enable_pwm_dithering = 0,
|
|
#endif
|
|
#ifdef CONFIG_LP503X_MAX_CURRENT
|
|
.set_max_current_35ma = 1,
|
|
#else
|
|
.set_max_current_35ma = 0,
|
|
#endif
|
|
#ifdef CONFIG_LP503X_GLOBAL_SHUTDOWN
|
|
.enable_all_led_shutdown = 1,
|
|
#else
|
|
.enable_all_led_shutdown = 0,
|
|
#endif
|
|
|
|
/* all leds will default to independent control, not bank control */
|
|
|
|
.led_mode[0] = LP503X_LED_BANK_MODE_DISABLED,
|
|
.led_mode[1] = LP503X_LED_BANK_MODE_DISABLED,
|
|
.led_mode[2] = LP503X_LED_BANK_MODE_DISABLED,
|
|
.led_mode[3] = LP503X_LED_BANK_MODE_DISABLED,
|
|
.led_mode[4] = LP503X_LED_BANK_MODE_DISABLED,
|
|
.led_mode[5] = LP503X_LED_BANK_MODE_DISABLED,
|
|
.led_mode[6] = LP503X_LED_BANK_MODE_DISABLED,
|
|
.led_mode[7] = LP503X_LED_BANK_MODE_DISABLED,
|
|
.led_mode[8] = LP503X_LED_BANK_MODE_DISABLED,
|
|
.led_mode[9] = LP503X_LED_BANK_MODE_DISABLED,
|
|
.led_mode[10] = LP503X_LED_BANK_MODE_DISABLED,
|
|
.led_mode[11] = LP503X_LED_BANK_MODE_DISABLED,
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
|
|
static int lp503x_i2c_write_reg(struct lp503x_dev_s *priv,
|
|
uint8_t const reg_addr,
|
|
uint8_t const reg_val);
|
|
static int lp503x_i2c_read_reg(struct lp503x_dev_s *priv,
|
|
uint8_t const reg_addr,
|
|
uint8_t *regval);
|
|
static int lp503x_open(struct file *filep);
|
|
static int lp503x_close(struct file *filep);
|
|
static int lp503x_ioctl(struct file *filep, int cmd,
|
|
unsigned long arg);
|
|
#ifdef CONFIG_DEBUG_LP503X
|
|
static int lp503x_dump_registers(struct lp503x_dev_s *priv,
|
|
const char *msg);
|
|
#else
|
|
# define lp503x_dump_registers(priv, msg);
|
|
#endif
|
|
static int lp503x_reset(struct lp503x_dev_s *priv);
|
|
static int lp503x_enable(struct lp503x_dev_s *priv, bool enable);
|
|
static int lp503x_set_rgbbrightness(struct lp503x_dev_s *priv, int led,
|
|
int brightness);
|
|
static int lp503x_set_outcolour(struct lp503x_dev_s *priv, int led,
|
|
int colour);
|
|
static int lp503x_set_config(struct lp503x_dev_s *priv);
|
|
static int lp503x_set_bank_mode(struct lp503x_dev_s *priv);
|
|
static int lp503x_set_bank_colour(struct lp503x_dev_s *priv, char bank,
|
|
int brightness);
|
|
static int lp503x_set_bank_brightness(struct lp503x_dev_s *priv,
|
|
int brightness);
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
static const struct file_operations g_lp503x_fileops =
|
|
{
|
|
lp503x_open, /* open */
|
|
lp503x_close, /* close */
|
|
NULL, /* read */
|
|
NULL, /* write */
|
|
NULL, /* seek */
|
|
lp503x_ioctl, /* ioctl */
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: lp503x_dumpregs
|
|
*
|
|
* Description:
|
|
* Dump the contents of all lp503x registers
|
|
*
|
|
* Input Parameters:
|
|
* priv - A reference to the lp503x peripheral state
|
|
* msg - Message to print before the register data
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_DEBUG_LP503X
|
|
static int lp503x_dump_registers(struct lp503x_dev_s *priv,
|
|
const char *msg)
|
|
{
|
|
uint8_t val1;
|
|
uint8_t val2;
|
|
uint8_t val3;
|
|
uint8_t val4;
|
|
int ret;
|
|
lp503x_info("lp503x Registers: %s\n", msg);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_DEVICE_CONFIG0, &val1);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_DEVICE_CONFIG1, &val2);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_LED_CONFIG0, &val3);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_LED_CONFIG1, &val4);
|
|
lp503x_info
|
|
("Dev Config0:\t%02x Dev Conf1:\t%02x \
|
|
LED Conf0:\t%02x: LED Conf1: \t %02x\n",
|
|
val1, val2, val3, val4);
|
|
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_BANK_BRIGHTNESS, &val1);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_BANK_A_COLOUR, &val2);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_BANK_B_COLOUR, &val3);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_BANK_C_COLOUR, &val4);
|
|
lp503x_info
|
|
("Bank Bright:\t%02x BankA Col:\t%02x \
|
|
BankB Col:\t%02x: BankC Col:\t %02x\n",
|
|
val1, val2, val3, val4);
|
|
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_LED0_BRIGHTNESS, &val1);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_LED1_BRIGHTNESS, &val2);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_LED2_BRIGHTNESS, &val3);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_LED3_BRIGHTNESS, &val4);
|
|
lp503x_info
|
|
("LED0 Bright:\t%02x LED1 Col:\t%02x \
|
|
LED2 Bright:\t%02x: LED3 Bright: %02x\n",
|
|
val1, val2, val3, val4);
|
|
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_LED4_BRIGHTNESS, &val1);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_LED5_BRIGHTNESS, &val2);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_LED6_BRIGHTNESS, &val3);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_LED7_BRIGHTNESS, &val4);
|
|
lp503x_info
|
|
("LED4 Bright:\t%02x LED5 Bright:\t%02x \
|
|
LED6 Bright:\t%02x: LED7 Bright: %02x\n",
|
|
val1, val2, val3, val4);
|
|
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_LED8_BRIGHTNESS, &val1);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_LED9_BRIGHTNESS, &val2);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_LED10_BRIGHTNESS, &val3);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_LED11_BRIGHTNESS, &val4);
|
|
lp503x_info
|
|
("LED8 Bright:\t%02x LED9 Bright:\t%02x \
|
|
LED10 Bright:\t%02x: LED11 Bright:%02x\n",
|
|
val1, val2, val3, val4);
|
|
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_OUT0_COLOUR, &val1);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_OUT1_COLOUR, &val2);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_OUT2_COLOUR, &val3);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_OUT3_COLOUR, &val4);
|
|
lp503x_info
|
|
("Out0 Col:\t%02x Out1 Col:\t%02x \
|
|
Out2 Col:\t\t%02x Out3 Col:\t %02x\n",
|
|
val1, val2, val3, val4);
|
|
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_OUT4_COLOUR, &val1);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_OUT5_COLOUR, &val2);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_OUT6_COLOUR, &val3);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_OUT7_COLOUR, &val4);
|
|
lp503x_info
|
|
("Out4 Col:\t%02x Out5 Col:\t%02x \
|
|
Out6 Col:\t\t%02x Out7 Col:\t %02x\n",
|
|
val1, val2, val3, val4);
|
|
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_OUT8_COLOUR, &val1);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_OUT9_COLOUR, &val2);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_OUT10_COLOUR, &val3);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_OUT11_COLOUR, &val4);
|
|
lp503x_info
|
|
("Out8 Col:\t%02x Out9 Col:\t%02x \
|
|
Out10 Col:\t%02x Out11 Col:\t %02x\n",
|
|
val1, val2, val3, val4);
|
|
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_OUT12_COLOUR, &val1);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_OUT13_COLOUR, &val2);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_OUT14_COLOUR, &val3);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_OUT15_COLOUR, &val4);
|
|
lp503x_info
|
|
("Out12 Col:\t%02x Out13 Col:\t%02x \
|
|
Out14 Col:\t%02x Out15 Col:\t %02x\n",
|
|
val1, val2, val3, val4);
|
|
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_OUT16_COLOUR, &val1);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_OUT17_COLOUR, &val2);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_OUT18_COLOUR, &val3);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_OUT19_COLOUR, &val4);
|
|
lp503x_info
|
|
("Out16 Col:\t%02x Out17 Col:\t%02x \
|
|
Out18 Col:\t%02x Out19 Col:\t %02x\n",
|
|
val1, val2, val3, val4);
|
|
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_OUT20_COLOUR, &val1);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_OUT21_COLOUR, &val2);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_OUT22_COLOUR, &val3);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_OUT23_COLOUR, &val4);
|
|
lp503x_info
|
|
("Out20 Col:\t%02x Out21 Col:\t%02x \
|
|
Out22 Col:\t%02x Out23 Col:\t %02x\n",
|
|
val1, val2, val3, val4);
|
|
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_OUT24_COLOUR, &val1);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_OUT25_COLOUR, &val2);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_OUT26_COLOUR, &val3);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_OUT27_COLOUR, &val4);
|
|
lp503x_info
|
|
("Out24 Col:\t%02x Out25 Col:\t%02x \
|
|
Out26 Col:\t%02x Out27 Col:\t %02x\n",
|
|
val1, val2, val3, val4);
|
|
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_OUT28_COLOUR, &val1);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_OUT29_COLOUR, &val2);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_OUT30_COLOUR, &val3);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_OUT31_COLOUR, &val4);
|
|
lp503x_info
|
|
("Out28 Col:\t%02x Out29 Col:\t%02x \
|
|
Out30 Col:\t%02x Out31 Col:\t %02x\n",
|
|
val1, val2, val3, val4);
|
|
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_OUT32_COLOUR, &val1);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_OUT33_COLOUR, &val2);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_OUT34_COLOUR, &val3);
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_OUT35_COLOUR, &val4);
|
|
lp503x_info
|
|
("Out28 Col:\t%02x Out29 Col:\t%02x \
|
|
Out30 Col:\t%02x Out31 Col:\t %02x\n",
|
|
val1, val2, val3, val4);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#endif
|
|
/****************************************************************************
|
|
* Name: lp503x_i2c_write_reg
|
|
*
|
|
* Description:
|
|
* Write a single byte to one of the LP503X configuration registers.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int lp503x_i2c_write_reg(struct lp503x_dev_s *priv,
|
|
uint8_t const reg_addr,
|
|
uint8_t const reg_val)
|
|
{
|
|
struct i2c_config_s config;
|
|
int ret;
|
|
|
|
/* assemble the 2 byte message comprised of reg_addr and reg_val */
|
|
|
|
uint8_t const BUFFER_SIZE = 2;
|
|
uint8_t buffer[BUFFER_SIZE];
|
|
|
|
buffer[0] = reg_addr;
|
|
buffer[1] = reg_val;
|
|
|
|
/* Setup up the I2C configuration */
|
|
|
|
config.frequency = priv->i2c_freq;
|
|
config.address = priv->i2c_addr;
|
|
config.addrlen = 7;
|
|
|
|
/* Write the register address followed by the data (no RESTART) */
|
|
|
|
ledinfo("i2c addr: 0x%02X reg addr: 0x%02X value: 0x%02X\n",
|
|
priv->i2c_addr, buffer[0], buffer[1]);
|
|
|
|
ret = i2c_write(priv->i2c, &config, buffer, BUFFER_SIZE);
|
|
if (ret < 0)
|
|
{
|
|
lederr("ERROR: i2c_write returned error code %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lp503x_i2c_read_reg
|
|
*
|
|
* Description:
|
|
* Read a single byte from one of the LP503X configuration registers.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int lp503x_i2c_read_reg(struct lp503x_dev_s *priv,
|
|
uint8_t const reg_addr,
|
|
uint8_t *regval)
|
|
{
|
|
struct i2c_config_s config;
|
|
int ret;
|
|
|
|
/* Setup up the I2C configuration */
|
|
|
|
config.frequency = priv->i2c_freq;
|
|
config.address = priv->i2c_addr;
|
|
config.addrlen = 7;
|
|
|
|
/* Write the register address followed by the data (no RESTART) */
|
|
|
|
ret = i2c_write(priv->i2c, &config, ®_addr, 1);
|
|
ret = i2c_read(priv->i2c, &config, regval, 1);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lp503x_open
|
|
*
|
|
* Description:
|
|
* This function is called whenever a LP503X device is opened.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int lp503x_open(struct file *filep)
|
|
{
|
|
struct inode *inode = filep->f_inode;
|
|
struct lp503x_dev_s *priv = inode->i_private;
|
|
int ret;
|
|
|
|
ledinfo("INFO: Opening, resetting and enabling the LP503X for business\n");
|
|
|
|
/* reset and enable the device */
|
|
|
|
/* means the device was possibly never registered? */
|
|
|
|
if (priv->state == LP503X_STATE_UNINIT)
|
|
{
|
|
return -ENODEV;
|
|
}
|
|
else if (priv->state == LP503X_STATE_RESET)
|
|
{
|
|
ret = lp503x_enable(priv, true);
|
|
|
|
if (ret != 0)
|
|
{
|
|
lederr("ERROR: unable to enable lp503x\n");
|
|
return -EIO;
|
|
}
|
|
else
|
|
{
|
|
/* use device defaults */
|
|
|
|
priv->lp503x_config = &config_default;
|
|
}
|
|
|
|
ret = lp503x_set_config(priv);
|
|
if (ret != 0)
|
|
{
|
|
lederr("ERROR: Unable to set device config: %d\n", ret);
|
|
return -EIO;
|
|
}
|
|
|
|
priv->state = LP503X_STATE_CONFIGURED;
|
|
}
|
|
|
|
lp503x_dump_registers(priv, "File Open");
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lp503x_close
|
|
*
|
|
* Description:
|
|
* This function is called whenever a LP503X device is closed.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int lp503x_close(struct file *filep)
|
|
{
|
|
int ret;
|
|
|
|
struct inode *inode = filep->f_inode;
|
|
struct lp503x_dev_s *priv = inode->i_private;
|
|
|
|
ret = lp503x_enable(priv, false);
|
|
if (ret < 0)
|
|
{
|
|
lederr("ERROR: Could not disable LP503X\n");
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lp503x_reset
|
|
*
|
|
* Description:
|
|
* Resets all registers to default values
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int lp503x_reset(struct lp503x_dev_s *priv)
|
|
{
|
|
int ret;
|
|
|
|
ret = lp503x_i2c_write_reg(priv, LP503X_RESET, LP503X_RESET_ALL_REGISTERS);
|
|
if (ret != 0)
|
|
{
|
|
return -EIO;
|
|
}
|
|
else
|
|
{
|
|
priv->state = LP503X_STATE_RESET;
|
|
return OK;
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lp503x_enable
|
|
*
|
|
* Description:
|
|
* enables or disables the entire device
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int lp503x_enable(struct lp503x_dev_s *priv, bool enable)
|
|
{
|
|
int ret;
|
|
|
|
if (enable)
|
|
{
|
|
ret = lp503x_i2c_write_reg(priv, LP503X_DEVICE_CONFIG0,
|
|
LP503X_CHIP_ENABLE);
|
|
ledinfo("INFO: LP503x enabled\n");
|
|
}
|
|
else
|
|
{
|
|
ret = lp503x_i2c_write_reg(priv, LP503X_DEVICE_CONFIG0,
|
|
LP503X_CHIP_DISABLE);
|
|
ledinfo("INFO: LP503x disabled\n");
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lp503x_set_config
|
|
*
|
|
* Description:
|
|
* configures basic operation modes of the device
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int lp503x_set_config(struct lp503x_dev_s *priv)
|
|
{
|
|
int ret;
|
|
uint8_t regval;
|
|
struct lp503x_config_s *config;
|
|
|
|
config = priv->lp503x_config;
|
|
|
|
ret = lp503x_i2c_read_reg(priv, LP503X_DEVICE_CONFIG1, ®val);
|
|
if (config->enable_log_mode)
|
|
{
|
|
regval |= LP503X_CONFIG1_LOG_SCALE;
|
|
}
|
|
else
|
|
{
|
|
regval &= ~LP503X_CONFIG1_LOG_SCALE;
|
|
}
|
|
|
|
if (config->enable_power_save)
|
|
{
|
|
regval |= LP503X_CONFIG1_PWRSAVE;
|
|
}
|
|
else
|
|
{
|
|
regval &= ~LP503X_CONFIG1_PWRSAVE;
|
|
}
|
|
|
|
if (config->enable_pwm_dithering)
|
|
{
|
|
regval |= LP503X_CONFIG1_DITHERING;
|
|
}
|
|
else
|
|
{
|
|
regval &= ~LP503X_CONFIG1_DITHERING;
|
|
}
|
|
|
|
if (config->set_max_current_35ma)
|
|
{
|
|
regval |= LP503X_CONFIG1_PWRSAVE;
|
|
}
|
|
else
|
|
{
|
|
regval &= ~LP503X_CONFIG1_PWRSAVE;
|
|
}
|
|
|
|
if (config->enable_all_led_shutdown)
|
|
{
|
|
regval |= LP503X_CONFIG1_GLOBAL_OFF;
|
|
}
|
|
else
|
|
{
|
|
regval &= ~LP503X_CONFIG1_GLOBAL_OFF;
|
|
}
|
|
|
|
ret = lp503x_i2c_write_reg(priv, LP503X_DEVICE_CONFIG1, regval);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lp503x_set_bank_brightness
|
|
*
|
|
* Description:
|
|
* sets banks to the required brightness
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int lp503x_set_bank_brightness(struct lp503x_dev_s *priv,
|
|
int brightness)
|
|
{
|
|
if (brightness > MAX_BRIGHTNESS)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
else
|
|
{
|
|
return lp503x_i2c_write_reg(priv, LP503X_BANK_BRIGHTNESS, brightness);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lp503x_set_bank_colour
|
|
*
|
|
* Description:
|
|
* sets bank A, B or C led to required coloiur (mix)
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int lp503x_set_bank_colour(struct lp503x_dev_s *priv, char bank,
|
|
int brightness)
|
|
{
|
|
if (brightness > MAX_BRIGHTNESS)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
else
|
|
{
|
|
if (bank == 'A')
|
|
{
|
|
return lp503x_i2c_write_reg(priv, LP503X_BANK_A_COLOUR,
|
|
brightness);
|
|
}
|
|
else if (bank == 'B')
|
|
{
|
|
return lp503x_i2c_write_reg(priv, LP503X_BANK_B_COLOUR,
|
|
brightness);
|
|
}
|
|
else if (bank == 'C')
|
|
{
|
|
return lp503x_i2c_write_reg(priv, LP503X_BANK_C_COLOUR,
|
|
brightness);
|
|
}
|
|
else
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lp503x_set_bank_mode
|
|
*
|
|
* Description:
|
|
* enables or disables bank mode for selected LED
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int lp503x_set_bank_mode(struct lp503x_dev_s *priv)
|
|
{
|
|
int ret;
|
|
int count;
|
|
int regval;
|
|
|
|
struct lp503x_config_s *config;
|
|
|
|
config = priv->lp503x_config;
|
|
|
|
regval = 0;
|
|
for (count = 0; count < 8; count++)
|
|
{
|
|
if (config->led_mode[count] == LP503X_LED_BANK_MODE_ENABLED)
|
|
{
|
|
regval |= (LP503X_LED0_BANK_ENABLE << count);
|
|
}
|
|
else
|
|
{
|
|
regval &= ~(LP503X_LED0_BANK_ENABLE << count);
|
|
}
|
|
}
|
|
|
|
ret = lp503x_i2c_write_reg(priv, LP503X_LED_CONFIG0, regval);
|
|
|
|
for (count = 8; count < 12; count++)
|
|
{
|
|
if (config->led_mode[count] == LP503X_LED_BANK_MODE_ENABLED)
|
|
{
|
|
regval |= (LP503X_LED0_BANK_ENABLE << (count - 8));
|
|
}
|
|
else
|
|
{
|
|
regval &= ~(LP503X_LED0_BANK_ENABLE << (count - 8));
|
|
}
|
|
}
|
|
|
|
ret = lp503x_i2c_write_reg(priv, LP503X_LED_CONFIG1, regval);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lp503x_set_rgbled_colour
|
|
*
|
|
* Description:
|
|
* sets RGB led to chosen html colour
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int lp503x_set_rgbled_colour(struct lp503x_dev_s *priv,
|
|
int led, int colour)
|
|
{
|
|
int ret;
|
|
int regaddr;
|
|
|
|
if ((led > MAX_RGB_LEDS) || (colour > MAX_RGB_COLOUR))
|
|
{
|
|
ret = -EINVAL;
|
|
}
|
|
else
|
|
{
|
|
regaddr = LP503X_OUT0_COLOUR + (3*led);
|
|
|
|
ret = lp503x_i2c_write_reg(priv, regaddr++, (colour >> 16) & 0xff);
|
|
ret = lp503x_i2c_write_reg(priv, regaddr++, (colour >> 8) & 0xff);
|
|
ret = lp503x_i2c_write_reg(priv, regaddr, (colour >> 0) & 0xff);
|
|
ledinfo("INFO: RGB LED %d set to RGB colour %06x\n", led, colour);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lp503x_set_outcolour
|
|
*
|
|
* Description:
|
|
* Sets OUT brightness ("colour" of individual LED outputs
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int lp503x_set_outcolour(struct lp503x_dev_s *priv, int led,
|
|
int brightness)
|
|
{
|
|
int ret;
|
|
if ((led > MAX_LEDS) || (brightness > MAX_BRIGHTNESS))
|
|
{
|
|
ret = -EINVAL;
|
|
}
|
|
else
|
|
{
|
|
ret = lp503x_i2c_write_reg(priv, LP503X_OUT0_COLOUR + led, brightness);
|
|
ledinfo("INFO: individual LED %d set to brightness %d\n", led,
|
|
brightness);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lp503x_set_rgbbrightness
|
|
*
|
|
* Description:
|
|
* Sets brightness of all RGB LED
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int lp503x_set_rgbbrightness(struct lp503x_dev_s *priv, int led,
|
|
int brightness)
|
|
{
|
|
int ret;
|
|
|
|
if ((led > MAX_RGB_LEDS) || (brightness > MAX_BRIGHTNESS))
|
|
{
|
|
ret = -EINVAL;
|
|
}
|
|
else
|
|
{
|
|
ret = lp503x_i2c_write_reg(priv, LP503X_LED0_BRIGHTNESS + led,
|
|
brightness);
|
|
ledinfo("INFO: LED %d set to brightness %d\n", led, brightness);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lp503x_ioctl
|
|
*
|
|
* Description:
|
|
* This function is called whenever an ioctl call to a LP503X is
|
|
* performed.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int lp503x_ioctl(struct file *filep, int cmd,
|
|
unsigned long arg)
|
|
{
|
|
struct inode *inode = filep->f_inode;
|
|
struct lp503x_dev_s *priv = inode->i_private;
|
|
struct lp503x_config_s *config;
|
|
int ret;
|
|
const struct ioctl_arg_s *lp503x_ioctl_args = (struct ioctl_arg_s *)arg;
|
|
|
|
config = priv->lp503x_config;
|
|
|
|
ret = OK;
|
|
|
|
ledinfo("cmd: %d arg: %ld\n", cmd, arg);
|
|
|
|
switch (cmd)
|
|
{
|
|
case PWMIOC_ENABLE: /* arg is true or false */
|
|
config->enable_all_led_shutdown = lp503x_ioctl_args->param;
|
|
ret = lp503x_set_config(priv);
|
|
break;
|
|
|
|
case PWMIOC_RESET: /* no args */
|
|
lp503x_reset(priv);
|
|
break;
|
|
|
|
case PWMIOC_ENABLE_LED_BANK_MODE: /* led(0..11), mode required */
|
|
if (lp503x_ioctl_args->lednum > MAX_RGB_LEDS)
|
|
{
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
ledinfo("INFO: setting LED %d mode to %" PRIx32 "\n",
|
|
lp503x_ioctl_args->lednum,
|
|
lp503x_ioctl_args->param);
|
|
config->led_mode[lp503x_ioctl_args->lednum] =
|
|
lp503x_ioctl_args->param;
|
|
lp503x_set_bank_mode(priv);
|
|
break;
|
|
|
|
case PWMIOC_SET_BANK_MIX_COLOUR:/* bank(A/B/C),level(0-255) */
|
|
ledinfo("INFO: setting bank %c to brightness %" PRIx32 "\n",
|
|
lp503x_ioctl_args->lednum, lp503x_ioctl_args->param);
|
|
ret = lp503x_set_bank_colour(priv, lp503x_ioctl_args->lednum,
|
|
lp503x_ioctl_args->param);
|
|
break;
|
|
|
|
case PWMIOC_SET_BANK_BRIGHTNESS:
|
|
ledinfo("INFO: setting bank brightness to %" PRIx32 "\n",
|
|
lp503x_ioctl_args->param);
|
|
lp503x_set_bank_brightness(priv, lp503x_ioctl_args->param);
|
|
break;
|
|
|
|
case PWMIOC_CONFIG: /* config is struct within priv */
|
|
ledinfo("INFO: setting device config to:\n");
|
|
ledinfo("\tlog mode = %d\n", config->enable_log_mode);
|
|
ledinfo("\tpower save = %d\n", config->enable_power_save);
|
|
ledinfo("\tpwm dithering = %d\n", config->enable_pwm_dithering);
|
|
ledinfo("\tmax current = %s\n", (config->set_max_current_35ma)
|
|
? "30mA" : "25.5mA");
|
|
ledinfo("\tall leds shutdown = %d\n",
|
|
config->enable_all_led_shutdown);
|
|
|
|
ret = lp503x_set_config(priv);
|
|
break;
|
|
|
|
case PWMIOC_SET_LED_COLOUR: /* led(0..35), Colour(0..255) */
|
|
ledinfo("INFO: set LED %d to colour/brightness %" PRIx32 "\n",
|
|
lp503x_ioctl_args->lednum, lp503x_ioctl_args->param);
|
|
ret = lp503x_set_outcolour(priv, lp503x_ioctl_args->lednum,
|
|
lp503x_ioctl_args->param);
|
|
break;
|
|
|
|
case PWMIOC_SET_RGB_BRIGHTNESS: /* led(0..11), level(0..255) */
|
|
ledinfo("INFO: requested brightness level %d for led %" PRIx32 "\n",
|
|
lp503x_ioctl_args->lednum, lp503x_ioctl_args->param);
|
|
ret = lp503x_set_rgbbrightness(priv, lp503x_ioctl_args->lednum,
|
|
lp503x_ioctl_args->param);
|
|
break;
|
|
|
|
case PWMIOC_SET_RGB_COLOUR: /* led(0..11) */
|
|
ledinfo("requested led %d to be RGB colour = %" PRIx32 "\n",
|
|
lp503x_ioctl_args->lednum, lp503x_ioctl_args->param);
|
|
ret = lp503x_set_rgbled_colour(priv, lp503x_ioctl_args->lednum,
|
|
lp503x_ioctl_args->param);
|
|
break;
|
|
|
|
default: /* The used ioctl command was invalid */
|
|
lederr("ERROR: Unrecognized cmd: %d\n", cmd);
|
|
ret = -ENOTTY;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: lp503x_register
|
|
*
|
|
* Description:
|
|
* Register the LP503X device as 'devpath'
|
|
*
|
|
* Input Parameters:
|
|
* devpath - The full path to the driver to register. E.g., "/dev/leddrv0".
|
|
* i2c - An instance of the I2C interface to use to communicate
|
|
* with the LM92.
|
|
* lp503x_i2c_addr
|
|
* - The I2C address of the LP503X.
|
|
*
|
|
* Returned Value:
|
|
* Zero (OK) on success; a negated errno value on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int lp503x_register(const char *devpath, struct i2c_master_s *i2c,
|
|
uint8_t const lp503x_i2c_addr, int const i2c_frequency)
|
|
{
|
|
struct lp503x_dev_s *priv;
|
|
int ret;
|
|
|
|
/* Sanity check */
|
|
|
|
DEBUGASSERT(devpath != NULL && i2c != NULL);
|
|
|
|
/* Initialize the LP503X device structure */
|
|
|
|
priv = kmm_malloc(sizeof(struct lp503x_dev_s));
|
|
if (priv == NULL)
|
|
{
|
|
lederr("ERROR: Failed to allocate instance of lp503x_dev_s\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
priv->i2c = i2c;
|
|
priv->i2c_addr = lp503x_i2c_addr;
|
|
priv->i2c_freq = i2c_frequency;
|
|
priv->state = LP503X_STATE_UNINIT;
|
|
|
|
/* Register the character driver */
|
|
|
|
ret = register_driver(devpath, &g_lp503x_fileops, 0200, priv);
|
|
if (ret != OK)
|
|
{
|
|
lederr("ERROR: Failed to register driver: %d\n", ret);
|
|
kmm_free(priv);
|
|
return ret;
|
|
}
|
|
else
|
|
{
|
|
ret = lp503x_reset(priv);
|
|
if (ret != OK)
|
|
{
|
|
lederr("ERROR: failed to reset lp503x device\n");
|
|
return ret;
|
|
}
|
|
|
|
priv->state = LP503X_STATE_RESET;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
#endif /* CONFIG_I2C && CONFIG_I2C_LP503X */
|