Last active
June 2, 2022 12:26
-
-
Save dannas/59321ba0851c9895b0af71a6e39c83fb to your computer and use it in GitHub Desktop.
sc18im700 hung task
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[ 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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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