Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-2.0-only
wanxlfw.inc
+262
View File
@@ -0,0 +1,262 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# wan devices configuration
#
menuconfig WAN
bool "Wan interfaces support"
help
Wide Area Networks (WANs), such as X.25, Frame Relay and leased
lines, are used to interconnect Local Area Networks (LANs) over vast
distances with data transfer rates significantly higher than those
achievable with commonly used asynchronous modem connections.
Usually, a quite expensive external device called a `WAN router' is
needed to connect to a WAN. As an alternative, a relatively
inexpensive WAN interface card can allow your Linux box to directly
connect to a WAN.
If you have one of those cards and wish to use it under Linux,
say Y here and also to the WAN driver for your card.
If unsure, say N.
if WAN
# Generic HDLC
config HDLC
tristate "Generic HDLC layer"
help
Say Y to this option if your Linux box contains a WAN (Wide Area
Network) card supported by this driver and you are planning to
connect the box to a WAN.
You will need supporting software from
<http://www.kernel.org/pub/linux/utils/net/hdlc/>.
Generic HDLC driver currently supports raw HDLC, Cisco HDLC, Frame
Relay, synchronous Point-to-Point Protocol (PPP) and X.25.
To compile this driver as a module, choose M here: the
module will be called hdlc.
If unsure, say N.
config HDLC_RAW
tristate "Raw HDLC support"
depends on HDLC
help
Generic HDLC driver supporting raw HDLC over WAN connections.
If unsure, say N.
config HDLC_RAW_ETH
tristate "Raw HDLC Ethernet device support"
depends on HDLC
help
Generic HDLC driver supporting raw HDLC Ethernet device emulation
over WAN connections.
You will need it for Ethernet over HDLC bridges.
If unsure, say N.
config HDLC_CISCO
tristate "Cisco HDLC support"
depends on HDLC
help
Generic HDLC driver supporting Cisco HDLC over WAN connections.
If unsure, say N.
config HDLC_FR
tristate "Frame Relay support"
depends on HDLC
help
Generic HDLC driver supporting Frame Relay over WAN connections.
If unsure, say N.
config HDLC_PPP
tristate "Synchronous Point-to-Point Protocol (PPP) support"
depends on HDLC
help
Generic HDLC driver supporting PPP over WAN connections.
If unsure, say N.
config HDLC_X25
tristate "X.25 protocol support"
depends on HDLC && (LAPB=m && HDLC=m || LAPB=y)
help
Generic HDLC driver supporting X.25 over WAN connections.
If unsure, say N.
comment "X.25/LAPB support is disabled"
depends on HDLC && (LAPB!=m || HDLC!=m) && LAPB!=y
source "drivers/net/wan/framer/Kconfig"
config PCI200SYN
tristate "Goramo PCI200SYN support"
depends on HDLC && PCI
help
Driver for PCI200SYN cards by Goramo sp. j.
If you have such a card, say Y here and see
<http://www.kernel.org/pub/linux/utils/net/hdlc/>.
To compile this as a module, choose M here: the
module will be called pci200syn.
If unsure, say N.
config WANXL
tristate "SBE Inc. wanXL support"
depends on HDLC && PCI
help
Driver for wanXL PCI cards by SBE Inc.
If you have such a card, say Y here and see
<http://www.kernel.org/pub/linux/utils/net/hdlc/>.
To compile this as a module, choose M here: the
module will be called wanxl.
If unsure, say N.
config WANXL_BUILD_FIRMWARE
bool "rebuild wanXL firmware"
depends on WANXL && !PREVENT_FIRMWARE_BUILD
help
Allows you to rebuild firmware run by the QUICC processor.
It requires m68k toolchains and hexdump programs.
You should never need this option, say N.
config PC300TOO
tristate "Cyclades PC300 RSV/X21 alternative support"
depends on HDLC && PCI
help
Alternative driver for PC300 RSV/X21 PCI cards made by
Cyclades, Inc. If you have such a card, say Y here and see
<http://www.kernel.org/pub/linux/utils/net/hdlc/>.
To compile this as a module, choose M here: the module
will be called pc300too.
If unsure, say N here.
config N2
tristate "SDL RISCom/N2 support"
depends on HDLC && ISA
help
Driver for RISCom/N2 single or dual channel ISA cards by
SDL Communications Inc.
If you have such a card, say Y here and see
<http://www.kernel.org/pub/linux/utils/net/hdlc/>.
Note that N2csu and N2dds cards are not supported by this driver.
To compile this driver as a module, choose M here: the module
will be called n2.
If unsure, say N.
config C101
tristate "Moxa C101 support"
depends on HDLC && ISA
help
Driver for C101 SuperSync ISA cards by Moxa Technologies Co., Ltd.
If you have such a card, say Y here and see
<http://www.kernel.org/pub/linux/utils/net/hdlc/>.
To compile this driver as a module, choose M here: the
module will be called c101.
If unsure, say N.
config FARSYNC
tristate "FarSync T-Series support"
depends on HDLC && PCI && HAS_IOPORT
help
Support for the FarSync T-Series X.21 (and V.35/V.24) cards by
FarSite Communications Ltd.
Synchronous communication is supported on all ports at speeds up to
8Mb/s (128K on V.24) using synchronous PPP, Cisco HDLC, raw HDLC,
Frame Relay or X.25/LAPB.
If you want the module to be automatically loaded when the interface
is referenced then you should add "alias hdlcX farsync" to a file
in /etc/modprobe.d/ for each interface, where X is 0, 1, 2, ..., or
simply use "alias hdlc* farsync" to indicate all of them.
To compile this driver as a module, choose M here: the
module will be called farsync.
config FSL_QMC_HDLC
tristate "Freescale QMC HDLC support"
depends on HDLC
depends on CPM_QMC
help
HDLC support using the Freescale QUICC Multichannel Controller (QMC).
To compile this driver as a module, choose M here: the
module will be called fsl_qmc_hdlc.
If unsure, say N.
config FSL_UCC_HDLC
tristate "Freescale QUICC Engine HDLC support"
depends on HDLC
depends on QUICC_ENGINE
help
Driver for Freescale QUICC Engine HDLC controller. The driver
supports HDLC in NMSI and TDM mode.
To compile this driver as a module, choose M here: the
module will be called fsl_ucc_hdlc.
config SLIC_DS26522
tristate "Slic Maxim ds26522 card support"
depends on SPI
depends on FSL_SOC || ARCH_MXC || ARCH_LAYERSCAPE || COMPILE_TEST
select BITREVERSE
help
This module initializes and configures the slic maxim card
in T1 or E1 mode.
To compile this driver as a module, choose M here: the
module will be called slic_ds26522.
config IXP4XX_HSS
tristate "Intel IXP4xx HSS (synchronous serial port) support"
depends on HDLC && IXP4XX_NPE && IXP4XX_QMGR
depends on ARCH_IXP4XX && OF
select MFD_SYSCON
help
Say Y here if you want to use built-in HSS ports
on IXP4xx processor.
# X.25 network drivers
config LAPBETHER
tristate "LAPB over Ethernet driver"
depends on LAPB && X25
help
Driver for a pseudo device (typically called /dev/lapb0) which allows
you to open an LAPB point-to-point connection to some other computer
on your Ethernet network.
In order to do this, you need to say Y or M to the driver for your
Ethernet card as well as to "LAPB Data Link Driver".
To compile this driver as a module, choose M here: the
module will be called lapbether.
If unsure, say N.
endif # WAN
+65
View File
@@ -0,0 +1,65 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for the Linux network (wan) device drivers.
#
# 3 Aug 2000, Christoph Hellwig <hch@infradead.org>
# Rewritten to use lists instead of if-statements.
#
obj-$(CONFIG_HDLC) += hdlc.o
obj-$(CONFIG_HDLC_RAW) += hdlc_raw.o
obj-$(CONFIG_HDLC_RAW_ETH) += hdlc_raw_eth.o
obj-$(CONFIG_HDLC_CISCO) += hdlc_cisco.o
obj-$(CONFIG_HDLC_FR) += hdlc_fr.o
obj-$(CONFIG_HDLC_PPP) += hdlc_ppp.o
obj-$(CONFIG_HDLC_X25) += hdlc_x25.o
obj-y += framer/
obj-$(CONFIG_FARSYNC) += farsync.o
obj-$(CONFIG_LAPBETHER) += lapbether.o
obj-$(CONFIG_N2) += n2.o
obj-$(CONFIG_C101) += c101.o
obj-$(CONFIG_WANXL) += wanxl.o
obj-$(CONFIG_PCI200SYN) += pci200syn.o
obj-$(CONFIG_PC300TOO) += pc300too.o
obj-$(CONFIG_IXP4XX_HSS) += ixp4xx_hss.o
obj-$(CONFIG_FSL_QMC_HDLC) += fsl_qmc_hdlc.o
obj-$(CONFIG_FSL_UCC_HDLC) += fsl_ucc_hdlc.o
obj-$(CONFIG_SLIC_DS26522) += slic_ds26522.o
clean-files := wanxlfw.inc
$(obj)/wanxl.o: $(obj)/wanxlfw.inc
CROSS_COMPILE_M68K = m68k-linux-gnu-
ifeq ($(CONFIG_WANXL_BUILD_FIRMWARE),y)
ifeq ($(ARCH),m68k)
M68KCC = $(CC)
M68KLD = $(LD)
else
M68KCC = $(CROSS_COMPILE_M68K)gcc
M68KLD = $(CROSS_COMPILE_M68K)ld
endif
quiet_cmd_build_wanxlfw = BLDFW $@
cmd_build_wanxlfw = hexdump -ve '"\n" 16/1 "0x%02X,"' $< | \
sed 's/0x ,//g;1s/^/static const u8 firmware[]={/;$$s/,$$/\n};\n/' > $@
$(obj)/wanxlfw.inc: $(obj)/wanxlfw.bin FORCE
$(call if_changed,build_wanxlfw)
quiet_cmd_m68kld_bin_o = M68KLD $@
cmd_m68kld_bin_o = $(M68KLD) --oformat binary -Ttext 0x1000 $< -o $@
$(obj)/wanxlfw.bin: $(obj)/wanxlfw.o FORCE
$(call if_changed,m68kld_bin_o)
quiet_cmd_m68kas_o_S = M68KAS $@
cmd_m68kas_o_S = $(M68KCC) -D__ASSEMBLY__ -Wp,-MD,$(depfile) -I$(srctree)/include/uapi -c -o $@ $<
$(obj)/wanxlfw.o: $(src)/wanxlfw.S FORCE
$(call if_changed_dep,m68kas_o_S)
endif
targets += wanxlfw.inc wanxlfw.bin wanxlfw.o
+440
View File
@@ -0,0 +1,440 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Moxa C101 synchronous serial card driver for Linux
*
* Copyright (C) 2000-2003 Krzysztof Halasa <khc@pm.waw.pl>
*
* For information see <https://www.kernel.org/pub/linux/utils/net/hdlc/>
*
* Sources of information:
* Hitachi HD64570 SCA User's Manual
* Moxa C101 User's Manual
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/capability.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/netdevice.h>
#include <linux/hdlc.h>
#include <linux/delay.h>
#include <asm/io.h>
#include "hd64570.h"
static const char *version = "Moxa C101 driver version: 1.15";
static const char *devname = "C101";
#undef DEBUG_PKT
#define DEBUG_RINGS
#define C101_PAGE 0x1D00
#define C101_DTR 0x1E00
#define C101_SCA 0x1F00
#define C101_WINDOW_SIZE 0x2000
#define C101_MAPPED_RAM_SIZE 0x4000
#define RAM_SIZE (256 * 1024)
#define TX_RING_BUFFERS 10
#define RX_RING_BUFFERS ((RAM_SIZE - C101_WINDOW_SIZE) / \
(sizeof(pkt_desc) + HDLC_MAX_MRU) - TX_RING_BUFFERS)
#define CLOCK_BASE 9830400 /* 9.8304 MHz */
#define PAGE0_ALWAYS_MAPPED
static char *hw; /* pointer to hw=xxx command line string */
typedef struct card_s {
struct net_device *dev;
spinlock_t lock; /* TX lock */
u8 __iomem *win0base; /* ISA window base address */
u32 phy_winbase; /* ISA physical base address */
sync_serial_settings settings;
int rxpart; /* partial frame received, next frame invalid*/
unsigned short encoding;
unsigned short parity;
u16 rx_ring_buffers; /* number of buffers in a ring */
u16 tx_ring_buffers;
u16 buff_offset; /* offset of first buffer of first channel */
u16 rxin; /* rx ring buffer 'in' pointer */
u16 txin; /* tx ring buffer 'in' and 'last' pointers */
u16 txlast;
u8 rxs, txs, tmc; /* SCA registers */
u8 irq; /* IRQ (3-15) */
u8 page;
struct card_s *next_card;
} card_t;
typedef card_t port_t;
static card_t *first_card;
static card_t **new_card = &first_card;
#define sca_in(reg, card) readb((card)->win0base + C101_SCA + (reg))
#define sca_out(value, reg, card) writeb(value, (card)->win0base + C101_SCA + (reg))
#define sca_inw(reg, card) readw((card)->win0base + C101_SCA + (reg))
/* EDA address register must be set in EDAL, EDAH order - 8 bit ISA bus */
#define sca_outw(value, reg, card) do { \
writeb(value & 0xFF, (card)->win0base + C101_SCA + (reg)); \
writeb((value >> 8) & 0xFF, (card)->win0base + C101_SCA + (reg + 1));\
} while (0)
#define port_to_card(port) (port)
#define log_node(port) (0)
#define phy_node(port) (0)
#define winsize(card) (C101_WINDOW_SIZE)
#define win0base(card) ((card)->win0base)
#define winbase(card) ((card)->win0base + 0x2000)
#define get_port(card, port) (card)
static void sca_msci_intr(port_t *port);
static inline u8 sca_get_page(card_t *card)
{
return card->page;
}
static inline void openwin(card_t *card, u8 page)
{
card->page = page;
writeb(page, card->win0base + C101_PAGE);
}
#include "hd64570.c"
static inline void set_carrier(port_t *port)
{
if (!(sca_in(MSCI1_OFFSET + ST3, port) & ST3_DCD))
netif_carrier_on(port_to_dev(port));
else
netif_carrier_off(port_to_dev(port));
}
static void sca_msci_intr(port_t *port)
{
u8 stat = sca_in(MSCI0_OFFSET + ST1, port); /* read MSCI ST1 status */
/* Reset MSCI TX underrun and CDCD (ignored) status bit */
sca_out(stat & (ST1_UDRN | ST1_CDCD), MSCI0_OFFSET + ST1, port);
if (stat & ST1_UDRN) {
/* TX Underrun error detected */
port_to_dev(port)->stats.tx_errors++;
port_to_dev(port)->stats.tx_fifo_errors++;
}
stat = sca_in(MSCI1_OFFSET + ST1, port); /* read MSCI1 ST1 status */
/* Reset MSCI CDCD status bit - uses ch#2 DCD input */
sca_out(stat & ST1_CDCD, MSCI1_OFFSET + ST1, port);
if (stat & ST1_CDCD)
set_carrier(port);
}
static void c101_set_iface(port_t *port)
{
u8 rxs = port->rxs & CLK_BRG_MASK;
u8 txs = port->txs & CLK_BRG_MASK;
switch (port->settings.clock_type) {
case CLOCK_INT:
rxs |= CLK_BRG_RX; /* TX clock */
txs |= CLK_RXCLK_TX; /* BRG output */
break;
case CLOCK_TXINT:
rxs |= CLK_LINE_RX; /* RXC input */
txs |= CLK_BRG_TX; /* BRG output */
break;
case CLOCK_TXFROMRX:
rxs |= CLK_LINE_RX; /* RXC input */
txs |= CLK_RXCLK_TX; /* RX clock */
break;
default: /* EXTernal clock */
rxs |= CLK_LINE_RX; /* RXC input */
txs |= CLK_LINE_TX; /* TXC input */
}
port->rxs = rxs;
port->txs = txs;
sca_out(rxs, MSCI1_OFFSET + RXS, port);
sca_out(txs, MSCI1_OFFSET + TXS, port);
sca_set_port(port);
}
static int c101_open(struct net_device *dev)
{
port_t *port = dev_to_port(dev);
int result;
result = hdlc_open(dev);
if (result)
return result;
writeb(1, port->win0base + C101_DTR);
sca_out(0, MSCI1_OFFSET + CTL, port); /* RTS uses ch#2 output */
sca_open(dev);
/* DCD is connected to port 2 !@#$%^& - disable MSCI0 CDCD interrupt */
sca_out(IE1_UDRN, MSCI0_OFFSET + IE1, port);
sca_out(IE0_TXINT, MSCI0_OFFSET + IE0, port);
set_carrier(port);
/* enable MSCI1 CDCD interrupt */
sca_out(IE1_CDCD, MSCI1_OFFSET + IE1, port);
sca_out(IE0_RXINTA, MSCI1_OFFSET + IE0, port);
sca_out(0x48, IER0, port); /* TXINT #0 and RXINT #1 */
c101_set_iface(port);
return 0;
}
static int c101_close(struct net_device *dev)
{
port_t *port = dev_to_port(dev);
sca_close(dev);
writeb(0, port->win0base + C101_DTR);
sca_out(CTL_NORTS, MSCI1_OFFSET + CTL, port);
hdlc_close(dev);
return 0;
}
static int c101_siocdevprivate(struct net_device *dev, struct ifreq *ifr,
void __user *data, int cmd)
{
#ifdef DEBUG_RINGS
port_t *port = dev_to_port(dev);
if (cmd == SIOCDEVPRIVATE) {
sca_dump_rings(dev);
printk(KERN_DEBUG "MSCI1: ST: %02x %02x %02x %02x\n",
sca_in(MSCI1_OFFSET + ST0, port),
sca_in(MSCI1_OFFSET + ST1, port),
sca_in(MSCI1_OFFSET + ST2, port),
sca_in(MSCI1_OFFSET + ST3, port));
return 0;
}
#endif
return -EOPNOTSUPP;
}
static int c101_ioctl(struct net_device *dev, struct if_settings *ifs)
{
const size_t size = sizeof(sync_serial_settings);
sync_serial_settings new_line;
sync_serial_settings __user *line = ifs->ifs_ifsu.sync;
port_t *port = dev_to_port(dev);
switch (ifs->type) {
case IF_GET_IFACE:
ifs->type = IF_IFACE_SYNC_SERIAL;
if (ifs->size < size) {
ifs->size = size; /* data size wanted */
return -ENOBUFS;
}
if (copy_to_user(line, &port->settings, size))
return -EFAULT;
return 0;
case IF_IFACE_SYNC_SERIAL:
if (!capable(CAP_NET_ADMIN))
return -EPERM;
if (copy_from_user(&new_line, line, size))
return -EFAULT;
if (new_line.clock_type != CLOCK_EXT &&
new_line.clock_type != CLOCK_TXFROMRX &&
new_line.clock_type != CLOCK_INT &&
new_line.clock_type != CLOCK_TXINT)
return -EINVAL; /* No such clock setting */
if (new_line.loopback != 0 && new_line.loopback != 1)
return -EINVAL;
memcpy(&port->settings, &new_line, size); /* Update settings */
c101_set_iface(port);
return 0;
default:
return hdlc_ioctl(dev, ifs);
}
}
static void c101_destroy_card(card_t *card)
{
readb(card->win0base + C101_PAGE); /* Resets SCA? */
if (card->irq)
free_irq(card->irq, card);
if (card->win0base) {
iounmap(card->win0base);
release_mem_region(card->phy_winbase, C101_MAPPED_RAM_SIZE);
}
free_netdev(card->dev);
kfree(card);
}
static const struct net_device_ops c101_ops = {
.ndo_open = c101_open,
.ndo_stop = c101_close,
.ndo_start_xmit = hdlc_start_xmit,
.ndo_siocwandev = c101_ioctl,
.ndo_siocdevprivate = c101_siocdevprivate,
};
static int __init c101_run(unsigned long irq, unsigned long winbase)
{
struct net_device *dev;
hdlc_device *hdlc;
card_t *card;
int result;
if (irq < 3 || irq > 15 || irq == 6) /* FIXME */ {
pr_err("invalid IRQ value\n");
return -ENODEV;
}
if (winbase < 0xC0000 || winbase > 0xDFFFF || (winbase & 0x3FFF) != 0) {
pr_err("invalid RAM value\n");
return -ENODEV;
}
card = kzalloc_obj(card_t);
if (!card)
return -ENOBUFS;
card->dev = alloc_hdlcdev(card);
if (!card->dev) {
pr_err("unable to allocate memory\n");
kfree(card);
return -ENOBUFS;
}
if (request_irq(irq, sca_intr, 0, devname, card)) {
pr_err("could not allocate IRQ\n");
c101_destroy_card(card);
return -EBUSY;
}
card->irq = irq;
if (!request_mem_region(winbase, C101_MAPPED_RAM_SIZE, devname)) {
pr_err("could not request RAM window\n");
c101_destroy_card(card);
return -EBUSY;
}
card->phy_winbase = winbase;
card->win0base = ioremap(winbase, C101_MAPPED_RAM_SIZE);
if (!card->win0base) {
pr_err("could not map I/O address\n");
c101_destroy_card(card);
return -EFAULT;
}
card->tx_ring_buffers = TX_RING_BUFFERS;
card->rx_ring_buffers = RX_RING_BUFFERS;
card->buff_offset = C101_WINDOW_SIZE; /* Bytes 1D00-1FFF reserved */
readb(card->win0base + C101_PAGE); /* Resets SCA? */
udelay(100);
writeb(0, card->win0base + C101_PAGE);
writeb(0, card->win0base + C101_DTR); /* Power-up for RAM? */
sca_init(card, 0);
dev = port_to_dev(card);
hdlc = dev_to_hdlc(dev);
spin_lock_init(&card->lock);
dev->irq = irq;
dev->mem_start = winbase;
dev->mem_end = winbase + C101_MAPPED_RAM_SIZE - 1;
dev->tx_queue_len = 50;
dev->netdev_ops = &c101_ops;
hdlc->attach = sca_attach;
hdlc->xmit = sca_xmit;
card->settings.clock_type = CLOCK_EXT;
result = register_hdlc_device(dev);
if (result) {
pr_warn("unable to register hdlc device\n");
c101_destroy_card(card);
return result;
}
sca_init_port(card); /* Set up C101 memory */
set_carrier(card);
netdev_info(dev, "Moxa C101 on IRQ%u, using %u TX + %u RX packets rings\n",
card->irq, card->tx_ring_buffers, card->rx_ring_buffers);
*new_card = card;
new_card = &card->next_card;
return 0;
}
static int __init c101_init(void)
{
if (!hw) {
#ifdef MODULE
pr_info("no card initialized\n");
#endif
return -EINVAL; /* no parameters specified, abort */
}
pr_info("%s\n", version);
do {
unsigned long irq, ram;
irq = simple_strtoul(hw, &hw, 0);
if (*hw++ != ',')
break;
ram = simple_strtoul(hw, &hw, 0);
if (*hw == ':' || *hw == '\x0')
c101_run(irq, ram);
if (*hw == '\x0')
return first_card ? 0 : -EINVAL;
} while (*hw++ == ':');
pr_err("invalid hardware parameters\n");
return first_card ? 0 : -EINVAL;
}
static void __exit c101_cleanup(void)
{
card_t *card = first_card;
while (card) {
card_t *ptr = card;
card = card->next_card;
unregister_hdlc_device(port_to_dev(ptr));
c101_destroy_card(ptr);
}
}
module_init(c101_init);
module_exit(c101_cleanup);
MODULE_AUTHOR("Krzysztof Halasa <khc@pm.waw.pl>");
MODULE_DESCRIPTION("Moxa C101 serial port driver");
MODULE_LICENSE("GPL v2");
module_param(hw, charp, 0444);
MODULE_PARM_DESC(hw, "irq,ram:irq,...");
File diff suppressed because it is too large Load Diff
+347
View File
@@ -0,0 +1,347 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* FarSync X21 driver for Linux
*
* Actually sync driver for X.21, V.35 and V.24 on FarSync T-series cards
*
* Copyright (C) 2001 FarSite Communications Ltd.
* www.farsite.co.uk
*
* Author: R.J.Dunlop <bob.dunlop@farsite.co.uk>
*
* For the most part this file only contains structures and information
* that is visible to applications outside the driver. Shared memory
* layout etc is internal to the driver and described within farsync.c.
* Overlap exists in that the values used for some fields within the
* ioctl interface extend into the cards firmware interface so values in
* this file may not be changed arbitrarily.
*/
/* What's in a name
*
* The project name for this driver is Oscar. The driver is intended to be
* used with the FarSite T-Series cards (T2P & T4P) running in the high
* speed frame shifter mode. This is sometimes referred to as X.21 mode
* which is a complete misnomer as the card continues to support V.24 and
* V.35 as well as X.21.
*
* A short common prefix is useful for routines within the driver to avoid
* conflict with other similar drivers and I chosen to use "fst_" for this
* purpose (FarSite T-series).
*
* Finally the device driver needs a short network interface name. Since
* "hdlc" is already in use I've chosen the even less informative "sync"
* for the present.
*/
#define FST_NAME "fst" /* In debug/info etc */
#define FST_NDEV_NAME "sync" /* For net interface */
#define FST_DEV_NAME "farsync" /* For misc interfaces */
/* User version number
*
* This version number is incremented with each official release of the
* package and is a simplified number for normal user reference.
* Individual files are tracked by the version control system and may
* have individual versions (or IDs) that move much faster than
* the release version as individual updates are tracked.
*/
#define FST_USER_VERSION "1.04"
/* Ioctl call command values
*/
#define FSTWRITE (SIOCDEVPRIVATE+10)
#define FSTCPURESET (SIOCDEVPRIVATE+11)
#define FSTCPURELEASE (SIOCDEVPRIVATE+12)
#define FSTGETCONF (SIOCDEVPRIVATE+13)
#define FSTSETCONF (SIOCDEVPRIVATE+14)
/* FSTWRITE
*
* Used to write a block of data (firmware etc) before the card is running
*/
struct fstioc_write {
unsigned int size;
unsigned int offset;
unsigned char data[];
};
/* FSTCPURESET and FSTCPURELEASE
*
* These take no additional data.
* FSTCPURESET forces the cards CPU into a reset state and holds it there.
* FSTCPURELEASE releases the CPU from this reset state allowing it to run,
* the reset vector should be setup before this ioctl is run.
*/
/* FSTGETCONF and FSTSETCONF
*
* Get and set a card/ports configuration.
* In order to allow selective setting of items and for the kernel to
* indicate a partial status response the first field "valid" is a bitmask
* indicating which other fields in the structure are valid.
* Many of the field names in this structure match those used in the
* firmware shared memory configuration interface and come originally from
* the NT header file Smc.h
*
* When used with FSTGETCONF this structure should be zeroed before use.
* This is to allow for possible future expansion when some of the fields
* might be used to indicate a different (expanded) structure.
*/
struct fstioc_info {
unsigned int valid; /* Bits of structure that are valid */
unsigned int nports; /* Number of serial ports */
unsigned int type; /* Type index of card */
unsigned int state; /* State of card */
unsigned int index; /* Index of port ioctl was issued on */
unsigned int smcFirmwareVersion;
unsigned long kernelVersion; /* What Kernel version we are working with */
unsigned short lineInterface; /* Physical interface type */
unsigned char proto; /* Line protocol */
unsigned char internalClock; /* 1 => internal clock, 0 => external */
unsigned int lineSpeed; /* Speed in bps */
unsigned int v24IpSts; /* V.24 control input status */
unsigned int v24OpSts; /* V.24 control output status */
unsigned short clockStatus; /* lsb: 0=> present, 1=> absent */
unsigned short cableStatus; /* lsb: 0=> present, 1=> absent */
unsigned short cardMode; /* lsb: LED id mode */
unsigned short debug; /* Debug flags */
unsigned char transparentMode; /* Not used always 0 */
unsigned char invertClock; /* Invert clock feature for syncing */
unsigned char startingSlot; /* Time slot to use for start of tx */
unsigned char clockSource; /* External or internal */
unsigned char framing; /* E1, T1 or J1 */
unsigned char structure; /* unframed, double, crc4, f4, f12, */
/* f24 f72 */
unsigned char interface; /* rj48c or bnc */
unsigned char coding; /* hdb3 b8zs */
unsigned char lineBuildOut; /* 0, -7.5, -15, -22 */
unsigned char equalizer; /* short or lon haul settings */
unsigned char loopMode; /* various loopbacks */
unsigned char range; /* cable lengths */
unsigned char txBufferMode; /* tx elastic buffer depth */
unsigned char rxBufferMode; /* rx elastic buffer depth */
unsigned char losThreshold; /* Attenuation on LOS signal */
unsigned char idleCode; /* Value to send as idle timeslot */
unsigned int receiveBufferDelay; /* delay thro rx buffer timeslots */
unsigned int framingErrorCount; /* framing errors */
unsigned int codeViolationCount; /* code violations */
unsigned int crcErrorCount; /* CRC errors */
int lineAttenuation; /* in dB*/
unsigned short lossOfSignal;
unsigned short receiveRemoteAlarm;
unsigned short alarmIndicationSignal;
};
/* "valid" bitmask */
#define FSTVAL_NONE 0x00000000 /* Nothing valid (firmware not running).
* Slight misnomer. In fact nports,
* type, state and index will be set
* based on hardware detected.
*/
#define FSTVAL_OMODEM 0x0000001F /* First 5 bits correspond to the
* output status bits defined for
* v24OpSts
*/
#define FSTVAL_SPEED 0x00000020 /* internalClock, lineSpeed, clockStatus
*/
#define FSTVAL_CABLE 0x00000040 /* lineInterface, cableStatus */
#define FSTVAL_IMODEM 0x00000080 /* v24IpSts */
#define FSTVAL_CARD 0x00000100 /* nports, type, state, index,
* smcFirmwareVersion
*/
#define FSTVAL_PROTO 0x00000200 /* proto */
#define FSTVAL_MODE 0x00000400 /* cardMode */
#define FSTVAL_PHASE 0x00000800 /* Clock phase */
#define FSTVAL_TE1 0x00001000 /* T1E1 Configuration */
#define FSTVAL_DEBUG 0x80000000 /* debug */
#define FSTVAL_ALL 0x00001FFF /* Note: does not include DEBUG flag */
/* "type" */
#define FST_TYPE_NONE 0 /* Probably should never happen */
#define FST_TYPE_T2P 1 /* T2P X21 2 port card */
#define FST_TYPE_T4P 2 /* T4P X21 4 port card */
#define FST_TYPE_T1U 3 /* T1U X21 1 port card */
#define FST_TYPE_T2U 4 /* T2U X21 2 port card */
#define FST_TYPE_T4U 5 /* T4U X21 4 port card */
#define FST_TYPE_TE1 6 /* T1E1 X21 1 port card */
/* "family" */
#define FST_FAMILY_TXP 0 /* T2P or T4P */
#define FST_FAMILY_TXU 1 /* T1U or T2U or T4U */
/* "state" */
#define FST_UNINIT 0 /* Raw uninitialised state following
* system startup */
#define FST_RESET 1 /* Processor held in reset state */
#define FST_DOWNLOAD 2 /* Card being downloaded */
#define FST_STARTING 3 /* Released following download */
#define FST_RUNNING 4 /* Processor running */
#define FST_BADVERSION 5 /* Bad shared memory version detected */
#define FST_HALTED 6 /* Processor flagged a halt */
#define FST_IFAILED 7 /* Firmware issued initialisation failed
* interrupt
*/
/* "lineInterface" */
#define V24 1
#define X21 2
#define V35 3
#define X21D 4
#define T1 5
#define E1 6
#define J1 7
/* "proto" */
#define FST_RAW 4 /* Two way raw packets */
#define FST_GEN_HDLC 5 /* Using "Generic HDLC" module */
/* "internalClock" */
#define INTCLK 1
#define EXTCLK 0
/* "v24IpSts" bitmask */
#define IPSTS_CTS 0x00000001 /* Clear To Send (Indicate for X.21) */
#define IPSTS_INDICATE IPSTS_CTS
#define IPSTS_DSR 0x00000002 /* Data Set Ready (T2P Port A) */
#define IPSTS_DCD 0x00000004 /* Data Carrier Detect */
#define IPSTS_RI 0x00000008 /* Ring Indicator (T2P Port A) */
#define IPSTS_TMI 0x00000010 /* Test Mode Indicator (Not Supported)*/
/* "v24OpSts" bitmask */
#define OPSTS_RTS 0x00000001 /* Request To Send (Control for X.21) */
#define OPSTS_CONTROL OPSTS_RTS
#define OPSTS_DTR 0x00000002 /* Data Terminal Ready */
#define OPSTS_DSRS 0x00000004 /* Data Signalling Rate Select (Not
* Supported) */
#define OPSTS_SS 0x00000008 /* Select Standby (Not Supported) */
#define OPSTS_LL 0x00000010 /* Maintenance Test (Not Supported) */
/* "cardMode" bitmask */
#define CARD_MODE_IDENTIFY 0x0001
/*
* Constants for T1/E1 configuration
*/
/*
* Clock source
*/
#define CLOCKING_SLAVE 0
#define CLOCKING_MASTER 1
/*
* Framing
*/
#define FRAMING_E1 0
#define FRAMING_J1 1
#define FRAMING_T1 2
/*
* Structure
*/
#define STRUCTURE_UNFRAMED 0
#define STRUCTURE_E1_DOUBLE 1
#define STRUCTURE_E1_CRC4 2
#define STRUCTURE_E1_CRC4M 3
#define STRUCTURE_T1_4 4
#define STRUCTURE_T1_12 5
#define STRUCTURE_T1_24 6
#define STRUCTURE_T1_72 7
/*
* Interface
*/
#define INTERFACE_RJ48C 0
#define INTERFACE_BNC 1
/*
* Coding
*/
#define CODING_HDB3 0
#define CODING_NRZ 1
#define CODING_CMI 2
#define CODING_CMI_HDB3 3
#define CODING_CMI_B8ZS 4
#define CODING_AMI 5
#define CODING_AMI_ZCS 6
#define CODING_B8ZS 7
/*
* Line Build Out
*/
#define LBO_0dB 0
#define LBO_7dB5 1
#define LBO_15dB 2
#define LBO_22dB5 3
/*
* Range for long haul t1 > 655ft
*/
#define RANGE_0_133_FT 0
#define RANGE_0_40_M RANGE_0_133_FT
#define RANGE_133_266_FT 1
#define RANGE_40_81_M RANGE_133_266_FT
#define RANGE_266_399_FT 2
#define RANGE_81_122_M RANGE_266_399_FT
#define RANGE_399_533_FT 3
#define RANGE_122_162_M RANGE_399_533_FT
#define RANGE_533_655_FT 4
#define RANGE_162_200_M RANGE_533_655_FT
/*
* Receive Equaliser
*/
#define EQUALIZER_SHORT 0
#define EQUALIZER_LONG 1
/*
* Loop modes
*/
#define LOOP_NONE 0
#define LOOP_LOCAL 1
#define LOOP_PAYLOAD_EXC_TS0 2
#define LOOP_PAYLOAD_INC_TS0 3
#define LOOP_REMOTE 4
/*
* Buffer modes
*/
#define BUFFER_2_FRAME 0
#define BUFFER_1_FRAME 1
#define BUFFER_96_BIT 2
#define BUFFER_NONE 3
/* Debug support
*
* These should only be enabled for development kernels, production code
* should define FST_DEBUG=0 in order to exclude the code.
* Setting FST_DEBUG=1 will include all the debug code but in a disabled
* state, use the FSTSETCONF ioctl to enable specific debug actions, or
* FST_DEBUG can be set to prime the debug selection.
*/
#define FST_DEBUG 0x0000
#if FST_DEBUG
extern int fst_debug_mask; /* Bit mask of actions to debug, bits
* listed below. Note: Bit 0 is used
* to trigger the inclusion of this
* code, without enabling any actions.
*/
#define DBG_INIT 0x0002 /* Card detection and initialisation */
#define DBG_OPEN 0x0004 /* Open and close sequences */
#define DBG_PCI 0x0008 /* PCI config operations */
#define DBG_IOCTL 0x0010 /* Ioctls and other config */
#define DBG_INTR 0x0020 /* Interrupt routines (be careful) */
#define DBG_TX 0x0040 /* Packet transmission */
#define DBG_RX 0x0080 /* Packet reception */
#define DBG_CMD 0x0100 /* Port command issuing */
#define DBG_ASS 0xFFFF /* Assert like statements. Code that
* should never be reached, if you see
* one of these then I've been an ass
*/
#endif /* FST_DEBUG */
@@ -0,0 +1,42 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# FRAMER
#
menuconfig FRAMER
tristate "Framer Subsystem"
help
A framer is a component in charge of an E1/T1 line interface.
Connected usually to a TDM bus, it converts TDM frames to/from E1/T1
frames. It also provides information related to the E1/T1 line.
Used with HDLC, the network can be reached through the E1/T1 line.
This framework is designed to provide a generic interface for framer
devices present in the kernel. This layer will have the generic
API by which framer drivers can create framer using the framer
framework and framer users can obtain reference to the framer.
All the users of this framework should select this config.
if FRAMER
config GENERIC_FRAMER
bool
config FRAMER_PEF2256
tristate "Lantiq PEF2256"
depends on OF
depends on HAS_IOMEM
select GENERIC_FRAMER
select MFD_CORE
select REGMAP_MMIO
help
Enable support for the Lantiq PEF2256 (FALC56) framer.
The PEF2256 is a framer and line interface between analog E1/T1/J1
line and a digital PCM bus.
If unsure, say N.
To compile this driver as a module, choose M here: the
module will be called framer-pef2256.
endif # FRAMER
@@ -0,0 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for the framer drivers.
#
obj-$(CONFIG_GENERIC_FRAMER) += framer-core.o
obj-$(CONFIG_FRAMER_PEF2256) += pef2256/
@@ -0,0 +1,873 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Generic Framer framework.
*
* Copyright 2023 CS GROUP France
*
* Author: Herve Codina <herve.codina@bootlin.com>
*/
#include <linux/device.h>
#include <linux/framer/framer.h>
#include <linux/framer/framer-provider.h>
#include <linux/idr.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/of.h>
#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
#include <linux/slab.h>
static void framer_release(struct device *dev);
static const struct class framer_class = {
.name = "framer",
.dev_release = framer_release,
};
static DEFINE_MUTEX(framer_provider_mutex);
static LIST_HEAD(framer_provider_list);
static DEFINE_IDA(framer_ida);
#define dev_to_framer(a) (container_of((a), struct framer, dev))
int framer_pm_runtime_get(struct framer *framer)
{
int ret;
if (!pm_runtime_enabled(&framer->dev))
return -EOPNOTSUPP;
ret = pm_runtime_get(&framer->dev);
if (ret < 0 && ret != -EINPROGRESS)
pm_runtime_put_noidle(&framer->dev);
return ret;
}
EXPORT_SYMBOL_GPL(framer_pm_runtime_get);
int framer_pm_runtime_get_sync(struct framer *framer)
{
int ret;
if (!pm_runtime_enabled(&framer->dev))
return -EOPNOTSUPP;
ret = pm_runtime_get_sync(&framer->dev);
if (ret < 0)
pm_runtime_put_sync(&framer->dev);
return ret;
}
EXPORT_SYMBOL_GPL(framer_pm_runtime_get_sync);
void framer_pm_runtime_put(struct framer *framer)
{
if (!pm_runtime_enabled(&framer->dev))
return;
pm_runtime_put(&framer->dev);
}
EXPORT_SYMBOL_GPL(framer_pm_runtime_put);
int framer_pm_runtime_put_sync(struct framer *framer)
{
if (!pm_runtime_enabled(&framer->dev))
return -EOPNOTSUPP;
return pm_runtime_put_sync(&framer->dev);
}
EXPORT_SYMBOL_GPL(framer_pm_runtime_put_sync);
/**
* framer_init - framer internal initialization before framer operation
* @framer: the framer returned by framer_get()
*
* Used to allow framer's driver to perform framer internal initialization,
* such as PLL block powering, clock initialization or anything that's
* is required by the framer to perform the start of operation.
* Must be called before framer_power_on().
*
* Return: %0 if successful, a negative error code otherwise
*/
int framer_init(struct framer *framer)
{
bool start_polling = false;
int ret;
ret = framer_pm_runtime_get_sync(framer);
if (ret < 0 && ret != -EOPNOTSUPP)
return ret;
ret = 0; /* Override possible ret == -EOPNOTSUPP */
mutex_lock(&framer->mutex);
if (framer->power_count > framer->init_count)
dev_warn(&framer->dev, "framer_power_on was called before framer init\n");
if (framer->init_count == 0) {
if (framer->ops->init) {
ret = framer->ops->init(framer);
if (ret < 0) {
dev_err(&framer->dev, "framer init failed --> %d\n", ret);
goto out;
}
}
if (framer->ops->flags & FRAMER_FLAG_POLL_STATUS)
start_polling = true;
}
++framer->init_count;
out:
mutex_unlock(&framer->mutex);
if (!ret && start_polling) {
ret = framer_get_status(framer, &framer->prev_status);
if (ret < 0) {
dev_warn(&framer->dev, "framer get status failed --> %d\n", ret);
/* Will be retried on polling_work */
ret = 0;
}
queue_delayed_work(system_power_efficient_wq, &framer->polling_work, 1 * HZ);
}
framer_pm_runtime_put(framer);
return ret;
}
EXPORT_SYMBOL_GPL(framer_init);
/**
* framer_exit - Framer internal un-initialization
* @framer: the framer returned by framer_get()
*
* Must be called after framer_power_off().
*/
int framer_exit(struct framer *framer)
{
int ret;
ret = framer_pm_runtime_get_sync(framer);
if (ret < 0 && ret != -EOPNOTSUPP)
return ret;
ret = 0; /* Override possible ret == -EOPNOTSUPP */
mutex_lock(&framer->mutex);
--framer->init_count;
if (framer->init_count == 0) {
if (framer->ops->flags & FRAMER_FLAG_POLL_STATUS) {
mutex_unlock(&framer->mutex);
cancel_delayed_work_sync(&framer->polling_work);
mutex_lock(&framer->mutex);
}
if (framer->ops->exit)
framer->ops->exit(framer);
}
mutex_unlock(&framer->mutex);
framer_pm_runtime_put(framer);
return ret;
}
EXPORT_SYMBOL_GPL(framer_exit);
/**
* framer_power_on - Enable the framer and enter proper operation
* @framer: the framer returned by framer_get()
*
* Must be called after framer_init().
*
* Return: %0 if successful, a negative error code otherwise
*/
int framer_power_on(struct framer *framer)
{
int ret;
if (framer->pwr) {
ret = regulator_enable(framer->pwr);
if (ret)
return ret;
}
ret = framer_pm_runtime_get_sync(framer);
if (ret < 0 && ret != -EOPNOTSUPP)
goto err_pm_sync;
mutex_lock(&framer->mutex);
if (framer->power_count == 0 && framer->ops->power_on) {
ret = framer->ops->power_on(framer);
if (ret < 0) {
dev_err(&framer->dev, "framer poweron failed --> %d\n", ret);
goto err_pwr_on;
}
}
++framer->power_count;
mutex_unlock(&framer->mutex);
return 0;
err_pwr_on:
mutex_unlock(&framer->mutex);
framer_pm_runtime_put_sync(framer);
err_pm_sync:
if (framer->pwr)
regulator_disable(framer->pwr);
return ret;
}
EXPORT_SYMBOL_GPL(framer_power_on);
/**
* framer_power_off - Disable the framer.
* @framer: the framer returned by framer_get()
*
* Must be called before framer_exit().
*
* Return: %0 if successful, a negative error code otherwise
*/
int framer_power_off(struct framer *framer)
{
int ret;
mutex_lock(&framer->mutex);
if (framer->power_count == 1 && framer->ops->power_off) {
ret = framer->ops->power_off(framer);
if (ret < 0) {
dev_err(&framer->dev, "framer poweroff failed --> %d\n", ret);
mutex_unlock(&framer->mutex);
return ret;
}
}
--framer->power_count;
mutex_unlock(&framer->mutex);
framer_pm_runtime_put(framer);
if (framer->pwr)
regulator_disable(framer->pwr);
return 0;
}
EXPORT_SYMBOL_GPL(framer_power_off);
/**
* framer_get_status() - Gets the framer status
* @framer: the framer returned by framer_get()
* @status: the status to retrieve
*
* Used to get the framer status. framer_init() must have been called
* on the framer.
*
* Return: %0 if successful, a negative error code otherwise
*/
int framer_get_status(struct framer *framer, struct framer_status *status)
{
int ret;
if (!framer->ops->get_status)
return -EOPNOTSUPP;
/* Be sure to have known values (struct padding and future extensions) */
memset(status, 0, sizeof(*status));
mutex_lock(&framer->mutex);
ret = framer->ops->get_status(framer, status);
mutex_unlock(&framer->mutex);
return ret;
}
EXPORT_SYMBOL_GPL(framer_get_status);
/**
* framer_set_config() - Sets the framer configuration
* @framer: the framer returned by framer_get()
* @config: the configuration to set
*
* Used to set the framer configuration. framer_init() must have been called
* on the framer.
*
* Return: %0 if successful, a negative error code otherwise
*/
int framer_set_config(struct framer *framer, const struct framer_config *config)
{
int ret;
if (!framer->ops->set_config)
return -EOPNOTSUPP;
mutex_lock(&framer->mutex);
ret = framer->ops->set_config(framer, config);
mutex_unlock(&framer->mutex);
return ret;
}
EXPORT_SYMBOL_GPL(framer_set_config);
/**
* framer_get_config() - Gets the framer configuration
* @framer: the framer returned by framer_get()
* @config: the configuration to retrieve
*
* Used to get the framer configuration. framer_init() must have been called
* on the framer.
*
* Return: %0 if successful, a negative error code otherwise
*/
int framer_get_config(struct framer *framer, struct framer_config *config)
{
int ret;
if (!framer->ops->get_config)
return -EOPNOTSUPP;
mutex_lock(&framer->mutex);
ret = framer->ops->get_config(framer, config);
mutex_unlock(&framer->mutex);
return ret;
}
EXPORT_SYMBOL_GPL(framer_get_config);
static void framer_polling_work(struct work_struct *work)
{
struct framer *framer = container_of(work, struct framer, polling_work.work);
struct framer_status status;
int ret;
ret = framer_get_status(framer, &status);
if (ret) {
dev_err(&framer->dev, "polling, get status failed (%d)\n", ret);
goto end;
}
if (memcmp(&framer->prev_status, &status, sizeof(status))) {
blocking_notifier_call_chain(&framer->notifier_list,
FRAMER_EVENT_STATUS, NULL);
memcpy(&framer->prev_status, &status, sizeof(status));
}
end:
/* Re-schedule task in 1 sec */
queue_delayed_work(system_power_efficient_wq, &framer->polling_work, 1 * HZ);
}
/**
* framer_notifier_register() - Registers a notifier
* @framer: the framer returned by framer_get()
* @nb: the notifier block to register
*
* Used to register a notifier block on framer events. framer_init() must have
* been called on the framer.
* The available framer events are present in enum framer_events.
*
* Return: %0 if successful, a negative error code otherwise
*/
int framer_notifier_register(struct framer *framer, struct notifier_block *nb)
{
return blocking_notifier_chain_register(&framer->notifier_list, nb);
}
EXPORT_SYMBOL_GPL(framer_notifier_register);
/**
* framer_notifier_unregister() - Unregisters a notifier
* @framer: the framer returned by framer_get()
* @nb: the notifier block to unregister
*
* Used to unregister a notifier block. framer_init() must have
* been called on the framer.
*
* Return: %0 if successful, a negative error code otherwise
*/
int framer_notifier_unregister(struct framer *framer, struct notifier_block *nb)
{
return blocking_notifier_chain_unregister(&framer->notifier_list, nb);
}
EXPORT_SYMBOL_GPL(framer_notifier_unregister);
static struct framer_provider *framer_provider_of_lookup(const struct device_node *node)
{
struct framer_provider *framer_provider;
list_for_each_entry(framer_provider, &framer_provider_list, list) {
if (device_match_of_node(framer_provider->dev, node))
return framer_provider;
}
return ERR_PTR(-EPROBE_DEFER);
}
static struct framer *framer_of_get_from_provider(const struct of_phandle_args *args)
{
struct framer_provider *framer_provider;
struct framer *framer;
mutex_lock(&framer_provider_mutex);
framer_provider = framer_provider_of_lookup(args->np);
if (IS_ERR(framer_provider) || !try_module_get(framer_provider->owner)) {
framer = ERR_PTR(-EPROBE_DEFER);
goto end;
}
framer = framer_provider->of_xlate(framer_provider->dev, args);
module_put(framer_provider->owner);
end:
mutex_unlock(&framer_provider_mutex);
return framer;
}
static struct framer *framer_of_get_byphandle(struct device_node *np, const char *propname,
int index)
{
struct of_phandle_args args;
struct framer *framer;
int ret;
ret = of_parse_phandle_with_optional_args(np, propname, "#framer-cells", index, &args);
if (ret)
return ERR_PTR(-ENODEV);
if (!of_device_is_available(args.np)) {
framer = ERR_PTR(-ENODEV);
goto out_node_put;
}
framer = framer_of_get_from_provider(&args);
out_node_put:
of_node_put(args.np);
return framer;
}
static struct framer *framer_of_get_byparent(struct device_node *np, int index)
{
struct of_phandle_args args;
struct framer *framer;
args.np = of_get_parent(np);
args.args_count = 1;
args.args[0] = index;
while (args.np) {
framer = framer_of_get_from_provider(&args);
if (IS_ERR(framer) && PTR_ERR(framer) != -EPROBE_DEFER) {
args.np = of_get_next_parent(args.np);
continue;
}
of_node_put(args.np);
return framer;
}
return ERR_PTR(-ENODEV);
}
/**
* framer_get() - lookup and obtain a reference to a framer.
* @dev: device that requests the framer
* @con_id: name of the framer from device's point of view
*
* Returns the framer driver, after getting a refcount to it; or
* -ENODEV if there is no such framer. The caller is responsible for
* calling framer_put() to release that count.
*/
struct framer *framer_get(struct device *dev, const char *con_id)
{
struct framer *framer = ERR_PTR(-ENODEV);
struct device_link *link;
int ret;
if (dev->of_node) {
if (con_id)
framer = framer_of_get_byphandle(dev->of_node, con_id, 0);
else
framer = framer_of_get_byparent(dev->of_node, 0);
}
if (IS_ERR(framer))
return framer;
get_device(&framer->dev);
if (!try_module_get(framer->ops->owner)) {
ret = -EPROBE_DEFER;
goto err_put_device;
}
link = device_link_add(dev, &framer->dev, DL_FLAG_STATELESS);
if (!link) {
dev_err(dev, "failed to create device_link to %s\n", dev_name(&framer->dev));
ret = -EPROBE_DEFER;
goto err_module_put;
}
return framer;
err_module_put:
module_put(framer->ops->owner);
err_put_device:
put_device(&framer->dev);
return ERR_PTR(ret);
}
EXPORT_SYMBOL_GPL(framer_get);
/**
* framer_put() - release the framer
* @dev: device that wants to release this framer
* @framer: the framer returned by framer_get()
*
* Releases a refcount the caller received from framer_get().
*/
void framer_put(struct device *dev, struct framer *framer)
{
device_link_remove(dev, &framer->dev);
module_put(framer->ops->owner);
put_device(&framer->dev);
}
EXPORT_SYMBOL_GPL(framer_put);
static void devm_framer_put(struct device *dev, void *res)
{
struct framer *framer = *(struct framer **)res;
framer_put(dev, framer);
}
/**
* devm_framer_get() - lookup and obtain a reference to a framer.
* @dev: device that requests this framer
* @con_id: name of the framer from device's point of view
*
* Gets the framer using framer_get(), and associates a device with it using
* devres. On driver detach, framer_put() function is invoked on the devres
* data, then, devres data is freed.
*/
struct framer *devm_framer_get(struct device *dev, const char *con_id)
{
struct framer **ptr, *framer;
ptr = devres_alloc(devm_framer_put, sizeof(*ptr), GFP_KERNEL);
if (!ptr)
return ERR_PTR(-ENOMEM);
framer = framer_get(dev, con_id);
if (!IS_ERR(framer)) {
*ptr = framer;
devres_add(dev, ptr);
} else {
devres_free(ptr);
return framer;
}
return framer;
}
EXPORT_SYMBOL_GPL(devm_framer_get);
/**
* devm_framer_optional_get() - lookup and obtain a reference to an optional
* framer.
* @dev: device that requests this framer
* @con_id: name of the framer from device's point of view
*
* Same as devm_framer_get() except that if the framer does not exist, it is not
* considered an error and -ENODEV will not be returned. Instead the NULL framer
* is returned.
*/
struct framer *devm_framer_optional_get(struct device *dev, const char *con_id)
{
struct framer *framer = devm_framer_get(dev, con_id);
if (PTR_ERR(framer) == -ENODEV)
framer = NULL;
return framer;
}
EXPORT_SYMBOL_GPL(devm_framer_optional_get);
static void framer_notify_status_work(struct work_struct *work)
{
struct framer *framer = container_of(work, struct framer, notify_status_work);
blocking_notifier_call_chain(&framer->notifier_list, FRAMER_EVENT_STATUS, NULL);
}
void framer_notify_status_change(struct framer *framer)
{
/* Can be called from atomic context -> just schedule a task to call
* blocking notifiers
*/
queue_work(system_power_efficient_wq, &framer->notify_status_work);
}
EXPORT_SYMBOL_GPL(framer_notify_status_change);
/**
* framer_create() - create a new framer
* @dev: device that is creating the new framer
* @node: device node of the framer. default to dev->of_node.
* @ops: function pointers for performing framer operations
*
* Called to create a framer using framer framework.
*/
struct framer *framer_create(struct device *dev, struct device_node *node,
const struct framer_ops *ops)
{
struct framer *framer;
int ret;
int id;
/* get_status() is mandatory if the provider ask for polling status */
if (WARN_ON((ops->flags & FRAMER_FLAG_POLL_STATUS) && !ops->get_status))
return ERR_PTR(-EINVAL);
framer = kzalloc_obj(*framer);
if (!framer)
return ERR_PTR(-ENOMEM);
id = ida_alloc(&framer_ida, GFP_KERNEL);
if (id < 0) {
dev_err(dev, "unable to get id\n");
ret = id;
goto free_framer;
}
device_initialize(&framer->dev);
mutex_init(&framer->mutex);
INIT_WORK(&framer->notify_status_work, framer_notify_status_work);
INIT_DELAYED_WORK(&framer->polling_work, framer_polling_work);
BLOCKING_INIT_NOTIFIER_HEAD(&framer->notifier_list);
framer->dev.class = &framer_class;
framer->dev.parent = dev;
framer->dev.of_node = node ? node : dev->of_node;
framer->id = id;
framer->ops = ops;
ret = dev_set_name(&framer->dev, "framer-%s.%d", dev_name(dev), id);
if (ret)
goto put_dev;
/* framer-supply */
framer->pwr = regulator_get_optional(&framer->dev, "framer");
if (IS_ERR(framer->pwr)) {
ret = PTR_ERR(framer->pwr);
if (ret == -EPROBE_DEFER)
goto put_dev;
framer->pwr = NULL;
}
ret = device_add(&framer->dev);
if (ret)
goto put_dev;
if (pm_runtime_enabled(dev)) {
pm_runtime_enable(&framer->dev);
pm_runtime_no_callbacks(&framer->dev);
}
return framer;
put_dev:
put_device(&framer->dev); /* calls framer_release() which frees resources */
return ERR_PTR(ret);
free_framer:
kfree(framer);
return ERR_PTR(ret);
}
EXPORT_SYMBOL_GPL(framer_create);
/**
* framer_destroy() - destroy the framer
* @framer: the framer to be destroyed
*
* Called to destroy the framer.
*/
void framer_destroy(struct framer *framer)
{
/* polling_work should already be stopped but if framer_exit() was not
* called (bug), here it's the last time to do that ...
*/
cancel_delayed_work_sync(&framer->polling_work);
cancel_work_sync(&framer->notify_status_work);
pm_runtime_disable(&framer->dev);
device_unregister(&framer->dev); /* calls framer_release() which frees resources */
}
EXPORT_SYMBOL_GPL(framer_destroy);
static void devm_framer_destroy(struct device *dev, void *res)
{
struct framer *framer = *(struct framer **)res;
framer_destroy(framer);
}
/**
* devm_framer_create() - create a new framer
* @dev: device that is creating the new framer
* @node: device node of the framer
* @ops: function pointers for performing framer operations
*
* Creates a new framer device adding it to the framer class.
* While at that, it also associates the device with the framer using devres.
* On driver detach, release function is invoked on the devres data,
* then, devres data is freed.
*/
struct framer *devm_framer_create(struct device *dev, struct device_node *node,
const struct framer_ops *ops)
{
struct framer **ptr, *framer;
ptr = devres_alloc(devm_framer_destroy, sizeof(*ptr), GFP_KERNEL);
if (!ptr)
return ERR_PTR(-ENOMEM);
framer = framer_create(dev, node, ops);
if (!IS_ERR(framer)) {
*ptr = framer;
devres_add(dev, ptr);
} else {
devres_free(ptr);
}
return framer;
}
EXPORT_SYMBOL_GPL(devm_framer_create);
/**
* framer_provider_simple_of_xlate() - returns the framer instance from framer provider
* @dev: the framer provider device (not used here)
* @args: of_phandle_args
*
* Intended to be used by framer provider for the common case where #framer-cells is
* 0. For other cases where #framer-cells is greater than '0', the framer provider
* should provide a custom of_xlate function that reads the *args* and returns
* the appropriate framer.
*/
struct framer *framer_provider_simple_of_xlate(struct device *dev,
const struct of_phandle_args *args)
{
struct device *target_dev;
target_dev = class_find_device_by_of_node(&framer_class, args->np);
if (!target_dev)
return ERR_PTR(-ENODEV);
put_device(target_dev);
return dev_to_framer(target_dev);
}
EXPORT_SYMBOL_GPL(framer_provider_simple_of_xlate);
/**
* __framer_provider_of_register() - create/register framer provider with the framework
* @dev: struct device of the framer provider
* @owner: the module owner containing of_xlate
* @of_xlate: function pointer to obtain framer instance from framer provider
*
* Creates struct framer_provider from dev and of_xlate function pointer.
* This is used in the case of dt boot for finding the framer instance from
* framer provider.
*/
struct framer_provider *
__framer_provider_of_register(struct device *dev, struct module *owner,
struct framer *(*of_xlate)(struct device *dev,
const struct of_phandle_args *args))
{
struct framer_provider *framer_provider;
framer_provider = kzalloc_obj(*framer_provider);
if (!framer_provider)
return ERR_PTR(-ENOMEM);
framer_provider->dev = dev;
framer_provider->owner = owner;
framer_provider->of_xlate = of_xlate;
of_node_get(framer_provider->dev->of_node);
mutex_lock(&framer_provider_mutex);
list_add_tail(&framer_provider->list, &framer_provider_list);
mutex_unlock(&framer_provider_mutex);
return framer_provider;
}
EXPORT_SYMBOL_GPL(__framer_provider_of_register);
/**
* framer_provider_of_unregister() - unregister framer provider from the framework
* @framer_provider: framer provider returned by framer_provider_of_register()
*
* Removes the framer_provider created using framer_provider_of_register().
*/
void framer_provider_of_unregister(struct framer_provider *framer_provider)
{
mutex_lock(&framer_provider_mutex);
list_del(&framer_provider->list);
mutex_unlock(&framer_provider_mutex);
of_node_put(framer_provider->dev->of_node);
kfree(framer_provider);
}
EXPORT_SYMBOL_GPL(framer_provider_of_unregister);
static void devm_framer_provider_of_unregister(struct device *dev, void *res)
{
struct framer_provider *framer_provider = *(struct framer_provider **)res;
framer_provider_of_unregister(framer_provider);
}
/**
* __devm_framer_provider_of_register() - create/register framer provider with
* the framework
* @dev: struct device of the framer provider
* @owner: the module owner containing of_xlate
* @of_xlate: function pointer to obtain framer instance from framer provider
*
* Creates struct framer_provider from dev and of_xlate function pointer.
* This is used in the case of dt boot for finding the framer instance from
* framer provider. While at that, it also associates the device with the
* framer provider using devres. On driver detach, release function is invoked
* on the devres data, then, devres data is freed.
*/
struct framer_provider *
__devm_framer_provider_of_register(struct device *dev, struct module *owner,
struct framer *(*of_xlate)(struct device *dev,
const struct of_phandle_args *args))
{
struct framer_provider **ptr, *framer_provider;
ptr = devres_alloc(devm_framer_provider_of_unregister, sizeof(*ptr), GFP_KERNEL);
if (!ptr)
return ERR_PTR(-ENOMEM);
framer_provider = __framer_provider_of_register(dev, owner, of_xlate);
if (!IS_ERR(framer_provider)) {
*ptr = framer_provider;
devres_add(dev, ptr);
} else {
devres_free(ptr);
}
return framer_provider;
}
EXPORT_SYMBOL_GPL(__devm_framer_provider_of_register);
/**
* framer_release() - release the framer
* @dev: the dev member within framer
*
* When the last reference to the device is removed, it is called
* from the embedded kobject as release method.
*/
static void framer_release(struct device *dev)
{
struct framer *framer;
framer = dev_to_framer(dev);
regulator_put(framer->pwr);
ida_free(&framer_ida, framer->id);
kfree(framer);
}
static int __init framer_core_init(void)
{
return class_register(&framer_class);
}
device_initcall(framer_core_init);
@@ -0,0 +1,8 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for the pef2256 driver.
#
obj-$(CONFIG_FRAMER_PEF2256) += framer-pef2256.o
framer-pef2256-objs := pef2256.o
@@ -0,0 +1,250 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* PEF2256 registers definition
*
* Copyright 2023 CS GROUP France
*
* Author: Herve Codina <herve.codina@bootlin.com>
*/
#ifndef __PEF2256_REGS_H__
#define __PEF2256_REGS_H__
#include "linux/bitfield.h"
/* Command Register */
#define PEF2256_CMDR 0x02
#define PEF2256_CMDR_RRES BIT(6)
#define PEF2256_CMDR_XRES BIT(4)
#define PEF2256_CMDR_SRES BIT(0)
/* Interrupt Mask Register 0..5 */
#define PEF2256_IMR0 0x14
#define PEF2256_IMR1 0x15
#define PEF2256_IMR2 0x16
#define PEF2256_IMR3 0x17
#define PEF2256_IMR4 0x18
#define PEF2256_IMR5 0x19
/* Framer Mode Register 0 */
#define PEF2256_FMR0 0x1C
#define PEF2256_FMR0_XC_MASK GENMASK(7, 6)
#define PEF2256_FMR0_XC_NRZ FIELD_PREP_CONST(PEF2256_FMR0_XC_MASK, 0x0)
#define PEF2256_FMR0_XC_CMI FIELD_PREP_CONST(PEF2256_FMR0_XC_MASK, 0x1)
#define PEF2256_FMR0_XC_AMI FIELD_PREP_CONST(PEF2256_FMR0_XC_MASK, 0x2)
#define PEF2256_FMR0_XC_HDB3 FIELD_PREP_CONST(PEF2256_FMR0_XC_MASK, 0x3)
#define PEF2256_FMR0_RC_MASK GENMASK(5, 4)
#define PEF2256_FMR0_RC_NRZ FIELD_PREP_CONST(PEF2256_FMR0_RC_MASK, 0x0)
#define PEF2256_FMR0_RC_CMI FIELD_PREP_CONST(PEF2256_FMR0_RC_MASK, 0x1)
#define PEF2256_FMR0_RC_AMI FIELD_PREP_CONST(PEF2256_FMR0_RC_MASK, 0x2)
#define PEF2256_FMR0_RC_HDB3 FIELD_PREP_CONST(PEF2256_FMR0_RC_MASK, 0x3)
/* Framer Mode Register 1 */
#define PEF2256_FMR1 0x1D
#define PEF2256_FMR1_XFS BIT(3)
#define PEF2256_FMR1_ECM BIT(2)
/* SSD is defined on 2 bits. The other bit is on SIC1 register */
#define PEF2256_FMR1_SSD_MASK GENMASK(1, 1)
#define PEF2256_FMR1_SSD_2048 FIELD_PREP_CONST(PEF2256_FMR1_SSD_MASK, 0x0)
#define PEF2256_FMR1_SSD_4096 FIELD_PREP_CONST(PEF2256_FMR1_SSD_MASK, 0x1)
#define PEF2256_FMR1_SSD_8192 FIELD_PREP_CONST(PEF2256_FMR1_SSD_MASK, 0x0)
#define PEF2256_FMR1_SSD_16384 FIELD_PREP_CONST(PEF2256_FMR1_SSD_MASK, 0x1)
/* Framer Mode Register 2 */
#define PEF2256_FMR2 0x1E
#define PEF2256_FMR2_RFS_MASK GENMASK(7, 6)
#define PEF2256_FMR2_RFS_DOUBLEFRAME FIELD_PREP_CONST(PEF2256_FMR2_RFS_MASK, 0x0)
#define PEF2256_FMR2_RFS_CRC4_MULTIFRAME FIELD_PREP_CONST(PEF2256_FMR2_RFS_MASK, 0x2)
#define PEF2256_FMR2_RFS_AUTO_MULTIFRAME FIELD_PREP_CONST(PEF2256_FMR2_RFS_MASK, 0x3)
#define PEF2256_FMR2_AXRA BIT(1)
/* Transmit Service Word */
#define PEF2256_XSW 0x20
#define PEF2256_XSW_XSIS BIT(7)
#define PEF2256_XSW_XTM BIT(6)
#define PEF2256_XSW_XY_MASK GENMASK(5, 0)
#define PEF2256_XSW_XY(_v) FIELD_PREP(PEF2256_XSW_XY_MASK, _v)
/* Transmit Spare Bits */
#define PEF2256_XSP 0x21
#define PEF2256_XSP_XSIF BIT(2)
/* Transmit Control 0..1 */
#define PEF2256_XC0 0x22
#define PEF2256_XC1 0x23
/* Receive Control 0 */
#define PEF2256_RC0 0x24
#define PEF2256_RC0_SWD BIT(7)
#define PEF2256_RC0_ASY4 BIT(6)
/* Receive Control 1 */
#define PEF2256_RC1 0x25
/* Transmit Pulse Mask 0..1 */
#define PEF2256_XPM0 0x26
#define PEF2256_XPM1 0x27
/* Transmit Pulse Mask 2 */
#define PEF2256_XPM2 0x28
#define PEF2256_XPM2_XLT BIT(6)
/* Transparent Service Word Mask */
#define PEF2256_TSWM 0x29
/* Line Interface Mode 0 */
#define PEF2256_LIM0 0x36
#define PEF2256_2X_LIM0_BIT3 BIT(3) /* v2.x, described as a forced '1' bit */
#define PEF2256_LIM0_MAS BIT(0)
/* Line Interface Mode 1 */
#define PEF2256_LIM1 0x37
#define PEF2256_12_LIM1_RIL_MASK GENMASK(6, 4)
#define PEF2256_12_LIM1_RIL_910 FIELD_PREP_CONST(PEF2256_12_LIM1_RIL_MASK, 0x0)
#define PEF2256_12_LIM1_RIL_740 FIELD_PREP_CONST(PEF2256_12_LIM1_RIL_MASK, 0x1)
#define PEF2256_12_LIM1_RIL_590 FIELD_PREP_CONST(PEF2256_12_LIM1_RIL_MASK, 0x2)
#define PEF2256_12_LIM1_RIL_420 FIELD_PREP_CONST(PEF2256_12_LIM1_RIL_MASK, 0x3)
#define PEF2256_12_LIM1_RIL_320 FIELD_PREP_CONST(PEF2256_12_LIM1_RIL_MASK, 0x4)
#define PEF2256_12_LIM1_RIL_210 FIELD_PREP_CONST(PEF2256_12_LIM1_RIL_MASK, 0x5)
#define PEF2256_12_LIM1_RIL_160 FIELD_PREP_CONST(PEF2256_12_LIM1_RIL_MASK, 0x6)
#define PEF2256_12_LIM1_RIL_100 FIELD_PREP_CONST(PEF2256_12_LIM1_RIL_MASK, 0x7)
#define PEF2256_2X_LIM1_RIL_MASK GENMASK(6, 4)
#define PEF2256_2X_LIM1_RIL_2250 FIELD_PREP_CONST(PEF2256_2X_LIM1_RIL_MASK, 0x0)
#define PEF2256_2X_LIM1_RIL_1100 FIELD_PREP_CONST(PEF2256_2X_LIM1_RIL_MASK, 0x1)
#define PEF2256_2X_LIM1_RIL_600 FIELD_PREP_CONST(PEF2256_2X_LIM1_RIL_MASK, 0x2)
#define PEF2256_2X_LIM1_RIL_350 FIELD_PREP_CONST(PEF2256_2X_LIM1_RIL_MASK, 0x3)
#define PEF2256_2X_LIM1_RIL_210 FIELD_PREP_CONST(PEF2256_2X_LIM1_RIL_MASK, 0x4)
#define PEF2256_2X_LIM1_RIL_140 FIELD_PREP_CONST(PEF2256_2X_LIM1_RIL_MASK, 0x5)
#define PEF2256_2X_LIM1_RIL_100 FIELD_PREP_CONST(PEF2256_2X_LIM1_RIL_MASK, 0x6)
#define PEF2256_2X_LIM1_RIL_50 FIELD_PREP_CONST(PEF2256_2X_LIM1_RIL_MASK, 0x7)
/* Pulse Count Detection */
#define PEF2256_PCD 0x38
/* Pulse Count Recovery */
#define PEF2256_PCR 0x39
/* Line Interface Mode 2 */
#define PEF2256_LIM2 0x3A
#define PEF2256_LIM2_SLT_MASK GENMASK(5, 4)
#define PEF2256_LIM2_SLT_THR55 FIELD_PREP_CONST(PEF2256_LIM2_SLT_MASK, 0x0)
#define PEF2256_LIM2_SLT_THR67 FIELD_PREP_CONST(PEF2256_LIM2_SLT_MASK, 0x1)
#define PEF2256_LIM2_SLT_THR50 FIELD_PREP_CONST(PEF2256_LIM2_SLT_MASK, 0x2)
#define PEF2256_LIM2_SLT_THR45 FIELD_PREP_CONST(PEF2256_LIM2_SLT_MASK, 0x3)
#define PEF2256_LIM2_ELT BIT(2)
/* System Interface Control 1 */
#define PEF2256_SIC1 0x3E
#define PEF2256_SIC1_SSC_MASK (BIT(7) | BIT(3))
#define PEF2256_SIC1_SSC_2048 (0)
#define PEF2256_SIC1_SSC_4096 BIT(3)
#define PEF2256_SIC1_SSC_8192 BIT(7)
#define PEF2256_SIC1_SSC_16384 (BIT(7) | BIT(3))
/* SSD is defined on 2 bits. The other bit is on FMR1 register */
#define PEF2256_SIC1_SSD_MASK GENMASK(6, 6)
#define PEF2256_SIC1_SSD_2048 FIELD_PREP_CONST(PEF2256_SIC1_SSD_MASK, 0x0)
#define PEF2256_SIC1_SSD_4096 FIELD_PREP_CONST(PEF2256_SIC1_SSD_MASK, 0x0)
#define PEF2256_SIC1_SSD_8192 FIELD_PREP_CONST(PEF2256_SIC1_SSD_MASK, 0x1)
#define PEF2256_SIC1_SSD_16384 FIELD_PREP_CONST(PEF2256_SIC1_SSD_MASK, 0x1)
#define PEF2256_SIC1_RBS_MASK GENMASK(5, 4)
#define PEF2256_SIC1_RBS_2FRAMES FIELD_PREP_CONST(PEF2256_SIC1_RBS_MASK, 0x0)
#define PEF2256_SIC1_RBS_1FRAME FIELD_PREP_CONST(PEF2256_SIC1_RBS_MASK, 0x1)
#define PEF2256_SIC1_RBS_96BITS FIELD_PREP_CONST(PEF2256_SIC1_RBS_MASK, 0x2)
#define PEF2256_SIC1_RBS_BYPASS FIELD_PREP_CONST(PEF2256_SIC1_RBS_MASK, 0x3)
#define PEF2256_SIC1_XBS_MASK GENMASK(1, 0)
#define PEF2256_SIC1_XBS_BYPASS FIELD_PREP_CONST(PEF2256_SIC1_XBS_MASK, 0x0)
#define PEF2256_SIC1_XBS_1FRAME FIELD_PREP_CONST(PEF2256_SIC1_XBS_MASK, 0x1)
#define PEF2256_SIC1_XBS_2FRAMES FIELD_PREP_CONST(PEF2256_SIC1_XBS_MASK, 0x2)
#define PEF2256_SIC1_XBS_96BITS FIELD_PREP_CONST(PEF2256_SIC1_XBS_MASK, 0x3)
/* System Interface Control 2 */
#define PEF2256_SIC2 0x3F
#define PEF2256_SIC2_SICS_MASK GENMASK(3, 1)
#define PEF2256_SIC2_SICS(_v) FIELD_PREP(PEF2256_SIC2_SICS_MASK, _v)
/* System Interface Control 3 */
#define PEF2256_SIC3 0x40
#define PEF2256_SIC3_RTRI BIT(5)
#define PEF2256_SIC3_RESX BIT(3)
#define PEF2256_SIC3_RESR BIT(2)
/* Clock Mode Register 1 */
#define PEF2256_CMR1 0x44
#define PEF2256_CMR1_RS_MASK GENMASK(5, 4)
#define PEF2256_CMR1_RS_DPLL FIELD_PREP_CONST(PEF2256_CMR1_RS_MASK, 0x0)
#define PEF2256_CMR1_RS_DPLL_LOS_HIGH FIELD_PREP_CONST(PEF2256_CMR1_RS_MASK, 0x1)
#define PEF2256_CMR1_RS_DCOR_2048 FIELD_PREP_CONST(PEF2256_CMR1_RS_MASK, 0x2)
#define PEF2256_CMR1_RS_DCOR_8192 FIELD_PREP_CONST(PEF2256_CMR1_RS_MASK, 0x3)
#define PEF2256_CMR1_DCS BIT(3)
/* Clock Mode Register 2 */
#define PEF2256_CMR2 0x45
#define PEF2256_CMR2_DCOXC BIT(5)
/* Global Configuration Register */
#define PEF2256_GCR 0x46
#define PEF2256_GCR_SCI BIT(6)
#define PEF2256_GCR_ECMC BIT(4)
/* Port Configuration 5 */
#define PEF2256_PC5 0x84
#define PEF2256_PC5_CRP BIT(0)
/* Global Port Configuration 1 */
#define PEF2256_GPC1 0x85
#define PEF2256_GPC1_CSFP_MASK GENMASK(7, 5)
#define PEF2256_GPC1_CSFP_SEC_IN_HIGH FIELD_PREP_CONST(PEF2256_GPC1_CSFP_MASK, 0x0)
#define PEF2256_GPC1_CSFP_SEC_OUT_HIGH FIELD_PREP_CONST(PEF2256_GPC1_CSFP_MASK, 0x1)
#define PEF2256_GPC1_CSFP_FSC_OUT_HIGH FIELD_PREP_CONST(PEF2256_GPC1_CSFP_MASK, 0x2)
#define PEF2256_GPC1_CSFP_FSC_OUT_LOW FIELD_PREP_CONST(PEF2256_GPC1_CSFP_MASK, 0x3)
/* Port Configuration 6 */
#define PEF2256_PC6 0x86
/* Global Counter Mode n=1..8 */
#define PEF2256_GCM(_n) (0x92 + (_n) - 1)
#define PEF2256_GCM1 0x92
#define PEF2256_GCM2 0x93
#define PEF2256_GCM3 0x94
#define PEF2256_GCM4 0x95
#define PEF2256_GCM5 0x96
#define PEF2256_GCM6 0x97
#define PEF2256_GCM7 0x98
#define PEF2256_GCM8 0x99
/* Version Status Register */
#define PEF2256_VSTR 0x4A
#define PEF2256_VSTR_VERSION_12 0x00
#define PEF2256_VSTR_VERSION_21 0x10
#define PEF2256_VSTR_VERSION_2x 0x05
/* Framer Receive Status 0 */
#define PEF2256_FRS0 0x4C
#define PEF2256_FRS0_LOS BIT(7)
#define PEF2256_FRS0_AIS BIT(6)
/* Interrupt Status Register 0..5 */
#define PEF2256_ISR(_n) (0x68 + (_n))
#define PEF2256_ISR0 0x68
#define PEF2256_ISR1 0x69
#define PEF2256_ISR2 0x6A
#define PEF2256_ISR3 0x6B
#define PEF2256_ISR4 0x6C
#define PEF2256_ISR5 0x6D
/* Global Interrupt Status */
#define PEF2256_GIS 0x6E
#define PEF2256_GIS_ISR(_n) BIT(_n)
/* Wafer Identification Register */
#define PEF2256_WID 0xEC
#define PEF2256_12_WID_MASK GENMASK(1, 0)
#define PEF2256_12_WID_VERSION_12 FIELD_PREP_CONST(PEF2256_12_WID_MASK, 0x3)
#define PEF2256_2X_WID_MASK GENMASK(7, 6)
#define PEF2256_2X_WID_VERSION_21 FIELD_PREP_CONST(PEF2256_2X_WID_MASK, 0x0)
#define PEF2256_2X_WID_VERSION_22 FIELD_PREP_CONST(PEF2256_2X_WID_MASK, 0x1)
/* IMR2/ISR2 Interrupts common bits */
#define PEF2256_INT2_AIS BIT(3)
#define PEF2256_INT2_LOS BIT(2)
#endif /* __PEF2256_REGS_H__ */
@@ -0,0 +1,893 @@
// SPDX-License-Identifier: GPL-2.0
/*
* PEF2256 also known as FALC56 driver
*
* Copyright 2023 CS GROUP France
*
* Author: Herve Codina <herve.codina@bootlin.com>
*/
#include <linux/framer/pef2256.h>
#include <linux/clk.h>
#include <linux/framer/framer-provider.h>
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/mfd/core.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include "pef2256-regs.h"
enum pef2256_frame_type {
PEF2256_FRAME_E1_DOUBLEFRAME,
PEF2256_FRAME_E1_CRC4_MULTIFRAME,
PEF2256_FRAME_E1_AUTO_MULTIFRAME,
PEF2256_FRAME_T1J1_4FRAME,
PEF2256_FRAME_T1J1_12FRAME,
PEF2256_FRAME_T1J1_24FRAME,
PEF2256_FRAME_T1J1_72FRAME,
};
struct pef2256 {
struct device *dev;
struct regmap *regmap;
enum pef2256_version version;
const char *version_txt;
struct clk *mclk;
struct clk *sclkr;
struct clk *sclkx;
struct gpio_desc *reset_gpio;
unsigned long sysclk_rate;
u32 data_rate;
bool is_tx_falling_edge;
bool is_subordinate;
enum pef2256_frame_type frame_type;
u8 channel_phase;
atomic_t carrier;
struct framer *framer;
};
static u8 pef2256_read8(struct pef2256 *pef2256, int offset)
{
int val;
regmap_read(pef2256->regmap, offset, &val);
return val;
}
static void pef2256_write8(struct pef2256 *pef2256, int offset, u8 val)
{
regmap_write(pef2256->regmap, offset, val);
}
static void pef2256_clrbits8(struct pef2256 *pef2256, int offset, u8 clr)
{
regmap_clear_bits(pef2256->regmap, offset, clr);
}
static void pef2256_setbits8(struct pef2256 *pef2256, int offset, u8 set)
{
regmap_set_bits(pef2256->regmap, offset, set);
}
static void pef2256_clrsetbits8(struct pef2256 *pef2256, int offset, u8 clr, u8 set)
{
regmap_update_bits(pef2256->regmap, offset, clr | set, set);
}
enum pef2256_version pef2256_get_version(struct pef2256 *pef2256)
{
enum pef2256_version version = PEF2256_VERSION_UNKNOWN;
u8 vstr, wid;
vstr = pef2256_read8(pef2256, PEF2256_VSTR);
wid = pef2256_read8(pef2256, PEF2256_WID);
switch (vstr) {
case PEF2256_VSTR_VERSION_12:
if ((wid & PEF2256_12_WID_MASK) == PEF2256_12_WID_VERSION_12)
version = PEF2256_VERSION_1_2;
break;
case PEF2256_VSTR_VERSION_2x:
switch (wid & PEF2256_2X_WID_MASK) {
case PEF2256_2X_WID_VERSION_21:
version = PEF2256_VERSION_2_1;
break;
case PEF2256_2X_WID_VERSION_22:
version = PEF2256_VERSION_2_2;
break;
}
break;
case PEF2256_VSTR_VERSION_21:
version = PEF2256_VERSION_2_1;
break;
}
if (version == PEF2256_VERSION_UNKNOWN)
dev_err(pef2256->dev, "Unknown version (0x%02x, 0x%02x)\n", vstr, wid);
return version;
}
EXPORT_SYMBOL_GPL(pef2256_get_version);
static ssize_t version_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct pef2256 *pef2256 = dev_get_drvdata(dev);
return sysfs_emit(buf, "%s\n", pef2256->version_txt);
}
static DEVICE_ATTR_RO(version);
enum pef2256_gcm_config_item {
PEF2256_GCM_CONFIG_1544000 = 0,
PEF2256_GCM_CONFIG_2048000,
PEF2256_GCM_CONFIG_8192000,
PEF2256_GCM_CONFIG_10000000,
PEF2256_GCM_CONFIG_12352000,
PEF2256_GCM_CONFIG_16384000,
};
struct pef2256_gcm_config {
u8 gcm_12[6];
u8 gcm_2x[8];
};
static const struct pef2256_gcm_config pef2256_gcm_configs[] = {
[PEF2256_GCM_CONFIG_1544000] = {
.gcm_12 = {0xF0, 0x51, 0x00, 0x80, 0x00, 0x15},
.gcm_2x = {0x00, 0x15, 0x00, 0x08, 0x00, 0x3F, 0x9C, 0xDF},
},
[PEF2256_GCM_CONFIG_2048000] = {
.gcm_12 = {0x00, 0x58, 0xD2, 0xC2, 0x00, 0x10},
.gcm_2x = {0x00, 0x18, 0xFB, 0x0B, 0x00, 0x2F, 0xDB, 0xDF},
},
[PEF2256_GCM_CONFIG_8192000] = {
.gcm_12 = {0x00, 0x58, 0xD2, 0xC2, 0x03, 0x10},
.gcm_2x = {0x00, 0x18, 0xFB, 0x0B, 0x00, 0x0B, 0xDB, 0xDF},
},
[PEF2256_GCM_CONFIG_10000000] = {
.gcm_12 = {0x90, 0x51, 0x81, 0x8F, 0x04, 0x10},
.gcm_2x = {0x40, 0x1B, 0x3D, 0x0A, 0x00, 0x07, 0xC9, 0xDC},
},
[PEF2256_GCM_CONFIG_12352000] = {
.gcm_12 = {0xF0, 0x51, 0x00, 0x80, 0x07, 0x15},
.gcm_2x = {0x00, 0x19, 0x00, 0x08, 0x01, 0x0A, 0x98, 0xDA},
},
[PEF2256_GCM_CONFIG_16384000] = {
.gcm_12 = {0x00, 0x58, 0xD2, 0xC2, 0x07, 0x10},
.gcm_2x = {0x00, 0x18, 0xFB, 0x0B, 0x01, 0x0B, 0xDB, 0xDF},
},
};
static int pef2256_setup_gcm(struct pef2256 *pef2256)
{
enum pef2256_gcm_config_item item;
unsigned long mclk_rate;
const u8 *gcm;
int i, count;
mclk_rate = clk_get_rate(pef2256->mclk);
switch (mclk_rate) {
case 1544000:
item = PEF2256_GCM_CONFIG_1544000;
break;
case 2048000:
item = PEF2256_GCM_CONFIG_2048000;
break;
case 8192000:
item = PEF2256_GCM_CONFIG_8192000;
break;
case 10000000:
item = PEF2256_GCM_CONFIG_10000000;
break;
case 12352000:
item = PEF2256_GCM_CONFIG_12352000;
break;
case 16384000:
item = PEF2256_GCM_CONFIG_16384000;
break;
default:
dev_err(pef2256->dev, "Unsupported v2.x MCLK rate %lu\n", mclk_rate);
return -EINVAL;
}
BUILD_BUG_ON(item >= ARRAY_SIZE(pef2256_gcm_configs));
if (pef2256->version == PEF2256_VERSION_1_2) {
gcm = pef2256_gcm_configs[item].gcm_12;
count = ARRAY_SIZE(pef2256_gcm_configs[item].gcm_12);
} else {
gcm = pef2256_gcm_configs[item].gcm_2x;
count = ARRAY_SIZE(pef2256_gcm_configs[item].gcm_2x);
}
for (i = 0; i < count; i++)
pef2256_write8(pef2256, PEF2256_GCM(i + 1), *(gcm + i));
return 0;
}
static int pef2256_setup_e1_line(struct pef2256 *pef2256)
{
u8 fmr1, fmr2;
/* RCLK output : DPLL clock, DCO-X enabled, DCO-X internal ref clock */
pef2256_write8(pef2256, PEF2256_CMR1, 0x00);
/* SCLKR selected, SCLKX selected,
* receive synchro pulse sourced by SYPR,
* transmit synchro pulse sourced by SYPX,
* DCO-X center frequency enabled
*/
pef2256_write8(pef2256, PEF2256_CMR2, PEF2256_CMR2_DCOXC);
if (pef2256->is_subordinate) {
/* select RCLK source = 2M, disable switching from RCLK to SYNC */
pef2256_clrsetbits8(pef2256, PEF2256_CMR1, PEF2256_CMR1_RS_MASK,
PEF2256_CMR1_RS_DCOR_2048 | PEF2256_CMR1_DCS);
}
/* slave mode, local loop off, mode short-haul
* In v2.x, bit3 is a forced 1 bit in the datasheet -> Need to be set.
*/
if (pef2256->version == PEF2256_VERSION_1_2)
pef2256_write8(pef2256, PEF2256_LIM0, 0x00);
else
pef2256_write8(pef2256, PEF2256_LIM0, PEF2256_2X_LIM0_BIT3);
/* "master" mode */
if (!pef2256->is_subordinate)
pef2256_setbits8(pef2256, PEF2256_LIM0, PEF2256_LIM0_MAS);
/* analog interface selected, remote loop off */
pef2256_write8(pef2256, PEF2256_LIM1, 0x00);
/* receive input threshold = 0,21V */
if (pef2256->version == PEF2256_VERSION_1_2)
pef2256_clrsetbits8(pef2256, PEF2256_LIM1, PEF2256_12_LIM1_RIL_MASK,
PEF2256_12_LIM1_RIL_210);
else
pef2256_clrsetbits8(pef2256, PEF2256_LIM1, PEF2256_2X_LIM1_RIL_MASK,
PEF2256_2X_LIM1_RIL_210);
/* transmit pulse mask, default value from datasheet
* transmit line in normal operation
*/
if (pef2256->version == PEF2256_VERSION_1_2)
pef2256_write8(pef2256, PEF2256_XPM0, 0x7B);
else
pef2256_write8(pef2256, PEF2256_XPM0, 0x9C);
pef2256_write8(pef2256, PEF2256_XPM1, 0x03);
pef2256_write8(pef2256, PEF2256_XPM2, 0x00);
/* HDB3 coding, no alarm simulation */
pef2256_write8(pef2256, PEF2256_FMR0, PEF2256_FMR0_XC_HDB3 | PEF2256_FMR0_RC_HDB3);
/* E1, frame format, 2 Mbit/s system data rate, no AIS
* transmission to remote end or system interface, payload loop
* off, transmit remote alarm on
*/
fmr1 = 0x00;
fmr2 = PEF2256_FMR2_AXRA;
switch (pef2256->frame_type) {
case PEF2256_FRAME_E1_DOUBLEFRAME:
fmr2 |= PEF2256_FMR2_RFS_DOUBLEFRAME;
break;
case PEF2256_FRAME_E1_CRC4_MULTIFRAME:
fmr1 |= PEF2256_FMR1_XFS;
fmr2 |= PEF2256_FMR2_RFS_CRC4_MULTIFRAME;
break;
case PEF2256_FRAME_E1_AUTO_MULTIFRAME:
fmr1 |= PEF2256_FMR1_XFS;
fmr2 |= PEF2256_FMR2_RFS_AUTO_MULTIFRAME;
break;
default:
dev_err(pef2256->dev, "Unsupported frame type %d\n", pef2256->frame_type);
return -EINVAL;
}
pef2256_clrsetbits8(pef2256, PEF2256_FMR1, PEF2256_FMR1_XFS, fmr1);
pef2256_write8(pef2256, PEF2256_FMR2, fmr2);
if (!pef2256->is_subordinate) {
/* SEC input, active high */
pef2256_write8(pef2256, PEF2256_GPC1, PEF2256_GPC1_CSFP_SEC_IN_HIGH);
} else {
/* FSC output, active high */
pef2256_write8(pef2256, PEF2256_GPC1, PEF2256_GPC1_CSFP_FSC_OUT_HIGH);
}
/* SCLKR, SCLKX, RCLK configured to inputs,
* XFMS active low, CLK1 and CLK2 pin configuration
*/
pef2256_write8(pef2256, PEF2256_PC5, 0x00);
pef2256_write8(pef2256, PEF2256_PC6, 0x00);
/* port RCLK is output */
pef2256_setbits8(pef2256, PEF2256_PC5, PEF2256_PC5_CRP);
return 0;
}
static void pef2256_setup_e1_los(struct pef2256 *pef2256)
{
/* detection of LOS alarm = 176 pulses (ie (10 + 1) * 16) */
pef2256_write8(pef2256, PEF2256_PCD, 10);
/* recovery of LOS alarm = 22 pulses (ie 21 + 1) */
pef2256_write8(pef2256, PEF2256_PCR, 21);
/* E1 default for the receive slicer threshold */
pef2256_write8(pef2256, PEF2256_LIM2, PEF2256_LIM2_SLT_THR50);
if (pef2256->is_subordinate) {
/* Loop-timed */
pef2256_setbits8(pef2256, PEF2256_LIM2, PEF2256_LIM2_ELT);
}
}
static int pef2256_setup_e1_system(struct pef2256 *pef2256)
{
u8 sic1, fmr1;
/* 2.048 MHz system clocking rate, receive buffer 2 frames, transmit
* buffer bypass, data sampled and transmitted on the falling edge of
* SCLKR/X, automatic freeze signaling, data is active in the first
* channel phase
*/
pef2256_write8(pef2256, PEF2256_SIC1, 0x00);
pef2256_write8(pef2256, PEF2256_SIC2, 0x00);
pef2256_write8(pef2256, PEF2256_SIC3, 0x00);
if (pef2256->is_subordinate) {
/* transmit buffer size = 2 frames, transparent mode */
pef2256_clrsetbits8(pef2256, PEF2256_SIC1, PEF2256_SIC1_XBS_MASK,
PEF2256_SIC1_XBS_2FRAMES);
}
if (pef2256->version != PEF2256_VERSION_1_2) {
/* during inactive channel phase switch RDO/RSIG into tri-state */
pef2256_setbits8(pef2256, PEF2256_SIC3, PEF2256_SIC3_RTRI);
}
if (pef2256->is_tx_falling_edge) {
/* falling edge sync pulse transmit, rising edge sync pulse receive */
pef2256_clrsetbits8(pef2256, PEF2256_SIC3, PEF2256_SIC3_RESX, PEF2256_SIC3_RESR);
} else {
/* rising edge sync pulse transmit, falling edge sync pulse receive */
pef2256_clrsetbits8(pef2256, PEF2256_SIC3, PEF2256_SIC3_RESR, PEF2256_SIC3_RESX);
}
/* transmit offset counter (XCO10..0) = 4 */
pef2256_write8(pef2256, PEF2256_XC0, 0);
pef2256_write8(pef2256, PEF2256_XC1, 4);
/* receive offset counter (RCO10..0) = 4 */
pef2256_write8(pef2256, PEF2256_RC0, 0);
pef2256_write8(pef2256, PEF2256_RC1, 4);
/* system clock rate */
switch (pef2256->sysclk_rate) {
case 2048000:
sic1 = PEF2256_SIC1_SSC_2048;
break;
case 4096000:
sic1 = PEF2256_SIC1_SSC_4096;
break;
case 8192000:
sic1 = PEF2256_SIC1_SSC_8192;
break;
case 16384000:
sic1 = PEF2256_SIC1_SSC_16384;
break;
default:
dev_err(pef2256->dev, "Unsupported sysclk rate %lu\n", pef2256->sysclk_rate);
return -EINVAL;
}
pef2256_clrsetbits8(pef2256, PEF2256_SIC1, PEF2256_SIC1_SSC_MASK, sic1);
/* data clock rate */
switch (pef2256->data_rate) {
case 2048000:
fmr1 = PEF2256_FMR1_SSD_2048;
sic1 = PEF2256_SIC1_SSD_2048;
break;
case 4096000:
fmr1 = PEF2256_FMR1_SSD_4096;
sic1 = PEF2256_SIC1_SSD_4096;
break;
case 8192000:
fmr1 = PEF2256_FMR1_SSD_8192;
sic1 = PEF2256_SIC1_SSD_8192;
break;
case 16384000:
fmr1 = PEF2256_FMR1_SSD_16384;
sic1 = PEF2256_SIC1_SSD_16384;
break;
default:
dev_err(pef2256->dev, "Unsupported data rate %u\n", pef2256->data_rate);
return -EINVAL;
}
pef2256_clrsetbits8(pef2256, PEF2256_FMR1, PEF2256_FMR1_SSD_MASK, fmr1);
pef2256_clrsetbits8(pef2256, PEF2256_SIC1, PEF2256_SIC1_SSD_MASK, sic1);
/* channel phase */
pef2256_clrsetbits8(pef2256, PEF2256_SIC2, PEF2256_SIC2_SICS_MASK,
PEF2256_SIC2_SICS(pef2256->channel_phase));
return 0;
}
static void pef2256_setup_e1_signaling(struct pef2256 *pef2256)
{
/* All bits of the transmitted service word are cleared */
pef2256_write8(pef2256, PEF2256_XSW, PEF2256_XSW_XY(0x1F));
/* CAS disabled and clear spare bit values */
pef2256_write8(pef2256, PEF2256_XSP, 0x00);
if (pef2256->is_subordinate) {
/* transparent mode */
pef2256_setbits8(pef2256, PEF2256_XSW, PEF2256_XSW_XTM);
}
/* Si-Bit, Spare bit For International, FAS word */
pef2256_setbits8(pef2256, PEF2256_XSW, PEF2256_XSW_XSIS);
pef2256_setbits8(pef2256, PEF2256_XSP, PEF2256_XSP_XSIF);
/* no transparent mode active */
pef2256_write8(pef2256, PEF2256_TSWM, 0x00);
}
static void pef2256_setup_e1_errors(struct pef2256 *pef2256)
{
/* error counter latched every 1s */
pef2256_setbits8(pef2256, PEF2256_FMR1, PEF2256_FMR1_ECM);
/* error counter mode COFA */
pef2256_setbits8(pef2256, PEF2256_GCR, PEF2256_GCR_ECMC);
/* errors in service words have no influence */
pef2256_setbits8(pef2256, PEF2256_RC0, PEF2256_RC0_SWD);
/* 4 consecutive incorrect FAS causes loss of sync */
pef2256_setbits8(pef2256, PEF2256_RC0, PEF2256_RC0_ASY4);
}
static int pef2256_setup_e1(struct pef2256 *pef2256)
{
int ret;
/* Setup, Master clocking mode (GCM8..1) */
ret = pef2256_setup_gcm(pef2256);
if (ret)
return ret;
/* Select E1 mode */
pef2256_write8(pef2256, PEF2256_FMR1, 0x00);
/* internal second timer, power on */
pef2256_write8(pef2256, PEF2256_GCR, 0x00);
/* Setup line interface */
ret = pef2256_setup_e1_line(pef2256);
if (ret)
return ret;
/* Setup Loss-of-signal detection and recovery */
pef2256_setup_e1_los(pef2256);
/* Setup system interface */
ret = pef2256_setup_e1_system(pef2256);
if (ret)
return ret;
/* Setup signaling */
pef2256_setup_e1_signaling(pef2256);
/* Setup errors counters and condition */
pef2256_setup_e1_errors(pef2256);
/* status changed interrupt at both up and down */
pef2256_setbits8(pef2256, PEF2256_GCR, PEF2256_GCR_SCI);
/* Clear any ISR2 pending interrupts and unmask needed interrupts */
pef2256_read8(pef2256, PEF2256_ISR2);
pef2256_clrbits8(pef2256, PEF2256_IMR2, PEF2256_INT2_LOS | PEF2256_INT2_AIS);
/* reset lines */
pef2256_write8(pef2256, PEF2256_CMDR, PEF2256_CMDR_RRES | PEF2256_CMDR_XRES);
return 0;
}
static void pef2256_isr_default_handler(struct pef2256 *pef2256, u8 nbr, u8 isr)
{
dev_warn_ratelimited(pef2256->dev, "ISR%u: 0x%02x not handled\n", nbr, isr);
}
static bool pef2256_is_carrier_on(struct pef2256 *pef2256)
{
u8 frs0;
frs0 = pef2256_read8(pef2256, PEF2256_FRS0);
return !(frs0 & (PEF2256_FRS0_LOS | PEF2256_FRS0_AIS));
}
static void pef2256_isr2_handler(struct pef2256 *pef2256, u8 nbr, u8 isr)
{
bool carrier;
if (isr & (PEF2256_INT2_LOS | PEF2256_INT2_AIS)) {
carrier = pef2256_is_carrier_on(pef2256);
if (atomic_xchg(&pef2256->carrier, carrier) != carrier)
framer_notify_status_change(pef2256->framer);
}
}
static irqreturn_t pef2256_irq_handler(int irq, void *priv)
{
static void (*pef2256_isr_handler[])(struct pef2256 *, u8, u8) = {
[0] = pef2256_isr_default_handler,
[1] = pef2256_isr_default_handler,
[2] = pef2256_isr2_handler,
[3] = pef2256_isr_default_handler,
[4] = pef2256_isr_default_handler,
[5] = pef2256_isr_default_handler
};
struct pef2256 *pef2256 = (struct pef2256 *)priv;
u8 gis;
u8 isr;
u8 n;
gis = pef2256_read8(pef2256, PEF2256_GIS);
for (n = 0; n < ARRAY_SIZE(pef2256_isr_handler); n++) {
if (gis & PEF2256_GIS_ISR(n)) {
isr = pef2256_read8(pef2256, PEF2256_ISR(n));
pef2256_isr_handler[n](pef2256, n, isr);
}
}
return IRQ_HANDLED;
}
static int pef2256_check_rates(struct pef2256 *pef2256, unsigned long sysclk_rate,
unsigned long data_rate)
{
unsigned long rate;
switch (sysclk_rate) {
case 2048000:
case 4096000:
case 8192000:
case 16384000:
break;
default:
dev_err(pef2256->dev, "Unsupported system clock rate %lu\n", sysclk_rate);
return -EINVAL;
}
for (rate = data_rate; rate <= data_rate * 4; rate *= 2) {
if (rate == sysclk_rate)
return 0;
}
dev_err(pef2256->dev, "Unsupported data rate %lu with system clock rate %lu\n",
data_rate, sysclk_rate);
return -EINVAL;
}
static int pef2556_of_parse(struct pef2256 *pef2256, struct device_node *np)
{
int ret;
pef2256->data_rate = 2048000;
ret = of_property_read_u32(np, "lantiq,data-rate-bps", &pef2256->data_rate);
if (ret && ret != -EINVAL) {
dev_err(pef2256->dev, "%pOF: failed to read lantiq,data-rate-bps\n", np);
return ret;
}
ret = pef2256_check_rates(pef2256, pef2256->sysclk_rate, pef2256->data_rate);
if (ret)
return ret;
pef2256->is_tx_falling_edge = of_property_read_bool(np, "lantiq,clock-falling-edge");
pef2256->channel_phase = 0;
ret = of_property_read_u8(np, "lantiq,channel-phase", &pef2256->channel_phase);
if (ret && ret != -EINVAL) {
dev_err(pef2256->dev, "%pOF: failed to read lantiq,channel-phase\n",
np);
return ret;
}
if (pef2256->channel_phase >= pef2256->sysclk_rate / pef2256->data_rate) {
dev_err(pef2256->dev, "%pOF: Invalid lantiq,channel-phase %u\n",
np, pef2256->channel_phase);
return -EINVAL;
}
return 0;
}
static const struct regmap_config pef2256_regmap_config = {
.reg_bits = 32,
.val_bits = 8,
.max_register = 0xff,
};
static const struct mfd_cell pef2256_devs[] = {
{ .name = "lantiq-pef2256-pinctrl", },
};
static int pef2256_add_audio_devices(struct pef2256 *pef2256)
{
const char *compatible = "lantiq,pef2256-codec";
struct mfd_cell *audio_devs;
struct device_node *np;
unsigned int count = 0;
unsigned int i;
int ret;
for_each_available_child_of_node(pef2256->dev->of_node, np) {
if (of_device_is_compatible(np, compatible))
count++;
}
if (!count)
return 0;
audio_devs = kzalloc_objs(*audio_devs, count);
if (!audio_devs)
return -ENOMEM;
for (i = 0; i < count; i++) {
audio_devs[i].name = "framer-codec";
audio_devs[i].of_compatible = compatible;
audio_devs[i].id = i;
}
ret = devm_mfd_add_devices(pef2256->dev, 0, audio_devs, count,
NULL, 0, NULL);
kfree(audio_devs);
return ret;
}
static int pef2256_framer_get_status(struct framer *framer, struct framer_status *status)
{
struct pef2256 *pef2256 = framer_get_drvdata(framer);
status->link_is_on = !!atomic_read(&pef2256->carrier);
return 0;
}
static int pef2256_framer_set_config(struct framer *framer, const struct framer_config *config)
{
struct pef2256 *pef2256 = framer_get_drvdata(framer);
if (config->iface != FRAMER_IFACE_E1) {
dev_err(pef2256->dev, "Only E1 line is currently supported\n");
return -EOPNOTSUPP;
}
switch (config->clock_type) {
case FRAMER_CLOCK_EXT:
pef2256->is_subordinate = true;
break;
case FRAMER_CLOCK_INT:
pef2256->is_subordinate = false;
break;
default:
return -EINVAL;
}
/* Apply the new settings */
return pef2256_setup_e1(pef2256);
}
static int pef2256_framer_get_config(struct framer *framer, struct framer_config *config)
{
struct pef2256 *pef2256 = framer_get_drvdata(framer);
config->iface = FRAMER_IFACE_E1;
config->clock_type = pef2256->is_subordinate ? FRAMER_CLOCK_EXT : FRAMER_CLOCK_INT;
config->line_clock_rate = 2048000;
return 0;
}
static const struct framer_ops pef2256_framer_ops = {
.owner = THIS_MODULE,
.get_status = pef2256_framer_get_status,
.get_config = pef2256_framer_get_config,
.set_config = pef2256_framer_set_config,
};
static int pef2256_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
unsigned long sclkr_rate, sclkx_rate;
struct framer_provider *framer_provider;
struct pef2256 *pef2256;
void __iomem *iomem;
int ret;
int irq;
pef2256 = devm_kzalloc(&pdev->dev, sizeof(*pef2256), GFP_KERNEL);
if (!pef2256)
return -ENOMEM;
pef2256->dev = &pdev->dev;
atomic_set(&pef2256->carrier, 0);
pef2256->is_subordinate = true;
pef2256->frame_type = PEF2256_FRAME_E1_DOUBLEFRAME;
iomem = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(iomem))
return PTR_ERR(iomem);
pef2256->regmap = devm_regmap_init_mmio(&pdev->dev, iomem,
&pef2256_regmap_config);
if (IS_ERR(pef2256->regmap)) {
dev_err(&pdev->dev, "Failed to initialise Regmap (%pe)\n",
pef2256->regmap);
return PTR_ERR(pef2256->regmap);
}
pef2256->mclk = devm_clk_get_enabled(&pdev->dev, "mclk");
if (IS_ERR(pef2256->mclk))
return PTR_ERR(pef2256->mclk);
pef2256->sclkr = devm_clk_get_enabled(&pdev->dev, "sclkr");
if (IS_ERR(pef2256->sclkr))
return PTR_ERR(pef2256->sclkr);
pef2256->sclkx = devm_clk_get_enabled(&pdev->dev, "sclkx");
if (IS_ERR(pef2256->sclkx))
return PTR_ERR(pef2256->sclkx);
/* Both SCLKR (receive) and SCLKX (transmit) must have the same rate,
* stored as sysclk_rate.
* The exact value will be checked at pef2256_check_rates()
*/
sclkr_rate = clk_get_rate(pef2256->sclkr);
sclkx_rate = clk_get_rate(pef2256->sclkx);
if (sclkr_rate != sclkx_rate) {
dev_err(pef2256->dev, "clk rate mismatch. sclkr %lu Hz, sclkx %lu Hz\n",
sclkr_rate, sclkx_rate);
return -EINVAL;
}
pef2256->sysclk_rate = sclkr_rate;
/* Reset the component. The MCLK clock must be active during reset */
pef2256->reset_gpio = devm_gpiod_get_optional(&pdev->dev, "reset", GPIOD_OUT_LOW);
if (IS_ERR(pef2256->reset_gpio))
return PTR_ERR(pef2256->reset_gpio);
if (pef2256->reset_gpio) {
gpiod_set_value_cansleep(pef2256->reset_gpio, 1);
usleep_range(10, 20);
gpiod_set_value_cansleep(pef2256->reset_gpio, 0);
usleep_range(10, 20);
}
pef2256->version = pef2256_get_version(pef2256);
switch (pef2256->version) {
case PEF2256_VERSION_1_2:
pef2256->version_txt = "1.2";
break;
case PEF2256_VERSION_2_1:
pef2256->version_txt = "2.1";
break;
case PEF2256_VERSION_2_2:
pef2256->version_txt = "2.2";
break;
default:
return -ENODEV;
}
dev_info(pef2256->dev, "Version %s detected\n", pef2256->version_txt);
ret = pef2556_of_parse(pef2256, np);
if (ret)
return ret;
/* Create the framer. It can be used on interrupts */
pef2256->framer = devm_framer_create(pef2256->dev, NULL, &pef2256_framer_ops);
if (IS_ERR(pef2256->framer))
return PTR_ERR(pef2256->framer);
framer_set_drvdata(pef2256->framer, pef2256);
/* Disable interrupts */
pef2256_write8(pef2256, PEF2256_IMR0, 0xff);
pef2256_write8(pef2256, PEF2256_IMR1, 0xff);
pef2256_write8(pef2256, PEF2256_IMR2, 0xff);
pef2256_write8(pef2256, PEF2256_IMR3, 0xff);
pef2256_write8(pef2256, PEF2256_IMR4, 0xff);
pef2256_write8(pef2256, PEF2256_IMR5, 0xff);
/* Clear any pending interrupts */
pef2256_read8(pef2256, PEF2256_ISR0);
pef2256_read8(pef2256, PEF2256_ISR1);
pef2256_read8(pef2256, PEF2256_ISR2);
pef2256_read8(pef2256, PEF2256_ISR3);
pef2256_read8(pef2256, PEF2256_ISR4);
pef2256_read8(pef2256, PEF2256_ISR5);
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
ret = devm_request_irq(pef2256->dev, irq, pef2256_irq_handler, 0, "pef2256", pef2256);
if (ret < 0)
return ret;
platform_set_drvdata(pdev, pef2256);
ret = devm_mfd_add_devices(pef2256->dev, 0, pef2256_devs,
ARRAY_SIZE(pef2256_devs), NULL, 0, NULL);
if (ret) {
dev_err(pef2256->dev, "add devices failed (%d)\n", ret);
return ret;
}
ret = pef2256_setup_e1(pef2256);
if (ret)
return ret;
framer_provider = devm_framer_provider_of_register(pef2256->dev,
framer_provider_simple_of_xlate);
if (IS_ERR(framer_provider))
return PTR_ERR(framer_provider);
/* Add audio devices */
ret = pef2256_add_audio_devices(pef2256);
if (ret < 0) {
dev_err(pef2256->dev, "add audio devices failed (%d)\n", ret);
return ret;
}
device_create_file(pef2256->dev, &dev_attr_version);
return 0;
}
static void pef2256_remove(struct platform_device *pdev)
{
struct pef2256 *pef2256 = platform_get_drvdata(pdev);
/* Disable interrupts */
pef2256_write8(pef2256, PEF2256_IMR0, 0xff);
pef2256_write8(pef2256, PEF2256_IMR1, 0xff);
pef2256_write8(pef2256, PEF2256_IMR2, 0xff);
pef2256_write8(pef2256, PEF2256_IMR3, 0xff);
pef2256_write8(pef2256, PEF2256_IMR4, 0xff);
pef2256_write8(pef2256, PEF2256_IMR5, 0xff);
device_remove_file(pef2256->dev, &dev_attr_version);
}
static const struct of_device_id pef2256_id_table[] = {
{ .compatible = "lantiq,pef2256" },
{} /* sentinel */
};
MODULE_DEVICE_TABLE(of, pef2256_id_table);
static struct platform_driver pef2256_driver = {
.driver = {
.name = "lantiq-pef2256",
.of_match_table = pef2256_id_table,
},
.probe = pef2256_probe,
.remove = pef2256_remove,
};
module_platform_driver(pef2256_driver);
struct regmap *pef2256_get_regmap(struct pef2256 *pef2256)
{
return pef2256->regmap;
}
EXPORT_SYMBOL_GPL(pef2256_get_regmap);
MODULE_AUTHOR("Herve Codina <herve.codina@bootlin.com>");
MODULE_DESCRIPTION("PEF2256 driver");
MODULE_LICENSE("GPL");
+807
View File
@@ -0,0 +1,807 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Freescale QMC HDLC Device Driver
*
* Copyright 2023 CS GROUP France
*
* Author: Herve Codina <herve.codina@bootlin.com>
*/
#include <linux/array_size.h>
#include <linux/bug.h>
#include <linux/cleanup.h>
#include <linux/bitmap.h>
#include <linux/dma-mapping.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/framer/framer.h>
#include <linux/hdlc.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/types.h>
#include <soc/fsl/qe/qmc.h>
struct qmc_hdlc_desc {
struct net_device *netdev;
struct sk_buff *skb; /* NULL if the descriptor is not in use */
dma_addr_t dma_addr;
size_t dma_size;
};
struct qmc_hdlc {
struct device *dev;
struct qmc_chan *qmc_chan;
struct net_device *netdev;
struct framer *framer;
struct mutex carrier_lock; /* Protect carrier detection */
struct notifier_block nb;
bool is_crc32;
spinlock_t tx_lock; /* Protect tx descriptors */
struct qmc_hdlc_desc tx_descs[8];
unsigned int tx_out;
struct qmc_hdlc_desc rx_descs[4];
u32 slot_map;
};
static struct qmc_hdlc *netdev_to_qmc_hdlc(struct net_device *netdev)
{
return dev_to_hdlc(netdev)->priv;
}
static int qmc_hdlc_framer_set_carrier(struct qmc_hdlc *qmc_hdlc)
{
struct framer_status framer_status;
int ret;
if (!qmc_hdlc->framer)
return 0;
guard(mutex)(&qmc_hdlc->carrier_lock);
ret = framer_get_status(qmc_hdlc->framer, &framer_status);
if (ret) {
dev_err(qmc_hdlc->dev, "get framer status failed (%d)\n", ret);
return ret;
}
if (framer_status.link_is_on)
netif_carrier_on(qmc_hdlc->netdev);
else
netif_carrier_off(qmc_hdlc->netdev);
return 0;
}
static int qmc_hdlc_framer_notifier(struct notifier_block *nb, unsigned long action,
void *data)
{
struct qmc_hdlc *qmc_hdlc = container_of(nb, struct qmc_hdlc, nb);
int ret;
if (action != FRAMER_EVENT_STATUS)
return NOTIFY_DONE;
ret = qmc_hdlc_framer_set_carrier(qmc_hdlc);
return ret ? NOTIFY_DONE : NOTIFY_OK;
}
static int qmc_hdlc_framer_start(struct qmc_hdlc *qmc_hdlc)
{
struct framer_status framer_status;
int ret;
if (!qmc_hdlc->framer)
return 0;
ret = framer_power_on(qmc_hdlc->framer);
if (ret) {
dev_err(qmc_hdlc->dev, "framer power-on failed (%d)\n", ret);
return ret;
}
/* Be sure that get_status is supported */
ret = framer_get_status(qmc_hdlc->framer, &framer_status);
if (ret) {
dev_err(qmc_hdlc->dev, "get framer status failed (%d)\n", ret);
goto framer_power_off;
}
qmc_hdlc->nb.notifier_call = qmc_hdlc_framer_notifier;
ret = framer_notifier_register(qmc_hdlc->framer, &qmc_hdlc->nb);
if (ret) {
dev_err(qmc_hdlc->dev, "framer notifier register failed (%d)\n", ret);
goto framer_power_off;
}
return 0;
framer_power_off:
framer_power_off(qmc_hdlc->framer);
return ret;
}
static void qmc_hdlc_framer_stop(struct qmc_hdlc *qmc_hdlc)
{
if (!qmc_hdlc->framer)
return;
framer_notifier_unregister(qmc_hdlc->framer, &qmc_hdlc->nb);
framer_power_off(qmc_hdlc->framer);
}
static int qmc_hdlc_framer_set_iface(struct qmc_hdlc *qmc_hdlc, int if_iface,
const te1_settings *te1)
{
struct framer_config config;
int ret;
if (!qmc_hdlc->framer)
return 0;
ret = framer_get_config(qmc_hdlc->framer, &config);
if (ret)
return ret;
switch (if_iface) {
case IF_IFACE_E1:
config.iface = FRAMER_IFACE_E1;
break;
case IF_IFACE_T1:
config.iface = FRAMER_IFACE_T1;
break;
default:
return -EINVAL;
}
switch (te1->clock_type) {
case CLOCK_DEFAULT:
/* Keep current value */
break;
case CLOCK_EXT:
config.clock_type = FRAMER_CLOCK_EXT;
break;
case CLOCK_INT:
config.clock_type = FRAMER_CLOCK_INT;
break;
default:
return -EINVAL;
}
config.line_clock_rate = te1->clock_rate;
return framer_set_config(qmc_hdlc->framer, &config);
}
static int qmc_hdlc_framer_get_iface(struct qmc_hdlc *qmc_hdlc, int *if_iface, te1_settings *te1)
{
struct framer_config config;
int ret;
if (!qmc_hdlc->framer) {
*if_iface = IF_IFACE_E1;
return 0;
}
ret = framer_get_config(qmc_hdlc->framer, &config);
if (ret)
return ret;
switch (config.iface) {
case FRAMER_IFACE_E1:
*if_iface = IF_IFACE_E1;
break;
case FRAMER_IFACE_T1:
*if_iface = IF_IFACE_T1;
break;
}
if (!te1)
return 0; /* Only iface type requested */
switch (config.clock_type) {
case FRAMER_CLOCK_EXT:
te1->clock_type = CLOCK_EXT;
break;
case FRAMER_CLOCK_INT:
te1->clock_type = CLOCK_INT;
break;
default:
return -EINVAL;
}
te1->clock_rate = config.line_clock_rate;
return 0;
}
static int qmc_hdlc_framer_init(struct qmc_hdlc *qmc_hdlc)
{
int ret;
if (!qmc_hdlc->framer)
return 0;
ret = framer_init(qmc_hdlc->framer);
if (ret) {
dev_err(qmc_hdlc->dev, "framer init failed (%d)\n", ret);
return ret;
}
return 0;
}
static void qmc_hdlc_framer_exit(struct qmc_hdlc *qmc_hdlc)
{
if (!qmc_hdlc->framer)
return;
framer_exit(qmc_hdlc->framer);
}
static int qmc_hdlc_recv_queue(struct qmc_hdlc *qmc_hdlc, struct qmc_hdlc_desc *desc, size_t size);
#define QMC_HDLC_RX_ERROR_FLAGS \
(QMC_RX_FLAG_HDLC_OVF | QMC_RX_FLAG_HDLC_UNA | \
QMC_RX_FLAG_HDLC_CRC | QMC_RX_FLAG_HDLC_ABORT)
static void qmc_hcld_recv_complete(void *context, size_t length, unsigned int flags)
{
struct qmc_hdlc_desc *desc = context;
struct net_device *netdev;
struct qmc_hdlc *qmc_hdlc;
size_t crc_size;
int ret;
netdev = desc->netdev;
qmc_hdlc = netdev_to_qmc_hdlc(netdev);
dma_unmap_single(qmc_hdlc->dev, desc->dma_addr, desc->dma_size, DMA_FROM_DEVICE);
if (flags & QMC_HDLC_RX_ERROR_FLAGS) {
netdev->stats.rx_errors++;
if (flags & QMC_RX_FLAG_HDLC_OVF) /* Data overflow */
netdev->stats.rx_over_errors++;
if (flags & QMC_RX_FLAG_HDLC_UNA) /* bits received not multiple of 8 */
netdev->stats.rx_frame_errors++;
if (flags & QMC_RX_FLAG_HDLC_ABORT) /* Received an abort sequence */
netdev->stats.rx_frame_errors++;
if (flags & QMC_RX_FLAG_HDLC_CRC) /* CRC error */
netdev->stats.rx_crc_errors++;
kfree_skb(desc->skb);
goto re_queue;
}
/* Discard the CRC */
crc_size = qmc_hdlc->is_crc32 ? 4 : 2;
if (length < crc_size) {
netdev->stats.rx_length_errors++;
kfree_skb(desc->skb);
goto re_queue;
}
length -= crc_size;
netdev->stats.rx_packets++;
netdev->stats.rx_bytes += length;
skb_put(desc->skb, length);
desc->skb->protocol = hdlc_type_trans(desc->skb, netdev);
netif_rx(desc->skb);
re_queue:
/* Re-queue a transfer using the same descriptor */
ret = qmc_hdlc_recv_queue(qmc_hdlc, desc, desc->dma_size);
if (ret) {
dev_err(qmc_hdlc->dev, "queue recv desc failed (%d)\n", ret);
netdev->stats.rx_errors++;
}
}
static int qmc_hdlc_recv_queue(struct qmc_hdlc *qmc_hdlc, struct qmc_hdlc_desc *desc, size_t size)
{
int ret;
desc->skb = dev_alloc_skb(size);
if (!desc->skb)
return -ENOMEM;
desc->dma_size = size;
desc->dma_addr = dma_map_single(qmc_hdlc->dev, desc->skb->data,
desc->dma_size, DMA_FROM_DEVICE);
ret = dma_mapping_error(qmc_hdlc->dev, desc->dma_addr);
if (ret)
goto free_skb;
ret = qmc_chan_read_submit(qmc_hdlc->qmc_chan, desc->dma_addr, desc->dma_size,
qmc_hcld_recv_complete, desc);
if (ret)
goto dma_unmap;
return 0;
dma_unmap:
dma_unmap_single(qmc_hdlc->dev, desc->dma_addr, desc->dma_size, DMA_FROM_DEVICE);
free_skb:
kfree_skb(desc->skb);
desc->skb = NULL;
return ret;
}
static void qmc_hdlc_xmit_complete(void *context)
{
struct qmc_hdlc_desc *desc = context;
struct net_device *netdev;
struct qmc_hdlc *qmc_hdlc;
struct sk_buff *skb;
netdev = desc->netdev;
qmc_hdlc = netdev_to_qmc_hdlc(netdev);
scoped_guard(spinlock_irqsave, &qmc_hdlc->tx_lock) {
dma_unmap_single(qmc_hdlc->dev, desc->dma_addr, desc->dma_size, DMA_TO_DEVICE);
skb = desc->skb;
desc->skb = NULL; /* Release the descriptor */
if (netif_queue_stopped(netdev))
netif_wake_queue(netdev);
}
netdev->stats.tx_packets++;
netdev->stats.tx_bytes += skb->len;
dev_consume_skb_any(skb);
}
static int qmc_hdlc_xmit_queue(struct qmc_hdlc *qmc_hdlc, struct qmc_hdlc_desc *desc)
{
int ret;
desc->dma_addr = dma_map_single(qmc_hdlc->dev, desc->skb->data,
desc->dma_size, DMA_TO_DEVICE);
ret = dma_mapping_error(qmc_hdlc->dev, desc->dma_addr);
if (ret) {
dev_err(qmc_hdlc->dev, "failed to map skb\n");
return ret;
}
ret = qmc_chan_write_submit(qmc_hdlc->qmc_chan, desc->dma_addr, desc->dma_size,
qmc_hdlc_xmit_complete, desc);
if (ret) {
dma_unmap_single(qmc_hdlc->dev, desc->dma_addr, desc->dma_size, DMA_TO_DEVICE);
dev_err(qmc_hdlc->dev, "qmc chan write returns %d\n", ret);
return ret;
}
return 0;
}
static netdev_tx_t qmc_hdlc_xmit(struct sk_buff *skb, struct net_device *netdev)
{
struct qmc_hdlc *qmc_hdlc = netdev_to_qmc_hdlc(netdev);
struct qmc_hdlc_desc *desc;
int err;
guard(spinlock_irqsave)(&qmc_hdlc->tx_lock);
desc = &qmc_hdlc->tx_descs[qmc_hdlc->tx_out];
if (WARN_ONCE(desc->skb, "No tx descriptors available\n")) {
/* Should never happen.
* Previous xmit should have already stopped the queue.
*/
netif_stop_queue(netdev);
return NETDEV_TX_BUSY;
}
desc->netdev = netdev;
desc->dma_size = skb->len;
desc->skb = skb;
err = qmc_hdlc_xmit_queue(qmc_hdlc, desc);
if (err) {
desc->skb = NULL; /* Release the descriptor */
if (err == -EBUSY) {
netif_stop_queue(netdev);
return NETDEV_TX_BUSY;
}
dev_kfree_skb(skb);
netdev->stats.tx_dropped++;
return NETDEV_TX_OK;
}
qmc_hdlc->tx_out = (qmc_hdlc->tx_out + 1) % ARRAY_SIZE(qmc_hdlc->tx_descs);
if (qmc_hdlc->tx_descs[qmc_hdlc->tx_out].skb)
netif_stop_queue(netdev);
return NETDEV_TX_OK;
}
static int qmc_hdlc_xlate_slot_map(struct qmc_hdlc *qmc_hdlc,
u32 slot_map, struct qmc_chan_ts_info *ts_info)
{
DECLARE_BITMAP(ts_mask_avail, 64);
DECLARE_BITMAP(ts_mask, 64);
DECLARE_BITMAP(map, 64);
/* Tx and Rx available masks must be identical */
if (ts_info->rx_ts_mask_avail != ts_info->tx_ts_mask_avail) {
dev_err(qmc_hdlc->dev, "tx and rx available timeslots mismatch (0x%llx, 0x%llx)\n",
ts_info->rx_ts_mask_avail, ts_info->tx_ts_mask_avail);
return -EINVAL;
}
bitmap_from_u64(ts_mask_avail, ts_info->rx_ts_mask_avail);
bitmap_from_u64(map, slot_map);
bitmap_scatter(ts_mask, map, ts_mask_avail, 64);
if (bitmap_weight(ts_mask, 64) != bitmap_weight(map, 64)) {
dev_err(qmc_hdlc->dev, "Cannot translate timeslots %64pb -> (%64pb, %64pb)\n",
map, ts_mask_avail, ts_mask);
return -EINVAL;
}
bitmap_to_arr64(&ts_info->tx_ts_mask, ts_mask, 64);
ts_info->rx_ts_mask = ts_info->tx_ts_mask;
return 0;
}
static int qmc_hdlc_xlate_ts_info(struct qmc_hdlc *qmc_hdlc,
const struct qmc_chan_ts_info *ts_info, u32 *slot_map)
{
DECLARE_BITMAP(ts_mask_avail, 64);
DECLARE_BITMAP(ts_mask, 64);
DECLARE_BITMAP(map, 64);
u32 slot_array[2];
/* Tx and Rx masks and available masks must be identical */
if (ts_info->rx_ts_mask_avail != ts_info->tx_ts_mask_avail) {
dev_err(qmc_hdlc->dev, "tx and rx available timeslots mismatch (0x%llx, 0x%llx)\n",
ts_info->rx_ts_mask_avail, ts_info->tx_ts_mask_avail);
return -EINVAL;
}
if (ts_info->rx_ts_mask != ts_info->tx_ts_mask) {
dev_err(qmc_hdlc->dev, "tx and rx timeslots mismatch (0x%llx, 0x%llx)\n",
ts_info->rx_ts_mask, ts_info->tx_ts_mask);
return -EINVAL;
}
bitmap_from_u64(ts_mask_avail, ts_info->rx_ts_mask_avail);
bitmap_from_u64(ts_mask, ts_info->rx_ts_mask);
bitmap_gather(map, ts_mask, ts_mask_avail, 64);
if (bitmap_weight(ts_mask, 64) != bitmap_weight(map, 64)) {
dev_err(qmc_hdlc->dev, "Cannot translate timeslots (%64pb, %64pb) -> %64pb\n",
ts_mask_avail, ts_mask, map);
return -EINVAL;
}
bitmap_to_arr32(slot_array, map, 64);
if (slot_array[1]) {
dev_err(qmc_hdlc->dev, "Slot map out of 32bit (%64pb, %64pb) -> %64pb\n",
ts_mask_avail, ts_mask, map);
return -EINVAL;
}
*slot_map = slot_array[0];
return 0;
}
static int qmc_hdlc_set_iface(struct qmc_hdlc *qmc_hdlc, int if_iface, const te1_settings *te1)
{
struct qmc_chan_ts_info ts_info;
int ret;
ret = qmc_chan_get_ts_info(qmc_hdlc->qmc_chan, &ts_info);
if (ret) {
dev_err(qmc_hdlc->dev, "get QMC channel ts info failed %d\n", ret);
return ret;
}
ret = qmc_hdlc_xlate_slot_map(qmc_hdlc, te1->slot_map, &ts_info);
if (ret)
return ret;
ret = qmc_chan_set_ts_info(qmc_hdlc->qmc_chan, &ts_info);
if (ret) {
dev_err(qmc_hdlc->dev, "set QMC channel ts info failed %d\n", ret);
return ret;
}
qmc_hdlc->slot_map = te1->slot_map;
ret = qmc_hdlc_framer_set_iface(qmc_hdlc, if_iface, te1);
if (ret) {
dev_err(qmc_hdlc->dev, "framer set iface failed %d\n", ret);
return ret;
}
return 0;
}
static int qmc_hdlc_ioctl(struct net_device *netdev, struct if_settings *ifs)
{
struct qmc_hdlc *qmc_hdlc = netdev_to_qmc_hdlc(netdev);
te1_settings te1;
int ret;
switch (ifs->type) {
case IF_GET_IFACE:
if (ifs->size < sizeof(te1)) {
/* Retrieve type only */
ret = qmc_hdlc_framer_get_iface(qmc_hdlc, &ifs->type, NULL);
if (ret)
return ret;
if (!ifs->size)
return 0; /* only type requested */
ifs->size = sizeof(te1); /* data size wanted */
return -ENOBUFS;
}
memset(&te1, 0, sizeof(te1));
/* Retrieve info from framer */
ret = qmc_hdlc_framer_get_iface(qmc_hdlc, &ifs->type, &te1);
if (ret)
return ret;
/* Update slot_map */
te1.slot_map = qmc_hdlc->slot_map;
if (copy_to_user(ifs->ifs_ifsu.te1, &te1, sizeof(te1)))
return -EFAULT;
return 0;
case IF_IFACE_E1:
case IF_IFACE_T1:
if (!capable(CAP_NET_ADMIN))
return -EPERM;
if (netdev->flags & IFF_UP)
return -EBUSY;
if (copy_from_user(&te1, ifs->ifs_ifsu.te1, sizeof(te1)))
return -EFAULT;
return qmc_hdlc_set_iface(qmc_hdlc, ifs->type, &te1);
default:
return hdlc_ioctl(netdev, ifs);
}
}
static int qmc_hdlc_open(struct net_device *netdev)
{
struct qmc_hdlc *qmc_hdlc = netdev_to_qmc_hdlc(netdev);
struct qmc_chan_param chan_param;
struct qmc_hdlc_desc *desc;
int ret;
int i;
ret = qmc_hdlc_framer_start(qmc_hdlc);
if (ret)
return ret;
ret = hdlc_open(netdev);
if (ret)
goto framer_stop;
/* Update carrier */
qmc_hdlc_framer_set_carrier(qmc_hdlc);
chan_param.mode = QMC_HDLC;
/* HDLC_MAX_MRU + 4 for the CRC
* HDLC_MAX_MRU + 4 + 8 for the CRC and some extraspace needed by the QMC
*/
chan_param.hdlc.max_rx_buf_size = HDLC_MAX_MRU + 4 + 8;
chan_param.hdlc.max_rx_frame_size = HDLC_MAX_MRU + 4;
chan_param.hdlc.is_crc32 = qmc_hdlc->is_crc32;
ret = qmc_chan_set_param(qmc_hdlc->qmc_chan, &chan_param);
if (ret) {
dev_err(qmc_hdlc->dev, "failed to set param (%d)\n", ret);
goto hdlc_close;
}
/* Queue as many recv descriptors as possible */
for (i = 0; i < ARRAY_SIZE(qmc_hdlc->rx_descs); i++) {
desc = &qmc_hdlc->rx_descs[i];
desc->netdev = netdev;
ret = qmc_hdlc_recv_queue(qmc_hdlc, desc, chan_param.hdlc.max_rx_buf_size);
if (ret == -EBUSY && i != 0)
break; /* We use all the QMC chan capability */
if (ret)
goto free_desc;
}
ret = qmc_chan_start(qmc_hdlc->qmc_chan, QMC_CHAN_ALL);
if (ret) {
dev_err(qmc_hdlc->dev, "qmc chan start failed (%d)\n", ret);
goto free_desc;
}
netif_start_queue(netdev);
return 0;
free_desc:
qmc_chan_reset(qmc_hdlc->qmc_chan, QMC_CHAN_ALL);
while (i--) {
desc = &qmc_hdlc->rx_descs[i];
dma_unmap_single(qmc_hdlc->dev, desc->dma_addr, desc->dma_size,
DMA_FROM_DEVICE);
kfree_skb(desc->skb);
desc->skb = NULL;
}
hdlc_close:
hdlc_close(netdev);
framer_stop:
qmc_hdlc_framer_stop(qmc_hdlc);
return ret;
}
static int qmc_hdlc_close(struct net_device *netdev)
{
struct qmc_hdlc *qmc_hdlc = netdev_to_qmc_hdlc(netdev);
struct qmc_hdlc_desc *desc;
int i;
qmc_chan_stop(qmc_hdlc->qmc_chan, QMC_CHAN_ALL);
qmc_chan_reset(qmc_hdlc->qmc_chan, QMC_CHAN_ALL);
netif_stop_queue(netdev);
for (i = 0; i < ARRAY_SIZE(qmc_hdlc->tx_descs); i++) {
desc = &qmc_hdlc->tx_descs[i];
if (!desc->skb)
continue;
dma_unmap_single(qmc_hdlc->dev, desc->dma_addr, desc->dma_size,
DMA_TO_DEVICE);
kfree_skb(desc->skb);
desc->skb = NULL;
}
for (i = 0; i < ARRAY_SIZE(qmc_hdlc->rx_descs); i++) {
desc = &qmc_hdlc->rx_descs[i];
if (!desc->skb)
continue;
dma_unmap_single(qmc_hdlc->dev, desc->dma_addr, desc->dma_size,
DMA_FROM_DEVICE);
kfree_skb(desc->skb);
desc->skb = NULL;
}
hdlc_close(netdev);
qmc_hdlc_framer_stop(qmc_hdlc);
return 0;
}
static int qmc_hdlc_attach(struct net_device *netdev, unsigned short encoding,
unsigned short parity)
{
struct qmc_hdlc *qmc_hdlc = netdev_to_qmc_hdlc(netdev);
if (encoding != ENCODING_NRZ)
return -EINVAL;
switch (parity) {
case PARITY_CRC16_PR1_CCITT:
qmc_hdlc->is_crc32 = false;
break;
case PARITY_CRC32_PR1_CCITT:
qmc_hdlc->is_crc32 = true;
break;
default:
dev_err(qmc_hdlc->dev, "unsupported parity %u\n", parity);
return -EINVAL;
}
return 0;
}
static const struct net_device_ops qmc_hdlc_netdev_ops = {
.ndo_open = qmc_hdlc_open,
.ndo_stop = qmc_hdlc_close,
.ndo_start_xmit = hdlc_start_xmit,
.ndo_siocwandev = qmc_hdlc_ioctl,
};
static int qmc_hdlc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct qmc_chan_ts_info ts_info;
struct qmc_hdlc *qmc_hdlc;
struct qmc_chan_info info;
hdlc_device *hdlc;
int ret;
qmc_hdlc = devm_kzalloc(dev, sizeof(*qmc_hdlc), GFP_KERNEL);
if (!qmc_hdlc)
return -ENOMEM;
qmc_hdlc->dev = dev;
spin_lock_init(&qmc_hdlc->tx_lock);
mutex_init(&qmc_hdlc->carrier_lock);
qmc_hdlc->qmc_chan = devm_qmc_chan_get_bychild(dev, dev->of_node);
if (IS_ERR(qmc_hdlc->qmc_chan))
return dev_err_probe(dev, PTR_ERR(qmc_hdlc->qmc_chan),
"get QMC channel failed\n");
ret = qmc_chan_get_info(qmc_hdlc->qmc_chan, &info);
if (ret)
return dev_err_probe(dev, ret, "get QMC channel info failed\n");
if (info.mode != QMC_HDLC)
return dev_err_probe(dev, -EINVAL, "QMC chan mode %d is not QMC_HDLC\n",
info.mode);
ret = qmc_chan_get_ts_info(qmc_hdlc->qmc_chan, &ts_info);
if (ret)
return dev_err_probe(dev, ret, "get QMC channel ts info failed\n");
ret = qmc_hdlc_xlate_ts_info(qmc_hdlc, &ts_info, &qmc_hdlc->slot_map);
if (ret)
return ret;
qmc_hdlc->framer = devm_framer_optional_get(dev, "fsl,framer");
if (IS_ERR(qmc_hdlc->framer))
return PTR_ERR(qmc_hdlc->framer);
ret = qmc_hdlc_framer_init(qmc_hdlc);
if (ret)
return ret;
qmc_hdlc->netdev = alloc_hdlcdev(qmc_hdlc);
if (!qmc_hdlc->netdev) {
ret = -ENOMEM;
goto framer_exit;
}
hdlc = dev_to_hdlc(qmc_hdlc->netdev);
hdlc->attach = qmc_hdlc_attach;
hdlc->xmit = qmc_hdlc_xmit;
SET_NETDEV_DEV(qmc_hdlc->netdev, dev);
qmc_hdlc->netdev->tx_queue_len = ARRAY_SIZE(qmc_hdlc->tx_descs);
qmc_hdlc->netdev->netdev_ops = &qmc_hdlc_netdev_ops;
ret = register_hdlc_device(qmc_hdlc->netdev);
if (ret) {
dev_err_probe(dev, ret, "failed to register hdlc device\n");
goto free_netdev;
}
platform_set_drvdata(pdev, qmc_hdlc);
return 0;
free_netdev:
free_netdev(qmc_hdlc->netdev);
framer_exit:
qmc_hdlc_framer_exit(qmc_hdlc);
return ret;
}
static void qmc_hdlc_remove(struct platform_device *pdev)
{
struct qmc_hdlc *qmc_hdlc = platform_get_drvdata(pdev);
unregister_hdlc_device(qmc_hdlc->netdev);
free_netdev(qmc_hdlc->netdev);
qmc_hdlc_framer_exit(qmc_hdlc);
}
static const struct of_device_id qmc_hdlc_id_table[] = {
{ .compatible = "fsl,qmc-hdlc" },
{} /* sentinel */
};
MODULE_DEVICE_TABLE(of, qmc_hdlc_id_table);
static struct platform_driver qmc_hdlc_driver = {
.driver = {
.name = "fsl-qmc-hdlc",
.of_match_table = qmc_hdlc_id_table,
},
.probe = qmc_hdlc_probe,
.remove = qmc_hdlc_remove,
};
module_platform_driver(qmc_hdlc_driver);
MODULE_AUTHOR("Herve Codina <herve.codina@bootlin.com>");
MODULE_DESCRIPTION("QMC HDLC driver");
MODULE_LICENSE("GPL");
File diff suppressed because it is too large Load Diff
+145
View File
@@ -0,0 +1,145 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/* Freescale QUICC Engine HDLC Device Driver
*
* Copyright 2014 Freescale Semiconductor Inc.
*/
#ifndef _UCC_HDLC_H_
#define _UCC_HDLC_H_
#include <linux/kernel.h>
#include <linux/list.h>
#include <soc/fsl/qe/immap_qe.h>
#include <soc/fsl/qe/qe.h>
#include <soc/fsl/qe/ucc.h>
#include <soc/fsl/qe/ucc_fast.h>
/* UCC HDLC event register */
#define UCCE_HDLC_RX_EVENTS \
(UCC_HDLC_UCCE_RXF | UCC_HDLC_UCCE_RXB | UCC_HDLC_UCCE_BSY)
#define UCCE_HDLC_TX_EVENTS (UCC_HDLC_UCCE_TXB | UCC_HDLC_UCCE_TXE)
struct ucc_hdlc_param {
__be16 riptr;
__be16 tiptr;
__be16 res0;
__be16 mrblr;
__be32 rstate;
__be32 rbase;
__be16 rbdstat;
__be16 rbdlen;
__be32 rdptr;
__be32 tstate;
__be32 tbase;
__be16 tbdstat;
__be16 tbdlen;
__be32 tdptr;
__be32 rbptr;
__be32 tbptr;
__be32 rcrc;
__be32 res1;
__be32 tcrc;
__be32 res2;
__be32 res3;
__be32 c_mask;
__be32 c_pres;
__be16 disfc;
__be16 crcec;
__be16 abtsc;
__be16 nmarc;
__be32 max_cnt;
__be16 mflr;
__be16 rfthr;
__be16 rfcnt;
__be16 hmask;
__be16 haddr1;
__be16 haddr2;
__be16 haddr3;
__be16 haddr4;
__be16 ts_tmp;
__be16 tmp_mb;
};
struct ucc_hdlc_private {
struct ucc_tdm *utdm;
struct ucc_tdm_info *ut_info;
struct ucc_fast_private *uccf;
struct device *dev;
struct net_device *ndev;
struct napi_struct napi;
struct ucc_fast __iomem *uf_regs; /* UCC Fast registers */
struct ucc_hdlc_param __iomem *ucc_pram;
u16 tsa;
bool hdlc_busy;
bool loopback;
bool hdlc_bus;
u8 *tx_buffer;
u8 *rx_buffer;
dma_addr_t dma_tx_addr;
dma_addr_t dma_rx_addr;
struct qe_bd *tx_bd_base;
struct qe_bd *rx_bd_base;
dma_addr_t dma_tx_bd;
dma_addr_t dma_rx_bd;
struct qe_bd *curtx_bd;
struct qe_bd *currx_bd;
struct qe_bd *dirty_tx;
u16 currx_bdnum;
struct sk_buff **tx_skbuff;
struct sk_buff **rx_skbuff;
u16 skb_curtx;
u16 skb_currx;
unsigned short skb_dirtytx;
unsigned short tx_ring_size;
unsigned short rx_ring_size;
s32 ucc_pram_offset;
unsigned short encoding;
unsigned short parity;
unsigned short hmask;
u32 clocking;
spinlock_t lock; /* lock for Tx BD and Tx buffer */
#ifdef CONFIG_PM
struct ucc_hdlc_param *ucc_pram_bak;
u32 gumr;
u8 guemr;
u32 cmxsi1cr_l, cmxsi1cr_h;
u32 cmxsi1syr;
u32 cmxucr[4];
#endif
};
#define TX_BD_RING_LEN 0x10
#define RX_BD_RING_LEN 0x20
#define RX_CLEAN_MAX 0x10
#define NUM_OF_BUF 4
#define MAX_RX_BUF_LENGTH (48 * 0x20)
#define MAX_FRAME_LENGTH (MAX_RX_BUF_LENGTH + 8)
#define ALIGNMENT_OF_UCC_HDLC_PRAM 64
#define SI_BANK_SIZE 128
#define MAX_HDLC_NUM 4
#define HDLC_HEAD_LEN 2
#define HDLC_CRC_SIZE 2
#define TX_RING_MOD_MASK(size) (size - 1)
#define RX_RING_MOD_MASK(size) (size - 1)
#define HDLC_HEAD_MASK 0x0000
#define DEFAULT_HDLC_HEAD 0xff44
#define DEFAULT_ADDR_MASK 0x00ff
#define DEFAULT_HDLC_ADDR 0x00ff
#define BMR_GBL 0x20000000
#define BMR_BIG_ENDIAN 0x10000000
#define CRC_16BIT_MASK 0x0000F0B8
#define CRC_16BIT_PRES 0x0000FFFF
#define DEFAULT_RFTHR 1
#define DEFAULT_PPP_HEAD 0xff03
#endif
+724
View File
@@ -0,0 +1,724 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Hitachi SCA HD64570 driver for Linux
*
* Copyright (C) 1998-2003 Krzysztof Halasa <khc@pm.waw.pl>
*
* Source of information: Hitachi HD64570 SCA User's Manual
*
* We use the following SCA memory map:
*
* Packet buffer descriptor rings - starting from winbase or win0base:
* rx_ring_buffers * sizeof(pkt_desc) = logical channel #0 RX ring
* tx_ring_buffers * sizeof(pkt_desc) = logical channel #0 TX ring
* rx_ring_buffers * sizeof(pkt_desc) = logical channel #1 RX ring (if used)
* tx_ring_buffers * sizeof(pkt_desc) = logical channel #1 TX ring (if used)
*
* Packet data buffers - starting from winbase + buff_offset:
* rx_ring_buffers * HDLC_MAX_MRU = logical channel #0 RX buffers
* tx_ring_buffers * HDLC_MAX_MRU = logical channel #0 TX buffers
* rx_ring_buffers * HDLC_MAX_MRU = logical channel #0 RX buffers (if used)
* tx_ring_buffers * HDLC_MAX_MRU = logical channel #0 TX buffers (if used)
*/
#include <linux/bitops.h>
#include <linux/errno.h>
#include <linux/fcntl.h>
#include <linux/hdlc.h>
#include <linux/in.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/skbuff.h>
#include <linux/string.h>
#include <linux/types.h>
#include <asm/io.h>
#include <linux/uaccess.h>
#include "hd64570.h"
#define get_msci(port) (phy_node(port) ? MSCI1_OFFSET : MSCI0_OFFSET)
#define get_dmac_rx(port) (phy_node(port) ? DMAC1RX_OFFSET : DMAC0RX_OFFSET)
#define get_dmac_tx(port) (phy_node(port) ? DMAC1TX_OFFSET : DMAC0TX_OFFSET)
#define SCA_INTR_MSCI(node) (node ? 0x10 : 0x01)
#define SCA_INTR_DMAC_RX(node) (node ? 0x20 : 0x02)
#define SCA_INTR_DMAC_TX(node) (node ? 0x40 : 0x04)
static inline struct net_device *port_to_dev(port_t *port)
{
return port->dev;
}
static inline int sca_intr_status(card_t *card)
{
u8 result = 0;
u8 isr0 = sca_in(ISR0, card);
u8 isr1 = sca_in(ISR1, card);
if (isr1 & 0x03)
result |= SCA_INTR_DMAC_RX(0);
if (isr1 & 0x0C)
result |= SCA_INTR_DMAC_TX(0);
if (isr1 & 0x30)
result |= SCA_INTR_DMAC_RX(1);
if (isr1 & 0xC0)
result |= SCA_INTR_DMAC_TX(1);
if (isr0 & 0x0F)
result |= SCA_INTR_MSCI(0);
if (isr0 & 0xF0)
result |= SCA_INTR_MSCI(1);
if (!(result & SCA_INTR_DMAC_TX(0)))
if (sca_in(DSR_TX(0), card) & DSR_EOM)
result |= SCA_INTR_DMAC_TX(0);
if (!(result & SCA_INTR_DMAC_TX(1)))
if (sca_in(DSR_TX(1), card) & DSR_EOM)
result |= SCA_INTR_DMAC_TX(1);
return result;
}
static inline port_t *dev_to_port(struct net_device *dev)
{
return dev_to_hdlc(dev)->priv;
}
static inline u16 next_desc(port_t *port, u16 desc, int transmit)
{
return (desc + 1) % (transmit ? port_to_card(port)->tx_ring_buffers
: port_to_card(port)->rx_ring_buffers);
}
static inline u16 desc_abs_number(port_t *port, u16 desc, int transmit)
{
u16 rx_buffs = port_to_card(port)->rx_ring_buffers;
u16 tx_buffs = port_to_card(port)->tx_ring_buffers;
desc %= (transmit ? tx_buffs : rx_buffs); // called with "X + 1" etc.
return log_node(port) * (rx_buffs + tx_buffs) +
transmit * rx_buffs + desc;
}
static inline u16 desc_offset(port_t *port, u16 desc, int transmit)
{
/* Descriptor offset always fits in 16 bits */
return desc_abs_number(port, desc, transmit) * sizeof(pkt_desc);
}
static inline pkt_desc __iomem *desc_address(port_t *port, u16 desc,
int transmit)
{
#ifdef PAGE0_ALWAYS_MAPPED
return (pkt_desc __iomem *)(win0base(port_to_card(port))
+ desc_offset(port, desc, transmit));
#else
return (pkt_desc __iomem *)(winbase(port_to_card(port))
+ desc_offset(port, desc, transmit));
#endif
}
static inline u32 buffer_offset(port_t *port, u16 desc, int transmit)
{
return port_to_card(port)->buff_offset +
desc_abs_number(port, desc, transmit) * (u32)HDLC_MAX_MRU;
}
static inline void sca_set_carrier(port_t *port)
{
if (!(sca_in(get_msci(port) + ST3, port_to_card(port)) & ST3_DCD)) {
#ifdef DEBUG_LINK
printk(KERN_DEBUG "%s: sca_set_carrier on\n",
port_to_dev(port)->name);
#endif
netif_carrier_on(port_to_dev(port));
} else {
#ifdef DEBUG_LINK
printk(KERN_DEBUG "%s: sca_set_carrier off\n",
port_to_dev(port)->name);
#endif
netif_carrier_off(port_to_dev(port));
}
}
static void sca_init_port(port_t *port)
{
card_t *card = port_to_card(port);
int transmit, i;
port->rxin = 0;
port->txin = 0;
port->txlast = 0;
#ifndef PAGE0_ALWAYS_MAPPED
openwin(card, 0);
#endif
for (transmit = 0; transmit < 2; transmit++) {
u16 dmac = transmit ? get_dmac_tx(port) : get_dmac_rx(port);
u16 buffs = transmit ? card->tx_ring_buffers
: card->rx_ring_buffers;
for (i = 0; i < buffs; i++) {
pkt_desc __iomem *desc = desc_address(port, i, transmit);
u16 chain_off = desc_offset(port, i + 1, transmit);
u32 buff_off = buffer_offset(port, i, transmit);
writew(chain_off, &desc->cp);
writel(buff_off, &desc->bp);
writew(0, &desc->len);
writeb(0, &desc->stat);
}
/* DMA disable - to halt state */
sca_out(0, transmit ? DSR_TX(phy_node(port)) :
DSR_RX(phy_node(port)), card);
/* software ABORT - to initial state */
sca_out(DCR_ABORT, transmit ? DCR_TX(phy_node(port)) :
DCR_RX(phy_node(port)), card);
/* current desc addr */
sca_out(0, dmac + CPB, card); /* pointer base */
sca_outw(desc_offset(port, 0, transmit), dmac + CDAL, card);
if (!transmit)
sca_outw(desc_offset(port, buffs - 1, transmit),
dmac + EDAL, card);
else
sca_outw(desc_offset(port, 0, transmit), dmac + EDAL,
card);
/* clear frame end interrupt counter */
sca_out(DCR_CLEAR_EOF, transmit ? DCR_TX(phy_node(port)) :
DCR_RX(phy_node(port)), card);
if (!transmit) { /* Receive */
/* set buffer length */
sca_outw(HDLC_MAX_MRU, dmac + BFLL, card);
/* Chain mode, Multi-frame */
sca_out(0x14, DMR_RX(phy_node(port)), card);
sca_out(DIR_EOME | DIR_BOFE, DIR_RX(phy_node(port)),
card);
/* DMA enable */
sca_out(DSR_DE, DSR_RX(phy_node(port)), card);
} else { /* Transmit */
/* Chain mode, Multi-frame */
sca_out(0x14, DMR_TX(phy_node(port)), card);
/* enable underflow interrupts */
sca_out(DIR_BOFE, DIR_TX(phy_node(port)), card);
}
}
sca_set_carrier(port);
}
#ifdef NEED_SCA_MSCI_INTR
/* MSCI interrupt service */
static inline void sca_msci_intr(port_t *port)
{
u16 msci = get_msci(port);
card_t *card = port_to_card(port);
u8 stat = sca_in(msci + ST1, card); /* read MSCI ST1 status */
/* Reset MSCI TX underrun and CDCD status bit */
sca_out(stat & (ST1_UDRN | ST1_CDCD), msci + ST1, card);
if (stat & ST1_UDRN) {
/* TX Underrun error detected */
port_to_dev(port)->stats.tx_errors++;
port_to_dev(port)->stats.tx_fifo_errors++;
}
if (stat & ST1_CDCD)
sca_set_carrier(port);
}
#endif
static inline void sca_rx(card_t *card, port_t *port, pkt_desc __iomem *desc,
u16 rxin)
{
struct net_device *dev = port_to_dev(port);
struct sk_buff *skb;
u16 len;
u32 buff;
u32 maxlen;
u8 page;
len = readw(&desc->len);
skb = dev_alloc_skb(len);
if (!skb) {
dev->stats.rx_dropped++;
return;
}
buff = buffer_offset(port, rxin, 0);
page = buff / winsize(card);
buff = buff % winsize(card);
maxlen = winsize(card) - buff;
openwin(card, page);
if (len > maxlen) {
memcpy_fromio(skb->data, winbase(card) + buff, maxlen);
openwin(card, page + 1);
memcpy_fromio(skb->data + maxlen, winbase(card), len - maxlen);
} else {
memcpy_fromio(skb->data, winbase(card) + buff, len);
}
#ifndef PAGE0_ALWAYS_MAPPED
openwin(card, 0); /* select pkt_desc table page back */
#endif
skb_put(skb, len);
#ifdef DEBUG_PKT
printk(KERN_DEBUG "%s RX(%i):", dev->name, skb->len);
debug_frame(skb);
#endif
dev->stats.rx_packets++;
dev->stats.rx_bytes += skb->len;
skb->protocol = hdlc_type_trans(skb, dev);
netif_rx(skb);
}
/* Receive DMA interrupt service */
static inline void sca_rx_intr(port_t *port)
{
struct net_device *dev = port_to_dev(port);
u16 dmac = get_dmac_rx(port);
card_t *card = port_to_card(port);
u8 stat = sca_in(DSR_RX(phy_node(port)), card); /* read DMA Status */
/* Reset DSR status bits */
sca_out((stat & (DSR_EOT | DSR_EOM | DSR_BOF | DSR_COF)) | DSR_DWE,
DSR_RX(phy_node(port)), card);
if (stat & DSR_BOF)
/* Dropped one or more frames */
dev->stats.rx_over_errors++;
while (1) {
u32 desc_off = desc_offset(port, port->rxin, 0);
pkt_desc __iomem *desc;
u32 cda = sca_inw(dmac + CDAL, card);
if (cda >= desc_off && (cda < desc_off + sizeof(pkt_desc)))
break; /* No frame received */
desc = desc_address(port, port->rxin, 0);
stat = readb(&desc->stat);
if (!(stat & ST_RX_EOM))
port->rxpart = 1; /* partial frame received */
else if ((stat & ST_ERROR_MASK) || port->rxpart) {
dev->stats.rx_errors++;
if (stat & ST_RX_OVERRUN)
dev->stats.rx_fifo_errors++;
else if ((stat & (ST_RX_SHORT | ST_RX_ABORT |
ST_RX_RESBIT)) || port->rxpart)
dev->stats.rx_frame_errors++;
else if (stat & ST_RX_CRC)
dev->stats.rx_crc_errors++;
if (stat & ST_RX_EOM)
port->rxpart = 0; /* received last fragment */
} else {
sca_rx(card, port, desc, port->rxin);
}
/* Set new error descriptor address */
sca_outw(desc_off, dmac + EDAL, card);
port->rxin = next_desc(port, port->rxin, 0);
}
/* make sure RX DMA is enabled */
sca_out(DSR_DE, DSR_RX(phy_node(port)), card);
}
/* Transmit DMA interrupt service */
static inline void sca_tx_intr(port_t *port)
{
struct net_device *dev = port_to_dev(port);
u16 dmac = get_dmac_tx(port);
card_t *card = port_to_card(port);
u8 stat;
spin_lock(&port->lock);
stat = sca_in(DSR_TX(phy_node(port)), card); /* read DMA Status */
/* Reset DSR status bits */
sca_out((stat & (DSR_EOT | DSR_EOM | DSR_BOF | DSR_COF)) | DSR_DWE,
DSR_TX(phy_node(port)), card);
while (1) {
pkt_desc __iomem *desc;
u32 desc_off = desc_offset(port, port->txlast, 1);
u32 cda = sca_inw(dmac + CDAL, card);
if (cda >= desc_off && (cda < desc_off + sizeof(pkt_desc)))
break; /* Transmitter is/will_be sending this frame */
desc = desc_address(port, port->txlast, 1);
dev->stats.tx_packets++;
dev->stats.tx_bytes += readw(&desc->len);
writeb(0, &desc->stat); /* Free descriptor */
port->txlast = next_desc(port, port->txlast, 1);
}
netif_wake_queue(dev);
spin_unlock(&port->lock);
}
static irqreturn_t sca_intr(int irq, void *dev_id)
{
card_t *card = dev_id;
int i;
u8 stat;
int handled = 0;
u8 page = sca_get_page(card);
while ((stat = sca_intr_status(card)) != 0) {
handled = 1;
for (i = 0; i < 2; i++) {
port_t *port = get_port(card, i);
if (port) {
if (stat & SCA_INTR_MSCI(i))
sca_msci_intr(port);
if (stat & SCA_INTR_DMAC_RX(i))
sca_rx_intr(port);
if (stat & SCA_INTR_DMAC_TX(i))
sca_tx_intr(port);
}
}
}
openwin(card, page); /* Restore original page */
return IRQ_RETVAL(handled);
}
static void sca_set_port(port_t *port)
{
card_t *card = port_to_card(port);
u16 msci = get_msci(port);
u8 md2 = sca_in(msci + MD2, card);
unsigned int tmc, br = 10, brv = 1024;
if (port->settings.clock_rate > 0) {
/* Try lower br for better accuracy*/
do {
br--;
brv >>= 1; /* brv = 2^9 = 512 max in specs */
/* Baud Rate = CLOCK_BASE / TMC / 2^BR */
tmc = CLOCK_BASE / brv / port->settings.clock_rate;
} while (br > 1 && tmc <= 128);
if (tmc < 1) {
tmc = 1;
br = 0; /* For baud=CLOCK_BASE we use tmc=1 br=0 */
brv = 1;
} else if (tmc > 255) {
tmc = 256; /* tmc=0 means 256 - low baud rates */
}
port->settings.clock_rate = CLOCK_BASE / brv / tmc;
} else {
br = 9; /* Minimum clock rate */
tmc = 256; /* 8bit = 0 */
port->settings.clock_rate = CLOCK_BASE / (256 * 512);
}
port->rxs = (port->rxs & ~CLK_BRG_MASK) | br;
port->txs = (port->txs & ~CLK_BRG_MASK) | br;
port->tmc = tmc;
/* baud divisor - time constant*/
sca_out(port->tmc, msci + TMC, card);
/* Set BRG bits */
sca_out(port->rxs, msci + RXS, card);
sca_out(port->txs, msci + TXS, card);
if (port->settings.loopback)
md2 |= MD2_LOOPBACK;
else
md2 &= ~MD2_LOOPBACK;
sca_out(md2, msci + MD2, card);
}
static void sca_open(struct net_device *dev)
{
port_t *port = dev_to_port(dev);
card_t *card = port_to_card(port);
u16 msci = get_msci(port);
u8 md0, md2;
switch (port->encoding) {
case ENCODING_NRZ:
md2 = MD2_NRZ;
break;
case ENCODING_NRZI:
md2 = MD2_NRZI;
break;
case ENCODING_FM_MARK:
md2 = MD2_FM_MARK;
break;
case ENCODING_FM_SPACE:
md2 = MD2_FM_SPACE;
break;
default:
md2 = MD2_MANCHESTER;
}
if (port->settings.loopback)
md2 |= MD2_LOOPBACK;
switch (port->parity) {
case PARITY_CRC16_PR0:
md0 = MD0_HDLC | MD0_CRC_16_0;
break;
case PARITY_CRC16_PR1:
md0 = MD0_HDLC | MD0_CRC_16;
break;
case PARITY_CRC16_PR0_CCITT:
md0 = MD0_HDLC | MD0_CRC_ITU_0;
break;
case PARITY_CRC16_PR1_CCITT:
md0 = MD0_HDLC | MD0_CRC_ITU;
break;
default:
md0 = MD0_HDLC | MD0_CRC_NONE;
}
sca_out(CMD_RESET, msci + CMD, card);
sca_out(md0, msci + MD0, card);
sca_out(0x00, msci + MD1, card); /* no address field check */
sca_out(md2, msci + MD2, card);
sca_out(0x7E, msci + IDL, card); /* flag character 0x7E */
sca_out(CTL_IDLE, msci + CTL, card);
/* Allow at least 8 bytes before requesting RX DMA operation */
/* TX with higher priority and possibly with shorter transfers */
sca_out(0x07, msci + RRC, card); /* +1=RXRDY/DMA activation condition*/
sca_out(0x10, msci + TRC0, card); /* = TXRDY/DMA activation condition*/
sca_out(0x14, msci + TRC1, card); /* +1=TXRDY/DMA deactiv condition */
/* We're using the following interrupts:
* - TXINT (DMAC completed all transmisions, underrun or DCD change)
* - all DMA interrupts
*/
sca_set_carrier(port);
/* MSCI TX INT and RX INT A IRQ enable */
sca_out(IE0_TXINT | IE0_RXINTA, msci + IE0, card);
sca_out(IE1_UDRN | IE1_CDCD, msci + IE1, card);
sca_out(sca_in(IER0, card) | (phy_node(port) ? 0xC0 : 0x0C),
IER0, card); /* TXINT and RXINT */
/* enable DMA IRQ */
sca_out(sca_in(IER1, card) | (phy_node(port) ? 0xF0 : 0x0F),
IER1, card);
sca_out(port->tmc, msci + TMC, card); /* Restore registers */
sca_out(port->rxs, msci + RXS, card);
sca_out(port->txs, msci + TXS, card);
sca_out(CMD_TX_ENABLE, msci + CMD, card);
sca_out(CMD_RX_ENABLE, msci + CMD, card);
netif_start_queue(dev);
}
static void sca_close(struct net_device *dev)
{
port_t *port = dev_to_port(dev);
card_t *card = port_to_card(port);
/* reset channel */
sca_out(CMD_RESET, get_msci(port) + CMD, port_to_card(port));
/* disable MSCI interrupts */
sca_out(sca_in(IER0, card) & (phy_node(port) ? 0x0F : 0xF0),
IER0, card);
/* disable DMA interrupts */
sca_out(sca_in(IER1, card) & (phy_node(port) ? 0x0F : 0xF0),
IER1, card);
netif_stop_queue(dev);
}
static int sca_attach(struct net_device *dev, unsigned short encoding,
unsigned short parity)
{
if (encoding != ENCODING_NRZ &&
encoding != ENCODING_NRZI &&
encoding != ENCODING_FM_MARK &&
encoding != ENCODING_FM_SPACE &&
encoding != ENCODING_MANCHESTER)
return -EINVAL;
if (parity != PARITY_NONE &&
parity != PARITY_CRC16_PR0 &&
parity != PARITY_CRC16_PR1 &&
parity != PARITY_CRC16_PR0_CCITT &&
parity != PARITY_CRC16_PR1_CCITT)
return -EINVAL;
dev_to_port(dev)->encoding = encoding;
dev_to_port(dev)->parity = parity;
return 0;
}
#ifdef DEBUG_RINGS
static void sca_dump_rings(struct net_device *dev)
{
port_t *port = dev_to_port(dev);
card_t *card = port_to_card(port);
u16 cnt;
#ifndef PAGE0_ALWAYS_MAPPED
u8 page = sca_get_page(card);
openwin(card, 0);
#endif
printk(KERN_DEBUG "RX ring: CDA=%u EDA=%u DSR=%02X in=%u %sactive",
sca_inw(get_dmac_rx(port) + CDAL, card),
sca_inw(get_dmac_rx(port) + EDAL, card),
sca_in(DSR_RX(phy_node(port)), card), port->rxin,
sca_in(DSR_RX(phy_node(port)), card) & DSR_DE ? "" : "in");
for (cnt = 0; cnt < port_to_card(port)->rx_ring_buffers; cnt++)
pr_cont(" %02X", readb(&(desc_address(port, cnt, 0)->stat)));
pr_cont("\n");
printk(KERN_DEBUG "TX ring: CDA=%u EDA=%u DSR=%02X in=%u "
"last=%u %sactive",
sca_inw(get_dmac_tx(port) + CDAL, card),
sca_inw(get_dmac_tx(port) + EDAL, card),
sca_in(DSR_TX(phy_node(port)), card), port->txin, port->txlast,
sca_in(DSR_TX(phy_node(port)), card) & DSR_DE ? "" : "in");
for (cnt = 0; cnt < port_to_card(port)->tx_ring_buffers; cnt++)
pr_cont(" %02X", readb(&(desc_address(port, cnt, 1)->stat)));
pr_cont("\n");
printk(KERN_DEBUG "MSCI: MD: %02x %02x %02x, ST: %02x %02x %02x %02x,"
" FST: %02x CST: %02x %02x\n",
sca_in(get_msci(port) + MD0, card),
sca_in(get_msci(port) + MD1, card),
sca_in(get_msci(port) + MD2, card),
sca_in(get_msci(port) + ST0, card),
sca_in(get_msci(port) + ST1, card),
sca_in(get_msci(port) + ST2, card),
sca_in(get_msci(port) + ST3, card),
sca_in(get_msci(port) + FST, card),
sca_in(get_msci(port) + CST0, card),
sca_in(get_msci(port) + CST1, card));
printk(KERN_DEBUG "ISR: %02x %02x %02x\n", sca_in(ISR0, card),
sca_in(ISR1, card), sca_in(ISR2, card));
#ifndef PAGE0_ALWAYS_MAPPED
openwin(card, page); /* Restore original page */
#endif
}
#endif /* DEBUG_RINGS */
static netdev_tx_t sca_xmit(struct sk_buff *skb, struct net_device *dev)
{
port_t *port = dev_to_port(dev);
card_t *card = port_to_card(port);
pkt_desc __iomem *desc;
u32 buff, len;
u8 page;
u32 maxlen;
spin_lock_irq(&port->lock);
desc = desc_address(port, port->txin + 1, 1);
BUG_ON(readb(&desc->stat)); /* previous xmit should stop queue */
#ifdef DEBUG_PKT
printk(KERN_DEBUG "%s TX(%i):", dev->name, skb->len);
debug_frame(skb);
#endif
desc = desc_address(port, port->txin, 1);
buff = buffer_offset(port, port->txin, 1);
len = skb->len;
page = buff / winsize(card);
buff = buff % winsize(card);
maxlen = winsize(card) - buff;
openwin(card, page);
if (len > maxlen) {
memcpy_toio(winbase(card) + buff, skb->data, maxlen);
openwin(card, page + 1);
memcpy_toio(winbase(card), skb->data + maxlen, len - maxlen);
} else {
memcpy_toio(winbase(card) + buff, skb->data, len);
}
#ifndef PAGE0_ALWAYS_MAPPED
openwin(card, 0); /* select pkt_desc table page back */
#endif
writew(len, &desc->len);
writeb(ST_TX_EOM, &desc->stat);
port->txin = next_desc(port, port->txin, 1);
sca_outw(desc_offset(port, port->txin, 1),
get_dmac_tx(port) + EDAL, card);
sca_out(DSR_DE, DSR_TX(phy_node(port)), card); /* Enable TX DMA */
desc = desc_address(port, port->txin + 1, 1);
if (readb(&desc->stat)) /* allow 1 packet gap */
netif_stop_queue(dev);
spin_unlock_irq(&port->lock);
dev_kfree_skb(skb);
return NETDEV_TX_OK;
}
#ifdef NEED_DETECT_RAM
static u32 sca_detect_ram(card_t *card, u8 __iomem *rambase, u32 ramsize)
{
/* Round RAM size to 32 bits, fill from end to start */
u32 i = ramsize &= ~3;
u32 size = winsize(card);
openwin(card, (i - 4) / size); /* select last window */
do {
i -= 4;
if ((i + 4) % size == 0)
openwin(card, i / size);
writel(i ^ 0x12345678, rambase + i % size);
} while (i > 0);
for (i = 0; i < ramsize ; i += 4) {
if (i % size == 0)
openwin(card, i / size);
if (readl(rambase + i % size) != (i ^ 0x12345678))
break;
}
return i;
}
#endif /* NEED_DETECT_RAM */
static void sca_init(card_t *card, int wait_states)
{
sca_out(wait_states, WCRL, card); /* Wait Control */
sca_out(wait_states, WCRM, card);
sca_out(wait_states, WCRH, card);
sca_out(0, DMER, card); /* DMA Master disable */
sca_out(0x03, PCR, card); /* DMA priority */
sca_out(0, DSR_RX(0), card); /* DMA disable - to halt state */
sca_out(0, DSR_TX(0), card);
sca_out(0, DSR_RX(1), card);
sca_out(0, DSR_TX(1), card);
sca_out(DMER_DME, DMER, card); /* DMA Master enable */
}
+242
View File
@@ -0,0 +1,242 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __HD64570_H
#define __HD64570_H
/* SCA HD64570 register definitions - all addresses for mode 0 (8086 MPU)
and 1 (64180 MPU). For modes 2 and 3, XOR the address with 0x01.
Source: HD64570 SCA User's Manual
*/
/* SCA Control Registers */
#define LPR 0x00 /* Low Power */
/* Wait controller registers */
#define PABR0 0x02 /* Physical Address Boundary 0 */
#define PABR1 0x03 /* Physical Address Boundary 1 */
#define WCRL 0x04 /* Wait Control L */
#define WCRM 0x05 /* Wait Control M */
#define WCRH 0x06 /* Wait Control H */
#define PCR 0x08 /* DMA Priority Control */
#define DMER 0x09 /* DMA Master Enable */
/* Interrupt registers */
#define ISR0 0x10 /* Interrupt Status 0 */
#define ISR1 0x11 /* Interrupt Status 1 */
#define ISR2 0x12 /* Interrupt Status 2 */
#define IER0 0x14 /* Interrupt Enable 0 */
#define IER1 0x15 /* Interrupt Enable 1 */
#define IER2 0x16 /* Interrupt Enable 2 */
#define ITCR 0x18 /* Interrupt Control */
#define IVR 0x1A /* Interrupt Vector */
#define IMVR 0x1C /* Interrupt Modified Vector */
/* MSCI channel (port) 0 registers - offset 0x20
MSCI channel (port) 1 registers - offset 0x40 */
#define MSCI0_OFFSET 0x20
#define MSCI1_OFFSET 0x40
#define TRBL 0x00 /* TX/RX buffer L */
#define TRBH 0x01 /* TX/RX buffer H */
#define ST0 0x02 /* Status 0 */
#define ST1 0x03 /* Status 1 */
#define ST2 0x04 /* Status 2 */
#define ST3 0x05 /* Status 3 */
#define FST 0x06 /* Frame Status */
#define IE0 0x08 /* Interrupt Enable 0 */
#define IE1 0x09 /* Interrupt Enable 1 */
#define IE2 0x0A /* Interrupt Enable 2 */
#define FIE 0x0B /* Frame Interrupt Enable */
#define CMD 0x0C /* Command */
#define MD0 0x0E /* Mode 0 */
#define MD1 0x0F /* Mode 1 */
#define MD2 0x10 /* Mode 2 */
#define CTL 0x11 /* Control */
#define SA0 0x12 /* Sync/Address 0 */
#define SA1 0x13 /* Sync/Address 1 */
#define IDL 0x14 /* Idle Pattern */
#define TMC 0x15 /* Time Constant */
#define RXS 0x16 /* RX Clock Source */
#define TXS 0x17 /* TX Clock Source */
#define TRC0 0x18 /* TX Ready Control 0 */
#define TRC1 0x19 /* TX Ready Control 1 */
#define RRC 0x1A /* RX Ready Control */
#define CST0 0x1C /* Current Status 0 */
#define CST1 0x1D /* Current Status 1 */
/* Timer channel 0 (port 0 RX) registers - offset 0x60
Timer channel 1 (port 0 TX) registers - offset 0x68
Timer channel 2 (port 1 RX) registers - offset 0x70
Timer channel 3 (port 1 TX) registers - offset 0x78
*/
#define TIMER0RX_OFFSET 0x60
#define TIMER0TX_OFFSET 0x68
#define TIMER1RX_OFFSET 0x70
#define TIMER1TX_OFFSET 0x78
#define TCNTL 0x00 /* Up-counter L */
#define TCNTH 0x01 /* Up-counter H */
#define TCONRL 0x02 /* Constant L */
#define TCONRH 0x03 /* Constant H */
#define TCSR 0x04 /* Control/Status */
#define TEPR 0x05 /* Expand Prescale */
/* DMA channel 0 (port 0 RX) registers - offset 0x80
DMA channel 1 (port 0 TX) registers - offset 0xA0
DMA channel 2 (port 1 RX) registers - offset 0xC0
DMA channel 3 (port 1 TX) registers - offset 0xE0
*/
#define DMAC0RX_OFFSET 0x80
#define DMAC0TX_OFFSET 0xA0
#define DMAC1RX_OFFSET 0xC0
#define DMAC1TX_OFFSET 0xE0
#define BARL 0x00 /* Buffer Address L (chained block) */
#define BARH 0x01 /* Buffer Address H (chained block) */
#define BARB 0x02 /* Buffer Address B (chained block) */
#define DARL 0x00 /* RX Destination Addr L (single block) */
#define DARH 0x01 /* RX Destination Addr H (single block) */
#define DARB 0x02 /* RX Destination Addr B (single block) */
#define SARL 0x04 /* TX Source Address L (single block) */
#define SARH 0x05 /* TX Source Address H (single block) */
#define SARB 0x06 /* TX Source Address B (single block) */
#define CPB 0x06 /* Chain Pointer Base (chained block) */
#define CDAL 0x08 /* Current Descriptor Addr L (chained block) */
#define CDAH 0x09 /* Current Descriptor Addr H (chained block) */
#define EDAL 0x0A /* Error Descriptor Addr L (chained block) */
#define EDAH 0x0B /* Error Descriptor Addr H (chained block) */
#define BFLL 0x0C /* RX Receive Buffer Length L (chained block)*/
#define BFLH 0x0D /* RX Receive Buffer Length H (chained block)*/
#define BCRL 0x0E /* Byte Count L */
#define BCRH 0x0F /* Byte Count H */
#define DSR 0x10 /* DMA Status */
#define DSR_RX(node) (DSR + (node ? DMAC1RX_OFFSET : DMAC0RX_OFFSET))
#define DSR_TX(node) (DSR + (node ? DMAC1TX_OFFSET : DMAC0TX_OFFSET))
#define DMR 0x11 /* DMA Mode */
#define DMR_RX(node) (DMR + (node ? DMAC1RX_OFFSET : DMAC0RX_OFFSET))
#define DMR_TX(node) (DMR + (node ? DMAC1TX_OFFSET : DMAC0TX_OFFSET))
#define FCT 0x13 /* Frame End Interrupt Counter */
#define FCT_RX(node) (FCT + (node ? DMAC1RX_OFFSET : DMAC0RX_OFFSET))
#define FCT_TX(node) (FCT + (node ? DMAC1TX_OFFSET : DMAC0TX_OFFSET))
#define DIR 0x14 /* DMA Interrupt Enable */
#define DIR_RX(node) (DIR + (node ? DMAC1RX_OFFSET : DMAC0RX_OFFSET))
#define DIR_TX(node) (DIR + (node ? DMAC1TX_OFFSET : DMAC0TX_OFFSET))
#define DCR 0x15 /* DMA Command */
#define DCR_RX(node) (DCR + (node ? DMAC1RX_OFFSET : DMAC0RX_OFFSET))
#define DCR_TX(node) (DCR + (node ? DMAC1TX_OFFSET : DMAC0TX_OFFSET))
/* Descriptor Structure */
typedef struct {
u16 cp; /* Chain Pointer */
u32 bp; /* Buffer Pointer (24 bits) */
u16 len; /* Data Length */
u8 stat; /* Status */
u8 unused; /* pads to 2-byte boundary */
}__packed pkt_desc;
/* Packet Descriptor Status bits */
#define ST_TX_EOM 0x80 /* End of frame */
#define ST_TX_EOT 0x01 /* End of transmission */
#define ST_RX_EOM 0x80 /* End of frame */
#define ST_RX_SHORT 0x40 /* Short frame */
#define ST_RX_ABORT 0x20 /* Abort */
#define ST_RX_RESBIT 0x10 /* Residual bit */
#define ST_RX_OVERRUN 0x08 /* Overrun */
#define ST_RX_CRC 0x04 /* CRC */
#define ST_ERROR_MASK 0x7C
#define DIR_EOTE 0x80 /* Transfer completed */
#define DIR_EOME 0x40 /* Frame Transfer Completed (chained-block) */
#define DIR_BOFE 0x20 /* Buffer Overflow/Underflow (chained-block)*/
#define DIR_COFE 0x10 /* Counter Overflow (chained-block) */
#define DSR_EOT 0x80 /* Transfer completed */
#define DSR_EOM 0x40 /* Frame Transfer Completed (chained-block) */
#define DSR_BOF 0x20 /* Buffer Overflow/Underflow (chained-block)*/
#define DSR_COF 0x10 /* Counter Overflow (chained-block) */
#define DSR_DE 0x02 /* DMA Enable */
#define DSR_DWE 0x01 /* DMA Write Disable */
/* DMA Master Enable Register (DMER) bits */
#define DMER_DME 0x80 /* DMA Master Enable */
#define CMD_RESET 0x21 /* Reset Channel */
#define CMD_TX_ENABLE 0x02 /* Start transmitter */
#define CMD_RX_ENABLE 0x12 /* Start receiver */
#define MD0_HDLC 0x80 /* Bit-sync HDLC mode */
#define MD0_CRC_ENA 0x04 /* Enable CRC code calculation */
#define MD0_CRC_CCITT 0x02 /* CCITT CRC instead of CRC-16 */
#define MD0_CRC_PR1 0x01 /* Initial all-ones instead of all-zeros */
#define MD0_CRC_NONE 0x00
#define MD0_CRC_16_0 0x04
#define MD0_CRC_16 0x05
#define MD0_CRC_ITU_0 0x06
#define MD0_CRC_ITU 0x07
#define MD2_NRZ 0x00
#define MD2_NRZI 0x20
#define MD2_MANCHESTER 0x80
#define MD2_FM_MARK 0xA0
#define MD2_FM_SPACE 0xC0
#define MD2_LOOPBACK 0x03 /* Local data Loopback */
#define CTL_NORTS 0x01
#define CTL_IDLE 0x10 /* Transmit an idle pattern */
#define CTL_UDRNC 0x20 /* Idle after CRC or FCS+flag transmission */
#define ST0_TXRDY 0x02 /* TX ready */
#define ST0_RXRDY 0x01 /* RX ready */
#define ST1_UDRN 0x80 /* MSCI TX underrun */
#define ST1_CDCD 0x04 /* DCD level changed */
#define ST3_CTS 0x08 /* modem input - /CTS */
#define ST3_DCD 0x04 /* modem input - /DCD */
#define IE0_TXINT 0x80 /* TX INT MSCI interrupt enable */
#define IE0_RXINTA 0x40 /* RX INT A MSCI interrupt enable */
#define IE1_UDRN 0x80 /* TX underrun MSCI interrupt enable */
#define IE1_CDCD 0x04 /* DCD level changed */
#define DCR_ABORT 0x01 /* Software abort command */
#define DCR_CLEAR_EOF 0x02 /* Clear EOF interrupt */
/* TX and RX Clock Source - RXS and TXS */
#define CLK_BRG_MASK 0x0F
#define CLK_LINE_RX 0x00 /* TX/RX clock line input */
#define CLK_LINE_TX 0x00 /* TX/RX line input */
#define CLK_BRG_RX 0x40 /* internal baud rate generator */
#define CLK_BRG_TX 0x40 /* internal baud rate generator */
#define CLK_RXCLK_TX 0x60 /* TX clock from RX clock */
#endif
+636
View File
@@ -0,0 +1,636 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Hitachi (now Renesas) SCA-II HD64572 driver for Linux
*
* Copyright (C) 1998-2008 Krzysztof Halasa <khc@pm.waw.pl>
*
* Source of information: HD64572 SCA-II User's Manual
*
* We use the following SCA memory map:
*
* Packet buffer descriptor rings - starting from card->rambase:
* rx_ring_buffers * sizeof(pkt_desc) = logical channel #0 RX ring
* tx_ring_buffers * sizeof(pkt_desc) = logical channel #0 TX ring
* rx_ring_buffers * sizeof(pkt_desc) = logical channel #1 RX ring (if used)
* tx_ring_buffers * sizeof(pkt_desc) = logical channel #1 TX ring (if used)
*
* Packet data buffers - starting from card->rambase + buff_offset:
* rx_ring_buffers * HDLC_MAX_MRU = logical channel #0 RX buffers
* tx_ring_buffers * HDLC_MAX_MRU = logical channel #0 TX buffers
* rx_ring_buffers * HDLC_MAX_MRU = logical channel #0 RX buffers (if used)
* tx_ring_buffers * HDLC_MAX_MRU = logical channel #0 TX buffers (if used)
*/
#include <linux/bitops.h>
#include <linux/errno.h>
#include <linux/fcntl.h>
#include <linux/hdlc.h>
#include <linux/in.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/skbuff.h>
#include <linux/string.h>
#include <linux/types.h>
#include <asm/io.h>
#include <linux/uaccess.h>
#include "hd64572.h"
#define NAPI_WEIGHT 16
#define get_msci(port) ((port)->chan ? MSCI1_OFFSET : MSCI0_OFFSET)
#define get_dmac_rx(port) ((port)->chan ? DMAC1RX_OFFSET : DMAC0RX_OFFSET)
#define get_dmac_tx(port) ((port)->chan ? DMAC1TX_OFFSET : DMAC0TX_OFFSET)
#define sca_in(reg, card) readb((card)->scabase + (reg))
#define sca_out(value, reg, card) writeb(value, (card)->scabase + (reg))
#define sca_inw(reg, card) readw((card)->scabase + (reg))
#define sca_outw(value, reg, card) writew(value, (card)->scabase + (reg))
#define sca_inl(reg, card) readl((card)->scabase + (reg))
#define sca_outl(value, reg, card) writel(value, (card)->scabase + (reg))
static int sca_poll(struct napi_struct *napi, int budget);
static inline port_t *dev_to_port(struct net_device *dev)
{
return dev_to_hdlc(dev)->priv;
}
static inline void enable_intr(port_t *port)
{
/* enable DMIB and MSCI RXINTA interrupts */
sca_outl(sca_inl(IER0, port->card) |
(port->chan ? 0x08002200 : 0x00080022), IER0, port->card);
}
static inline void disable_intr(port_t *port)
{
sca_outl(sca_inl(IER0, port->card) &
(port->chan ? 0x00FF00FF : 0xFF00FF00), IER0, port->card);
}
static inline u16 desc_abs_number(port_t *port, u16 desc, int transmit)
{
u16 rx_buffs = port->card->rx_ring_buffers;
u16 tx_buffs = port->card->tx_ring_buffers;
desc %= (transmit ? tx_buffs : rx_buffs); // called with "X + 1" etc.
return port->chan * (rx_buffs + tx_buffs) + transmit * rx_buffs + desc;
}
static inline u16 desc_offset(port_t *port, u16 desc, int transmit)
{
/* Descriptor offset always fits in 16 bits */
return desc_abs_number(port, desc, transmit) * sizeof(pkt_desc);
}
static inline pkt_desc __iomem *desc_address(port_t *port, u16 desc,
int transmit)
{
return (pkt_desc __iomem *)(port->card->rambase +
desc_offset(port, desc, transmit));
}
static inline u32 buffer_offset(port_t *port, u16 desc, int transmit)
{
return port->card->buff_offset +
desc_abs_number(port, desc, transmit) * (u32)HDLC_MAX_MRU;
}
static inline void sca_set_carrier(port_t *port)
{
if (!(sca_in(get_msci(port) + ST3, port->card) & ST3_DCD)) {
#ifdef DEBUG_LINK
printk(KERN_DEBUG "%s: sca_set_carrier on\n",
port->netdev.name);
#endif
netif_carrier_on(port->netdev);
} else {
#ifdef DEBUG_LINK
printk(KERN_DEBUG "%s: sca_set_carrier off\n",
port->netdev.name);
#endif
netif_carrier_off(port->netdev);
}
}
static void sca_init_port(port_t *port)
{
card_t *card = port->card;
u16 dmac_rx = get_dmac_rx(port), dmac_tx = get_dmac_tx(port);
int transmit, i;
port->rxin = 0;
port->txin = 0;
port->txlast = 0;
for (transmit = 0; transmit < 2; transmit++) {
u16 buffs = transmit ? card->tx_ring_buffers
: card->rx_ring_buffers;
for (i = 0; i < buffs; i++) {
pkt_desc __iomem *desc = desc_address(port, i, transmit);
u16 chain_off = desc_offset(port, i + 1, transmit);
u32 buff_off = buffer_offset(port, i, transmit);
writel(chain_off, &desc->cp);
writel(buff_off, &desc->bp);
writew(0, &desc->len);
writeb(0, &desc->stat);
}
}
/* DMA disable - to halt state */
sca_out(0, DSR_RX(port->chan), card);
sca_out(0, DSR_TX(port->chan), card);
/* software ABORT - to initial state */
sca_out(DCR_ABORT, DCR_RX(port->chan), card);
sca_out(DCR_ABORT, DCR_TX(port->chan), card);
/* current desc addr */
sca_outl(desc_offset(port, 0, 0), dmac_rx + CDAL, card);
sca_outl(desc_offset(port, card->tx_ring_buffers - 1, 0),
dmac_rx + EDAL, card);
sca_outl(desc_offset(port, 0, 1), dmac_tx + CDAL, card);
sca_outl(desc_offset(port, 0, 1), dmac_tx + EDAL, card);
/* clear frame end interrupt counter */
sca_out(DCR_CLEAR_EOF, DCR_RX(port->chan), card);
sca_out(DCR_CLEAR_EOF, DCR_TX(port->chan), card);
/* Receive */
sca_outw(HDLC_MAX_MRU, dmac_rx + BFLL, card); /* set buffer length */
sca_out(0x14, DMR_RX(port->chan), card); /* Chain mode, Multi-frame */
sca_out(DIR_EOME, DIR_RX(port->chan), card); /* enable interrupts */
sca_out(DSR_DE, DSR_RX(port->chan), card); /* DMA enable */
/* Transmit */
sca_out(0x14, DMR_TX(port->chan), card); /* Chain mode, Multi-frame */
sca_out(DIR_EOME, DIR_TX(port->chan), card); /* enable interrupts */
sca_set_carrier(port);
netif_napi_add_weight(port->netdev, &port->napi, sca_poll,
NAPI_WEIGHT);
}
/* MSCI interrupt service */
static inline void sca_msci_intr(port_t *port)
{
u16 msci = get_msci(port);
card_t *card = port->card;
if (sca_in(msci + ST1, card) & ST1_CDCD) {
/* Reset MSCI CDCD status bit */
sca_out(ST1_CDCD, msci + ST1, card);
sca_set_carrier(port);
}
}
static inline void sca_rx(card_t *card, port_t *port, pkt_desc __iomem *desc,
u16 rxin)
{
struct net_device *dev = port->netdev;
struct sk_buff *skb;
u16 len;
u32 buff;
len = readw(&desc->len);
skb = dev_alloc_skb(len);
if (!skb) {
dev->stats.rx_dropped++;
return;
}
buff = buffer_offset(port, rxin, 0);
memcpy_fromio(skb->data, card->rambase + buff, len);
skb_put(skb, len);
#ifdef DEBUG_PKT
printk(KERN_DEBUG "%s RX(%i):", dev->name, skb->len);
debug_frame(skb);
#endif
dev->stats.rx_packets++;
dev->stats.rx_bytes += skb->len;
skb->protocol = hdlc_type_trans(skb, dev);
netif_receive_skb(skb);
}
/* Receive DMA service */
static inline int sca_rx_done(port_t *port, int budget)
{
struct net_device *dev = port->netdev;
u16 dmac = get_dmac_rx(port);
card_t *card = port->card;
u8 stat = sca_in(DSR_RX(port->chan), card); /* read DMA Status */
int received = 0;
/* Reset DSR status bits */
sca_out((stat & (DSR_EOT | DSR_EOM | DSR_BOF | DSR_COF)) | DSR_DWE,
DSR_RX(port->chan), card);
if (stat & DSR_BOF)
/* Dropped one or more frames */
dev->stats.rx_over_errors++;
while (received < budget) {
u32 desc_off = desc_offset(port, port->rxin, 0);
pkt_desc __iomem *desc;
u32 cda = sca_inl(dmac + CDAL, card);
if ((cda >= desc_off) && (cda < desc_off + sizeof(pkt_desc)))
break; /* No frame received */
desc = desc_address(port, port->rxin, 0);
stat = readb(&desc->stat);
if (!(stat & ST_RX_EOM))
port->rxpart = 1; /* partial frame received */
else if ((stat & ST_ERROR_MASK) || port->rxpart) {
dev->stats.rx_errors++;
if (stat & ST_RX_OVERRUN)
dev->stats.rx_fifo_errors++;
else if ((stat & (ST_RX_SHORT | ST_RX_ABORT |
ST_RX_RESBIT)) || port->rxpart)
dev->stats.rx_frame_errors++;
else if (stat & ST_RX_CRC)
dev->stats.rx_crc_errors++;
if (stat & ST_RX_EOM)
port->rxpart = 0; /* received last fragment */
} else {
sca_rx(card, port, desc, port->rxin);
received++;
}
/* Set new error descriptor address */
sca_outl(desc_off, dmac + EDAL, card);
port->rxin = (port->rxin + 1) % card->rx_ring_buffers;
}
/* make sure RX DMA is enabled */
sca_out(DSR_DE, DSR_RX(port->chan), card);
return received;
}
/* Transmit DMA service */
static inline void sca_tx_done(port_t *port)
{
struct net_device *dev = port->netdev;
card_t *card = port->card;
u8 stat;
unsigned count = 0;
spin_lock(&port->lock);
stat = sca_in(DSR_TX(port->chan), card); /* read DMA Status */
/* Reset DSR status bits */
sca_out((stat & (DSR_EOT | DSR_EOM | DSR_BOF | DSR_COF)) | DSR_DWE,
DSR_TX(port->chan), card);
while (1) {
pkt_desc __iomem *desc = desc_address(port, port->txlast, 1);
u8 stat = readb(&desc->stat);
if (!(stat & ST_TX_OWNRSHP))
break; /* not yet transmitted */
if (stat & ST_TX_UNDRRUN) {
dev->stats.tx_errors++;
dev->stats.tx_fifo_errors++;
} else {
dev->stats.tx_packets++;
dev->stats.tx_bytes += readw(&desc->len);
}
writeb(0, &desc->stat); /* Free descriptor */
count++;
port->txlast = (port->txlast + 1) % card->tx_ring_buffers;
}
if (count)
netif_wake_queue(dev);
spin_unlock(&port->lock);
}
static int sca_poll(struct napi_struct *napi, int budget)
{
port_t *port = container_of(napi, port_t, napi);
u32 isr0 = sca_inl(ISR0, port->card);
int received = 0;
if (isr0 & (port->chan ? 0x08000000 : 0x00080000))
sca_msci_intr(port);
if (isr0 & (port->chan ? 0x00002000 : 0x00000020))
sca_tx_done(port);
if (isr0 & (port->chan ? 0x00000200 : 0x00000002))
received = sca_rx_done(port, budget);
if (received < budget) {
napi_complete_done(napi, received);
enable_intr(port);
}
return received;
}
static irqreturn_t sca_intr(int irq, void *dev_id)
{
card_t *card = dev_id;
u32 isr0 = sca_inl(ISR0, card);
int i, handled = 0;
for (i = 0; i < 2; i++) {
port_t *port = get_port(card, i);
if (port && (isr0 & (i ? 0x08002200 : 0x00080022))) {
handled = 1;
disable_intr(port);
napi_schedule(&port->napi);
}
}
return IRQ_RETVAL(handled);
}
static void sca_set_port(port_t *port)
{
card_t *card = port->card;
u16 msci = get_msci(port);
u8 md2 = sca_in(msci + MD2, card);
unsigned int tmc, br = 10, brv = 1024;
if (port->settings.clock_rate > 0) {
/* Try lower br for better accuracy*/
do {
br--;
brv >>= 1; /* brv = 2^9 = 512 max in specs */
/* Baud Rate = CLOCK_BASE / TMC / 2^BR */
tmc = CLOCK_BASE / brv / port->settings.clock_rate;
} while (br > 1 && tmc <= 128);
if (tmc < 1) {
tmc = 1;
br = 0; /* For baud=CLOCK_BASE we use tmc=1 br=0 */
brv = 1;
} else if (tmc > 255) {
tmc = 256; /* tmc=0 means 256 - low baud rates */
}
port->settings.clock_rate = CLOCK_BASE / brv / tmc;
} else {
br = 9; /* Minimum clock rate */
tmc = 256; /* 8bit = 0 */
port->settings.clock_rate = CLOCK_BASE / (256 * 512);
}
port->rxs = (port->rxs & ~CLK_BRG_MASK) | br;
port->txs = (port->txs & ~CLK_BRG_MASK) | br;
port->tmc = tmc;
/* baud divisor - time constant*/
sca_out(port->tmc, msci + TMCR, card);
sca_out(port->tmc, msci + TMCT, card);
/* Set BRG bits */
sca_out(port->rxs, msci + RXS, card);
sca_out(port->txs, msci + TXS, card);
if (port->settings.loopback)
md2 |= MD2_LOOPBACK;
else
md2 &= ~MD2_LOOPBACK;
sca_out(md2, msci + MD2, card);
}
static void sca_open(struct net_device *dev)
{
port_t *port = dev_to_port(dev);
card_t *card = port->card;
u16 msci = get_msci(port);
u8 md0, md2;
switch (port->encoding) {
case ENCODING_NRZ:
md2 = MD2_NRZ;
break;
case ENCODING_NRZI:
md2 = MD2_NRZI;
break;
case ENCODING_FM_MARK:
md2 = MD2_FM_MARK;
break;
case ENCODING_FM_SPACE:
md2 = MD2_FM_SPACE;
break;
default:
md2 = MD2_MANCHESTER;
}
if (port->settings.loopback)
md2 |= MD2_LOOPBACK;
switch (port->parity) {
case PARITY_CRC16_PR0:
md0 = MD0_HDLC | MD0_CRC_16_0;
break;
case PARITY_CRC16_PR1:
md0 = MD0_HDLC | MD0_CRC_16;
break;
case PARITY_CRC32_PR1_CCITT:
md0 = MD0_HDLC | MD0_CRC_ITU32;
break;
case PARITY_CRC16_PR1_CCITT:
md0 = MD0_HDLC | MD0_CRC_ITU;
break;
default:
md0 = MD0_HDLC | MD0_CRC_NONE;
}
sca_out(CMD_RESET, msci + CMD, card);
sca_out(md0, msci + MD0, card);
sca_out(0x00, msci + MD1, card); /* no address field check */
sca_out(md2, msci + MD2, card);
sca_out(0x7E, msci + IDL, card); /* flag character 0x7E */
/* Skip the rest of underrun frame */
sca_out(CTL_IDLE | CTL_URCT | CTL_URSKP, msci + CTL, card);
sca_out(0x0F, msci + RNR, card); /* +1=RX DMA activation condition */
sca_out(0x3C, msci + TFS, card); /* +1 = TX start */
sca_out(0x38, msci + TCR, card); /* =Critical TX DMA activ condition */
sca_out(0x38, msci + TNR0, card); /* =TX DMA activation condition */
sca_out(0x3F, msci + TNR1, card); /* +1=TX DMA deactivation condition*/
/* We're using the following interrupts:
- RXINTA (DCD changes only)
- DMIB (EOM - single frame transfer complete)
*/
sca_outl(IE0_RXINTA | IE0_CDCD, msci + IE0, card);
sca_out(port->tmc, msci + TMCR, card);
sca_out(port->tmc, msci + TMCT, card);
sca_out(port->rxs, msci + RXS, card);
sca_out(port->txs, msci + TXS, card);
sca_out(CMD_TX_ENABLE, msci + CMD, card);
sca_out(CMD_RX_ENABLE, msci + CMD, card);
sca_set_carrier(port);
enable_intr(port);
napi_enable(&port->napi);
netif_start_queue(dev);
}
static void sca_close(struct net_device *dev)
{
port_t *port = dev_to_port(dev);
/* reset channel */
sca_out(CMD_RESET, get_msci(port) + CMD, port->card);
disable_intr(port);
napi_disable(&port->napi);
netif_stop_queue(dev);
}
static int sca_attach(struct net_device *dev, unsigned short encoding,
unsigned short parity)
{
if (encoding != ENCODING_NRZ &&
encoding != ENCODING_NRZI &&
encoding != ENCODING_FM_MARK &&
encoding != ENCODING_FM_SPACE &&
encoding != ENCODING_MANCHESTER)
return -EINVAL;
if (parity != PARITY_NONE &&
parity != PARITY_CRC16_PR0 &&
parity != PARITY_CRC16_PR1 &&
parity != PARITY_CRC32_PR1_CCITT &&
parity != PARITY_CRC16_PR1_CCITT)
return -EINVAL;
dev_to_port(dev)->encoding = encoding;
dev_to_port(dev)->parity = parity;
return 0;
}
#ifdef DEBUG_RINGS
static void sca_dump_rings(struct net_device *dev)
{
port_t *port = dev_to_port(dev);
card_t *card = port->card;
u16 cnt;
printk(KERN_DEBUG "RX ring: CDA=%u EDA=%u DSR=%02X in=%u %sactive",
sca_inl(get_dmac_rx(port) + CDAL, card),
sca_inl(get_dmac_rx(port) + EDAL, card),
sca_in(DSR_RX(port->chan), card), port->rxin,
sca_in(DSR_RX(port->chan), card) & DSR_DE ? "" : "in");
for (cnt = 0; cnt < port->card->rx_ring_buffers; cnt++)
pr_cont(" %02X", readb(&(desc_address(port, cnt, 0)->stat)));
pr_cont("\n");
printk(KERN_DEBUG "TX ring: CDA=%u EDA=%u DSR=%02X in=%u "
"last=%u %sactive",
sca_inl(get_dmac_tx(port) + CDAL, card),
sca_inl(get_dmac_tx(port) + EDAL, card),
sca_in(DSR_TX(port->chan), card), port->txin, port->txlast,
sca_in(DSR_TX(port->chan), card) & DSR_DE ? "" : "in");
for (cnt = 0; cnt < port->card->tx_ring_buffers; cnt++)
pr_cont(" %02X", readb(&(desc_address(port, cnt, 1)->stat)));
pr_cont("\n");
printk(KERN_DEBUG "MSCI: MD: %02x %02x %02x,"
" ST: %02x %02x %02x %02x %02x, FST: %02x CST: %02x %02x\n",
sca_in(get_msci(port) + MD0, card),
sca_in(get_msci(port) + MD1, card),
sca_in(get_msci(port) + MD2, card),
sca_in(get_msci(port) + ST0, card),
sca_in(get_msci(port) + ST1, card),
sca_in(get_msci(port) + ST2, card),
sca_in(get_msci(port) + ST3, card),
sca_in(get_msci(port) + ST4, card),
sca_in(get_msci(port) + FST, card),
sca_in(get_msci(port) + CST0, card),
sca_in(get_msci(port) + CST1, card));
printk(KERN_DEBUG "ILAR: %02x ISR: %08x %08x\n", sca_in(ILAR, card),
sca_inl(ISR0, card), sca_inl(ISR1, card));
}
#endif /* DEBUG_RINGS */
static netdev_tx_t sca_xmit(struct sk_buff *skb, struct net_device *dev)
{
port_t *port = dev_to_port(dev);
card_t *card = port->card;
pkt_desc __iomem *desc;
u32 buff, len;
spin_lock_irq(&port->lock);
desc = desc_address(port, port->txin + 1, 1);
BUG_ON(readb(&desc->stat)); /* previous xmit should stop queue */
#ifdef DEBUG_PKT
printk(KERN_DEBUG "%s TX(%i):", dev->name, skb->len);
debug_frame(skb);
#endif
desc = desc_address(port, port->txin, 1);
buff = buffer_offset(port, port->txin, 1);
len = skb->len;
memcpy_toio(card->rambase + buff, skb->data, len);
writew(len, &desc->len);
writeb(ST_TX_EOM, &desc->stat);
port->txin = (port->txin + 1) % card->tx_ring_buffers;
sca_outl(desc_offset(port, port->txin, 1),
get_dmac_tx(port) + EDAL, card);
sca_out(DSR_DE, DSR_TX(port->chan), card); /* Enable TX DMA */
desc = desc_address(port, port->txin + 1, 1);
if (readb(&desc->stat)) /* allow 1 packet gap */
netif_stop_queue(dev);
spin_unlock_irq(&port->lock);
dev_kfree_skb(skb);
return NETDEV_TX_OK;
}
static u32 sca_detect_ram(card_t *card, u8 __iomem *rambase, u32 ramsize)
{
/* Round RAM size to 32 bits, fill from end to start */
u32 i = ramsize &= ~3;
do {
i -= 4;
writel(i ^ 0x12345678, rambase + i);
} while (i > 0);
for (i = 0; i < ramsize ; i += 4) {
if (readl(rambase + i) != (i ^ 0x12345678))
break;
}
return i;
}
static void sca_init(card_t *card, int wait_states)
{
sca_out(wait_states, WCRL, card); /* Wait Control */
sca_out(wait_states, WCRM, card);
sca_out(wait_states, WCRH, card);
sca_out(0, DMER, card); /* DMA Master disable */
sca_out(0x03, PCR, card); /* DMA priority */
sca_out(0, DSR_RX(0), card); /* DMA disable - to halt state */
sca_out(0, DSR_TX(0), card);
sca_out(0, DSR_RX(1), card);
sca_out(0, DSR_TX(1), card);
sca_out(DMER_DME, DMER, card); /* DMA Master enable */
}
+522
View File
@@ -0,0 +1,522 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* hd64572.h Description of the Hitachi HD64572 (SCA-II), valid for
* CPU modes 0 & 2.
*
* Author: Ivan Passos <ivan@cyclades.com>
*
* Copyright: (c) 2000-2001 Cyclades Corp.
*
* $Log: hd64572.h,v $
* Revision 3.1 2001/06/15 12:41:10 regina
* upping major version number
*
* Revision 1.1.1.1 2001/06/13 20:24:49 daniela
* PC300 initial CVS version (3.4.0-pre1)
*
* Revision 1.0 2000/01/25 ivan
* Initial version.
*/
#ifndef __HD64572_H
#define __HD64572_H
/* Illegal Access Register */
#define ILAR 0x00
/* Wait Controller Registers */
#define PABR0L 0x20 /* Physical Addr Boundary Register 0 L */
#define PABR0H 0x21 /* Physical Addr Boundary Register 0 H */
#define PABR1L 0x22 /* Physical Addr Boundary Register 1 L */
#define PABR1H 0x23 /* Physical Addr Boundary Register 1 H */
#define WCRL 0x24 /* Wait Control Register L */
#define WCRM 0x25 /* Wait Control Register M */
#define WCRH 0x26 /* Wait Control Register H */
/* Interrupt Registers */
#define IVR 0x60 /* Interrupt Vector Register */
#define IMVR 0x64 /* Interrupt Modified Vector Register */
#define ITCR 0x68 /* Interrupt Control Register */
#define ISR0 0x6c /* Interrupt Status Register 0 */
#define ISR1 0x70 /* Interrupt Status Register 1 */
#define IER0 0x74 /* Interrupt Enable Register 0 */
#define IER1 0x78 /* Interrupt Enable Register 1 */
/* Register Access Macros (chan is 0 or 1 in _any_ case) */
#define M_REG(reg, chan) (reg + 0x80*chan) /* MSCI */
#define DRX_REG(reg, chan) (reg + 0x40*chan) /* DMA Rx */
#define DTX_REG(reg, chan) (reg + 0x20*(2*chan + 1)) /* DMA Tx */
#define TRX_REG(reg, chan) (reg + 0x20*chan) /* Timer Rx */
#define TTX_REG(reg, chan) (reg + 0x10*(2*chan + 1)) /* Timer Tx */
#define ST_REG(reg, chan) (reg + 0x80*chan) /* Status Cnt */
#define IR0_DRX(val, chan) ((val)<<(8*(chan))) /* Int DMA Rx */
#define IR0_DTX(val, chan) ((val)<<(4*(2*chan + 1))) /* Int DMA Tx */
#define IR0_M(val, chan) ((val)<<(8*(chan))) /* Int MSCI */
/* MSCI Channel Registers */
#define MSCI0_OFFSET 0x00
#define MSCI1_OFFSET 0x80
#define MD0 0x138 /* Mode reg 0 */
#define MD1 0x139 /* Mode reg 1 */
#define MD2 0x13a /* Mode reg 2 */
#define MD3 0x13b /* Mode reg 3 */
#define CTL 0x130 /* Control reg */
#define RXS 0x13c /* RX clock source */
#define TXS 0x13d /* TX clock source */
#define EXS 0x13e /* External clock input selection */
#define TMCT 0x144 /* Time constant (Tx) */
#define TMCR 0x145 /* Time constant (Rx) */
#define CMD 0x128 /* Command reg */
#define ST0 0x118 /* Status reg 0 */
#define ST1 0x119 /* Status reg 1 */
#define ST2 0x11a /* Status reg 2 */
#define ST3 0x11b /* Status reg 3 */
#define ST4 0x11c /* Status reg 4 */
#define FST 0x11d /* frame Status reg */
#define IE0 0x120 /* Interrupt enable reg 0 */
#define IE1 0x121 /* Interrupt enable reg 1 */
#define IE2 0x122 /* Interrupt enable reg 2 */
#define IE4 0x124 /* Interrupt enable reg 4 */
#define FIE 0x125 /* Frame Interrupt enable reg */
#define SA0 0x140 /* Syn Address reg 0 */
#define SA1 0x141 /* Syn Address reg 1 */
#define IDL 0x142 /* Idle register */
#define TRBL 0x100 /* TX/RX buffer reg L */
#define TRBK 0x101 /* TX/RX buffer reg K */
#define TRBJ 0x102 /* TX/RX buffer reg J */
#define TRBH 0x103 /* TX/RX buffer reg H */
#define TRC0 0x148 /* TX Ready control reg 0 */
#define TRC1 0x149 /* TX Ready control reg 1 */
#define RRC 0x14a /* RX Ready control reg */
#define CST0 0x108 /* Current Status Register 0 */
#define CST1 0x109 /* Current Status Register 1 */
#define CST2 0x10a /* Current Status Register 2 */
#define CST3 0x10b /* Current Status Register 3 */
#define GPO 0x131 /* General Purpose Output Pin Ctl Reg */
#define TFS 0x14b /* Tx Start Threshold Ctl Reg */
#define TFN 0x143 /* Inter-transmit-frame Time Fill Ctl Reg */
#define TBN 0x110 /* Tx Buffer Number Reg */
#define RBN 0x111 /* Rx Buffer Number Reg */
#define TNR0 0x150 /* Tx DMA Request Ctl Reg 0 */
#define TNR1 0x151 /* Tx DMA Request Ctl Reg 1 */
#define TCR 0x152 /* Tx DMA Critical Request Reg */
#define RNR 0x154 /* Rx DMA Request Ctl Reg */
#define RCR 0x156 /* Rx DMA Critical Request Reg */
/* Timer Registers */
#define TIMER0RX_OFFSET 0x00
#define TIMER0TX_OFFSET 0x10
#define TIMER1RX_OFFSET 0x20
#define TIMER1TX_OFFSET 0x30
#define TCNTL 0x200 /* Timer Upcounter L */
#define TCNTH 0x201 /* Timer Upcounter H */
#define TCONRL 0x204 /* Timer Constant Register L */
#define TCONRH 0x205 /* Timer Constant Register H */
#define TCSR 0x206 /* Timer Control/Status Register */
#define TEPR 0x207 /* Timer Expand Prescale Register */
/* DMA registers */
#define PCR 0x40 /* DMA priority control reg */
#define DRR 0x44 /* DMA reset reg */
#define DMER 0x07 /* DMA Master Enable reg */
#define BTCR 0x08 /* Burst Tx Ctl Reg */
#define BOLR 0x0c /* Back-off Length Reg */
#define DSR_RX(chan) (0x48 + 2*chan) /* DMA Status Reg (Rx) */
#define DSR_TX(chan) (0x49 + 2*chan) /* DMA Status Reg (Tx) */
#define DIR_RX(chan) (0x4c + 2*chan) /* DMA Interrupt Enable Reg (Rx) */
#define DIR_TX(chan) (0x4d + 2*chan) /* DMA Interrupt Enable Reg (Tx) */
#define FCT_RX(chan) (0x50 + 2*chan) /* Frame End Interrupt Counter (Rx) */
#define FCT_TX(chan) (0x51 + 2*chan) /* Frame End Interrupt Counter (Tx) */
#define DMR_RX(chan) (0x54 + 2*chan) /* DMA Mode Reg (Rx) */
#define DMR_TX(chan) (0x55 + 2*chan) /* DMA Mode Reg (Tx) */
#define DCR_RX(chan) (0x58 + 2*chan) /* DMA Command Reg (Rx) */
#define DCR_TX(chan) (0x59 + 2*chan) /* DMA Command Reg (Tx) */
/* DMA Channel Registers */
#define DMAC0RX_OFFSET 0x00
#define DMAC0TX_OFFSET 0x20
#define DMAC1RX_OFFSET 0x40
#define DMAC1TX_OFFSET 0x60
#define DARL 0x80 /* Dest Addr Register L (single-block, RX only) */
#define DARH 0x81 /* Dest Addr Register H (single-block, RX only) */
#define DARB 0x82 /* Dest Addr Register B (single-block, RX only) */
#define DARBH 0x83 /* Dest Addr Register BH (single-block, RX only) */
#define SARL 0x80 /* Source Addr Register L (single-block, TX only) */
#define SARH 0x81 /* Source Addr Register H (single-block, TX only) */
#define SARB 0x82 /* Source Addr Register B (single-block, TX only) */
#define DARBH 0x83 /* Source Addr Register BH (single-block, TX only) */
#define BARL 0x80 /* Buffer Addr Register L (chained-block) */
#define BARH 0x81 /* Buffer Addr Register H (chained-block) */
#define BARB 0x82 /* Buffer Addr Register B (chained-block) */
#define BARBH 0x83 /* Buffer Addr Register BH (chained-block) */
#define CDAL 0x84 /* Current Descriptor Addr Register L */
#define CDAH 0x85 /* Current Descriptor Addr Register H */
#define CDAB 0x86 /* Current Descriptor Addr Register B */
#define CDABH 0x87 /* Current Descriptor Addr Register BH */
#define EDAL 0x88 /* Error Descriptor Addr Register L */
#define EDAH 0x89 /* Error Descriptor Addr Register H */
#define EDAB 0x8a /* Error Descriptor Addr Register B */
#define EDABH 0x8b /* Error Descriptor Addr Register BH */
#define BFLL 0x90 /* RX Buffer Length L (only RX) */
#define BFLH 0x91 /* RX Buffer Length H (only RX) */
#define BCRL 0x8c /* Byte Count Register L */
#define BCRH 0x8d /* Byte Count Register H */
/* Block Descriptor Structure */
typedef struct {
unsigned long next; /* pointer to next block descriptor */
unsigned long ptbuf; /* buffer pointer */
unsigned short len; /* data length */
unsigned char status; /* status */
unsigned char filler[5]; /* alignment filler (16 bytes) */
} pcsca_bd_t;
/* Block Descriptor Structure */
typedef struct {
u32 cp; /* pointer to next block descriptor */
u32 bp; /* buffer pointer */
u16 len; /* data length */
u8 stat; /* status */
u8 unused; /* pads to 4-byte boundary */
}pkt_desc;
/*
Descriptor Status definitions:
Bit Transmission Reception
7 EOM EOM
6 - Short Frame
5 - Abort
4 - Residual bit
3 Underrun Overrun
2 - CRC
1 Ownership Ownership
0 EOT -
*/
#define DST_EOT 0x01 /* End of transmit command */
#define DST_OSB 0x02 /* Ownership bit */
#define DST_CRC 0x04 /* CRC Error */
#define DST_OVR 0x08 /* Overrun */
#define DST_UDR 0x08 /* Underrun */
#define DST_RBIT 0x10 /* Residual bit */
#define DST_ABT 0x20 /* Abort */
#define DST_SHRT 0x40 /* Short Frame */
#define DST_EOM 0x80 /* End of Message */
/* Packet Descriptor Status bits */
#define ST_TX_EOM 0x80 /* End of frame */
#define ST_TX_UNDRRUN 0x08
#define ST_TX_OWNRSHP 0x02
#define ST_TX_EOT 0x01 /* End of transmission */
#define ST_RX_EOM 0x80 /* End of frame */
#define ST_RX_SHORT 0x40 /* Short frame */
#define ST_RX_ABORT 0x20 /* Abort */
#define ST_RX_RESBIT 0x10 /* Residual bit */
#define ST_RX_OVERRUN 0x08 /* Overrun */
#define ST_RX_CRC 0x04 /* CRC */
#define ST_RX_OWNRSHP 0x02
#define ST_ERROR_MASK 0x7C
/* Status Counter Registers */
#define CMCR 0x158 /* Counter Master Ctl Reg */
#define TECNTL 0x160 /* Tx EOM Counter L */
#define TECNTM 0x161 /* Tx EOM Counter M */
#define TECNTH 0x162 /* Tx EOM Counter H */
#define TECCR 0x163 /* Tx EOM Counter Ctl Reg */
#define URCNTL 0x164 /* Underrun Counter L */
#define URCNTH 0x165 /* Underrun Counter H */
#define URCCR 0x167 /* Underrun Counter Ctl Reg */
#define RECNTL 0x168 /* Rx EOM Counter L */
#define RECNTM 0x169 /* Rx EOM Counter M */
#define RECNTH 0x16a /* Rx EOM Counter H */
#define RECCR 0x16b /* Rx EOM Counter Ctl Reg */
#define ORCNTL 0x16c /* Overrun Counter L */
#define ORCNTH 0x16d /* Overrun Counter H */
#define ORCCR 0x16f /* Overrun Counter Ctl Reg */
#define CECNTL 0x170 /* CRC Counter L */
#define CECNTH 0x171 /* CRC Counter H */
#define CECCR 0x173 /* CRC Counter Ctl Reg */
#define ABCNTL 0x174 /* Abort frame Counter L */
#define ABCNTH 0x175 /* Abort frame Counter H */
#define ABCCR 0x177 /* Abort frame Counter Ctl Reg */
#define SHCNTL 0x178 /* Short frame Counter L */
#define SHCNTH 0x179 /* Short frame Counter H */
#define SHCCR 0x17b /* Short frame Counter Ctl Reg */
#define RSCNTL 0x17c /* Residual bit Counter L */
#define RSCNTH 0x17d /* Residual bit Counter H */
#define RSCCR 0x17f /* Residual bit Counter Ctl Reg */
/* Register Programming Constants */
#define IR0_DMIC 0x00000001
#define IR0_DMIB 0x00000002
#define IR0_DMIA 0x00000004
#define IR0_EFT 0x00000008
#define IR0_DMAREQ 0x00010000
#define IR0_TXINT 0x00020000
#define IR0_RXINTB 0x00040000
#define IR0_RXINTA 0x00080000
#define IR0_TXRDY 0x00100000
#define IR0_RXRDY 0x00200000
#define MD0_CRC16_0 0x00
#define MD0_CRC16_1 0x01
#define MD0_CRC32 0x02
#define MD0_CRC_CCITT 0x03
#define MD0_CRCC0 0x04
#define MD0_CRCC1 0x08
#define MD0_AUTO_ENA 0x10
#define MD0_ASYNC 0x00
#define MD0_BY_MSYNC 0x20
#define MD0_BY_BISYNC 0x40
#define MD0_BY_EXT 0x60
#define MD0_BIT_SYNC 0x80
#define MD0_TRANSP 0xc0
#define MD0_HDLC 0x80 /* Bit-sync HDLC mode */
#define MD0_CRC_NONE 0x00
#define MD0_CRC_16_0 0x04
#define MD0_CRC_16 0x05
#define MD0_CRC_ITU32 0x06
#define MD0_CRC_ITU 0x07
#define MD1_NOADDR 0x00
#define MD1_SADDR1 0x40
#define MD1_SADDR2 0x80
#define MD1_DADDR 0xc0
#define MD2_NRZI_IEEE 0x40
#define MD2_MANCHESTER 0x80
#define MD2_FM_MARK 0xA0
#define MD2_FM_SPACE 0xC0
#define MD2_LOOPBACK 0x03 /* Local data Loopback */
#define MD2_F_DUPLEX 0x00
#define MD2_AUTO_ECHO 0x01
#define MD2_LOOP_HI_Z 0x02
#define MD2_LOOP_MIR 0x03
#define MD2_ADPLL_X8 0x00
#define MD2_ADPLL_X16 0x08
#define MD2_ADPLL_X32 0x10
#define MD2_NRZ 0x00
#define MD2_NRZI 0x20
#define MD2_NRZ_IEEE 0x40
#define MD2_MANCH 0x00
#define MD2_FM1 0x20
#define MD2_FM0 0x40
#define MD2_FM 0x80
#define CTL_RTS 0x01
#define CTL_DTR 0x02
#define CTL_SYN 0x04
#define CTL_IDLC 0x10
#define CTL_UDRNC 0x20
#define CTL_URSKP 0x40
#define CTL_URCT 0x80
#define CTL_NORTS 0x01
#define CTL_NODTR 0x02
#define CTL_IDLE 0x10
#define RXS_BR0 0x01
#define RXS_BR1 0x02
#define RXS_BR2 0x04
#define RXS_BR3 0x08
#define RXS_ECLK 0x00
#define RXS_ECLK_NS 0x20
#define RXS_IBRG 0x40
#define RXS_PLL1 0x50
#define RXS_PLL2 0x60
#define RXS_PLL3 0x70
#define RXS_DRTXC 0x80
#define TXS_BR0 0x01
#define TXS_BR1 0x02
#define TXS_BR2 0x04
#define TXS_BR3 0x08
#define TXS_ECLK 0x00
#define TXS_IBRG 0x40
#define TXS_RCLK 0x60
#define TXS_DTRXC 0x80
#define EXS_RES0 0x01
#define EXS_RES1 0x02
#define EXS_RES2 0x04
#define EXS_TES0 0x10
#define EXS_TES1 0x20
#define EXS_TES2 0x40
#define CLK_BRG_MASK 0x0F
#define CLK_PIN_OUT 0x80
#define CLK_LINE 0x00 /* clock line input */
#define CLK_BRG 0x40 /* internal baud rate generator */
#define CLK_TX_RXCLK 0x60 /* TX clock from RX clock */
#define CMD_RX_RST 0x11
#define CMD_RX_ENA 0x12
#define CMD_RX_DIS 0x13
#define CMD_RX_CRC_INIT 0x14
#define CMD_RX_MSG_REJ 0x15
#define CMD_RX_MP_SRCH 0x16
#define CMD_RX_CRC_EXC 0x17
#define CMD_RX_CRC_FRC 0x18
#define CMD_TX_RST 0x01
#define CMD_TX_ENA 0x02
#define CMD_TX_DISA 0x03
#define CMD_TX_CRC_INIT 0x04
#define CMD_TX_CRC_EXC 0x05
#define CMD_TX_EOM 0x06
#define CMD_TX_ABORT 0x07
#define CMD_TX_MP_ON 0x08
#define CMD_TX_BUF_CLR 0x09
#define CMD_TX_DISB 0x0b
#define CMD_CH_RST 0x21
#define CMD_SRCH_MODE 0x31
#define CMD_NOP 0x00
#define CMD_RESET 0x21
#define CMD_TX_ENABLE 0x02
#define CMD_RX_ENABLE 0x12
#define ST0_RXRDY 0x01
#define ST0_TXRDY 0x02
#define ST0_RXINTB 0x20
#define ST0_RXINTA 0x40
#define ST0_TXINT 0x80
#define ST1_IDLE 0x01
#define ST1_ABORT 0x02
#define ST1_CDCD 0x04
#define ST1_CCTS 0x08
#define ST1_SYN_FLAG 0x10
#define ST1_CLMD 0x20
#define ST1_TXIDLE 0x40
#define ST1_UDRN 0x80
#define ST2_CRCE 0x04
#define ST2_ONRN 0x08
#define ST2_RBIT 0x10
#define ST2_ABORT 0x20
#define ST2_SHORT 0x40
#define ST2_EOM 0x80
#define ST3_RX_ENA 0x01
#define ST3_TX_ENA 0x02
#define ST3_DCD 0x04
#define ST3_CTS 0x08
#define ST3_SRCH_MODE 0x10
#define ST3_SLOOP 0x20
#define ST3_GPI 0x80
#define ST4_RDNR 0x01
#define ST4_RDCR 0x02
#define ST4_TDNR 0x04
#define ST4_TDCR 0x08
#define ST4_OCLM 0x20
#define ST4_CFT 0x40
#define ST4_CGPI 0x80
#define FST_CRCEF 0x04
#define FST_OVRNF 0x08
#define FST_RBIF 0x10
#define FST_ABTF 0x20
#define FST_SHRTF 0x40
#define FST_EOMF 0x80
#define IE0_RXRDY 0x01
#define IE0_TXRDY 0x02
#define IE0_RXINTB 0x20
#define IE0_RXINTA 0x40
#define IE0_TXINT 0x80
#define IE0_UDRN 0x00008000 /* TX underrun MSCI interrupt enable */
#define IE0_CDCD 0x00000400 /* CD level change interrupt enable */
#define IE1_IDLD 0x01
#define IE1_ABTD 0x02
#define IE1_CDCD 0x04
#define IE1_CCTS 0x08
#define IE1_SYNCD 0x10
#define IE1_CLMD 0x20
#define IE1_IDL 0x40
#define IE1_UDRN 0x80
#define IE2_CRCE 0x04
#define IE2_OVRN 0x08
#define IE2_RBIT 0x10
#define IE2_ABT 0x20
#define IE2_SHRT 0x40
#define IE2_EOM 0x80
#define IE4_RDNR 0x01
#define IE4_RDCR 0x02
#define IE4_TDNR 0x04
#define IE4_TDCR 0x08
#define IE4_OCLM 0x20
#define IE4_CFT 0x40
#define IE4_CGPI 0x80
#define FIE_CRCEF 0x04
#define FIE_OVRNF 0x08
#define FIE_RBIF 0x10
#define FIE_ABTF 0x20
#define FIE_SHRTF 0x40
#define FIE_EOMF 0x80
#define DSR_DWE 0x01
#define DSR_DE 0x02
#define DSR_REF 0x04
#define DSR_UDRF 0x04
#define DSR_COA 0x08
#define DSR_COF 0x10
#define DSR_BOF 0x20
#define DSR_EOM 0x40
#define DSR_EOT 0x80
#define DIR_REF 0x04
#define DIR_UDRF 0x04
#define DIR_COA 0x08
#define DIR_COF 0x10
#define DIR_BOF 0x20
#define DIR_EOM 0x40
#define DIR_EOT 0x80
#define DIR_REFE 0x04
#define DIR_UDRFE 0x04
#define DIR_COAE 0x08
#define DIR_COFE 0x10
#define DIR_BOFE 0x20
#define DIR_EOME 0x40
#define DIR_EOTE 0x80
#define DMR_CNTE 0x02
#define DMR_NF 0x04
#define DMR_SEOME 0x08
#define DMR_TMOD 0x10
#define DMER_DME 0x80 /* DMA Master Enable */
#define DCR_SW_ABT 0x01
#define DCR_FCT_CLR 0x02
#define DCR_ABORT 0x01
#define DCR_CLEAR_EOF 0x02
#define PCR_COTE 0x80
#define PCR_PR0 0x01
#define PCR_PR1 0x02
#define PCR_PR2 0x04
#define PCR_CCC 0x08
#define PCR_BRC 0x10
#define PCR_OSB 0x40
#define PCR_BURST 0x80
#endif /* (__HD64572_H) */
+379
View File
@@ -0,0 +1,379 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Generic HDLC support routines for Linux
*
* Copyright (C) 1999 - 2008 Krzysztof Halasa <khc@pm.waw.pl>
*
* Currently supported:
* * raw IP-in-HDLC
* * Cisco HDLC
* * Frame Relay with ANSI or CCITT LMI (both user and network side)
* * PPP
* * X.25
*
* Use sethdlc utility to set line parameters, protocol and PVCs
*
* How does it work:
* - proto->open(), close(), start(), stop() calls are serialized.
* The order is: open, [ start, stop ... ] close ...
* - proto->start() and stop() are called with spin_lock_irq held.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/errno.h>
#include <linux/hdlc.h>
#include <linux/if_arp.h>
#include <linux/inetdevice.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/pkt_sched.h>
#include <linux/poll.h>
#include <linux/rtnetlink.h>
#include <linux/skbuff.h>
#include <linux/slab.h>
#include <net/net_namespace.h>
static const char *version = "HDLC support module revision 1.22";
#undef DEBUG_LINK
static struct hdlc_proto *first_proto;
static int hdlc_rcv(struct sk_buff *skb, struct net_device *dev,
struct packet_type *p, struct net_device *orig_dev)
{
struct hdlc_device *hdlc;
/* First make sure "dev" is an HDLC device */
if (!(dev->priv_flags & IFF_WAN_HDLC)) {
kfree_skb(skb);
return NET_RX_SUCCESS;
}
hdlc = dev_to_hdlc(dev);
if (!net_eq(dev_net(dev), &init_net)) {
kfree_skb(skb);
return 0;
}
BUG_ON(!hdlc->proto->netif_rx);
return hdlc->proto->netif_rx(skb);
}
netdev_tx_t hdlc_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
hdlc_device *hdlc = dev_to_hdlc(dev);
if (hdlc->proto->xmit)
return hdlc->proto->xmit(skb, dev);
return hdlc->xmit(skb, dev); /* call hardware driver directly */
}
EXPORT_SYMBOL(hdlc_start_xmit);
static inline void hdlc_proto_start(struct net_device *dev)
{
hdlc_device *hdlc = dev_to_hdlc(dev);
if (hdlc->proto->start)
hdlc->proto->start(dev);
}
static inline void hdlc_proto_stop(struct net_device *dev)
{
hdlc_device *hdlc = dev_to_hdlc(dev);
if (hdlc->proto->stop)
hdlc->proto->stop(dev);
}
static int hdlc_device_event(struct notifier_block *this, unsigned long event,
void *ptr)
{
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
hdlc_device *hdlc;
unsigned long flags;
int on;
if (!net_eq(dev_net(dev), &init_net))
return NOTIFY_DONE;
if (!(dev->priv_flags & IFF_WAN_HDLC))
return NOTIFY_DONE; /* not an HDLC device */
if (event != NETDEV_CHANGE)
return NOTIFY_DONE; /* Only interested in carrier changes */
on = netif_carrier_ok(dev);
#ifdef DEBUG_LINK
printk(KERN_DEBUG "%s: hdlc_device_event NETDEV_CHANGE, carrier %i\n",
dev->name, on);
#endif
hdlc = dev_to_hdlc(dev);
spin_lock_irqsave(&hdlc->state_lock, flags);
if (hdlc->carrier == on)
goto carrier_exit; /* no change in DCD line level */
hdlc->carrier = on;
if (!hdlc->open)
goto carrier_exit;
if (hdlc->carrier) {
netdev_info(dev, "Carrier detected\n");
hdlc_proto_start(dev);
} else {
netdev_info(dev, "Carrier lost\n");
hdlc_proto_stop(dev);
}
carrier_exit:
spin_unlock_irqrestore(&hdlc->state_lock, flags);
return NOTIFY_DONE;
}
/* Must be called by hardware driver when HDLC device is being opened */
int hdlc_open(struct net_device *dev)
{
hdlc_device *hdlc = dev_to_hdlc(dev);
#ifdef DEBUG_LINK
printk(KERN_DEBUG "%s: hdlc_open() carrier %i open %i\n", dev->name,
hdlc->carrier, hdlc->open);
#endif
if (!hdlc->proto)
return -ENOSYS; /* no protocol attached */
if (hdlc->proto->open) {
int result = hdlc->proto->open(dev);
if (result)
return result;
}
spin_lock_irq(&hdlc->state_lock);
if (hdlc->carrier) {
netdev_info(dev, "Carrier detected\n");
hdlc_proto_start(dev);
} else {
netdev_info(dev, "No carrier\n");
}
hdlc->open = 1;
spin_unlock_irq(&hdlc->state_lock);
return 0;
}
EXPORT_SYMBOL(hdlc_open);
/* Must be called by hardware driver when HDLC device is being closed */
void hdlc_close(struct net_device *dev)
{
hdlc_device *hdlc = dev_to_hdlc(dev);
#ifdef DEBUG_LINK
printk(KERN_DEBUG "%s: hdlc_close() carrier %i open %i\n", dev->name,
hdlc->carrier, hdlc->open);
#endif
spin_lock_irq(&hdlc->state_lock);
hdlc->open = 0;
if (hdlc->carrier)
hdlc_proto_stop(dev);
spin_unlock_irq(&hdlc->state_lock);
if (hdlc->proto->close)
hdlc->proto->close(dev);
}
EXPORT_SYMBOL(hdlc_close);
int hdlc_ioctl(struct net_device *dev, struct if_settings *ifs)
{
struct hdlc_proto *proto = first_proto;
int result;
if (dev_to_hdlc(dev)->proto) {
result = dev_to_hdlc(dev)->proto->ioctl(dev, ifs);
if (result != -EINVAL)
return result;
}
/* Not handled by currently attached protocol (if any) */
while (proto) {
result = proto->ioctl(dev, ifs);
if (result != -EINVAL)
return result;
proto = proto->next;
}
return -EINVAL;
}
EXPORT_SYMBOL(hdlc_ioctl);
static const struct header_ops hdlc_null_ops;
static void hdlc_setup_dev(struct net_device *dev)
{
/* Re-init all variables changed by HDLC protocol drivers,
* including ether_setup() called from hdlc_raw_eth.c.
*/
dev->flags = IFF_POINTOPOINT | IFF_NOARP;
dev->priv_flags = IFF_WAN_HDLC;
dev->mtu = HDLC_MAX_MTU;
dev->min_mtu = 68;
dev->max_mtu = HDLC_MAX_MTU;
dev->type = ARPHRD_RAWHDLC;
dev->hard_header_len = 0;
dev->needed_headroom = 0;
dev->addr_len = 0;
dev->header_ops = &hdlc_null_ops;
}
static void hdlc_setup(struct net_device *dev)
{
hdlc_device *hdlc = dev_to_hdlc(dev);
hdlc_setup_dev(dev);
hdlc->carrier = 1;
hdlc->open = 0;
spin_lock_init(&hdlc->state_lock);
}
struct net_device *alloc_hdlcdev(void *priv)
{
struct net_device *dev;
dev = alloc_netdev(sizeof(struct hdlc_device), "hdlc%d",
NET_NAME_UNKNOWN, hdlc_setup);
if (dev)
dev_to_hdlc(dev)->priv = priv;
return dev;
}
EXPORT_SYMBOL(alloc_hdlcdev);
void unregister_hdlc_device(struct net_device *dev)
{
rtnl_lock();
detach_hdlc_protocol(dev);
unregister_netdevice(dev);
rtnl_unlock();
}
EXPORT_SYMBOL(unregister_hdlc_device);
int attach_hdlc_protocol(struct net_device *dev, struct hdlc_proto *proto,
size_t size)
{
int err;
err = detach_hdlc_protocol(dev);
if (err)
return err;
if (!try_module_get(proto->module))
return -ENOSYS;
if (size) {
dev_to_hdlc(dev)->state = kmalloc(size, GFP_KERNEL);
if (!dev_to_hdlc(dev)->state) {
module_put(proto->module);
return -ENOBUFS;
}
}
dev_to_hdlc(dev)->proto = proto;
return 0;
}
EXPORT_SYMBOL(attach_hdlc_protocol);
int detach_hdlc_protocol(struct net_device *dev)
{
hdlc_device *hdlc = dev_to_hdlc(dev);
int err;
if (hdlc->proto) {
err = call_netdevice_notifiers(NETDEV_PRE_TYPE_CHANGE, dev);
err = notifier_to_errno(err);
if (err) {
netdev_err(dev, "Refused to change device type\n");
return err;
}
if (hdlc->proto->detach)
hdlc->proto->detach(dev);
module_put(hdlc->proto->module);
hdlc->proto = NULL;
}
kfree(hdlc->state);
hdlc->state = NULL;
hdlc_setup_dev(dev);
return 0;
}
EXPORT_SYMBOL(detach_hdlc_protocol);
void register_hdlc_protocol(struct hdlc_proto *proto)
{
rtnl_lock();
proto->next = first_proto;
first_proto = proto;
rtnl_unlock();
}
EXPORT_SYMBOL(register_hdlc_protocol);
void unregister_hdlc_protocol(struct hdlc_proto *proto)
{
struct hdlc_proto **p;
rtnl_lock();
p = &first_proto;
while (*p != proto) {
BUG_ON(!*p);
p = &((*p)->next);
}
*p = proto->next;
rtnl_unlock();
}
EXPORT_SYMBOL(unregister_hdlc_protocol);
MODULE_AUTHOR("Krzysztof Halasa <khc@pm.waw.pl>");
MODULE_DESCRIPTION("HDLC support module");
MODULE_LICENSE("GPL v2");
static struct packet_type hdlc_packet_type __read_mostly = {
.type = cpu_to_be16(ETH_P_HDLC),
.func = hdlc_rcv,
};
static struct notifier_block hdlc_notifier = {
.notifier_call = hdlc_device_event,
};
static int __init hdlc_module_init(void)
{
int result;
pr_info("%s\n", version);
result = register_netdevice_notifier(&hdlc_notifier);
if (result)
return result;
dev_add_pack(&hdlc_packet_type);
return 0;
}
static void __exit hdlc_module_exit(void)
{
dev_remove_pack(&hdlc_packet_type);
unregister_netdevice_notifier(&hdlc_notifier);
}
module_init(hdlc_module_init);
module_exit(hdlc_module_exit);
+383
View File
@@ -0,0 +1,383 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Generic HDLC support routines for Linux
* Cisco HDLC support
*
* Copyright (C) 2000 - 2006 Krzysztof Halasa <khc@pm.waw.pl>
*/
#include <linux/errno.h>
#include <linux/hdlc.h>
#include <linux/if_arp.h>
#include <linux/inetdevice.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pkt_sched.h>
#include <linux/poll.h>
#include <linux/rtnetlink.h>
#include <linux/skbuff.h>
#undef DEBUG_HARD_HEADER
#define CISCO_MULTICAST 0x8F /* Cisco multicast address */
#define CISCO_UNICAST 0x0F /* Cisco unicast address */
#define CISCO_KEEPALIVE 0x8035 /* Cisco keepalive protocol */
#define CISCO_SYS_INFO 0x2000 /* Cisco interface/system info */
#define CISCO_ADDR_REQ 0 /* Cisco address request */
#define CISCO_ADDR_REPLY 1 /* Cisco address reply */
#define CISCO_KEEPALIVE_REQ 2 /* Cisco keepalive request */
struct hdlc_header {
u8 address;
u8 control;
__be16 protocol;
} __packed;
struct cisco_packet {
__be32 type; /* code */
__be32 par1;
__be32 par2;
__be16 rel; /* reliability */
__be32 time;
} __packed;
#define CISCO_PACKET_LEN 18
#define CISCO_BIG_PACKET_LEN 20
struct cisco_state {
cisco_proto settings;
struct timer_list timer;
struct net_device *dev;
spinlock_t lock;
unsigned long last_poll;
int up;
u32 txseq; /* TX sequence number, 0 = none */
u32 rxseq; /* RX sequence number */
};
static int cisco_ioctl(struct net_device *dev, struct if_settings *ifs);
static inline struct cisco_state *state(hdlc_device *hdlc)
{
return (struct cisco_state *)hdlc->state;
}
static int cisco_hard_header(struct sk_buff *skb, struct net_device *dev,
u16 type, const void *daddr, const void *saddr,
unsigned int len)
{
struct hdlc_header *data;
#ifdef DEBUG_HARD_HEADER
netdev_dbg(dev, "%s called\n", __func__);
#endif
skb_push(skb, sizeof(struct hdlc_header));
data = (struct hdlc_header *)skb->data;
if (type == CISCO_KEEPALIVE)
data->address = CISCO_MULTICAST;
else
data->address = CISCO_UNICAST;
data->control = 0;
data->protocol = htons(type);
return sizeof(struct hdlc_header);
}
static void cisco_keepalive_send(struct net_device *dev, u32 type,
__be32 par1, __be32 par2)
{
struct sk_buff *skb;
struct cisco_packet *data;
skb = dev_alloc_skb(sizeof(struct hdlc_header) +
sizeof(struct cisco_packet));
if (!skb)
return;
skb_reserve(skb, 4);
cisco_hard_header(skb, dev, CISCO_KEEPALIVE, NULL, NULL, 0);
data = (struct cisco_packet *)(skb->data + 4);
data->type = htonl(type);
data->par1 = par1;
data->par2 = par2;
data->rel = cpu_to_be16(0xFFFF);
/* we will need do_div here if 1000 % HZ != 0 */
data->time = htonl((jiffies - INITIAL_JIFFIES) * (1000 / HZ));
skb_put(skb, sizeof(struct cisco_packet));
skb->priority = TC_PRIO_CONTROL;
skb->dev = dev;
skb->protocol = htons(ETH_P_HDLC);
skb_reset_network_header(skb);
dev_queue_xmit(skb);
}
static __be16 cisco_type_trans(struct sk_buff *skb, struct net_device *dev)
{
struct hdlc_header *data = (struct hdlc_header *)skb->data;
if (skb->len < sizeof(struct hdlc_header))
return cpu_to_be16(ETH_P_HDLC);
if (data->address != CISCO_MULTICAST &&
data->address != CISCO_UNICAST)
return cpu_to_be16(ETH_P_HDLC);
switch (data->protocol) {
case cpu_to_be16(ETH_P_IP):
case cpu_to_be16(ETH_P_IPX):
case cpu_to_be16(ETH_P_IPV6):
skb_pull(skb, sizeof(struct hdlc_header));
return data->protocol;
default:
return cpu_to_be16(ETH_P_HDLC);
}
}
static int cisco_rx(struct sk_buff *skb)
{
struct net_device *dev = skb->dev;
hdlc_device *hdlc = dev_to_hdlc(dev);
struct cisco_state *st = state(hdlc);
struct hdlc_header *data = (struct hdlc_header *)skb->data;
struct cisco_packet *cisco_data;
struct in_device *in_dev;
__be32 addr, mask;
u32 ack;
if (skb->len < sizeof(struct hdlc_header))
goto rx_error;
if (data->address != CISCO_MULTICAST &&
data->address != CISCO_UNICAST)
goto rx_error;
switch (ntohs(data->protocol)) {
case CISCO_SYS_INFO:
/* Packet is not needed, drop it. */
dev_kfree_skb_any(skb);
return NET_RX_SUCCESS;
case CISCO_KEEPALIVE:
if ((skb->len != sizeof(struct hdlc_header) +
CISCO_PACKET_LEN) &&
(skb->len != sizeof(struct hdlc_header) +
CISCO_BIG_PACKET_LEN)) {
netdev_info(dev, "Invalid length of Cisco control packet (%d bytes)\n",
skb->len);
goto rx_error;
}
cisco_data = (struct cisco_packet *)(skb->data + sizeof
(struct hdlc_header));
switch (ntohl(cisco_data->type)) {
case CISCO_ADDR_REQ: /* Stolen from syncppp.c :-) */
rcu_read_lock();
in_dev = __in_dev_get_rcu(dev);
addr = 0;
mask = ~cpu_to_be32(0); /* is the mask correct? */
if (in_dev != NULL) {
const struct in_ifaddr *ifa;
in_dev_for_each_ifa_rcu(ifa, in_dev) {
if (strcmp(dev->name,
ifa->ifa_label) == 0) {
addr = ifa->ifa_local;
mask = ifa->ifa_mask;
break;
}
}
cisco_keepalive_send(dev, CISCO_ADDR_REPLY,
addr, mask);
}
rcu_read_unlock();
dev_kfree_skb_any(skb);
return NET_RX_SUCCESS;
case CISCO_ADDR_REPLY:
netdev_info(dev, "Unexpected Cisco IP address reply\n");
goto rx_error;
case CISCO_KEEPALIVE_REQ:
spin_lock(&st->lock);
st->rxseq = ntohl(cisco_data->par1);
ack = ntohl(cisco_data->par2);
if (ack && (ack == st->txseq ||
/* our current REQ may be in transit */
ack == st->txseq - 1)) {
st->last_poll = jiffies;
if (!st->up) {
u32 sec, min, hrs, days;
sec = ntohl(cisco_data->time) / 1000;
min = sec / 60; sec -= min * 60;
hrs = min / 60; min -= hrs * 60;
days = hrs / 24; hrs -= days * 24;
netdev_info(dev, "Link up (peer uptime %ud%uh%um%us)\n",
days, hrs, min, sec);
netif_dormant_off(dev);
st->up = 1;
}
}
spin_unlock(&st->lock);
dev_kfree_skb_any(skb);
return NET_RX_SUCCESS;
} /* switch (keepalive type) */
} /* switch (protocol) */
netdev_info(dev, "Unsupported protocol %x\n", ntohs(data->protocol));
dev_kfree_skb_any(skb);
return NET_RX_DROP;
rx_error:
dev->stats.rx_errors++; /* Mark error */
dev_kfree_skb_any(skb);
return NET_RX_DROP;
}
static void cisco_timer(struct timer_list *t)
{
struct cisco_state *st = timer_container_of(st, t, timer);
struct net_device *dev = st->dev;
spin_lock(&st->lock);
if (st->up &&
time_after(jiffies, st->last_poll + st->settings.timeout * HZ)) {
st->up = 0;
netdev_info(dev, "Link down\n");
netif_dormant_on(dev);
}
cisco_keepalive_send(dev, CISCO_KEEPALIVE_REQ, htonl(++st->txseq),
htonl(st->rxseq));
spin_unlock(&st->lock);
st->timer.expires = jiffies + st->settings.interval * HZ;
add_timer(&st->timer);
}
static void cisco_start(struct net_device *dev)
{
hdlc_device *hdlc = dev_to_hdlc(dev);
struct cisco_state *st = state(hdlc);
unsigned long flags;
spin_lock_irqsave(&st->lock, flags);
st->up = st->txseq = st->rxseq = 0;
spin_unlock_irqrestore(&st->lock, flags);
st->dev = dev;
timer_setup(&st->timer, cisco_timer, 0);
st->timer.expires = jiffies + HZ; /* First poll after 1 s */
add_timer(&st->timer);
}
static void cisco_stop(struct net_device *dev)
{
hdlc_device *hdlc = dev_to_hdlc(dev);
struct cisco_state *st = state(hdlc);
unsigned long flags;
timer_delete_sync(&st->timer);
spin_lock_irqsave(&st->lock, flags);
netif_dormant_on(dev);
st->up = st->txseq = 0;
spin_unlock_irqrestore(&st->lock, flags);
}
static struct hdlc_proto proto = {
.start = cisco_start,
.stop = cisco_stop,
.type_trans = cisco_type_trans,
.ioctl = cisco_ioctl,
.netif_rx = cisco_rx,
.module = THIS_MODULE,
};
static const struct header_ops cisco_header_ops = {
.create = cisco_hard_header,
};
static int cisco_ioctl(struct net_device *dev, struct if_settings *ifs)
{
cisco_proto __user *cisco_s = ifs->ifs_ifsu.cisco;
const size_t size = sizeof(cisco_proto);
cisco_proto new_settings;
hdlc_device *hdlc = dev_to_hdlc(dev);
int result;
switch (ifs->type) {
case IF_GET_PROTO:
if (dev_to_hdlc(dev)->proto != &proto)
return -EINVAL;
ifs->type = IF_PROTO_CISCO;
if (ifs->size < size) {
ifs->size = size; /* data size wanted */
return -ENOBUFS;
}
if (copy_to_user(cisco_s, &state(hdlc)->settings, size))
return -EFAULT;
return 0;
case IF_PROTO_CISCO:
if (!capable(CAP_NET_ADMIN))
return -EPERM;
if (dev->flags & IFF_UP)
return -EBUSY;
if (copy_from_user(&new_settings, cisco_s, size))
return -EFAULT;
if (new_settings.interval < 1 ||
new_settings.timeout < 2)
return -EINVAL;
result = hdlc->attach(dev, ENCODING_NRZ,
PARITY_CRC16_PR1_CCITT);
if (result)
return result;
result = attach_hdlc_protocol(dev, &proto,
sizeof(struct cisco_state));
if (result)
return result;
memcpy(&state(hdlc)->settings, &new_settings, size);
spin_lock_init(&state(hdlc)->lock);
dev->header_ops = &cisco_header_ops;
dev->hard_header_len = sizeof(struct hdlc_header);
dev->type = ARPHRD_CISCO;
call_netdevice_notifiers(NETDEV_POST_TYPE_CHANGE, dev);
netif_dormant_on(dev);
return 0;
}
return -EINVAL;
}
static int __init hdlc_cisco_init(void)
{
register_hdlc_protocol(&proto);
return 0;
}
static void __exit hdlc_cisco_exit(void)
{
unregister_hdlc_protocol(&proto);
}
module_init(hdlc_cisco_init);
module_exit(hdlc_cisco_exit);
MODULE_AUTHOR("Krzysztof Halasa <khc@pm.waw.pl>");
MODULE_DESCRIPTION("Cisco HDLC protocol support for generic HDLC");
MODULE_LICENSE("GPL v2");
File diff suppressed because it is too large Load Diff
+734
View File
@@ -0,0 +1,734 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Generic HDLC support routines for Linux
* Point-to-point protocol support
*
* Copyright (C) 1999 - 2008 Krzysztof Halasa <khc@pm.waw.pl>
*/
#include <linux/errno.h>
#include <linux/hdlc.h>
#include <linux/if_arp.h>
#include <linux/inetdevice.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pkt_sched.h>
#include <linux/poll.h>
#include <linux/skbuff.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#define DEBUG_CP 0 /* also bytes# to dump */
#define DEBUG_STATE 0
#define DEBUG_HARD_HEADER 0
#define HDLC_ADDR_ALLSTATIONS 0xFF
#define HDLC_CTRL_UI 0x03
#define PID_LCP 0xC021
#define PID_IP 0x0021
#define PID_IPCP 0x8021
#define PID_IPV6 0x0057
#define PID_IPV6CP 0x8057
enum {IDX_LCP = 0, IDX_IPCP, IDX_IPV6CP, IDX_COUNT};
enum {CP_CONF_REQ = 1, CP_CONF_ACK, CP_CONF_NAK, CP_CONF_REJ, CP_TERM_REQ,
CP_TERM_ACK, CP_CODE_REJ, LCP_PROTO_REJ, LCP_ECHO_REQ, LCP_ECHO_REPLY,
LCP_DISC_REQ, CP_CODES};
#if DEBUG_CP
static const char *const code_names[CP_CODES] = {
"0", "ConfReq", "ConfAck", "ConfNak", "ConfRej", "TermReq",
"TermAck", "CodeRej", "ProtoRej", "EchoReq", "EchoReply", "Discard"
};
static char debug_buffer[64 + 3 * DEBUG_CP];
#endif
enum {LCP_OPTION_MRU = 1, LCP_OPTION_ACCM, LCP_OPTION_MAGIC = 5};
struct hdlc_header {
u8 address;
u8 control;
__be16 protocol;
};
struct cp_header {
u8 code;
u8 id;
__be16 len;
};
struct proto {
struct net_device *dev;
struct timer_list timer;
unsigned long timeout;
u16 pid; /* protocol ID */
u8 state;
u8 cr_id; /* ID of last Configuration-Request */
u8 restart_counter;
};
struct ppp {
struct proto protos[IDX_COUNT];
spinlock_t lock;
unsigned long last_pong;
unsigned int req_timeout, cr_retries, term_retries;
unsigned int keepalive_interval, keepalive_timeout;
u8 seq; /* local sequence number for requests */
u8 echo_id; /* ID of last Echo-Request (LCP) */
};
enum {CLOSED = 0, STOPPED, STOPPING, REQ_SENT, ACK_RECV, ACK_SENT, OPENED,
STATES, STATE_MASK = 0xF};
enum {START = 0, STOP, TO_GOOD, TO_BAD, RCR_GOOD, RCR_BAD, RCA, RCN, RTR, RTA,
RUC, RXJ_GOOD, RXJ_BAD, EVENTS};
enum {INV = 0x10, IRC = 0x20, ZRC = 0x40, SCR = 0x80, SCA = 0x100,
SCN = 0x200, STR = 0x400, STA = 0x800, SCJ = 0x1000};
#if DEBUG_STATE
static const char *const state_names[STATES] = {
"Closed", "Stopped", "Stopping", "ReqSent", "AckRecv", "AckSent",
"Opened"
};
static const char *const event_names[EVENTS] = {
"Start", "Stop", "TO+", "TO-", "RCR+", "RCR-", "RCA", "RCN",
"RTR", "RTA", "RUC", "RXJ+", "RXJ-"
};
#endif
static struct sk_buff_head tx_queue; /* used when holding the spin lock */
static int ppp_ioctl(struct net_device *dev, struct if_settings *ifs);
static inline struct ppp *get_ppp(struct net_device *dev)
{
return (struct ppp *)dev_to_hdlc(dev)->state;
}
static inline struct proto *get_proto(struct net_device *dev, u16 pid)
{
struct ppp *ppp = get_ppp(dev);
switch (pid) {
case PID_LCP:
return &ppp->protos[IDX_LCP];
case PID_IPCP:
return &ppp->protos[IDX_IPCP];
case PID_IPV6CP:
return &ppp->protos[IDX_IPV6CP];
default:
return NULL;
}
}
static inline const char *proto_name(u16 pid)
{
switch (pid) {
case PID_IPCP:
return "IPCP";
case PID_IPV6CP:
return "IPV6CP";
default:
return "LCP";
}
}
static __be16 ppp_type_trans(struct sk_buff *skb, struct net_device *dev)
{
struct hdlc_header *data = (struct hdlc_header *)skb->data;
if (skb->len < sizeof(struct hdlc_header))
return htons(ETH_P_HDLC);
if (data->address != HDLC_ADDR_ALLSTATIONS ||
data->control != HDLC_CTRL_UI)
return htons(ETH_P_HDLC);
switch (data->protocol) {
case cpu_to_be16(PID_IP):
skb_pull(skb, sizeof(struct hdlc_header));
return htons(ETH_P_IP);
case cpu_to_be16(PID_IPV6):
skb_pull(skb, sizeof(struct hdlc_header));
return htons(ETH_P_IPV6);
default:
return htons(ETH_P_HDLC);
}
}
static int ppp_hard_header(struct sk_buff *skb, struct net_device *dev,
u16 type, const void *daddr, const void *saddr,
unsigned int len)
{
struct hdlc_header *data;
#if DEBUG_HARD_HEADER
printk(KERN_DEBUG "%s: ppp_hard_header() called\n", dev->name);
#endif
skb_push(skb, sizeof(struct hdlc_header));
data = (struct hdlc_header *)skb->data;
data->address = HDLC_ADDR_ALLSTATIONS;
data->control = HDLC_CTRL_UI;
switch (type) {
case ETH_P_IP:
data->protocol = htons(PID_IP);
break;
case ETH_P_IPV6:
data->protocol = htons(PID_IPV6);
break;
case PID_LCP:
case PID_IPCP:
case PID_IPV6CP:
data->protocol = htons(type);
break;
default: /* unknown protocol */
data->protocol = 0;
}
return sizeof(struct hdlc_header);
}
static void ppp_tx_flush(void)
{
struct sk_buff *skb;
while ((skb = skb_dequeue(&tx_queue)) != NULL)
dev_queue_xmit(skb);
}
static void ppp_tx_cp(struct net_device *dev, u16 pid, u8 code,
u8 id, unsigned int len, const void *data)
{
struct sk_buff *skb;
struct cp_header *cp;
unsigned int magic_len = 0;
static u32 magic;
#if DEBUG_CP
int i;
char *ptr;
#endif
if (pid == PID_LCP && (code == LCP_ECHO_REQ || code == LCP_ECHO_REPLY))
magic_len = sizeof(magic);
skb = dev_alloc_skb(sizeof(struct hdlc_header) +
sizeof(struct cp_header) + magic_len + len);
if (!skb)
return;
skb_reserve(skb, sizeof(struct hdlc_header));
cp = skb_put(skb, sizeof(struct cp_header));
cp->code = code;
cp->id = id;
cp->len = htons(sizeof(struct cp_header) + magic_len + len);
if (magic_len)
skb_put_data(skb, &magic, magic_len);
if (len)
skb_put_data(skb, data, len);
#if DEBUG_CP
BUG_ON(code >= CP_CODES);
ptr = debug_buffer;
*ptr = '\x0';
for (i = 0; i < min_t(unsigned int, magic_len + len, DEBUG_CP); i++) {
sprintf(ptr, " %02X", skb->data[sizeof(struct cp_header) + i]);
ptr += strlen(ptr);
}
printk(KERN_DEBUG "%s: TX %s [%s id 0x%X]%s\n", dev->name,
proto_name(pid), code_names[code], id, debug_buffer);
#endif
ppp_hard_header(skb, dev, pid, NULL, NULL, 0);
skb->priority = TC_PRIO_CONTROL;
skb->dev = dev;
skb->protocol = htons(ETH_P_HDLC);
skb_reset_network_header(skb);
skb_queue_tail(&tx_queue, skb);
}
/* State transition table (compare STD-51)
Events Actions
TO+ = Timeout with counter > 0 irc = Initialize-Restart-Count
TO- = Timeout with counter expired zrc = Zero-Restart-Count
RCR+ = Receive-Configure-Request (Good) scr = Send-Configure-Request
RCR- = Receive-Configure-Request (Bad)
RCA = Receive-Configure-Ack sca = Send-Configure-Ack
RCN = Receive-Configure-Nak/Rej scn = Send-Configure-Nak/Rej
RTR = Receive-Terminate-Request str = Send-Terminate-Request
RTA = Receive-Terminate-Ack sta = Send-Terminate-Ack
RUC = Receive-Unknown-Code scj = Send-Code-Reject
RXJ+ = Receive-Code-Reject (permitted)
or Receive-Protocol-Reject
RXJ- = Receive-Code-Reject (catastrophic)
or Receive-Protocol-Reject
*/
static int cp_table[EVENTS][STATES] = {
/* CLOSED STOPPED STOPPING REQ_SENT ACK_RECV ACK_SENT OPENED
0 1 2 3 4 5 6 */
{IRC|SCR|3, INV , INV , INV , INV , INV , INV }, /* START */
{ INV , 0 , 0 , 0 , 0 , 0 , 0 }, /* STOP */
{ INV , INV ,STR|2, SCR|3 ,SCR|3, SCR|5 , INV }, /* TO+ */
{ INV , INV , 1 , 1 , 1 , 1 , INV }, /* TO- */
{ STA|0 ,IRC|SCR|SCA|5, 2 , SCA|5 ,SCA|6, SCA|5 ,SCR|SCA|5}, /* RCR+ */
{ STA|0 ,IRC|SCR|SCN|3, 2 , SCN|3 ,SCN|4, SCN|3 ,SCR|SCN|3}, /* RCR- */
{ STA|0 , STA|1 , 2 , IRC|4 ,SCR|3, 6 , SCR|3 }, /* RCA */
{ STA|0 , STA|1 , 2 ,IRC|SCR|3,SCR|3,IRC|SCR|5, SCR|3 }, /* RCN */
{ STA|0 , STA|1 ,STA|2, STA|3 ,STA|3, STA|3 ,ZRC|STA|2}, /* RTR */
{ 0 , 1 , 1 , 3 , 3 , 5 , SCR|3 }, /* RTA */
{ SCJ|0 , SCJ|1 ,SCJ|2, SCJ|3 ,SCJ|4, SCJ|5 , SCJ|6 }, /* RUC */
{ 0 , 1 , 2 , 3 , 3 , 5 , 6 }, /* RXJ+ */
{ 0 , 1 , 1 , 1 , 1 , 1 ,IRC|STR|2}, /* RXJ- */
};
/* SCA: RCR+ must supply id, len and data
SCN: RCR- must supply code, id, len and data
STA: RTR must supply id
SCJ: RUC must supply CP packet len and data */
static void ppp_cp_event(struct net_device *dev, u16 pid, u16 event, u8 code,
u8 id, unsigned int len, const void *data)
{
int old_state, action;
struct ppp *ppp = get_ppp(dev);
struct proto *proto = get_proto(dev, pid);
old_state = proto->state;
BUG_ON(old_state >= STATES);
BUG_ON(event >= EVENTS);
#if DEBUG_STATE
printk(KERN_DEBUG "%s: %s ppp_cp_event(%s) %s ...\n", dev->name,
proto_name(pid), event_names[event], state_names[proto->state]);
#endif
action = cp_table[event][old_state];
proto->state = action & STATE_MASK;
if (action & (SCR | STR)) /* set Configure-Req/Terminate-Req timer */
mod_timer(&proto->timer, proto->timeout =
jiffies + ppp->req_timeout * HZ);
if (action & ZRC)
proto->restart_counter = 0;
if (action & IRC)
proto->restart_counter = (proto->state == STOPPING) ?
ppp->term_retries : ppp->cr_retries;
if (action & SCR) /* send Configure-Request */
ppp_tx_cp(dev, pid, CP_CONF_REQ, proto->cr_id = ++ppp->seq,
0, NULL);
if (action & SCA) /* send Configure-Ack */
ppp_tx_cp(dev, pid, CP_CONF_ACK, id, len, data);
if (action & SCN) /* send Configure-Nak/Reject */
ppp_tx_cp(dev, pid, code, id, len, data);
if (action & STR) /* send Terminate-Request */
ppp_tx_cp(dev, pid, CP_TERM_REQ, ++ppp->seq, 0, NULL);
if (action & STA) /* send Terminate-Ack */
ppp_tx_cp(dev, pid, CP_TERM_ACK, id, 0, NULL);
if (action & SCJ) /* send Code-Reject */
ppp_tx_cp(dev, pid, CP_CODE_REJ, ++ppp->seq, len, data);
if (old_state != OPENED && proto->state == OPENED) {
netdev_info(dev, "%s up\n", proto_name(pid));
if (pid == PID_LCP) {
netif_dormant_off(dev);
ppp_cp_event(dev, PID_IPCP, START, 0, 0, 0, NULL);
ppp_cp_event(dev, PID_IPV6CP, START, 0, 0, 0, NULL);
ppp->last_pong = jiffies;
mod_timer(&proto->timer, proto->timeout =
jiffies + ppp->keepalive_interval * HZ);
}
}
if (old_state == OPENED && proto->state != OPENED) {
netdev_info(dev, "%s down\n", proto_name(pid));
if (pid == PID_LCP) {
netif_dormant_on(dev);
ppp_cp_event(dev, PID_IPCP, STOP, 0, 0, 0, NULL);
ppp_cp_event(dev, PID_IPV6CP, STOP, 0, 0, 0, NULL);
}
}
if (old_state != CLOSED && proto->state == CLOSED)
timer_delete(&proto->timer);
#if DEBUG_STATE
printk(KERN_DEBUG "%s: %s ppp_cp_event(%s) ... %s\n", dev->name,
proto_name(pid), event_names[event], state_names[proto->state]);
#endif
}
static void ppp_cp_parse_cr(struct net_device *dev, u16 pid, u8 id,
unsigned int req_len, const u8 *data)
{
static u8 const valid_accm[6] = { LCP_OPTION_ACCM, 6, 0, 0, 0, 0 };
const u8 *opt;
u8 *out;
unsigned int len = req_len, nak_len = 0, rej_len = 0;
out = kmalloc(len, GFP_ATOMIC);
if (!out) {
dev->stats.rx_dropped++;
return; /* out of memory, ignore CR packet */
}
for (opt = data; len; len -= opt[1], opt += opt[1]) {
if (len < 2 || opt[1] < 2 || len < opt[1])
goto err_out;
if (pid == PID_LCP)
switch (opt[0]) {
case LCP_OPTION_MRU:
continue; /* MRU always OK and > 1500 bytes? */
case LCP_OPTION_ACCM: /* async control character map */
if (opt[1] < sizeof(valid_accm))
goto err_out;
if (!memcmp(opt, valid_accm,
sizeof(valid_accm)))
continue;
if (!rej_len) { /* NAK it */
memcpy(out + nak_len, valid_accm,
sizeof(valid_accm));
nak_len += sizeof(valid_accm);
continue;
}
break;
case LCP_OPTION_MAGIC:
if (len < 6)
goto err_out;
if (opt[1] != 6 || (!opt[2] && !opt[3] &&
!opt[4] && !opt[5]))
break; /* reject invalid magic number */
continue;
}
/* reject this option */
memcpy(out + rej_len, opt, opt[1]);
rej_len += opt[1];
}
if (rej_len)
ppp_cp_event(dev, pid, RCR_BAD, CP_CONF_REJ, id, rej_len, out);
else if (nak_len)
ppp_cp_event(dev, pid, RCR_BAD, CP_CONF_NAK, id, nak_len, out);
else
ppp_cp_event(dev, pid, RCR_GOOD, CP_CONF_ACK, id, req_len, data);
kfree(out);
return;
err_out:
dev->stats.rx_errors++;
kfree(out);
}
static int ppp_rx(struct sk_buff *skb)
{
struct hdlc_header *hdr = (struct hdlc_header *)skb->data;
struct net_device *dev = skb->dev;
struct ppp *ppp = get_ppp(dev);
struct proto *proto;
struct cp_header *cp;
unsigned long flags;
unsigned int len;
u16 pid;
#if DEBUG_CP
int i;
char *ptr;
#endif
spin_lock_irqsave(&ppp->lock, flags);
/* Check HDLC header */
if (skb->len < sizeof(struct hdlc_header))
goto rx_error;
cp = skb_pull(skb, sizeof(struct hdlc_header));
if (hdr->address != HDLC_ADDR_ALLSTATIONS ||
hdr->control != HDLC_CTRL_UI)
goto rx_error;
pid = ntohs(hdr->protocol);
proto = get_proto(dev, pid);
if (!proto) {
if (ppp->protos[IDX_LCP].state == OPENED)
ppp_tx_cp(dev, PID_LCP, LCP_PROTO_REJ,
++ppp->seq, skb->len + 2, &hdr->protocol);
goto rx_error;
}
len = ntohs(cp->len);
if (len < sizeof(struct cp_header) /* no complete CP header? */ ||
skb->len < len /* truncated packet? */)
goto rx_error;
skb_pull(skb, sizeof(struct cp_header));
len -= sizeof(struct cp_header);
/* HDLC and CP headers stripped from skb */
#if DEBUG_CP
if (cp->code < CP_CODES)
sprintf(debug_buffer, "[%s id 0x%X]", code_names[cp->code],
cp->id);
else
sprintf(debug_buffer, "[code %u id 0x%X]", cp->code, cp->id);
ptr = debug_buffer + strlen(debug_buffer);
for (i = 0; i < min_t(unsigned int, len, DEBUG_CP); i++) {
sprintf(ptr, " %02X", skb->data[i]);
ptr += strlen(ptr);
}
printk(KERN_DEBUG "%s: RX %s %s\n", dev->name, proto_name(pid),
debug_buffer);
#endif
/* LCP only */
if (pid == PID_LCP)
switch (cp->code) {
case LCP_PROTO_REJ:
pid = ntohs(*(__be16 *)skb->data);
if (pid == PID_LCP || pid == PID_IPCP ||
pid == PID_IPV6CP)
ppp_cp_event(dev, pid, RXJ_BAD, 0, 0,
0, NULL);
goto out;
case LCP_ECHO_REQ: /* send Echo-Reply */
if (len >= 4 && proto->state == OPENED)
ppp_tx_cp(dev, PID_LCP, LCP_ECHO_REPLY,
cp->id, len - 4, skb->data + 4);
goto out;
case LCP_ECHO_REPLY:
if (cp->id == ppp->echo_id)
ppp->last_pong = jiffies;
goto out;
case LCP_DISC_REQ: /* discard */
goto out;
}
/* LCP, IPCP and IPV6CP */
switch (cp->code) {
case CP_CONF_REQ:
ppp_cp_parse_cr(dev, pid, cp->id, len, skb->data);
break;
case CP_CONF_ACK:
if (cp->id == proto->cr_id)
ppp_cp_event(dev, pid, RCA, 0, 0, 0, NULL);
break;
case CP_CONF_REJ:
case CP_CONF_NAK:
if (cp->id == proto->cr_id)
ppp_cp_event(dev, pid, RCN, 0, 0, 0, NULL);
break;
case CP_TERM_REQ:
ppp_cp_event(dev, pid, RTR, 0, cp->id, 0, NULL);
break;
case CP_TERM_ACK:
ppp_cp_event(dev, pid, RTA, 0, 0, 0, NULL);
break;
case CP_CODE_REJ:
ppp_cp_event(dev, pid, RXJ_BAD, 0, 0, 0, NULL);
break;
default:
len += sizeof(struct cp_header);
if (len > dev->mtu)
len = dev->mtu;
ppp_cp_event(dev, pid, RUC, 0, 0, len, cp);
break;
}
goto out;
rx_error:
dev->stats.rx_errors++;
out:
spin_unlock_irqrestore(&ppp->lock, flags);
dev_kfree_skb_any(skb);
ppp_tx_flush();
return NET_RX_DROP;
}
static void ppp_timer(struct timer_list *t)
{
struct proto *proto = timer_container_of(proto, t, timer);
struct ppp *ppp = get_ppp(proto->dev);
unsigned long flags;
spin_lock_irqsave(&ppp->lock, flags);
/* mod_timer could be called after we entered this function but
* before we got the lock.
*/
if (timer_pending(&proto->timer)) {
spin_unlock_irqrestore(&ppp->lock, flags);
return;
}
switch (proto->state) {
case STOPPING:
case REQ_SENT:
case ACK_RECV:
case ACK_SENT:
if (proto->restart_counter) {
ppp_cp_event(proto->dev, proto->pid, TO_GOOD, 0, 0,
0, NULL);
proto->restart_counter--;
} else if (netif_carrier_ok(proto->dev))
ppp_cp_event(proto->dev, proto->pid, TO_GOOD, 0, 0,
0, NULL);
else
ppp_cp_event(proto->dev, proto->pid, TO_BAD, 0, 0,
0, NULL);
break;
case OPENED:
if (proto->pid != PID_LCP)
break;
if (time_after(jiffies, ppp->last_pong +
ppp->keepalive_timeout * HZ)) {
netdev_info(proto->dev, "Link down\n");
ppp_cp_event(proto->dev, PID_LCP, STOP, 0, 0, 0, NULL);
ppp_cp_event(proto->dev, PID_LCP, START, 0, 0, 0, NULL);
} else { /* send keep-alive packet */
ppp->echo_id = ++ppp->seq;
ppp_tx_cp(proto->dev, PID_LCP, LCP_ECHO_REQ,
ppp->echo_id, 0, NULL);
proto->timer.expires = jiffies +
ppp->keepalive_interval * HZ;
add_timer(&proto->timer);
}
break;
}
spin_unlock_irqrestore(&ppp->lock, flags);
ppp_tx_flush();
}
static void ppp_start(struct net_device *dev)
{
struct ppp *ppp = get_ppp(dev);
int i;
for (i = 0; i < IDX_COUNT; i++) {
struct proto *proto = &ppp->protos[i];
proto->dev = dev;
proto->state = CLOSED;
}
ppp->protos[IDX_LCP].pid = PID_LCP;
ppp->protos[IDX_IPCP].pid = PID_IPCP;
ppp->protos[IDX_IPV6CP].pid = PID_IPV6CP;
ppp_cp_event(dev, PID_LCP, START, 0, 0, 0, NULL);
}
static void ppp_stop(struct net_device *dev)
{
ppp_cp_event(dev, PID_LCP, STOP, 0, 0, 0, NULL);
}
static void ppp_close(struct net_device *dev)
{
ppp_tx_flush();
}
static void ppp_timer_release(struct net_device *dev)
{
struct ppp *ppp = get_ppp(dev);
int i;
for (i = 0; i < IDX_COUNT; i++)
timer_shutdown_sync(&ppp->protos[i].timer);
}
static struct hdlc_proto proto = {
.start = ppp_start,
.stop = ppp_stop,
.close = ppp_close,
.type_trans = ppp_type_trans,
.ioctl = ppp_ioctl,
.netif_rx = ppp_rx,
.module = THIS_MODULE,
.detach = ppp_timer_release,
};
static const struct header_ops ppp_header_ops = {
.create = ppp_hard_header,
};
static int ppp_ioctl(struct net_device *dev, struct if_settings *ifs)
{
hdlc_device *hdlc = dev_to_hdlc(dev);
struct ppp *ppp;
int i, result;
switch (ifs->type) {
case IF_GET_PROTO:
if (dev_to_hdlc(dev)->proto != &proto)
return -EINVAL;
ifs->type = IF_PROTO_PPP;
return 0; /* return protocol only, no settable parameters */
case IF_PROTO_PPP:
if (!capable(CAP_NET_ADMIN))
return -EPERM;
if (dev->flags & IFF_UP)
return -EBUSY;
/* no settable parameters */
result = hdlc->attach(dev, ENCODING_NRZ,
PARITY_CRC16_PR1_CCITT);
if (result)
return result;
result = attach_hdlc_protocol(dev, &proto, sizeof(struct ppp));
if (result)
return result;
ppp = get_ppp(dev);
for (i = 0; i < IDX_COUNT; i++)
timer_setup(&ppp->protos[i].timer, ppp_timer, 0);
spin_lock_init(&ppp->lock);
ppp->req_timeout = 2;
ppp->cr_retries = 10;
ppp->term_retries = 2;
ppp->keepalive_interval = 10;
ppp->keepalive_timeout = 60;
dev->hard_header_len = sizeof(struct hdlc_header);
dev->header_ops = &ppp_header_ops;
dev->type = ARPHRD_PPP;
call_netdevice_notifiers(NETDEV_POST_TYPE_CHANGE, dev);
netif_dormant_on(dev);
return 0;
}
return -EINVAL;
}
static int __init hdlc_ppp_init(void)
{
skb_queue_head_init(&tx_queue);
register_hdlc_protocol(&proto);
return 0;
}
static void __exit hdlc_ppp_exit(void)
{
unregister_hdlc_protocol(&proto);
}
module_init(hdlc_ppp_init);
module_exit(hdlc_ppp_exit);
MODULE_AUTHOR("Krzysztof Halasa <khc@pm.waw.pl>");
MODULE_DESCRIPTION("PPP protocol support for generic HDLC");
MODULE_LICENSE("GPL v2");
+112
View File
@@ -0,0 +1,112 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Generic HDLC support routines for Linux
* HDLC support
*
* Copyright (C) 1999 - 2006 Krzysztof Halasa <khc@pm.waw.pl>
*/
#include <linux/errno.h>
#include <linux/hdlc.h>
#include <linux/if_arp.h>
#include <linux/inetdevice.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pkt_sched.h>
#include <linux/poll.h>
#include <linux/rtnetlink.h>
#include <linux/skbuff.h>
static int raw_ioctl(struct net_device *dev, struct if_settings *ifs);
static __be16 raw_type_trans(struct sk_buff *skb, struct net_device *dev)
{
return cpu_to_be16(ETH_P_IP);
}
static struct hdlc_proto proto = {
.type_trans = raw_type_trans,
.ioctl = raw_ioctl,
.module = THIS_MODULE,
};
static int raw_ioctl(struct net_device *dev, struct if_settings *ifs)
{
raw_hdlc_proto __user *raw_s = ifs->ifs_ifsu.raw_hdlc;
const size_t size = sizeof(raw_hdlc_proto);
raw_hdlc_proto new_settings;
hdlc_device *hdlc = dev_to_hdlc(dev);
int result;
switch (ifs->type) {
case IF_GET_PROTO:
if (dev_to_hdlc(dev)->proto != &proto)
return -EINVAL;
ifs->type = IF_PROTO_HDLC;
if (ifs->size < size) {
ifs->size = size; /* data size wanted */
return -ENOBUFS;
}
if (copy_to_user(raw_s, hdlc->state, size))
return -EFAULT;
return 0;
case IF_PROTO_HDLC:
if (!capable(CAP_NET_ADMIN))
return -EPERM;
if (dev->flags & IFF_UP)
return -EBUSY;
if (copy_from_user(&new_settings, raw_s, size))
return -EFAULT;
if (new_settings.encoding == ENCODING_DEFAULT)
new_settings.encoding = ENCODING_NRZ;
if (new_settings.parity == PARITY_DEFAULT)
new_settings.parity = PARITY_CRC16_PR1_CCITT;
result = hdlc->attach(dev, new_settings.encoding,
new_settings.parity);
if (result)
return result;
result = attach_hdlc_protocol(dev, &proto,
sizeof(raw_hdlc_proto));
if (result)
return result;
memcpy(hdlc->state, &new_settings, size);
dev->type = ARPHRD_RAWHDLC;
call_netdevice_notifiers(NETDEV_POST_TYPE_CHANGE, dev);
netif_dormant_off(dev);
return 0;
}
return -EINVAL;
}
static int __init hdlc_raw_init(void)
{
register_hdlc_protocol(&proto);
return 0;
}
static void __exit hdlc_raw_exit(void)
{
unregister_hdlc_protocol(&proto);
}
module_init(hdlc_raw_init);
module_exit(hdlc_raw_exit);
MODULE_AUTHOR("Krzysztof Halasa <khc@pm.waw.pl>");
MODULE_DESCRIPTION("Raw HDLC protocol support for generic HDLC");
MODULE_LICENSE("GPL v2");
+132
View File
@@ -0,0 +1,132 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Generic HDLC support routines for Linux
* HDLC Ethernet emulation support
*
* Copyright (C) 2002-2006 Krzysztof Halasa <khc@pm.waw.pl>
*/
#include <linux/errno.h>
#include <linux/etherdevice.h>
#include <linux/gfp.h>
#include <linux/hdlc.h>
#include <linux/if_arp.h>
#include <linux/inetdevice.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pkt_sched.h>
#include <linux/poll.h>
#include <linux/rtnetlink.h>
#include <linux/skbuff.h>
static int raw_eth_ioctl(struct net_device *dev, struct if_settings *ifs);
static netdev_tx_t eth_tx(struct sk_buff *skb, struct net_device *dev)
{
int pad = ETH_ZLEN - skb->len;
if (pad > 0) { /* Pad the frame with zeros */
int len = skb->len;
if (skb_tailroom(skb) < pad)
if (pskb_expand_head(skb, 0, pad, GFP_ATOMIC)) {
dev->stats.tx_dropped++;
dev_kfree_skb(skb);
return 0;
}
skb_put(skb, pad);
memset(skb->data + len, 0, pad);
}
return dev_to_hdlc(dev)->xmit(skb, dev);
}
static struct hdlc_proto proto = {
.type_trans = eth_type_trans,
.xmit = eth_tx,
.ioctl = raw_eth_ioctl,
.module = THIS_MODULE,
};
static int raw_eth_ioctl(struct net_device *dev, struct if_settings *ifs)
{
raw_hdlc_proto __user *raw_s = ifs->ifs_ifsu.raw_hdlc;
const size_t size = sizeof(raw_hdlc_proto);
raw_hdlc_proto new_settings;
hdlc_device *hdlc = dev_to_hdlc(dev);
unsigned int old_qlen;
int result;
switch (ifs->type) {
case IF_GET_PROTO:
if (dev_to_hdlc(dev)->proto != &proto)
return -EINVAL;
ifs->type = IF_PROTO_HDLC_ETH;
if (ifs->size < size) {
ifs->size = size; /* data size wanted */
return -ENOBUFS;
}
if (copy_to_user(raw_s, hdlc->state, size))
return -EFAULT;
return 0;
case IF_PROTO_HDLC_ETH:
if (!capable(CAP_NET_ADMIN))
return -EPERM;
if (dev->flags & IFF_UP)
return -EBUSY;
if (copy_from_user(&new_settings, raw_s, size))
return -EFAULT;
if (new_settings.encoding == ENCODING_DEFAULT)
new_settings.encoding = ENCODING_NRZ;
if (new_settings.parity == PARITY_DEFAULT)
new_settings.parity = PARITY_CRC16_PR1_CCITT;
result = hdlc->attach(dev, new_settings.encoding,
new_settings.parity);
if (result)
return result;
result = attach_hdlc_protocol(dev, &proto,
sizeof(raw_hdlc_proto));
if (result)
return result;
memcpy(hdlc->state, &new_settings, size);
old_qlen = dev->tx_queue_len;
ether_setup(dev);
dev->tx_queue_len = old_qlen;
dev->priv_flags &= ~IFF_TX_SKB_SHARING;
eth_hw_addr_random(dev);
call_netdevice_notifiers(NETDEV_POST_TYPE_CHANGE, dev);
netif_dormant_off(dev);
return 0;
}
return -EINVAL;
}
static int __init hdlc_eth_init(void)
{
register_hdlc_protocol(&proto);
return 0;
}
static void __exit hdlc_eth_exit(void)
{
unregister_hdlc_protocol(&proto);
}
module_init(hdlc_eth_init);
module_exit(hdlc_eth_exit);
MODULE_AUTHOR("Krzysztof Halasa <khc@pm.waw.pl>");
MODULE_DESCRIPTION("Ethernet encapsulation support for generic HDLC");
MODULE_LICENSE("GPL v2");
+384
View File
@@ -0,0 +1,384 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Generic HDLC support routines for Linux
* X.25 support
*
* Copyright (C) 1999 - 2006 Krzysztof Halasa <khc@pm.waw.pl>
*/
#include <linux/errno.h>
#include <linux/gfp.h>
#include <linux/hdlc.h>
#include <linux/if_arp.h>
#include <linux/inetdevice.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/lapb.h>
#include <linux/module.h>
#include <linux/pkt_sched.h>
#include <linux/poll.h>
#include <linux/rtnetlink.h>
#include <linux/skbuff.h>
#include <net/x25device.h>
struct x25_state {
x25_hdlc_proto settings;
bool up;
spinlock_t up_lock; /* Protects "up" */
struct sk_buff_head rx_queue;
struct tasklet_struct rx_tasklet;
};
static int x25_ioctl(struct net_device *dev, struct if_settings *ifs);
static struct x25_state *state(hdlc_device *hdlc)
{
return hdlc->state;
}
static void x25_rx_queue_kick(struct tasklet_struct *t)
{
struct x25_state *x25st = from_tasklet(x25st, t, rx_tasklet);
struct sk_buff *skb = skb_dequeue(&x25st->rx_queue);
while (skb) {
netif_receive_skb_core(skb);
skb = skb_dequeue(&x25st->rx_queue);
}
}
/* These functions are callbacks called by LAPB layer */
static void x25_connect_disconnect(struct net_device *dev, int reason, int code)
{
struct x25_state *x25st = state(dev_to_hdlc(dev));
struct sk_buff *skb;
unsigned char *ptr;
skb = __dev_alloc_skb(1, GFP_ATOMIC | __GFP_NOMEMALLOC);
if (!skb)
return;
ptr = skb_put(skb, 1);
*ptr = code;
skb->protocol = x25_type_trans(skb, dev);
skb_queue_tail(&x25st->rx_queue, skb);
tasklet_schedule(&x25st->rx_tasklet);
}
static void x25_connected(struct net_device *dev, int reason)
{
x25_connect_disconnect(dev, reason, X25_IFACE_CONNECT);
}
static void x25_disconnected(struct net_device *dev, int reason)
{
x25_connect_disconnect(dev, reason, X25_IFACE_DISCONNECT);
}
static int x25_data_indication(struct net_device *dev, struct sk_buff *skb)
{
struct x25_state *x25st = state(dev_to_hdlc(dev));
unsigned char *ptr;
if (skb_cow(skb, 1)) {
kfree_skb(skb);
return NET_RX_DROP;
}
skb_push(skb, 1);
ptr = skb->data;
*ptr = X25_IFACE_DATA;
skb->protocol = x25_type_trans(skb, dev);
skb_queue_tail(&x25st->rx_queue, skb);
tasklet_schedule(&x25st->rx_tasklet);
return NET_RX_SUCCESS;
}
static void x25_data_transmit(struct net_device *dev, struct sk_buff *skb)
{
hdlc_device *hdlc = dev_to_hdlc(dev);
skb_reset_network_header(skb);
skb->protocol = hdlc_type_trans(skb, dev);
if (dev_nit_active(dev))
dev_queue_xmit_nit(skb, dev);
hdlc->xmit(skb, dev); /* Ignore return value :-( */
}
static netdev_tx_t x25_xmit(struct sk_buff *skb, struct net_device *dev)
{
hdlc_device *hdlc = dev_to_hdlc(dev);
struct x25_state *x25st = state(hdlc);
int result;
/* There should be a pseudo header of 1 byte added by upper layers.
* Check to make sure it is there before reading it.
*/
if (skb->len < 1) {
kfree_skb(skb);
return NETDEV_TX_OK;
}
spin_lock_bh(&x25st->up_lock);
if (!x25st->up) {
spin_unlock_bh(&x25st->up_lock);
kfree_skb(skb);
return NETDEV_TX_OK;
}
switch (skb->data[0]) {
case X25_IFACE_DATA: /* Data to be transmitted */
skb_pull(skb, 1);
result = lapb_data_request(dev, skb);
if (result != LAPB_OK)
dev_kfree_skb(skb);
spin_unlock_bh(&x25st->up_lock);
return NETDEV_TX_OK;
case X25_IFACE_CONNECT:
result = lapb_connect_request(dev);
if (result != LAPB_OK) {
if (result == LAPB_CONNECTED)
/* Send connect confirm. msg to level 3 */
x25_connected(dev, 0);
else
netdev_err(dev, "LAPB connect request failed, error code = %i\n",
result);
}
break;
case X25_IFACE_DISCONNECT:
result = lapb_disconnect_request(dev);
if (result != LAPB_OK) {
if (result == LAPB_NOTCONNECTED)
/* Send disconnect confirm. msg to level 3 */
x25_disconnected(dev, 0);
else
netdev_err(dev, "LAPB disconnect request failed, error code = %i\n",
result);
}
break;
default: /* to be defined */
break;
}
spin_unlock_bh(&x25st->up_lock);
dev_kfree_skb(skb);
return NETDEV_TX_OK;
}
static int x25_open(struct net_device *dev)
{
static const struct lapb_register_struct cb = {
.connect_confirmation = x25_connected,
.connect_indication = x25_connected,
.disconnect_confirmation = x25_disconnected,
.disconnect_indication = x25_disconnected,
.data_indication = x25_data_indication,
.data_transmit = x25_data_transmit,
};
hdlc_device *hdlc = dev_to_hdlc(dev);
struct x25_state *x25st = state(hdlc);
struct lapb_parms_struct params;
int result;
result = lapb_register(dev, &cb);
if (result != LAPB_OK)
return -ENOMEM;
result = lapb_getparms(dev, &params);
if (result != LAPB_OK)
return -EINVAL;
if (state(hdlc)->settings.dce)
params.mode = params.mode | LAPB_DCE;
if (state(hdlc)->settings.modulo == 128)
params.mode = params.mode | LAPB_EXTENDED;
params.window = state(hdlc)->settings.window;
params.t1 = state(hdlc)->settings.t1;
params.t2 = state(hdlc)->settings.t2;
params.n2 = state(hdlc)->settings.n2;
result = lapb_setparms(dev, &params);
if (result != LAPB_OK)
return -EINVAL;
spin_lock_bh(&x25st->up_lock);
x25st->up = true;
spin_unlock_bh(&x25st->up_lock);
return 0;
}
static void x25_close(struct net_device *dev)
{
hdlc_device *hdlc = dev_to_hdlc(dev);
struct x25_state *x25st = state(hdlc);
spin_lock_bh(&x25st->up_lock);
x25st->up = false;
spin_unlock_bh(&x25st->up_lock);
lapb_unregister(dev);
tasklet_kill(&x25st->rx_tasklet);
}
static int x25_rx(struct sk_buff *skb)
{
struct net_device *dev = skb->dev;
hdlc_device *hdlc = dev_to_hdlc(dev);
struct x25_state *x25st = state(hdlc);
skb = skb_share_check(skb, GFP_ATOMIC);
if (!skb) {
dev->stats.rx_dropped++;
return NET_RX_DROP;
}
spin_lock_bh(&x25st->up_lock);
if (!x25st->up) {
spin_unlock_bh(&x25st->up_lock);
kfree_skb(skb);
dev->stats.rx_dropped++;
return NET_RX_DROP;
}
if (lapb_data_received(dev, skb) == LAPB_OK) {
spin_unlock_bh(&x25st->up_lock);
return NET_RX_SUCCESS;
}
spin_unlock_bh(&x25st->up_lock);
dev->stats.rx_errors++;
dev_kfree_skb_any(skb);
return NET_RX_DROP;
}
static struct hdlc_proto proto = {
.open = x25_open,
.close = x25_close,
.ioctl = x25_ioctl,
.netif_rx = x25_rx,
.xmit = x25_xmit,
.module = THIS_MODULE,
};
static int x25_ioctl(struct net_device *dev, struct if_settings *ifs)
{
x25_hdlc_proto __user *x25_s = ifs->ifs_ifsu.x25;
const size_t size = sizeof(x25_hdlc_proto);
hdlc_device *hdlc = dev_to_hdlc(dev);
x25_hdlc_proto new_settings;
int result;
switch (ifs->type) {
case IF_GET_PROTO:
if (dev_to_hdlc(dev)->proto != &proto)
return -EINVAL;
ifs->type = IF_PROTO_X25;
if (ifs->size < size) {
ifs->size = size; /* data size wanted */
return -ENOBUFS;
}
if (copy_to_user(x25_s, &state(hdlc)->settings, size))
return -EFAULT;
return 0;
case IF_PROTO_X25:
if (!capable(CAP_NET_ADMIN))
return -EPERM;
if (dev->flags & IFF_UP)
return -EBUSY;
/* backward compatibility */
if (ifs->size == 0) {
new_settings.dce = 0;
new_settings.modulo = 8;
new_settings.window = 7;
new_settings.t1 = 3;
new_settings.t2 = 1;
new_settings.n2 = 10;
} else {
if (copy_from_user(&new_settings, x25_s, size))
return -EFAULT;
if ((new_settings.dce != 0 &&
new_settings.dce != 1) ||
(new_settings.modulo != 8 &&
new_settings.modulo != 128) ||
new_settings.window < 1 ||
(new_settings.modulo == 8 &&
new_settings.window > 7) ||
(new_settings.modulo == 128 &&
new_settings.window > 127) ||
new_settings.t1 < 1 ||
new_settings.t1 > 255 ||
new_settings.t2 < 1 ||
new_settings.t2 > 255 ||
new_settings.n2 < 1 ||
new_settings.n2 > 255)
return -EINVAL;
}
result = hdlc->attach(dev, ENCODING_NRZ,
PARITY_CRC16_PR1_CCITT);
if (result)
return result;
result = attach_hdlc_protocol(dev, &proto,
sizeof(struct x25_state));
if (result)
return result;
memcpy(&state(hdlc)->settings, &new_settings, size);
state(hdlc)->up = false;
spin_lock_init(&state(hdlc)->up_lock);
skb_queue_head_init(&state(hdlc)->rx_queue);
tasklet_setup(&state(hdlc)->rx_tasklet, x25_rx_queue_kick);
/* There's no header_ops so hard_header_len should be 0. */
dev->hard_header_len = 0;
/* When transmitting data:
* first we'll remove a pseudo header of 1 byte,
* then we'll prepend an LAPB header of at most 3 bytes.
*/
dev->needed_headroom = 3 - 1;
dev->type = ARPHRD_X25;
call_netdevice_notifiers(NETDEV_POST_TYPE_CHANGE, dev);
netif_dormant_off(dev);
return 0;
}
return -EINVAL;
}
static int __init hdlc_x25_init(void)
{
register_hdlc_protocol(&proto);
return 0;
}
static void __exit hdlc_x25_exit(void)
{
unregister_hdlc_protocol(&proto);
}
module_init(hdlc_x25_init);
module_exit(hdlc_x25_exit);
MODULE_AUTHOR("Krzysztof Halasa <khc@pm.waw.pl>");
MODULE_DESCRIPTION("X.25 protocol support for generic HDLC");
MODULE_LICENSE("GPL v2");
File diff suppressed because it is too large Load Diff
+531
View File
@@ -0,0 +1,531 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* "LAPB via ethernet" driver release 001
*
* This code REQUIRES 2.1.15 or higher/ NET3.038
*
* This is a "pseudo" network driver to allow LAPB over Ethernet.
*
* This driver can use any ethernet destination address, and can be
* limited to accept frames from one dedicated ethernet card only.
*
* History
* LAPBETH 001 Jonathan Naylor Cloned from bpqether.c
* 2000-10-29 Henner Eisen lapb_data_indication() return status.
* 2000-11-14 Henner Eisen dev_hold/put, NETDEV_GOING_DOWN support
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/socket.h>
#include <linux/in.h>
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/net.h>
#include <linux/inet.h>
#include <linux/netdevice.h>
#include <linux/if_arp.h>
#include <linux/skbuff.h>
#include <net/sock.h>
#include <linux/uaccess.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/notifier.h>
#include <linux/stat.h>
#include <linux/module.h>
#include <linux/lapb.h>
#include <linux/init.h>
#include <net/netdev_lock.h>
#include <net/x25device.h>
static const u8 bcast_addr[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
/* If this number is made larger, check that the temporary string buffer
* in lapbeth_new_device is large enough to store the probe device name.
*/
#define MAXLAPBDEV 100
struct lapbethdev {
struct list_head node;
struct net_device *ethdev; /* link to ethernet device */
struct net_device *axdev; /* lapbeth device (lapb#) */
bool up;
spinlock_t up_lock; /* Protects "up" */
struct sk_buff_head rx_queue;
struct napi_struct napi;
};
static LIST_HEAD(lapbeth_devices);
static void lapbeth_connected(struct net_device *dev, int reason);
static void lapbeth_disconnected(struct net_device *dev, int reason);
/* ------------------------------------------------------------------------ */
/* Get the LAPB device for the ethernet device
*/
static struct lapbethdev *lapbeth_get_x25_dev(struct net_device *dev)
{
struct lapbethdev *lapbeth;
list_for_each_entry_rcu(lapbeth, &lapbeth_devices, node, lockdep_rtnl_is_held()) {
if (lapbeth->ethdev == dev)
return lapbeth;
}
return NULL;
}
static __inline__ int dev_is_ethdev(struct net_device *dev)
{
return dev->type == ARPHRD_ETHER && !netdev_need_ops_lock(dev);
}
/* ------------------------------------------------------------------------ */
static int lapbeth_napi_poll(struct napi_struct *napi, int budget)
{
struct lapbethdev *lapbeth = container_of(napi, struct lapbethdev,
napi);
struct sk_buff *skb;
int processed = 0;
for (; processed < budget; ++processed) {
skb = skb_dequeue(&lapbeth->rx_queue);
if (!skb)
break;
netif_receive_skb_core(skb);
}
if (processed < budget)
napi_complete(napi);
return processed;
}
/* Receive a LAPB frame via an ethernet interface.
*/
static int lapbeth_rcv(struct sk_buff *skb, struct net_device *dev,
struct packet_type *ptype, struct net_device *orig_dev)
{
int len, err;
struct lapbethdev *lapbeth;
if (dev_net(dev) != &init_net)
goto drop;
skb = skb_share_check(skb, GFP_ATOMIC);
if (!skb)
return NET_RX_DROP;
if (!pskb_may_pull(skb, 2))
goto drop;
rcu_read_lock();
lapbeth = lapbeth_get_x25_dev(dev);
if (!lapbeth)
goto drop_unlock_rcu;
spin_lock_bh(&lapbeth->up_lock);
if (!lapbeth->up)
goto drop_unlock;
len = skb->data[0] + skb->data[1] * 256;
dev->stats.rx_packets++;
dev->stats.rx_bytes += len;
skb_pull(skb, 2); /* Remove the length bytes */
skb_trim(skb, len); /* Set the length of the data */
err = lapb_data_received(lapbeth->axdev, skb);
if (err != LAPB_OK) {
printk(KERN_DEBUG "lapbether: lapb_data_received err - %d\n", err);
goto drop_unlock;
}
out:
spin_unlock_bh(&lapbeth->up_lock);
rcu_read_unlock();
return 0;
drop_unlock:
kfree_skb(skb);
goto out;
drop_unlock_rcu:
rcu_read_unlock();
drop:
kfree_skb(skb);
return 0;
}
static int lapbeth_data_indication(struct net_device *dev, struct sk_buff *skb)
{
struct lapbethdev *lapbeth = netdev_priv(dev);
unsigned char *ptr;
if (skb_cow(skb, 1)) {
kfree_skb(skb);
return NET_RX_DROP;
}
skb_push(skb, 1);
ptr = skb->data;
*ptr = X25_IFACE_DATA;
skb->protocol = x25_type_trans(skb, dev);
skb_queue_tail(&lapbeth->rx_queue, skb);
napi_schedule(&lapbeth->napi);
return NET_RX_SUCCESS;
}
/* Send a LAPB frame via an ethernet interface
*/
static netdev_tx_t lapbeth_xmit(struct sk_buff *skb,
struct net_device *dev)
{
struct lapbethdev *lapbeth = netdev_priv(dev);
int err;
spin_lock_bh(&lapbeth->up_lock);
if (!lapbeth->up)
goto drop;
/* There should be a pseudo header of 1 byte added by upper layers.
* Check to make sure it is there before reading it.
*/
if (skb->len < 1)
goto drop;
switch (skb->data[0]) {
case X25_IFACE_DATA:
break;
case X25_IFACE_CONNECT:
err = lapb_connect_request(dev);
if (err == LAPB_CONNECTED)
lapbeth_connected(dev, LAPB_OK);
else if (err != LAPB_OK)
pr_err("lapb_connect_request error: %d\n", err);
goto drop;
case X25_IFACE_DISCONNECT:
err = lapb_disconnect_request(dev);
if (err == LAPB_NOTCONNECTED)
lapbeth_disconnected(dev, LAPB_OK);
else if (err != LAPB_OK)
pr_err("lapb_disconnect_request err: %d\n", err);
fallthrough;
default:
goto drop;
}
skb_pull(skb, 1);
err = lapb_data_request(dev, skb);
if (err != LAPB_OK) {
pr_err("lapb_data_request error - %d\n", err);
goto drop;
}
out:
spin_unlock_bh(&lapbeth->up_lock);
return NETDEV_TX_OK;
drop:
kfree_skb(skb);
goto out;
}
static void lapbeth_data_transmit(struct net_device *ndev, struct sk_buff *skb)
{
struct lapbethdev *lapbeth = netdev_priv(ndev);
unsigned char *ptr;
struct net_device *dev;
int size = skb->len;
ptr = skb_push(skb, 2);
*ptr++ = size % 256;
*ptr++ = size / 256;
ndev->stats.tx_packets++;
ndev->stats.tx_bytes += size;
skb->dev = dev = lapbeth->ethdev;
skb->protocol = htons(ETH_P_DEC);
skb_reset_network_header(skb);
dev_hard_header(skb, dev, ETH_P_DEC, bcast_addr, NULL, 0);
dev_queue_xmit(skb);
}
static void lapbeth_connected(struct net_device *dev, int reason)
{
struct lapbethdev *lapbeth = netdev_priv(dev);
unsigned char *ptr;
struct sk_buff *skb = __dev_alloc_skb(1, GFP_ATOMIC | __GFP_NOMEMALLOC);
if (!skb)
return;
ptr = skb_put(skb, 1);
*ptr = X25_IFACE_CONNECT;
skb->protocol = x25_type_trans(skb, dev);
skb_queue_tail(&lapbeth->rx_queue, skb);
napi_schedule(&lapbeth->napi);
}
static void lapbeth_disconnected(struct net_device *dev, int reason)
{
struct lapbethdev *lapbeth = netdev_priv(dev);
unsigned char *ptr;
struct sk_buff *skb = __dev_alloc_skb(1, GFP_ATOMIC | __GFP_NOMEMALLOC);
if (!skb)
return;
ptr = skb_put(skb, 1);
*ptr = X25_IFACE_DISCONNECT;
skb->protocol = x25_type_trans(skb, dev);
skb_queue_tail(&lapbeth->rx_queue, skb);
napi_schedule(&lapbeth->napi);
}
/* Set AX.25 callsign
*/
static int lapbeth_set_mac_address(struct net_device *dev, void *addr)
{
struct sockaddr *sa = addr;
dev_addr_set(dev, sa->sa_data);
return 0;
}
static const struct lapb_register_struct lapbeth_callbacks = {
.connect_confirmation = lapbeth_connected,
.connect_indication = lapbeth_connected,
.disconnect_confirmation = lapbeth_disconnected,
.disconnect_indication = lapbeth_disconnected,
.data_indication = lapbeth_data_indication,
.data_transmit = lapbeth_data_transmit,
};
/* open/close a device
*/
static int lapbeth_open(struct net_device *dev)
{
struct lapbethdev *lapbeth = netdev_priv(dev);
int err;
napi_enable(&lapbeth->napi);
err = lapb_register(dev, &lapbeth_callbacks);
if (err != LAPB_OK) {
napi_disable(&lapbeth->napi);
pr_err("lapb_register error: %d\n", err);
return -ENODEV;
}
spin_lock_bh(&lapbeth->up_lock);
lapbeth->up = true;
spin_unlock_bh(&lapbeth->up_lock);
return 0;
}
static int lapbeth_close(struct net_device *dev)
{
struct lapbethdev *lapbeth = netdev_priv(dev);
int err;
spin_lock_bh(&lapbeth->up_lock);
lapbeth->up = false;
spin_unlock_bh(&lapbeth->up_lock);
err = lapb_unregister(dev);
if (err != LAPB_OK)
pr_err("lapb_unregister error: %d\n", err);
napi_disable(&lapbeth->napi);
return 0;
}
/* ------------------------------------------------------------------------ */
static const struct net_device_ops lapbeth_netdev_ops = {
.ndo_open = lapbeth_open,
.ndo_stop = lapbeth_close,
.ndo_start_xmit = lapbeth_xmit,
.ndo_set_mac_address = lapbeth_set_mac_address,
};
static void lapbeth_setup(struct net_device *dev)
{
netdev_lockdep_set_classes(dev);
dev->netdev_ops = &lapbeth_netdev_ops;
dev->needs_free_netdev = true;
dev->type = ARPHRD_X25;
dev->hard_header_len = 0;
dev->mtu = 1000;
dev->addr_len = 0;
}
/* Setup a new device.
*/
static int lapbeth_new_device(struct net_device *dev)
{
struct net_device *ndev;
struct lapbethdev *lapbeth;
int rc = -ENOMEM;
ASSERT_RTNL();
if (dev->type != ARPHRD_ETHER)
return -EINVAL;
ndev = alloc_netdev(sizeof(*lapbeth), "lapb%d", NET_NAME_UNKNOWN,
lapbeth_setup);
if (!ndev)
goto out;
/* When transmitting data:
* first this driver removes a pseudo header of 1 byte,
* then the lapb module prepends an LAPB header of at most 3 bytes,
* then this driver prepends a length field of 2 bytes,
* then the underlying Ethernet device prepends its own header.
*/
ndev->needed_headroom = -1 + 3 + 2 + dev->hard_header_len
+ dev->needed_headroom;
ndev->needed_tailroom = dev->needed_tailroom;
lapbeth = netdev_priv(ndev);
lapbeth->axdev = ndev;
dev_hold(dev);
lapbeth->ethdev = dev;
lapbeth->up = false;
spin_lock_init(&lapbeth->up_lock);
skb_queue_head_init(&lapbeth->rx_queue);
netif_napi_add_weight(ndev, &lapbeth->napi, lapbeth_napi_poll, 16);
rc = -EIO;
if (register_netdevice(ndev))
goto fail;
list_add_rcu(&lapbeth->node, &lapbeth_devices);
rc = 0;
out:
return rc;
fail:
dev_put(dev);
free_netdev(ndev);
goto out;
}
/* Free a lapb network device.
*/
static void lapbeth_free_device(struct lapbethdev *lapbeth)
{
dev_put(lapbeth->ethdev);
list_del_rcu(&lapbeth->node);
unregister_netdevice(lapbeth->axdev);
}
/* Handle device status changes.
*
* Called from notifier with RTNL held.
*/
static int lapbeth_device_event(struct notifier_block *this,
unsigned long event, void *ptr)
{
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
struct lapbethdev *lapbeth;
if (dev_net(dev) != &init_net)
return NOTIFY_DONE;
lapbeth = lapbeth_get_x25_dev(dev);
if (!dev_is_ethdev(dev) && !lapbeth)
return NOTIFY_DONE;
switch (event) {
case NETDEV_UP:
/* New ethernet device -> new LAPB interface */
if (!lapbeth)
lapbeth_new_device(dev);
break;
case NETDEV_GOING_DOWN:
/* ethernet device closes -> close LAPB interface */
if (lapbeth)
dev_close(lapbeth->axdev);
break;
case NETDEV_UNREGISTER:
/* ethernet device disappears -> remove LAPB interface */
if (lapbeth)
lapbeth_free_device(lapbeth);
break;
case NETDEV_PRE_TYPE_CHANGE:
/* Our underlying device type must not change. */
if (lapbeth)
return NOTIFY_BAD;
}
return NOTIFY_DONE;
}
/* ------------------------------------------------------------------------ */
static struct packet_type lapbeth_packet_type __read_mostly = {
.type = cpu_to_be16(ETH_P_DEC),
.func = lapbeth_rcv,
};
static struct notifier_block lapbeth_dev_notifier = {
.notifier_call = lapbeth_device_event,
};
static const char banner[] __initconst =
KERN_INFO "LAPB Ethernet driver version 0.02\n";
static int __init lapbeth_init_driver(void)
{
dev_add_pack(&lapbeth_packet_type);
register_netdevice_notifier(&lapbeth_dev_notifier);
printk(banner);
return 0;
}
module_init(lapbeth_init_driver);
static void __exit lapbeth_cleanup_driver(void)
{
struct lapbethdev *lapbeth;
struct list_head *entry, *tmp;
dev_remove_pack(&lapbeth_packet_type);
unregister_netdevice_notifier(&lapbeth_dev_notifier);
rtnl_lock();
list_for_each_safe(entry, tmp, &lapbeth_devices) {
lapbeth = list_entry(entry, struct lapbethdev, node);
dev_put(lapbeth->ethdev);
unregister_netdevice(lapbeth->axdev);
}
rtnl_unlock();
}
module_exit(lapbeth_cleanup_driver);
MODULE_AUTHOR("Jonathan Naylor <g4klx@g4klx.demon.co.uk>");
MODULE_DESCRIPTION("The unofficial LAPB over Ethernet driver");
MODULE_LICENSE("GPL");
+546
View File
@@ -0,0 +1,546 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* SDL Inc. RISCom/N2 synchronous serial card driver for Linux
*
* Copyright (C) 1998-2003 Krzysztof Halasa <khc@pm.waw.pl>
*
* For information see <https://www.kernel.org/pub/linux/utils/net/hdlc/>
*
* Note: integrated CSU/DSU/DDS are not supported by this driver
*
* Sources of information:
* Hitachi HD64570 SCA User's Manual
* SDL Inc. PPP/HDLC/CISCO driver
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/capability.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/in.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/moduleparam.h>
#include <linux/netdevice.h>
#include <linux/hdlc.h>
#include <asm/io.h>
#include "hd64570.h"
static const char *version = "SDL RISCom/N2 driver version: 1.15";
static const char *devname = "RISCom/N2";
#undef DEBUG_PKT
#define DEBUG_RINGS
#define USE_WINDOWSIZE 16384
#define USE_BUS16BITS 1
#define CLOCK_BASE 9830400 /* 9.8304 MHz */
#define MAX_PAGES 16 /* 16 RAM pages at max */
#define MAX_RAM_SIZE 0x80000 /* 512 KB */
#if MAX_RAM_SIZE > MAX_PAGES * USE_WINDOWSIZE
#undef MAX_RAM_SIZE
#define MAX_RAM_SIZE (MAX_PAGES * USE_WINDOWSIZE)
#endif
#define N2_IOPORTS 0x10
#define NEED_DETECT_RAM
#define NEED_SCA_MSCI_INTR
#define MAX_TX_BUFFERS 10
static char *hw; /* pointer to hw=xxx command line string */
/* RISCom/N2 Board Registers */
/* PC Control Register */
#define N2_PCR 0
#define PCR_RUNSCA 1 /* Run 64570 */
#define PCR_VPM 2 /* Enable VPM - needed if using RAM above 1 MB */
#define PCR_ENWIN 4 /* Open window */
#define PCR_BUS16 8 /* 16-bit bus */
/* Memory Base Address Register */
#define N2_BAR 2
/* Page Scan Register */
#define N2_PSR 4
#define WIN16K 0x00
#define WIN32K 0x20
#define WIN64K 0x40
#define PSR_WINBITS 0x60
#define PSR_DMAEN 0x80
#define PSR_PAGEBITS 0x0F
/* Modem Control Reg */
#define N2_MCR 6
#define CLOCK_OUT_PORT1 0x80
#define CLOCK_OUT_PORT0 0x40
#define TX422_PORT1 0x20
#define TX422_PORT0 0x10
#define DSR_PORT1 0x08
#define DSR_PORT0 0x04
#define DTR_PORT1 0x02
#define DTR_PORT0 0x01
typedef struct port_s {
struct net_device *dev;
struct card_s *card;
spinlock_t lock; /* TX lock */
sync_serial_settings settings;
int valid; /* port enabled */
int rxpart; /* partial frame received, next frame invalid*/
unsigned short encoding;
unsigned short parity;
u16 rxin; /* rx ring buffer 'in' pointer */
u16 txin; /* tx ring buffer 'in' and 'last' pointers */
u16 txlast;
u8 rxs, txs, tmc; /* SCA registers */
u8 phy_node; /* physical port # - 0 or 1 */
u8 log_node; /* logical port # */
} port_t;
typedef struct card_s {
u8 __iomem *winbase; /* ISA window base address */
u32 phy_winbase; /* ISA physical base address */
u32 ram_size; /* number of bytes */
u16 io; /* IO Base address */
u16 buff_offset; /* offset of first buffer of first channel */
u16 rx_ring_buffers; /* number of buffers in a ring */
u16 tx_ring_buffers;
u8 irq; /* IRQ (3-15) */
port_t ports[2];
struct card_s *next_card;
} card_t;
static card_t *first_card;
static card_t **new_card = &first_card;
#define sca_reg(reg, card) (0x8000 | (card)->io | \
((reg) & 0x0F) | (((reg) & 0xF0) << 6))
#define sca_in(reg, card) inb(sca_reg(reg, card))
#define sca_out(value, reg, card) outb(value, sca_reg(reg, card))
#define sca_inw(reg, card) inw(sca_reg(reg, card))
#define sca_outw(value, reg, card) outw(value, sca_reg(reg, card))
#define port_to_card(port) ((port)->card)
#define log_node(port) ((port)->log_node)
#define phy_node(port) ((port)->phy_node)
#define winsize(card) (USE_WINDOWSIZE)
#define winbase(card) ((card)->winbase)
#define get_port(card, port) ((card)->ports[port].valid ? \
&(card)->ports[port] : NULL)
static __inline__ u8 sca_get_page(card_t *card)
{
return inb(card->io + N2_PSR) & PSR_PAGEBITS;
}
static __inline__ void openwin(card_t *card, u8 page)
{
u8 psr = inb(card->io + N2_PSR);
outb((psr & ~PSR_PAGEBITS) | page, card->io + N2_PSR);
}
#include "hd64570.c"
static void n2_set_iface(port_t *port)
{
card_t *card = port->card;
int io = card->io;
u8 mcr = inb(io + N2_MCR);
u8 msci = get_msci(port);
u8 rxs = port->rxs & CLK_BRG_MASK;
u8 txs = port->txs & CLK_BRG_MASK;
switch (port->settings.clock_type) {
case CLOCK_INT:
mcr |= port->phy_node ? CLOCK_OUT_PORT1 : CLOCK_OUT_PORT0;
rxs |= CLK_BRG_RX; /* BRG output */
txs |= CLK_RXCLK_TX; /* RX clock */
break;
case CLOCK_TXINT:
mcr |= port->phy_node ? CLOCK_OUT_PORT1 : CLOCK_OUT_PORT0;
rxs |= CLK_LINE_RX; /* RXC input */
txs |= CLK_BRG_TX; /* BRG output */
break;
case CLOCK_TXFROMRX:
mcr |= port->phy_node ? CLOCK_OUT_PORT1 : CLOCK_OUT_PORT0;
rxs |= CLK_LINE_RX; /* RXC input */
txs |= CLK_RXCLK_TX; /* RX clock */
break;
default: /* Clock EXTernal */
mcr &= port->phy_node ? ~CLOCK_OUT_PORT1 : ~CLOCK_OUT_PORT0;
rxs |= CLK_LINE_RX; /* RXC input */
txs |= CLK_LINE_TX; /* TXC input */
}
outb(mcr, io + N2_MCR);
port->rxs = rxs;
port->txs = txs;
sca_out(rxs, msci + RXS, card);
sca_out(txs, msci + TXS, card);
sca_set_port(port);
}
static int n2_open(struct net_device *dev)
{
port_t *port = dev_to_port(dev);
int io = port->card->io;
u8 mcr = inb(io + N2_MCR) |
(port->phy_node ? TX422_PORT1 : TX422_PORT0);
int result;
result = hdlc_open(dev);
if (result)
return result;
mcr &= port->phy_node ? ~DTR_PORT1 : ~DTR_PORT0; /* set DTR ON */
outb(mcr, io + N2_MCR);
outb(inb(io + N2_PCR) | PCR_ENWIN, io + N2_PCR); /* open window */
outb(inb(io + N2_PSR) | PSR_DMAEN, io + N2_PSR); /* enable dma */
sca_open(dev);
n2_set_iface(port);
return 0;
}
static int n2_close(struct net_device *dev)
{
port_t *port = dev_to_port(dev);
int io = port->card->io;
u8 mcr = inb(io + N2_MCR) |
(port->phy_node ? TX422_PORT1 : TX422_PORT0);
sca_close(dev);
mcr |= port->phy_node ? DTR_PORT1 : DTR_PORT0; /* set DTR OFF */
outb(mcr, io + N2_MCR);
hdlc_close(dev);
return 0;
}
static int n2_siocdevprivate(struct net_device *dev, struct ifreq *ifr,
void __user *data, int cmd)
{
#ifdef DEBUG_RINGS
if (cmd == SIOCDEVPRIVATE) {
sca_dump_rings(dev);
return 0;
}
#endif
return -EOPNOTSUPP;
}
static int n2_ioctl(struct net_device *dev, struct if_settings *ifs)
{
const size_t size = sizeof(sync_serial_settings);
sync_serial_settings new_line;
sync_serial_settings __user *line = ifs->ifs_ifsu.sync;
port_t *port = dev_to_port(dev);
switch (ifs->type) {
case IF_GET_IFACE:
ifs->type = IF_IFACE_SYNC_SERIAL;
if (ifs->size < size) {
ifs->size = size; /* data size wanted */
return -ENOBUFS;
}
if (copy_to_user(line, &port->settings, size))
return -EFAULT;
return 0;
case IF_IFACE_SYNC_SERIAL:
if (!capable(CAP_NET_ADMIN))
return -EPERM;
if (copy_from_user(&new_line, line, size))
return -EFAULT;
if (new_line.clock_type != CLOCK_EXT &&
new_line.clock_type != CLOCK_TXFROMRX &&
new_line.clock_type != CLOCK_INT &&
new_line.clock_type != CLOCK_TXINT)
return -EINVAL; /* No such clock setting */
if (new_line.loopback != 0 && new_line.loopback != 1)
return -EINVAL;
memcpy(&port->settings, &new_line, size); /* Update settings */
n2_set_iface(port);
return 0;
default:
return hdlc_ioctl(dev, ifs);
}
}
static void n2_destroy_card(card_t *card)
{
int cnt;
for (cnt = 0; cnt < 2; cnt++)
if (card->ports[cnt].card) {
struct net_device *dev = port_to_dev(&card->ports[cnt]);
unregister_hdlc_device(dev);
}
if (card->irq)
free_irq(card->irq, card);
if (card->winbase) {
iounmap(card->winbase);
release_mem_region(card->phy_winbase, USE_WINDOWSIZE);
}
if (card->io)
release_region(card->io, N2_IOPORTS);
if (card->ports[0].dev)
free_netdev(card->ports[0].dev);
if (card->ports[1].dev)
free_netdev(card->ports[1].dev);
kfree(card);
}
static const struct net_device_ops n2_ops = {
.ndo_open = n2_open,
.ndo_stop = n2_close,
.ndo_start_xmit = hdlc_start_xmit,
.ndo_siocwandev = n2_ioctl,
.ndo_siocdevprivate = n2_siocdevprivate,
};
static int __init n2_run(unsigned long io, unsigned long irq,
unsigned long winbase, long valid0, long valid1)
{
card_t *card;
u8 cnt, pcr;
int i;
if (io < 0x200 || io > 0x3FF || (io % N2_IOPORTS) != 0) {
pr_err("invalid I/O port value\n");
return -ENODEV;
}
if (irq < 3 || irq > 15 || irq == 6) /* FIXME */ {
pr_err("invalid IRQ value\n");
return -ENODEV;
}
if (winbase < 0xA0000 || winbase > 0xFFFFF || (winbase & 0xFFF) != 0) {
pr_err("invalid RAM value\n");
return -ENODEV;
}
card = kzalloc_obj(card_t);
if (!card)
return -ENOBUFS;
card->ports[0].dev = alloc_hdlcdev(&card->ports[0]);
card->ports[1].dev = alloc_hdlcdev(&card->ports[1]);
if (!card->ports[0].dev || !card->ports[1].dev) {
pr_err("unable to allocate memory\n");
n2_destroy_card(card);
return -ENOMEM;
}
if (!request_region(io, N2_IOPORTS, devname)) {
pr_err("I/O port region in use\n");
n2_destroy_card(card);
return -EBUSY;
}
card->io = io;
if (request_irq(irq, sca_intr, 0, devname, card)) {
pr_err("could not allocate IRQ\n");
n2_destroy_card(card);
return -EBUSY;
}
card->irq = irq;
if (!request_mem_region(winbase, USE_WINDOWSIZE, devname)) {
pr_err("could not request RAM window\n");
n2_destroy_card(card);
return -EBUSY;
}
card->phy_winbase = winbase;
card->winbase = ioremap(winbase, USE_WINDOWSIZE);
if (!card->winbase) {
pr_err("ioremap() failed\n");
n2_destroy_card(card);
return -EFAULT;
}
outb(0, io + N2_PCR);
outb(winbase >> 12, io + N2_BAR);
switch (USE_WINDOWSIZE) {
case 16384:
outb(WIN16K, io + N2_PSR);
break;
case 32768:
outb(WIN32K, io + N2_PSR);
break;
case 65536:
outb(WIN64K, io + N2_PSR);
break;
default:
pr_err("invalid window size\n");
n2_destroy_card(card);
return -ENODEV;
}
pcr = PCR_ENWIN | PCR_VPM | (USE_BUS16BITS ? PCR_BUS16 : 0);
outb(pcr, io + N2_PCR);
card->ram_size = sca_detect_ram(card, card->winbase, MAX_RAM_SIZE);
/* number of TX + RX buffers for one port */
i = card->ram_size / ((valid0 + valid1) * (sizeof(pkt_desc) +
HDLC_MAX_MRU));
card->tx_ring_buffers = min(i / 2, MAX_TX_BUFFERS);
card->rx_ring_buffers = i - card->tx_ring_buffers;
card->buff_offset = (valid0 + valid1) * sizeof(pkt_desc) *
(card->tx_ring_buffers + card->rx_ring_buffers);
pr_info("RISCom/N2 %u KB RAM, IRQ%u, using %u TX + %u RX packets rings\n",
card->ram_size / 1024, card->irq,
card->tx_ring_buffers, card->rx_ring_buffers);
if (card->tx_ring_buffers < 1) {
pr_err("RAM test failed\n");
n2_destroy_card(card);
return -EIO;
}
pcr |= PCR_RUNSCA; /* run SCA */
outb(pcr, io + N2_PCR);
outb(0, io + N2_MCR);
sca_init(card, 0);
for (cnt = 0; cnt < 2; cnt++) {
port_t *port = &card->ports[cnt];
struct net_device *dev = port_to_dev(port);
hdlc_device *hdlc = dev_to_hdlc(dev);
if ((cnt == 0 && !valid0) || (cnt == 1 && !valid1))
continue;
port->phy_node = cnt;
port->valid = 1;
if ((cnt == 1) && valid0)
port->log_node = 1;
spin_lock_init(&port->lock);
dev->irq = irq;
dev->mem_start = winbase;
dev->mem_end = winbase + USE_WINDOWSIZE - 1;
dev->tx_queue_len = 50;
dev->netdev_ops = &n2_ops;
hdlc->attach = sca_attach;
hdlc->xmit = sca_xmit;
port->settings.clock_type = CLOCK_EXT;
port->card = card;
if (register_hdlc_device(dev)) {
pr_warn("unable to register hdlc device\n");
port->card = NULL;
n2_destroy_card(card);
return -ENOBUFS;
}
sca_init_port(port); /* Set up SCA memory */
netdev_info(dev, "RISCom/N2 node %d\n", port->phy_node);
}
*new_card = card;
new_card = &card->next_card;
return 0;
}
static int __init n2_init(void)
{
if (!hw) {
#ifdef MODULE
pr_info("no card initialized\n");
#endif
return -EINVAL; /* no parameters specified, abort */
}
pr_info("%s\n", version);
do {
unsigned long io, irq, ram;
long valid[2] = { 0, 0 }; /* Default = both ports disabled */
io = simple_strtoul(hw, &hw, 0);
if (*hw++ != ',')
break;
irq = simple_strtoul(hw, &hw, 0);
if (*hw++ != ',')
break;
ram = simple_strtoul(hw, &hw, 0);
if (*hw++ != ',')
break;
while (1) {
if (*hw == '0' && !valid[0])
valid[0] = 1; /* Port 0 enabled */
else if (*hw == '1' && !valid[1])
valid[1] = 1; /* Port 1 enabled */
else
break;
hw++;
}
if (!valid[0] && !valid[1])
break; /* at least one port must be used */
if (*hw == ':' || *hw == '\x0')
n2_run(io, irq, ram, valid[0], valid[1]);
if (*hw == '\x0')
return first_card ? 0 : -EINVAL;
} while (*hw++ == ':');
pr_err("invalid hardware parameters\n");
return first_card ? 0 : -EINVAL;
}
static void __exit n2_cleanup(void)
{
card_t *card = first_card;
while (card) {
card_t *ptr = card;
card = card->next_card;
n2_destroy_card(ptr);
}
}
module_init(n2_init);
module_exit(n2_cleanup);
MODULE_AUTHOR("Krzysztof Halasa <khc@pm.waw.pl>");
MODULE_DESCRIPTION("RISCom/N2 serial port driver");
MODULE_LICENSE("GPL v2");
module_param(hw, charp, 0444);
MODULE_PARM_DESC(hw, "io,irq,ram,ports:io,irq,...");
+509
View File
@@ -0,0 +1,509 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Cyclades PC300 synchronous serial card driver for Linux
*
* Copyright (C) 2000-2008 Krzysztof Halasa <khc@pm.waw.pl>
*
* For information see <https://www.kernel.org/pub/linux/utils/net/hdlc/>.
*
* Sources of information:
* Hitachi HD64572 SCA-II User's Manual
* Original Cyclades PC300 Linux driver
*
* This driver currently supports only PC300/RSV (V.24/V.35) and
* PC300/X21 cards.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/in.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/moduleparam.h>
#include <linux/netdevice.h>
#include <linux/hdlc.h>
#include <linux/pci.h>
#include <linux/delay.h>
#include <asm/io.h>
#include "hd64572.h"
#undef DEBUG_PKT
#define DEBUG_RINGS
#define PC300_PLX_SIZE 0x80 /* PLX control window size (128 B) */
#define PC300_SCA_SIZE 0x400 /* SCA window size (1 KB) */
#define MAX_TX_BUFFERS 10
static int pci_clock_freq = 33000000;
static int use_crystal_clock;
static unsigned int CLOCK_BASE;
/* Masks to access the init_ctrl PLX register */
#define PC300_CLKSEL_MASK (0x00000004UL)
#define PC300_CHMEDIA_MASK(port) (0x00000020UL << ((port) * 3))
#define PC300_CTYPE_MASK (0x00000800UL)
enum { PC300_RSV = 1, PC300_X21, PC300_TE }; /* card types */
/* PLX PCI9050-1 local configuration and shared runtime registers.
* This structure can be used to access 9050 registers (memory mapped).
*/
typedef struct {
u32 loc_addr_range[4]; /* 00-0Ch : Local Address Ranges */
u32 loc_rom_range; /* 10h : Local ROM Range */
u32 loc_addr_base[4]; /* 14-20h : Local Address Base Addrs */
u32 loc_rom_base; /* 24h : Local ROM Base */
u32 loc_bus_descr[4]; /* 28-34h : Local Bus Descriptors */
u32 rom_bus_descr; /* 38h : ROM Bus Descriptor */
u32 cs_base[4]; /* 3C-48h : Chip Select Base Addrs */
u32 intr_ctrl_stat; /* 4Ch : Interrupt Control/Status */
u32 init_ctrl; /* 50h : EEPROM ctrl, Init Ctrl, etc */
} plx9050;
typedef struct port_s {
struct napi_struct napi;
struct net_device *netdev;
struct card_s *card;
spinlock_t lock; /* TX lock */
sync_serial_settings settings;
int rxpart; /* partial frame received, next frame invalid*/
unsigned short encoding;
unsigned short parity;
unsigned int iface;
u16 rxin; /* rx ring buffer 'in' pointer */
u16 txin; /* tx ring buffer 'in' and 'last' pointers */
u16 txlast;
u8 rxs, txs, tmc; /* SCA registers */
u8 chan; /* physical port # - 0 or 1 */
} port_t;
typedef struct card_s {
int type; /* RSV, X21, etc. */
int n_ports; /* 1 or 2 ports */
u8 __iomem *rambase; /* buffer memory base (virtual) */
u8 __iomem *scabase; /* SCA memory base (virtual) */
plx9050 __iomem *plxbase; /* PLX registers memory base (virtual) */
u32 init_ctrl_value; /* Saved value - 9050 bug workaround */
u16 rx_ring_buffers; /* number of buffers in a ring */
u16 tx_ring_buffers;
u16 buff_offset; /* offset of first buffer of first channel */
u8 irq; /* interrupt request level */
port_t ports[2];
} card_t;
#define get_port(card, port) ((port) < (card)->n_ports ? \
(&(card)->ports[port]) : (NULL))
#include "hd64572.c"
static void pc300_set_iface(port_t *port)
{
card_t *card = port->card;
u32 __iomem *init_ctrl = &card->plxbase->init_ctrl;
u16 msci = get_msci(port);
u8 rxs = port->rxs & CLK_BRG_MASK;
u8 txs = port->txs & CLK_BRG_MASK;
sca_out(EXS_TES1, (port->chan ? MSCI1_OFFSET : MSCI0_OFFSET) + EXS,
port->card);
switch (port->settings.clock_type) {
case CLOCK_INT:
rxs |= CLK_BRG; /* BRG output */
txs |= CLK_PIN_OUT | CLK_TX_RXCLK; /* RX clock */
break;
case CLOCK_TXINT:
rxs |= CLK_LINE; /* RXC input */
txs |= CLK_PIN_OUT | CLK_BRG; /* BRG output */
break;
case CLOCK_TXFROMRX:
rxs |= CLK_LINE; /* RXC input */
txs |= CLK_PIN_OUT | CLK_TX_RXCLK; /* RX clock */
break;
default: /* EXTernal clock */
rxs |= CLK_LINE; /* RXC input */
txs |= CLK_PIN_OUT | CLK_LINE; /* TXC input */
break;
}
port->rxs = rxs;
port->txs = txs;
sca_out(rxs, msci + RXS, card);
sca_out(txs, msci + TXS, card);
sca_set_port(port);
if (port->card->type == PC300_RSV) {
if (port->iface == IF_IFACE_V35)
writel(card->init_ctrl_value |
PC300_CHMEDIA_MASK(port->chan), init_ctrl);
else
writel(card->init_ctrl_value &
~PC300_CHMEDIA_MASK(port->chan), init_ctrl);
}
}
static int pc300_open(struct net_device *dev)
{
port_t *port = dev_to_port(dev);
int result = hdlc_open(dev);
if (result)
return result;
sca_open(dev);
pc300_set_iface(port);
return 0;
}
static int pc300_close(struct net_device *dev)
{
sca_close(dev);
hdlc_close(dev);
return 0;
}
static int pc300_siocdevprivate(struct net_device *dev, struct ifreq *ifr,
void __user *data, int cmd)
{
#ifdef DEBUG_RINGS
if (cmd == SIOCDEVPRIVATE) {
sca_dump_rings(dev);
return 0;
}
#endif
return -EOPNOTSUPP;
}
static int pc300_ioctl(struct net_device *dev, struct if_settings *ifs)
{
const size_t size = sizeof(sync_serial_settings);
sync_serial_settings new_line;
sync_serial_settings __user *line = ifs->ifs_ifsu.sync;
int new_type;
port_t *port = dev_to_port(dev);
if (ifs->type == IF_GET_IFACE) {
ifs->type = port->iface;
if (ifs->size < size) {
ifs->size = size; /* data size wanted */
return -ENOBUFS;
}
if (copy_to_user(line, &port->settings, size))
return -EFAULT;
return 0;
}
if (port->card->type == PC300_X21 &&
(ifs->type == IF_IFACE_SYNC_SERIAL ||
ifs->type == IF_IFACE_X21))
new_type = IF_IFACE_X21;
else if (port->card->type == PC300_RSV &&
(ifs->type == IF_IFACE_SYNC_SERIAL ||
ifs->type == IF_IFACE_V35))
new_type = IF_IFACE_V35;
else if (port->card->type == PC300_RSV &&
ifs->type == IF_IFACE_V24)
new_type = IF_IFACE_V24;
else
return hdlc_ioctl(dev, ifs);
if (!capable(CAP_NET_ADMIN))
return -EPERM;
if (copy_from_user(&new_line, line, size))
return -EFAULT;
if (new_line.clock_type != CLOCK_EXT &&
new_line.clock_type != CLOCK_TXFROMRX &&
new_line.clock_type != CLOCK_INT &&
new_line.clock_type != CLOCK_TXINT)
return -EINVAL; /* No such clock setting */
if (new_line.loopback != 0 && new_line.loopback != 1)
return -EINVAL;
memcpy(&port->settings, &new_line, size); /* Update settings */
port->iface = new_type;
pc300_set_iface(port);
return 0;
}
static void pc300_pci_remove_one(struct pci_dev *pdev)
{
int i;
card_t *card = pci_get_drvdata(pdev);
for (i = 0; i < 2; i++)
if (card->ports[i].card)
unregister_hdlc_device(card->ports[i].netdev);
if (card->irq)
free_irq(card->irq, card);
if (card->rambase)
iounmap(card->rambase);
if (card->scabase)
iounmap(card->scabase);
if (card->plxbase)
iounmap(card->plxbase);
pci_release_regions(pdev);
pci_disable_device(pdev);
if (card->ports[0].netdev)
free_netdev(card->ports[0].netdev);
if (card->ports[1].netdev)
free_netdev(card->ports[1].netdev);
kfree(card);
}
static const struct net_device_ops pc300_ops = {
.ndo_open = pc300_open,
.ndo_stop = pc300_close,
.ndo_start_xmit = hdlc_start_xmit,
.ndo_siocwandev = pc300_ioctl,
.ndo_siocdevprivate = pc300_siocdevprivate,
};
static int pc300_pci_init_one(struct pci_dev *pdev,
const struct pci_device_id *ent)
{
card_t *card;
u32 __iomem *p;
int i;
u32 ramsize;
u32 ramphys; /* buffer memory base */
u32 scaphys; /* SCA memory base */
u32 plxphys; /* PLX registers memory base */
i = pci_enable_device(pdev);
if (i)
return i;
i = pci_request_regions(pdev, "PC300");
if (i) {
pci_disable_device(pdev);
return i;
}
card = kzalloc_obj(card_t);
if (!card) {
pci_release_regions(pdev);
pci_disable_device(pdev);
return -ENOBUFS;
}
pci_set_drvdata(pdev, card);
if (pci_resource_len(pdev, 0) != PC300_PLX_SIZE ||
pci_resource_len(pdev, 2) != PC300_SCA_SIZE ||
pci_resource_len(pdev, 3) < 16384) {
pr_err("invalid card EEPROM parameters\n");
pc300_pci_remove_one(pdev);
return -EFAULT;
}
plxphys = pci_resource_start(pdev, 0) & PCI_BASE_ADDRESS_MEM_MASK;
card->plxbase = ioremap(plxphys, PC300_PLX_SIZE);
scaphys = pci_resource_start(pdev, 2) & PCI_BASE_ADDRESS_MEM_MASK;
card->scabase = ioremap(scaphys, PC300_SCA_SIZE);
ramphys = pci_resource_start(pdev, 3) & PCI_BASE_ADDRESS_MEM_MASK;
card->rambase = pci_ioremap_bar(pdev, 3);
if (!card->plxbase || !card->scabase || !card->rambase) {
pr_err("ioremap() failed\n");
pc300_pci_remove_one(pdev);
return -ENOMEM;
}
/* PLX PCI 9050 workaround for local configuration register read bug */
pci_write_config_dword(pdev, PCI_BASE_ADDRESS_0, scaphys);
card->init_ctrl_value = readl(&((plx9050 __iomem *)card->scabase)->init_ctrl);
pci_write_config_dword(pdev, PCI_BASE_ADDRESS_0, plxphys);
if (pdev->device == PCI_DEVICE_ID_PC300_TE_1 ||
pdev->device == PCI_DEVICE_ID_PC300_TE_2)
card->type = PC300_TE; /* not fully supported */
else if (card->init_ctrl_value & PC300_CTYPE_MASK)
card->type = PC300_X21;
else
card->type = PC300_RSV;
if (pdev->device == PCI_DEVICE_ID_PC300_RX_1 ||
pdev->device == PCI_DEVICE_ID_PC300_TE_1)
card->n_ports = 1;
else
card->n_ports = 2;
for (i = 0; i < card->n_ports; i++) {
card->ports[i].netdev = alloc_hdlcdev(&card->ports[i]);
if (!card->ports[i].netdev) {
pr_err("unable to allocate memory\n");
pc300_pci_remove_one(pdev);
return -ENOMEM;
}
}
/* Reset PLX */
p = &card->plxbase->init_ctrl;
writel(card->init_ctrl_value | 0x40000000, p);
readl(p); /* Flush the write - do not use sca_flush */
udelay(1);
writel(card->init_ctrl_value, p);
readl(p); /* Flush the write - do not use sca_flush */
udelay(1);
/* Reload Config. Registers from EEPROM */
writel(card->init_ctrl_value | 0x20000000, p);
readl(p); /* Flush the write - do not use sca_flush */
udelay(1);
writel(card->init_ctrl_value, p);
readl(p); /* Flush the write - do not use sca_flush */
udelay(1);
ramsize = sca_detect_ram(card, card->rambase,
pci_resource_len(pdev, 3));
if (use_crystal_clock)
card->init_ctrl_value &= ~PC300_CLKSEL_MASK;
else
card->init_ctrl_value |= PC300_CLKSEL_MASK;
writel(card->init_ctrl_value, &card->plxbase->init_ctrl);
/* number of TX + RX buffers for one port */
i = ramsize / (card->n_ports * (sizeof(pkt_desc) + HDLC_MAX_MRU));
card->tx_ring_buffers = min(i / 2, MAX_TX_BUFFERS);
card->rx_ring_buffers = i - card->tx_ring_buffers;
card->buff_offset = card->n_ports * sizeof(pkt_desc) *
(card->tx_ring_buffers + card->rx_ring_buffers);
pr_info("PC300/%s, %u KB RAM at 0x%x, IRQ%u, using %u TX + %u RX packets rings\n",
card->type == PC300_X21 ? "X21" :
card->type == PC300_TE ? "TE" : "RSV",
ramsize / 1024, ramphys, pdev->irq,
card->tx_ring_buffers, card->rx_ring_buffers);
if (card->tx_ring_buffers < 1) {
pr_err("RAM test failed\n");
pc300_pci_remove_one(pdev);
return -EFAULT;
}
/* Enable interrupts on the PCI bridge, LINTi1 active low */
writew(0x0041, &card->plxbase->intr_ctrl_stat);
/* Allocate IRQ */
if (request_irq(pdev->irq, sca_intr, IRQF_SHARED, "pc300", card)) {
pr_warn("could not allocate IRQ%d\n", pdev->irq);
pc300_pci_remove_one(pdev);
return -EBUSY;
}
card->irq = pdev->irq;
sca_init(card, 0);
// COTE not set - allows better TX DMA settings
// sca_out(sca_in(PCR, card) | PCR_COTE, PCR, card);
sca_out(0x10, BTCR, card);
for (i = 0; i < card->n_ports; i++) {
port_t *port = &card->ports[i];
struct net_device *dev = port->netdev;
hdlc_device *hdlc = dev_to_hdlc(dev);
port->chan = i;
spin_lock_init(&port->lock);
dev->irq = card->irq;
dev->mem_start = ramphys;
dev->mem_end = ramphys + ramsize - 1;
dev->tx_queue_len = 50;
dev->netdev_ops = &pc300_ops;
hdlc->attach = sca_attach;
hdlc->xmit = sca_xmit;
port->settings.clock_type = CLOCK_EXT;
port->card = card;
if (card->type == PC300_X21)
port->iface = IF_IFACE_X21;
else
port->iface = IF_IFACE_V35;
sca_init_port(port);
if (register_hdlc_device(dev)) {
pr_err("unable to register hdlc device\n");
port->card = NULL;
pc300_pci_remove_one(pdev);
return -ENOBUFS;
}
netdev_info(dev, "PC300 channel %d\n", port->chan);
}
return 0;
}
static const struct pci_device_id pc300_pci_tbl[] = {
{ PCI_VDEVICE(CYCLADES, PCI_DEVICE_ID_PC300_RX_1) },
{ PCI_VDEVICE(CYCLADES, PCI_DEVICE_ID_PC300_RX_2) },
{ PCI_VDEVICE(CYCLADES, PCI_DEVICE_ID_PC300_TE_1) },
{ PCI_VDEVICE(CYCLADES, PCI_DEVICE_ID_PC300_TE_2) },
{ }
};
static struct pci_driver pc300_pci_driver = {
.name = "PC300",
.id_table = pc300_pci_tbl,
.probe = pc300_pci_init_one,
.remove = pc300_pci_remove_one,
};
static int __init pc300_init_module(void)
{
if (pci_clock_freq < 1000000 || pci_clock_freq > 80000000) {
pr_err("Invalid PCI clock frequency\n");
return -EINVAL;
}
if (use_crystal_clock != 0 && use_crystal_clock != 1) {
pr_err("Invalid 'use_crystal_clock' value\n");
return -EINVAL;
}
CLOCK_BASE = use_crystal_clock ? 24576000 : pci_clock_freq;
return pci_register_driver(&pc300_pci_driver);
}
static void __exit pc300_cleanup_module(void)
{
pci_unregister_driver(&pc300_pci_driver);
}
MODULE_AUTHOR("Krzysztof Halasa <khc@pm.waw.pl>");
MODULE_DESCRIPTION("Cyclades PC300 serial port driver");
MODULE_LICENSE("GPL v2");
MODULE_DEVICE_TABLE(pci, pc300_pci_tbl);
module_param(pci_clock_freq, int, 0444);
MODULE_PARM_DESC(pci_clock_freq, "System PCI clock frequency in Hz");
module_param(use_crystal_clock, int, 0444);
MODULE_PARM_DESC(use_crystal_clock,
"Use 24.576 MHz clock instead of PCI clock");
module_init(pc300_init_module);
module_exit(pc300_cleanup_module);
+433
View File
@@ -0,0 +1,433 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Goramo PCI200SYN synchronous serial card driver for Linux
*
* Copyright (C) 2002-2008 Krzysztof Halasa <khc@pm.waw.pl>
*
* For information see <https://www.kernel.org/pub/linux/utils/net/hdlc/>
*
* Sources of information:
* Hitachi HD64572 SCA-II User's Manual
* PLX Technology Inc. PCI9052 Data Book
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/capability.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/in.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/netdevice.h>
#include <linux/hdlc.h>
#include <linux/pci.h>
#include <linux/delay.h>
#include <asm/io.h>
#include "hd64572.h"
#undef DEBUG_PKT
#define DEBUG_RINGS
#define PCI200SYN_PLX_SIZE 0x80 /* PLX control window size (128b) */
#define PCI200SYN_SCA_SIZE 0x400 /* SCA window size (1Kb) */
#define MAX_TX_BUFFERS 10
static int pci_clock_freq = 33000000;
#define CLOCK_BASE pci_clock_freq
/* PLX PCI9052 local configuration and shared runtime registers.
* This structure can be used to access 9052 registers (memory mapped).
*/
typedef struct {
u32 loc_addr_range[4]; /* 00-0Ch : Local Address Ranges */
u32 loc_rom_range; /* 10h : Local ROM Range */
u32 loc_addr_base[4]; /* 14-20h : Local Address Base Addrs */
u32 loc_rom_base; /* 24h : Local ROM Base */
u32 loc_bus_descr[4]; /* 28-34h : Local Bus Descriptors */
u32 rom_bus_descr; /* 38h : ROM Bus Descriptor */
u32 cs_base[4]; /* 3C-48h : Chip Select Base Addrs */
u32 intr_ctrl_stat; /* 4Ch : Interrupt Control/Status */
u32 init_ctrl; /* 50h : EEPROM ctrl, Init Ctrl, etc */
} plx9052;
typedef struct port_s {
struct napi_struct napi;
struct net_device *netdev;
struct card_s *card;
spinlock_t lock; /* TX lock */
sync_serial_settings settings;
int rxpart; /* partial frame received, next frame invalid*/
unsigned short encoding;
unsigned short parity;
u16 rxin; /* rx ring buffer 'in' pointer */
u16 txin; /* tx ring buffer 'in' and 'last' pointers */
u16 txlast;
u8 rxs, txs, tmc; /* SCA registers */
u8 chan; /* physical port # - 0 or 1 */
} port_t;
typedef struct card_s {
u8 __iomem *rambase; /* buffer memory base (virtual) */
u8 __iomem *scabase; /* SCA memory base (virtual) */
plx9052 __iomem *plxbase;/* PLX registers memory base (virtual) */
u16 rx_ring_buffers; /* number of buffers in a ring */
u16 tx_ring_buffers;
u16 buff_offset; /* offset of first buffer of first channel */
u8 irq; /* interrupt request level */
port_t ports[2];
} card_t;
#define get_port(card, port) (&(card)->ports[port])
#define sca_flush(card) (sca_in(IER0, card))
static inline void new_memcpy_toio(char __iomem *dest, char *src, int length)
{
int len;
do {
len = length > 256 ? 256 : length;
memcpy_toio(dest, src, len);
dest += len;
src += len;
length -= len;
readb(dest);
} while (len);
}
#undef memcpy_toio
#define memcpy_toio new_memcpy_toio
#include "hd64572.c"
static void pci200_set_iface(port_t *port)
{
card_t *card = port->card;
u16 msci = get_msci(port);
u8 rxs = port->rxs & CLK_BRG_MASK;
u8 txs = port->txs & CLK_BRG_MASK;
sca_out(EXS_TES1, (port->chan ? MSCI1_OFFSET : MSCI0_OFFSET) + EXS,
port->card);
switch (port->settings.clock_type) {
case CLOCK_INT:
rxs |= CLK_BRG; /* BRG output */
txs |= CLK_PIN_OUT | CLK_TX_RXCLK; /* RX clock */
break;
case CLOCK_TXINT:
rxs |= CLK_LINE; /* RXC input */
txs |= CLK_PIN_OUT | CLK_BRG; /* BRG output */
break;
case CLOCK_TXFROMRX:
rxs |= CLK_LINE; /* RXC input */
txs |= CLK_PIN_OUT | CLK_TX_RXCLK; /* RX clock */
break;
default: /* EXTernal clock */
rxs |= CLK_LINE; /* RXC input */
txs |= CLK_PIN_OUT | CLK_LINE; /* TXC input */
break;
}
port->rxs = rxs;
port->txs = txs;
sca_out(rxs, msci + RXS, card);
sca_out(txs, msci + TXS, card);
sca_set_port(port);
}
static int pci200_open(struct net_device *dev)
{
port_t *port = dev_to_port(dev);
int result = hdlc_open(dev);
if (result)
return result;
sca_open(dev);
pci200_set_iface(port);
sca_flush(port->card);
return 0;
}
static int pci200_close(struct net_device *dev)
{
sca_close(dev);
sca_flush(dev_to_port(dev)->card);
hdlc_close(dev);
return 0;
}
static int pci200_siocdevprivate(struct net_device *dev, struct ifreq *ifr,
void __user *data, int cmd)
{
#ifdef DEBUG_RINGS
if (cmd == SIOCDEVPRIVATE) {
sca_dump_rings(dev);
return 0;
}
#endif
return -EOPNOTSUPP;
}
static int pci200_ioctl(struct net_device *dev, struct if_settings *ifs)
{
const size_t size = sizeof(sync_serial_settings);
sync_serial_settings new_line;
sync_serial_settings __user *line = ifs->ifs_ifsu.sync;
port_t *port = dev_to_port(dev);
switch (ifs->type) {
case IF_GET_IFACE:
ifs->type = IF_IFACE_V35;
if (ifs->size < size) {
ifs->size = size; /* data size wanted */
return -ENOBUFS;
}
if (copy_to_user(line, &port->settings, size))
return -EFAULT;
return 0;
case IF_IFACE_V35:
case IF_IFACE_SYNC_SERIAL:
if (!capable(CAP_NET_ADMIN))
return -EPERM;
if (copy_from_user(&new_line, line, size))
return -EFAULT;
if (new_line.clock_type != CLOCK_EXT &&
new_line.clock_type != CLOCK_TXFROMRX &&
new_line.clock_type != CLOCK_INT &&
new_line.clock_type != CLOCK_TXINT)
return -EINVAL; /* No such clock setting */
if (new_line.loopback != 0 && new_line.loopback != 1)
return -EINVAL;
memcpy(&port->settings, &new_line, size); /* Update settings */
pci200_set_iface(port);
sca_flush(port->card);
return 0;
default:
return hdlc_ioctl(dev, ifs);
}
}
static void pci200_pci_remove_one(struct pci_dev *pdev)
{
int i;
card_t *card = pci_get_drvdata(pdev);
for (i = 0; i < 2; i++)
if (card->ports[i].card)
unregister_hdlc_device(card->ports[i].netdev);
if (card->irq)
free_irq(card->irq, card);
if (card->rambase)
iounmap(card->rambase);
if (card->scabase)
iounmap(card->scabase);
if (card->plxbase)
iounmap(card->plxbase);
pci_release_regions(pdev);
pci_disable_device(pdev);
if (card->ports[0].netdev)
free_netdev(card->ports[0].netdev);
if (card->ports[1].netdev)
free_netdev(card->ports[1].netdev);
kfree(card);
}
static const struct net_device_ops pci200_ops = {
.ndo_open = pci200_open,
.ndo_stop = pci200_close,
.ndo_start_xmit = hdlc_start_xmit,
.ndo_siocwandev = pci200_ioctl,
.ndo_siocdevprivate = pci200_siocdevprivate,
};
static int pci200_pci_init_one(struct pci_dev *pdev,
const struct pci_device_id *ent)
{
card_t *card;
u32 __iomem *p;
int i;
u32 ramsize;
u32 ramphys; /* buffer memory base */
u32 scaphys; /* SCA memory base */
u32 plxphys; /* PLX registers memory base */
i = pci_enable_device(pdev);
if (i)
return i;
i = pci_request_regions(pdev, "PCI200SYN");
if (i) {
pci_disable_device(pdev);
return i;
}
card = kzalloc_obj(card_t);
if (!card) {
pci_release_regions(pdev);
pci_disable_device(pdev);
return -ENOBUFS;
}
pci_set_drvdata(pdev, card);
card->ports[0].netdev = alloc_hdlcdev(&card->ports[0]);
card->ports[1].netdev = alloc_hdlcdev(&card->ports[1]);
if (!card->ports[0].netdev || !card->ports[1].netdev) {
pr_err("unable to allocate memory\n");
pci200_pci_remove_one(pdev);
return -ENOMEM;
}
if (pci_resource_len(pdev, 0) != PCI200SYN_PLX_SIZE ||
pci_resource_len(pdev, 2) != PCI200SYN_SCA_SIZE ||
pci_resource_len(pdev, 3) < 16384) {
pr_err("invalid card EEPROM parameters\n");
pci200_pci_remove_one(pdev);
return -EFAULT;
}
plxphys = pci_resource_start(pdev, 0) & PCI_BASE_ADDRESS_MEM_MASK;
card->plxbase = ioremap(plxphys, PCI200SYN_PLX_SIZE);
scaphys = pci_resource_start(pdev, 2) & PCI_BASE_ADDRESS_MEM_MASK;
card->scabase = ioremap(scaphys, PCI200SYN_SCA_SIZE);
ramphys = pci_resource_start(pdev, 3) & PCI_BASE_ADDRESS_MEM_MASK;
card->rambase = pci_ioremap_bar(pdev, 3);
if (!card->plxbase || !card->scabase || !card->rambase) {
pr_err("ioremap() failed\n");
pci200_pci_remove_one(pdev);
return -EFAULT;
}
/* Reset PLX */
p = &card->plxbase->init_ctrl;
writel(readl(p) | 0x40000000, p);
readl(p); /* Flush the write - do not use sca_flush */
udelay(1);
writel(readl(p) & ~0x40000000, p);
readl(p); /* Flush the write - do not use sca_flush */
udelay(1);
ramsize = sca_detect_ram(card, card->rambase,
pci_resource_len(pdev, 3));
/* number of TX + RX buffers for one port - this is dual port card */
i = ramsize / (2 * (sizeof(pkt_desc) + HDLC_MAX_MRU));
card->tx_ring_buffers = min(i / 2, MAX_TX_BUFFERS);
card->rx_ring_buffers = i - card->tx_ring_buffers;
card->buff_offset = 2 * sizeof(pkt_desc) * (card->tx_ring_buffers +
card->rx_ring_buffers);
pr_info("%u KB RAM at 0x%x, IRQ%u, using %u TX + %u RX packets rings\n",
ramsize / 1024, ramphys,
pdev->irq, card->tx_ring_buffers, card->rx_ring_buffers);
if (card->tx_ring_buffers < 1) {
pr_err("RAM test failed\n");
pci200_pci_remove_one(pdev);
return -EFAULT;
}
/* Enable interrupts on the PCI bridge */
p = &card->plxbase->intr_ctrl_stat;
writew(readw(p) | 0x0040, p);
/* Allocate IRQ */
if (request_irq(pdev->irq, sca_intr, IRQF_SHARED, "pci200syn", card)) {
pr_warn("could not allocate IRQ%d\n", pdev->irq);
pci200_pci_remove_one(pdev);
return -EBUSY;
}
card->irq = pdev->irq;
sca_init(card, 0);
for (i = 0; i < 2; i++) {
port_t *port = &card->ports[i];
struct net_device *dev = port->netdev;
hdlc_device *hdlc = dev_to_hdlc(dev);
port->chan = i;
spin_lock_init(&port->lock);
dev->irq = card->irq;
dev->mem_start = ramphys;
dev->mem_end = ramphys + ramsize - 1;
dev->tx_queue_len = 50;
dev->netdev_ops = &pci200_ops;
hdlc->attach = sca_attach;
hdlc->xmit = sca_xmit;
port->settings.clock_type = CLOCK_EXT;
port->card = card;
sca_init_port(port);
if (register_hdlc_device(dev)) {
pr_err("unable to register hdlc device\n");
port->card = NULL;
pci200_pci_remove_one(pdev);
return -ENOBUFS;
}
netdev_info(dev, "PCI200SYN channel %d\n", port->chan);
}
sca_flush(card);
return 0;
}
static const struct pci_device_id pci200_pci_tbl[] = {
{ PCI_VDEVICE_SUB(PLX, PCI_DEVICE_ID_PLX_9050,
PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_PCI200SYN) },
{ }
};
static struct pci_driver pci200_pci_driver = {
.name = "PCI200SYN",
.id_table = pci200_pci_tbl,
.probe = pci200_pci_init_one,
.remove = pci200_pci_remove_one,
};
static int __init pci200_init_module(void)
{
if (pci_clock_freq < 1000000 || pci_clock_freq > 80000000) {
pr_err("Invalid PCI clock frequency\n");
return -EINVAL;
}
return pci_register_driver(&pci200_pci_driver);
}
static void __exit pci200_cleanup_module(void)
{
pci_unregister_driver(&pci200_pci_driver);
}
MODULE_AUTHOR("Krzysztof Halasa <khc@pm.waw.pl>");
MODULE_DESCRIPTION("Goramo PCI200SYN serial port driver");
MODULE_LICENSE("GPL v2");
MODULE_DEVICE_TABLE(pci, pci200_pci_tbl);
module_param(pci_clock_freq, int, 0444);
MODULE_PARM_DESC(pci_clock_freq, "System PCI clock frequency in Hz");
module_init(pci200_init_module);
module_exit(pci200_cleanup_module);
+245
View File
@@ -0,0 +1,245 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* drivers/net/wan/slic_ds26522.c
*
* Copyright (C) 2016 Freescale Semiconductor, Inc.
*
* Author:Zhao Qiang<qiang.zhao@nxp.com>
*/
#include <linux/bitrev.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/kthread.h>
#include <linux/spi/spi.h>
#include <linux/wait.h>
#include <linux/param.h>
#include <linux/delay.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/io.h>
#include "slic_ds26522.h"
#define SLIC_TRANS_LEN 1
#define SLIC_TWO_LEN 2
#define SLIC_THREE_LEN 3
static struct spi_device *g_spi;
MODULE_DESCRIPTION("Slic Maxim DS26522 driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Zhao Qiang<B45475@freescale.com>");
/* the read/write format of address is
* w/r|A13|A12|A11|A10|A9|A8|A7|A6|A5|A4|A3|A2|A1|A0|x
*/
static void slic_write(struct spi_device *spi, u16 addr,
u8 data)
{
u8 temp[3];
addr = bitrev16(addr) >> 1;
data = bitrev8(data);
temp[0] = (u8)((addr >> 8) & 0x7f);
temp[1] = (u8)(addr & 0xfe);
temp[2] = data;
/* write spi addr and value */
spi_write(spi, &temp[0], SLIC_THREE_LEN);
}
static u8 slic_read(struct spi_device *spi, u16 addr)
{
u8 temp[2];
u8 data;
addr = bitrev16(addr) >> 1;
temp[0] = (u8)(((addr >> 8) & 0x7f) | 0x80);
temp[1] = (u8)(addr & 0xfe);
spi_write_then_read(spi, &temp[0], SLIC_TWO_LEN, &data,
SLIC_TRANS_LEN);
data = bitrev8(data);
return data;
}
static bool get_slic_product_code(struct spi_device *spi)
{
u8 device_id;
device_id = slic_read(spi, DS26522_IDR_ADDR);
if ((device_id & 0xf8) == 0x68)
return true;
else
return false;
}
static void ds26522_e1_spec_config(struct spi_device *spi)
{
/* Receive E1 Mode, Framer Disabled */
slic_write(spi, DS26522_RMMR_ADDR, DS26522_RMMR_E1);
/* Transmit E1 Mode, Framer Disable */
slic_write(spi, DS26522_TMMR_ADDR, DS26522_TMMR_E1);
/* Receive E1 Mode Framer Enable */
slic_write(spi, DS26522_RMMR_ADDR,
slic_read(spi, DS26522_RMMR_ADDR) | DS26522_RMMR_FRM_EN);
/* Transmit E1 Mode Framer Enable */
slic_write(spi, DS26522_TMMR_ADDR,
slic_read(spi, DS26522_TMMR_ADDR) | DS26522_TMMR_FRM_EN);
/* RCR1, receive E1 B8zs & ESF */
slic_write(spi, DS26522_RCR1_ADDR,
DS26522_RCR1_E1_HDB3 | DS26522_RCR1_E1_CCS);
/* RSYSCLK=2.048MHz, RSYNC-Output */
slic_write(spi, DS26522_RIOCR_ADDR,
DS26522_RIOCR_2048KHZ | DS26522_RIOCR_RSIO_OUT);
/* TCR1 Transmit E1 b8zs */
slic_write(spi, DS26522_TCR1_ADDR, DS26522_TCR1_TB8ZS);
/* TSYSCLK=2.048MHz, TSYNC-Output */
slic_write(spi, DS26522_TIOCR_ADDR,
DS26522_TIOCR_2048KHZ | DS26522_TIOCR_TSIO_OUT);
/* Set E1TAF */
slic_write(spi, DS26522_E1TAF_ADDR, DS26522_E1TAF_DEFAULT);
/* Set E1TNAF register */
slic_write(spi, DS26522_E1TNAF_ADDR, DS26522_E1TNAF_DEFAULT);
/* Receive E1 Mode Framer Enable & init Done */
slic_write(spi, DS26522_RMMR_ADDR, slic_read(spi, DS26522_RMMR_ADDR) |
DS26522_RMMR_INIT_DONE);
/* Transmit E1 Mode Framer Enable & init Done */
slic_write(spi, DS26522_TMMR_ADDR, slic_read(spi, DS26522_TMMR_ADDR) |
DS26522_TMMR_INIT_DONE);
/* Configure LIU E1 mode */
slic_write(spi, DS26522_LTRCR_ADDR, DS26522_LTRCR_E1);
/* E1 Mode default 75 ohm w/Transmit Impedance Matlinking */
slic_write(spi, DS26522_LTITSR_ADDR,
DS26522_LTITSR_TLIS_75OHM | DS26522_LTITSR_LBOS_75OHM);
/* E1 Mode default 75 ohm Long Haul w/Receive Impedance Matlinking */
slic_write(spi, DS26522_LRISMR_ADDR,
DS26522_LRISMR_75OHM | DS26522_LRISMR_MAX);
/* Enable Transmit output */
slic_write(spi, DS26522_LMCR_ADDR, DS26522_LMCR_TE);
}
static int slic_ds26522_init_configure(struct spi_device *spi)
{
u16 addr;
/* set clock */
slic_write(spi, DS26522_GTCCR_ADDR, DS26522_GTCCR_BPREFSEL_REFCLKIN |
DS26522_GTCCR_BFREQSEL_2048KHZ |
DS26522_GTCCR_FREQSEL_2048KHZ);
slic_write(spi, DS26522_GTCR2_ADDR, DS26522_GTCR2_TSSYNCOUT);
slic_write(spi, DS26522_GFCR_ADDR, DS26522_GFCR_BPCLK_2048KHZ);
/* set gtcr */
slic_write(spi, DS26522_GTCR1_ADDR, DS26522_GTCR1);
/* Global LIU Software Reset Register */
slic_write(spi, DS26522_GLSRR_ADDR, DS26522_GLSRR_RESET);
/* Global Framer and BERT Software Reset Register */
slic_write(spi, DS26522_GFSRR_ADDR, DS26522_GFSRR_RESET);
usleep_range(100, 120);
slic_write(spi, DS26522_GLSRR_ADDR, DS26522_GLSRR_NORMAL);
slic_write(spi, DS26522_GFSRR_ADDR, DS26522_GFSRR_NORMAL);
/* Perform RX/TX SRESET,Reset receiver */
slic_write(spi, DS26522_RMMR_ADDR, DS26522_RMMR_SFTRST);
/* Reset tranceiver */
slic_write(spi, DS26522_TMMR_ADDR, DS26522_TMMR_SFTRST);
usleep_range(100, 120);
/* Zero all Framer Registers */
for (addr = DS26522_RF_ADDR_START; addr <= DS26522_RF_ADDR_END;
addr++)
slic_write(spi, addr, 0);
for (addr = DS26522_TF_ADDR_START; addr <= DS26522_TF_ADDR_END;
addr++)
slic_write(spi, addr, 0);
for (addr = DS26522_LIU_ADDR_START; addr <= DS26522_LIU_ADDR_END;
addr++)
slic_write(spi, addr, 0);
for (addr = DS26522_BERT_ADDR_START; addr <= DS26522_BERT_ADDR_END;
addr++)
slic_write(spi, addr, 0);
/* setup ds26522 for E1 specification */
ds26522_e1_spec_config(spi);
slic_write(spi, DS26522_GTCR1_ADDR, 0x00);
return 0;
}
static void slic_ds26522_remove(struct spi_device *spi)
{
pr_info("DS26522 module uninstalled\n");
}
static int slic_ds26522_probe(struct spi_device *spi)
{
int ret = 0;
g_spi = spi;
spi->bits_per_word = 8;
if (!get_slic_product_code(spi))
return ret;
ret = slic_ds26522_init_configure(spi);
if (ret == 0)
pr_info("DS26522 cs%d configured\n", spi_get_chipselect(spi, 0));
return ret;
}
static const struct spi_device_id slic_ds26522_id[] = {
{ .name = "ds26522" },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(spi, slic_ds26522_id);
static const struct of_device_id slic_ds26522_match[] = {
{
.compatible = "maxim,ds26522",
},
{},
};
MODULE_DEVICE_TABLE(of, slic_ds26522_match);
static struct spi_driver slic_ds26522_driver = {
.driver = {
.name = "ds26522",
.bus = &spi_bus_type,
.of_match_table = slic_ds26522_match,
},
.probe = slic_ds26522_probe,
.remove = slic_ds26522_remove,
.id_table = slic_ds26522_id,
};
module_spi_driver(slic_ds26522_driver);
+130
View File
@@ -0,0 +1,130 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* drivers/tdm/line_ctrl/slic_ds26522.h
*
* Copyright 2016 Freescale Semiconductor, Inc.
*
* Author: Zhao Qiang <B45475@freescale.com>
*/
#define DS26522_RF_ADDR_START 0x00
#define DS26522_RF_ADDR_END 0xef
#define DS26522_GLB_ADDR_START 0xf0
#define DS26522_GLB_ADDR_END 0xff
#define DS26522_TF_ADDR_START 0x100
#define DS26522_TF_ADDR_END 0x1ef
#define DS26522_LIU_ADDR_START 0x1000
#define DS26522_LIU_ADDR_END 0x101f
#define DS26522_TEST_ADDR_START 0x1008
#define DS26522_TEST_ADDR_END 0x101f
#define DS26522_BERT_ADDR_START 0x1100
#define DS26522_BERT_ADDR_END 0x110f
#define DS26522_RMMR_ADDR 0x80
#define DS26522_RCR1_ADDR 0x81
#define DS26522_RCR3_ADDR 0x83
#define DS26522_RIOCR_ADDR 0x84
#define DS26522_GTCR1_ADDR 0xf0
#define DS26522_GFCR_ADDR 0xf1
#define DS26522_GTCR2_ADDR 0xf2
#define DS26522_GTCCR_ADDR 0xf3
#define DS26522_GLSRR_ADDR 0xf5
#define DS26522_GFSRR_ADDR 0xf6
#define DS26522_IDR_ADDR 0xf8
#define DS26522_E1TAF_ADDR 0x164
#define DS26522_E1TNAF_ADDR 0x165
#define DS26522_TMMR_ADDR 0x180
#define DS26522_TCR1_ADDR 0x181
#define DS26522_TIOCR_ADDR 0x184
#define DS26522_LTRCR_ADDR 0x1000
#define DS26522_LTITSR_ADDR 0x1001
#define DS26522_LMCR_ADDR 0x1002
#define DS26522_LRISMR_ADDR 0x1007
#define MAX_NUM_OF_CHANNELS 8
#define PQ_MDS_8E1T1_BRD_REV 0x00
#define PQ_MDS_8E1T1_PLD_REV 0x00
#define DS26522_GTCCR_BPREFSEL_REFCLKIN 0xa0
#define DS26522_GTCCR_BFREQSEL_1544KHZ 0x08
#define DS26522_GTCCR_FREQSEL_1544KHZ 0x04
#define DS26522_GTCCR_BFREQSEL_2048KHZ 0x00
#define DS26522_GTCCR_FREQSEL_2048KHZ 0x00
#define DS26522_GFCR_BPCLK_2048KHZ 0x00
#define DS26522_GTCR2_TSSYNCOUT 0x02
#define DS26522_GTCR1 0x00
#define DS26522_GFSRR_RESET 0x01
#define DS26522_GFSRR_NORMAL 0x00
#define DS26522_GLSRR_RESET 0x01
#define DS26522_GLSRR_NORMAL 0x00
#define DS26522_RMMR_SFTRST 0x02
#define DS26522_RMMR_FRM_EN 0x80
#define DS26522_RMMR_INIT_DONE 0x40
#define DS26522_RMMR_T1 0x00
#define DS26522_RMMR_E1 0x01
#define DS26522_E1TAF_DEFAULT 0x1b
#define DS26522_E1TNAF_DEFAULT 0x40
#define DS26522_TMMR_SFTRST 0x02
#define DS26522_TMMR_FRM_EN 0x80
#define DS26522_TMMR_INIT_DONE 0x40
#define DS26522_TMMR_T1 0x00
#define DS26522_TMMR_E1 0x01
#define DS26522_RCR1_T1_SYNCT 0x80
#define DS26522_RCR1_T1_RB8ZS 0x40
#define DS26522_RCR1_T1_SYNCC 0x08
#define DS26522_RCR1_E1_HDB3 0x40
#define DS26522_RCR1_E1_CCS 0x20
#define DS26522_RIOCR_1544KHZ 0x00
#define DS26522_RIOCR_2048KHZ 0x10
#define DS26522_RIOCR_RSIO_OUT 0x00
#define DS26522_RCR3_FLB 0x01
#define DS26522_TIOCR_1544KHZ 0x00
#define DS26522_TIOCR_2048KHZ 0x10
#define DS26522_TIOCR_TSIO_OUT 0x04
#define DS26522_TCR1_TB8ZS 0x04
#define DS26522_LTRCR_T1 0x02
#define DS26522_LTRCR_E1 0x00
#define DS26522_LTITSR_TLIS_75OHM 0x00
#define DS26522_LTITSR_LBOS_75OHM 0x00
#define DS26522_LTITSR_TLIS_100OHM 0x10
#define DS26522_LTITSR_TLIS_0DB_CSU 0x00
#define DS26522_LRISMR_75OHM 0x00
#define DS26522_LRISMR_100OHM 0x10
#define DS26522_LRISMR_MAX 0x03
#define DS26522_LMCR_TE 0x01
enum line_rate {
LINE_RATE_T1, /* T1 line rate (1.544 Mbps) */
LINE_RATE_E1 /* E1 line rate (2.048 Mbps) */
};
enum tdm_trans_mode {
NORMAL = 0,
FRAMER_LB
};
enum card_support_type {
LM_CARD = 0,
DS26522_CARD,
NO_CARD
};
+842
View File
@@ -0,0 +1,842 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* wanXL serial card driver for Linux
* host part
*
* Copyright (C) 2003 Krzysztof Halasa <khc@pm.waw.pl>
*
* Status:
* - Only DTE (external clock) support with NRZ and NRZI encodings
* - wanXL100 will require minor driver modifications, no access to hw
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <linux/netdevice.h>
#include <linux/hdlc.h>
#include <linux/pci.h>
#include <linux/dma-mapping.h>
#include <linux/delay.h>
#include <asm/io.h>
#include "wanxl.h"
static const char *version = "wanXL serial card driver version: 0.48";
#define PLX_CTL_RESET 0x40000000 /* adapter reset */
#undef DEBUG_PKT
#undef DEBUG_PCI
/* MAILBOX #1 - PUTS COMMANDS */
#define MBX1_CMD_ABORTJ 0x85000000 /* Abort and Jump */
#ifdef __LITTLE_ENDIAN
#define MBX1_CMD_BSWAP 0x8C000001 /* little-endian Byte Swap Mode */
#else
#define MBX1_CMD_BSWAP 0x8C000000 /* big-endian Byte Swap Mode */
#endif
/* MAILBOX #2 - DRAM SIZE */
#define MBX2_MEMSZ_MASK 0xFFFF0000 /* PUTS Memory Size Register mask */
struct port {
struct net_device *dev;
struct card *card;
spinlock_t lock; /* for wanxl_xmit */
int node; /* physical port #0 - 3 */
unsigned int clock_type;
int tx_in, tx_out;
struct sk_buff *tx_skbs[TX_BUFFERS];
};
struct card_status {
desc_t rx_descs[RX_QUEUE_LENGTH];
port_status_t port_status[4];
};
struct card {
int n_ports; /* 1, 2 or 4 ports */
u8 irq;
u8 __iomem *plx; /* PLX PCI9060 virtual base address */
struct pci_dev *pdev; /* for pci_name(pdev) */
int rx_in;
struct sk_buff *rx_skbs[RX_QUEUE_LENGTH];
struct card_status *status; /* shared between host and card */
dma_addr_t status_address;
struct port ports[]; /* 1 - 4 port structures follow */
};
static inline struct port *dev_to_port(struct net_device *dev)
{
return (struct port *)dev_to_hdlc(dev)->priv;
}
static inline port_status_t *get_status(struct port *port)
{
return &port->card->status->port_status[port->node];
}
#ifdef DEBUG_PCI
static inline dma_addr_t pci_map_single_debug(struct pci_dev *pdev, void *ptr,
size_t size, int direction)
{
dma_addr_t addr = dma_map_single(&pdev->dev, ptr, size, direction);
if (addr + size > 0x100000000LL)
pr_crit("%s: pci_map_single() returned memory at 0x%llx!\n",
pci_name(pdev), (unsigned long long)addr);
return addr;
}
#undef pci_map_single
#define pci_map_single pci_map_single_debug
#endif
/* Cable and/or personality module change interrupt service */
static inline void wanxl_cable_intr(struct port *port)
{
u32 value = get_status(port)->cable;
int valid = 1;
const char *cable, *pm, *dte = "", *dsr = "", *dcd = "";
switch (value & 0x7) {
case STATUS_CABLE_V35:
cable = "V.35";
break;
case STATUS_CABLE_X21:
cable = "X.21";
break;
case STATUS_CABLE_V24:
cable = "V.24";
break;
case STATUS_CABLE_EIA530:
cable = "EIA530";
break;
case STATUS_CABLE_NONE:
cable = "no";
break;
default:
cable = "invalid";
}
switch ((value >> STATUS_CABLE_PM_SHIFT) & 0x7) {
case STATUS_CABLE_V35:
pm = "V.35";
break;
case STATUS_CABLE_X21:
pm = "X.21";
break;
case STATUS_CABLE_V24:
pm = "V.24";
break;
case STATUS_CABLE_EIA530:
pm = "EIA530";
break;
case STATUS_CABLE_NONE:
pm = "no personality";
valid = 0;
break;
default:
pm = "invalid personality";
valid = 0;
}
if (valid) {
if ((value & 7) == ((value >> STATUS_CABLE_PM_SHIFT) & 7)) {
dsr = (value & STATUS_CABLE_DSR) ? ", DSR ON" :
", DSR off";
dcd = (value & STATUS_CABLE_DCD) ? ", carrier ON" :
", carrier off";
}
dte = (value & STATUS_CABLE_DCE) ? " DCE" : " DTE";
}
netdev_info(port->dev, "%s%s module, %s cable%s%s\n",
pm, dte, cable, dsr, dcd);
if (value & STATUS_CABLE_DCD)
netif_carrier_on(port->dev);
else
netif_carrier_off(port->dev);
}
/* Transmit complete interrupt service */
static inline void wanxl_tx_intr(struct port *port)
{
struct net_device *dev = port->dev;
while (1) {
desc_t *desc = &get_status(port)->tx_descs[port->tx_in];
struct sk_buff *skb = port->tx_skbs[port->tx_in];
switch (desc->stat) {
case PACKET_FULL:
case PACKET_EMPTY:
netif_wake_queue(dev);
return;
case PACKET_UNDERRUN:
dev->stats.tx_errors++;
dev->stats.tx_fifo_errors++;
break;
default:
dev->stats.tx_packets++;
dev->stats.tx_bytes += skb->len;
}
desc->stat = PACKET_EMPTY; /* Free descriptor */
dma_unmap_single(&port->card->pdev->dev, desc->address,
skb->len, DMA_TO_DEVICE);
dev_consume_skb_irq(skb);
port->tx_in = (port->tx_in + 1) % TX_BUFFERS;
}
}
/* Receive complete interrupt service */
static inline void wanxl_rx_intr(struct card *card)
{
desc_t *desc;
while (desc = &card->status->rx_descs[card->rx_in],
desc->stat != PACKET_EMPTY) {
if ((desc->stat & PACKET_PORT_MASK) > card->n_ports) {
pr_crit("%s: received packet for nonexistent port\n",
pci_name(card->pdev));
} else {
struct sk_buff *skb = card->rx_skbs[card->rx_in];
struct port *port = &card->ports[desc->stat &
PACKET_PORT_MASK];
struct net_device *dev = port->dev;
if (!skb) {
dev->stats.rx_dropped++;
} else {
dma_unmap_single(&card->pdev->dev,
desc->address, BUFFER_LENGTH,
DMA_FROM_DEVICE);
skb_put(skb, desc->length);
#ifdef DEBUG_PKT
printk(KERN_DEBUG "%s RX(%i):", dev->name,
skb->len);
debug_frame(skb);
#endif
dev->stats.rx_packets++;
dev->stats.rx_bytes += skb->len;
skb->protocol = hdlc_type_trans(skb, dev);
netif_rx(skb);
skb = NULL;
}
if (!skb) {
skb = dev_alloc_skb(BUFFER_LENGTH);
desc->address = skb ?
dma_map_single(&card->pdev->dev,
skb->data,
BUFFER_LENGTH,
DMA_FROM_DEVICE) : 0;
card->rx_skbs[card->rx_in] = skb;
}
}
desc->stat = PACKET_EMPTY; /* Free descriptor */
card->rx_in = (card->rx_in + 1) % RX_QUEUE_LENGTH;
}
}
static irqreturn_t wanxl_intr(int irq, void *dev_id)
{
struct card *card = dev_id;
int i;
u32 stat;
int handled = 0;
while ((stat = readl(card->plx + PLX_DOORBELL_FROM_CARD)) != 0) {
handled = 1;
writel(stat, card->plx + PLX_DOORBELL_FROM_CARD);
for (i = 0; i < card->n_ports; i++) {
if (stat & (1 << (DOORBELL_FROM_CARD_TX_0 + i)))
wanxl_tx_intr(&card->ports[i]);
if (stat & (1 << (DOORBELL_FROM_CARD_CABLE_0 + i)))
wanxl_cable_intr(&card->ports[i]);
}
if (stat & (1 << DOORBELL_FROM_CARD_RX))
wanxl_rx_intr(card);
}
return IRQ_RETVAL(handled);
}
static netdev_tx_t wanxl_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct port *port = dev_to_port(dev);
desc_t *desc;
spin_lock(&port->lock);
desc = &get_status(port)->tx_descs[port->tx_out];
if (desc->stat != PACKET_EMPTY) {
/* should never happen - previous xmit should stop queue */
#ifdef DEBUG_PKT
printk(KERN_DEBUG "%s: transmitter buffer full\n", dev->name);
#endif
netif_stop_queue(dev);
spin_unlock(&port->lock);
return NETDEV_TX_BUSY; /* request packet to be queued */
}
#ifdef DEBUG_PKT
printk(KERN_DEBUG "%s TX(%i):", dev->name, skb->len);
debug_frame(skb);
#endif
port->tx_skbs[port->tx_out] = skb;
desc->address = dma_map_single(&port->card->pdev->dev, skb->data,
skb->len, DMA_TO_DEVICE);
desc->length = skb->len;
desc->stat = PACKET_FULL;
writel(1 << (DOORBELL_TO_CARD_TX_0 + port->node),
port->card->plx + PLX_DOORBELL_TO_CARD);
port->tx_out = (port->tx_out + 1) % TX_BUFFERS;
if (get_status(port)->tx_descs[port->tx_out].stat != PACKET_EMPTY) {
netif_stop_queue(dev);
#ifdef DEBUG_PKT
printk(KERN_DEBUG "%s: transmitter buffer full\n", dev->name);
#endif
}
spin_unlock(&port->lock);
return NETDEV_TX_OK;
}
static int wanxl_attach(struct net_device *dev, unsigned short encoding,
unsigned short parity)
{
struct port *port = dev_to_port(dev);
if (encoding != ENCODING_NRZ &&
encoding != ENCODING_NRZI)
return -EINVAL;
if (parity != PARITY_NONE &&
parity != PARITY_CRC32_PR1_CCITT &&
parity != PARITY_CRC16_PR1_CCITT &&
parity != PARITY_CRC32_PR0_CCITT &&
parity != PARITY_CRC16_PR0_CCITT)
return -EINVAL;
get_status(port)->encoding = encoding;
get_status(port)->parity = parity;
return 0;
}
static int wanxl_ioctl(struct net_device *dev, struct if_settings *ifs)
{
const size_t size = sizeof(sync_serial_settings);
sync_serial_settings line;
struct port *port = dev_to_port(dev);
switch (ifs->type) {
case IF_GET_IFACE:
ifs->type = IF_IFACE_SYNC_SERIAL;
if (ifs->size < size) {
ifs->size = size; /* data size wanted */
return -ENOBUFS;
}
memset(&line, 0, sizeof(line));
line.clock_type = get_status(port)->clocking;
line.clock_rate = 0;
line.loopback = 0;
if (copy_to_user(ifs->ifs_ifsu.sync, &line, size))
return -EFAULT;
return 0;
case IF_IFACE_SYNC_SERIAL:
if (!capable(CAP_NET_ADMIN))
return -EPERM;
if (dev->flags & IFF_UP)
return -EBUSY;
if (copy_from_user(&line, ifs->ifs_ifsu.sync,
size))
return -EFAULT;
if (line.clock_type != CLOCK_EXT &&
line.clock_type != CLOCK_TXFROMRX)
return -EINVAL; /* No such clock setting */
if (line.loopback != 0)
return -EINVAL;
get_status(port)->clocking = line.clock_type;
return 0;
default:
return hdlc_ioctl(dev, ifs);
}
}
static int wanxl_open(struct net_device *dev)
{
struct port *port = dev_to_port(dev);
u8 __iomem *dbr = port->card->plx + PLX_DOORBELL_TO_CARD;
unsigned long timeout;
int i;
if (get_status(port)->open) {
netdev_err(dev, "port already open\n");
return -EIO;
}
i = hdlc_open(dev);
if (i)
return i;
port->tx_in = port->tx_out = 0;
for (i = 0; i < TX_BUFFERS; i++)
get_status(port)->tx_descs[i].stat = PACKET_EMPTY;
/* signal the card */
writel(1 << (DOORBELL_TO_CARD_OPEN_0 + port->node), dbr);
timeout = jiffies + HZ;
do {
if (get_status(port)->open) {
netif_start_queue(dev);
return 0;
}
} while (time_after(timeout, jiffies));
netdev_err(dev, "unable to open port\n");
/* ask the card to close the port, should it be still alive */
writel(1 << (DOORBELL_TO_CARD_CLOSE_0 + port->node), dbr);
return -EFAULT;
}
static int wanxl_close(struct net_device *dev)
{
struct port *port = dev_to_port(dev);
unsigned long timeout;
int i;
hdlc_close(dev);
/* signal the card */
writel(1 << (DOORBELL_TO_CARD_CLOSE_0 + port->node),
port->card->plx + PLX_DOORBELL_TO_CARD);
timeout = jiffies + HZ;
do {
if (!get_status(port)->open)
break;
} while (time_after(timeout, jiffies));
if (get_status(port)->open)
netdev_err(dev, "unable to close port\n");
netif_stop_queue(dev);
for (i = 0; i < TX_BUFFERS; i++) {
desc_t *desc = &get_status(port)->tx_descs[i];
if (desc->stat != PACKET_EMPTY) {
desc->stat = PACKET_EMPTY;
dma_unmap_single(&port->card->pdev->dev,
desc->address, port->tx_skbs[i]->len,
DMA_TO_DEVICE);
dev_kfree_skb(port->tx_skbs[i]);
}
}
return 0;
}
static struct net_device_stats *wanxl_get_stats(struct net_device *dev)
{
struct port *port = dev_to_port(dev);
dev->stats.rx_over_errors = get_status(port)->rx_overruns;
dev->stats.rx_frame_errors = get_status(port)->rx_frame_errors;
dev->stats.rx_errors = dev->stats.rx_over_errors +
dev->stats.rx_frame_errors;
return &dev->stats;
}
static int wanxl_puts_command(struct card *card, u32 cmd)
{
unsigned long timeout = jiffies + 5 * HZ;
writel(cmd, card->plx + PLX_MAILBOX_1);
do {
if (readl(card->plx + PLX_MAILBOX_1) == 0)
return 0;
schedule();
} while (time_after(timeout, jiffies));
return -1;
}
static void wanxl_reset(struct card *card)
{
u32 old_value = readl(card->plx + PLX_CONTROL) & ~PLX_CTL_RESET;
writel(0x80, card->plx + PLX_MAILBOX_0);
writel(old_value | PLX_CTL_RESET, card->plx + PLX_CONTROL);
readl(card->plx + PLX_CONTROL); /* wait for posted write */
udelay(1);
writel(old_value, card->plx + PLX_CONTROL);
readl(card->plx + PLX_CONTROL); /* wait for posted write */
}
static void wanxl_pci_remove_one(struct pci_dev *pdev)
{
struct card *card = pci_get_drvdata(pdev);
int i;
for (i = 0; i < card->n_ports; i++) {
unregister_hdlc_device(card->ports[i].dev);
free_netdev(card->ports[i].dev);
}
/* unregister and free all host resources */
if (card->irq)
free_irq(card->irq, card);
if (card->plx)
wanxl_reset(card);
for (i = 0; i < RX_QUEUE_LENGTH; i++)
if (card->rx_skbs[i]) {
dma_unmap_single(&card->pdev->dev,
card->status->rx_descs[i].address,
BUFFER_LENGTH, DMA_FROM_DEVICE);
dev_kfree_skb(card->rx_skbs[i]);
}
if (card->plx)
iounmap(card->plx);
if (card->status)
dma_free_coherent(&pdev->dev, sizeof(struct card_status),
card->status, card->status_address);
pci_release_regions(pdev);
pci_disable_device(pdev);
kfree(card);
}
#include "wanxlfw.inc"
static const struct net_device_ops wanxl_ops = {
.ndo_open = wanxl_open,
.ndo_stop = wanxl_close,
.ndo_start_xmit = hdlc_start_xmit,
.ndo_siocwandev = wanxl_ioctl,
.ndo_get_stats = wanxl_get_stats,
};
static int wanxl_pci_init_one(struct pci_dev *pdev,
const struct pci_device_id *ent)
{
struct card *card;
u32 ramsize, stat;
unsigned long timeout;
u32 plx_phy; /* PLX PCI base address */
u32 mem_phy; /* memory PCI base addr */
u8 __iomem *mem; /* memory virtual base addr */
int i, ports;
#ifndef MODULE
pr_info_once("%s\n", version);
#endif
i = pci_enable_device(pdev);
if (i)
return i;
/* QUICC can only access first 256 MB of host RAM directly,
* but PLX9060 DMA does 32-bits for actual packet data transfers
*/
/* FIXME when PCI/DMA subsystems are fixed.
* We set both dma_mask and consistent_dma_mask to 28 bits
* and pray pci_alloc_consistent() will use this info. It should
* work on most platforms
*/
if (dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(28)) ||
dma_set_mask(&pdev->dev, DMA_BIT_MASK(28))) {
pr_err("No usable DMA configuration\n");
pci_disable_device(pdev);
return -EIO;
}
i = pci_request_regions(pdev, "wanXL");
if (i) {
pci_disable_device(pdev);
return i;
}
switch (pdev->device) {
case PCI_DEVICE_ID_SBE_WANXL100:
ports = 1;
break;
case PCI_DEVICE_ID_SBE_WANXL200:
ports = 2;
break;
default:
ports = 4;
}
card = kzalloc_flex(*card, ports, ports);
if (!card) {
pci_release_regions(pdev);
pci_disable_device(pdev);
return -ENOBUFS;
}
pci_set_drvdata(pdev, card);
card->pdev = pdev;
card->status = dma_alloc_coherent(&pdev->dev,
sizeof(struct card_status),
&card->status_address, GFP_KERNEL);
if (!card->status) {
wanxl_pci_remove_one(pdev);
return -ENOBUFS;
}
#ifdef DEBUG_PCI
printk(KERN_DEBUG "wanXL %s: pci_alloc_consistent() returned memory"
" at 0x%LX\n", pci_name(pdev),
(unsigned long long)card->status_address);
#endif
/* FIXME when PCI/DMA subsystems are fixed.
* We set both dma_mask and consistent_dma_mask back to 32 bits
* to indicate the card can do 32-bit DMA addressing
*/
if (dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32)) ||
dma_set_mask(&pdev->dev, DMA_BIT_MASK(32))) {
pr_err("No usable DMA configuration\n");
wanxl_pci_remove_one(pdev);
return -EIO;
}
/* set up PLX mapping */
plx_phy = pci_resource_start(pdev, 0);
card->plx = ioremap(plx_phy, 0x70);
if (!card->plx) {
pr_err("ioremap() failed\n");
wanxl_pci_remove_one(pdev);
return -EFAULT;
}
#if RESET_WHILE_LOADING
wanxl_reset(card);
#endif
timeout = jiffies + 20 * HZ;
while ((stat = readl(card->plx + PLX_MAILBOX_0)) != 0) {
if (time_before(timeout, jiffies)) {
pr_warn("%s: timeout waiting for PUTS to complete\n",
pci_name(pdev));
wanxl_pci_remove_one(pdev);
return -ENODEV;
}
switch (stat & 0xC0) {
case 0x00: /* hmm - PUTS completed with non-zero code? */
case 0x80: /* PUTS still testing the hardware */
break;
default:
pr_warn("%s: PUTS test 0x%X failed\n",
pci_name(pdev), stat & 0x30);
wanxl_pci_remove_one(pdev);
return -ENODEV;
}
schedule();
}
/* get on-board memory size (PUTS detects no more than 4 MB) */
ramsize = readl(card->plx + PLX_MAILBOX_2) & MBX2_MEMSZ_MASK;
/* set up on-board RAM mapping */
mem_phy = pci_resource_start(pdev, 2);
/* sanity check the board's reported memory size */
if (ramsize < BUFFERS_ADDR +
(TX_BUFFERS + RX_BUFFERS) * BUFFER_LENGTH * ports) {
pr_warn("%s: no enough on-board RAM (%u bytes detected, %u bytes required)\n",
pci_name(pdev), ramsize,
BUFFERS_ADDR +
(TX_BUFFERS + RX_BUFFERS) * BUFFER_LENGTH * ports);
wanxl_pci_remove_one(pdev);
return -ENODEV;
}
if (wanxl_puts_command(card, MBX1_CMD_BSWAP)) {
pr_warn("%s: unable to Set Byte Swap Mode\n", pci_name(pdev));
wanxl_pci_remove_one(pdev);
return -ENODEV;
}
for (i = 0; i < RX_QUEUE_LENGTH; i++) {
struct sk_buff *skb = dev_alloc_skb(BUFFER_LENGTH);
card->rx_skbs[i] = skb;
if (skb)
card->status->rx_descs[i].address =
dma_map_single(&card->pdev->dev, skb->data,
BUFFER_LENGTH, DMA_FROM_DEVICE);
}
mem = ioremap(mem_phy, PDM_OFFSET + sizeof(firmware));
if (!mem) {
pr_err("ioremap() failed\n");
wanxl_pci_remove_one(pdev);
return -EFAULT;
}
for (i = 0; i < sizeof(firmware); i += 4)
writel(ntohl(*(__be32 *)(firmware + i)), mem + PDM_OFFSET + i);
for (i = 0; i < ports; i++)
writel(card->status_address +
(void *)&card->status->port_status[i] -
(void *)card->status, mem + PDM_OFFSET + 4 + i * 4);
writel(card->status_address, mem + PDM_OFFSET + 20);
writel(PDM_OFFSET, mem);
iounmap(mem);
writel(0, card->plx + PLX_MAILBOX_5);
if (wanxl_puts_command(card, MBX1_CMD_ABORTJ)) {
pr_warn("%s: unable to Abort and Jump\n", pci_name(pdev));
wanxl_pci_remove_one(pdev);
return -ENODEV;
}
timeout = jiffies + 5 * HZ;
do {
stat = readl(card->plx + PLX_MAILBOX_5);
if (stat)
break;
schedule();
} while (time_after(timeout, jiffies));
if (!stat) {
pr_warn("%s: timeout while initializing card firmware\n",
pci_name(pdev));
wanxl_pci_remove_one(pdev);
return -ENODEV;
}
#if DETECT_RAM
ramsize = stat;
#endif
pr_info("%s: at 0x%X, %u KB of RAM at 0x%X, irq %u\n",
pci_name(pdev), plx_phy, ramsize / 1024, mem_phy, pdev->irq);
/* Allocate IRQ */
if (request_irq(pdev->irq, wanxl_intr, IRQF_SHARED, "wanXL", card)) {
pr_warn("%s: could not allocate IRQ%i\n",
pci_name(pdev), pdev->irq);
wanxl_pci_remove_one(pdev);
return -EBUSY;
}
card->irq = pdev->irq;
for (i = 0; i < ports; i++) {
hdlc_device *hdlc;
struct port *port = &card->ports[i];
struct net_device *dev = alloc_hdlcdev(port);
if (!dev) {
pr_err("%s: unable to allocate memory\n",
pci_name(pdev));
wanxl_pci_remove_one(pdev);
return -ENOMEM;
}
port->dev = dev;
hdlc = dev_to_hdlc(dev);
spin_lock_init(&port->lock);
dev->tx_queue_len = 50;
dev->netdev_ops = &wanxl_ops;
hdlc->attach = wanxl_attach;
hdlc->xmit = wanxl_xmit;
port->card = card;
port->node = i;
get_status(port)->clocking = CLOCK_EXT;
if (register_hdlc_device(dev)) {
pr_err("%s: unable to register hdlc device\n",
pci_name(pdev));
free_netdev(dev);
wanxl_pci_remove_one(pdev);
return -ENOBUFS;
}
card->n_ports++;
}
pr_info("%s: port", pci_name(pdev));
for (i = 0; i < ports; i++)
pr_cont("%s #%i: %s",
i ? "," : "", i, card->ports[i].dev->name);
pr_cont("\n");
for (i = 0; i < ports; i++)
wanxl_cable_intr(&card->ports[i]); /* get carrier status etc.*/
return 0;
}
static const struct pci_device_id wanxl_pci_tbl[] = {
{ PCI_VDEVICE(SBE, PCI_DEVICE_ID_SBE_WANXL100) },
{ PCI_VDEVICE(SBE, PCI_DEVICE_ID_SBE_WANXL200) },
{ PCI_VDEVICE(SBE, PCI_DEVICE_ID_SBE_WANXL400) },
{ }
};
static struct pci_driver wanxl_pci_driver = {
.name = "wanXL",
.id_table = wanxl_pci_tbl,
.probe = wanxl_pci_init_one,
.remove = wanxl_pci_remove_one,
};
static int __init wanxl_init_module(void)
{
#ifdef MODULE
pr_info("%s\n", version);
#endif
return pci_register_driver(&wanxl_pci_driver);
}
static void __exit wanxl_cleanup_module(void)
{
pci_unregister_driver(&wanxl_pci_driver);
}
MODULE_AUTHOR("Krzysztof Halasa <khc@pm.waw.pl>");
MODULE_DESCRIPTION("SBE Inc. wanXL serial port driver");
MODULE_LICENSE("GPL v2");
MODULE_DEVICE_TABLE(pci, wanxl_pci_tbl);
module_init(wanxl_init_module);
module_exit(wanxl_cleanup_module);
+149
View File
@@ -0,0 +1,149 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* wanXL serial card driver for Linux
* definitions common to host driver and card firmware
*
* Copyright (C) 2003 Krzysztof Halasa <khc@pm.waw.pl>
*/
#define RESET_WHILE_LOADING 0
/* you must rebuild the firmware if any of the following is changed */
#define DETECT_RAM 0 /* needed for > 4MB RAM, 16 MB maximum */
#define QUICC_MEMCPY_USES_PLX 1 /* must be used if the host has > 256 MB RAM */
#define STATUS_CABLE_V35 2
#define STATUS_CABLE_X21 3
#define STATUS_CABLE_V24 4
#define STATUS_CABLE_EIA530 5
#define STATUS_CABLE_INVALID 6
#define STATUS_CABLE_NONE 7
#define STATUS_CABLE_DCE 0x8000
#define STATUS_CABLE_DSR 0x0010
#define STATUS_CABLE_DCD 0x0008
#define STATUS_CABLE_PM_SHIFT 5
#define PDM_OFFSET 0x1000
#define TX_BUFFERS 10 /* per port */
#define RX_BUFFERS 30
#define RX_QUEUE_LENGTH 40 /* card->host queue length - per card */
#define PACKET_EMPTY 0x00
#define PACKET_FULL 0x10
#define PACKET_SENT 0x20 /* TX only */
#define PACKET_UNDERRUN 0x30 /* TX only */
#define PACKET_PORT_MASK 0x03 /* RX only */
/* bit numbers in PLX9060 doorbell registers */
#define DOORBELL_FROM_CARD_TX_0 0 /* packet sent by the card */
#define DOORBELL_FROM_CARD_TX_1 1
#define DOORBELL_FROM_CARD_TX_2 2
#define DOORBELL_FROM_CARD_TX_3 3
#define DOORBELL_FROM_CARD_RX 4
#define DOORBELL_FROM_CARD_CABLE_0 5 /* cable/PM/etc. changed */
#define DOORBELL_FROM_CARD_CABLE_1 6
#define DOORBELL_FROM_CARD_CABLE_2 7
#define DOORBELL_FROM_CARD_CABLE_3 8
#define DOORBELL_TO_CARD_OPEN_0 0
#define DOORBELL_TO_CARD_OPEN_1 1
#define DOORBELL_TO_CARD_OPEN_2 2
#define DOORBELL_TO_CARD_OPEN_3 3
#define DOORBELL_TO_CARD_CLOSE_0 4
#define DOORBELL_TO_CARD_CLOSE_1 5
#define DOORBELL_TO_CARD_CLOSE_2 6
#define DOORBELL_TO_CARD_CLOSE_3 7
#define DOORBELL_TO_CARD_TX_0 8 /* outbound packet queued */
#define DOORBELL_TO_CARD_TX_1 9
#define DOORBELL_TO_CARD_TX_2 10
#define DOORBELL_TO_CARD_TX_3 11
/* firmware-only status bits, starting from last DOORBELL_TO_CARD + 1 */
#define TASK_SCC_0 12
#define TASK_SCC_1 13
#define TASK_SCC_2 14
#define TASK_SCC_3 15
#define ALIGN32(x) (((x) + 3) & 0xFFFFFFFC)
#define BUFFER_LENGTH ALIGN32(HDLC_MAX_MRU + 4) /* 4 bytes for 32-bit CRC */
/* Address of TX and RX buffers in 68360 address space */
#define BUFFERS_ADDR 0x4000 /* 16 KB */
#ifndef __ASSEMBLER__
#define PLX_OFFSET 0
#else
#define PLX_OFFSET PLX + 0x80
#endif
#define PLX_MAILBOX_0 (PLX_OFFSET + 0x40)
#define PLX_MAILBOX_1 (PLX_OFFSET + 0x44)
#define PLX_MAILBOX_2 (PLX_OFFSET + 0x48)
#define PLX_MAILBOX_3 (PLX_OFFSET + 0x4C)
#define PLX_MAILBOX_4 (PLX_OFFSET + 0x50)
#define PLX_MAILBOX_5 (PLX_OFFSET + 0x54)
#define PLX_MAILBOX_6 (PLX_OFFSET + 0x58)
#define PLX_MAILBOX_7 (PLX_OFFSET + 0x5C)
#define PLX_DOORBELL_TO_CARD (PLX_OFFSET + 0x60)
#define PLX_DOORBELL_FROM_CARD (PLX_OFFSET + 0x64)
#define PLX_INTERRUPT_CS (PLX_OFFSET + 0x68)
#define PLX_CONTROL (PLX_OFFSET + 0x6C)
#ifdef __ASSEMBLER__
#define PLX_DMA_0_MODE (PLX + 0x100)
#define PLX_DMA_0_PCI (PLX + 0x104)
#define PLX_DMA_0_LOCAL (PLX + 0x108)
#define PLX_DMA_0_LENGTH (PLX + 0x10C)
#define PLX_DMA_0_DESC (PLX + 0x110)
#define PLX_DMA_1_MODE (PLX + 0x114)
#define PLX_DMA_1_PCI (PLX + 0x118)
#define PLX_DMA_1_LOCAL (PLX + 0x11C)
#define PLX_DMA_1_LENGTH (PLX + 0x120)
#define PLX_DMA_1_DESC (PLX + 0x124)
#define PLX_DMA_CMD_STS (PLX + 0x128)
#define PLX_DMA_ARBITR_0 (PLX + 0x12C)
#define PLX_DMA_ARBITR_1 (PLX + 0x130)
#endif
#define DESC_LENGTH 12
/* offsets from start of status_t */
/* card to host */
#define STATUS_OPEN 0
#define STATUS_CABLE (STATUS_OPEN + 4)
#define STATUS_RX_OVERRUNS (STATUS_CABLE + 4)
#define STATUS_RX_FRAME_ERRORS (STATUS_RX_OVERRUNS + 4)
/* host to card */
#define STATUS_PARITY (STATUS_RX_FRAME_ERRORS + 4)
#define STATUS_ENCODING (STATUS_PARITY + 4)
#define STATUS_CLOCKING (STATUS_ENCODING + 4)
#define STATUS_TX_DESCS (STATUS_CLOCKING + 4)
#ifndef __ASSEMBLER__
typedef struct {
volatile u32 stat;
u32 address; /* PCI address */
volatile u32 length;
}desc_t;
typedef struct {
// Card to host
volatile u32 open;
volatile u32 cable;
volatile u32 rx_overruns;
volatile u32 rx_frame_errors;
// Host to card
u32 parity;
u32 encoding;
u32 clocking;
desc_t tx_descs[TX_BUFFERS];
}port_status_t;
#endif /* __ASSEMBLER__ */
+894
View File
@@ -0,0 +1,894 @@
/* SPDX-License-Identifier: GPL-2.0-only */
.psize 0
/*
wanXL serial card driver for Linux
card firmware part
Copyright (C) 2003 Krzysztof Halasa <khc@pm.waw.pl>
DPRAM BDs:
0x000 - 0x050 TX#0 0x050 - 0x140 RX#0
0x140 - 0x190 TX#1 0x190 - 0x280 RX#1
0x280 - 0x2D0 TX#2 0x2D0 - 0x3C0 RX#2
0x3C0 - 0x410 TX#3 0x410 - 0x500 RX#3
000 5FF 1536 Bytes Dual-Port RAM User Data / BDs
600 6FF 256 Bytes Dual-Port RAM User Data / BDs
700 7FF 256 Bytes Dual-Port RAM User Data / BDs
C00 CBF 192 Bytes Dual-Port RAM Parameter RAM Page 1
D00 DBF 192 Bytes Dual-Port RAM Parameter RAM Page 2
E00 EBF 192 Bytes Dual-Port RAM Parameter RAM Page 3
F00 FBF 192 Bytes Dual-Port RAM Parameter RAM Page 4
local interrupts level
NMI 7
PIT timer, CPM (RX/TX complete) 4
PCI9060 DMA and PCI doorbells 3
Cable - not used 1
*/
#include <linux/hdlc.h>
#include <linux/hdlc/ioctl.h>
#include "wanxl.h"
/* memory addresses and offsets */
MAX_RAM_SIZE = 16 * 1024 * 1024 // max RAM supported by hardware
PCI9060_VECTOR = 0x0000006C
CPM_IRQ_BASE = 0x40
ERROR_VECTOR = CPM_IRQ_BASE * 4
SCC1_VECTOR = (CPM_IRQ_BASE + 0x1E) * 4
SCC2_VECTOR = (CPM_IRQ_BASE + 0x1D) * 4
SCC3_VECTOR = (CPM_IRQ_BASE + 0x1C) * 4
SCC4_VECTOR = (CPM_IRQ_BASE + 0x1B) * 4
CPM_IRQ_LEVEL = 4
TIMER_IRQ = 128
TIMER_IRQ_LEVEL = 4
PITR_CONST = 0x100 + 16 // 1 Hz timer
MBAR = 0x0003FF00
VALUE_WINDOW = 0x40000000
ORDER_WINDOW = 0xC0000000
PLX = 0xFFF90000
CSRA = 0xFFFB0000
CSRB = 0xFFFB0002
CSRC = 0xFFFB0004
CSRD = 0xFFFB0006
STATUS_CABLE_LL = 0x2000
STATUS_CABLE_DTR = 0x1000
DPRBASE = 0xFFFC0000
SCC1_BASE = DPRBASE + 0xC00
MISC_BASE = DPRBASE + 0xCB0
SCC2_BASE = DPRBASE + 0xD00
SCC3_BASE = DPRBASE + 0xE00
SCC4_BASE = DPRBASE + 0xF00
// offset from SCCx_BASE
// SCC_xBASE contain offsets from DPRBASE and must be divisible by 8
SCC_RBASE = 0 // 16-bit RxBD base address
SCC_TBASE = 2 // 16-bit TxBD base address
SCC_RFCR = 4 // 8-bit Rx function code
SCC_TFCR = 5 // 8-bit Tx function code
SCC_MRBLR = 6 // 16-bit maximum Rx buffer length
SCC_C_MASK = 0x34 // 32-bit CRC constant
SCC_C_PRES = 0x38 // 32-bit CRC preset
SCC_MFLR = 0x46 // 16-bit max Rx frame length (without flags)
REGBASE = DPRBASE + 0x1000
PICR = REGBASE + 0x026 // 16-bit periodic irq control
PITR = REGBASE + 0x02A // 16-bit periodic irq timing
OR1 = REGBASE + 0x064 // 32-bit RAM bank #1 options
CICR = REGBASE + 0x540 // 32(24)-bit CP interrupt config
CIMR = REGBASE + 0x548 // 32-bit CP interrupt mask
CISR = REGBASE + 0x54C // 32-bit CP interrupts in-service
PADIR = REGBASE + 0x550 // 16-bit PortA data direction bitmap
PAPAR = REGBASE + 0x552 // 16-bit PortA pin assignment bitmap
PAODR = REGBASE + 0x554 // 16-bit PortA open drain bitmap
PADAT = REGBASE + 0x556 // 16-bit PortA data register
PCDIR = REGBASE + 0x560 // 16-bit PortC data direction bitmap
PCPAR = REGBASE + 0x562 // 16-bit PortC pin assignment bitmap
PCSO = REGBASE + 0x564 // 16-bit PortC special options
PCDAT = REGBASE + 0x566 // 16-bit PortC data register
PCINT = REGBASE + 0x568 // 16-bit PortC interrupt control
CR = REGBASE + 0x5C0 // 16-bit Command register
SCC1_REGS = REGBASE + 0x600
SCC2_REGS = REGBASE + 0x620
SCC3_REGS = REGBASE + 0x640
SCC4_REGS = REGBASE + 0x660
SICR = REGBASE + 0x6EC // 32-bit SI clock route
// offset from SCCx_REGS
SCC_GSMR_L = 0x00 // 32 bits
SCC_GSMR_H = 0x04 // 32 bits
SCC_PSMR = 0x08 // 16 bits
SCC_TODR = 0x0C // 16 bits
SCC_DSR = 0x0E // 16 bits
SCC_SCCE = 0x10 // 16 bits
SCC_SCCM = 0x14 // 16 bits
SCC_SCCS = 0x17 // 8 bits
#if QUICC_MEMCPY_USES_PLX
.macro memcpy_from_pci src, dest, len // len must be < 8 MB
addl #3, \len
andl #0xFFFFFFFC, \len // always copy n * 4 bytes
movel \src, PLX_DMA_0_PCI
movel \dest, PLX_DMA_0_LOCAL
movel \len, PLX_DMA_0_LENGTH
movel #0x0103, PLX_DMA_CMD_STS // start channel 0 transfer
bsr memcpy_from_pci_run
.endm
.macro memcpy_to_pci src, dest, len
addl #3, \len
andl #0xFFFFFFFC, \len // always copy n * 4 bytes
movel \src, PLX_DMA_1_LOCAL
movel \dest, PLX_DMA_1_PCI
movel \len, PLX_DMA_1_LENGTH
movel #0x0301, PLX_DMA_CMD_STS // start channel 1 transfer
bsr memcpy_to_pci_run
.endm
#else
.macro memcpy src, dest, len // len must be < 65536 bytes
movel %d7, -(%sp) // src and dest must be < 256 MB
movel \len, %d7 // bits 0 and 1
lsrl #2, \len
andl \len, \len
beq 99f // only 0 - 3 bytes
subl #1, \len // for dbf
98: movel (\src)+, (\dest)+
dbfw \len, 98b
99: movel %d7, \len
btstl #1, \len
beq 99f
movew (\src)+, (\dest)+
99: btstl #0, \len
beq 99f
moveb (\src)+, (\dest)+
99:
movel (%sp)+, %d7
.endm
.macro memcpy_from_pci src, dest, len
addl #VALUE_WINDOW, \src
memcpy \src, \dest, \len
.endm
.macro memcpy_to_pci src, dest, len
addl #VALUE_WINDOW, \dest
memcpy \src, \dest, \len
.endm
#endif
.macro wait_for_command
99: btstl #0, CR
bne 99b
.endm
/****************************** card initialization *******************/
.text
.global _start
_start: bra init
.org _start + 4
ch_status_addr: .long 0, 0, 0, 0
rx_descs_addr: .long 0
init:
#if DETECT_RAM
movel OR1, %d0
andl #0xF00007FF, %d0 // mask AMxx bits
orl #0xFFFF800 & ~(MAX_RAM_SIZE - 1), %d0 // update RAM bank size
movel %d0, OR1
#endif
addl #VALUE_WINDOW, rx_descs_addr // PCI addresses of shared data
clrl %d0 // D0 = 4 * port
init_1: tstl ch_status_addr(%d0)
beq init_2
addl #VALUE_WINDOW, ch_status_addr(%d0)
init_2: addl #4, %d0
cmpl #4 * 4, %d0
bne init_1
movel #pci9060_interrupt, PCI9060_VECTOR
movel #error_interrupt, ERROR_VECTOR
movel #port_interrupt_1, SCC1_VECTOR
movel #port_interrupt_2, SCC2_VECTOR
movel #port_interrupt_3, SCC3_VECTOR
movel #port_interrupt_4, SCC4_VECTOR
movel #timer_interrupt, TIMER_IRQ * 4
movel #0x78000000, CIMR // only SCCx IRQs from CPM
movew #(TIMER_IRQ_LEVEL << 8) + TIMER_IRQ, PICR // interrupt from PIT
movew #PITR_CONST, PITR
// SCC1=SCCa SCC2=SCCb SCC3=SCCc SCC4=SCCd prio=4 HP=-1 IRQ=64-79
movel #0xD41F40 + (CPM_IRQ_LEVEL << 13), CICR
movel #0x543, PLX_DMA_0_MODE // 32-bit, Ready, Burst, IRQ
movel #0x543, PLX_DMA_1_MODE
movel #0x0, PLX_DMA_0_DESC // from PCI to local
movel #0x8, PLX_DMA_1_DESC // from local to PCI
movel #0x101, PLX_DMA_CMD_STS // enable both DMA channels
// enable local IRQ, DMA, doorbells and PCI IRQ
orl #0x000F0300, PLX_INTERRUPT_CS
#if DETECT_RAM
bsr ram_test
#else
movel #1, PLX_MAILBOX_5 // non-zero value = init complete
#endif
bsr check_csr
movew #0xFFFF, PAPAR // all pins are clocks/data
clrw PADIR // first function
clrw PCSO // CD and CTS always active
/****************************** main loop *****************************/
main: movel channel_stats, %d7 // D7 = doorbell + irq status
clrl channel_stats
tstl %d7
bne main_1
// nothing to do - wait for next event
stop #0x2200 // supervisor + IRQ level 2
movew #0x2700, %sr // disable IRQs again
bra main
main_1: clrl %d0 // D0 = 4 * port
clrl %d6 // D6 = doorbell to host value
main_l: btstl #DOORBELL_TO_CARD_CLOSE_0, %d7
beq main_op
bclrl #DOORBELL_TO_CARD_OPEN_0, %d7 // in case both bits are set
bsr close_port
main_op:
btstl #DOORBELL_TO_CARD_OPEN_0, %d7
beq main_cl
bsr open_port
main_cl:
btstl #DOORBELL_TO_CARD_TX_0, %d7
beq main_txend
bsr tx
main_txend:
btstl #TASK_SCC_0, %d7
beq main_next
bsr tx_end
bsr rx
main_next:
lsrl #1, %d7 // port status for next port
addl #4, %d0 // D0 = 4 * next port
cmpl #4 * 4, %d0
bne main_l
movel %d6, PLX_DOORBELL_FROM_CARD // signal the host
bra main
/****************************** open port *****************************/
open_port: // D0 = 4 * port, D6 = doorbell to host
movel ch_status_addr(%d0), %a0 // A0 = port status address
tstl STATUS_OPEN(%a0)
bne open_port_ret // port already open
movel #1, STATUS_OPEN(%a0) // confirm the port is open
// setup BDs
clrl tx_in(%d0)
clrl tx_out(%d0)
clrl tx_count(%d0)
clrl rx_in(%d0)
movel SICR, %d1 // D1 = clock settings in SICR
andl clocking_mask(%d0), %d1
cmpl #CLOCK_TXFROMRX, STATUS_CLOCKING(%a0)
bne open_port_clock_ext
orl clocking_txfromrx(%d0), %d1
bra open_port_set_clock
open_port_clock_ext:
orl clocking_ext(%d0), %d1
open_port_set_clock:
movel %d1, SICR // update clock settings in SICR
orw #STATUS_CABLE_DTR, csr_output(%d0) // DTR on
bsr check_csr // call with disabled timer interrupt
// Setup TX descriptors
movel first_buffer(%d0), %d1 // D1 = starting buffer address
movel tx_first_bd(%d0), %a1 // A1 = starting TX BD address
movel #TX_BUFFERS - 2, %d2 // D2 = TX_BUFFERS - 1 counter
movel #0x18000000, %d3 // D3 = initial TX BD flags: Int + Last
cmpl #PARITY_NONE, STATUS_PARITY(%a0)
beq open_port_tx_loop
bsetl #26, %d3 // TX BD flag: Transmit CRC
open_port_tx_loop:
movel %d3, (%a1)+ // TX flags + length
movel %d1, (%a1)+ // buffer address
addl #BUFFER_LENGTH, %d1
dbfw %d2, open_port_tx_loop
bsetl #29, %d3 // TX BD flag: Wrap (last BD)
movel %d3, (%a1)+ // Final TX flags + length
movel %d1, (%a1)+ // buffer address
// Setup RX descriptors // A1 = starting RX BD address
movel #RX_BUFFERS - 2, %d2 // D2 = RX_BUFFERS - 1 counter
open_port_rx_loop:
movel #0x90000000, (%a1)+ // RX flags + length
movel %d1, (%a1)+ // buffer address
addl #BUFFER_LENGTH, %d1
dbfw %d2, open_port_rx_loop
movel #0xB0000000, (%a1)+ // Final RX flags + length
movel %d1, (%a1)+ // buffer address
// Setup port parameters
movel scc_base_addr(%d0), %a1 // A1 = SCC_BASE address
movel scc_reg_addr(%d0), %a2 // A2 = SCC_REGS address
movel #0xFFFF, SCC_SCCE(%a2) // clear status bits
movel #0x0000, SCC_SCCM(%a2) // interrupt mask
movel tx_first_bd(%d0), %d1
movew %d1, SCC_TBASE(%a1) // D1 = offset of first TxBD
addl #TX_BUFFERS * 8, %d1
movew %d1, SCC_RBASE(%a1) // D1 = offset of first RxBD
moveb #0x8, SCC_RFCR(%a1) // Intel mode, 1000
moveb #0x8, SCC_TFCR(%a1)
// Parity settings
cmpl #PARITY_CRC16_PR1_CCITT, STATUS_PARITY(%a0)
bne open_port_parity_1
clrw SCC_PSMR(%a2) // CRC16-CCITT
movel #0xF0B8, SCC_C_MASK(%a1)
movel #0xFFFF, SCC_C_PRES(%a1)
movew #HDLC_MAX_MRU + 2, SCC_MFLR(%a1) // 2 bytes for CRC
movew #2, parity_bytes(%d0)
bra open_port_2
open_port_parity_1:
cmpl #PARITY_CRC32_PR1_CCITT, STATUS_PARITY(%a0)
bne open_port_parity_2
movew #0x0800, SCC_PSMR(%a2) // CRC32-CCITT
movel #0xDEBB20E3, SCC_C_MASK(%a1)
movel #0xFFFFFFFF, SCC_C_PRES(%a1)
movew #HDLC_MAX_MRU + 4, SCC_MFLR(%a1) // 4 bytes for CRC
movew #4, parity_bytes(%d0)
bra open_port_2
open_port_parity_2:
cmpl #PARITY_CRC16_PR0_CCITT, STATUS_PARITY(%a0)
bne open_port_parity_3
clrw SCC_PSMR(%a2) // CRC16-CCITT preset 0
movel #0xF0B8, SCC_C_MASK(%a1)
clrl SCC_C_PRES(%a1)
movew #HDLC_MAX_MRU + 2, SCC_MFLR(%a1) // 2 bytes for CRC
movew #2, parity_bytes(%d0)
bra open_port_2
open_port_parity_3:
cmpl #PARITY_CRC32_PR0_CCITT, STATUS_PARITY(%a0)
bne open_port_parity_4
movew #0x0800, SCC_PSMR(%a2) // CRC32-CCITT preset 0
movel #0xDEBB20E3, SCC_C_MASK(%a1)
clrl SCC_C_PRES(%a1)
movew #HDLC_MAX_MRU + 4, SCC_MFLR(%a1) // 4 bytes for CRC
movew #4, parity_bytes(%d0)
bra open_port_2
open_port_parity_4:
clrw SCC_PSMR(%a2) // no parity
movel #0xF0B8, SCC_C_MASK(%a1)
movel #0xFFFF, SCC_C_PRES(%a1)
movew #HDLC_MAX_MRU, SCC_MFLR(%a1) // 0 bytes for CRC
clrw parity_bytes(%d0)
open_port_2:
movel #0x00000003, SCC_GSMR_H(%a2) // RTSM
cmpl #ENCODING_NRZI, STATUS_ENCODING(%a0)
bne open_port_nrz
movel #0x10040900, SCC_GSMR_L(%a2) // NRZI: TCI Tend RECN+TENC=1
bra open_port_3
open_port_nrz:
movel #0x10040000, SCC_GSMR_L(%a2) // NRZ: TCI Tend RECN+TENC=0
open_port_3:
movew #BUFFER_LENGTH, SCC_MRBLR(%a1)
movel %d0, %d1
lsll #4, %d1 // D1 bits 7 and 6 = port
orl #1, %d1
movew %d1, CR // Init SCC RX and TX params
wait_for_command
// TCI Tend ENR ENT
movew #0x001F, SCC_SCCM(%a2) // TXE RXF BSY TXB RXB interrupts
orl #0x00000030, SCC_GSMR_L(%a2) // enable SCC
open_port_ret:
rts
/****************************** close port ****************************/
close_port: // D0 = 4 * port, D6 = doorbell to host
movel scc_reg_addr(%d0), %a0 // A0 = SCC_REGS address
clrw SCC_SCCM(%a0) // no SCC interrupts
andl #0xFFFFFFCF, SCC_GSMR_L(%a0) // Disable ENT and ENR
andw #~STATUS_CABLE_DTR, csr_output(%d0) // DTR off
bsr check_csr // call with disabled timer interrupt
movel ch_status_addr(%d0), %d1
clrl STATUS_OPEN(%d1) // confirm the port is closed
rts
/****************************** transmit packet ***********************/
// queue packets for transmission
tx: // D0 = 4 * port, D6 = doorbell to host
cmpl #TX_BUFFERS, tx_count(%d0)
beq tx_ret // all DB's = descs in use
movel tx_out(%d0), %d1
movel %d1, %d2 // D1 = D2 = tx_out BD# = desc#
mulul #DESC_LENGTH, %d2 // D2 = TX desc offset
addl ch_status_addr(%d0), %d2
addl #STATUS_TX_DESCS, %d2 // D2 = TX desc address
cmpl #PACKET_FULL, (%d2) // desc status
bne tx_ret
// queue it
movel 4(%d2), %a0 // PCI address
lsll #3, %d1 // BD is 8-bytes long
addl tx_first_bd(%d0), %d1 // D1 = current tx_out BD addr
movel 4(%d1), %a1 // A1 = dest address
movel 8(%d2), %d2 // D2 = length
movew %d2, 2(%d1) // length into BD
memcpy_from_pci %a0, %a1, %d2
bsetl #31, (%d1) // CP go ahead
// update tx_out and tx_count
movel tx_out(%d0), %d1
addl #1, %d1
cmpl #TX_BUFFERS, %d1
bne tx_1
clrl %d1
tx_1: movel %d1, tx_out(%d0)
addl #1, tx_count(%d0)
bra tx
tx_ret: rts
/****************************** packet received ***********************/
// Service receive buffers // D0 = 4 * port, D6 = doorbell to host
rx: movel rx_in(%d0), %d1 // D1 = rx_in BD#
lsll #3, %d1 // BD is 8-bytes long
addl rx_first_bd(%d0), %d1 // D1 = current rx_in BD address
movew (%d1), %d2 // D2 = RX BD flags
btstl #15, %d2
bne rx_ret // BD still empty
btstl #1, %d2
bne rx_overrun
tstw parity_bytes(%d0)
bne rx_parity
bclrl #2, %d2 // do not test for CRC errors
rx_parity:
andw #0x0CBC, %d2 // mask status bits
cmpw #0x0C00, %d2 // correct frame
bne rx_bad_frame
clrl %d3
movew 2(%d1), %d3
subw parity_bytes(%d0), %d3 // D3 = packet length
cmpw #HDLC_MAX_MRU, %d3
bgt rx_bad_frame
rx_good_frame:
movel rx_out, %d2
mulul #DESC_LENGTH, %d2
addl rx_descs_addr, %d2 // D2 = RX desc address
cmpl #PACKET_EMPTY, (%d2) // desc stat
bne rx_overrun
movel %d3, 8(%d2)
movel 4(%d1), %a0 // A0 = source address
movel 4(%d2), %a1
tstl %a1
beq rx_ignore_data
memcpy_to_pci %a0, %a1, %d3
rx_ignore_data:
movel packet_full(%d0), (%d2) // update desc stat
// update D6 and rx_out
bsetl #DOORBELL_FROM_CARD_RX, %d6 // signal host that RX completed
movel rx_out, %d2
addl #1, %d2
cmpl #RX_QUEUE_LENGTH, %d2
bne rx_1
clrl %d2
rx_1: movel %d2, rx_out
rx_free_bd:
andw #0xF000, (%d1) // clear CM and error bits
bsetl #31, (%d1) // free BD
// update rx_in
movel rx_in(%d0), %d1
addl #1, %d1
cmpl #RX_BUFFERS, %d1
bne rx_2
clrl %d1
rx_2: movel %d1, rx_in(%d0)
bra rx
rx_overrun:
movel ch_status_addr(%d0), %d2
addl #1, STATUS_RX_OVERRUNS(%d2)
bra rx_free_bd
rx_bad_frame:
movel ch_status_addr(%d0), %d2
addl #1, STATUS_RX_FRAME_ERRORS(%d2)
bra rx_free_bd
rx_ret: rts
/****************************** packet transmitted ********************/
// Service transmit buffers // D0 = 4 * port, D6 = doorbell to host
tx_end: tstl tx_count(%d0)
beq tx_end_ret // TX buffers already empty
movel tx_in(%d0), %d1
movel %d1, %d2 // D1 = D2 = tx_in BD# = desc#
lsll #3, %d1 // BD is 8-bytes long
addl tx_first_bd(%d0), %d1 // D1 = current tx_in BD address
movew (%d1), %d3 // D3 = TX BD flags
btstl #15, %d3
bne tx_end_ret // BD still being transmitted
// update D6, tx_in and tx_count
orl bell_tx(%d0), %d6 // signal host that TX desc freed
subl #1, tx_count(%d0)
movel tx_in(%d0), %d1
addl #1, %d1
cmpl #TX_BUFFERS, %d1
bne tx_end_1
clrl %d1
tx_end_1:
movel %d1, tx_in(%d0)
// free host's descriptor
mulul #DESC_LENGTH, %d2 // D2 = TX desc offset
addl ch_status_addr(%d0), %d2
addl #STATUS_TX_DESCS, %d2 // D2 = TX desc address
btstl #1, %d3
bne tx_end_underrun
movel #PACKET_SENT, (%d2)
bra tx_end
tx_end_underrun:
movel #PACKET_UNDERRUN, (%d2)
bra tx_end
tx_end_ret: rts
/****************************** PLX PCI9060 DMA memcpy ****************/
#if QUICC_MEMCPY_USES_PLX
// called with interrupts disabled
memcpy_from_pci_run:
movel %d0, -(%sp)
movew %sr, -(%sp)
memcpy_1:
movel PLX_DMA_CMD_STS, %d0 // do not btst PLX register directly
btstl #4, %d0 // transfer done?
bne memcpy_end
stop #0x2200 // enable PCI9060 interrupts
movew #0x2700, %sr // disable interrupts again
bra memcpy_1
memcpy_to_pci_run:
movel %d0, -(%sp)
movew %sr, -(%sp)
memcpy_2:
movel PLX_DMA_CMD_STS, %d0 // do not btst PLX register directly
btstl #12, %d0 // transfer done?
bne memcpy_end
stop #0x2200 // enable PCI9060 interrupts
movew #0x2700, %sr // disable interrupts again
bra memcpy_2
memcpy_end:
movew (%sp)+, %sr
movel (%sp)+, %d0
rts
#endif
/****************************** PLX PCI9060 interrupt *****************/
pci9060_interrupt:
movel %d0, -(%sp)
movel PLX_DOORBELL_TO_CARD, %d0
movel %d0, PLX_DOORBELL_TO_CARD // confirm all requests
orl %d0, channel_stats
movel #0x0909, PLX_DMA_CMD_STS // clear DMA ch #0 and #1 interrupts
movel (%sp)+, %d0
rte
/****************************** SCC interrupts ************************/
port_interrupt_1:
orl #0, SCC1_REGS + SCC_SCCE; // confirm SCC events
orl #1 << TASK_SCC_0, channel_stats
movel #0x40000000, CISR
rte
port_interrupt_2:
orl #0, SCC2_REGS + SCC_SCCE; // confirm SCC events
orl #1 << TASK_SCC_1, channel_stats
movel #0x20000000, CISR
rte
port_interrupt_3:
orl #0, SCC3_REGS + SCC_SCCE; // confirm SCC events
orl #1 << TASK_SCC_2, channel_stats
movel #0x10000000, CISR
rte
port_interrupt_4:
orl #0, SCC4_REGS + SCC_SCCE; // confirm SCC events
orl #1 << TASK_SCC_3, channel_stats
movel #0x08000000, CISR
rte
error_interrupt:
rte
/****************************** cable and PM routine ******************/
// modified registers: none
check_csr:
movel %d0, -(%sp)
movel %d1, -(%sp)
movel %d2, -(%sp)
movel %a0, -(%sp)
movel %a1, -(%sp)
clrl %d0 // D0 = 4 * port
movel #CSRA, %a0 // A0 = CSR address
check_csr_loop:
movew (%a0), %d1 // D1 = CSR input bits
andl #0xE7, %d1 // PM and cable sense bits (no DCE bit)
cmpw #STATUS_CABLE_V35 * (1 + 1 << STATUS_CABLE_PM_SHIFT), %d1
bne check_csr_1
movew #0x0E08, %d1
bra check_csr_valid
check_csr_1:
cmpw #STATUS_CABLE_X21 * (1 + 1 << STATUS_CABLE_PM_SHIFT), %d1
bne check_csr_2
movew #0x0408, %d1
bra check_csr_valid
check_csr_2:
cmpw #STATUS_CABLE_V24 * (1 + 1 << STATUS_CABLE_PM_SHIFT), %d1
bne check_csr_3
movew #0x0208, %d1
bra check_csr_valid
check_csr_3:
cmpw #STATUS_CABLE_EIA530 * (1 + 1 << STATUS_CABLE_PM_SHIFT), %d1
bne check_csr_disable
movew #0x0D08, %d1
bra check_csr_valid
check_csr_disable:
movew #0x0008, %d1 // D1 = disable everything
movew #0x80E7, %d2 // D2 = input mask: ignore DSR
bra check_csr_write
check_csr_valid: // D1 = mode and IRQ bits
movew csr_output(%d0), %d2
andw #0x3000, %d2 // D2 = requested LL and DTR bits
orw %d2, %d1 // D1 = all requested output bits
movew #0x80FF, %d2 // D2 = input mask: include DSR
check_csr_write:
cmpw old_csr_output(%d0), %d1
beq check_csr_input
movew %d1, old_csr_output(%d0)
movew %d1, (%a0) // Write CSR output bits
check_csr_input:
movew (PCDAT), %d1
andw dcd_mask(%d0), %d1
beq check_csr_dcd_on // DCD and CTS signals are negated
movew (%a0), %d1 // D1 = CSR input bits
andw #~STATUS_CABLE_DCD, %d1 // DCD off
bra check_csr_previous
check_csr_dcd_on:
movew (%a0), %d1 // D1 = CSR input bits
orw #STATUS_CABLE_DCD, %d1 // DCD on
check_csr_previous:
andw %d2, %d1 // input mask
movel ch_status_addr(%d0), %a1
cmpl STATUS_CABLE(%a1), %d1 // check for change
beq check_csr_next
movel %d1, STATUS_CABLE(%a1) // update status
movel bell_cable(%d0), PLX_DOORBELL_FROM_CARD // signal the host
check_csr_next:
addl #2, %a0 // next CSR register
addl #4, %d0 // D0 = 4 * next port
cmpl #4 * 4, %d0
bne check_csr_loop
movel (%sp)+, %a1
movel (%sp)+, %a0
movel (%sp)+, %d2
movel (%sp)+, %d1
movel (%sp)+, %d0
rts
/****************************** timer interrupt ***********************/
timer_interrupt:
bsr check_csr
rte
/****************************** RAM sizing and test *******************/
#if DETECT_RAM
ram_test:
movel #0x12345678, %d1 // D1 = test value
movel %d1, (128 * 1024 - 4)
movel #128 * 1024, %d0 // D0 = RAM size tested
ram_test_size:
cmpl #MAX_RAM_SIZE, %d0
beq ram_test_size_found
movel %d0, %a0
addl #128 * 1024 - 4, %a0
cmpl (%a0), %d1
beq ram_test_size_check
ram_test_next_size:
lsll #1, %d0
bra ram_test_size
ram_test_size_check:
eorl #0xFFFFFFFF, %d1
movel %d1, (128 * 1024 - 4)
cmpl (%a0), %d1
bne ram_test_next_size
ram_test_size_found: // D0 = RAM size
movel %d0, %a0 // A0 = fill ptr
subl #firmware_end + 4, %d0
lsrl #2, %d0
movel %d0, %d1 // D1 = DBf counter
ram_test_fill:
movel %a0, -(%a0)
dbfw %d1, ram_test_fill
subl #0x10000, %d1
cmpl #0xFFFFFFFF, %d1
bne ram_test_fill
ram_test_loop: // D0 = DBf counter
cmpl (%a0)+, %a0
dbnew %d0, ram_test_loop
bne ram_test_found_bad
subl #0x10000, %d0
cmpl #0xFFFFFFFF, %d0
bne ram_test_loop
bra ram_test_all_ok
ram_test_found_bad:
subl #4, %a0
ram_test_all_ok:
movel %a0, PLX_MAILBOX_5
rts
#endif
/****************************** constants *****************************/
scc_reg_addr:
.long SCC1_REGS, SCC2_REGS, SCC3_REGS, SCC4_REGS
scc_base_addr:
.long SCC1_BASE, SCC2_BASE, SCC3_BASE, SCC4_BASE
tx_first_bd:
.long DPRBASE
.long DPRBASE + (TX_BUFFERS + RX_BUFFERS) * 8
.long DPRBASE + (TX_BUFFERS + RX_BUFFERS) * 8 * 2
.long DPRBASE + (TX_BUFFERS + RX_BUFFERS) * 8 * 3
rx_first_bd:
.long DPRBASE + TX_BUFFERS * 8
.long DPRBASE + TX_BUFFERS * 8 + (TX_BUFFERS + RX_BUFFERS) * 8
.long DPRBASE + TX_BUFFERS * 8 + (TX_BUFFERS + RX_BUFFERS) * 8 * 2
.long DPRBASE + TX_BUFFERS * 8 + (TX_BUFFERS + RX_BUFFERS) * 8 * 3
first_buffer:
.long BUFFERS_ADDR
.long BUFFERS_ADDR + (TX_BUFFERS + RX_BUFFERS) * BUFFER_LENGTH
.long BUFFERS_ADDR + (TX_BUFFERS + RX_BUFFERS) * BUFFER_LENGTH * 2
.long BUFFERS_ADDR + (TX_BUFFERS + RX_BUFFERS) * BUFFER_LENGTH * 3
bell_tx:
.long 1 << DOORBELL_FROM_CARD_TX_0, 1 << DOORBELL_FROM_CARD_TX_1
.long 1 << DOORBELL_FROM_CARD_TX_2, 1 << DOORBELL_FROM_CARD_TX_3
bell_cable:
.long 1 << DOORBELL_FROM_CARD_CABLE_0, 1 << DOORBELL_FROM_CARD_CABLE_1
.long 1 << DOORBELL_FROM_CARD_CABLE_2, 1 << DOORBELL_FROM_CARD_CABLE_3
packet_full:
.long PACKET_FULL, PACKET_FULL + 1, PACKET_FULL + 2, PACKET_FULL + 3
clocking_ext:
.long 0x0000002C, 0x00003E00, 0x002C0000, 0x3E000000
clocking_txfromrx:
.long 0x0000002D, 0x00003F00, 0x002D0000, 0x3F000000
clocking_mask:
.long 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000
dcd_mask:
.word 0x020, 0, 0x080, 0, 0x200, 0, 0x800
.ascii "wanXL firmware\n"
.asciz "Copyright (C) 2003 Krzysztof Halasa <khc@pm.waw.pl>\n"
/****************************** variables *****************************/
.align 4
channel_stats: .long 0
tx_in: .long 0, 0, 0, 0 // transmitted
tx_out: .long 0, 0, 0, 0 // received from host for transmission
tx_count: .long 0, 0, 0, 0 // currently in transmit queue
rx_in: .long 0, 0, 0, 0 // received from port
rx_out: .long 0 // transmitted to host
parity_bytes: .word 0, 0, 0, 0, 0, 0, 0 // only 4 words are used
csr_output: .word 0
old_csr_output: .word 0, 0, 0, 0, 0, 0, 0
.align 4
firmware_end: // must be dword-aligned
@@ -0,0 +1,158 @@
static u8 firmware[]={
0x60,0x00,0x00,0x16,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0xB9,0x40,0x00,0x00,0x00,0x00,0x00,
0x10,0x14,0x42,0x80,0x4A,0xB0,0x09,0xB0,0x00,0x00,0x10,0x04,0x67,0x00,0x00,0x0E,
0x06,0xB0,0x40,0x00,0x00,0x00,0x09,0xB0,0x00,0x00,0x10,0x04,0x58,0x80,0x0C,0x80,
0x00,0x00,0x00,0x10,0x66,0x00,0xFF,0xDE,0x21,0xFC,0x00,0x00,0x16,0xBC,0x00,0x6C,
0x21,0xFC,0x00,0x00,0x17,0x5E,0x01,0x00,0x21,0xFC,0x00,0x00,0x16,0xDE,0x01,0x78,
0x21,0xFC,0x00,0x00,0x16,0xFE,0x01,0x74,0x21,0xFC,0x00,0x00,0x17,0x1E,0x01,0x70,
0x21,0xFC,0x00,0x00,0x17,0x3E,0x01,0x6C,0x21,0xFC,0x00,0x00,0x18,0x4C,0x02,0x00,
0x23,0xFC,0x78,0x00,0x00,0x00,0xFF,0xFC,0x15,0x48,0x33,0xFC,0x04,0x80,0xFF,0xFC,
0x10,0x26,0x33,0xFC,0x01,0x10,0xFF,0xFC,0x10,0x2A,0x23,0xFC,0x00,0xD4,0x9F,0x40,
0xFF,0xFC,0x15,0x40,0x23,0xFC,0x00,0x00,0x05,0x43,0xFF,0xF9,0x01,0x00,0x23,0xFC,
0x00,0x00,0x05,0x43,0xFF,0xF9,0x01,0x14,0x23,0xFC,0x00,0x00,0x00,0x00,0xFF,0xF9,
0x01,0x10,0x23,0xFC,0x00,0x00,0x00,0x08,0xFF,0xF9,0x01,0x24,0x23,0xFC,0x00,0x00,
0x01,0x01,0xFF,0xF9,0x01,0x28,0x00,0xB9,0x00,0x0F,0x03,0x00,0xFF,0xF9,0x00,0xE8,
0x23,0xFC,0x00,0x00,0x00,0x01,0xFF,0xF9,0x00,0xD4,0x61,0x00,0x06,0x74,0x33,0xFC,
0xFF,0xFF,0xFF,0xFC,0x15,0x52,0x42,0x79,0xFF,0xFC,0x15,0x50,0x42,0x79,0xFF,0xFC,
0x15,0x64,0x2E,0x3A,0x08,0x50,0x42,0xB9,0x00,0x00,0x19,0x54,0x4A,0x87,0x66,0x00,
0x00,0x0E,0x4E,0x72,0x22,0x00,0x46,0xFC,0x27,0x00,0x60,0x00,0xFF,0xE6,0x42,0x80,
0x42,0x86,0x08,0x07,0x00,0x04,0x67,0x00,0x00,0x0A,0x08,0x87,0x00,0x00,0x61,0x00,
0x02,0xA0,0x08,0x07,0x00,0x00,0x67,0x00,0x00,0x06,0x61,0x00,0x00,0x36,0x08,0x07,
0x00,0x08,0x67,0x00,0x00,0x06,0x61,0x00,0x02,0xB8,0x08,0x07,0x00,0x0C,0x67,0x00,
0x00,0x0A,0x61,0x00,0x04,0x94,0x61,0x00,0x03,0x60,0xE2,0x8F,0x58,0x80,0x0C,0x80,
0x00,0x00,0x00,0x10,0x66,0x00,0xFF,0xBC,0x23,0xC6,0xFF,0xF9,0x00,0xE4,0x60,0x00,
0xFF,0x92,0x20,0x70,0x09,0xB0,0x00,0x00,0x10,0x04,0x4A,0xA8,0x00,0x00,0x66,0x00,
0x02,0x4E,0x21,0x7C,0x00,0x00,0x00,0x01,0x00,0x00,0x42,0xB0,0x09,0xB0,0x00,0x00,
0x19,0x58,0x42,0xB0,0x09,0xB0,0x00,0x00,0x19,0x68,0x42,0xB0,0x09,0xB0,0x00,0x00,
0x19,0x78,0x42,0xB0,0x09,0xB0,0x00,0x00,0x19,0x88,0x22,0x39,0xFF,0xFC,0x16,0xEC,
0xC2,0xB0,0x09,0xB0,0x00,0x00,0x18,0xF2,0x0C,0xA8,0x00,0x00,0x00,0x04,0x00,0x18,
0x66,0x00,0x00,0x0E,0x82,0xB0,0x09,0xB0,0x00,0x00,0x18,0xE2,0x60,0x00,0x00,0x0A,
0x82,0xB0,0x09,0xB0,0x00,0x00,0x18,0xD2,0x23,0xC1,0xFF,0xFC,0x16,0xEC,0x00,0x70,
0x10,0x00,0x09,0xB0,0x00,0x00,0x19,0xAA,0x61,0x00,0x05,0x76,0x22,0x30,0x09,0xB0,
0x00,0x00,0x18,0x92,0x22,0x70,0x09,0xB0,0x00,0x00,0x18,0x72,0x74,0x08,0x26,0x3C,
0x18,0x00,0x00,0x00,0x0C,0xA8,0x00,0x00,0x00,0x01,0x00,0x10,0x67,0x00,0x00,0x06,
0x08,0xC3,0x00,0x1A,0x22,0xC3,0x22,0xC1,0x06,0x81,0x00,0x00,0x05,0xFC,0x51,0xCA,
0xFF,0xF4,0x08,0xC3,0x00,0x1D,0x22,0xC3,0x22,0xC1,0x74,0x1C,0x22,0xFC,0x90,0x00,
0x00,0x00,0x22,0xC1,0x06,0x81,0x00,0x00,0x05,0xFC,0x51,0xCA,0xFF,0xF0,0x22,0xFC,
0xB0,0x00,0x00,0x00,0x22,0xC1,0x22,0x70,0x09,0xB0,0x00,0x00,0x18,0x62,0x24,0x70,
0x09,0xB0,0x00,0x00,0x18,0x52,0x25,0x7C,0x00,0x00,0xFF,0xFF,0x00,0x10,0x25,0x7C,
0x00,0x00,0x00,0x00,0x00,0x14,0x22,0x30,0x09,0xB0,0x00,0x00,0x18,0x72,0x33,0x41,
0x00,0x02,0x06,0x81,0x00,0x00,0x00,0x50,0x33,0x41,0x00,0x00,0x13,0x7C,0x00,0x08,
0x00,0x04,0x13,0x7C,0x00,0x08,0x00,0x05,0x0C,0xA8,0x00,0x00,0x00,0x05,0x00,0x10,
0x66,0x00,0x00,0x2A,0x42,0x6A,0x00,0x08,0x23,0x7C,0x00,0x00,0xF0,0xB8,0x00,0x34,
0x23,0x7C,0x00,0x00,0xFF,0xFF,0x00,0x38,0x33,0x7C,0x05,0xFA,0x00,0x46,0x31,0xBC,
0x00,0x02,0x09,0xB0,0x00,0x00,0x19,0x9C,0x60,0x00,0x00,0xBC,0x0C,0xA8,0x00,0x00,
0x00,0x07,0x00,0x10,0x66,0x00,0x00,0x2C,0x35,0x7C,0x08,0x00,0x00,0x08,0x23,0x7C,
0xDE,0xBB,0x20,0xE3,0x00,0x34,0x23,0x7C,0xFF,0xFF,0xFF,0xFF,0x00,0x38,0x33,0x7C,
0x05,0xFC,0x00,0x46,0x31,0xBC,0x00,0x04,0x09,0xB0,0x00,0x00,0x19,0x9C,0x60,0x00,
0x00,0x86,0x0C,0xA8,0x00,0x00,0x00,0x04,0x00,0x10,0x66,0x00,0x00,0x26,0x42,0x6A,
0x00,0x08,0x23,0x7C,0x00,0x00,0xF0,0xB8,0x00,0x34,0x42,0xA9,0x00,0x38,0x33,0x7C,
0x05,0xFA,0x00,0x46,0x31,0xBC,0x00,0x02,0x09,0xB0,0x00,0x00,0x19,0x9C,0x60,0x00,
0x00,0x56,0x0C,0xA8,0x00,0x00,0x00,0x06,0x00,0x10,0x66,0x00,0x00,0x28,0x35,0x7C,
0x08,0x00,0x00,0x08,0x23,0x7C,0xDE,0xBB,0x20,0xE3,0x00,0x34,0x42,0xA9,0x00,0x38,
0x33,0x7C,0x05,0xFC,0x00,0x46,0x31,0xBC,0x00,0x04,0x09,0xB0,0x00,0x00,0x19,0x9C,
0x60,0x00,0x00,0x24,0x42,0x6A,0x00,0x08,0x23,0x7C,0x00,0x00,0xF0,0xB8,0x00,0x34,
0x23,0x7C,0x00,0x00,0xFF,0xFF,0x00,0x38,0x33,0x7C,0x05,0xF8,0x00,0x46,0x42,0x70,
0x09,0xB0,0x00,0x00,0x19,0x9C,0x25,0x7C,0x00,0x00,0x00,0x03,0x00,0x04,0x0C,0xA8,
0x00,0x00,0x00,0x02,0x00,0x14,0x66,0x00,0x00,0x0E,0x25,0x7C,0x10,0x04,0x09,0x00,
0x00,0x00,0x60,0x00,0x00,0x0A,0x25,0x7C,0x10,0x04,0x00,0x00,0x00,0x00,0x33,0x7C,
0x05,0xFC,0x00,0x06,0x22,0x00,0xE9,0x89,0x00,0x81,0x00,0x00,0x00,0x01,0x33,0xC1,
0xFF,0xFC,0x15,0xC0,0x08,0x39,0x00,0x00,0xFF,0xFC,0x15,0xC0,0x66,0x00,0xFF,0xF6,
0x35,0x7C,0x00,0x1F,0x00,0x14,0x00,0xAA,0x00,0x00,0x00,0x30,0x00,0x00,0x4E,0x75,
0x20,0x70,0x09,0xB0,0x00,0x00,0x18,0x52,0x42,0x68,0x00,0x14,0x02,0xA8,0xFF,0xFF,
0xFF,0xCF,0x00,0x00,0x02,0x70,0xEF,0xFF,0x09,0xB0,0x00,0x00,0x19,0xAA,0x61,0x00,
0x03,0x70,0x22,0x30,0x09,0xB0,0x00,0x00,0x10,0x04,0x42,0xB0,0x19,0x90,0x4E,0x75,
0x0C,0xB0,0x00,0x00,0x00,0x0A,0x09,0xB0,0x00,0x00,0x19,0x78,0x67,0x00,0x00,0xA8,
0x22,0x30,0x09,0xB0,0x00,0x00,0x19,0x68,0x24,0x01,0x4C,0x3C,0x20,0x00,0x00,0x00,
0x00,0x0C,0xD4,0xB0,0x09,0xB0,0x00,0x00,0x10,0x04,0x06,0x82,0x00,0x00,0x00,0x1C,
0x0C,0xB0,0x00,0x00,0x00,0x10,0x29,0x90,0x66,0x00,0x00,0x7C,0x20,0x70,0x29,0xA0,
0x00,0x04,0xE7,0x89,0xD2,0xB0,0x09,0xB0,0x00,0x00,0x18,0x72,0x22,0x70,0x19,0xA0,
0x00,0x04,0x24,0x30,0x29,0xA0,0x00,0x08,0x31,0x82,0x19,0xA0,0x00,0x02,0x56,0x82,
0x02,0x82,0xFF,0xFF,0xFF,0xFC,0x23,0xC8,0xFF,0xF9,0x01,0x04,0x23,0xC9,0xFF,0xF9,
0x01,0x08,0x23,0xC2,0xFF,0xF9,0x01,0x0C,0x23,0xFC,0x00,0x00,0x01,0x03,0xFF,0xF9,
0x01,0x28,0x61,0x00,0x01,0xF6,0x08,0xF0,0x00,0x1F,0x19,0x90,0x22,0x30,0x09,0xB0,
0x00,0x00,0x19,0x68,0x52,0x81,0x0C,0x81,0x00,0x00,0x00,0x0A,0x66,0x00,0x00,0x04,
0x42,0x81,0x21,0x81,0x09,0xB0,0x00,0x00,0x19,0x68,0x52,0xB0,0x09,0xB0,0x00,0x00,
0x19,0x78,0x60,0x00,0xFF,0x4C,0x4E,0x75,0x22,0x30,0x09,0xB0,0x00,0x00,0x19,0x88,
0xE7,0x89,0xD2,0xB0,0x09,0xB0,0x00,0x00,0x18,0x82,0x34,0x30,0x19,0x90,0x08,0x02,
0x00,0x0F,0x66,0x00,0x01,0x12,0x08,0x02,0x00,0x01,0x66,0x00,0x00,0xE6,0x4A,0x70,
0x09,0xB0,0x00,0x00,0x19,0x9C,0x66,0x00,0x00,0x06,0x08,0x82,0x00,0x02,0x02,0x42,
0x0C,0xBC,0x0C,0x42,0x0C,0x00,0x66,0x00,0x00,0xDC,0x42,0x83,0x36,0x30,0x19,0xA0,
0x00,0x02,0x96,0x70,0x09,0xB0,0x00,0x00,0x19,0x9C,0x0C,0x43,0x05,0xF8,0x6E,0x00,
0x00,0xC4,0x24,0x3A,0x04,0x84,0x4C,0x3C,0x20,0x00,0x00,0x00,0x00,0x0C,0xD4,0xBA,
0xFA,0xF4,0x0C,0xB0,0x00,0x00,0x00,0x00,0x29,0x90,0x66,0x00,0x00,0x96,0x21,0x83,
0x29,0xA0,0x00,0x08,0x20,0x70,0x19,0xA0,0x00,0x04,0x22,0x70,0x29,0xA0,0x00,0x04,
0x4A,0x89,0x67,0x00,0x00,0x2A,0x56,0x83,0x02,0x83,0xFF,0xFF,0xFF,0xFC,0x23,0xC8,
0xFF,0xF9,0x01,0x1C,0x23,0xC9,0xFF,0xF9,0x01,0x18,0x23,0xC3,0xFF,0xF9,0x01,0x20,
0x23,0xFC,0x00,0x00,0x03,0x01,0xFF,0xF9,0x01,0x28,0x61,0x00,0x01,0x2C,0x21,0xB0,
0x09,0xB0,0x00,0x00,0x18,0xC2,0x29,0x90,0x08,0xC6,0x00,0x04,0x24,0x3A,0x04,0x1A,
0x52,0x82,0x0C,0x82,0x00,0x00,0x00,0x28,0x66,0x00,0x00,0x04,0x42,0x82,0x23,0xC2,
0x00,0x00,0x19,0x98,0x02,0x70,0xF0,0x00,0x19,0x90,0x08,0xF0,0x00,0x1F,0x19,0x90,
0x22,0x30,0x09,0xB0,0x00,0x00,0x19,0x88,0x52,0x81,0x0C,0x81,0x00,0x00,0x00,0x1E,
0x66,0x00,0x00,0x04,0x42,0x81,0x21,0x81,0x09,0xB0,0x00,0x00,0x19,0x88,0x60,0x00,
0xFE,0xF8,0x24,0x30,0x09,0xB0,0x00,0x00,0x10,0x04,0x52,0xB0,0x29,0xA0,0x00,0x08,
0x60,0x00,0xFF,0xC2,0x24,0x30,0x09,0xB0,0x00,0x00,0x10,0x04,0x52,0xB0,0x29,0xA0,
0x00,0x0C,0x60,0x00,0xFF,0xB0,0x4E,0x75,0x4A,0xB0,0x09,0xB0,0x00,0x00,0x19,0x78,
0x67,0x00,0x00,0x86,0x22,0x30,0x09,0xB0,0x00,0x00,0x19,0x58,0x24,0x01,0xE7,0x89,
0xD2,0xB0,0x09,0xB0,0x00,0x00,0x18,0x72,0x36,0x30,0x19,0x90,0x08,0x03,0x00,0x0F,
0x66,0x00,0x00,0x66,0x8C,0xB0,0x09,0xB0,0x00,0x00,0x18,0xA2,0x53,0xB0,0x09,0xB0,
0x00,0x00,0x19,0x78,0x22,0x30,0x09,0xB0,0x00,0x00,0x19,0x58,0x52,0x81,0x0C,0x81,
0x00,0x00,0x00,0x0A,0x66,0x00,0x00,0x04,0x42,0x81,0x21,0x81,0x09,0xB0,0x00,0x00,
0x19,0x58,0x4C,0x3C,0x20,0x00,0x00,0x00,0x00,0x0C,0xD4,0xB0,0x09,0xB0,0x00,0x00,
0x10,0x04,0x06,0x82,0x00,0x00,0x00,0x1C,0x08,0x03,0x00,0x01,0x66,0x00,0x00,0x0E,
0x21,0xBC,0x00,0x00,0x00,0x20,0x29,0x90,0x60,0x00,0xFF,0x7E,0x21,0xBC,0x00,0x00,
0x00,0x30,0x29,0x90,0x60,0x00,0xFF,0x72,0x4E,0x75,0x2F,0x00,0x40,0xE7,0x20,0x39,
0xFF,0xF9,0x01,0x28,0x08,0x00,0x00,0x04,0x66,0x00,0x00,0x2C,0x4E,0x72,0x22,0x00,
0x46,0xFC,0x27,0x00,0x60,0x00,0xFF,0xE8,0x2F,0x00,0x40,0xE7,0x20,0x39,0xFF,0xF9,
0x01,0x28,0x08,0x00,0x00,0x0C,0x66,0x00,0x00,0x0E,0x4E,0x72,0x22,0x00,0x46,0xFC,
0x27,0x00,0x60,0x00,0xFF,0xE8,0x46,0xDF,0x20,0x1F,0x4E,0x75,0x2F,0x00,0x20,0x39,
0xFF,0xF9,0x00,0xE0,0x23,0xC0,0xFF,0xF9,0x00,0xE0,0x81,0xB9,0x00,0x00,0x19,0x54,
0x23,0xFC,0x00,0x00,0x09,0x09,0xFF,0xF9,0x01,0x28,0x20,0x1F,0x4E,0x73,0x00,0xB9,
0x00,0x00,0x00,0x00,0xFF,0xFC,0x16,0x10,0x00,0xB9,0x00,0x00,0x10,0x00,0x00,0x00,
0x19,0x54,0x23,0xFC,0x40,0x00,0x00,0x00,0xFF,0xFC,0x15,0x4C,0x4E,0x73,0x00,0xB9,
0x00,0x00,0x00,0x00,0xFF,0xFC,0x16,0x30,0x00,0xB9,0x00,0x00,0x20,0x00,0x00,0x00,
0x19,0x54,0x23,0xFC,0x20,0x00,0x00,0x00,0xFF,0xFC,0x15,0x4C,0x4E,0x73,0x00,0xB9,
0x00,0x00,0x00,0x00,0xFF,0xFC,0x16,0x50,0x00,0xB9,0x00,0x00,0x40,0x00,0x00,0x00,
0x19,0x54,0x23,0xFC,0x10,0x00,0x00,0x00,0xFF,0xFC,0x15,0x4C,0x4E,0x73,0x00,0xB9,
0x00,0x00,0x00,0x00,0xFF,0xFC,0x16,0x70,0x00,0xB9,0x00,0x00,0x80,0x00,0x00,0x00,
0x19,0x54,0x23,0xFC,0x08,0x00,0x00,0x00,0xFF,0xFC,0x15,0x4C,0x4E,0x73,0x4E,0x73,
0x2F,0x00,0x2F,0x01,0x2F,0x02,0x2F,0x08,0x2F,0x09,0x42,0x80,0x20,0x7C,0xFF,0xFB,
0x00,0x00,0x32,0x10,0x02,0x81,0x00,0x00,0x00,0xE7,0x0C,0x41,0x00,0x42,0x66,0x00,
0x00,0x0A,0x32,0x3C,0x0E,0x08,0x60,0x00,0x00,0x3E,0x0C,0x41,0x00,0x63,0x66,0x00,
0x00,0x0A,0x32,0x3C,0x04,0x08,0x60,0x00,0x00,0x2E,0x0C,0x41,0x00,0x84,0x66,0x00,
0x00,0x0A,0x32,0x3C,0x02,0x08,0x60,0x00,0x00,0x1E,0x0C,0x41,0x00,0xA5,0x66,0x00,
0x00,0x0A,0x32,0x3C,0x0D,0x08,0x60,0x00,0x00,0x0E,0x32,0x3C,0x00,0x08,0x34,0x3C,
0x80,0xE7,0x60,0x00,0x00,0x14,0x34,0x30,0x09,0xB0,0x00,0x00,0x19,0xAA,0x02,0x42,
0x30,0x00,0x82,0x42,0x34,0x3C,0x80,0xFF,0xB2,0x70,0x09,0xB0,0x00,0x00,0x19,0xAC,
0x67,0x00,0x00,0x0C,0x31,0x81,0x09,0xB0,0x00,0x00,0x19,0xAC,0x30,0x81,0x32,0x39,
0xFF,0xFC,0x15,0x66,0xC2,0x70,0x09,0xB0,0x00,0x00,0x19,0x02,0x67,0x00,0x00,0x0C,
0x32,0x10,0x02,0x41,0xFF,0xF7,0x60,0x00,0x00,0x08,0x32,0x10,0x00,0x41,0x00,0x08,
0xC2,0x42,0x22,0x70,0x09,0xB0,0x00,0x00,0x10,0x04,0xB2,0xA9,0x00,0x04,0x67,0x00,
0x00,0x12,0x23,0x41,0x00,0x04,0x23,0xF0,0x09,0xB0,0x00,0x00,0x18,0xB2,0xFF,0xF9,
0x00,0xE4,0x54,0x88,0x58,0x80,0x0C,0x80,0x00,0x00,0x00,0x10,0x66,0x00,0xFF,0x34,
0x22,0x5F,0x20,0x5F,0x24,0x1F,0x22,0x1F,0x20,0x1F,0x4E,0x75,0x61,0x00,0xFF,0x12,
0x4E,0x73,0xFF,0xFC,0x16,0x00,0xFF,0xFC,0x16,0x20,0xFF,0xFC,0x16,0x40,0xFF,0xFC,
0x16,0x60,0xFF,0xFC,0x0C,0x00,0xFF,0xFC,0x0D,0x00,0xFF,0xFC,0x0E,0x00,0xFF,0xFC,
0x0F,0x00,0xFF,0xFC,0x00,0x00,0xFF,0xFC,0x01,0x40,0xFF,0xFC,0x02,0x80,0xFF,0xFC,
0x03,0xC0,0xFF,0xFC,0x00,0x50,0xFF,0xFC,0x01,0x90,0xFF,0xFC,0x02,0xD0,0xFF,0xFC,
0x04,0x10,0x00,0x00,0x40,0x00,0x00,0x01,0x2F,0x60,0x00,0x02,0x1E,0xC0,0x00,0x03,
0x0E,0x20,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x04,0x00,0x00,
0x00,0x08,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x80,0x00,0x00,
0x01,0x00,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0x12,0x00,0x00,
0x00,0x13,0x00,0x00,0x00,0x2C,0x00,0x00,0x3E,0x00,0x00,0x2C,0x00,0x00,0x3E,0x00,
0x00,0x00,0x00,0x00,0x00,0x2D,0x00,0x00,0x3F,0x00,0x00,0x2D,0x00,0x00,0x3F,0x00,
0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0xFF,0x00,0x00,0xFF,0x00,0x00,0xFF,0x00,
0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x80,0x00,0x00,0x02,0x00,0x00,0x00,0x08,0x00,
0x77,0x61,0x6E,0x58,0x4C,0x20,0x66,0x69,0x72,0x6D,0x77,0x61,0x72,0x65,0x0A,0x43,
0x6F,0x70,0x79,0x72,0x69,0x67,0x68,0x74,0x20,0x28,0x43,0x29,0x20,0x32,0x30,0x30,
0x33,0x20,0x4B,0x72,0x7A,0x79,0x73,0x7A,0x74,0x6F,0x66,0x20,0x48,0x61,0x6C,0x61,
0x73,0x61,0x20,0x3C,0x6B,0x68,0x63,0x40,0x70,0x6D,0x2E,0x77,0x61,0x77,0x2E,0x70,
0x6C,0x3E,0x0A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};