Skip to content

Instantly share code, notes, and snippets.

@dannas
Last active June 2, 2022 12:26
Show Gist options
  • Save dannas/59321ba0851c9895b0af71a6e39c83fb to your computer and use it in GitHub Desktop.
Save dannas/59321ba0851c9895b0af71a6e39c83fb to your computer and use it in GitHub Desktop.
sc18im700 hung task
[ 24.661114] ftdi_sio ttyUSB8: FTDI USB Serial Device converter now disconnected from ttyUSB8
[ 24.661212] ftdi_sio 1-1.2.4:1.0: device disconnected
[ 24.661901] ftdi_sio ttyUSB9: FTDI USB Serial Device converter now disconnected from ttyUSB9
[ 24.661972] ftdi_sio 1-1.2.4:1.1: device disconnected
[ 24.878017] sc18im700: i2c read timed out - gpio_get_all()
[ 243.695663] INFO: task kworker/0:2:495 blocked for more than 120 seconds.
[ 243.708006] Tainted: G C O 4.19.71 #1
[ 243.713310] "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
[ 243.721156] kworker/0:2 D 0 495 2 0x00000000
[ 243.721184] Workqueue: usb_hub_wq hub_event
[ 243.721211] [<8080fcd0>] (__schedule) from [<80810448>] (schedule+0x4c/0xac)
[ 243.721222] [<80810448>] (schedule) from [<808145d4>] (schedule_timeout+0x314/0x448)
[ 243.721232] [<808145d4>] (schedule_timeout) from [<808111cc>] (wait_for_common+0x1ac/0x1d4)
[ 243.721240] [<808111cc>] (wait_for_common) from [<80811214>] (wait_for_completion+0x20/0x24)
[ 243.721251] [<80811214>] (wait_for_completion) from [<8064bb34>] (i2c_del_adapter+0x18c/0x1cc)
[ 243.721270] [<8064bb34>] (i2c_del_adapter) from [<7f41724c>] (sc18im700_tty_close+0xe8/0xec [sc18im700])
[ 243.721288] [<7f41724c>] (sc18im700_tty_close [sc18im700]) from [<7f417268>] (sc18im700_tty_hangup+0x18/0x20 [sc18im700])
[ 243.721303] [<7f417268>] (sc18im700_tty_hangup [sc18im700]) from [<8050e1b8>] (tty_ldisc_hangup+0x84/0x1b4)
[ 243.721319] [<8050e1b8>] (tty_ldisc_hangup) from [<80506aa8>] (__tty_hangup.part.8+0x1b4/0x2d8)
[ 243.721330] [<80506aa8>] (__tty_hangup.part.8) from [<80506bf0>] (tty_vhangup+0x24/0x28)
[ 243.721356] [<80506bf0>] (tty_vhangup) from [<7f80b188>] (usb_serial_disconnect+0x7c/0xfc [usbserial])
[ 243.721416] [<7f80b188>] (usb_serial_disconnect [usbserial]) from [<806037c4>] (usb_unbind_interface+0x8c/0x274)
[ 243.721429] [<806037c4>] (usb_unbind_interface) from [<8058b070>] (device_release_driver_internal+0x188/0x228)
[ 243.721440] [<8058b070>] (device_release_driver_internal) from [<8058b130>] (device_release_driver+0x20/0x24)
[ 243.721448] [<8058b130>] (device_release_driver) from [<80589a34>] (bus_remove_device+0xdc/0x108)
[ 243.721459] [<80589a34>] (bus_remove_device) from [<80586a94>] (device_del+0x15c/0x39c)
[ 243.721470] [<80586a94>] (device_del) from [<80600f8c>] (usb_disable_device+0x9c/0x1cc)
[ 243.721482] [<80600f8c>] (usb_disable_device) from [<805f7770>] (usb_disconnect+0xcc/0x23c)
[ 243.721493] [<805f7770>] (usb_disconnect) from [<805f7758>] (usb_disconnect+0xb4/0x23c)
[ 243.721502] [<805f7758>] (usb_disconnect) from [<805f9774>] (hub_event+0xaec/0x11b8)
[ 243.721513] [<805f9774>] (hub_event) from [<8013bfd4>] (process_one_work+0x23c/0x518)
[ 243.721523] [<8013bfd4>] (process_one_work) from [<8013d0cc>] (worker_thread+0x60/0x5b8)
[ 243.721533] [<8013d0cc>] (worker_thread) from [<80142bac>] (kthread+0x16c/0x174)
[ 243.721544] [<80142bac>] (kthread) from [<801010ac>] (ret_from_fork+0x14/0x28)
[ 243.721548] Exception stack(0xb3fd7fb0 to 0xb3fd7ff8)
/*
* sc18im700.c - SC18IM700 I2C adaptor driver
*
* This file is derived from https://github.com/uber-foo/sc18im700-mod
* and https://github.com/gschorcht/i2c-ch341-usb/
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, see http://www.gnu.org/licenses/gpl.html
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/completion.h>
#include <linux/err.h>
#include <linux/tty.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/limits.h>
#include <linux/of.h>
#include <linux/io.h>
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
// Arbitrary line discipline number
#define N_SC18IM700 29
MODULE_ALIAS_LDISC(N_SC18IM700);
MODULE_DESCRIPTION("SC18IM700 serial line I2C bus adapter");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("James Turton <james@titv.se>");
#define OVERHEAD_BYTES 4
static int max_message_len = 1024 - OVERHEAD_BYTES;
module_param(max_message_len, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
MODULE_PARM_DESC(max_message_len, "The maximum length of an I2C message.");
static int debug_level = 3;
module_param(debug_level, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
MODULE_PARM_DESC(debug_level, "The verbosity level of debug messaging.");
static bool allow_repeated_start = true;
module_param(allow_repeated_start, bool, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
MODULE_PARM_DESC(allow_repeated_start, "Allow combining multiple messages into one transaction using repeated starts.");
// Arbitrary magic number
#define SC18IM700_MAGIC 0x53CB
#define SC18IM700_START 0x53
#define SC18IM700_STOP 0x50
#define SC18IM700_READ_REG 0x52
#define SC18IM700_WRITE_REG 0x57
#define SC18IM700_READ_GPIO 0x49
#define SC18IM700_WRITE_GPIO 0x4F
#define SC18IM700_POWER_DOWN 0x5A
#define SC18IM700_PORT_CFG1 0x02
#define SC18IM700_PORT_CFG2 0x03
#define SC18IM700_IO_REG 0x04
#define SC18IM700_STAT_REG 0x0A
#define SC18IM700_CFG_BI 0x00
#define SC18IM700_CFG_IN 0x01
#define SC18IM700_CFG_PP 0x02
#define SC18IM700_CFG_OD 0x03
#define SC18IM700_OK 0xF0
#define SC18IM700_NACK_ON_ADDR 0xF1
#define SC18IM700_NACK_ON_DATA 0xF2
#define SC18IM700_TIMEOUT 0xF8
#define DEB1(x) if (debug_level >= 1) x
#define DEB2(x) if (debug_level >= 2) x
#define DEB3(x) if (debug_level >= 3) x
#define DEB4(x) if (debug_level >= 4) x
#define DEB5(x) if (debug_level >= 5) x
#define DEB6(x) if (debug_level >= 6) x
#define DEB7(x) if (debug_level >= 7) x
// GPIO
#define SC18IM700_GPIO_NUM_PINS 8
#undef SC18IM700_GPIO_USE_POLLING
#define SC18IM700_GPIO_POLL_INTERVAL 100 // 100ms
struct sc18im700_pin_config
{
uint8_t pin; // pin number of sc18im700 chip
uint8_t mode; // GPIO mode
char* name; // GPIO name
};
struct sc18im700_pin_config sc18im700_board_config[SC18IM700_GPIO_NUM_PINS] =
{
// pin GPIO mode GPIO name
{ 0, SC18IM700_CFG_BI, "gpio300" }, // used as input
{ 1, SC18IM700_CFG_BI, "gpio301" }, // used as input
{ 2, SC18IM700_CFG_BI, "gpio302" }, // used as input
{ 3, SC18IM700_CFG_BI, "gpio303" }, // used as input
{ 4, SC18IM700_CFG_BI, "gpio304" }, // used as input
{ 5, SC18IM700_CFG_BI, "gpio305" }, // used as input
{ 6, SC18IM700_CFG_BI, "gpio306" }, // used as input
{ 7, SC18IM700_CFG_BI, "gpio307" } // used as input
};
struct sc18im700_device
{
int magic;
struct tty_struct *tty_dev; // ptr to TTY structure
struct i2c_adapter i2c_dev; // ptr to I2C structure
struct work_struct tx_work; // Flushes transmit buffer
unsigned char *rbuff; // receiver buffer
int rcount; // received chars counter
int rlen; // bytes to receive
unsigned char *xbuff; // transmitter buffer
unsigned char *xhead; // pointer to next XMIT byte
int xleft; // bytes left in XMIT queue
spinlock_t lock; // xmit lock
struct completion xcompletion; // read completion
struct completion rcompletion; // xmit completion
struct i2c_msg *curr_msg; // the current message
struct gpio_chip gpio; // chip descriptor for GPIOs
uint8_t gpio_num; // number of pins used as GPIOs
uint8_t gpio_mask; // configuratoin mask defines IN/OUT pins
uint8_t gpio_io_data; // current value of SC18IM700 I/O register
uint16_t gpio_io_cfg; // current value of SC18IM700 cfg register
struct task_struct * gpio_thread; // GPIO poll thread
struct sc18im700_pin_config* gpio_pins [SC18IM700_GPIO_NUM_PINS]; // pin configurations (gpio_num elements)
uint8_t gpio_bits [SC18IM700_GPIO_NUM_PINS]; // bit of I/O data byte (gpio_num elements)
uint8_t gpio_values [SC18IM700_GPIO_NUM_PINS]; // current values (gpio_num elements)
char* gpio_names [SC18IM700_GPIO_NUM_PINS]; // pin names (gpio_num elements)
struct workqueue_struct* gpio_poll_wq; // workqueue for polling the hardware
struct delayed_work gpio_poll_dw; // delayed work for polling the hardware
};
static const struct i2c_adapter_quirks sc18im700_i2c_quirks =
{
.flags = 0,
};
static int sc18im700_cfg_probe(struct sc18im700_device* sc18im700_dev)
{
struct sc18im700_pin_config* cfg;
int i;
if (!sc18im700_dev)
return -EINVAL;
DEB7(printk(KERN_DEBUG "sc18im700: cfg_probe\n"));
sc18im700_dev->gpio_mask = 0x3f; // default
sc18im700_dev->gpio_num = 0;
sc18im700_dev->gpio_thread = 0;
sc18im700_dev->gpio_poll_wq = 0;
for (i = 0; i < SC18IM700_GPIO_NUM_PINS; i++)
{
cfg = sc18im700_board_config + i;
// is pin configurable at all
if (cfg->pin > 7)
{
DEB3(printk(KERN_ERR "sc18im700: pin %d: is not configurable \n", cfg->pin));
return -EINVAL;
}
// set GPIO configuration
sc18im700_dev->gpio_names [sc18im700_dev->gpio_num] = cfg->name;
sc18im700_dev->gpio_pins [sc18im700_dev->gpio_num] = cfg;
// map SC18IM700 pin to bit D0...D7 in the SC18IM700 I/O data byte
sc18im700_dev->gpio_bits[sc18im700_dev->gpio_num] = (1 << cfg->pin);
if (cfg->mode == SC18IM700_CFG_BI)
// if pin is INPUT, it has to be masked out in GPIO direction mask
sc18im700_dev->gpio_mask &= ~sc18im700_dev->gpio_bits[sc18im700_dev->gpio_num];
DEB7(printk(KERN_DEBUG "sc18im700: %s %s gpio=%d\n",
cfg->mode == SC18IM700_CFG_BI ? "input " : "output",
cfg->name, sc18im700_dev->gpio_num));
sc18im700_dev->gpio_num++;
}
return 0;
}
static void sc18im700_cfg_remove(struct sc18im700_device* sc18im700_dev)
{
return;
}
static uint8_t sc18im700_cfg_get_mode(uint16_t cfg, unsigned int gpio)
{
return (cfg & (0x3 << (gpio *2))) >> (gpio *2);
}
static uint16_t sc18im700_cfg_set_mode(uint16_t cfg, unsigned int gpio, uint8_t dir)
{
return (cfg & ~(0x3 << (gpio *2))) | (dir << (gpio *2));
}
static int sc18im700_gpio_get_all(struct gpio_chip *chip)
{
struct sc18im700_device* sc18im700_dev;
unsigned char *pos;
unsigned long time_left;
int actual, val;
sc18im700_dev = (struct sc18im700_device*)gpiochip_get_data(chip);
if (!sc18im700_dev)
return -EINVAL;
pos = sc18im700_dev->xbuff;
// Write out the beginning of the message
*pos++ = SC18IM700_READ_GPIO;
*pos++ = SC18IM700_STOP;
spin_lock_bh(&sc18im700_dev->lock);
// First make sure we're connected
if (!sc18im700_dev->tty_dev || sc18im700_dev->magic != SC18IM700_MAGIC)
{
spin_unlock_bh(&sc18im700_dev->lock);
return -EINVAL;
}
// The ordering here is important
sc18im700_dev->rlen = 1;
reinit_completion(&sc18im700_dev->rcompletion);
reinit_completion(&sc18im700_dev->xcompletion);
set_bit(TTY_DO_WRITE_WAKEUP, &sc18im700_dev->tty_dev->flags);
actual = sc18im700_dev->tty_dev->ops->write(sc18im700_dev->tty_dev, sc18im700_dev->xbuff, pos - sc18im700_dev->xbuff);
sc18im700_dev->xleft = (pos - sc18im700_dev->xbuff) - actual;
sc18im700_dev->xhead = sc18im700_dev->xbuff + actual;
spin_unlock_bh(&sc18im700_dev->lock);
// If we didn't xmit the whole message, we need to wait for it to finish
if (sc18im700_dev->xleft > 0)
{
time_left = wait_for_completion_timeout(&sc18im700_dev->xcompletion, sc18im700_dev->i2c_dev.timeout);
if (!time_left)
{
DEB3(printk(KERN_ERR "sc18im700: i2c write timed out"));
return -ETIMEDOUT;
}
}
time_left = wait_for_completion_timeout(&sc18im700_dev->rcompletion, sc18im700_dev->i2c_dev.timeout);
if (!time_left)
{
DEB3(printk(KERN_ERR "sc18im700: i2c read timed out - gpio_get_all()"));
return -ETIMEDOUT;
}
sc18im700_dev->rcount = 0; // Reset the read counter
val = sc18im700_dev->rbuff[0];
sc18im700_dev->gpio_io_data = val;
DEB7(printk(KERN_DEBUG "sc18im700: gpio_get_all: 0x%02x\n", val));
return val;
}
static void sc18im700_gpio_set(struct gpio_chip *chip, unsigned int gpio, int val)
{
struct sc18im700_device* sc18im700_dev;
unsigned char *pos;
unsigned long time_left, pins, cfg;
int actual, i;
sc18im700_dev = (struct sc18im700_device*)gpiochip_get_data(chip);
if (!sc18im700_dev)
return;
DEB7(printk(KERN_DEBUG "sc18im700: gpio_set: %d=%d\n", gpio, val));
// Calculate new pin character
#ifdef SC18IM700_GPIO_USE_POLLING
pins = sc18im700_dev->gpio_io_data;
#else
pins = sc18im700_gpio_get_all(chip);
#endif
if (val)
set_bit(gpio, &pins);
else
clear_bit(gpio, &pins);
// If the pin is set as an input we should always write 1 to it
cfg = sc18im700_dev->gpio_io_cfg;
for (i = 0; i < SC18IM700_GPIO_NUM_PINS; i++)
{
if (sc18im700_cfg_get_mode(cfg, i) == SC18IM700_CFG_BI)
pins |= (1 << i);
}
pos = sc18im700_dev->xbuff;
// Write out the beginning of the message
*pos++ = SC18IM700_WRITE_GPIO;
*pos++ = pins;
*pos++ = SC18IM700_STOP;
spin_lock_bh(&sc18im700_dev->lock);
// First make sure we're connected
if (!sc18im700_dev->tty_dev || sc18im700_dev->magic != SC18IM700_MAGIC)
{
spin_unlock_bh(&sc18im700_dev->lock);
return;
}
// The ordering here is important
sc18im700_dev->rlen = 0;
reinit_completion(&sc18im700_dev->xcompletion);
set_bit(TTY_DO_WRITE_WAKEUP, &sc18im700_dev->tty_dev->flags);
actual = sc18im700_dev->tty_dev->ops->write(sc18im700_dev->tty_dev, sc18im700_dev->xbuff, pos - sc18im700_dev->xbuff);
sc18im700_dev->xleft = (pos - sc18im700_dev->xbuff) - actual;
sc18im700_dev->xhead = sc18im700_dev->xbuff + actual;
spin_unlock_bh(&sc18im700_dev->lock);
// If we didn't xmit the whole message, we need to wait for it to finish
if (sc18im700_dev->xleft > 0)
{
time_left = wait_for_completion_timeout(&sc18im700_dev->xcompletion, sc18im700_dev->i2c_dev.timeout);
if (!time_left)
{
DEB3(printk(KERN_ERR "sc18im700: i2c write timed out"));
return;
}
}
sc18im700_dev->rcount = 0; // Reset the read counter
return;
}
static int sc18im700_gpio_get(struct gpio_chip *chip, unsigned int gpio)
{
struct sc18im700_device* sc18im700_dev;
bool result;
unsigned long pins;
sc18im700_dev = (struct sc18im700_device*)gpiochip_get_data(chip);
if (!sc18im700_dev)
return -EINVAL;
#ifdef SC18IM700_GPIO_USE_POLLING
pins = sc18im700_dev->gpio_io_data;
#else
pins = sc18im700_gpio_get_all(chip);
#endif
result = test_bit(gpio, &pins);
DEB7(printk(KERN_DEBUG "sc18im700: gpio_get: %d=%d\n", gpio, result));
return result;
}
#ifdef SC18IM700_GPIO_USE_POLLING
static int sc18im700_gpio_get_direction_all(struct gpio_chip *chip)
{
struct sc18im700_device* sc18im700_dev;
unsigned char *pos;
unsigned long time_left;
int actual, val;
sc18im700_dev = (struct sc18im700_device*)gpiochip_get_data(chip);
if (!sc18im700_dev)
return -EINVAL;
pos = sc18im700_dev->xbuff;
// Write out the beginning of the message
*pos++ = SC18IM700_READ_REG;
*pos++ = SC18IM700_PORT_CFG1;
*pos++ = SC18IM700_PORT_CFG2;
*pos++ = SC18IM700_STOP;
spin_lock_bh(&sc18im700_dev->lock);
// First make sure we're connected
if (!sc18im700_dev->tty_dev || sc18im700_dev->magic != SC18IM700_MAGIC)
{
spin_unlock_bh(&sc18im700_dev->lock);
return -EINVAL;
}
// The ordering here is important
sc18im700_dev->rlen = 2;
reinit_completion(&sc18im700_dev->rcompletion);
reinit_completion(&sc18im700_dev->xcompletion);
set_bit(TTY_DO_WRITE_WAKEUP, &sc18im700_dev->tty_dev->flags);
actual = sc18im700_dev->tty_dev->ops->write(sc18im700_dev->tty_dev, sc18im700_dev->xbuff, pos - sc18im700_dev->xbuff);
sc18im700_dev->xleft = (pos - sc18im700_dev->xbuff) - actual;
sc18im700_dev->xhead = sc18im700_dev->xbuff + actual;
spin_unlock_bh(&sc18im700_dev->lock);
// If we didn't xmit the whole message, we need to wait for it to finish
if (sc18im700_dev->xleft > 0)
{
time_left = wait_for_completion_timeout(&sc18im700_dev->xcompletion, sc18im700_dev->i2c_dev.timeout);
if (!time_left)
{
DEB3(printk(KERN_ERR "sc18im700: i2c write timed out"));
return -ETIMEDOUT;
}
}
time_left = wait_for_completion_timeout(&sc18im700_dev->rcompletion, sc18im700_dev->i2c_dev.timeout);
if (!time_left)
{
DEB3(printk(KERN_ERR "sc18im700: i2c read timed out - gpio_get_direction_all()"));
return -ETIMEDOUT;
}
sc18im700_dev->rcount = 0; // Reset the read counter
val = sc18im700_dev->rbuff[0];
val |= (sc18im700_dev->rbuff[1] << 8);
DEB7(printk(KERN_DEBUG "sc18im700: gpio_get_direction_all: 0x%04x\n", val));
return val;
}
#endif
static int sc18im700_gpio_direction(struct gpio_chip *chip, unsigned int gpio, int dir)
{
struct sc18im700_device* sc18im700_dev;
unsigned char *pos;
unsigned long time_left, cfg;
int actual;
sc18im700_dev = (struct sc18im700_device*)gpiochip_get_data(chip);
if (!sc18im700_dev)
return -EINVAL;
DEB7(printk(KERN_DEBUG "sc18im700: gpio_direction_input: %d\n", gpio));
// Calculate new config character
// cfg = sc18im700_gpio_get_direction_all(chip);
cfg = sc18im700_cfg_set_mode(sc18im700_dev->gpio_io_cfg, gpio, dir);
sc18im700_dev->gpio_io_cfg = cfg;
pos = sc18im700_dev->xbuff;
// Write out the beginning of the message
*pos++ = SC18IM700_WRITE_REG;
*pos++ = SC18IM700_PORT_CFG1;
*pos++ = (cfg & 0xff);
*pos++ = SC18IM700_PORT_CFG2;
*pos++ = (cfg >> 8);
*pos++ = SC18IM700_STOP;
spin_lock_bh(&sc18im700_dev->lock);
// First make sure we're connected
if (!sc18im700_dev->tty_dev || sc18im700_dev->magic != SC18IM700_MAGIC)
{
spin_unlock_bh(&sc18im700_dev->lock);
return -EINVAL;
}
// The ordering here is important
sc18im700_dev->rlen = 0;
reinit_completion(&sc18im700_dev->xcompletion);
set_bit(TTY_DO_WRITE_WAKEUP, &sc18im700_dev->tty_dev->flags);
actual = sc18im700_dev->tty_dev->ops->write(sc18im700_dev->tty_dev, sc18im700_dev->xbuff, pos - sc18im700_dev->xbuff);
sc18im700_dev->xleft = (pos - sc18im700_dev->xbuff) - actual;
sc18im700_dev->xhead = sc18im700_dev->xbuff + actual;
spin_unlock_bh(&sc18im700_dev->lock);
// If we didn't xmit the whole message, we need to wait for it to finish
if (sc18im700_dev->xleft > 0) {
time_left = wait_for_completion_timeout(&sc18im700_dev->xcompletion, sc18im700_dev->i2c_dev.timeout);
if (!time_left) {
DEB3(printk(KERN_ERR "sc18im700: i2c write timed out"));
return -ETIMEDOUT;
}
}
sc18im700_dev->rcount = 0; // Reset the read counter
return 0;
}
static int sc18im700_gpio_direction_input(struct gpio_chip *chip, unsigned int gpio)
{
// We must set the pin to be high in order to activate the internal pullup so
// that the pin doesn't wildly float.
sc18im700_gpio_set(chip, gpio, 1);
return sc18im700_gpio_direction(chip, gpio, SC18IM700_CFG_BI);
}
static int sc18im700_gpio_direction_output(struct gpio_chip *chip, unsigned int gpio, int val)
{
(void)val;
return sc18im700_gpio_direction(chip, gpio, SC18IM700_CFG_PP);
}
static int sc18im700_gpio_get_direction(struct gpio_chip *chip, unsigned int gpio)
{
struct sc18im700_device* sc18im700_dev;
int cfg, result;
sc18im700_dev = (struct sc18im700_device*)gpiochip_get_data(chip);
if (!sc18im700_dev)
return -EINVAL;
// cfg = sc18im700_gpio_get_direction_all(chip);
cfg = sc18im700_dev->gpio_io_cfg;
result = sc18im700_cfg_get_mode(cfg, gpio);
DEB7(printk(KERN_DEBUG "sc18im700: gpio_get_direction: %d=%d\n", gpio, result));
if (result == SC18IM700_CFG_BI)
return 1;
else if (result == SC18IM700_CFG_PP)
return 0;
return -EINVAL;;
}
static int sc18im700_gpio_probe(struct sc18im700_device* sc18im700_dev)
{
struct gpio_chip *gpio = &sc18im700_dev->gpio;
int result;
int i, j = 0;
if (!sc18im700_dev)
return -EINVAL;
DEB7(printk(KERN_DEBUG "sc18im700: gpio_probe\n"));
gpio->label = "sc18im700";
gpio->parent = sc18im700_dev->tty_dev->dev;
gpio->owner = THIS_MODULE;
gpio->request = NULL;
gpio->free = NULL;
gpio->base = -1; // request dynamic ID allocation
gpio->ngpio = sc18im700_dev->gpio_num;
gpio->can_sleep = 1;
gpio->names = (void*)sc18im700_dev->gpio_names;
gpio->get_direction = sc18im700_gpio_get_direction;
gpio->direction_input = sc18im700_gpio_direction_input;
gpio->direction_output = sc18im700_gpio_direction_output;
gpio->get = sc18im700_gpio_get;
gpio->set = sc18im700_gpio_set;
result = gpiochip_add_data(gpio, sc18im700_dev);
if (result)
{
DEB3(printk(KERN_ERR "sc18im700: failed to register GPIOs: %d\n", result));
gpio->base = -1;
return result;
}
DEB7(printk(KERN_DEBUG "sc18im700: registered GPIOs from %d to %d\n", gpio->base, gpio->base + gpio->ngpio - 1));
for (i = 0; i < SC18IM700_GPIO_NUM_PINS; i++)
{
if ((result = gpio_request(gpio->base + j, sc18im700_board_config[i].name)) ||
// (result = sc18im700_gpio_direction_input(gpio, i)) ||
(result = gpio_export(gpio->base + j, true)))
{
DEB3(printk(KERN_ERR "sc18im700: failed to export GPIO %s: %d",
sc18im700_board_config[i].name, result));
// reduce number of GPIOs to avoid crashes during free in case of error
sc18im700_dev->gpio_num = j ? j-1 : 0;
return result;
}
j++;
}
// sc18im700_dev->gpio_thread = kthread_run (&ch341_gpio_poll_function, ch341_dev, "i2c-ch341-usb-poll");
return 0;
}
static void sc18im700_gpio_remove(struct sc18im700_device* sc18im700_dev)
{
int i;
if (!sc18im700_dev)
return;
// if (sc18im700_dev->gpio_thread)
// {
// kthread_stop(sc18im700_dev->gpio_thread);
// wake_up_process (sc18im700_dev->gpio_thread);
// }
if (sc18im700_dev->gpio.base > 0)
{
for (i = 0; i < sc18im700_dev->gpio_num; ++i)
gpio_free(sc18im700_dev->gpio.base + i);
gpiochip_remove(&sc18im700_dev->gpio);
}
}
static int sc18im700_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
{
struct sc18im700_device *sc18im700_dev = (struct sc18im700_device *)adap->algo_data;
struct i2c_msg *msg;
int curmsg, addr, numbytes, actual, i;
unsigned char *pos;
unsigned long time_left;
DEB7(printk(KERN_DEBUG "sc18im700: Transfer Starts"));
curmsg = 0;
while (curmsg < num)
{
numbytes = 0;
msg = &msgs[curmsg];
sc18im700_dev->curr_msg = msg;
sc18im700_dev->rlen = msg->len;
DEB7(printk(KERN_DEBUG "sc18im700: Operation: %s, "
"Addr: 0x%02x, Length: %d, "
"Current Transfer No: %d, "
"Total No of transfer: %d, "
"Flags: 0x%02x",
(msg->flags & I2C_M_RD) ? "Read" : "Write",
msg->addr, msg->len, (curmsg + 1), num, msg->flags));
if (msg->len > max_message_len)
{
DEB3(printk(KERN_ERR "sc18im700: message length %d is greater than allowed (%d)\n", msg->len, max_message_len));
return -EIO;
}
// Address is in the 7 most significant bits
addr = (msg->addr & 0x7F) << 1;
// Least significant bit is R/W flag
// Check if this is a read operation
if (msg->flags & I2C_M_RD)
addr |= 1;
if (msg->flags & I2C_M_REV_DIR_ADDR)
addr ^= 1;
// Reset buffer if starting first message or for new transaction
if (curmsg == 0 || !allow_repeated_start)
pos = sc18im700_dev->xbuff;
// Write out the beginning of the message
*pos++ = SC18IM700_START;
*pos++ = addr;
*pos++ = msg->len;
// If this is a write, write out the message body
if (!(msg->flags & I2C_M_RD))
{
for (i = 0; i < msg->len; i++)
{
*pos++ = msg->buf[i];
}
}
// Append stop bit and send when transation is completed
if (++curmsg == num || !allow_repeated_start)
{
// Write out the stop marker
*pos++ = SC18IM700_STOP;
spin_lock_bh(&sc18im700_dev->lock);
// First make sure we're connected
if (!sc18im700_dev->tty_dev || sc18im700_dev->magic != SC18IM700_MAGIC)
{
spin_unlock_bh(&sc18im700_dev->lock);
return -EINVAL;
}
if (!(msg->flags & I2C_M_RD))
{
*pos++ = SC18IM700_READ_REG;
*pos++ = SC18IM700_STAT_REG;
*pos++ = SC18IM700_STOP;
sc18im700_dev->rlen = 1;
}
// The ordering here is important
reinit_completion(&sc18im700_dev->rcompletion);
reinit_completion(&sc18im700_dev->xcompletion);
set_bit(TTY_DO_WRITE_WAKEUP, &sc18im700_dev->tty_dev->flags);
actual = sc18im700_dev->tty_dev->ops->write(sc18im700_dev->tty_dev, sc18im700_dev->xbuff, pos - sc18im700_dev->xbuff);
sc18im700_dev->xleft = (pos - sc18im700_dev->xbuff) - actual;
sc18im700_dev->xhead = sc18im700_dev->xbuff + actual;
spin_unlock_bh(&sc18im700_dev->lock);
// If we didn't xmit the whole message, we need to wait for it to finish
if (sc18im700_dev->xleft > 0)
{
time_left = wait_for_completion_timeout(&sc18im700_dev->xcompletion, adap->timeout);
if (!time_left)
{
DEB3(printk(KERN_ERR "sc18im700: i2c write timed out"));
return -ETIMEDOUT;
}
}
time_left = wait_for_completion_timeout(&sc18im700_dev->rcompletion, adap->timeout);
if (!time_left)
{
DEB3(printk(KERN_ERR "sc18im700: i2c read timed out"));
return -ETIMEDOUT;
}
if (msg->flags & I2C_M_RD)
{
// If this is a read, drain read buffer into message buffer
for (i = 0; i < sc18im700_dev->rcount; i++)
msg->buf[i] = sc18im700_dev->rbuff[i];
}
else
{
// If this is a write, check the return from the status register
if (sc18im700_dev->rbuff[0] != SC18IM700_OK)
{
if (sc18im700_dev->rbuff[0] & SC18IM700_NACK_ON_ADDR)
{
DEB6(printk(KERN_INFO "sc18im700: i2c NACK on address"));
return -EREMOTEIO;
}
if (sc18im700_dev->rbuff[0] & SC18IM700_NACK_ON_DATA)
{
DEB3(printk(KERN_ERR "sc18im700: i2c NACK on data"));
return -EREMOTEIO;
}
DEB3(printk(KERN_ERR "sc18im700: i2c timed out"));
return -ETIMEDOUT;
}
}
sc18im700_dev->rcount = 0; // Reset the read counter
}
}
return num;
}
// Write out any remaining transmit buffer.
// Scheduled when tty is writable.
static void sc18im700_tty_transmit(struct work_struct *work)
{
struct sc18im700_device *sc18im700_dev = container_of(work, struct sc18im700_device, tx_work);
int actual;
DEB7(printk(KERN_DEBUG "sc18im700: tty_transmit\n"));
spin_lock_bh(&sc18im700_dev->lock);
// First make sure we're connected
if (!sc18im700_dev->tty_dev || sc18im700_dev->magic != SC18IM700_MAGIC)
{
spin_unlock_bh(&sc18im700_dev->lock);
return;
}
if (sc18im700_dev->xleft <= 0)
{
// Now serial buffer is almost free & we can start
// transmission of another packet
clear_bit(TTY_DO_WRITE_WAKEUP, &sc18im700_dev->tty_dev->flags);
complete(&sc18im700_dev->xcompletion);
spin_unlock_bh(&sc18im700_dev->lock);
return;
}
actual = sc18im700_dev->tty_dev->ops->write(sc18im700_dev->tty_dev, sc18im700_dev->xhead, sc18im700_dev->xleft);
sc18im700_dev->xleft -= actual;
sc18im700_dev->xhead += actual;
spin_unlock_bh(&sc18im700_dev->lock);
}
static u32 sc18im700_i2c_func(struct i2c_adapter *adap)
{
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
}
static const struct i2c_algorithm sc18im700_i2c_algo =
{
.master_xfer = sc18im700_i2c_xfer,
.functionality = sc18im700_i2c_func,
};
static int sc18im700_i2c_probe(struct sc18im700_device* sc18im700_dev)
{
int result;
if (!sc18im700_dev)
return -EINVAL;
DEB7(printk(KERN_DEBUG "sc18im700: i2c_probe\n"));
sc18im700_dev->i2c_dev.owner = THIS_MODULE;
sc18im700_dev->i2c_dev.class = I2C_CLASS_DEPRECATED;
sc18im700_dev->i2c_dev.algo = &sc18im700_i2c_algo;
sc18im700_dev->i2c_dev.algo_data = sc18im700_dev;
strlcpy(sc18im700_dev->i2c_dev.name, "sc18im700 I2C adapter", sizeof(sc18im700_dev->i2c_dev.name));
sc18im700_dev->i2c_dev.dev.parent = sc18im700_dev->tty_dev->dev;
sc18im700_dev->i2c_dev.dev.of_node = sc18im700_dev->tty_dev->dev->of_node;
sc18im700_dev->i2c_dev.quirks = &sc18im700_i2c_quirks;
result = i2c_add_adapter(&sc18im700_dev->i2c_dev);
if (result < 0)
{
DEB3(printk(KERN_ERR "sc18im700: failed to add i2c adapter\n"));
return result;
}
DEB7(printk(KERN_DEBUG "sc18im700: created i2c device /dev/i2c-%d", sc18im700_dev->i2c_dev.nr));
return 0;
}
static void sc18im700_i2c_remove(struct sc18im700_device* sc18im700_dev)
{
if (!sc18im700_dev)
return;
if (sc18im700_dev->i2c_dev.nr)
i2c_del_adapter (&sc18im700_dev->i2c_dev);
return;
}
#ifdef SC18IM700_GPIO_USE_POLLING
static void sc18im700_poll_work_handler(struct work_struct *work)
{
struct sc18im700_device *sc18im700_dev = container_of(to_delayed_work(work), struct sc18im700_device, gpio_poll_dw);
struct gpio_chip *gpio = &sc18im700_dev->gpio;
DEB7(printk(KERN_DEBUG "sc18im700: sc18im700_poll_work_handler\n"));
sc18im700_gpio_get_all(gpio);
queue_delayed_work(sc18im700_dev->gpio_poll_wq, &(sc18im700_dev->gpio_poll_dw), msecs_to_jiffies(SC18IM700_GPIO_POLL_INTERVAL));
}
#endif
static int sc18im700_tty_open(struct tty_struct *tty)
{
struct sc18im700_device *sc18im700_dev;
int err, status;
tty->termios.c_lflag &= ~(ICANON | ECHO);
if (tty->ops->write == NULL)
return -EOPNOTSUPP;
if (tty->ops->set_termios == NULL)
return -EOPNOTSUPP;
tty->ops->set_termios(tty, &tty->termios);
sc18im700_dev = tty->disc_data;
err = -EEXIST;
// First make sure we're not already connected
if (sc18im700_dev && sc18im700_dev->magic == SC18IM700_MAGIC)
return err;
sc18im700_dev = kzalloc(sizeof(*sc18im700_dev), GFP_KERNEL);
sc18im700_dev->magic = SC18IM700_MAGIC;
sc18im700_dev->tty_dev = tty;
sc18im700_dev->xbuff = kzalloc(max_message_len + OVERHEAD_BYTES, GFP_KERNEL);
sc18im700_dev->rbuff = kzalloc(max_message_len, GFP_KERNEL);
init_completion(&sc18im700_dev->xcompletion);
init_completion(&sc18im700_dev->rcompletion);
spin_lock_init(&sc18im700_dev->lock);
INIT_WORK(&sc18im700_dev->tx_work, sc18im700_tty_transmit);
tty->disc_data = sc18im700_dev;
tty->receive_room = 65536; // We don't flow control
status = sc18im700_cfg_probe(sc18im700_dev);
if (status)
{
DEB3(printk(KERN_ERR "sc18im700: can't configure gpio device: %d\n", status));
return status;
}
else
{
DEB7(printk(KERN_DEBUG "sc18im700: configured gpio device\n"));
}
status = sc18im700_gpio_probe(sc18im700_dev);
if (status)
{
DEB3(printk(KERN_ERR "sc18im700: can't probe gpio device: %d\n", status));
return status;
}
else
{
DEB7(printk(KERN_DEBUG "sc18im700: probed gpio device\n"));
}
status = sc18im700_i2c_probe(sc18im700_dev);
if (status)
{
DEB3(printk(KERN_ERR "sc18im700: can't probe i2c device: %d\n", status));
return status;
}
else
{
DEB7(printk(KERN_DEBUG "sc18im700: probed i2c device\n"));
}
#ifdef SC18IM700_GPIO_USE_POLLING
if (!sc18im700_dev->gpio_poll_wq)
sc18im700_dev->gpio_poll_wq = create_singlethread_workqueue("sc18im700_gpio_poll_wq");
if (sc18im700_dev->gpio_poll_wq)
{
DEB7(printk(KERN_DEBUG "sc18im700: queue polling hardware\n"));
INIT_DELAYED_WORK(&(sc18im700_dev->gpio_poll_dw), sc18im700_poll_work_handler);
queue_delayed_work(sc18im700_dev->gpio_poll_wq, &(sc18im700_dev->gpio_poll_dw), msecs_to_jiffies(SC18IM700_GPIO_POLL_INTERVAL));
}
#endif
return 0;
}
static void sc18im700_tty_close(struct tty_struct *tty)
{
struct sc18im700_device *sc18im700_dev = (struct sc18im700_device *) tty->disc_data;
// First make sure we're connected
if (!sc18im700_dev || sc18im700_dev->magic != SC18IM700_MAGIC || sc18im700_dev->tty_dev != tty)
return;
flush_work(&sc18im700_dev->tx_work);
if (sc18im700_dev->gpio_poll_wq)
flush_workqueue(sc18im700_dev->gpio_poll_wq);
spin_lock_bh(&sc18im700_dev->lock);
tty->disc_data = NULL;
sc18im700_dev->tty_dev = NULL;
spin_unlock_bh(&sc18im700_dev->lock);
sc18im700_cfg_remove(sc18im700_dev);
sc18im700_gpio_remove(sc18im700_dev);
sc18im700_i2c_remove(sc18im700_dev);
kfree(sc18im700_dev->xbuff);
kfree(sc18im700_dev->rbuff);
kfree(sc18im700_dev);
}
static int sc18im700_tty_hangup(struct tty_struct *tty)
{
sc18im700_tty_close(tty);
return 0;
}
// Perform I/O control on an active SLCAN channel
static int sc18im700_tty_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg)
{
// struct sc18im700_device *sc18im700_dev = (struct sc18im700_device *) tty->disc_data;
return tty_mode_ioctl(tty, file, cmd, arg);
}
// Called by the driver when there's room for more data.
// Schedule the transmit.
static void sc18im700_tty_write_wakeup(struct tty_struct *tty)
{
struct sc18im700_device *sc18im700_dev = (struct sc18im700_device *) tty->disc_data;
schedule_work(&sc18im700_dev->tx_work);
}
// Handle the 'receiver data ready' interrupt.
// This function is called by the 'tty_io' module in the kernel when
// a block of data has been received. This will not be re-entered
// while running but other ldisc functions may be called
// in parallel
static void sc18im700_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp, char *fp, int count)
{
struct sc18im700_device *sc18im700_dev = (struct sc18im700_device *) tty->disc_data;
DEB7(printk(KERN_DEBUG "sc18im700: tty_receive_buf\n"));
if (!sc18im700_dev || sc18im700_dev->magic != SC18IM700_MAGIC)
return;
spin_lock_bh(&sc18im700_dev->lock);
// Read the characters out of the buffer
while (count--)
{
if (fp && *fp++)
{
cp++;
continue;
}
sc18im700_dev->rbuff[sc18im700_dev->rcount++] = *cp++;
}
if (sc18im700_dev->rcount >= sc18im700_dev->rlen)
{
complete(&sc18im700_dev->rcompletion);
}
spin_unlock_bh(&sc18im700_dev->lock);
}
static struct tty_ldisc_ops sc18im700_ldisc =
{
.owner = THIS_MODULE,
.magic = TTY_LDISC_MAGIC,
.name = "sc18im700",
.open = sc18im700_tty_open,
.close = sc18im700_tty_close,
.hangup = sc18im700_tty_hangup,
.ioctl = sc18im700_tty_ioctl,
.receive_buf = sc18im700_tty_receive_buf,
.write_wakeup = sc18im700_tty_write_wakeup,
};
static int __init sc18im700_init(void)
{
int status;
DEB6(printk(KERN_INFO "sc18im700: SC18IM700 serial line I2C bus adapter\n"));
// Fill in our line protocol discipline, and register it
status = tty_register_ldisc(N_SC18IM700, &sc18im700_ldisc);
if (status)
{
DEB3(printk(KERN_ERR "sc18im700: can't register line discipline: %d\n", status));
}
else
{
DEB7(printk(KERN_DEBUG "sc18im700: registered line discipline\n"));
}
return status;
}
static void __exit sc18im700_exit(void)
{
// TODO(james): First of all: check for active disciplines and hangup them
int status;
status = tty_unregister_ldisc(N_SC18IM700);
if (status)
{
DEB3(printk(KERN_ERR "sc18im700: can't unregister line discipline: %d\n", status));
}
else
{
DEB7(printk(KERN_DEBUG "sc18im700: unregistered line discipline\n"));
}
}
module_init(sc18im700_init);
module_exit(sc18im700_exit);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment