From: Jozsef Horvath <info@ministro.hu>
To: "'Greg Kroah-Hartman'" <gregkh@linuxfoundation.org>,
"'Rob Herring'" <robh+dt@kernel.org>,
"'Jiri Slaby'" <jirislaby@kernel.org>,
"'József Horváth'" <info@ministro.hu>,
linux-serial@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org
Subject: [PATCH v8 1/2] Serial: silabs si4455 serial driver
Date: Tue, 12 Jan 2021 10:18:33 +0000 [thread overview]
Message-ID: <20210112101831.GA12483@dev> (raw)
This is a serial port driver for
Silicon Labs Si4455 Sub-GHz transciver.
The goal of this driver is to removing wires
between central(linux) device and remote serial devices/sensors,
but keeping the original user software.
It represents regular serial interface for the user space.
Datasheet: https://www.silabs.com/documents/public/data-sheets/Si4455.pdf
Guide: Documentation/driver-api/serial/si4455.rst
Signed-off-by: Jozsef Horvath <info@ministro.hu>
---
changes v1:
- fixed: drivers: serial: si4455: coding style
- fixed: drivers: serial: si4455: error checking and order
- fixed: drivers: serial: si4455: remove unnecessary compatibility
strings from si4455_dt_ids
changes v2:
- fixed: drivers: serial: si4455: coding style
changes v3:
- fixed: drivers: serial: si4455: coding style
- fixed: drivers: serial: si4455: replace device configuration procedure
(SI4455_IOC_SEZC ioctl call) with request_firmware(...).
The firmware name comes from dt (silabs,ez-config)
- fixed: drivers: serial: si4455: replace transmit/receive channel
select (SI4455_IOC_STXC/SI4455_IOC_SRXC ioctl calls)
with sysfs entries (tx_channel, rx_channel).
Initial values comes from dt (silabs,tx-channel and silabs,rx-channel)
- fixed: drivers: serial: si4455: replace package size setting
(SI4455_IOC_SSIZ ioctl call) with sysfs entry (package_size).
Initial value comes from dt (silabs,package-size)
- fixed: drivers: serial: si4455: replace getting last rssi
(SI4455_IOC_GRSSI ioctl call) with sysfs entry (current_rssi)
- fixed: drivers: serial: si4455: remove si4455_api.h
and custom ioctl definitions
changes v5:
- fixed: drivers: serial: si4455: coding style
- fixed: drivers: serial: si4455: remove struct si4455_one,
members moved to struct si4455_port
- fixed: drivers: serial: si4455: fix line endings in dev_err and
dev_dbg messages
- fixed: drivers: serial: si4455: remove unnecessary else { ... }
- fixed: drivers: serial: si4455: refactor si4455_do_work(...),
xmit circular buffer handling and start tx moved to
si4455_start_tx_xmit(...)
- fixed: drivers: serial: si4455: refactor si4455_configure
- fixed: drivers: serial: si4455: refactor interrupt handling,
remove unnecessary wrapper
- fixed: drivers: serial: si4455: modem line(si4455_get_mctrl)
and tx buffer status(si4455_tx_empty) conditions and signaling
- fixed: drivers: serial: si4455: remove unsafe int to pointer conversion
changes v6:
- fixed: drivers: serial: si4455: illegal characters in
MODULE_AUTHOR("...") Reported-by: kernel test robot <lkp@intel.com>
changes v7:
- added: drivers: serial: si4455: variable packet length support
- added: drivers: serial: si4455: extended error handling
changes v8:
- fixed: drivers: serial: si4455: removed unnecessary error checking around debugfs
- fixed: drivers: serial: si4455: removed unnecessary #ifdefs
- fixed: drivers: serial: kconfig: add missing module name to help text
- fixed: drivers: serial: si4455: device name ttySI changed to ttySSi
because of collosion. The ttySI is allocated alredy in devices.txt
- fixed: drivers: serial: si4455: revoved unnecessary administrative definitions
(major, minor)
- fixed: drivers: serial: si4455: definition PORT_SI4455 changed and moved to serial_core.h
- fixed: drivers: serial: si4455: silabs,tx-timeout property name changed
to silabs,tx-timeout-ms
---
Documentation/driver-api/serial/index.rst | 1 +
Documentation/driver-api/serial/si4455.rst | 216 +++
MAINTAINERS | 7 +
drivers/tty/serial/Kconfig | 11 +
drivers/tty/serial/Makefile | 1 +
drivers/tty/serial/si4455.c | 1660 ++++++++++++++++++++
include/uapi/linux/serial_core.h | 3 +
7 files changed, 1899 insertions(+)
create mode 100644 Documentation/driver-api/serial/si4455.rst
create mode 100644 drivers/tty/serial/si4455.c
diff --git a/Documentation/driver-api/serial/index.rst b/Documentation/driver-api/serial/index.rst
index 33ad10d05b26..2b71e87f1103 100644
--- a/Documentation/driver-api/serial/index.rst
+++ b/Documentation/driver-api/serial/index.rst
@@ -23,6 +23,7 @@ Serial drivers
rocket
serial-iso7816
serial-rs485
+ si4455
.. only:: subproject and html
diff --git a/Documentation/driver-api/serial/si4455.rst b/Documentation/driver-api/serial/si4455.rst
new file mode 100644
index 000000000000..f81c35c18067
--- /dev/null
+++ b/Documentation/driver-api/serial/si4455.rst
@@ -0,0 +1,216 @@
+.. SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+
+======================================
+Silicon Labs Si4455 serial port driver
+======================================
+
+This is a serial port driver for Silicon Labs Si4455 Sub-GHz transciver.
+
+The goal of this driver is to removing wires between central(linux) device and remote serial devices/sensors,
+but keeping the original user software.
+It represents regular serial interface for the user space.
+
+[0] - Datasheet: https://www.silabs.com/documents/public/data-sheets/Si4455.pdf
+
+Additional readings:
+
+* [1] - AN 692 Si4355/Si4455 PROGRAMMING GUIDE (https://www.silabs.com/documents/public/application-notes/AN692.pdf)
+* [2] - AN796 WIRELESS DEVELOPMENT SUITE GENERAL DESCRIPTION (https://www.silabs.com/documents/public/application-notes/AN796.pdf)
+* [3] - EZRadio REVB1A API (https://www.silabs.com/documents/public/application-notes/EZRadio_REVB1_API.zip)
+* [4] - EZRadio REVC2A API (https://www.silabs.com/documents/public/application-notes/EZRadio_REVC2_API.zip)
+* [5] - Using PART_INFO command to identify EZRadio/PRO part number (https://www.silabs.com/community/wireless/proprietary/knowledge-base.entry.html/2017/04/04/using_part_info_comm-XwP9)
+
+1. Device tree bindings
+=======================
+You can see the required parameters in silabs,si4455.yaml (Documentation/devicetree/bindings/serial/silabs,si4455.yaml)
+
+2. Device configuration(firmware)
+=================================
+
+2.1. Prerequirements
+^^^^^^^^^^^^^^^^^^^^
+
+To generate radio configuration, the Silicon Labs Wireless Development Suite
+(https://www.silabs.com/documents/login/software/WDS3-Setup.exe) is required.
+
+2.2. Generate
+^^^^^^^^^^^^^
+
+Before you start, see ref: [2]
+
+1. Start WDS
+2. Select *Simulate radio* option on startup screen
+3. Select *Si4455* and revision you have in *Select Radio* dialog
+4. Select *Radio Configuration Application* in *Application Manager*
+5. Select *Bidirectional packet* project in *Radio Configuration Application* dialog.
+6. Make your project specific configuration.
+7. Click *Generate source* than choose *Export custom project for third party IDE*
+
+2.3. Compile
+^^^^^^^^^^^^
+
+1. create ``radio_config/main.c`` with the following code::
+
+ #include <stdlib.h>
+ #include <stdio.h>
+ #include <stdint.h>
+ #include <unistd.h>
+ #include "radio.h"
+ #include "radio_config.h"
+ uint8_t Radio_Configuration_Data_Array[] = RADIO_CONFIGURATION_DATA_ARRAY;
+ int main (int argc, char ** argv)
+ {
+ write(1, Radio_Configuration_Data_Array, sizeof(Radio_Configuration_Data_Array));
+ }
+
+2. create ``radio_config/radio.h`` with the following code::
+
+ #ifndef RADIO_H_
+ #define RADIO_H_
+
+ #define RADIO_MAX_PACKET_LENGTH 64u
+
+ typedef struct
+ {
+ uint8_t *Radio_ConfigurationArray;
+
+ uint8_t Radio_ChannelNumber;
+ uint8_t Radio_PacketLength;
+ uint8_t Radio_State_After_Power_Up;
+
+ uint16_t Radio_Delay_Cnt_After_Reset;
+
+ uint8_t Radio_CustomPayload[RADIO_MAX_PACKET_LENGTH];
+ } tRadioConfiguration;
+ #endif /* RADIO_H_ */
+
+3. in case of **Chip revision C2** copy ``src\drivers\radio\Si4455\si4455_patch.h`` from project directory generated by WDS to ``radio_config/si4455_patch.h``
+4. copy ``src\application\radio_config.h`` from project directory generated by WDS to ``radio_config/radio_config.h``
+5. in case of **Chip revision C2** change the content of ``radio_config/radio_config.h``::
+
+ - #include "..\drivers\radio\Si4455\si4455_patch.h"
+ + #include "si4455_patch.h"
+
+6. compile
+
+ gcc radio_config/main.c -o radio_config/fwc
+
+7. run
+
+ radio_config/fwc > /lib/firmware/$(firmware-name)
+
+3. Device
+=========
+
+The driver instances are comes up under ``/dev`` as
+
+ /dev/ttySLX
+
+, where ``X`` is the index of instance.
+
+The driver supports only:
+
+* Char size: 8 bits
+* Handshake: None or RTSCTS.
+ RTSCTS preferred
+
+4. sysfs
+========
+
+The si4455 driver uses configuration parameters and maintains statistics inside sysfs filesystem.
+
+**current_rssi**:
+
+ Path:
+ /sys/class/tty/ttySLX/device/current_rssi
+
+ Description:
+ Shows the latest rssi value measured by chip.
+ To convert the value to dBm. See chapter *3.2.1. Received Signal Strength Indicator* in [0]
+
+**package_size**:
+
+ Path:
+ /sys/class/tty/ttySLX/device/package_size
+
+ Description:
+ Shows or stores the package size.
+ The new value applied immediately.
+ Variable package size (package_size = 0)
+
+**rx_channel**:
+
+ Path:
+ /sys/class/tty/ttySLX/device/rx_channel
+
+ Description:
+ Shows or stores the receive channel index.
+ The new value applied immediately.
+
+**tx_channel**:
+
+ Path:
+ /sys/class/tty/ttySLX/device/tx_channel
+
+ Description:
+ Shows or stores transmit channel index.
+ The new value will be used on next data transmit.
+
+**tx_timeout**:
+
+ Path:
+ /sys/class/tty/ttySLX/device/tx_timeout
+
+ Description:
+ Shows or stores the transmit timeout.
+ The new value will be used on next data transmit.
+
+5. debugfs
+^^^^^^^^^^
+
+The si4455 driver maintains statistics inside debugfs filesystem.
+
+**cts_error_count**:
+
+ Path:
+ /sys/kernel/debug/spiX.Y/si4455/cts_error_count
+
+ Description:
+ The numer of cts timeouts. In case of cts timeout, the driver reinitialize the chip.
+
+**tx_error_count**:
+
+ Path:
+ /sys/kernel/debug/spiX.Y/si4455/tx_error_count
+
+ Description:
+ The number of tx timeouts. In case of tx timeout, the driver restarts the last data transmission.
+
+**chip_rev**:
+
+ Path:
+ /sys/kernel/debug/spiX.Y/si4455/partinfo/chip_rev
+
+ Description:
+ The value of CHIPREV field in PART_INFO structure.
+ See *PART_INFO* command details in [3] or in [4]
+
+**rom_id**:
+
+ Path:
+ /sys/kernel/debug/spiX.Y/si4455/partinfo/rom_id
+
+ Description:
+ The value of ROMID field in PART_INFO structure.
+ See *PART_INFO* command details in [3] or in [4]
+ See ref [5]
+
+**part**:
+
+ Path:
+ /sys/kernel/debug/spiX.Y/si4455/partinfo/part
+
+ Description:
+ The value of PART field in PART_INFO structure.
+ See *PART_INFO* command details in [3] or in [4]
+ See ref [5]
\ No newline at end of file
diff --git a/MAINTAINERS b/MAINTAINERS
index a008b70f3c16..e6e9f2b1aecc 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15952,6 +15952,13 @@ M: Jérôme Pouiller <jerome.pouiller@silabs.com>
S: Supported
F: drivers/staging/wfx/
+SILICON LABS SI4455 SERIAL DRIVER
+M: Jozsef Horvath <info@ministro.hu>
+S: Maintained
+F: Documentation/devicetree/bindings/serial/silabs,si4455.yaml
+F: Documentation/driver-api/serial/si4455.rst
+F: drivers/tty/serial/si4455.c
+
SILICON MOTION SM712 FRAME BUFFER DRIVER
M: Sudip Mukherjee <sudipm.mukherjee@gmail.com>
M: Teddy Wang <teddy.wang@siliconmotion.com>
diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index 28f22e58639c..e91cce1074df 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -1583,6 +1583,17 @@ config SERIAL_MILBEAUT_USIO_CONSOLE
receives all kernel messages and warnings and which allows logins in
single user mode).
+config SERIAL_SI4455
+ tristate "Si4455 support"
+ depends on SPI
+ select SERIAL_CORE
+ help
+ This driver is for Silicon Labs's Si4455 Sub-GHz transciver.
+ Say 'Y' here if you wish to use it as serial port.
+
+ To compile this driver as a module, choose M here: the
+ module will be called si4455.
+
endmenu
config SERIAL_MCTRL_GPIO
diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
index caf167f0c10a..f01ff43db1d6 100644
--- a/drivers/tty/serial/Makefile
+++ b/drivers/tty/serial/Makefile
@@ -89,6 +89,7 @@ obj-$(CONFIG_SERIAL_MPS2_UART) += mps2-uart.o
obj-$(CONFIG_SERIAL_OWL) += owl-uart.o
obj-$(CONFIG_SERIAL_RDA) += rda-uart.o
obj-$(CONFIG_SERIAL_MILBEAUT_USIO) += milbeaut_usio.o
+obj-$(CONFIG_SERIAL_SI4455) += si4455.o
obj-$(CONFIG_SERIAL_SIFIVE) += sifive.o
# GPIOLIB helpers for modem control lines
diff --git a/drivers/tty/serial/si4455.c b/drivers/tty/serial/si4455.c
new file mode 100644
index 000000000000..98f80fce3486
--- /dev/null
+++ b/drivers/tty/serial/si4455.c
@@ -0,0 +1,1660 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020 Jozsef Horvath <info@ministro.hu>
+ *
+ */
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/gpio/driver.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/regmap.h>
+#include <linux/serial_core.h>
+#include <linux/serial.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/spi/spi.h>
+#include <linux/uaccess.h>
+#include <linux/string.h>
+#include <linux/firmware.h>
+#include <linux/timer.h>
+#include <linux/debugfs.h>
+
+#define SI4455_NAME "Si4455"
+#define SI4455_DEV_NAME "ttySSi"
+#define SI4455_UART_NRMAX 16
+#define SI4455_FIFO_SIZE 64
+
+#define SI4455_CMD_ID_EZCONFIG_CHECK 0x19
+#define SI4455_CMD_ID_PART_INFO 0x01
+#define SI4455_CMD_REPLY_COUNT_PART_INFO 9
+#define SI4455_CMD_ID_GET_INT_STATUS 0x20
+#define SI4455_CMD_REPLY_COUNT_GET_INT_STATUS 8
+#define SI4455_CMD_ID_FIFO_INFO 0x15
+#define SI4455_CMD_ARG_COUNT_FIFO_INFO 2
+#define SI4455_CMD_REPLY_COUNT_FIFO_INFO 2
+#define SI4455_CMD_FIFO_INFO_ARG_TX_BIT 0x01
+#define SI4455_CMD_FIFO_INFO_ARG_RX_BIT 0x02
+#define SI4455_CMD_ID_READ_CMD_BUFF 0x44
+#define SI4455_CMD_ID_READ_RX_FIFO 0x77
+#define SI4455_CMD_ID_WRITE_TX_FIFO 0x66
+#define SI4455_CMD_ID_START_RX 0x32
+#define SI4455_CMD_ARG_COUNT_START_RX 8
+#define SI4455_CMD_START_RX_RXTIMEOUT_STATE_RX 8
+#define SI4455_CMD_START_RX_RXVALID_STATE_SLEEP 1
+#define SI4455_CMD_START_RX_RXVALID_STATE_RX 8
+#define SI4455_CMD_START_RX_RXINVALID_STATE_RX 8
+#define SI4455_CMD_ID_START_TX 0x31
+#define SI4455_CMD_ARG_COUNT_START_TX 5
+#define SI4455_CMD_ID_CHANGE_STATE 0x34
+#define SI4455_CMD_ARG_COUNT_CHANGE_STATE 2
+#define SI4455_CMD_CHANGE_STATE_STATE_SLEEP 1
+#define SI4455_CMD_CHANGE_STATE_STATE_READY 3
+#define SI4455_CMD_CHANGE_STATE_STATE_RX 8
+#define SI4455_CMD_GET_CHIP_STATUS_ERROR_PEND_MASK 0x08
+#define SI4455_CMD_GET_CHIP_STATUS_ERROR_PEND_BIT 0x08
+#define SI4455_CMD_GET_INT_STATUS_RX_FIFO_AF_BIT 0x01
+#define SI4455_CMD_GET_INT_STATUS_TX_FIFO_AE_BIT 0x02
+#define SI4455_CMD_GET_INT_STATUS_PACKET_SENT_PEND_BIT 0x20
+#define SI4455_CMD_GET_INT_STATUS_PACKET_RX_PEND_BIT 0x10
+#define SI4455_CMD_GET_INT_STATUS_CRC_ERROR_BIT 0x08
+#define SI4455_CMD_GET_INT_STATUS_CHIP_RDY_BIT 0x04
+#define SI4455_CMD_GET_INT_STATUS_CMD_ERROR_BIT 0x08
+#define SI4455_CMD_GET_INT_STATUS_ST_CHANGED_BIT 0x10
+#define SI4455_CMD_GET_INT_STATUS_FIFO_UO_BIT 0x20
+#define SI4455_CMD_ID_GET_MODEM_STATUS 0x22
+#define SI4455_CMD_ARG_COUNT_GET_MODEM_STATUS 2
+#define SI4455_CMD_REPLY_COUNT_GET_MODEM_STATUS 8
+
+struct si4455_part_info {
+ u8 chip_rev;
+ u16 part;
+ u8 pbuild;
+ u16 id;
+ u8 customer;
+ u8 rom_id;
+ u8 bond;
+};
+
+struct si4455_int_status {
+ u8 int_pend;
+ u8 int_status;
+ u8 ph_pend;
+ u8 ph_status;
+ u8 modem_pend;
+ u8 modem_status;
+ u8 chip_pend;
+ u8 chip_status;
+};
+
+struct si4455_modem_status {
+ u8 modem_pend;
+ u8 modem_status;
+ u8 curr_rssi;
+ u8 latch_rssi;
+ u8 ant1_rssi;
+ u8 ant2_rssi;
+ u16 afc_freq_offset;
+};
+
+struct si4455_fifo_info {
+ u8 rx_fifo_count;
+ u8 tx_fifo_space;
+};
+
+struct si4455_port {
+ struct uart_port port;
+ struct dentry *dbgfs_dir;
+ struct work_struct tx_work;
+ struct work_struct tx_wd_work;
+ struct work_struct cts_wd_work;
+ struct timer_list tx_wd_timer;
+ struct timer_list cts_wd_timer;
+ struct mutex mutex; /* For syncing access to device */
+ struct gpio_desc *shdn_gpio;
+ struct si4455_part_info part_info;
+ struct si4455_modem_status modem_status;
+ u32 tx_channel;
+ u32 rx_channel;
+ u32 package_size;
+ u32 current_rssi;
+ u32 cts_error_count;
+ u32 tx_error_count;
+ int power_count;
+ u32 tx_wd_timeout;
+ u32 tx_pending_size;
+ char ez_fw_name[255];
+ bool connected;
+ bool suspended;
+ bool configured;
+ bool cts_error;
+ bool tx_pending;
+ bool rx_pending;
+ bool tx_stopped;
+ bool rx_stopped;
+};
+
+static struct uart_driver si4455_uart = {
+ .owner = THIS_MODULE,
+ .driver_name = SI4455_NAME,
+ .dev_name = SI4455_DEV_NAME,
+ .nr = SI4455_UART_NRMAX,
+};
+
+static DEFINE_MUTEX(si4455_ports_lock); /* race on probe */
+static DECLARE_BITMAP(si4455_port_lines, SI4455_UART_NRMAX);
+
+static int si4455_get_response(struct uart_port *port, int length, u8 *data)
+{
+ int ret;
+ u8 data_out[] = { SI4455_CMD_ID_READ_CMD_BUFF };
+ u8 *data_in;
+ struct spi_transfer xfer[2];
+ int timeout = 100;
+
+ if (length > 0 && !data)
+ return -EINVAL;
+
+ data_in = kzalloc(1 + length, GFP_KERNEL);
+ if (!data_in)
+ return -ENOMEM;
+
+ memset(&xfer, 0x00, sizeof(xfer));
+ xfer[0].tx_buf = data_out;
+ xfer[0].len = sizeof(data_out);
+ xfer[1].rx_buf = data_in;
+ xfer[1].len = 1 + length;
+
+ while (--timeout > 0) {
+ data_out[0] = SI4455_CMD_ID_READ_CMD_BUFF;
+ ret = spi_sync_transfer(to_spi_device(port->dev), xfer,
+ ARRAY_SIZE(xfer));
+ if (ret) {
+ dev_err(port->dev, "%s: spi_sync_transfer error (%i)\n", __func__, ret);
+ break;
+ }
+
+ if (data_in[0] == 0xFF) {
+ if (length > 0 && data)
+ memcpy(data, &data_in[1], length);
+
+ break;
+ }
+ usleep_range(100, 200);
+ }
+ if (!ret && timeout == 0) {
+ dev_err(port->dev, "%s: timeout\n", __func__);
+ ret = -EIO;
+ }
+ kfree(data_in);
+ return ret;
+}
+
+static int si4455_poll_cts(struct uart_port *port)
+{
+ int ret;
+ struct si4455_port *s = dev_get_drvdata(port->dev);
+
+ ret = si4455_get_response(port, 0, NULL);
+ if (ret == -EIO) {
+ s->cts_error_count++;
+ s->cts_error = true;
+ }
+ return ret;
+}
+
+static int si4455_send_command(struct uart_port *port, int length, u8 *data)
+{
+ int ret;
+
+ ret = si4455_poll_cts(port);
+ if (ret) {
+ dev_err(port->dev,
+ "%s: si4455_poll_cts error (%i)\n", __func__, ret);
+ return ret;
+ }
+
+ ret = spi_write(to_spi_device(port->dev), data, length);
+ if (ret) {
+ dev_err(port->dev,
+ "%s: spi_write error (%i)\n", __func__, ret);
+ }
+
+ return ret;
+}
+
+static int si4455_send_command_get_response(struct uart_port *port,
+ int out_length, u8 *data_out,
+ int in_length, u8 *data_in)
+{
+ int ret;
+
+ ret = si4455_send_command(port, out_length, data_out);
+ if (ret) {
+ dev_err(port->dev,
+ "%s: si4455_send_command error (%i)\n", __func__, ret);
+ return ret;
+ }
+
+ return si4455_get_response(port, in_length, data_in);
+}
+
+static int si4455_read_data(struct uart_port *port, u8 command, bool poll,
+ int length, u8 *data)
+{
+ int ret = 0;
+ u8 data_out[] = { command };
+ struct spi_transfer xfer[] = {
+ {
+ .tx_buf = data_out,
+ .len = sizeof(data_out),
+ }, {
+ .rx_buf = data,
+ .len = length,
+ }
+ };
+
+ if (poll) {
+ ret = si4455_poll_cts(port);
+ if (ret)
+ return ret;
+ }
+
+ ret = spi_sync_transfer(to_spi_device(port->dev),
+ xfer,
+ ARRAY_SIZE(xfer));
+ if (ret) {
+ dev_err(port->dev,
+ "%s: spi_sync_transfer error (%i)\n", __func__, ret);
+ }
+
+ return ret;
+}
+
+static int si4455_write_data(struct uart_port *port, u8 command, bool poll,
+ int length, const u8 *data)
+{
+ int ret = 0;
+ u8 *data_out;
+
+ if (poll) {
+ ret = si4455_poll_cts(port);
+ if (ret)
+ return ret;
+ }
+
+ data_out = kzalloc(1 + length, GFP_KERNEL);
+ if (!data_out)
+ return -ENOMEM;
+
+ data_out[0] = command;
+ memcpy(&data_out[1], data, length);
+ ret = spi_write(to_spi_device(port->dev), data_out, 1 + length);
+ if (ret) {
+ dev_err(port->dev,
+ "%s: spi_write error (%i)\n", __func__, ret);
+ }
+
+ kfree(data_out);
+
+ return ret;
+}
+
+static void si4455_set_power(struct si4455_port *priv, bool on)
+{
+ if (!priv->shdn_gpio)
+ return;
+
+ gpiod_direction_output(priv->shdn_gpio, 0);
+ if (on) {
+ usleep_range(14000, 15000);
+ gpiod_set_value(priv->shdn_gpio, 1);
+ usleep_range(14000, 15000);
+ }
+}
+
+static int si4455_s_power(struct device *dev, bool on)
+{
+ struct si4455_port *s = dev_get_drvdata(dev);
+
+ if (s->power_count == !on)
+ si4455_set_power(s, on);
+ s->power_count += on ? 1 : -1;
+ WARN_ON(s->power_count < 0);
+
+ return 0;
+}
+
+static int si4455_get_part_info(struct uart_port *port,
+ struct si4455_part_info *result)
+{
+ int ret;
+ u8 data_out[] = { SI4455_CMD_ID_PART_INFO };
+ u8 data_in[SI4455_CMD_REPLY_COUNT_PART_INFO];
+
+ ret = si4455_send_command_get_response(port, sizeof(data_out), data_out,
+ sizeof(data_in), data_in);
+ if (ret) {
+ dev_err(port->dev,
+ "%s: si4455_send_command_get_response error (%i)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ result->chip_rev = data_in[0];
+ memcpy(&result->part, &data_in[1], sizeof(result->part));
+ result->pbuild = data_in[3];
+ memcpy(&result->id, &data_in[4], sizeof(result->id));
+ result->customer = data_in[6];
+ result->rom_id = data_in[7];
+ result->bond = data_in[8];
+
+ return 0;
+}
+
+static int si4455_get_int_status(struct uart_port *port, u8 ph_clear,
+ u8 modem_clear, u8 chip_clear,
+ struct si4455_int_status *result)
+{
+ int ret;
+ u8 data_out[] = {
+ SI4455_CMD_ID_GET_INT_STATUS,
+ ph_clear,
+ modem_clear,
+ chip_clear
+ };
+ u8 data_in[SI4455_CMD_REPLY_COUNT_GET_INT_STATUS];
+
+ ret = si4455_send_command_get_response(port, sizeof(data_out), data_out,
+ sizeof(data_in), data_in);
+ if (ret) {
+ dev_err(port->dev,
+ "%s: si4455_send_command_get_response error (%i)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ result->int_pend = data_in[0];
+ result->int_status = data_in[1];
+ result->ph_pend = data_in[2];
+ result->ph_status = data_in[3];
+ result->modem_pend = data_in[4];
+ result->modem_status = data_in[5];
+ result->chip_pend = data_in[6];
+ result->chip_status = data_in[7];
+
+ return 0;
+}
+
+static int si4455_get_modem_status(struct uart_port *port, u8 modem_clear,
+ struct si4455_modem_status *result)
+{
+ int ret;
+ u8 data_out[] = {
+ SI4455_CMD_ID_GET_MODEM_STATUS,
+ modem_clear,
+ };
+ u8 data_in[SI4455_CMD_REPLY_COUNT_GET_MODEM_STATUS];
+
+ ret = si4455_send_command_get_response(port, sizeof(data_out), data_out,
+ sizeof(data_in), data_in);
+ if (ret) {
+ dev_err(port->dev,
+ "%s: si4455_send_command_get_response error (%i)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ result->modem_pend = data_in[0];
+ result->modem_status = data_in[1];
+ result->curr_rssi = data_in[2];
+ result->latch_rssi = data_in[3];
+ result->ant1_rssi = data_in[4];
+ result->ant2_rssi = data_in[5];
+ memcpy(&result->afc_freq_offset, &data_in[6],
+ sizeof(result->afc_freq_offset));
+
+ return 0;
+}
+
+static int si4455_fifo_info(struct uart_port *port, u8 fifo,
+ struct si4455_fifo_info *result)
+{
+ int ret;
+ u8 data_out[SI4455_CMD_ARG_COUNT_FIFO_INFO] = {
+ SI4455_CMD_ID_FIFO_INFO, fifo
+ };
+ u8 data_in[SI4455_CMD_REPLY_COUNT_FIFO_INFO] = { 0 };
+
+ ret = si4455_send_command_get_response(port, sizeof(data_out), data_out,
+ sizeof(data_in), data_in);
+ if (ret) {
+ dev_err(port->dev,
+ "%s: si4455_send_command_get_response error (%i)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ result->rx_fifo_count = data_in[0];
+ result->tx_fifo_space = data_in[1];
+
+ return 0;
+}
+
+static int si4455_read_rx_fifo(struct uart_port *port, int length, u8 *data)
+{
+ return si4455_read_data(port, SI4455_CMD_ID_READ_RX_FIFO, false,
+ length, data);
+}
+
+static int si4455_write_tx_fifo(struct uart_port *port, int length, u8 *data)
+{
+ return si4455_write_data(port, SI4455_CMD_ID_WRITE_TX_FIFO, false,
+ length, data);
+}
+
+static int si4455_rx(struct uart_port *port, u32 channel, u8 condition,
+ u16 length, u8 next_state1, u8 next_state2,
+ u8 next_state3)
+{
+ u8 data_out[SI4455_CMD_ARG_COUNT_START_RX];
+
+ data_out[0] = SI4455_CMD_ID_START_RX;
+ data_out[1] = channel;
+ data_out[2] = condition;
+ data_out[3] = (u8)(length >> 8);
+ data_out[4] = (u8)(length);
+ data_out[5] = next_state1;
+ data_out[6] = next_state2;
+ data_out[7] = next_state3;
+
+ return si4455_send_command(port, SI4455_CMD_ARG_COUNT_START_RX,
+ data_out);
+}
+
+static int si4455_tx(struct uart_port *port, u8 channel, u8 condition,
+ u16 length)
+{
+ struct si4455_port *s = dev_get_drvdata(port->dev);
+ u8 data_out[SI4455_CMD_ARG_COUNT_START_TX + 1];
+ u8 out_length = SI4455_CMD_ARG_COUNT_START_TX;
+
+ if (s->part_info.rom_id == 6)
+ out_length += 1;
+
+ data_out[0] = SI4455_CMD_ID_START_TX;
+ data_out[1] = channel;
+ data_out[2] = condition;
+ data_out[3] = (u8)(length >> 8);
+ data_out[4] = (u8)(length);
+ if (s->part_info.rom_id == 6)
+ data_out[5] = 0x44;
+
+ return si4455_send_command(port, out_length, data_out);
+}
+
+static int si4455_change_state(struct uart_port *port, u8 next_state1)
+{
+ u8 data_out[SI4455_CMD_ARG_COUNT_CHANGE_STATE];
+
+ data_out[0] = SI4455_CMD_ID_CHANGE_STATE;
+ data_out[1] = (u8)next_state1;
+
+ return si4455_send_command(port, SI4455_CMD_ARG_COUNT_CHANGE_STATE,
+ data_out);
+}
+
+static int si4455_begin_tx(struct uart_port *port, u32 channel, int length,
+ u8 *data)
+{
+ int ret = 0;
+ struct si4455_int_status int_status = { 0 };
+ struct si4455_fifo_info fifo_info = { 0 };
+
+ if (length > SI4455_FIFO_SIZE || length < 0)
+ return -EINVAL;
+
+ ret = si4455_get_int_status(port, 0, 0, 0, &int_status);
+ if (ret) {
+ dev_err(port->dev, "%s: si4455_get_int_status error (%i)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = si4455_fifo_info(port, SI4455_CMD_FIFO_INFO_ARG_TX_BIT,
+ &fifo_info);
+ if (ret) {
+ dev_err(port->dev, "%s: si4455_fifo_info error (%i)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = si4455_write_tx_fifo(port, (u16)length, data);
+ if (ret) {
+ dev_err(port->dev, "%s: si4455_write_tx_fifo error (%i)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = si4455_tx(port, channel, 0x10, length);
+ if (ret) {
+ dev_err(port->dev, "%s: si4455_tx error (%i)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int si4455_end_tx(struct uart_port *port)
+{
+ int ret = 0;
+ struct si4455_int_status int_status = { 0 };
+
+ ret = si4455_get_int_status(port, 0, 0, 0, &int_status);
+ if (ret) {
+ dev_err(port->dev, "%s: si4455_get_int_status error (%i)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int si4455_begin_rx(struct uart_port *port, u32 channel, u32 length)
+{
+ struct si4455_port *s = dev_get_drvdata(port->dev);
+ int ret = 0;
+ struct si4455_int_status int_status = { 0 };
+ struct si4455_fifo_info fifo_info = { 0 };
+
+ ret = si4455_get_int_status(port, 0, 0, 0, &int_status);
+ if (ret) {
+ dev_err(port->dev, "%s: si4455_get_int_status error (%i)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = si4455_fifo_info(port, SI4455_CMD_FIFO_INFO_ARG_RX_BIT,
+ &fifo_info);
+ if (ret) {
+ dev_err(port->dev, "%s: si4455_fifo_info error (%i)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ if (s->rx_stopped)
+ return 0;
+
+ ret = si4455_rx(port, channel, 0x00, length,
+ SI4455_CMD_START_RX_RXTIMEOUT_STATE_RX,
+ SI4455_CMD_START_RX_RXVALID_STATE_RX,
+ SI4455_CMD_START_RX_RXINVALID_STATE_RX);
+ if (ret) {
+ dev_err(port->dev, "%s: si4455_rx error (%i)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int si4455_end_rx(struct uart_port *port, u32 length, u8 *data)
+{
+ return si4455_read_rx_fifo(port, length, data);
+}
+
+static int si4455_configure(struct uart_port *port, const u8 *configuration_data)
+{
+ int ret = 0;
+ u8 col;
+ u8 response;
+ u8 count;
+ u8 cmd;
+ struct si4455_int_status int_status = { 0 };
+ u8 radio_cmd[16];
+
+ /*
+ * While cycle as far as the pointer points to a command
+ */
+ while (*configuration_data != 0x00) {
+ /*
+ * Commands structure in the array:
+ * --------------------------------
+ * LEN | <LEN length of data>
+ */
+ count = *configuration_data++;
+ cmd = *configuration_data;
+ dev_dbg(port->dev, "%s: count (%u), cmd (%u)\n",
+ __func__, count, cmd);
+
+ if (count > 16 && count <= 128 && cmd == SI4455_CMD_ID_WRITE_TX_FIFO) {
+ /*
+ * Load array to the device
+ */
+ configuration_data++;
+ ret = si4455_write_data(port, SI4455_CMD_ID_WRITE_TX_FIFO,
+ true, count - 1, configuration_data);
+ if (ret) {
+ dev_err(port->dev, "%s: si4455_write_data error (%i)\n",
+ __func__, ret);
+ break;
+ }
+
+ /*
+ * Point to the next command
+ */
+ configuration_data += count - 1;
+
+ /*
+ * Continue command interpreter
+ */
+ continue;
+ } else if (count > 16) {
+ /*
+ * Number of command bytes exceeds
+ * maximal allowable length
+ */
+ ret = -EINVAL;
+ break;
+ }
+
+ for (col = 0u; col < count; col++) {
+ radio_cmd[col] = *configuration_data;
+ configuration_data++;
+ }
+
+ dev_dbg(port->dev, "%s: radio_cmd[0] (%u)\n", __func__, radio_cmd[0]);
+ ret = si4455_send_command_get_response(port, count, radio_cmd,
+ 1, &response);
+ if (ret) {
+ dev_err(port->dev,
+ "%s: si4455_send_command_get_response error (%i)\n",
+ __func__, ret);
+ break;
+ }
+
+ /*
+ * Check response byte of EZCONFIG_CHECK command
+ */
+ if (radio_cmd[0] == SI4455_CMD_ID_EZCONFIG_CHECK) {
+ if (response) {
+ /*
+ * Number of command bytes exceeds
+ * maximal allowable length
+ */
+ ret = -EIO;
+ dev_err(port->dev, "%s: EZConfig check error (%i)\n",
+ __func__, radio_cmd[0]);
+ break;
+ }
+ }
+
+ /*
+ * Get and clear all interrupts. An error has occurred...
+ */
+ si4455_get_int_status(port, 0, 0, 0, &int_status);
+ if (int_status.chip_pend
+ & SI4455_CMD_GET_CHIP_STATUS_ERROR_PEND_MASK) {
+ ret = -EIO;
+ dev_err(port->dev, "%s: chip error (%i)\n",
+ __func__, int_status.chip_pend);
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static int si4455_re_configure(struct uart_port *port, const struct firmware *configuration)
+{
+ int ret = 0;
+ struct si4455_port *s = dev_get_drvdata(port->dev);
+
+ s->configured = 0;
+ if (s->power_count == 0)
+ si4455_s_power(port->dev, true);
+
+ ret = si4455_configure(port, configuration->data);
+ if (ret == 0) {
+ s->configured = true;
+ s->cts_error = false;
+ }
+ return ret;
+}
+
+static int si4455_start_tx_xmit(struct uart_port *port)
+{
+ int ret;
+ struct si4455_port *s = dev_get_drvdata(port->dev);
+ struct circ_buf *xmit = &port->state->xmit;
+ u32 tx_pending;
+ u32 tx_to_end;
+ u8 *data;
+ u8 *payload;
+ u32 max_length;
+
+ if (s->tx_stopped)
+ return 0;
+
+ tx_pending = uart_circ_chars_pending(xmit);
+ if (tx_pending == 0 || tx_pending < s->package_size)
+ return 0;
+
+ max_length = (s->package_size == 0) ? SI4455_FIFO_SIZE - 3 : s->package_size;
+ tx_pending = (tx_pending > max_length) ? max_length : tx_pending;
+ if (s->package_size == 0)
+ data = kzalloc(tx_pending + 1, GFP_KERNEL);
+ else
+ data = kzalloc(tx_pending, GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ if (s->package_size == 0) {
+ data[0] = tx_pending;
+ payload = &data[1];
+ } else {
+ payload = data;
+ }
+
+ tx_to_end = CIRC_CNT_TO_END(xmit->head, xmit->tail, UART_XMIT_SIZE);
+ if (tx_to_end < tx_pending) {
+ memcpy(payload, xmit->buf + xmit->tail, tx_to_end);
+ memcpy(&payload[tx_to_end], xmit->buf, tx_pending - tx_to_end);
+ } else {
+ memcpy(payload, xmit->buf + xmit->tail, tx_pending);
+ }
+
+ if (s->package_size == 0)
+ ret = si4455_begin_tx(port, s->tx_channel, tx_pending + 1, data);
+ else
+ ret = si4455_begin_tx(port, s->tx_channel, tx_pending, data);
+ if (!ret) {
+ s->tx_pending = true;
+ s->tx_pending_size = tx_pending;
+ uart_handle_cts_change(&s->port, 0);
+ mod_timer(&s->tx_wd_timer, jiffies + msecs_to_jiffies(s->tx_wd_timeout));
+ }
+
+ kfree(data);
+
+ return ret;
+}
+
+static int si4455_cancel_tx(struct uart_port *port)
+{
+ int ret = 0;
+ struct si4455_port *s = dev_get_drvdata(port->dev);
+
+ if (s->tx_pending) {
+ si4455_end_tx(port);
+ s->tx_pending = false;
+ s->tx_pending_size = 0;
+ uart_handle_cts_change(&s->port, TIOCM_CTS);
+ ret = si4455_change_state(port, SI4455_CMD_CHANGE_STATE_STATE_SLEEP);
+ }
+ return ret;
+}
+
+static int si4455_do_work(struct uart_port *port)
+{
+ int ret = 0;
+ struct si4455_port *s = dev_get_drvdata(port->dev);
+ struct circ_buf *xmit = &port->state->xmit;
+
+ mutex_lock(&s->mutex);
+ if (!s->suspended && s->connected && s->configured && s->power_count > 0) {
+ if (!(uart_circ_empty(xmit) || uart_tx_stopped(port) || s->tx_pending))
+ ret = si4455_start_tx_xmit(port);
+
+ if (!ret && !s->tx_pending)
+ ret = si4455_begin_rx(port, s->rx_channel, s->package_size);
+ }
+ mutex_unlock(&s->mutex);
+ return ret;
+}
+
+static void si4455_handle_rx_pend(struct si4455_port *s, struct si4455_fifo_info *fifo_info)
+{
+ struct uart_port *port = &s->port;
+ u8 *data;
+ int sret = 0;
+ int i = 0;
+ u32 length;
+
+ length = (s->package_size == 0) ? fifo_info->rx_fifo_count : s->package_size;
+
+ data = kzalloc(length, GFP_KERNEL);
+ if (!data)
+ return;
+
+ sret = si4455_end_rx(port, length, data);
+ if (sret) {
+ dev_err(port->dev, "%s: si4455_end_rx error (%i)\n",
+ __func__, sret);
+ } else {
+ if (!s->rx_stopped) {
+ for (i = 0; i < length; i++) {
+ uart_insert_char(port, 0, 0, data[i], TTY_NORMAL);
+ port->icount.rx++;
+ }
+ tty_flip_buffer_push(&port->state->port);
+ }
+ }
+ kfree(data);
+}
+
+static void si4455_handle_tx_pend(struct si4455_port *s)
+{
+ struct uart_port *port = &s->port;
+ struct circ_buf *xmit = &port->state->xmit;
+ u32 sent;
+
+ if (s->tx_pending) {
+ sent = (s->package_size == 0) ? s->tx_pending_size
+ : s->package_size;
+ port->icount.tx += sent;
+ xmit->tail = (xmit->tail + sent) & (UART_XMIT_SIZE - 1);
+ si4455_end_tx(port);
+ s->tx_pending = 0;
+ s->tx_pending_size = 0;
+ uart_handle_cts_change(&s->port, TIOCM_CTS);
+ }
+}
+
+static irqreturn_t si4455_ist(int irq, void *dev_id)
+{
+ struct si4455_port *s = (struct si4455_port *)dev_id;
+ struct uart_port *port = &s->port;
+ int ret;
+ struct si4455_int_status int_status = { 0 };
+ struct si4455_fifo_info fifo_info = { 0 };
+ bool have_to_do = false;
+
+ if (s->suspended || !s->connected || !s->configured || s->power_count == 0)
+ return IRQ_NONE;
+
+ mutex_lock(&s->mutex);
+ ret = si4455_get_int_status(port, 0, 0, 0, &int_status);
+ if (ret) {
+ mutex_unlock(&s->mutex);
+ return IRQ_NONE;
+ }
+
+ dev_dbg(port->dev, "%s: int_pend: 0x%x\n", __func__, int_status.int_pend);
+ dev_dbg(port->dev, "%s: int_status: 0x%x\n", __func__, int_status.int_status);
+ dev_dbg(port->dev, "%s: ph_pend: 0x%x\n", __func__, int_status.ph_pend);
+ dev_dbg(port->dev, "%s: ph_status: 0x%x\n", __func__, int_status.ph_status);
+ dev_dbg(port->dev, "%s: modem_pend: 0x%x\n", __func__, int_status.modem_pend);
+ dev_dbg(port->dev, "%s: modem_status: 0x%x\n", __func__, int_status.modem_status);
+ dev_dbg(port->dev, "%s: chip_pend: 0x%x\n", __func__, int_status.chip_pend);
+ dev_dbg(port->dev, "%s: chip_status: 0x%x\n", __func__, int_status.chip_status);
+
+ if (int_status.chip_pend & SI4455_CMD_GET_CHIP_STATUS_ERROR_PEND_BIT) {
+ dev_err(port->dev, "%s: chip_pend:CMD_ERROR_PEND\n", __func__);
+ si4455_change_state(port, SI4455_CMD_CHANGE_STATE_STATE_SLEEP);
+ si4455_fifo_info(&s->port, SI4455_CMD_FIFO_INFO_ARG_RX_BIT,
+ &fifo_info);
+ have_to_do = true;
+ } else if (int_status.ph_pend & SI4455_CMD_GET_INT_STATUS_PACKET_SENT_PEND_BIT) {
+ dev_dbg(port->dev, "%s: ph_pend:PACKET_SENT_PEND\n", __func__);
+ si4455_change_state(port, SI4455_CMD_CHANGE_STATE_STATE_SLEEP);
+ si4455_handle_tx_pend(s);
+ have_to_do = true;
+ } else if (int_status.ph_pend & SI4455_CMD_GET_INT_STATUS_PACKET_RX_PEND_BIT) {
+ dev_dbg(port->dev, "%s: ph_pend:PACKET_RX_PEND\n", __func__);
+ si4455_get_modem_status(port, 0, &s->modem_status);
+ s->current_rssi = s->modem_status.curr_rssi;
+ si4455_change_state(port, SI4455_CMD_CHANGE_STATE_STATE_SLEEP);
+ si4455_fifo_info(port, 0, &fifo_info);
+ si4455_handle_rx_pend(s, &fifo_info);
+ have_to_do = true;
+ } else if (int_status.ph_pend & SI4455_CMD_GET_INT_STATUS_CRC_ERROR_BIT) {
+ dev_dbg(port->dev, "%s: ph_pend:CRC_ERROR_PEND\n", __func__);
+ si4455_change_state(port, SI4455_CMD_CHANGE_STATE_STATE_SLEEP);
+ si4455_fifo_info(&s->port, SI4455_CMD_FIFO_INFO_ARG_RX_BIT,
+ &fifo_info);
+ have_to_do = true;
+ }
+ mutex_unlock(&s->mutex);
+ if (have_to_do)
+ si4455_do_work(port);
+ return IRQ_HANDLED;
+}
+
+static void si4455_tx_wd_event(struct timer_list *t)
+{
+ struct si4455_port *s = from_timer(s, t, tx_wd_timer);
+
+ if (s->tx_pending)
+ schedule_work(&s->tx_wd_work);
+}
+
+static void si4455_tx_wd_proc(struct work_struct *ws)
+{
+ struct si4455_port *s = container_of(ws, struct si4455_port, tx_wd_work);
+ bool have_to_work = false;
+
+ mutex_lock(&s->mutex);
+ if (s->connected && s->tx_pending) {
+ si4455_cancel_tx(&s->port);
+ s->tx_error_count++;
+ have_to_work = true;
+ dev_err(s->port.dev,
+ "%s: curent transmit operation interrupted by wd timeout\n",
+ __func__);
+ }
+ mutex_unlock(&s->mutex);
+
+ if (have_to_work)
+ si4455_do_work(&s->port);
+}
+
+static void si4455_cts_wd_event(struct timer_list *t)
+{
+ struct si4455_port *s = from_timer(s, t, cts_wd_timer);
+
+ if (s->cts_error)
+ schedule_work(&s->cts_wd_work);
+ else
+ mod_timer(&s->cts_wd_timer, jiffies + msecs_to_jiffies(100));
+}
+
+static void si4455_cts_wd_proc(struct work_struct *ws)
+{
+ struct si4455_port *s = container_of(ws, struct si4455_port, cts_wd_work);
+ const struct firmware *ez_fw = NULL;
+ bool have_to_work = false;
+ int ret;
+
+ mutex_lock(&s->mutex);
+ if (s->cts_error) {
+ dev_err(s->port.dev, "%s: interface recovery\n", __func__);
+ ret = request_firmware(&ez_fw, s->ez_fw_name, s->port.dev);
+ if (ret) {
+ dev_err(s->port.dev, "%s: firmware(%s) request error (%i)\n",
+ __func__, s->ez_fw_name, ret);
+ } else {
+ si4455_s_power(s->port.dev, false);
+ ret = si4455_re_configure(&s->port, ez_fw);
+ release_firmware(ez_fw);
+ if (ret) {
+ dev_err(s->port.dev, "%s: device configuration error (%i)\n",
+ __func__, ret);
+ }
+ }
+ have_to_work = !ret;
+ }
+ if (s->connected)
+ mod_timer(&s->cts_wd_timer, jiffies + msecs_to_jiffies(100));
+ mutex_unlock(&s->mutex);
+
+ if (have_to_work)
+ si4455_do_work(&s->port);
+}
+
+static void si4455_tx_proc(struct work_struct *ws)
+{
+ struct si4455_port *s = container_of(ws, struct si4455_port, tx_work);
+
+ si4455_do_work(&s->port);
+}
+
+static unsigned int si4455_tx_empty(struct uart_port *port)
+{
+ struct si4455_port *s = dev_get_drvdata(port->dev);
+
+ return s->tx_pending ? 0 : TIOCSER_TEMT;
+}
+
+static unsigned int si4455_get_mctrl(struct uart_port *port)
+{
+ int ret;
+ struct si4455_port *s = dev_get_drvdata(port->dev);
+
+ /*
+ * there is no a continuous real "carrier" like on phone line,
+ * but after configuration, the interface is ready to use the physical
+ * transport channel
+ */
+ ret = s->configured ? TIOCM_CAR | TIOCM_DSR : 0;
+ ret |= s->tx_pending ? 0 : TIOCM_CTS;
+
+ return ret;
+}
+
+static void si4455_set_mctrl(struct uart_port *port, unsigned int mctrl)
+{
+}
+
+static void si4455_set_termios(struct uart_port *port, struct ktermios *termios,
+ struct ktermios *old)
+{
+ dev_dbg(port->dev, "termios->c_iflag = 0x%x", termios->c_iflag);
+ dev_dbg(port->dev, "termios->c_oflag = 0x%x", termios->c_oflag);
+ dev_dbg(port->dev, "termios->c_cflag = 0x%x", termios->c_cflag);
+ dev_dbg(port->dev, "termios->c_lflag = 0x%x", termios->c_lflag);
+ dev_dbg(port->dev, "termios->c_ispeed = %u", termios->c_ispeed);
+ dev_dbg(port->dev, "termios->c_ospeed = %u", termios->c_ospeed);
+ if ((termios->c_cflag & CSIZE) != CS8)
+ dev_err(port->dev, "%s: CSIZE must be CS8\n", __func__);
+}
+
+static int si4455_startup(struct uart_port *port)
+{
+ struct si4455_port *s = dev_get_drvdata(port->dev);
+
+ mutex_lock(&s->mutex);
+ s->tx_pending = false;
+ s->tx_stopped = false;
+ s->rx_stopped = false;
+ s->connected = true;
+ mod_timer(&s->cts_wd_timer, jiffies + msecs_to_jiffies(100));
+ mutex_unlock(&s->mutex);
+ return si4455_do_work(port);
+}
+
+static void si4455_shutdown(struct uart_port *port)
+{
+ struct si4455_port *s = dev_get_drvdata(port->dev);
+
+ mutex_lock(&s->mutex);
+ del_timer_sync(&s->tx_wd_timer);
+ del_timer_sync(&s->cts_wd_timer);
+ s->connected = false;
+ si4455_change_state(&s->port, SI4455_CMD_CHANGE_STATE_STATE_SLEEP);
+ mutex_unlock(&s->mutex);
+}
+
+static const char *si4455_type(struct uart_port *port)
+{
+ struct si4455_port *s = dev_get_drvdata(port->dev);
+
+ if (port->type != PORT_SI4455)
+ return NULL;
+ if (s->part_info.rom_id == 3)
+ return "SI4455-B1A";
+ else if (s->part_info.rom_id == 6)
+ return "SI4455-C2A";
+
+ return "SI4455(UNKNOWN-REV)";
+}
+
+static void si4455_config_port(struct uart_port *port, int flags)
+{
+ if (flags & UART_CONFIG_TYPE)
+ port->type = PORT_SI4455;
+}
+
+static int si4455_verify_port(struct uart_port *port, struct serial_struct *s)
+{
+ if (s->type != PORT_UNKNOWN && s->type != PORT_SI4455)
+ return -EINVAL;
+
+ if (s->irq != port->irq)
+ return -EINVAL;
+
+ return 0;
+}
+
+static void si4455_start_tx(struct uart_port *port)
+{
+ struct si4455_port *s = container_of(port, struct si4455_port, port);
+
+ s->tx_stopped = false;
+ schedule_work(&s->tx_work);
+}
+
+static void si4455_stop_tx(struct uart_port *port)
+{
+ struct si4455_port *s = container_of(port, struct si4455_port, port);
+
+ s->tx_stopped = true;
+}
+
+static void si4455_stop_rx(struct uart_port *port)
+{
+ struct si4455_port *s = container_of(port, struct si4455_port, port);
+
+ mutex_lock(&s->mutex);
+ s->rx_stopped = true;
+ si4455_change_state(&s->port, SI4455_CMD_CHANGE_STATE_STATE_SLEEP);
+ mutex_unlock(&s->mutex);
+}
+
+static const struct uart_ops si4455_ops = {
+ .tx_empty = si4455_tx_empty,
+ /*
+ * set_mctrl: required by serial_core, but not used
+ */
+ .set_mctrl = si4455_set_mctrl,
+ .get_mctrl = si4455_get_mctrl,
+ .stop_tx = si4455_stop_tx,
+ .start_tx = si4455_start_tx,
+ .stop_rx = si4455_stop_rx,
+ .startup = si4455_startup,
+ .shutdown = si4455_shutdown,
+ .set_termios = si4455_set_termios,
+ .type = si4455_type,
+ .config_port = si4455_config_port,
+ .verify_port = si4455_verify_port,
+};
+
+static void si4455_debugfs_init(struct device *dev)
+{
+ struct si4455_port *s = dev_get_drvdata(dev);
+ struct dentry *dbgfs_si_dir;
+ struct dentry *dbgfs_partinfo_dir;
+
+ s->dbgfs_dir = debugfs_create_dir(dev_name(dev), NULL);
+
+ dbgfs_si_dir = debugfs_create_dir("si4455", s->dbgfs_dir);
+
+ debugfs_create_u32("cts_error_count", 0444, dbgfs_si_dir,
+ &s->cts_error_count);
+
+ debugfs_create_u32("tx_error_count", 0444, dbgfs_si_dir,
+ &s->tx_error_count);
+
+ dbgfs_partinfo_dir = debugfs_create_dir("partinfo", dbgfs_si_dir);
+
+ debugfs_create_u8("chip_rev", 0444, dbgfs_partinfo_dir,
+ &s->part_info.chip_rev);
+
+ debugfs_create_u8("rom_id", 0444, dbgfs_partinfo_dir,
+ &s->part_info.rom_id);
+
+ debugfs_create_u16("part", 0444, dbgfs_partinfo_dir,
+ &s->part_info.part);
+}
+
+static void si4455_debugfs_clear(struct device *dev)
+{
+ struct si4455_port *s = dev_get_drvdata(dev);
+
+ debugfs_remove_recursive(s->dbgfs_dir);
+}
+
+static int __maybe_unused si4455_suspend(struct device *dev)
+{
+ int ret;
+ struct si4455_port *s = dev_get_drvdata(dev);
+
+ mutex_lock(&s->mutex);
+ ret = si4455_cancel_tx(&s->port);
+ if (ret) {
+ mutex_unlock(&s->mutex);
+ dev_err(dev, "%s: si4455_cancel_tx error (%i)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = si4455_change_state(&s->port, SI4455_CMD_CHANGE_STATE_STATE_SLEEP);
+ s->suspended = !ret;
+ mutex_unlock(&s->mutex);
+
+ if (ret) {
+ dev_err(dev, "%s: si4455_change_state error (%i)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ return uart_suspend_port(&si4455_uart, &s->port);
+}
+
+static int __maybe_unused si4455_resume(struct device *dev)
+{
+ int ret;
+ struct si4455_port *s = dev_get_drvdata(dev);
+
+ ret = uart_resume_port(&si4455_uart, &s->port);
+ if (ret) {
+ dev_err(dev, "%s: uart_resume_port error (%i)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ s->suspended = false;
+ s->rx_stopped = false;
+
+ return si4455_do_work(&s->port);
+}
+
+static SIMPLE_DEV_PM_OPS(si4455_pm_ops, si4455_suspend, si4455_resume);
+
+static ssize_t package_size_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct si4455_port *s = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%u\n", s->package_size);
+}
+
+static ssize_t package_size_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct si4455_port *s = dev_get_drvdata(dev);
+ unsigned long val;
+ int ret;
+
+ ret = kstrtoul(buf, 10, &val);
+ if (ret)
+ return ret;
+
+ if (val > SI4455_FIFO_SIZE)
+ return -EINVAL;
+
+ s->package_size = val;
+ ret = si4455_do_work(&s->port);
+
+ return ret ? ret : count;
+}
+
+/*
+ * package_size: rw sysfs entry.
+ * Sets or returns the package size.
+ * The new value applied immediately.
+ * Variable package size (package_size == 0)
+ * currently not supported by driver.
+ */
+static DEVICE_ATTR_RW(package_size);
+
+static ssize_t rx_channel_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct si4455_port *s = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%u\n", s->rx_channel);
+}
+
+static ssize_t rx_channel_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct si4455_port *s = dev_get_drvdata(dev);
+ unsigned long val;
+ int ret;
+
+ ret = kstrtoul(buf, 10, &val);
+ if (ret)
+ return ret;
+
+ s->rx_channel = val;
+ ret = si4455_do_work(&s->port);
+
+ return ret ? ret : count;
+}
+
+/*
+ * rx_channel: rw sysfs entry.
+ * Sets or returns the receive channel index.
+ * The new value applied immediately.
+ */
+static DEVICE_ATTR_RW(rx_channel);
+
+static ssize_t tx_channel_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct si4455_port *s = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%u\n", s->tx_channel);
+}
+
+static ssize_t tx_channel_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct si4455_port *s = dev_get_drvdata(dev);
+ unsigned long val;
+ int ret;
+
+ ret = kstrtoul(buf, 10, &val);
+ if (ret)
+ return ret;
+
+ s->tx_channel = val;
+ ret = si4455_do_work(&s->port);
+
+ return ret ? ret : count;
+}
+
+/*
+ * tx_channel: rw sysfs entry.
+ * Sets or returns the transmit channel index.
+ * The new value will be used on next data transmit.
+ */
+static DEVICE_ATTR_RW(tx_channel);
+
+static ssize_t tx_timeout_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct si4455_port *s = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%u\n", s->tx_wd_timeout);
+}
+
+static ssize_t tx_timeout_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct si4455_port *s = dev_get_drvdata(dev);
+ unsigned long val;
+ int ret;
+
+ ret = kstrtoul(buf, 10, &val);
+ if (ret)
+ return ret;
+
+ s->tx_wd_timeout = val;
+ ret = si4455_do_work(&s->port);
+
+ return ret ? ret : count;
+}
+
+/*
+ * tx_timeout: rw sysfs entry.
+ * Sets or returns the transmit timeout.
+ * The new value will be used on next data transmit.
+ */
+static DEVICE_ATTR_RW(tx_timeout);
+
+static ssize_t current_rssi_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct si4455_port *s = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%u\n", s->current_rssi);
+}
+
+/*
+ * current_rssi: ro sysfs entry.
+ * Returns the latest rssi value measured by chip.
+ */
+static DEVICE_ATTR_RO(current_rssi);
+
+static struct attribute *si4455_attributes[] = {
+ &dev_attr_package_size.attr,
+ &dev_attr_rx_channel.attr,
+ &dev_attr_tx_channel.attr,
+ &dev_attr_tx_timeout.attr,
+ &dev_attr_current_rssi.attr,
+ NULL
+};
+
+static const struct attribute_group si4455_attr_group = {
+ .attrs = si4455_attributes,
+};
+
+static int si4455_probe(struct device *dev,
+ int irq)
+{
+ int ret;
+ struct si4455_port *s;
+ const void *of_ptr;
+ const struct firmware *ez_fw = NULL;
+ int line;
+
+ /* Alloc port structure */
+ s = devm_kzalloc(dev, sizeof(*s), GFP_KERNEL);
+ if (!s)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, s);
+ mutex_init(&s->mutex);
+
+ /* Alloc port line */
+ line = find_first_zero_bit(si4455_port_lines, SI4455_UART_NRMAX);
+ if (line == SI4455_UART_NRMAX) {
+ dev_err(dev, "Unable to reguest port line index\n");
+ ret = -ERANGE;
+ goto out_generic;
+ }
+
+ s->shdn_gpio = devm_gpiod_get(dev, "shutdown", GPIOD_OUT_HIGH);
+ if (IS_ERR(s->shdn_gpio)) {
+ dev_err(dev, "Unable to reguest shdn gpio\n");
+ ret = -EINVAL;
+ goto out_generic;
+ }
+
+ of_ptr = of_get_property(dev->of_node, "silabs,package-size", NULL);
+ if (IS_ERR_OR_NULL(of_ptr)) {
+ dev_err(dev, "dt silabs,package-size property not present\n");
+ ret = -EINVAL;
+ goto out_generic;
+ }
+ s->package_size = be32_to_cpup(of_ptr);
+ if (s->package_size > SI4455_FIFO_SIZE) {
+ dev_err(dev, "dt silabs,package-size property maximum is %i\n", SI4455_FIFO_SIZE);
+ ret = -EINVAL;
+ goto out_generic;
+ }
+
+ of_ptr = of_get_property(dev->of_node, "silabs,tx-channel", NULL);
+ if (IS_ERR_OR_NULL(of_ptr)) {
+ dev_err(dev, "dt silabs,tx-channel property not present\n");
+ ret = -EINVAL;
+ goto out_generic;
+ }
+ s->tx_channel = be32_to_cpup(of_ptr);
+
+ of_ptr = of_get_property(dev->of_node, "silabs,rx-channel", NULL);
+ if (IS_ERR_OR_NULL(of_ptr)) {
+ dev_err(dev, "dt silabs,rx-channel property not present\n");
+ ret = -EINVAL;
+ goto out_generic;
+ }
+ s->rx_channel = be32_to_cpup(of_ptr);
+
+ of_ptr = of_get_property(dev->of_node, "silabs,tx-timeout-ms", NULL);
+ if (IS_ERR_OR_NULL(of_ptr)) {
+ s->tx_wd_timeout = 100;
+ dev_warn(dev, "dt silabs,tx-timeout-ms property not present\n");
+ } else {
+ s->tx_wd_timeout = be32_to_cpup(of_ptr);
+ }
+
+ of_ptr = of_get_property(dev->of_node, "firmware-name", NULL);
+ if (IS_ERR_OR_NULL(of_ptr)) {
+ dev_err(dev, "dt firmware-name property not present\n");
+ ret = -EINVAL;
+ goto out_generic;
+ }
+ strncpy(s->ez_fw_name, of_ptr, sizeof(s->ez_fw_name) - 1);
+
+ /* Initialize port data */
+ s->port.dev = dev;
+ s->port.line = line;
+ s->port.irq = irq;
+ s->port.type = PORT_SI4455;
+ s->port.fifosize = SI4455_FIFO_SIZE;
+ s->port.flags = UPF_FIXED_TYPE | UPF_LOW_LATENCY;
+ s->port.iotype = UPIO_PORT;
+ s->port.iobase = 1;
+ s->port.ops = &si4455_ops;
+
+ si4455_s_power(dev, true);
+
+ ret = si4455_get_part_info(&s->port, &s->part_info);
+ dev_dbg(dev, "si4455_get_part_info() = %i\n", ret);
+ if (ret == 0) {
+ dev_info(dev, "partInfo.chip_rev = %u\n", s->part_info.chip_rev);
+ dev_info(dev, "partInfo.part = %u\n", s->part_info.part);
+ dev_info(dev, "partInfo.pbuild = %u\n", s->part_info.pbuild);
+ dev_info(dev, "partInfo.id = %u\n", s->part_info.id);
+ dev_info(dev, "partInfo.customer = %u\n", s->part_info.customer);
+ dev_info(dev, "partInfo.rom_id = %u\n", s->part_info.rom_id);
+ dev_info(dev, "partInfo.bond = %u\n", s->part_info.bond);
+ if (s->part_info.part != 0x5544) {
+ dev_err(dev, "unknown part(%u) error\n", s->part_info.part);
+ ret = -ENODEV;
+ }
+ }
+
+ if (ret)
+ goto out_generic;
+
+ ret = request_firmware(&ez_fw, s->ez_fw_name, dev);
+ if (ret) {
+ dev_err(dev, "firmware(%s) request error (%i)\n", s->ez_fw_name, ret);
+ ret = -EINVAL;
+ goto out_generic;
+ }
+
+ ret = si4455_re_configure(&s->port, ez_fw);
+ release_firmware(ez_fw);
+ if (ret) {
+ dev_err(dev, "device configuration error (%i)\n", ret);
+ ret = -EINVAL;
+ goto out_generic;
+ }
+
+ ret = si4455_change_state(&s->port, SI4455_CMD_CHANGE_STATE_STATE_SLEEP);
+ if (ret) {
+ dev_err(dev, "device change state error (%i)\n", ret);
+ goto out_generic;
+ }
+
+ /* Initialize queue for start TX */
+ INIT_WORK(&s->tx_work, si4455_tx_proc);
+ /* Initialize queue for start TX watchdog */
+ INIT_WORK(&s->tx_wd_work, si4455_tx_wd_proc);
+ /* Initialize queue for cts watchdog */
+ INIT_WORK(&s->cts_wd_work, si4455_cts_wd_proc);
+ /* Initialize timer for protecting and recovering tx_pending */
+ timer_setup(&s->tx_wd_timer, si4455_tx_wd_event, 0);
+ /* Initialize timer for recovering interface */
+ timer_setup(&s->cts_wd_timer, si4455_cts_wd_event, 0);
+
+ /* Register port */
+ ret = uart_add_one_port(&si4455_uart, &s->port);
+ if (ret) {
+ s->port.dev = NULL;
+ goto out_uart;
+ }
+
+ set_bit(line, si4455_port_lines);
+ s->port.line = line;
+
+ ret = sysfs_create_group(&dev->kobj, &si4455_attr_group);
+ if (ret) {
+ dev_err(dev, "sysfs_create_group error (%i)\n", ret);
+ goto out_uart;
+ }
+
+ si4455_debugfs_init(dev);
+
+ /* Setup interrupt */
+ ret = devm_request_threaded_irq(dev, irq, NULL, si4455_ist,
+ IRQF_ONESHOT | IRQF_SHARED,
+ dev_name(dev), s);
+ if (!ret)
+ return 0;
+
+ dev_err(dev, "Unable to reguest IRQ %i\n", irq);
+ sysfs_remove_group(&dev->kobj, &si4455_attr_group);
+
+out_uart:
+ uart_remove_one_port(&si4455_uart, &s->port);
+ clear_bit(line, si4455_port_lines);
+out_generic:
+ mutex_destroy(&s->mutex);
+ si4455_s_power(dev, false);
+
+ return ret;
+}
+
+static int si4455_remove(struct device *dev)
+{
+ struct si4455_port *s = dev_get_drvdata(dev);
+ int line = s->port.line;
+
+ cancel_work_sync(&s->tx_work);
+ sysfs_remove_group(&dev->kobj, &si4455_attr_group);
+ si4455_debugfs_clear(dev);
+ uart_remove_one_port(&si4455_uart, &s->port);
+ mutex_destroy(&s->mutex);
+ clear_bit(line, si4455_port_lines);
+
+ return 0;
+}
+
+static const struct of_device_id __maybe_unused si4455_dt_ids[] = {
+ { .compatible = "silabs,si4455" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, si4455_dt_ids);
+
+static int si4455_spi_probe(struct spi_device *spi)
+{
+ int ret;
+ const struct of_device_id *of_id;
+
+ /* Setup SPI bus */
+ spi->bits_per_word = 8;
+ spi->mode = SPI_MODE_0;
+ ret = spi_setup(spi);
+ if (ret)
+ return ret;
+
+ if (spi->dev.of_node) {
+ of_id = of_match_device(si4455_dt_ids, &spi->dev);
+ if (!of_id)
+ return -ENODEV;
+ }
+
+ mutex_lock(&si4455_ports_lock);
+ ret = si4455_probe(&spi->dev, spi->irq);
+ mutex_unlock(&si4455_ports_lock);
+ return ret;
+}
+
+static int si4455_spi_remove(struct spi_device *spi)
+{
+ int ret;
+
+ mutex_lock(&si4455_ports_lock);
+ ret = si4455_remove(&spi->dev);
+ mutex_unlock(&si4455_ports_lock);
+ return ret;
+}
+
+static struct spi_driver si4455_spi_driver = {
+ .driver = {
+ .name = SI4455_NAME,
+ .of_match_table = of_match_ptr(si4455_dt_ids),
+ .pm = &si4455_pm_ops,
+ },
+ .probe = si4455_spi_probe,
+ .remove = si4455_spi_remove,
+};
+
+static int __init si4455_uart_init(void)
+{
+ int ret;
+
+ bitmap_zero(si4455_port_lines, SI4455_UART_NRMAX);
+
+ ret = uart_register_driver(&si4455_uart);
+ if (ret)
+ return ret;
+
+ return spi_register_driver(&si4455_spi_driver);
+}
+module_init(si4455_uart_init);
+
+static void __exit si4455_uart_exit(void)
+{
+ spi_unregister_driver(&si4455_spi_driver);
+ uart_unregister_driver(&si4455_uart);
+}
+module_exit(si4455_uart_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jozsef Horvath <info@ministro.hu>");
+MODULE_DESCRIPTION("Si4455 serial driver");
diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h
index 851b982f8c4b..943c5e2e27fc 100644
--- a/include/uapi/linux/serial_core.h
+++ b/include/uapi/linux/serial_core.h
@@ -279,4 +279,7 @@
/* Freescale LINFlexD UART */
#define PORT_LINFLEXUART 122
+/* Silicon Labs SI4455 */
+#define PORT_SI4455 123
+
#endif /* _UAPILINUX_SERIAL_CORE_H */
--
2.17.1
next reply other threads:[~2021-01-12 10:20 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-01-12 10:18 Jozsef Horvath [this message]
2021-01-12 10:28 ` [PATCH v8 1/2] Serial: silabs si4455 serial driver 'Greg Kroah-Hartman'
2021-01-12 15:43 ` József Horváth
2021-01-12 16:13 ` 'Greg Kroah-Hartman'
2021-01-12 17:46 ` József Horváth
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20210112101831.GA12483@dev \
--to=info@ministro.hu \
--cc=devicetree@vger.kernel.org \
--cc=gregkh@linuxfoundation.org \
--cc=jirislaby@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-serial@vger.kernel.org \
--cc=robh+dt@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.