// SPDX-License-Identifier: GPL-2.0
/*
* CZ.NIC's Turris Omnia MCU GPIO and IRQ driver
*
* 2024 by Marek Behún <kabel@kernel.org>
*/
#include <linux/array_size.h>
#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/bug.h>
#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/devm-helpers.h>
#include <linux/errno.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio/driver.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/mutex.h>
#include <linux/sysfs.h>
#include <linux/types.h>
#include <linux/workqueue.h>
#include <linux/unaligned.h>
#include <linux/turris-omnia-mcu-interface.h>
#include "turris-omnia-mcu.h"
#define OMNIA_CMD_INT_ARG_LEN 8
#define FRONT_BUTTON_RELEASE_DELAY_MS 50
static const char * const omnia_mcu_gpio_names[64] = {
/* GPIOs with value read from the 16-bit wide status */
[4] = "MiniPCIe0 Card Detect",
[5] = "MiniPCIe0 mSATA Indicator",
[6] = "Front USB3 port over-current",
[7] = "Rear USB3 port over-current",
[8] = "Front USB3 port power",
[9] = "Rear USB3 port power",
[12] = "Front Button",
/* GPIOs with value read from the 32-bit wide extended status */
[16] = "SFP nDET",
[28] = "MiniPCIe0 LED",
[29] = "MiniPCIe1 LED",
[30] = "MiniPCIe2 LED",
[31] = "MiniPCIe0 PAN LED",
[32] = "MiniPCIe1 PAN LED",
[33] = "MiniPCIe2 PAN LED",
[34] = "WAN PHY LED0",
[35] = "WAN PHY LED1",
[36] = "LAN switch p0 LED0",
[37] = "LAN switch p0 LED1",
[38] = "LAN switch p1 LED0",
[39] = "LAN switch p1 LED1",
[40] = "LAN switch p2 LED0",
[41] = "LAN switch p2 LED1",
[42] = "LAN switch p3 LED0",
[43] = "LAN switch p3 LED1",
[44] = "LAN switch p4 LED0",
[45] = "LAN switch p4 LED1",
[46] = "LAN switch p5 LED0",
[47] = "LAN switch p5 LED1",
/* GPIOs with value read from the 16-bit wide extended control status */
[48] = "eMMC nRESET",
[49] = "LAN switch nRESET",
[50] = "WAN PHY nRESET",
[51] = "MiniPCIe0 nPERST",
[52] = "MiniPCIe1 nPERST",
[53] = "MiniPCIe2 nPERST",
[54] = "WAN PHY SFP mux",
[56] = "VHV power disable",
};
struct omnia_gpio {
u8 cmd;
u8 ctl_cmd;
u8 bit;
u8 ctl_bit;
u8 int_bit;
u16 feat;
u16 feat_mask;
};
#define OMNIA_GPIO_INVALID_INT_BIT 0xff
#define _DEF_GPIO(_cmd, _ctl_cmd, _bit, _ctl_bit, _int_bit, _feat, _feat_mask) \
{ \
.cmd = _cmd, \
.ctl_cmd = _ctl_cmd, \
.bit = _bit, \
.ctl_bit = _ctl_bit, \
.int_bit = (_int_bit) < 0 ? OMNIA_GPIO_INVALID_INT_BIT \
: (_int_bit), \
.feat = _feat, \
.feat_mask = _feat_mask, \
}
#define _DEF_GPIO_STS(_name) \
_DEF_GPIO(OMNIA_CMD_GET_STATUS_WORD, 0, __bf_shf(OMNIA_STS_ ## _name), \
0, __bf_shf(OMNIA_INT_ ## _name), 0, 0)
#define _DEF_GPIO_CTL(_name) \
_DEF_GPIO(OMNIA_CMD_GET_STATUS_WORD, OMNIA_CMD_GENERAL_CONTROL, \
__bf_shf(OMNIA_STS_ ## _name), __bf_shf(OMNIA_CTL_ ## _name), \
-1, 0, 0)
#define _DEF_GPIO_EXT_STS(_name, _feat) \
_DEF_GPIO(OMNIA_CMD_GET_EXT_STATUS_DWORD, 0, \
__bf_shf(OMNIA_EXT_STS_ ## _name), 0, \
__bf_shf(OMNIA_INT_ ## _name), \
OMNIA_FEAT_ ## _feat | OMNIA_FEAT_EXT_CMDS, \
OMNIA_FEAT_ ## _feat | OMNIA_FEAT_EXT_CMDS)
#define _DEF_GPIO_EXT_STS_LED(_name, _ledext) \
_DEF_GPIO(OMNIA_CMD_GET_EXT_STATUS_DWORD, 0, \
__bf_shf(OMNIA_EXT_STS_ ## _name), 0, \
__bf_shf(OMNIA_INT_ ## _name), \
OMNIA_FEAT_LED_STATE_ ## _ledext, \
OMNIA_FEAT_LED_STATE_EXT_MASK)
#define _DEF_GPIO_EXT_STS_LEDALL(_name) \
_DEF_GPIO(OMNIA_CMD_GET_EXT_STATUS_DWORD, 0, \
__bf_shf(OMNIA_EXT_STS_ ## _name), 0, \
__bf_shf(OMNIA_INT_ ## _name), \
OMNIA_FEAT_LED_STATE_EXT_MASK, 0)
#define _DEF_GPIO_EXT_CTL(_name, _feat) \
_DEF_GPIO(OMNIA_CMD_GET_EXT_CONTROL_STATUS, OMNIA_CMD_EXT_CONTROL, \
__bf_shf(OMNIA_EXT_CTL_ ## _name), \
__bf_shf(OMNIA_EXT_CTL_ ## _name), -1, \
OMNIA_FEAT_ ## _feat | OMNIA_FEAT_EXT_CMDS, \
OMNIA_FEAT_ ## _feat | OMNIA_FEAT_EXT_CMDS)
#define _DEF_INT(_name) \
_DEF_GPIO(0, 0, 0, 0, __bf_shf(OMNIA_INT_ ## _name), 0, 0)
static inline bool is_int_bit_valid(const struct omnia_gpio *gpio)
{
return gpio->int_bit != OMNIA_GPIO_INVALID_INT_BIT;
}
static const struct omnia_gpio omnia_gpios[64] = {
/* GPIOs with value read from the 16-bit wide status */
[4]