2020-07-23 12:53:19 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
/*
|
|
|
|
* CZ.NIC's Turris Omnia LEDs driver
|
|
|
|
*
|
leds: turris-omnia: Do not use SMBUS calls
The leds-turris-omnia driver uses three function for I2C access:
- i2c_smbus_write_byte_data() and i2c_smbus_read_byte_data(), which
cause an emulated SMBUS transfer,
- i2c_master_send(), which causes an ordinary I2C transfer.
The Turris Omnia MCU LED controller is not semantically SMBUS, it
operates as a simple I2C bus. It does not implement any of the SMBUS
specific features, like PEC, or procedure calls, or anything. Moreover
the I2C controller driver also does not implement SMBUS, and so the
emulated SMBUS procedure from drivers/i2c/i2c-core-smbus.c is used for
the SMBUS calls, which gives an unnecessary overhead.
When I first wrote the driver, I was unaware of these facts, and I
simply used the first function that worked.
Drop the I2C SMBUS calls and instead use simple I2C transfers.
Fixes: 089381b27abe ("leds: initial support for Turris Omnia LEDs")
Signed-off-by: Marek Behún <kabel@kernel.org>
Link: https://lore.kernel.org/r/20230918161104.20860-2-kabel@kernel.org
Signed-off-by: Lee Jones <lee@kernel.org>
2023-09-18 16:11:01 +00:00
|
|
|
* 2020, 2023 by Marek Behún <kabel@kernel.org>
|
2020-07-23 12:53:19 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/i2c.h>
|
|
|
|
#include <linux/led-class-multicolor.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/mutex.h>
|
|
|
|
#include <linux/of.h>
|
|
|
|
#include "leds.h"
|
|
|
|
|
2020-10-30 02:39:03 +00:00
|
|
|
#define OMNIA_BOARD_LEDS 12
|
|
|
|
#define OMNIA_LED_NUM_CHANNELS 3
|
2020-07-23 12:53:19 +00:00
|
|
|
|
2023-09-18 16:11:04 +00:00
|
|
|
/* MCU controller commands at I2C address 0x2a */
|
|
|
|
#define OMNIA_MCU_I2C_ADDR 0x2a
|
2020-07-23 12:53:19 +00:00
|
|
|
|
2023-09-18 16:11:04 +00:00
|
|
|
#define CMD_GET_STATUS_WORD 0x01
|
|
|
|
#define STS_FEATURES_SUPPORTED BIT(2)
|
2020-07-23 12:53:19 +00:00
|
|
|
|
2023-09-18 16:11:04 +00:00
|
|
|
#define CMD_GET_FEATURES 0x10
|
|
|
|
#define FEAT_LED_GAMMA_CORRECTION BIT(5)
|
|
|
|
|
|
|
|
/* LED controller commands at I2C address 0x2b */
|
|
|
|
#define CMD_LED_MODE 0x03
|
|
|
|
#define CMD_LED_MODE_LED(l) ((l) & 0x0f)
|
|
|
|
#define CMD_LED_MODE_USER 0x10
|
|
|
|
|
|
|
|
#define CMD_LED_STATE 0x04
|
|
|
|
#define CMD_LED_STATE_LED(l) ((l) & 0x0f)
|
|
|
|
#define CMD_LED_STATE_ON 0x10
|
|
|
|
|
|
|
|
#define CMD_LED_COLOR 0x05
|
|
|
|
#define CMD_LED_SET_BRIGHTNESS 0x07
|
|
|
|
#define CMD_LED_GET_BRIGHTNESS 0x08
|
|
|
|
|
|
|
|
#define CMD_SET_GAMMA_CORRECTION 0x30
|
|
|
|
#define CMD_GET_GAMMA_CORRECTION 0x31
|
2020-07-23 12:53:19 +00:00
|
|
|
|
|
|
|
struct omnia_led {
|
|
|
|
struct led_classdev_mc mc_cdev;
|
|
|
|
struct mc_subled subled_info[OMNIA_LED_NUM_CHANNELS];
|
leds: turris-omnia: Make set_brightness() more efficient
Implement caching of the LED color and state values that are sent to MCU
in order to make the set_brightness() operation more efficient by
avoiding I2C transactions which are not needed.
On Turris Omnia's MCU, which acts as the RGB LED controller, each LED
has a RGB color, and a ON/OFF state, which are configurable via I2C
commands CMD_LED_COLOR and CMD_LED_STATE.
The CMD_LED_COLOR command sends 5 bytes and the CMD_LED_STATE command 2
bytes over the I2C bus, which operates at 100 kHz. With I2C overhead
this allows ~1670 color changing commands and ~3200 state changing
commands per second (or around 1000 color + state changes per second).
This may seem more than enough, but the issue is that the I2C bus is
shared with another peripheral, the MCU. The MCU exposes an interrupt
interface, and it can trigger hundreds of interrupts per second. Each
time, we need to read the interrupt state register over this I2C bus.
Whenever we are sending a LED color/state changing command, the
interrupt reading is waiting.
Currently, every time LED brightness or LED multi intensity is changed,
we send a CMD_LED_STATE command, and if the computed color (brightness
adjusted multi_intensity) is non-zero, we also send a CMD_LED_COLOR
command.
Consider for example the situation when we have a netdev trigger enabled
for a LED. The netdev trigger does not change the LED color, only the
brightness (either to 0 or to currently configured brightness), and so
there is no need to send the CMD_LED_COLOR command. But each change of
brightness to 0 sends one CMD_LED_STATE command, and each change of
brightness to max_brightness sends one CMD_LED_STATE command and one
CMD_LED_COLOR command:
set_brightness(0) -> CMD_LED_STATE
set_brightness(255) -> CMD_LED_STATE + CMD_LED_COLOR
(unnecessary)
We can avoid the unnecessary I2C transactions if we cache the values of
state and color that are sent to the controller. If the color does not
change from the one previously sent, there is no need to do the
CMD_LED_COLOR I2C transaction, and if the state does not change, there
is no need to do the CMD_LED_STATE transaction.
Because we need to make sure that our cached values are consistent with
the controller state, add explicit setting of the LED color to white at
probe time (this is the default setting when MCU resets, but does not
necessarily need to be the case, for example if U-Boot played with the
LED colors).
Signed-off-by: Marek Behún <kabel@kernel.org>
Link: https://lore.kernel.org/r/20230918161104.20860-3-kabel@kernel.org
Signed-off-by: Lee Jones <lee@kernel.org>
2023-09-18 16:11:02 +00:00
|
|
|
u8 cached_channels[OMNIA_LED_NUM_CHANNELS];
|
leds: turris-omnia: Support HW controlled mode via private trigger
Add support for enabling MCU controlled mode of the Turris Omnia LEDs
via a LED private trigger called "omnia-mcu". Recall that private LED
triggers will only be listed in the sysfs trigger file for LEDs that
support them (currently there is no user of this mechanism).
When in MCU controlled mode, the user can still set LED color, but the
blinking is done by MCU, which does different things for different LEDs:
- WAN LED is blinked according to the LED[0] pin of the WAN PHY
- LAN LEDs are blinked according to the LED[0] output of the
corresponding port of the LAN switch
- PCIe LEDs are blinked according to the logical OR of the MiniPCIe port
LED pins
In the future I want to make the netdev trigger to transparently offload
the blinking to the HW if user sets compatible settings for the netdev
trigger (for LEDs associated with network devices).
There was some work on this already, and hopefully we will be able to
complete it sometime, but for now there are still multiple blockers for
this, and even if there weren't, we still would not be able to configure
HW controlled mode for the LEDs associated with MiniPCIe ports.
In the meantime let's support HW controlled mode via the private LED
trigger mechanism. If, in the future, we manage to complete the netdev
trigger offloading, we can still keep this private trigger for backwards
compatibility, if needed.
We also set "omnia-mcu" to cdev->default_trigger, so that the MCU keeps
control until the user first wants to take over it. If a different
default trigger is specified in device-tree via the
'linux,default-trigger' property, LED class will overwrite
cdev->default_trigger, and so the DT property will be respected.
Signed-off-by: Marek Behún <kabel@kernel.org>
Link: https://lore.kernel.org/r/20230918161104.20860-4-kabel@kernel.org
Signed-off-by: Lee Jones <lee@kernel.org>
2023-09-18 16:11:03 +00:00
|
|
|
bool on, hwtrig;
|
2020-07-23 12:53:19 +00:00
|
|
|
int reg;
|
|
|
|
};
|
|
|
|
|
2020-10-30 02:39:03 +00:00
|
|
|
#define to_omnia_led(l) container_of(l, struct omnia_led, mc_cdev)
|
2020-07-23 12:53:19 +00:00
|
|
|
|
|
|
|
struct omnia_leds {
|
|
|
|
struct i2c_client *client;
|
|
|
|
struct mutex lock;
|
2023-09-18 16:11:04 +00:00
|
|
|
bool has_gamma_correction;
|
2020-07-23 12:53:19 +00:00
|
|
|
struct omnia_led leds[];
|
|
|
|
};
|
|
|
|
|
leds: turris-omnia: Do not use SMBUS calls
The leds-turris-omnia driver uses three function for I2C access:
- i2c_smbus_write_byte_data() and i2c_smbus_read_byte_data(), which
cause an emulated SMBUS transfer,
- i2c_master_send(), which causes an ordinary I2C transfer.
The Turris Omnia MCU LED controller is not semantically SMBUS, it
operates as a simple I2C bus. It does not implement any of the SMBUS
specific features, like PEC, or procedure calls, or anything. Moreover
the I2C controller driver also does not implement SMBUS, and so the
emulated SMBUS procedure from drivers/i2c/i2c-core-smbus.c is used for
the SMBUS calls, which gives an unnecessary overhead.
When I first wrote the driver, I was unaware of these facts, and I
simply used the first function that worked.
Drop the I2C SMBUS calls and instead use simple I2C transfers.
Fixes: 089381b27abe ("leds: initial support for Turris Omnia LEDs")
Signed-off-by: Marek Behún <kabel@kernel.org>
Link: https://lore.kernel.org/r/20230918161104.20860-2-kabel@kernel.org
Signed-off-by: Lee Jones <lee@kernel.org>
2023-09-18 16:11:01 +00:00
|
|
|
static int omnia_cmd_write_u8(const struct i2c_client *client, u8 cmd, u8 val)
|
|
|
|
{
|
|
|
|
u8 buf[2] = { cmd, val };
|
2023-10-16 14:15:38 +00:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = i2c_master_send(client, buf, sizeof(buf));
|
leds: turris-omnia: Do not use SMBUS calls
The leds-turris-omnia driver uses three function for I2C access:
- i2c_smbus_write_byte_data() and i2c_smbus_read_byte_data(), which
cause an emulated SMBUS transfer,
- i2c_master_send(), which causes an ordinary I2C transfer.
The Turris Omnia MCU LED controller is not semantically SMBUS, it
operates as a simple I2C bus. It does not implement any of the SMBUS
specific features, like PEC, or procedure calls, or anything. Moreover
the I2C controller driver also does not implement SMBUS, and so the
emulated SMBUS procedure from drivers/i2c/i2c-core-smbus.c is used for
the SMBUS calls, which gives an unnecessary overhead.
When I first wrote the driver, I was unaware of these facts, and I
simply used the first function that worked.
Drop the I2C SMBUS calls and instead use simple I2C transfers.
Fixes: 089381b27abe ("leds: initial support for Turris Omnia LEDs")
Signed-off-by: Marek Behún <kabel@kernel.org>
Link: https://lore.kernel.org/r/20230918161104.20860-2-kabel@kernel.org
Signed-off-by: Lee Jones <lee@kernel.org>
2023-09-18 16:11:01 +00:00
|
|
|
|
2023-10-16 14:15:38 +00:00
|
|
|
return ret < 0 ? ret : 0;
|
leds: turris-omnia: Do not use SMBUS calls
The leds-turris-omnia driver uses three function for I2C access:
- i2c_smbus_write_byte_data() and i2c_smbus_read_byte_data(), which
cause an emulated SMBUS transfer,
- i2c_master_send(), which causes an ordinary I2C transfer.
The Turris Omnia MCU LED controller is not semantically SMBUS, it
operates as a simple I2C bus. It does not implement any of the SMBUS
specific features, like PEC, or procedure calls, or anything. Moreover
the I2C controller driver also does not implement SMBUS, and so the
emulated SMBUS procedure from drivers/i2c/i2c-core-smbus.c is used for
the SMBUS calls, which gives an unnecessary overhead.
When I first wrote the driver, I was unaware of these facts, and I
simply used the first function that worked.
Drop the I2C SMBUS calls and instead use simple I2C transfers.
Fixes: 089381b27abe ("leds: initial support for Turris Omnia LEDs")
Signed-off-by: Marek Behún <kabel@kernel.org>
Link: https://lore.kernel.org/r/20230918161104.20860-2-kabel@kernel.org
Signed-off-by: Lee Jones <lee@kernel.org>
2023-09-18 16:11:01 +00:00
|
|
|
}
|
|
|
|
|
2023-09-18 16:11:04 +00:00
|
|
|
static int omnia_cmd_read_raw(struct i2c_adapter *adapter, u8 addr, u8 cmd,
|
|
|
|
void *reply, size_t len)
|
leds: turris-omnia: Do not use SMBUS calls
The leds-turris-omnia driver uses three function for I2C access:
- i2c_smbus_write_byte_data() and i2c_smbus_read_byte_data(), which
cause an emulated SMBUS transfer,
- i2c_master_send(), which causes an ordinary I2C transfer.
The Turris Omnia MCU LED controller is not semantically SMBUS, it
operates as a simple I2C bus. It does not implement any of the SMBUS
specific features, like PEC, or procedure calls, or anything. Moreover
the I2C controller driver also does not implement SMBUS, and so the
emulated SMBUS procedure from drivers/i2c/i2c-core-smbus.c is used for
the SMBUS calls, which gives an unnecessary overhead.
When I first wrote the driver, I was unaware of these facts, and I
simply used the first function that worked.
Drop the I2C SMBUS calls and instead use simple I2C transfers.
Fixes: 089381b27abe ("leds: initial support for Turris Omnia LEDs")
Signed-off-by: Marek Behún <kabel@kernel.org>
Link: https://lore.kernel.org/r/20230918161104.20860-2-kabel@kernel.org
Signed-off-by: Lee Jones <lee@kernel.org>
2023-09-18 16:11:01 +00:00
|
|
|
{
|
|
|
|
struct i2c_msg msgs[2];
|
|
|
|
int ret;
|
|
|
|
|
2023-09-18 16:11:04 +00:00
|
|
|
msgs[0].addr = addr;
|
leds: turris-omnia: Do not use SMBUS calls
The leds-turris-omnia driver uses three function for I2C access:
- i2c_smbus_write_byte_data() and i2c_smbus_read_byte_data(), which
cause an emulated SMBUS transfer,
- i2c_master_send(), which causes an ordinary I2C transfer.
The Turris Omnia MCU LED controller is not semantically SMBUS, it
operates as a simple I2C bus. It does not implement any of the SMBUS
specific features, like PEC, or procedure calls, or anything. Moreover
the I2C controller driver also does not implement SMBUS, and so the
emulated SMBUS procedure from drivers/i2c/i2c-core-smbus.c is used for
the SMBUS calls, which gives an unnecessary overhead.
When I first wrote the driver, I was unaware of these facts, and I
simply used the first function that worked.
Drop the I2C SMBUS calls and instead use simple I2C transfers.
Fixes: 089381b27abe ("leds: initial support for Turris Omnia LEDs")
Signed-off-by: Marek Behún <kabel@kernel.org>
Link: https://lore.kernel.org/r/20230918161104.20860-2-kabel@kernel.org
Signed-off-by: Lee Jones <lee@kernel.org>
2023-09-18 16:11:01 +00:00
|
|
|
msgs[0].flags = 0;
|
|
|
|
msgs[0].len = 1;
|
|
|
|
msgs[0].buf = &cmd;
|
2023-09-18 16:11:04 +00:00
|
|
|
msgs[1].addr = addr;
|
leds: turris-omnia: Do not use SMBUS calls
The leds-turris-omnia driver uses three function for I2C access:
- i2c_smbus_write_byte_data() and i2c_smbus_read_byte_data(), which
cause an emulated SMBUS transfer,
- i2c_master_send(), which causes an ordinary I2C transfer.
The Turris Omnia MCU LED controller is not semantically SMBUS, it
operates as a simple I2C bus. It does not implement any of the SMBUS
specific features, like PEC, or procedure calls, or anything. Moreover
the I2C controller driver also does not implement SMBUS, and so the
emulated SMBUS procedure from drivers/i2c/i2c-core-smbus.c is used for
the SMBUS calls, which gives an unnecessary overhead.
When I first wrote the driver, I was unaware of these facts, and I
simply used the first function that worked.
Drop the I2C SMBUS calls and instead use simple I2C transfers.
Fixes: 089381b27abe ("leds: initial support for Turris Omnia LEDs")
Signed-off-by: Marek Behún <kabel@kernel.org>
Link: https://lore.kernel.org/r/20230918161104.20860-2-kabel@kernel.org
Signed-off-by: Lee Jones <lee@kernel.org>
2023-09-18 16:11:01 +00:00
|
|
|
msgs[1].flags = I2C_M_RD;
|
2023-09-18 16:11:04 +00:00
|
|
|
msgs[1].len = len;
|
|
|
|
msgs[1].buf = reply;
|
leds: turris-omnia: Do not use SMBUS calls
The leds-turris-omnia driver uses three function for I2C access:
- i2c_smbus_write_byte_data() and i2c_smbus_read_byte_data(), which
cause an emulated SMBUS transfer,
- i2c_master_send(), which causes an ordinary I2C transfer.
The Turris Omnia MCU LED controller is not semantically SMBUS, it
operates as a simple I2C bus. It does not implement any of the SMBUS
specific features, like PEC, or procedure calls, or anything. Moreover
the I2C controller driver also does not implement SMBUS, and so the
emulated SMBUS procedure from drivers/i2c/i2c-core-smbus.c is used for
the SMBUS calls, which gives an unnecessary overhead.
When I first wrote the driver, I was unaware of these facts, and I
simply used the first function that worked.
Drop the I2C SMBUS calls and instead use simple I2C transfers.
Fixes: 089381b27abe ("leds: initial support for Turris Omnia LEDs")
Signed-off-by: Marek Behún <kabel@kernel.org>
Link: https://lore.kernel.org/r/20230918161104.20860-2-kabel@kernel.org
Signed-off-by: Lee Jones <lee@kernel.org>
2023-09-18 16:11:01 +00:00
|
|
|
|
2023-09-18 16:11:04 +00:00
|
|
|
ret = i2c_transfer(adapter, msgs, ARRAY_SIZE(msgs));
|
leds: turris-omnia: Do not use SMBUS calls
The leds-turris-omnia driver uses three function for I2C access:
- i2c_smbus_write_byte_data() and i2c_smbus_read_byte_data(), which
cause an emulated SMBUS transfer,
- i2c_master_send(), which causes an ordinary I2C transfer.
The Turris Omnia MCU LED controller is not semantically SMBUS, it
operates as a simple I2C bus. It does not implement any of the SMBUS
specific features, like PEC, or procedure calls, or anything. Moreover
the I2C controller driver also does not implement SMBUS, and so the
emulated SMBUS procedure from drivers/i2c/i2c-core-smbus.c is used for
the SMBUS calls, which gives an unnecessary overhead.
When I first wrote the driver, I was unaware of these facts, and I
simply used the first function that worked.
Drop the I2C SMBUS calls and instead use simple I2C transfers.
Fixes: 089381b27abe ("leds: initial support for Turris Omnia LEDs")
Signed-off-by: Marek Behún <kabel@kernel.org>
Link: https://lore.kernel.org/r/20230918161104.20860-2-kabel@kernel.org
Signed-off-by: Lee Jones <lee@kernel.org>
2023-09-18 16:11:01 +00:00
|
|
|
if (likely(ret == ARRAY_SIZE(msgs)))
|
2023-10-16 14:15:38 +00:00
|
|
|
return 0;
|
leds: turris-omnia: Do not use SMBUS calls
The leds-turris-omnia driver uses three function for I2C access:
- i2c_smbus_write_byte_data() and i2c_smbus_read_byte_data(), which
cause an emulated SMBUS transfer,
- i2c_master_send(), which causes an ordinary I2C transfer.
The Turris Omnia MCU LED controller is not semantically SMBUS, it
operates as a simple I2C bus. It does not implement any of the SMBUS
specific features, like PEC, or procedure calls, or anything. Moreover
the I2C controller driver also does not implement SMBUS, and so the
emulated SMBUS procedure from drivers/i2c/i2c-core-smbus.c is used for
the SMBUS calls, which gives an unnecessary overhead.
When I first wrote the driver, I was unaware of these facts, and I
simply used the first function that worked.
Drop the I2C SMBUS calls and instead use simple I2C transfers.
Fixes: 089381b27abe ("leds: initial support for Turris Omnia LEDs")
Signed-off-by: Marek Behún <kabel@kernel.org>
Link: https://lore.kernel.org/r/20230918161104.20860-2-kabel@kernel.org
Signed-off-by: Lee Jones <lee@kernel.org>
2023-09-18 16:11:01 +00:00
|
|
|
else if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
else
|
|
|
|
return -EIO;
|
|
|
|
}
|
|
|
|
|
2023-09-18 16:11:04 +00:00
|
|
|
static int omnia_cmd_read_u8(const struct i2c_client *client, u8 cmd)
|
|
|
|
{
|
|
|
|
u8 reply;
|
2023-10-16 14:15:38 +00:00
|
|
|
int err;
|
2023-09-18 16:11:04 +00:00
|
|
|
|
2023-10-16 14:15:38 +00:00
|
|
|
err = omnia_cmd_read_raw(client->adapter, client->addr, cmd, &reply, 1);
|
|
|
|
if (err)
|
|
|
|
return err;
|
2023-09-18 16:11:04 +00:00
|
|
|
|
|
|
|
return reply;
|
|
|
|
}
|
|
|
|
|
leds: turris-omnia: Make set_brightness() more efficient
Implement caching of the LED color and state values that are sent to MCU
in order to make the set_brightness() operation more efficient by
avoiding I2C transactions which are not needed.
On Turris Omnia's MCU, which acts as the RGB LED controller, each LED
has a RGB color, and a ON/OFF state, which are configurable via I2C
commands CMD_LED_COLOR and CMD_LED_STATE.
The CMD_LED_COLOR command sends 5 bytes and the CMD_LED_STATE command 2
bytes over the I2C bus, which operates at 100 kHz. With I2C overhead
this allows ~1670 color changing commands and ~3200 state changing
commands per second (or around 1000 color + state changes per second).
This may seem more than enough, but the issue is that the I2C bus is
shared with another peripheral, the MCU. The MCU exposes an interrupt
interface, and it can trigger hundreds of interrupts per second. Each
time, we need to read the interrupt state register over this I2C bus.
Whenever we are sending a LED color/state changing command, the
interrupt reading is waiting.
Currently, every time LED brightness or LED multi intensity is changed,
we send a CMD_LED_STATE command, and if the computed color (brightness
adjusted multi_intensity) is non-zero, we also send a CMD_LED_COLOR
command.
Consider for example the situation when we have a netdev trigger enabled
for a LED. The netdev trigger does not change the LED color, only the
brightness (either to 0 or to currently configured brightness), and so
there is no need to send the CMD_LED_COLOR command. But each change of
brightness to 0 sends one CMD_LED_STATE command, and each change of
brightness to max_brightness sends one CMD_LED_STATE command and one
CMD_LED_COLOR command:
set_brightness(0) -> CMD_LED_STATE
set_brightness(255) -> CMD_LED_STATE + CMD_LED_COLOR
(unnecessary)
We can avoid the unnecessary I2C transactions if we cache the values of
state and color that are sent to the controller. If the color does not
change from the one previously sent, there is no need to do the
CMD_LED_COLOR I2C transaction, and if the state does not change, there
is no need to do the CMD_LED_STATE transaction.
Because we need to make sure that our cached values are consistent with
the controller state, add explicit setting of the LED color to white at
probe time (this is the default setting when MCU resets, but does not
necessarily need to be the case, for example if U-Boot played with the
LED colors).
Signed-off-by: Marek Behún <kabel@kernel.org>
Link: https://lore.kernel.org/r/20230918161104.20860-3-kabel@kernel.org
Signed-off-by: Lee Jones <lee@kernel.org>
2023-09-18 16:11:02 +00:00
|
|
|
static int omnia_led_send_color_cmd(const struct i2c_client *client,
|
|
|
|
struct omnia_led *led)
|
|
|
|
{
|
|
|
|
char cmd[5];
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
cmd[0] = CMD_LED_COLOR;
|
|
|
|
cmd[1] = led->reg;
|
|
|
|
cmd[2] = led->subled_info[0].brightness;
|
|
|
|
cmd[3] = led->subled_info[1].brightness;
|
|
|
|
cmd[4] = led->subled_info[2].brightness;
|
|
|
|
|
|
|
|
/* Send the color change command */
|
|
|
|
ret = i2c_master_send(client, cmd, 5);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
/* Cache the RGB channel brightnesses */
|
|
|
|
for (int i = 0; i < OMNIA_LED_NUM_CHANNELS; ++i)
|
|
|
|
led->cached_channels[i] = led->subled_info[i].brightness;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Determine if the computed RGB channels are different from the cached ones */
|
|
|
|
static bool omnia_led_channels_changed(struct omnia_led *led)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < OMNIA_LED_NUM_CHANNELS; ++i)
|
|
|
|
if (led->subled_info[i].brightness != led->cached_channels[i])
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-07-23 12:53:19 +00:00
|
|
|
static int omnia_led_brightness_set_blocking(struct led_classdev *cdev,
|
|
|
|
enum led_brightness brightness)
|
|
|
|
{
|
|
|
|
struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev);
|
|
|
|
struct omnia_leds *leds = dev_get_drvdata(cdev->dev->parent);
|
|
|
|
struct omnia_led *led = to_omnia_led(mc_cdev);
|
leds: turris-omnia: Make set_brightness() more efficient
Implement caching of the LED color and state values that are sent to MCU
in order to make the set_brightness() operation more efficient by
avoiding I2C transactions which are not needed.
On Turris Omnia's MCU, which acts as the RGB LED controller, each LED
has a RGB color, and a ON/OFF state, which are configurable via I2C
commands CMD_LED_COLOR and CMD_LED_STATE.
The CMD_LED_COLOR command sends 5 bytes and the CMD_LED_STATE command 2
bytes over the I2C bus, which operates at 100 kHz. With I2C overhead
this allows ~1670 color changing commands and ~3200 state changing
commands per second (or around 1000 color + state changes per second).
This may seem more than enough, but the issue is that the I2C bus is
shared with another peripheral, the MCU. The MCU exposes an interrupt
interface, and it can trigger hundreds of interrupts per second. Each
time, we need to read the interrupt state register over this I2C bus.
Whenever we are sending a LED color/state changing command, the
interrupt reading is waiting.
Currently, every time LED brightness or LED multi intensity is changed,
we send a CMD_LED_STATE command, and if the computed color (brightness
adjusted multi_intensity) is non-zero, we also send a CMD_LED_COLOR
command.
Consider for example the situation when we have a netdev trigger enabled
for a LED. The netdev trigger does not change the LED color, only the
brightness (either to 0 or to currently configured brightness), and so
there is no need to send the CMD_LED_COLOR command. But each change of
brightness to 0 sends one CMD_LED_STATE command, and each change of
brightness to max_brightness sends one CMD_LED_STATE command and one
CMD_LED_COLOR command:
set_brightness(0) -> CMD_LED_STATE
set_brightness(255) -> CMD_LED_STATE + CMD_LED_COLOR
(unnecessary)
We can avoid the unnecessary I2C transactions if we cache the values of
state and color that are sent to the controller. If the color does not
change from the one previously sent, there is no need to do the
CMD_LED_COLOR I2C transaction, and if the state does not change, there
is no need to do the CMD_LED_STATE transaction.
Because we need to make sure that our cached values are consistent with
the controller state, add explicit setting of the LED color to white at
probe time (this is the default setting when MCU resets, but does not
necessarily need to be the case, for example if U-Boot played with the
LED colors).
Signed-off-by: Marek Behún <kabel@kernel.org>
Link: https://lore.kernel.org/r/20230918161104.20860-3-kabel@kernel.org
Signed-off-by: Lee Jones <lee@kernel.org>
2023-09-18 16:11:02 +00:00
|
|
|
int err = 0;
|
2020-07-23 12:53:19 +00:00
|
|
|
|
|
|
|
mutex_lock(&leds->lock);
|
|
|
|
|
leds: turris-omnia: Make set_brightness() more efficient
Implement caching of the LED color and state values that are sent to MCU
in order to make the set_brightness() operation more efficient by
avoiding I2C transactions which are not needed.
On Turris Omnia's MCU, which acts as the RGB LED controller, each LED
has a RGB color, and a ON/OFF state, which are configurable via I2C
commands CMD_LED_COLOR and CMD_LED_STATE.
The CMD_LED_COLOR command sends 5 bytes and the CMD_LED_STATE command 2
bytes over the I2C bus, which operates at 100 kHz. With I2C overhead
this allows ~1670 color changing commands and ~3200 state changing
commands per second (or around 1000 color + state changes per second).
This may seem more than enough, but the issue is that the I2C bus is
shared with another peripheral, the MCU. The MCU exposes an interrupt
interface, and it can trigger hundreds of interrupts per second. Each
time, we need to read the interrupt state register over this I2C bus.
Whenever we are sending a LED color/state changing command, the
interrupt reading is waiting.
Currently, every time LED brightness or LED multi intensity is changed,
we send a CMD_LED_STATE command, and if the computed color (brightness
adjusted multi_intensity) is non-zero, we also send a CMD_LED_COLOR
command.
Consider for example the situation when we have a netdev trigger enabled
for a LED. The netdev trigger does not change the LED color, only the
brightness (either to 0 or to currently configured brightness), and so
there is no need to send the CMD_LED_COLOR command. But each change of
brightness to 0 sends one CMD_LED_STATE command, and each change of
brightness to max_brightness sends one CMD_LED_STATE command and one
CMD_LED_COLOR command:
set_brightness(0) -> CMD_LED_STATE
set_brightness(255) -> CMD_LED_STATE + CMD_LED_COLOR
(unnecessary)
We can avoid the unnecessary I2C transactions if we cache the values of
state and color that are sent to the controller. If the color does not
change from the one previously sent, there is no need to do the
CMD_LED_COLOR I2C transaction, and if the state does not change, there
is no need to do the CMD_LED_STATE transaction.
Because we need to make sure that our cached values are consistent with
the controller state, add explicit setting of the LED color to white at
probe time (this is the default setting when MCU resets, but does not
necessarily need to be the case, for example if U-Boot played with the
LED colors).
Signed-off-by: Marek Behún <kabel@kernel.org>
Link: https://lore.kernel.org/r/20230918161104.20860-3-kabel@kernel.org
Signed-off-by: Lee Jones <lee@kernel.org>
2023-09-18 16:11:02 +00:00
|
|
|
/*
|
|
|
|
* Only recalculate RGB brightnesses from intensities if brightness is
|
leds: turris-omnia: Support HW controlled mode via private trigger
Add support for enabling MCU controlled mode of the Turris Omnia LEDs
via a LED private trigger called "omnia-mcu". Recall that private LED
triggers will only be listed in the sysfs trigger file for LEDs that
support them (currently there is no user of this mechanism).
When in MCU controlled mode, the user can still set LED color, but the
blinking is done by MCU, which does different things for different LEDs:
- WAN LED is blinked according to the LED[0] pin of the WAN PHY
- LAN LEDs are blinked according to the LED[0] output of the
corresponding port of the LAN switch
- PCIe LEDs are blinked according to the logical OR of the MiniPCIe port
LED pins
In the future I want to make the netdev trigger to transparently offload
the blinking to the HW if user sets compatible settings for the netdev
trigger (for LEDs associated with network devices).
There was some work on this already, and hopefully we will be able to
complete it sometime, but for now there are still multiple blockers for
this, and even if there weren't, we still would not be able to configure
HW controlled mode for the LEDs associated with MiniPCIe ports.
In the meantime let's support HW controlled mode via the private LED
trigger mechanism. If, in the future, we manage to complete the netdev
trigger offloading, we can still keep this private trigger for backwards
compatibility, if needed.
We also set "omnia-mcu" to cdev->default_trigger, so that the MCU keeps
control until the user first wants to take over it. If a different
default trigger is specified in device-tree via the
'linux,default-trigger' property, LED class will overwrite
cdev->default_trigger, and so the DT property will be respected.
Signed-off-by: Marek Behún <kabel@kernel.org>
Link: https://lore.kernel.org/r/20230918161104.20860-4-kabel@kernel.org
Signed-off-by: Lee Jones <lee@kernel.org>
2023-09-18 16:11:03 +00:00
|
|
|
* non-zero (if it is zero and the LED is in HW blinking mode, we use
|
|
|
|
* max_brightness as brightness). Otherwise we won't be using them and
|
|
|
|
* we can save ourselves some software divisions (Omnia's CPU does not
|
|
|
|
* implement the division instruction).
|
leds: turris-omnia: Make set_brightness() more efficient
Implement caching of the LED color and state values that are sent to MCU
in order to make the set_brightness() operation more efficient by
avoiding I2C transactions which are not needed.
On Turris Omnia's MCU, which acts as the RGB LED controller, each LED
has a RGB color, and a ON/OFF state, which are configurable via I2C
commands CMD_LED_COLOR and CMD_LED_STATE.
The CMD_LED_COLOR command sends 5 bytes and the CMD_LED_STATE command 2
bytes over the I2C bus, which operates at 100 kHz. With I2C overhead
this allows ~1670 color changing commands and ~3200 state changing
commands per second (or around 1000 color + state changes per second).
This may seem more than enough, but the issue is that the I2C bus is
shared with another peripheral, the MCU. The MCU exposes an interrupt
interface, and it can trigger hundreds of interrupts per second. Each
time, we need to read the interrupt state register over this I2C bus.
Whenever we are sending a LED color/state changing command, the
interrupt reading is waiting.
Currently, every time LED brightness or LED multi intensity is changed,
we send a CMD_LED_STATE command, and if the computed color (brightness
adjusted multi_intensity) is non-zero, we also send a CMD_LED_COLOR
command.
Consider for example the situation when we have a netdev trigger enabled
for a LED. The netdev trigger does not change the LED color, only the
brightness (either to 0 or to currently configured brightness), and so
there is no need to send the CMD_LED_COLOR command. But each change of
brightness to 0 sends one CMD_LED_STATE command, and each change of
brightness to max_brightness sends one CMD_LED_STATE command and one
CMD_LED_COLOR command:
set_brightness(0) -> CMD_LED_STATE
set_brightness(255) -> CMD_LED_STATE + CMD_LED_COLOR
(unnecessary)
We can avoid the unnecessary I2C transactions if we cache the values of
state and color that are sent to the controller. If the color does not
change from the one previously sent, there is no need to do the
CMD_LED_COLOR I2C transaction, and if the state does not change, there
is no need to do the CMD_LED_STATE transaction.
Because we need to make sure that our cached values are consistent with
the controller state, add explicit setting of the LED color to white at
probe time (this is the default setting when MCU resets, but does not
necessarily need to be the case, for example if U-Boot played with the
LED colors).
Signed-off-by: Marek Behún <kabel@kernel.org>
Link: https://lore.kernel.org/r/20230918161104.20860-3-kabel@kernel.org
Signed-off-by: Lee Jones <lee@kernel.org>
2023-09-18 16:11:02 +00:00
|
|
|
*/
|
leds: turris-omnia: Support HW controlled mode via private trigger
Add support for enabling MCU controlled mode of the Turris Omnia LEDs
via a LED private trigger called "omnia-mcu". Recall that private LED
triggers will only be listed in the sysfs trigger file for LEDs that
support them (currently there is no user of this mechanism).
When in MCU controlled mode, the user can still set LED color, but the
blinking is done by MCU, which does different things for different LEDs:
- WAN LED is blinked according to the LED[0] pin of the WAN PHY
- LAN LEDs are blinked according to the LED[0] output of the
corresponding port of the LAN switch
- PCIe LEDs are blinked according to the logical OR of the MiniPCIe port
LED pins
In the future I want to make the netdev trigger to transparently offload
the blinking to the HW if user sets compatible settings for the netdev
trigger (for LEDs associated with network devices).
There was some work on this already, and hopefully we will be able to
complete it sometime, but for now there are still multiple blockers for
this, and even if there weren't, we still would not be able to configure
HW controlled mode for the LEDs associated with MiniPCIe ports.
In the meantime let's support HW controlled mode via the private LED
trigger mechanism. If, in the future, we manage to complete the netdev
trigger offloading, we can still keep this private trigger for backwards
compatibility, if needed.
We also set "omnia-mcu" to cdev->default_trigger, so that the MCU keeps
control until the user first wants to take over it. If a different
default trigger is specified in device-tree via the
'linux,default-trigger' property, LED class will overwrite
cdev->default_trigger, and so the DT property will be respected.
Signed-off-by: Marek Behún <kabel@kernel.org>
Link: https://lore.kernel.org/r/20230918161104.20860-4-kabel@kernel.org
Signed-off-by: Lee Jones <lee@kernel.org>
2023-09-18 16:11:03 +00:00
|
|
|
if (brightness || led->hwtrig) {
|
|
|
|
led_mc_calc_color_components(mc_cdev, brightness ?:
|
|
|
|
cdev->max_brightness);
|
leds: turris-omnia: Make set_brightness() more efficient
Implement caching of the LED color and state values that are sent to MCU
in order to make the set_brightness() operation more efficient by
avoiding I2C transactions which are not needed.
On Turris Omnia's MCU, which acts as the RGB LED controller, each LED
has a RGB color, and a ON/OFF state, which are configurable via I2C
commands CMD_LED_COLOR and CMD_LED_STATE.
The CMD_LED_COLOR command sends 5 bytes and the CMD_LED_STATE command 2
bytes over the I2C bus, which operates at 100 kHz. With I2C overhead
this allows ~1670 color changing commands and ~3200 state changing
commands per second (or around 1000 color + state changes per second).
This may seem more than enough, but the issue is that the I2C bus is
shared with another peripheral, the MCU. The MCU exposes an interrupt
interface, and it can trigger hundreds of interrupts per second. Each
time, we need to read the interrupt state register over this I2C bus.
Whenever we are sending a LED color/state changing command, the
interrupt reading is waiting.
Currently, every time LED brightness or LED multi intensity is changed,
we send a CMD_LED_STATE command, and if the computed color (brightness
adjusted multi_intensity) is non-zero, we also send a CMD_LED_COLOR
command.
Consider for example the situation when we have a netdev trigger enabled
for a LED. The netdev trigger does not change the LED color, only the
brightness (either to 0 or to currently configured brightness), and so
there is no need to send the CMD_LED_COLOR command. But each change of
brightness to 0 sends one CMD_LED_STATE command, and each change of
brightness to max_brightness sends one CMD_LED_STATE command and one
CMD_LED_COLOR command:
set_brightness(0) -> CMD_LED_STATE
set_brightness(255) -> CMD_LED_STATE + CMD_LED_COLOR
(unnecessary)
We can avoid the unnecessary I2C transactions if we cache the values of
state and color that are sent to the controller. If the color does not
change from the one previously sent, there is no need to do the
CMD_LED_COLOR I2C transaction, and if the state does not change, there
is no need to do the CMD_LED_STATE transaction.
Because we need to make sure that our cached values are consistent with
the controller state, add explicit setting of the LED color to white at
probe time (this is the default setting when MCU resets, but does not
necessarily need to be the case, for example if U-Boot played with the
LED colors).
Signed-off-by: Marek Behún <kabel@kernel.org>
Link: https://lore.kernel.org/r/20230918161104.20860-3-kabel@kernel.org
Signed-off-by: Lee Jones <lee@kernel.org>
2023-09-18 16:11:02 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Send color command only if brightness is non-zero and the RGB
|
|
|
|
* channel brightnesses changed.
|
|
|
|
*/
|
|
|
|
if (omnia_led_channels_changed(led))
|
|
|
|
err = omnia_led_send_color_cmd(leds->client, led);
|
|
|
|
}
|
2020-07-23 12:53:19 +00:00
|
|
|
|
leds: turris-omnia: Support HW controlled mode via private trigger
Add support for enabling MCU controlled mode of the Turris Omnia LEDs
via a LED private trigger called "omnia-mcu". Recall that private LED
triggers will only be listed in the sysfs trigger file for LEDs that
support them (currently there is no user of this mechanism).
When in MCU controlled mode, the user can still set LED color, but the
blinking is done by MCU, which does different things for different LEDs:
- WAN LED is blinked according to the LED[0] pin of the WAN PHY
- LAN LEDs are blinked according to the LED[0] output of the
corresponding port of the LAN switch
- PCIe LEDs are blinked according to the logical OR of the MiniPCIe port
LED pins
In the future I want to make the netdev trigger to transparently offload
the blinking to the HW if user sets compatible settings for the netdev
trigger (for LEDs associated with network devices).
There was some work on this already, and hopefully we will be able to
complete it sometime, but for now there are still multiple blockers for
this, and even if there weren't, we still would not be able to configure
HW controlled mode for the LEDs associated with MiniPCIe ports.
In the meantime let's support HW controlled mode via the private LED
trigger mechanism. If, in the future, we manage to complete the netdev
trigger offloading, we can still keep this private trigger for backwards
compatibility, if needed.
We also set "omnia-mcu" to cdev->default_trigger, so that the MCU keeps
control until the user first wants to take over it. If a different
default trigger is specified in device-tree via the
'linux,default-trigger' property, LED class will overwrite
cdev->default_trigger, and so the DT property will be respected.
Signed-off-by: Marek Behún <kabel@kernel.org>
Link: https://lore.kernel.org/r/20230918161104.20860-4-kabel@kernel.org
Signed-off-by: Lee Jones <lee@kernel.org>
2023-09-18 16:11:03 +00:00
|
|
|
/*
|
|
|
|
* Send on/off state change only if (bool)brightness changed and the LED
|
|
|
|
* is not being blinked by HW.
|
|
|
|
*/
|
|
|
|
if (!err && !led->hwtrig && !brightness != !led->on) {
|
leds: turris-omnia: Make set_brightness() more efficient
Implement caching of the LED color and state values that are sent to MCU
in order to make the set_brightness() operation more efficient by
avoiding I2C transactions which are not needed.
On Turris Omnia's MCU, which acts as the RGB LED controller, each LED
has a RGB color, and a ON/OFF state, which are configurable via I2C
commands CMD_LED_COLOR and CMD_LED_STATE.
The CMD_LED_COLOR command sends 5 bytes and the CMD_LED_STATE command 2
bytes over the I2C bus, which operates at 100 kHz. With I2C overhead
this allows ~1670 color changing commands and ~3200 state changing
commands per second (or around 1000 color + state changes per second).
This may seem more than enough, but the issue is that the I2C bus is
shared with another peripheral, the MCU. The MCU exposes an interrupt
interface, and it can trigger hundreds of interrupts per second. Each
time, we need to read the interrupt state register over this I2C bus.
Whenever we are sending a LED color/state changing command, the
interrupt reading is waiting.
Currently, every time LED brightness or LED multi intensity is changed,
we send a CMD_LED_STATE command, and if the computed color (brightness
adjusted multi_intensity) is non-zero, we also send a CMD_LED_COLOR
command.
Consider for example the situation when we have a netdev trigger enabled
for a LED. The netdev trigger does not change the LED color, only the
brightness (either to 0 or to currently configured brightness), and so
there is no need to send the CMD_LED_COLOR command. But each change of
brightness to 0 sends one CMD_LED_STATE command, and each change of
brightness to max_brightness sends one CMD_LED_STATE command and one
CMD_LED_COLOR command:
set_brightness(0) -> CMD_LED_STATE
set_brightness(255) -> CMD_LED_STATE + CMD_LED_COLOR
(unnecessary)
We can avoid the unnecessary I2C transactions if we cache the values of
state and color that are sent to the controller. If the color does not
change from the one previously sent, there is no need to do the
CMD_LED_COLOR I2C transaction, and if the state does not change, there
is no need to do the CMD_LED_STATE transaction.
Because we need to make sure that our cached values are consistent with
the controller state, add explicit setting of the LED color to white at
probe time (this is the default setting when MCU resets, but does not
necessarily need to be the case, for example if U-Boot played with the
LED colors).
Signed-off-by: Marek Behún <kabel@kernel.org>
Link: https://lore.kernel.org/r/20230918161104.20860-3-kabel@kernel.org
Signed-off-by: Lee Jones <lee@kernel.org>
2023-09-18 16:11:02 +00:00
|
|
|
u8 state = CMD_LED_STATE_LED(led->reg);
|
2020-07-23 12:53:19 +00:00
|
|
|
|
leds: turris-omnia: Make set_brightness() more efficient
Implement caching of the LED color and state values that are sent to MCU
in order to make the set_brightness() operation more efficient by
avoiding I2C transactions which are not needed.
On Turris Omnia's MCU, which acts as the RGB LED controller, each LED
has a RGB color, and a ON/OFF state, which are configurable via I2C
commands CMD_LED_COLOR and CMD_LED_STATE.
The CMD_LED_COLOR command sends 5 bytes and the CMD_LED_STATE command 2
bytes over the I2C bus, which operates at 100 kHz. With I2C overhead
this allows ~1670 color changing commands and ~3200 state changing
commands per second (or around 1000 color + state changes per second).
This may seem more than enough, but the issue is that the I2C bus is
shared with another peripheral, the MCU. The MCU exposes an interrupt
interface, and it can trigger hundreds of interrupts per second. Each
time, we need to read the interrupt state register over this I2C bus.
Whenever we are sending a LED color/state changing command, the
interrupt reading is waiting.
Currently, every time LED brightness or LED multi intensity is changed,
we send a CMD_LED_STATE command, and if the computed color (brightness
adjusted multi_intensity) is non-zero, we also send a CMD_LED_COLOR
command.
Consider for example the situation when we have a netdev trigger enabled
for a LED. The netdev trigger does not change the LED color, only the
brightness (either to 0 or to currently configured brightness), and so
there is no need to send the CMD_LED_COLOR command. But each change of
brightness to 0 sends one CMD_LED_STATE command, and each change of
brightness to max_brightness sends one CMD_LED_STATE command and one
CMD_LED_COLOR command:
set_brightness(0) -> CMD_LED_STATE
set_brightness(255) -> CMD_LED_STATE + CMD_LED_COLOR
(unnecessary)
We can avoid the unnecessary I2C transactions if we cache the values of
state and color that are sent to the controller. If the color does not
change from the one previously sent, there is no need to do the
CMD_LED_COLOR I2C transaction, and if the state does not change, there
is no need to do the CMD_LED_STATE transaction.
Because we need to make sure that our cached values are consistent with
the controller state, add explicit setting of the LED color to white at
probe time (this is the default setting when MCU resets, but does not
necessarily need to be the case, for example if U-Boot played with the
LED colors).
Signed-off-by: Marek Behún <kabel@kernel.org>
Link: https://lore.kernel.org/r/20230918161104.20860-3-kabel@kernel.org
Signed-off-by: Lee Jones <lee@kernel.org>
2023-09-18 16:11:02 +00:00
|
|
|
if (brightness)
|
|
|
|
state |= CMD_LED_STATE_ON;
|
2020-07-23 12:53:19 +00:00
|
|
|
|
leds: turris-omnia: Make set_brightness() more efficient
Implement caching of the LED color and state values that are sent to MCU
in order to make the set_brightness() operation more efficient by
avoiding I2C transactions which are not needed.
On Turris Omnia's MCU, which acts as the RGB LED controller, each LED
has a RGB color, and a ON/OFF state, which are configurable via I2C
commands CMD_LED_COLOR and CMD_LED_STATE.
The CMD_LED_COLOR command sends 5 bytes and the CMD_LED_STATE command 2
bytes over the I2C bus, which operates at 100 kHz. With I2C overhead
this allows ~1670 color changing commands and ~3200 state changing
commands per second (or around 1000 color + state changes per second).
This may seem more than enough, but the issue is that the I2C bus is
shared with another peripheral, the MCU. The MCU exposes an interrupt
interface, and it can trigger hundreds of interrupts per second. Each
time, we need to read the interrupt state register over this I2C bus.
Whenever we are sending a LED color/state changing command, the
interrupt reading is waiting.
Currently, every time LED brightness or LED multi intensity is changed,
we send a CMD_LED_STATE command, and if the computed color (brightness
adjusted multi_intensity) is non-zero, we also send a CMD_LED_COLOR
command.
Consider for example the situation when we have a netdev trigger enabled
for a LED. The netdev trigger does not change the LED color, only the
brightness (either to 0 or to currently configured brightness), and so
there is no need to send the CMD_LED_COLOR command. But each change of
brightness to 0 sends one CMD_LED_STATE command, and each change of
brightness to max_brightness sends one CMD_LED_STATE command and one
CMD_LED_COLOR command:
set_brightness(0) -> CMD_LED_STATE
set_brightness(255) -> CMD_LED_STATE + CMD_LED_COLOR
(unnecessary)
We can avoid the unnecessary I2C transactions if we cache the values of
state and color that are sent to the controller. If the color does not
change from the one previously sent, there is no need to do the
CMD_LED_COLOR I2C transaction, and if the state does not change, there
is no need to do the CMD_LED_STATE transaction.
Because we need to make sure that our cached values are consistent with
the controller state, add explicit setting of the LED color to white at
probe time (this is the default setting when MCU resets, but does not
necessarily need to be the case, for example if U-Boot played with the
LED colors).
Signed-off-by: Marek Behún <kabel@kernel.org>
Link: https://lore.kernel.org/r/20230918161104.20860-3-kabel@kernel.org
Signed-off-by: Lee Jones <lee@kernel.org>
2023-09-18 16:11:02 +00:00
|
|
|
err = omnia_cmd_write_u8(leds->client, CMD_LED_STATE, state);
|
|
|
|
if (!err)
|
|
|
|
led->on = !!brightness;
|
|
|
|
}
|
2020-07-23 12:53:19 +00:00
|
|
|
|
|
|
|
mutex_unlock(&leds->lock);
|
|
|
|
|
leds: turris-omnia: Make set_brightness() more efficient
Implement caching of the LED color and state values that are sent to MCU
in order to make the set_brightness() operation more efficient by
avoiding I2C transactions which are not needed.
On Turris Omnia's MCU, which acts as the RGB LED controller, each LED
has a RGB color, and a ON/OFF state, which are configurable via I2C
commands CMD_LED_COLOR and CMD_LED_STATE.
The CMD_LED_COLOR command sends 5 bytes and the CMD_LED_STATE command 2
bytes over the I2C bus, which operates at 100 kHz. With I2C overhead
this allows ~1670 color changing commands and ~3200 state changing
commands per second (or around 1000 color + state changes per second).
This may seem more than enough, but the issue is that the I2C bus is
shared with another peripheral, the MCU. The MCU exposes an interrupt
interface, and it can trigger hundreds of interrupts per second. Each
time, we need to read the interrupt state register over this I2C bus.
Whenever we are sending a LED color/state changing command, the
interrupt reading is waiting.
Currently, every time LED brightness or LED multi intensity is changed,
we send a CMD_LED_STATE command, and if the computed color (brightness
adjusted multi_intensity) is non-zero, we also send a CMD_LED_COLOR
command.
Consider for example the situation when we have a netdev trigger enabled
for a LED. The netdev trigger does not change the LED color, only the
brightness (either to 0 or to currently configured brightness), and so
there is no need to send the CMD_LED_COLOR command. But each change of
brightness to 0 sends one CMD_LED_STATE command, and each change of
brightness to max_brightness sends one CMD_LED_STATE command and one
CMD_LED_COLOR command:
set_brightness(0) -> CMD_LED_STATE
set_brightness(255) -> CMD_LED_STATE + CMD_LED_COLOR
(unnecessary)
We can avoid the unnecessary I2C transactions if we cache the values of
state and color that are sent to the controller. If the color does not
change from the one previously sent, there is no need to do the
CMD_LED_COLOR I2C transaction, and if the state does not change, there
is no need to do the CMD_LED_STATE transaction.
Because we need to make sure that our cached values are consistent with
the controller state, add explicit setting of the LED color to white at
probe time (this is the default setting when MCU resets, but does not
necessarily need to be the case, for example if U-Boot played with the
LED colors).
Signed-off-by: Marek Behún <kabel@kernel.org>
Link: https://lore.kernel.org/r/20230918161104.20860-3-kabel@kernel.org
Signed-off-by: Lee Jones <lee@kernel.org>
2023-09-18 16:11:02 +00:00
|
|
|
return err;
|
2020-07-23 12:53:19 +00:00
|
|
|
}
|
|
|
|
|
leds: turris-omnia: Support HW controlled mode via private trigger
Add support for enabling MCU controlled mode of the Turris Omnia LEDs
via a LED private trigger called "omnia-mcu". Recall that private LED
triggers will only be listed in the sysfs trigger file for LEDs that
support them (currently there is no user of this mechanism).
When in MCU controlled mode, the user can still set LED color, but the
blinking is done by MCU, which does different things for different LEDs:
- WAN LED is blinked according to the LED[0] pin of the WAN PHY
- LAN LEDs are blinked according to the LED[0] output of the
corresponding port of the LAN switch
- PCIe LEDs are blinked according to the logical OR of the MiniPCIe port
LED pins
In the future I want to make the netdev trigger to transparently offload
the blinking to the HW if user sets compatible settings for the netdev
trigger (for LEDs associated with network devices).
There was some work on this already, and hopefully we will be able to
complete it sometime, but for now there are still multiple blockers for
this, and even if there weren't, we still would not be able to configure
HW controlled mode for the LEDs associated with MiniPCIe ports.
In the meantime let's support HW controlled mode via the private LED
trigger mechanism. If, in the future, we manage to complete the netdev
trigger offloading, we can still keep this private trigger for backwards
compatibility, if needed.
We also set "omnia-mcu" to cdev->default_trigger, so that the MCU keeps
control until the user first wants to take over it. If a different
default trigger is specified in device-tree via the
'linux,default-trigger' property, LED class will overwrite
cdev->default_trigger, and so the DT property will be respected.
Signed-off-by: Marek Behún <kabel@kernel.org>
Link: https://lore.kernel.org/r/20230918161104.20860-4-kabel@kernel.org
Signed-off-by: Lee Jones <lee@kernel.org>
2023-09-18 16:11:03 +00:00
|
|
|
static struct led_hw_trigger_type omnia_hw_trigger_type;
|
|
|
|
|
|
|
|
static int omnia_hwtrig_activate(struct led_classdev *cdev)
|
|
|
|
{
|
|
|
|
struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev);
|
|
|
|
struct omnia_leds *leds = dev_get_drvdata(cdev->dev->parent);
|
|
|
|
struct omnia_led *led = to_omnia_led(mc_cdev);
|
|
|
|
int err = 0;
|
|
|
|
|
|
|
|
mutex_lock(&leds->lock);
|
|
|
|
|
|
|
|
if (!led->on) {
|
|
|
|
/*
|
|
|
|
* If the LED is off (brightness was set to 0), the last
|
|
|
|
* configured color was not necessarily sent to the MCU.
|
|
|
|
* Recompute with max_brightness and send if needed.
|
|
|
|
*/
|
|
|
|
led_mc_calc_color_components(mc_cdev, cdev->max_brightness);
|
|
|
|
|
|
|
|
if (omnia_led_channels_changed(led))
|
|
|
|
err = omnia_led_send_color_cmd(leds->client, led);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!err) {
|
|
|
|
/* Put the LED into MCU controlled mode */
|
|
|
|
err = omnia_cmd_write_u8(leds->client, CMD_LED_MODE,
|
|
|
|
CMD_LED_MODE_LED(led->reg));
|
|
|
|
if (!err)
|
|
|
|
led->hwtrig = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
mutex_unlock(&leds->lock);
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void omnia_hwtrig_deactivate(struct led_classdev *cdev)
|
|
|
|
{
|
|
|
|
struct omnia_leds *leds = dev_get_drvdata(cdev->dev->parent);
|
|
|
|
struct omnia_led *led = to_omnia_led(lcdev_to_mccdev(cdev));
|
|
|
|
int err;
|
|
|
|
|
|
|
|
mutex_lock(&leds->lock);
|
|
|
|
|
|
|
|
led->hwtrig = false;
|
|
|
|
|
|
|
|
/* Put the LED into software mode */
|
|
|
|
err = omnia_cmd_write_u8(leds->client, CMD_LED_MODE,
|
|
|
|
CMD_LED_MODE_LED(led->reg) |
|
|
|
|
CMD_LED_MODE_USER);
|
|
|
|
|
|
|
|
mutex_unlock(&leds->lock);
|
|
|
|
|
2023-10-16 14:15:38 +00:00
|
|
|
if (err)
|
leds: turris-omnia: Support HW controlled mode via private trigger
Add support for enabling MCU controlled mode of the Turris Omnia LEDs
via a LED private trigger called "omnia-mcu". Recall that private LED
triggers will only be listed in the sysfs trigger file for LEDs that
support them (currently there is no user of this mechanism).
When in MCU controlled mode, the user can still set LED color, but the
blinking is done by MCU, which does different things for different LEDs:
- WAN LED is blinked according to the LED[0] pin of the WAN PHY
- LAN LEDs are blinked according to the LED[0] output of the
corresponding port of the LAN switch
- PCIe LEDs are blinked according to the logical OR of the MiniPCIe port
LED pins
In the future I want to make the netdev trigger to transparently offload
the blinking to the HW if user sets compatible settings for the netdev
trigger (for LEDs associated with network devices).
There was some work on this already, and hopefully we will be able to
complete it sometime, but for now there are still multiple blockers for
this, and even if there weren't, we still would not be able to configure
HW controlled mode for the LEDs associated with MiniPCIe ports.
In the meantime let's support HW controlled mode via the private LED
trigger mechanism. If, in the future, we manage to complete the netdev
trigger offloading, we can still keep this private trigger for backwards
compatibility, if needed.
We also set "omnia-mcu" to cdev->default_trigger, so that the MCU keeps
control until the user first wants to take over it. If a different
default trigger is specified in device-tree via the
'linux,default-trigger' property, LED class will overwrite
cdev->default_trigger, and so the DT property will be respected.
Signed-off-by: Marek Behún <kabel@kernel.org>
Link: https://lore.kernel.org/r/20230918161104.20860-4-kabel@kernel.org
Signed-off-by: Lee Jones <lee@kernel.org>
2023-09-18 16:11:03 +00:00
|
|
|
dev_err(cdev->dev, "Cannot put LED to software mode: %i\n",
|
|
|
|
err);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct led_trigger omnia_hw_trigger = {
|
|
|
|
.name = "omnia-mcu",
|
|
|
|
.activate = omnia_hwtrig_activate,
|
|
|
|
.deactivate = omnia_hwtrig_deactivate,
|
|
|
|
.trigger_type = &omnia_hw_trigger_type,
|
|
|
|
};
|
|
|
|
|
2020-07-23 12:53:19 +00:00
|
|
|
static int omnia_led_register(struct i2c_client *client, struct omnia_led *led,
|
|
|
|
struct device_node *np)
|
|
|
|
{
|
|
|
|
struct led_init_data init_data = {};
|
|
|
|
struct device *dev = &client->dev;
|
|
|
|
struct led_classdev *cdev;
|
|
|
|
int ret, color;
|
|
|
|
|
|
|
|
ret = of_property_read_u32(np, "reg", &led->reg);
|
|
|
|
if (ret || led->reg >= OMNIA_BOARD_LEDS) {
|
|
|
|
dev_warn(dev,
|
|
|
|
"Node %pOF: must contain 'reg' property with values between 0 and %i\n",
|
|
|
|
np, OMNIA_BOARD_LEDS - 1);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = of_property_read_u32(np, "color", &color);
|
2020-10-30 02:39:06 +00:00
|
|
|
if (ret || color != LED_COLOR_ID_RGB) {
|
2020-07-23 12:53:19 +00:00
|
|
|
dev_warn(dev,
|
2020-10-30 02:39:06 +00:00
|
|
|
"Node %pOF: must contain 'color' property with value LED_COLOR_ID_RGB\n",
|
2020-07-23 12:53:19 +00:00
|
|
|
np);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
led->subled_info[0].color_index = LED_COLOR_ID_RED;
|
|
|
|
led->subled_info[1].color_index = LED_COLOR_ID_GREEN;
|
|
|
|
led->subled_info[2].color_index = LED_COLOR_ID_BLUE;
|
leds: turris-omnia: Make set_brightness() more efficient
Implement caching of the LED color and state values that are sent to MCU
in order to make the set_brightness() operation more efficient by
avoiding I2C transactions which are not needed.
On Turris Omnia's MCU, which acts as the RGB LED controller, each LED
has a RGB color, and a ON/OFF state, which are configurable via I2C
commands CMD_LED_COLOR and CMD_LED_STATE.
The CMD_LED_COLOR command sends 5 bytes and the CMD_LED_STATE command 2
bytes over the I2C bus, which operates at 100 kHz. With I2C overhead
this allows ~1670 color changing commands and ~3200 state changing
commands per second (or around 1000 color + state changes per second).
This may seem more than enough, but the issue is that the I2C bus is
shared with another peripheral, the MCU. The MCU exposes an interrupt
interface, and it can trigger hundreds of interrupts per second. Each
time, we need to read the interrupt state register over this I2C bus.
Whenever we are sending a LED color/state changing command, the
interrupt reading is waiting.
Currently, every time LED brightness or LED multi intensity is changed,
we send a CMD_LED_STATE command, and if the computed color (brightness
adjusted multi_intensity) is non-zero, we also send a CMD_LED_COLOR
command.
Consider for example the situation when we have a netdev trigger enabled
for a LED. The netdev trigger does not change the LED color, only the
brightness (either to 0 or to currently configured brightness), and so
there is no need to send the CMD_LED_COLOR command. But each change of
brightness to 0 sends one CMD_LED_STATE command, and each change of
brightness to max_brightness sends one CMD_LED_STATE command and one
CMD_LED_COLOR command:
set_brightness(0) -> CMD_LED_STATE
set_brightness(255) -> CMD_LED_STATE + CMD_LED_COLOR
(unnecessary)
We can avoid the unnecessary I2C transactions if we cache the values of
state and color that are sent to the controller. If the color does not
change from the one previously sent, there is no need to do the
CMD_LED_COLOR I2C transaction, and if the state does not change, there
is no need to do the CMD_LED_STATE transaction.
Because we need to make sure that our cached values are consistent with
the controller state, add explicit setting of the LED color to white at
probe time (this is the default setting when MCU resets, but does not
necessarily need to be the case, for example if U-Boot played with the
LED colors).
Signed-off-by: Marek Behún <kabel@kernel.org>
Link: https://lore.kernel.org/r/20230918161104.20860-3-kabel@kernel.org
Signed-off-by: Lee Jones <lee@kernel.org>
2023-09-18 16:11:02 +00:00
|
|
|
|
|
|
|
/* Initial color is white */
|
|
|
|
for (int i = 0; i < OMNIA_LED_NUM_CHANNELS; ++i) {
|
|
|
|
led->subled_info[i].intensity = 255;
|
|
|
|
led->subled_info[i].brightness = 255;
|
|
|
|
led->subled_info[i].channel = i;
|
|
|
|
}
|
2020-07-23 12:53:19 +00:00
|
|
|
|
|
|
|
led->mc_cdev.subled_info = led->subled_info;
|
|
|
|
led->mc_cdev.num_colors = OMNIA_LED_NUM_CHANNELS;
|
|
|
|
|
|
|
|
init_data.fwnode = &np->fwnode;
|
|
|
|
|
|
|
|
cdev = &led->mc_cdev.led_cdev;
|
|
|
|
cdev->max_brightness = 255;
|
|
|
|
cdev->brightness_set_blocking = omnia_led_brightness_set_blocking;
|
leds: turris-omnia: Support HW controlled mode via private trigger
Add support for enabling MCU controlled mode of the Turris Omnia LEDs
via a LED private trigger called "omnia-mcu". Recall that private LED
triggers will only be listed in the sysfs trigger file for LEDs that
support them (currently there is no user of this mechanism).
When in MCU controlled mode, the user can still set LED color, but the
blinking is done by MCU, which does different things for different LEDs:
- WAN LED is blinked according to the LED[0] pin of the WAN PHY
- LAN LEDs are blinked according to the LED[0] output of the
corresponding port of the LAN switch
- PCIe LEDs are blinked according to the logical OR of the MiniPCIe port
LED pins
In the future I want to make the netdev trigger to transparently offload
the blinking to the HW if user sets compatible settings for the netdev
trigger (for LEDs associated with network devices).
There was some work on this already, and hopefully we will be able to
complete it sometime, but for now there are still multiple blockers for
this, and even if there weren't, we still would not be able to configure
HW controlled mode for the LEDs associated with MiniPCIe ports.
In the meantime let's support HW controlled mode via the private LED
trigger mechanism. If, in the future, we manage to complete the netdev
trigger offloading, we can still keep this private trigger for backwards
compatibility, if needed.
We also set "omnia-mcu" to cdev->default_trigger, so that the MCU keeps
control until the user first wants to take over it. If a different
default trigger is specified in device-tree via the
'linux,default-trigger' property, LED class will overwrite
cdev->default_trigger, and so the DT property will be respected.
Signed-off-by: Marek Behún <kabel@kernel.org>
Link: https://lore.kernel.org/r/20230918161104.20860-4-kabel@kernel.org
Signed-off-by: Lee Jones <lee@kernel.org>
2023-09-18 16:11:03 +00:00
|
|
|
cdev->trigger_type = &omnia_hw_trigger_type;
|
|
|
|
/*
|
|
|
|
* Use the omnia-mcu trigger as the default trigger. It may be rewritten
|
|
|
|
* by LED class from the linux,default-trigger property.
|
|
|
|
*/
|
|
|
|
cdev->default_trigger = omnia_hw_trigger.name;
|
2020-07-23 12:53:19 +00:00
|
|
|
|
|
|
|
/* put the LED into software mode */
|
leds: turris-omnia: Do not use SMBUS calls
The leds-turris-omnia driver uses three function for I2C access:
- i2c_smbus_write_byte_data() and i2c_smbus_read_byte_data(), which
cause an emulated SMBUS transfer,
- i2c_master_send(), which causes an ordinary I2C transfer.
The Turris Omnia MCU LED controller is not semantically SMBUS, it
operates as a simple I2C bus. It does not implement any of the SMBUS
specific features, like PEC, or procedure calls, or anything. Moreover
the I2C controller driver also does not implement SMBUS, and so the
emulated SMBUS procedure from drivers/i2c/i2c-core-smbus.c is used for
the SMBUS calls, which gives an unnecessary overhead.
When I first wrote the driver, I was unaware of these facts, and I
simply used the first function that worked.
Drop the I2C SMBUS calls and instead use simple I2C transfers.
Fixes: 089381b27abe ("leds: initial support for Turris Omnia LEDs")
Signed-off-by: Marek Behún <kabel@kernel.org>
Link: https://lore.kernel.org/r/20230918161104.20860-2-kabel@kernel.org
Signed-off-by: Lee Jones <lee@kernel.org>
2023-09-18 16:11:01 +00:00
|
|
|
ret = omnia_cmd_write_u8(client, CMD_LED_MODE,
|
|
|
|
CMD_LED_MODE_LED(led->reg) |
|
|
|
|
CMD_LED_MODE_USER);
|
2023-10-16 14:15:38 +00:00
|
|
|
if (ret) {
|
2020-10-30 02:39:03 +00:00
|
|
|
dev_err(dev, "Cannot set LED %pOF to software mode: %i\n", np,
|
|
|
|
ret);
|
2020-07-23 12:53:19 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* disable the LED */
|
leds: turris-omnia: Do not use SMBUS calls
The leds-turris-omnia driver uses three function for I2C access:
- i2c_smbus_write_byte_data() and i2c_smbus_read_byte_data(), which
cause an emulated SMBUS transfer,
- i2c_master_send(), which causes an ordinary I2C transfer.
The Turris Omnia MCU LED controller is not semantically SMBUS, it
operates as a simple I2C bus. It does not implement any of the SMBUS
specific features, like PEC, or procedure calls, or anything. Moreover
the I2C controller driver also does not implement SMBUS, and so the
emulated SMBUS procedure from drivers/i2c/i2c-core-smbus.c is used for
the SMBUS calls, which gives an unnecessary overhead.
When I first wrote the driver, I was unaware of these facts, and I
simply used the first function that worked.
Drop the I2C SMBUS calls and instead use simple I2C transfers.
Fixes: 089381b27abe ("leds: initial support for Turris Omnia LEDs")
Signed-off-by: Marek Behún <kabel@kernel.org>
Link: https://lore.kernel.org/r/20230918161104.20860-2-kabel@kernel.org
Signed-off-by: Lee Jones <lee@kernel.org>
2023-09-18 16:11:01 +00:00
|
|
|
ret = omnia_cmd_write_u8(client, CMD_LED_STATE,
|
|
|
|
CMD_LED_STATE_LED(led->reg));
|
2023-10-16 14:15:38 +00:00
|
|
|
if (ret) {
|
2020-07-23 12:53:19 +00:00
|
|
|
dev_err(dev, "Cannot set LED %pOF brightness: %i\n", np, ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
leds: turris-omnia: Make set_brightness() more efficient
Implement caching of the LED color and state values that are sent to MCU
in order to make the set_brightness() operation more efficient by
avoiding I2C transactions which are not needed.
On Turris Omnia's MCU, which acts as the RGB LED controller, each LED
has a RGB color, and a ON/OFF state, which are configurable via I2C
commands CMD_LED_COLOR and CMD_LED_STATE.
The CMD_LED_COLOR command sends 5 bytes and the CMD_LED_STATE command 2
bytes over the I2C bus, which operates at 100 kHz. With I2C overhead
this allows ~1670 color changing commands and ~3200 state changing
commands per second (or around 1000 color + state changes per second).
This may seem more than enough, but the issue is that the I2C bus is
shared with another peripheral, the MCU. The MCU exposes an interrupt
interface, and it can trigger hundreds of interrupts per second. Each
time, we need to read the interrupt state register over this I2C bus.
Whenever we are sending a LED color/state changing command, the
interrupt reading is waiting.
Currently, every time LED brightness or LED multi intensity is changed,
we send a CMD_LED_STATE command, and if the computed color (brightness
adjusted multi_intensity) is non-zero, we also send a CMD_LED_COLOR
command.
Consider for example the situation when we have a netdev trigger enabled
for a LED. The netdev trigger does not change the LED color, only the
brightness (either to 0 or to currently configured brightness), and so
there is no need to send the CMD_LED_COLOR command. But each change of
brightness to 0 sends one CMD_LED_STATE command, and each change of
brightness to max_brightness sends one CMD_LED_STATE command and one
CMD_LED_COLOR command:
set_brightness(0) -> CMD_LED_STATE
set_brightness(255) -> CMD_LED_STATE + CMD_LED_COLOR
(unnecessary)
We can avoid the unnecessary I2C transactions if we cache the values of
state and color that are sent to the controller. If the color does not
change from the one previously sent, there is no need to do the
CMD_LED_COLOR I2C transaction, and if the state does not change, there
is no need to do the CMD_LED_STATE transaction.
Because we need to make sure that our cached values are consistent with
the controller state, add explicit setting of the LED color to white at
probe time (this is the default setting when MCU resets, but does not
necessarily need to be the case, for example if U-Boot played with the
LED colors).
Signed-off-by: Marek Behún <kabel@kernel.org>
Link: https://lore.kernel.org/r/20230918161104.20860-3-kabel@kernel.org
Signed-off-by: Lee Jones <lee@kernel.org>
2023-09-18 16:11:02 +00:00
|
|
|
/* Set initial color and cache it */
|
|
|
|
ret = omnia_led_send_color_cmd(client, led);
|
|
|
|
if (ret < 0) {
|
|
|
|
dev_err(dev, "Cannot set LED %pOF initial color: %i\n", np,
|
|
|
|
ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2020-10-30 02:39:03 +00:00
|
|
|
ret = devm_led_classdev_multicolor_register_ext(dev, &led->mc_cdev,
|
|
|
|
&init_data);
|
2020-07-23 12:53:19 +00:00
|
|
|
if (ret < 0) {
|
|
|
|
dev_err(dev, "Cannot register LED %pOF: %i\n", np, ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* On the front panel of the Turris Omnia router there is also a button which
|
|
|
|
* can be used to control the intensity of all the LEDs at once, so that if they
|
|
|
|
* are too bright, user can dim them.
|
|
|
|
* The microcontroller cycles between 8 levels of this global brightness (from
|
|
|
|
* 100% to 0%), but this setting can have any integer value between 0 and 100.
|
|
|
|
* It is therefore convenient to be able to change this setting from software.
|
|
|
|
* We expose this setting via a sysfs attribute file called "brightness". This
|
|
|
|
* file lives in the device directory of the LED controller, not an individual
|
|
|
|
* LED, so it should not confuse users.
|
|
|
|
*/
|
2020-10-30 02:39:03 +00:00
|
|
|
static ssize_t brightness_show(struct device *dev, struct device_attribute *a,
|
|
|
|
char *buf)
|
2020-07-23 12:53:19 +00:00
|
|
|
{
|
|
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
|
|
int ret;
|
|
|
|
|
leds: turris-omnia: Do not use SMBUS calls
The leds-turris-omnia driver uses three function for I2C access:
- i2c_smbus_write_byte_data() and i2c_smbus_read_byte_data(), which
cause an emulated SMBUS transfer,
- i2c_master_send(), which causes an ordinary I2C transfer.
The Turris Omnia MCU LED controller is not semantically SMBUS, it
operates as a simple I2C bus. It does not implement any of the SMBUS
specific features, like PEC, or procedure calls, or anything. Moreover
the I2C controller driver also does not implement SMBUS, and so the
emulated SMBUS procedure from drivers/i2c/i2c-core-smbus.c is used for
the SMBUS calls, which gives an unnecessary overhead.
When I first wrote the driver, I was unaware of these facts, and I
simply used the first function that worked.
Drop the I2C SMBUS calls and instead use simple I2C transfers.
Fixes: 089381b27abe ("leds: initial support for Turris Omnia LEDs")
Signed-off-by: Marek Behún <kabel@kernel.org>
Link: https://lore.kernel.org/r/20230918161104.20860-2-kabel@kernel.org
Signed-off-by: Lee Jones <lee@kernel.org>
2023-09-18 16:11:01 +00:00
|
|
|
ret = omnia_cmd_read_u8(client, CMD_LED_GET_BRIGHTNESS);
|
2020-07-23 12:53:19 +00:00
|
|
|
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
2023-08-02 16:07:45 +00:00
|
|
|
return sysfs_emit(buf, "%d\n", ret);
|
2020-07-23 12:53:19 +00:00
|
|
|
}
|
|
|
|
|
2020-10-30 02:39:03 +00:00
|
|
|
static ssize_t brightness_store(struct device *dev, struct device_attribute *a,
|
|
|
|
const char *buf, size_t count)
|
2020-07-23 12:53:19 +00:00
|
|
|
{
|
|
|
|
struct i2c_client *client = to_i2c_client(dev);
|
2020-10-30 02:39:04 +00:00
|
|
|
unsigned long brightness;
|
2023-10-16 14:15:38 +00:00
|
|
|
int err;
|
2020-07-23 12:53:19 +00:00
|
|
|
|
2020-10-30 02:39:04 +00:00
|
|
|
if (kstrtoul(buf, 10, &brightness))
|
2020-07-23 12:53:19 +00:00
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (brightness > 100)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2023-10-16 14:15:38 +00:00
|
|
|
err = omnia_cmd_write_u8(client, CMD_LED_SET_BRIGHTNESS, brightness);
|
2020-07-23 12:53:19 +00:00
|
|
|
|
2023-10-16 14:15:38 +00:00
|
|
|
return err ?: count;
|
2020-07-23 12:53:19 +00:00
|
|
|
}
|
|
|
|
static DEVICE_ATTR_RW(brightness);
|
|
|
|
|
2023-09-18 16:11:04 +00:00
|
|
|
static ssize_t gamma_correction_show(struct device *dev,
|
|
|
|
struct device_attribute *a, char *buf)
|
|
|
|
{
|
|
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
|
|
struct omnia_leds *leds = i2c_get_clientdata(client);
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (leds->has_gamma_correction) {
|
|
|
|
ret = omnia_cmd_read_u8(client, CMD_GET_GAMMA_CORRECTION);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
} else {
|
|
|
|
ret = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return sysfs_emit(buf, "%d\n", !!ret);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t gamma_correction_store(struct device *dev,
|
|
|
|
struct device_attribute *a,
|
|
|
|
const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
|
|
struct omnia_leds *leds = i2c_get_clientdata(client);
|
|
|
|
bool val;
|
2023-10-16 14:15:38 +00:00
|
|
|
int err;
|
2023-09-18 16:11:04 +00:00
|
|
|
|
|
|
|
if (!leds->has_gamma_correction)
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
|
|
|
if (kstrtobool(buf, &val) < 0)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2023-10-16 14:15:38 +00:00
|
|
|
err = omnia_cmd_write_u8(client, CMD_SET_GAMMA_CORRECTION, val);
|
2023-09-18 16:11:04 +00:00
|
|
|
|
2023-10-16 14:15:38 +00:00
|
|
|
return err ?: count;
|
2023-09-18 16:11:04 +00:00
|
|
|
}
|
|
|
|
static DEVICE_ATTR_RW(gamma_correction);
|
|
|
|
|
2020-07-23 12:53:19 +00:00
|
|
|
static struct attribute *omnia_led_controller_attrs[] = {
|
|
|
|
&dev_attr_brightness.attr,
|
2023-09-18 16:11:04 +00:00
|
|
|
&dev_attr_gamma_correction.attr,
|
2020-07-23 12:53:19 +00:00
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
ATTRIBUTE_GROUPS(omnia_led_controller);
|
|
|
|
|
2023-09-18 16:11:04 +00:00
|
|
|
static int omnia_mcu_get_features(const struct i2c_client *client)
|
|
|
|
{
|
|
|
|
u16 reply;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
err = omnia_cmd_read_raw(client->adapter, OMNIA_MCU_I2C_ADDR,
|
|
|
|
CMD_GET_STATUS_WORD, &reply, sizeof(reply));
|
2023-10-16 14:15:38 +00:00
|
|
|
if (err)
|
2023-09-18 16:11:04 +00:00
|
|
|
return err;
|
|
|
|
|
|
|
|
/* Check whether MCU firmware supports the CMD_GET_FEAUTRES command */
|
|
|
|
if (!(le16_to_cpu(reply) & STS_FEATURES_SUPPORTED))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err = omnia_cmd_read_raw(client->adapter, OMNIA_MCU_I2C_ADDR,
|
|
|
|
CMD_GET_FEATURES, &reply, sizeof(reply));
|
2023-10-16 14:15:38 +00:00
|
|
|
if (err)
|
2023-09-18 16:11:04 +00:00
|
|
|
return err;
|
|
|
|
|
|
|
|
return le16_to_cpu(reply);
|
|
|
|
}
|
|
|
|
|
2022-11-18 22:40:22 +00:00
|
|
|
static int omnia_leds_probe(struct i2c_client *client)
|
2020-07-23 12:53:19 +00:00
|
|
|
{
|
|
|
|
struct device *dev = &client->dev;
|
2020-09-17 22:32:54 +00:00
|
|
|
struct device_node *np = dev_of_node(dev), *child;
|
2020-07-23 12:53:19 +00:00
|
|
|
struct omnia_leds *leds;
|
|
|
|
struct omnia_led *led;
|
|
|
|
int ret, count;
|
|
|
|
|
|
|
|
count = of_get_available_child_count(np);
|
|
|
|
if (!count) {
|
|
|
|
dev_err(dev, "LEDs are not defined in device tree!\n");
|
|
|
|
return -ENODEV;
|
|
|
|
} else if (count > OMNIA_BOARD_LEDS) {
|
|
|
|
dev_err(dev, "Too many LEDs defined in device tree!\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
leds = devm_kzalloc(dev, struct_size(leds, leds, count), GFP_KERNEL);
|
|
|
|
if (!leds)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
leds->client = client;
|
|
|
|
i2c_set_clientdata(client, leds);
|
|
|
|
|
2023-09-18 16:11:04 +00:00
|
|
|
ret = omnia_mcu_get_features(client);
|
|
|
|
if (ret < 0) {
|
|
|
|
dev_err(dev, "Cannot determine MCU supported features: %d\n",
|
|
|
|
ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
leds->has_gamma_correction = ret & FEAT_LED_GAMMA_CORRECTION;
|
|
|
|
if (!leds->has_gamma_correction) {
|
|
|
|
dev_info(dev,
|
|
|
|
"Your board's MCU firmware does not support the LED gamma correction feature.\n");
|
|
|
|
dev_info(dev,
|
|
|
|
"Consider upgrading MCU firmware with the omnia-mcutool utility.\n");
|
|
|
|
}
|
|
|
|
|
2020-07-23 12:53:19 +00:00
|
|
|
mutex_init(&leds->lock);
|
|
|
|
|
leds: turris-omnia: Support HW controlled mode via private trigger
Add support for enabling MCU controlled mode of the Turris Omnia LEDs
via a LED private trigger called "omnia-mcu". Recall that private LED
triggers will only be listed in the sysfs trigger file for LEDs that
support them (currently there is no user of this mechanism).
When in MCU controlled mode, the user can still set LED color, but the
blinking is done by MCU, which does different things for different LEDs:
- WAN LED is blinked according to the LED[0] pin of the WAN PHY
- LAN LEDs are blinked according to the LED[0] output of the
corresponding port of the LAN switch
- PCIe LEDs are blinked according to the logical OR of the MiniPCIe port
LED pins
In the future I want to make the netdev trigger to transparently offload
the blinking to the HW if user sets compatible settings for the netdev
trigger (for LEDs associated with network devices).
There was some work on this already, and hopefully we will be able to
complete it sometime, but for now there are still multiple blockers for
this, and even if there weren't, we still would not be able to configure
HW controlled mode for the LEDs associated with MiniPCIe ports.
In the meantime let's support HW controlled mode via the private LED
trigger mechanism. If, in the future, we manage to complete the netdev
trigger offloading, we can still keep this private trigger for backwards
compatibility, if needed.
We also set "omnia-mcu" to cdev->default_trigger, so that the MCU keeps
control until the user first wants to take over it. If a different
default trigger is specified in device-tree via the
'linux,default-trigger' property, LED class will overwrite
cdev->default_trigger, and so the DT property will be respected.
Signed-off-by: Marek Behún <kabel@kernel.org>
Link: https://lore.kernel.org/r/20230918161104.20860-4-kabel@kernel.org
Signed-off-by: Lee Jones <lee@kernel.org>
2023-09-18 16:11:03 +00:00
|
|
|
ret = devm_led_trigger_register(dev, &omnia_hw_trigger);
|
|
|
|
if (ret < 0) {
|
|
|
|
dev_err(dev, "Cannot register private LED trigger: %d\n", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2020-07-23 12:53:19 +00:00
|
|
|
led = &leds->leds[0];
|
|
|
|
for_each_available_child_of_node(np, child) {
|
|
|
|
ret = omnia_led_register(client, led, child);
|
2020-09-17 22:32:57 +00:00
|
|
|
if (ret < 0) {
|
|
|
|
of_node_put(child);
|
2020-07-23 12:53:19 +00:00
|
|
|
return ret;
|
2020-09-17 22:32:57 +00:00
|
|
|
}
|
2020-07-23 12:53:19 +00:00
|
|
|
|
|
|
|
led += ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-08-15 08:02:30 +00:00
|
|
|
static void omnia_leds_remove(struct i2c_client *client)
|
2020-07-23 12:53:19 +00:00
|
|
|
{
|
2020-10-30 02:39:02 +00:00
|
|
|
u8 buf[5];
|
2020-07-23 12:53:19 +00:00
|
|
|
|
|
|
|
/* put all LEDs into default (HW triggered) mode */
|
leds: turris-omnia: Do not use SMBUS calls
The leds-turris-omnia driver uses three function for I2C access:
- i2c_smbus_write_byte_data() and i2c_smbus_read_byte_data(), which
cause an emulated SMBUS transfer,
- i2c_master_send(), which causes an ordinary I2C transfer.
The Turris Omnia MCU LED controller is not semantically SMBUS, it
operates as a simple I2C bus. It does not implement any of the SMBUS
specific features, like PEC, or procedure calls, or anything. Moreover
the I2C controller driver also does not implement SMBUS, and so the
emulated SMBUS procedure from drivers/i2c/i2c-core-smbus.c is used for
the SMBUS calls, which gives an unnecessary overhead.
When I first wrote the driver, I was unaware of these facts, and I
simply used the first function that worked.
Drop the I2C SMBUS calls and instead use simple I2C transfers.
Fixes: 089381b27abe ("leds: initial support for Turris Omnia LEDs")
Signed-off-by: Marek Behún <kabel@kernel.org>
Link: https://lore.kernel.org/r/20230918161104.20860-2-kabel@kernel.org
Signed-off-by: Lee Jones <lee@kernel.org>
2023-09-18 16:11:01 +00:00
|
|
|
omnia_cmd_write_u8(client, CMD_LED_MODE,
|
|
|
|
CMD_LED_MODE_LED(OMNIA_BOARD_LEDS));
|
2020-07-23 12:53:19 +00:00
|
|
|
|
|
|
|
/* set all LEDs color to [255, 255, 255] */
|
2020-10-30 02:39:02 +00:00
|
|
|
buf[0] = CMD_LED_COLOR;
|
|
|
|
buf[1] = OMNIA_BOARD_LEDS;
|
|
|
|
buf[2] = 255;
|
|
|
|
buf[3] = 255;
|
|
|
|
buf[4] = 255;
|
2020-07-23 12:53:19 +00:00
|
|
|
|
|
|
|
i2c_master_send(client, buf, 5);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct of_device_id of_omnia_leds_match[] = {
|
|
|
|
{ .compatible = "cznic,turris-omnia-leds", },
|
|
|
|
{},
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct i2c_device_id omnia_id[] = {
|
2024-05-22 16:53:59 +00:00
|
|
|
{ "omnia" },
|
2020-07-23 12:53:19 +00:00
|
|
|
{ }
|
|
|
|
};
|
2021-05-12 06:49:18 +00:00
|
|
|
MODULE_DEVICE_TABLE(i2c, omnia_id);
|
2020-07-23 12:53:19 +00:00
|
|
|
|
|
|
|
static struct i2c_driver omnia_leds_driver = {
|
2023-05-17 18:05:59 +00:00
|
|
|
.probe = omnia_leds_probe,
|
2020-07-23 12:53:19 +00:00
|
|
|
.remove = omnia_leds_remove,
|
|
|
|
.id_table = omnia_id,
|
|
|
|
.driver = {
|
|
|
|
.name = "leds-turris-omnia",
|
|
|
|
.of_match_table = of_omnia_leds_match,
|
2022-07-29 14:03:46 +00:00
|
|
|
.dev_groups = omnia_led_controller_groups,
|
2020-07-23 12:53:19 +00:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
module_i2c_driver(omnia_leds_driver);
|
|
|
|
|
2021-04-09 20:27:04 +00:00
|
|
|
MODULE_AUTHOR("Marek Behun <kabel@kernel.org>");
|
2020-07-23 12:53:19 +00:00
|
|
|
MODULE_DESCRIPTION("CZ.NIC's Turris Omnia LEDs");
|
|
|
|
MODULE_LICENSE("GPL v2");
|