* Re: [PATCH net-next 2/5] net: ethernet: oa_tc6: Allow custom mii_bus
From: Andrew Lunn @ 2026-05-03 3:50 UTC (permalink / raw)
To: ciprian.regus
Cc: Parthiban Veerasooran, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Heiner Kallweit, Russell King, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, netdev, linux-kernel,
linux-doc, devicetree
In-Reply-To: <20260503-adin1140-driver-v1-2-dd043cdd88f0@analog.com>
> @@ -538,32 +539,37 @@ static int oa_tc6_mdiobus_register(struct oa_tc6 *tc6)
> {
> int ret;
>
> - tc6->mdiobus = mdiobus_alloc();
> if (!tc6->mdiobus) {
> - netdev_err(tc6->netdev, "MDIO bus alloc failed\n");
> - return -ENOMEM;
> + tc6->mdiobus = mdiobus_alloc();
> + if (!tc6->mdiobus) {
> + netdev_err(tc6->netdev, "MDIO bus alloc failed\n");
> + return -ENOMEM;
> + }
> +
> + tc6->mdiobus->read = oa_tc6_mdiobus_read;
> + tc6->mdiobus->write = oa_tc6_mdiobus_write;
> + /* OPEN Alliance 10BASE-T1x compliance MAC-PHYs will have both C22 and
> + * C45 registers space. If the PHY is discovered via C22 bus protocol it
> + * assumes it uses C22 protocol and always uses C22 registers indirect
> + * access to access C45 registers. This is because, we don't have a
> + * clean separation between C22/C45 register space and C22/C45 MDIO bus
> + * protocols. Resulting, PHY C45 registers direct access can't be used
> + * which can save multiple SPI bus access. To support this feature, PHY
> + * drivers can set .read_mmd/.write_mmd in the PHY driver to call
> + * .read_c45/.write_c45. Ex: drivers/net/phy/microchip_t1s.c
> + */
> + tc6->mdiobus->read_c45 = oa_tc6_mdiobus_read_c45;
> + tc6->mdiobus->write_c45 = oa_tc6_mdiobus_write_c45;
> +
> + tc6->own_mdiobus = true;
> }
>
> tc6->mdiobus->priv = tc6;
> - tc6->mdiobus->read = oa_tc6_mdiobus_read;
> - tc6->mdiobus->write = oa_tc6_mdiobus_write;
> - /* OPEN Alliance 10BASE-T1x compliance MAC-PHYs will have both C22 and
> - * C45 registers space. If the PHY is discovered via C22 bus protocol it
> - * assumes it uses C22 protocol and always uses C22 registers indirect
> - * access to access C45 registers. This is because, we don't have a
> - * clean separation between C22/C45 register space and C22/C45 MDIO bus
> - * protocols. Resulting, PHY C45 registers direct access can't be used
> - * which can save multiple SPI bus access. To support this feature, PHY
> - * drivers can set .read_mmd/.write_mmd in the PHY driver to call
> - * .read_c45/.write_c45. Ex: drivers/net/phy/microchip_t1s.c
> - */
> - tc6->mdiobus->read_c45 = oa_tc6_mdiobus_read_c45;
> - tc6->mdiobus->write_c45 = oa_tc6_mdiobus_write_c45;
> - tc6->mdiobus->name = "oa-tc6-mdiobus";
> tc6->mdiobus->parent = tc6->dev;
> + tc6->mdiobus->name = "oa-tc6-mdiobus";
>
> snprintf(tc6->mdiobus->id, ARRAY_SIZE(tc6->mdiobus->id), "%s",
> - dev_name(&tc6->spi->dev));
> + dev_name(&tc6->spi->dev));
>
> ret = mdiobus_register(tc6->mdiobus);
> if (ret) {
> @@ -577,19 +583,30 @@ static int oa_tc6_mdiobus_register(struct oa_tc6 *tc6)
>
> static void oa_tc6_mdiobus_unregister(struct oa_tc6 *tc6)
> {
> + if (!tc6->mdiobus)
> + return;
> +
> mdiobus_unregister(tc6->mdiobus);
> - mdiobus_free(tc6->mdiobus);
> +
> + if (tc6->own_mdiobus)
> + mdiobus_free(tc6->mdiobus);
> }
>
> static int oa_tc6_phy_init(struct oa_tc6 *tc6)
> {
> int ret;
>
> - ret = oa_tc6_check_phy_reg_direct_access_capability(tc6);
> - if (ret) {
> - netdev_err(tc6->netdev,
> - "Direct PHY register access is not supported by the MAC-PHY\n");
> - return ret;
> + /* If the driver provided a mii_bus, it is also responsible for
> + * implementing the bus access methods, so we don't have to worry
> + * about checking the PHY access mode.
> + */
> + if (!tc6->mdiobus) {
> + ret = oa_tc6_check_phy_reg_direct_access_capability(tc6);
> + if (ret) {
> + netdev_err(tc6->netdev,
> + "Direct PHY register access is not supported by the MAC-PHY\n");
> + return ret;
> + }
This all seems pretty invasive and ugly. Please could you think what
happens if instead of passing in an mdiobus, you pass a phydev. Is the
change to the core simpler and cleaner?
Andrew
^ permalink raw reply
* Re: [PATCH net-next 4/5] net: ethernet: adi: Add a driver for the ADIN1140 MACPHY
From: Andrew Lunn @ 2026-05-03 3:36 UTC (permalink / raw)
To: ciprian.regus
Cc: Parthiban Veerasooran, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Heiner Kallweit, Russell King, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, netdev, linux-kernel,
linux-doc, devicetree
In-Reply-To: <20260503-adin1140-driver-v1-4-dd043cdd88f0@analog.com>
On Sun, May 03, 2026 at 02:24:53AM +0300, Ciprian Regus via B4 Relay wrote:
> From: Ciprian Regus <ciprian.regus@analog.com>
>
> Add a driver for ADIN1140. The device is a 10BASE-T1S MAC-PHY
> (integrated in the same package) that connects to a CPU over an SPI bus,
> and implements the Open Alliance TC6 protocol for control and frame
> transfers. As such, this driver relies on oa_tc6 for the communication
> with the device. The device has an alternative name (AD3306), so the
> driver can be probed using one of the two compatible strings.
>
> For control transactions, ADIN1140 only implements the protected mode.
> The driver has a custom implementation for the mii_bus access methods as a
> workaround for hardware issues:
>
> 1. The OA TC6 standard defines the direct and indirect access modes for
> MDIO transactions. The ADIN1140 incorrectly advertises indirect mode
> only (supported capabilities register - 0x2, bit 9), while actually
> implementing just the direct mode. We cannot rely on the CAP register
> to choose an access method (which oa_tc6 does by default, even though
> it only implements the direct mode), so the driver has to use its
> own.
> 2. The ADIN1140 cannot access the C22 register space of the internal
> PHY, while the PHY is busy receiving frames. If that happens, the
> CONFIG0 and CONFIG2 registers of the MAC will get corrupted and the
> data transfer will stop. Those two registers configure settings for
> the transfer protocol between the MAC and host, so the value for some
> of their subfields shouldn't be changed while the netdev is up.
> Since we know the PHY is internal, the MAC driver can implement a
> custom mii_bus, which can intercept C22 accesses. Most of the
> registers mapped in the 0x0 - 0x3 range (the only ones the PHY offers)
> are read only, and their value can be read from somewhere else (e.g
> the PHYID 1 & 2 have the same value as 0x1 in the MAC memory map).
> For the fields that are R/W (loopback and AN/reset) in the control
> register, the PHY driver already implements the set_loopback() and
> config_aneg() functions. The C22 write function of the driver is a
> no-op and is used to protect against the ioctl MDIO access path.
> C45 accesses do not cause this issue, so we can properly implement
> them.
>
> Signed-off-by: Ciprian Regus <ciprian.regus@analog.com>
> ---
> MAINTAINERS | 7 +
> drivers/net/ethernet/adi/Kconfig | 12 +
> drivers/net/ethernet/adi/Makefile | 1 +
> drivers/net/ethernet/adi/adin1140.c | 805 ++++++++++++++++++++++++++++++++++++
> 4 files changed, 825 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 1e58da5ef47a..f9784c25beac 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1843,6 +1843,13 @@ S: Supported
> W: https://ez.analog.com/linux-software-drivers
> F: drivers/dma/dma-axi-dmac.c
>
> +ANALOG DEVICES INC ETHERNET DRIVERS
> +M: Ciprian Regus <ciprian.regus@analog.com>
> +L: netdev@vger.kernel.org
> +S: Maintained
> +W: https://ez.analog.com/linux-software-drivers
> +F: drivers/net/ethernet/adi/adin1140.c
> +
> ANALOG DEVICES INC ETHERNET PHY DRIVERS
> M: Ciprian Regus <ciprian.regus@analog.com>
> L: netdev@vger.kernel.org
> diff --git a/drivers/net/ethernet/adi/Kconfig b/drivers/net/ethernet/adi/Kconfig
> index 760a9a60bc15..bdb8ff7d15da 100644
> --- a/drivers/net/ethernet/adi/Kconfig
> +++ b/drivers/net/ethernet/adi/Kconfig
> @@ -26,4 +26,16 @@ config ADIN1110
> Say yes here to build support for Analog Devices ADIN1110
> Low Power 10BASE-T1L Ethernet MAC-PHY.
>
> +config ADIN1140
> + tristate "Analog Devices ADIN1140 MAC-PHY"
> + depends on SPI
> + select ADIN1140_PHY
> + select OA_TC6
> + help
> + Say yes here to build support for Analog Devices, Inc. ADIN1140
> + 10BASE-T1S Ethernet MAC-PHY.
> +
> + To compile this driver as a module, choose M here. The module will be
> + called adin1140.
> +
> endif # NET_VENDOR_ADI
> diff --git a/drivers/net/ethernet/adi/Makefile b/drivers/net/ethernet/adi/Makefile
> index d0383d94303c..0390ca8ccc49 100644
> --- a/drivers/net/ethernet/adi/Makefile
> +++ b/drivers/net/ethernet/adi/Makefile
> @@ -4,3 +4,4 @@
> #
>
> obj-$(CONFIG_ADIN1110) += adin1110.o
> +obj-$(CONFIG_ADIN1140) += adin1140.o
> diff --git a/drivers/net/ethernet/adi/adin1140.c b/drivers/net/ethernet/adi/adin1140.c
> new file mode 100644
> index 000000000000..5bc3f5732ed8
> --- /dev/null
> +++ b/drivers/net/ethernet/adi/adin1140.c
> @@ -0,0 +1,805 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Driver for Analog Devices, Inc. ADIN1140 10BASE-T1S MAC-PHY
> + *
> + * Copyright 2026 Analog Devices Inc.
> + */
> +
> +#include <linux/etherdevice.h>
> +#include <linux/kernel.h>
> +#include <linux/mdio.h>
> +#include <linux/module.h>
> +#include <linux/oa_tc6.h>
> +#include <linux/phy.h>
> +
> +#define ADIN1140_MMS_REG(m, r) ((((m) & GENMASK(3, 0)) << 16) | \
> + ((r) & GENMASK(15, 0)))
> +
> +#define ADIN1140_MACPHY_ID_REG ADIN1140_MMS_REG(0x0, 0x1)
This is not an ADIN1140 MACPHY_ID_REG, it is the TC6 PHYID register.
> +
> +#define ADIN1140_CONFIG0_REG 0x0004
> +#define ADIN1140_CONFIG0_TXFCSVE BIT(14)
> +#define ADIN1140_CONFIG0_RFA_ZARFE BIT(12)
> +#define ADIN1140_CONFIG0_CPS_64 GENMASK(2, 1)
> +
> +#define ADIN1140_CONFIG2_REG ADIN1140_MMS_REG(0x0, 0x6)
This is not an ADIN1140 CONFIG2 register. It is the TC6 CONFIG2
register.
Please add the TC6 registers to include/linux/oa_tc6.
Andrew
^ permalink raw reply
* Re: [PATCH] fprobe: Add unregister_fprobe_sync() for synchronous unregistration
From: kernel test robot @ 2026-05-03 3:25 UTC (permalink / raw)
To: Masami Hiramatsu (Google), Steven Rostedt
Cc: llvm, oe-kbuild-all, Mathieu Desnoyers, Jonathan Corbet,
linux-kernel, linux-trace-kernel, linux-doc
In-Reply-To: <177729179863.401400.6063130067239479972.stgit@mhiramat.tok.corp.google.com>
Hi Masami,
kernel test robot noticed the following build errors:
[auto build test ERROR on trace/for-next]
[cannot apply to linus/master v7.1-rc1 next-20260430]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Masami-Hiramatsu-Google/fprobe-Add-unregister_fprobe_sync-for-synchronous-unregistration/20260427-214258
base: https://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace for-next
patch link: https://lore.kernel.org/r/177729179863.401400.6063130067239479972.stgit%40mhiramat.tok.corp.google.com
patch subject: [PATCH] fprobe: Add unregister_fprobe_sync() for synchronous unregistration
config: s390-allmodconfig (https://download.01.org/0day-ci/archive/20260503/202605031133.LJkoT4xo-lkp@intel.com/config)
compiler: clang version 18.1.8 (https://github.com/llvm/llvm-project 3b5b5c1ec4a3095ab096dd780e84d7ab81f3d7ff)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260503/202605031133.LJkoT4xo-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202605031133.LJkoT4xo-lkp@intel.com/
All errors (new ones prefixed by >>):
>> kernel/trace/fprobe.c:983:14: error: call to undeclared function 'fprobe_registered'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
983 | if (!fp || !fprobe_registered(fp))
| ^
>> kernel/trace/fprobe.c:986:8: error: call to undeclared function 'unregister_fprobe_nolock'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
986 | ret = unregister_fprobe_nolock(fp);
| ^
kernel/trace/fprobe.c:986:8: note: did you mean 'unregister_fprobe_sync'?
kernel/trace/fprobe.c:978:5: note: 'unregister_fprobe_sync' declared here
978 | int unregister_fprobe_sync(struct fprobe *fp)
| ^
979 | {
980 | int ret;
981 |
982 | guard(mutex)(&fprobe_mutex);
983 | if (!fp || !fprobe_registered(fp))
984 | return -EINVAL;
985 |
986 | ret = unregister_fprobe_nolock(fp);
| ~~~~~~~~~~~~~~~~~~~~~~~~
| unregister_fprobe_sync
2 errors generated.
vim +/fprobe_registered +983 kernel/trace/fprobe.c
967
968 /**
969 * unregister_fprobe_sync() - Unregister fprobe synchronously with RCU grace period.
970 * @fp: A fprobe data structure to be unregistered.
971 *
972 * Unregister fprobe (and remove ftrace hooks from the function entries) and
973 * wait for the RCU grace period to finish. This is useful for preventing
974 * the fprobe from being used after it is unregistered.
975 *
976 * Return 0 if @fp is unregistered successfully, -errno if not.
977 */
978 int unregister_fprobe_sync(struct fprobe *fp)
979 {
980 int ret;
981
982 guard(mutex)(&fprobe_mutex);
> 983 if (!fp || !fprobe_registered(fp))
984 return -EINVAL;
985
> 986 ret = unregister_fprobe_nolock(fp);
987 if (ret)
988 return ret;
989
990 synchronize_rcu();
991 return 0;
992 }
993 EXPORT_SYMBOL_GPL(unregister_fprobe_sync);
994
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* Re: [PATCH net-next 4/5] net: ethernet: adi: Add a driver for the ADIN1140 MACPHY
From: Andrew Lunn @ 2026-05-03 3:15 UTC (permalink / raw)
To: ciprian.regus
Cc: Parthiban Veerasooran, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Heiner Kallweit, Russell King, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, netdev, linux-kernel,
linux-doc, devicetree
In-Reply-To: <20260503-adin1140-driver-v1-4-dd043cdd88f0@analog.com>
> +enum adin1140_statistics_entry {
> + rx_frames,
> + rx_broadcast_frames,
> + rx_multicast_frames,
> + rx_unicast_frames,
> + rx_crc_errors,
> + rx_align_errors,
> + rx_preamble_errors,
> + rx_short_frame_errors,
> + rx_long_frame_errors,
> + rx_phy_errors,
> + rx_fifo_full_dropped,
> + rx_addr_filter_dropped,
> + rx_ifg_errors,
> + tx_frames,
> + tx_broadcast_frames,
> + tx_multicast_frames,
> + tx_unicast_frames,
> + tx_single_collision,
> + tx_multi_collision,
> + tx_deferred,
> + tx_late_collision,
> + tx_excess_collision,
> + tx_underrun,
> +};
Many of these seem to be ethtool_eth_mac_stats. Please use that to
report the. You should only use the free form strings/values for none
standard statistics.
Andrew
^ permalink raw reply
* [PATCH v2] tty: synclink_gt: remove broken driver
From: Ethan Nelson-Moore @ 2026-05-03 3:07 UTC (permalink / raw)
To: linux-doc, netdev, linux-serial, rust-for-linux
Cc: Ethan Nelson-Moore, Jonathan Corbet, Shuah Khan,
Madhavan Srinivasan, Michael Ellerman, Nicholas Piggin,
Christophe Leroy (CS GROUP), Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Greg Kroah-Hartman,
Jiri Slaby, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Bagas Sanjaya, Haren Myneni,
Eric Biggers, Qingfang Deng, Julian Braha
The synclink_gt driver was marked as broken in commit 426263d5fb40
("tty: synclink_gt: mark as BROKEN") in July 2023 because it had severe
structural problems and there had been no evidence of users since 2016.
Since then, no meaningful improvements have been made to the driver,
and it is unlikely that will ever happen due to the lack of interest.
Drop the driver and references to it in comments and documentation.
Retain include/uapi/linux/synclink.h to avoid breaking userspace
software.
Signed-off-by: Ethan Nelson-Moore <enelsonmoore@gmail.com>
---
Changes from v1:
- Retain UAPI header - the linux-raw-sys Rust crate generates bindings
for it [1]
- Remove Documentation/userspace-api/ioctl/ioctl-number.rst entry
instead of marking it as dead; other recent driver removals have simply
removed entries
[1] https://github.com/sunfishcode/linux-raw-sys/blob/3b15c17f2688445cd3e9ef2907113829837773dd/gen/ioctl/list.c#L151
.../userspace-api/ioctl/ioctl-number.rst | 1 -
arch/powerpc/configs/ppc6xx_defconfig | 1 -
drivers/net/ppp/Kconfig | 4 +-
drivers/tty/Kconfig | 11 +-
drivers/tty/Makefile | 1 -
drivers/tty/n_hdlc.c | 7 -
drivers/tty/synclink_gt.c | 5038 -----------------
include/linux/synclink.h | 37 -
8 files changed, 3 insertions(+), 5097 deletions(-)
delete mode 100644 drivers/tty/synclink_gt.c
delete mode 100644 include/linux/synclink.h
diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst
index 331223761fff..58716f4f4834 100644
--- a/Documentation/userspace-api/ioctl/ioctl-number.rst
+++ b/Documentation/userspace-api/ioctl/ioctl-number.rst
@@ -271,7 +271,6 @@ Code Seq# Include File Comments
'm' 00-09 linux/mmtimer.h conflict!
'm' all linux/mtio.h conflict!
'm' all linux/soundcard.h conflict!
-'m' all linux/synclink.h conflict!
'm' 00-19 drivers/message/fusion/mptctl.h conflict!
'm' 00 drivers/scsi/megaraid/megaraid_ioctl.h conflict!
'n' 00-7F linux/ncp_fs.h and fs/ncpfs/ioctl.c
diff --git a/arch/powerpc/configs/ppc6xx_defconfig b/arch/powerpc/configs/ppc6xx_defconfig
index ccabc6e17168..4ddd2c01b8b7 100644
--- a/arch/powerpc/configs/ppc6xx_defconfig
+++ b/arch/powerpc/configs/ppc6xx_defconfig
@@ -551,7 +551,6 @@ CONFIG_GAMEPORT_EMU10K1=m
CONFIG_GAMEPORT_FM801=m
# CONFIG_LEGACY_PTYS is not set
CONFIG_SERIAL_NONSTANDARD=y
-CONFIG_SYNCLINK_GT=m
CONFIG_NOZOMI=m
CONFIG_N_HDLC=m
CONFIG_SERIAL_8250=y
diff --git a/drivers/net/ppp/Kconfig b/drivers/net/ppp/Kconfig
index 753354b4e36c..5324e2102383 100644
--- a/drivers/net/ppp/Kconfig
+++ b/drivers/net/ppp/Kconfig
@@ -191,8 +191,8 @@ config PPP_SYNC_TTY
tristate "PPP support for sync tty ports"
help
Say Y (or M) here if you want to be able to use PPP over synchronous
- (HDLC) tty devices, such as the SyncLink adapter. These devices
- are often used for high-speed leased lines like T1/E1.
+ (HDLC) tty devices. These devices are often used for high-speed leased
+ lines like T1/E1.
To compile this driver as a module, choose M here.
diff --git a/drivers/tty/Kconfig b/drivers/tty/Kconfig
index 149f3d53b760..df6832a4c237 100644
--- a/drivers/tty/Kconfig
+++ b/drivers/tty/Kconfig
@@ -231,21 +231,12 @@ config MOXA_SMARTIO
This driver can also be built as a module. The module will be called
mxser. If you want to do that, say M here.
-config SYNCLINK_GT
- tristate "SyncLink GT/AC support"
- depends on SERIAL_NONSTANDARD && PCI
- depends on BROKEN
- help
- Support for SyncLink GT and SyncLink AC families of
- synchronous and asynchronous serial adapters
- manufactured by Microgate Systems, Ltd. (www.microgate.com)
-
config N_HDLC
tristate "HDLC line discipline support"
depends on SERIAL_NONSTANDARD
help
Allows synchronous HDLC communications with tty device drivers that
- support synchronous HDLC such as the Microgate SyncLink adapter.
+ support synchronous HDLC.
This driver can be built as a module ( = code which can be
inserted in and removed from the running kernel whenever you want).
diff --git a/drivers/tty/Makefile b/drivers/tty/Makefile
index 07aca5184a55..8ca1a0a2229f 100644
--- a/drivers/tty/Makefile
+++ b/drivers/tty/Makefile
@@ -21,7 +21,6 @@ obj-$(CONFIG_MOXA_INTELLIO) += moxa.o
obj-$(CONFIG_MOXA_SMARTIO) += mxser.o
obj-$(CONFIG_NOZOMI) += nozomi.o
obj-$(CONFIG_NULL_TTY) += ttynull.o
-obj-$(CONFIG_SYNCLINK_GT) += synclink_gt.o
obj-$(CONFIG_PPC_EPAPR_HV_BYTECHAN) += ehv_bytechan.o
obj-$(CONFIG_GOLDFISH_TTY) += goldfish.o
obj-$(CONFIG_MIPS_EJTAG_FDC_TTY) += mips_ejtag_fdc.o
diff --git a/drivers/tty/n_hdlc.c b/drivers/tty/n_hdlc.c
index 98eefa2cede4..895b850a5950 100644
--- a/drivers/tty/n_hdlc.c
+++ b/drivers/tty/n_hdlc.c
@@ -4,8 +4,6 @@
* Written by Paul Fulghum paulkf@microgate.com
* for Microgate Corporation
*
- * Microgate and SyncLink are registered trademarks of Microgate Corporation
- *
* Adapted from ppp.c, written by Michael Callahan <callahan@maths.ox.ac.uk>,
* Al Longyear <longyear@netcom.com>,
* Paul Mackerras <Paul.Mackerras@cs.anu.edu.au>
@@ -54,11 +52,6 @@
* this line discipline (or another line discipline that is frame
* oriented such as N_PPP).
*
- * The SyncLink driver (synclink.c) implements both asynchronous
- * (using standard line discipline N_TTY) and synchronous HDLC
- * (using N_HDLC) communications, with the latter using the above
- * conventions.
- *
* This implementation is very basic and does not maintain
* any statistics. The main point is to enforce the raw data
* and frame orientation of HDLC communications.
diff --git a/drivers/tty/synclink_gt.c b/drivers/tty/synclink_gt.c
deleted file mode 100644
index bf4f50e0ce94..000000000000
--- a/drivers/tty/synclink_gt.c
+++ /dev/null
@@ -1,5038 +0,0 @@
-// SPDX-License-Identifier: GPL-1.0+
-/*
- * Device driver for Microgate SyncLink GT serial adapters.
- *
- * written by Paul Fulghum for Microgate Corporation
- * paulkf@microgate.com
- *
- * Microgate and SyncLink are trademarks of Microgate Corporation
- *
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
- * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
- * OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/*
- * DEBUG OUTPUT DEFINITIONS
- *
- * uncomment lines below to enable specific types of debug output
- *
- * DBGINFO information - most verbose output
- * DBGERR serious errors
- * DBGBH bottom half service routine debugging
- * DBGISR interrupt service routine debugging
- * DBGDATA output receive and transmit data
- * DBGTBUF output transmit DMA buffers and registers
- * DBGRBUF output receive DMA buffers and registers
- */
-
-#define DBGINFO(fmt) if (debug_level >= DEBUG_LEVEL_INFO) printk fmt
-#define DBGERR(fmt) if (debug_level >= DEBUG_LEVEL_ERROR) printk fmt
-#define DBGBH(fmt) if (debug_level >= DEBUG_LEVEL_BH) printk fmt
-#define DBGISR(fmt) if (debug_level >= DEBUG_LEVEL_ISR) printk fmt
-#define DBGDATA(info, buf, size, label) if (debug_level >= DEBUG_LEVEL_DATA) trace_block((info), (buf), (size), (label))
-/*#define DBGTBUF(info) dump_tbufs(info)*/
-/*#define DBGRBUF(info) dump_rbufs(info)*/
-
-
-#include <linux/module.h>
-#include <linux/errno.h>
-#include <linux/signal.h>
-#include <linux/sched.h>
-#include <linux/timer.h>
-#include <linux/interrupt.h>
-#include <linux/pci.h>
-#include <linux/tty.h>
-#include <linux/tty_flip.h>
-#include <linux/serial.h>
-#include <linux/major.h>
-#include <linux/string.h>
-#include <linux/fcntl.h>
-#include <linux/ptrace.h>
-#include <linux/ioport.h>
-#include <linux/mm.h>
-#include <linux/seq_file.h>
-#include <linux/slab.h>
-#include <linux/netdevice.h>
-#include <linux/vmalloc.h>
-#include <linux/init.h>
-#include <linux/delay.h>
-#include <linux/ioctl.h>
-#include <linux/termios.h>
-#include <linux/bitops.h>
-#include <linux/workqueue.h>
-#include <linux/hdlc.h>
-#include <linux/synclink.h>
-
-#include <asm/io.h>
-#include <asm/irq.h>
-#include <asm/dma.h>
-#include <asm/types.h>
-#include <linux/uaccess.h>
-
-#if defined(CONFIG_HDLC) || (defined(CONFIG_HDLC_MODULE) && defined(CONFIG_SYNCLINK_GT_MODULE))
-#define SYNCLINK_GENERIC_HDLC 1
-#else
-#define SYNCLINK_GENERIC_HDLC 0
-#endif
-
-/*
- * module identification
- */
-static const char driver_name[] = "SyncLink GT";
-static const char tty_dev_prefix[] = "ttySLG";
-MODULE_DESCRIPTION("Device driver for Microgate SyncLink GT serial adapters");
-MODULE_LICENSE("GPL");
-#define MAX_DEVICES 32
-
-static const struct pci_device_id pci_table[] = {
- { PCI_VDEVICE(MICROGATE, SYNCLINK_GT_DEVICE_ID) },
- { PCI_VDEVICE(MICROGATE, SYNCLINK_GT2_DEVICE_ID) },
- { PCI_VDEVICE(MICROGATE, SYNCLINK_GT4_DEVICE_ID) },
- { PCI_VDEVICE(MICROGATE, SYNCLINK_AC_DEVICE_ID) },
- { 0 }, /* terminate list */
-};
-MODULE_DEVICE_TABLE(pci, pci_table);
-
-static int init_one(struct pci_dev *dev,const struct pci_device_id *ent);
-static void remove_one(struct pci_dev *dev);
-static struct pci_driver pci_driver = {
- .name = "synclink_gt",
- .id_table = pci_table,
- .probe = init_one,
- .remove = remove_one,
-};
-
-static bool pci_registered;
-
-/*
- * module configuration and status
- */
-static struct slgt_info *slgt_device_list;
-static int slgt_device_count;
-
-static int ttymajor;
-static int debug_level;
-static int maxframe[MAX_DEVICES];
-
-module_param(ttymajor, int, 0);
-module_param(debug_level, int, 0);
-module_param_array(maxframe, int, NULL, 0);
-
-MODULE_PARM_DESC(ttymajor, "TTY major device number override: 0=auto assigned");
-MODULE_PARM_DESC(debug_level, "Debug syslog output: 0=disabled, 1 to 5=increasing detail");
-MODULE_PARM_DESC(maxframe, "Maximum frame size used by device (4096 to 65535)");
-
-/*
- * tty support and callbacks
- */
-static struct tty_driver *serial_driver;
-
-static void wait_until_sent(struct tty_struct *tty, int timeout);
-static void flush_buffer(struct tty_struct *tty);
-static void tx_release(struct tty_struct *tty);
-
-/*
- * generic HDLC support
- */
-#define dev_to_port(D) (dev_to_hdlc(D)->priv)
-
-
-/*
- * device specific structures, macros and functions
- */
-
-#define SLGT_MAX_PORTS 4
-#define SLGT_REG_SIZE 256
-
-/*
- * conditional wait facility
- */
-struct cond_wait {
- struct cond_wait *next;
- wait_queue_head_t q;
- wait_queue_entry_t wait;
- unsigned int data;
-};
-static void flush_cond_wait(struct cond_wait **head);
-
-/*
- * DMA buffer descriptor and access macros
- */
-struct slgt_desc
-{
- __le16 count;
- __le16 status;
- __le32 pbuf; /* physical address of data buffer */
- __le32 next; /* physical address of next descriptor */
-
- /* driver book keeping */
- char *buf; /* virtual address of data buffer */
- unsigned int pdesc; /* physical address of this descriptor */
- dma_addr_t buf_dma_addr;
- unsigned short buf_count;
-};
-
-#define set_desc_buffer(a,b) (a).pbuf = cpu_to_le32((unsigned int)(b))
-#define set_desc_next(a,b) (a).next = cpu_to_le32((unsigned int)(b))
-#define set_desc_count(a,b)(a).count = cpu_to_le16((unsigned short)(b))
-#define set_desc_eof(a,b) (a).status = cpu_to_le16((b) ? (le16_to_cpu((a).status) | BIT0) : (le16_to_cpu((a).status) & ~BIT0))
-#define set_desc_status(a, b) (a).status = cpu_to_le16((unsigned short)(b))
-#define desc_count(a) (le16_to_cpu((a).count))
-#define desc_status(a) (le16_to_cpu((a).status))
-#define desc_complete(a) (le16_to_cpu((a).status) & BIT15)
-#define desc_eof(a) (le16_to_cpu((a).status) & BIT2)
-#define desc_crc_error(a) (le16_to_cpu((a).status) & BIT1)
-#define desc_abort(a) (le16_to_cpu((a).status) & BIT0)
-#define desc_residue(a) ((le16_to_cpu((a).status) & 0x38) >> 3)
-
-struct _input_signal_events {
- int ri_up;
- int ri_down;
- int dsr_up;
- int dsr_down;
- int dcd_up;
- int dcd_down;
- int cts_up;
- int cts_down;
-};
-
-/*
- * device instance data structure
- */
-struct slgt_info {
- void *if_ptr; /* General purpose pointer (used by SPPP) */
- struct tty_port port;
-
- struct slgt_info *next_device; /* device list link */
-
- char device_name[25];
- struct pci_dev *pdev;
-
- int port_count; /* count of ports on adapter */
- int adapter_num; /* adapter instance number */
- int port_num; /* port instance number */
-
- /* array of pointers to port contexts on this adapter */
- struct slgt_info *port_array[SLGT_MAX_PORTS];
-
- int line; /* tty line instance number */
-
- struct mgsl_icount icount;
-
- int timeout;
- int x_char; /* xon/xoff character */
- unsigned int read_status_mask;
- unsigned int ignore_status_mask;
-
- wait_queue_head_t status_event_wait_q;
- wait_queue_head_t event_wait_q;
- struct timer_list tx_timer;
- struct timer_list rx_timer;
-
- unsigned int gpio_present;
- struct cond_wait *gpio_wait_q;
-
- spinlock_t lock; /* spinlock for synchronizing with ISR */
-
- struct work_struct task;
- u32 pending_bh;
- bool bh_requested;
- bool bh_running;
-
- int isr_overflow;
- bool irq_requested; /* true if IRQ requested */
- bool irq_occurred; /* for diagnostics use */
-
- /* device configuration */
-
- unsigned int bus_type;
- unsigned int irq_level;
- unsigned long irq_flags;
-
- unsigned char __iomem * reg_addr; /* memory mapped registers address */
- u32 phys_reg_addr;
- bool reg_addr_requested;
-
- MGSL_PARAMS params; /* communications parameters */
- u32 idle_mode;
- u32 max_frame_size; /* as set by device config */
-
- unsigned int rbuf_fill_level;
- unsigned int rx_pio;
- unsigned int if_mode;
- unsigned int base_clock;
- unsigned int xsync;
- unsigned int xctrl;
-
- /* device status */
-
- bool rx_enabled;
- bool rx_restart;
-
- bool tx_enabled;
- bool tx_active;
-
- unsigned char signals; /* serial signal states */
- int init_error; /* initialization error */
-
- unsigned char *tx_buf;
- int tx_count;
-
- bool drop_rts_on_tx_done;
- struct _input_signal_events input_signal_events;
-
- int dcd_chkcount; /* check counts to prevent */
- int cts_chkcount; /* too many IRQs if a signal */
- int dsr_chkcount; /* is floating */
- int ri_chkcount;
-
- char *bufs; /* virtual address of DMA buffer lists */
- dma_addr_t bufs_dma_addr; /* physical address of buffer descriptors */
-
- unsigned int rbuf_count;
- struct slgt_desc *rbufs;
- unsigned int rbuf_current;
- unsigned int rbuf_index;
- unsigned int rbuf_fill_index;
- unsigned short rbuf_fill_count;
-
- unsigned int tbuf_count;
- struct slgt_desc *tbufs;
- unsigned int tbuf_current;
- unsigned int tbuf_start;
-
- unsigned char *tmp_rbuf;
- unsigned int tmp_rbuf_count;
-
- /* SPPP/Cisco HDLC device parts */
-
- int netcount;
- spinlock_t netlock;
-#if SYNCLINK_GENERIC_HDLC
- struct net_device *netdev;
-#endif
-
-};
-
-static const MGSL_PARAMS default_params = {
- .mode = MGSL_MODE_HDLC,
- .loopback = 0,
- .flags = HDLC_FLAG_UNDERRUN_ABORT15,
- .encoding = HDLC_ENCODING_NRZI_SPACE,
- .clock_speed = 0,
- .addr_filter = 0xff,
- .crc_type = HDLC_CRC_16_CCITT,
- .preamble_length = HDLC_PREAMBLE_LENGTH_8BITS,
- .preamble = HDLC_PREAMBLE_PATTERN_NONE,
- .data_rate = 9600,
- .data_bits = 8,
- .stop_bits = 1,
- .parity = ASYNC_PARITY_NONE
-};
-
-
-#define BH_RECEIVE 1
-#define BH_TRANSMIT 2
-#define BH_STATUS 4
-#define IO_PIN_SHUTDOWN_LIMIT 100
-
-#define DMABUFSIZE 256
-#define DESC_LIST_SIZE 4096
-
-#define MASK_PARITY BIT1
-#define MASK_FRAMING BIT0
-#define MASK_BREAK BIT14
-#define MASK_OVERRUN BIT4
-
-#define GSR 0x00 /* global status */
-#define JCR 0x04 /* JTAG control */
-#define IODR 0x08 /* GPIO direction */
-#define IOER 0x0c /* GPIO interrupt enable */
-#define IOVR 0x10 /* GPIO value */
-#define IOSR 0x14 /* GPIO interrupt status */
-#define TDR 0x80 /* tx data */
-#define RDR 0x80 /* rx data */
-#define TCR 0x82 /* tx control */
-#define TIR 0x84 /* tx idle */
-#define TPR 0x85 /* tx preamble */
-#define RCR 0x86 /* rx control */
-#define VCR 0x88 /* V.24 control */
-#define CCR 0x89 /* clock control */
-#define BDR 0x8a /* baud divisor */
-#define SCR 0x8c /* serial control */
-#define SSR 0x8e /* serial status */
-#define RDCSR 0x90 /* rx DMA control/status */
-#define TDCSR 0x94 /* tx DMA control/status */
-#define RDDAR 0x98 /* rx DMA descriptor address */
-#define TDDAR 0x9c /* tx DMA descriptor address */
-#define XSR 0x40 /* extended sync pattern */
-#define XCR 0x44 /* extended control */
-
-#define RXIDLE BIT14
-#define RXBREAK BIT14
-#define IRQ_TXDATA BIT13
-#define IRQ_TXIDLE BIT12
-#define IRQ_TXUNDER BIT11 /* HDLC */
-#define IRQ_RXDATA BIT10
-#define IRQ_RXIDLE BIT9 /* HDLC */
-#define IRQ_RXBREAK BIT9 /* async */
-#define IRQ_RXOVER BIT8
-#define IRQ_DSR BIT7
-#define IRQ_CTS BIT6
-#define IRQ_DCD BIT5
-#define IRQ_RI BIT4
-#define IRQ_ALL 0x3ff0
-#define IRQ_MASTER BIT0
-
-#define slgt_irq_on(info, mask) \
- wr_reg16((info), SCR, (unsigned short)(rd_reg16((info), SCR) | (mask)))
-#define slgt_irq_off(info, mask) \
- wr_reg16((info), SCR, (unsigned short)(rd_reg16((info), SCR) & ~(mask)))
-
-static __u8 rd_reg8(struct slgt_info *info, unsigned int addr);
-static void wr_reg8(struct slgt_info *info, unsigned int addr, __u8 value);
-static __u16 rd_reg16(struct slgt_info *info, unsigned int addr);
-static void wr_reg16(struct slgt_info *info, unsigned int addr, __u16 value);
-static __u32 rd_reg32(struct slgt_info *info, unsigned int addr);
-static void wr_reg32(struct slgt_info *info, unsigned int addr, __u32 value);
-
-static void msc_set_vcr(struct slgt_info *info);
-
-static int startup_hw(struct slgt_info *info);
-static int block_til_ready(struct tty_struct *tty, struct file * filp,struct slgt_info *info);
-static void shutdown_hw(struct slgt_info *info);
-static void program_hw(struct slgt_info *info);
-static void change_params(struct slgt_info *info);
-
-static int adapter_test(struct slgt_info *info);
-
-static void reset_port(struct slgt_info *info);
-static void async_mode(struct slgt_info *info);
-static void sync_mode(struct slgt_info *info);
-
-static void rx_stop(struct slgt_info *info);
-static void rx_start(struct slgt_info *info);
-static void reset_rbufs(struct slgt_info *info);
-static void free_rbufs(struct slgt_info *info, unsigned int first, unsigned int last);
-static bool rx_get_frame(struct slgt_info *info);
-static bool rx_get_buf(struct slgt_info *info);
-
-static void tx_start(struct slgt_info *info);
-static void tx_stop(struct slgt_info *info);
-static void tx_set_idle(struct slgt_info *info);
-static unsigned int tbuf_bytes(struct slgt_info *info);
-static void reset_tbufs(struct slgt_info *info);
-static void tdma_reset(struct slgt_info *info);
-static bool tx_load(struct slgt_info *info, const u8 *buf, unsigned int count);
-
-static void get_gtsignals(struct slgt_info *info);
-static void set_gtsignals(struct slgt_info *info);
-static void set_rate(struct slgt_info *info, u32 data_rate);
-
-static void bh_transmit(struct slgt_info *info);
-static void isr_txeom(struct slgt_info *info, unsigned short status);
-
-static void tx_timeout(struct timer_list *t);
-static void rx_timeout(struct timer_list *t);
-
-/*
- * ioctl handlers
- */
-static int get_stats(struct slgt_info *info, struct mgsl_icount __user *user_icount);
-static int get_params(struct slgt_info *info, MGSL_PARAMS __user *params);
-static int set_params(struct slgt_info *info, MGSL_PARAMS __user *params);
-static int get_txidle(struct slgt_info *info, int __user *idle_mode);
-static int set_txidle(struct slgt_info *info, int idle_mode);
-static int tx_enable(struct slgt_info *info, int enable);
-static int tx_abort(struct slgt_info *info);
-static int rx_enable(struct slgt_info *info, int enable);
-static int modem_input_wait(struct slgt_info *info,int arg);
-static int wait_mgsl_event(struct slgt_info *info, int __user *mask_ptr);
-static int get_interface(struct slgt_info *info, int __user *if_mode);
-static int set_interface(struct slgt_info *info, int if_mode);
-static int set_gpio(struct slgt_info *info, struct gpio_desc __user *gpio);
-static int get_gpio(struct slgt_info *info, struct gpio_desc __user *gpio);
-static int wait_gpio(struct slgt_info *info, struct gpio_desc __user *gpio);
-static int get_xsync(struct slgt_info *info, int __user *if_mode);
-static int set_xsync(struct slgt_info *info, int if_mode);
-static int get_xctrl(struct slgt_info *info, int __user *if_mode);
-static int set_xctrl(struct slgt_info *info, int if_mode);
-
-/*
- * driver functions
- */
-static void release_resources(struct slgt_info *info);
-
-/*
- * DEBUG OUTPUT CODE
- */
-#ifndef DBGINFO
-#define DBGINFO(fmt)
-#endif
-#ifndef DBGERR
-#define DBGERR(fmt)
-#endif
-#ifndef DBGBH
-#define DBGBH(fmt)
-#endif
-#ifndef DBGISR
-#define DBGISR(fmt)
-#endif
-
-#ifdef DBGDATA
-static void trace_block(struct slgt_info *info, const char *data, int count, const char *label)
-{
- int i;
- int linecount;
- printk("%s %s data:\n",info->device_name, label);
- while(count) {
- linecount = (count > 16) ? 16 : count;
- for(i=0; i < linecount; i++)
- printk("%02X ",(unsigned char)data[i]);
- for(;i<17;i++)
- printk(" ");
- for(i=0;i<linecount;i++) {
- if (data[i]>=040 && data[i]<=0176)
- printk("%c",data[i]);
- else
- printk(".");
- }
- printk("\n");
- data += linecount;
- count -= linecount;
- }
-}
-#else
-#define DBGDATA(info, buf, size, label)
-#endif
-
-#ifdef DBGTBUF
-static void dump_tbufs(struct slgt_info *info)
-{
- int i;
- printk("tbuf_current=%d\n", info->tbuf_current);
- for (i=0 ; i < info->tbuf_count ; i++) {
- printk("%d: count=%04X status=%04X\n",
- i, le16_to_cpu(info->tbufs[i].count), le16_to_cpu(info->tbufs[i].status));
- }
-}
-#else
-#define DBGTBUF(info)
-#endif
-
-#ifdef DBGRBUF
-static void dump_rbufs(struct slgt_info *info)
-{
- int i;
- printk("rbuf_current=%d\n", info->rbuf_current);
- for (i=0 ; i < info->rbuf_count ; i++) {
- printk("%d: count=%04X status=%04X\n",
- i, le16_to_cpu(info->rbufs[i].count), le16_to_cpu(info->rbufs[i].status));
- }
-}
-#else
-#define DBGRBUF(info)
-#endif
-
-static inline int sanity_check(struct slgt_info *info, char *devname, const char *name)
-{
-#ifdef SANITY_CHECK
- if (!info) {
- printk("null struct slgt_info for (%s) in %s\n", devname, name);
- return 1;
- }
-#else
- if (!info)
- return 1;
-#endif
- return 0;
-}
-
-/*
- * line discipline callback wrappers
- *
- * The wrappers maintain line discipline references
- * while calling into the line discipline.
- *
- * ldisc_receive_buf - pass receive data to line discipline
- */
-static void ldisc_receive_buf(struct tty_struct *tty,
- const __u8 *data, char *flags, int count)
-{
- struct tty_ldisc *ld;
- if (!tty)
- return;
- ld = tty_ldisc_ref(tty);
- if (ld) {
- if (ld->ops->receive_buf)
- ld->ops->receive_buf(tty, data, flags, count);
- tty_ldisc_deref(ld);
- }
-}
-
-/* tty callbacks */
-
-static int open(struct tty_struct *tty, struct file *filp)
-{
- struct slgt_info *info;
- int retval, line;
- unsigned long flags;
-
- line = tty->index;
- if (line >= slgt_device_count) {
- DBGERR(("%s: open with invalid line #%d.\n", driver_name, line));
- return -ENODEV;
- }
-
- info = slgt_device_list;
- while(info && info->line != line)
- info = info->next_device;
- if (sanity_check(info, tty->name, "open"))
- return -ENODEV;
- if (info->init_error) {
- DBGERR(("%s init error=%d\n", info->device_name, info->init_error));
- return -ENODEV;
- }
-
- tty->driver_data = info;
- info->port.tty = tty;
-
- DBGINFO(("%s open, old ref count = %d\n", info->device_name, info->port.count));
-
- mutex_lock(&info->port.mutex);
-
- spin_lock_irqsave(&info->netlock, flags);
- if (info->netcount) {
- retval = -EBUSY;
- spin_unlock_irqrestore(&info->netlock, flags);
- mutex_unlock(&info->port.mutex);
- goto cleanup;
- }
- info->port.count++;
- spin_unlock_irqrestore(&info->netlock, flags);
-
- if (info->port.count == 1) {
- /* 1st open on this device, init hardware */
- retval = startup_hw(info);
- if (retval < 0) {
- mutex_unlock(&info->port.mutex);
- goto cleanup;
- }
- }
- mutex_unlock(&info->port.mutex);
- retval = block_til_ready(tty, filp, info);
- if (retval) {
- DBGINFO(("%s block_til_ready rc=%d\n", info->device_name, retval));
- goto cleanup;
- }
-
- retval = 0;
-
-cleanup:
- if (retval) {
- if (tty->count == 1)
- info->port.tty = NULL; /* tty layer will release tty struct */
- if(info->port.count)
- info->port.count--;
- }
-
- DBGINFO(("%s open rc=%d\n", info->device_name, retval));
- return retval;
-}
-
-static void close(struct tty_struct *tty, struct file *filp)
-{
- struct slgt_info *info = tty->driver_data;
-
- if (sanity_check(info, tty->name, "close"))
- return;
- DBGINFO(("%s close entry, count=%d\n", info->device_name, info->port.count));
-
- if (tty_port_close_start(&info->port, tty, filp) == 0)
- goto cleanup;
-
- mutex_lock(&info->port.mutex);
- if (tty_port_initialized(&info->port))
- wait_until_sent(tty, info->timeout);
- flush_buffer(tty);
- tty_ldisc_flush(tty);
-
- shutdown_hw(info);
- mutex_unlock(&info->port.mutex);
-
- tty_port_close_end(&info->port, tty);
- info->port.tty = NULL;
-cleanup:
- DBGINFO(("%s close exit, count=%d\n", tty->driver->name, info->port.count));
-}
-
-static void hangup(struct tty_struct *tty)
-{
- struct slgt_info *info = tty->driver_data;
- unsigned long flags;
-
- if (sanity_check(info, tty->name, "hangup"))
- return;
- DBGINFO(("%s hangup\n", info->device_name));
-
- flush_buffer(tty);
-
- mutex_lock(&info->port.mutex);
- shutdown_hw(info);
-
- spin_lock_irqsave(&info->port.lock, flags);
- info->port.count = 0;
- info->port.tty = NULL;
- spin_unlock_irqrestore(&info->port.lock, flags);
- tty_port_set_active(&info->port, false);
- mutex_unlock(&info->port.mutex);
-
- wake_up_interruptible(&info->port.open_wait);
-}
-
-static void set_termios(struct tty_struct *tty,
- const struct ktermios *old_termios)
-{
- struct slgt_info *info = tty->driver_data;
- unsigned long flags;
-
- DBGINFO(("%s set_termios\n", tty->driver->name));
-
- change_params(info);
-
- /* Handle transition to B0 status */
- if ((old_termios->c_cflag & CBAUD) && !C_BAUD(tty)) {
- info->signals &= ~(SerialSignal_RTS | SerialSignal_DTR);
- spin_lock_irqsave(&info->lock,flags);
- set_gtsignals(info);
- spin_unlock_irqrestore(&info->lock,flags);
- }
-
- /* Handle transition away from B0 status */
- if (!(old_termios->c_cflag & CBAUD) && C_BAUD(tty)) {
- info->signals |= SerialSignal_DTR;
- if (!C_CRTSCTS(tty) || !tty_throttled(tty))
- info->signals |= SerialSignal_RTS;
- spin_lock_irqsave(&info->lock,flags);
- set_gtsignals(info);
- spin_unlock_irqrestore(&info->lock,flags);
- }
-
- /* Handle turning off CRTSCTS */
- if ((old_termios->c_cflag & CRTSCTS) && !C_CRTSCTS(tty)) {
- tty->hw_stopped = false;
- tx_release(tty);
- }
-}
-
-static void update_tx_timer(struct slgt_info *info)
-{
- /*
- * use worst case speed of 1200bps to calculate transmit timeout
- * based on data in buffers (tbuf_bytes) and FIFO (128 bytes)
- */
- if (info->params.mode == MGSL_MODE_HDLC) {
- int timeout = (tbuf_bytes(info) * 7) + 1000;
- mod_timer(&info->tx_timer, jiffies + msecs_to_jiffies(timeout));
- }
-}
-
-static ssize_t write(struct tty_struct *tty, const u8 *buf, size_t count)
-{
- int ret = 0;
- struct slgt_info *info = tty->driver_data;
- unsigned long flags;
-
- if (sanity_check(info, tty->name, "write"))
- return -EIO;
-
- DBGINFO(("%s write count=%zu\n", info->device_name, count));
-
- if (!info->tx_buf || (count > info->max_frame_size))
- return -EIO;
-
- if (!count || tty->flow.stopped || tty->hw_stopped)
- return 0;
-
- spin_lock_irqsave(&info->lock, flags);
-
- if (info->tx_count) {
- /* send accumulated data from send_char() */
- if (!tx_load(info, info->tx_buf, info->tx_count))
- goto cleanup;
- info->tx_count = 0;
- }
-
- if (tx_load(info, buf, count))
- ret = count;
-
-cleanup:
- spin_unlock_irqrestore(&info->lock, flags);
- DBGINFO(("%s write rc=%d\n", info->device_name, ret));
- return ret;
-}
-
-static int put_char(struct tty_struct *tty, u8 ch)
-{
- struct slgt_info *info = tty->driver_data;
- unsigned long flags;
- int ret = 0;
-
- if (sanity_check(info, tty->name, "put_char"))
- return 0;
- DBGINFO(("%s put_char(%u)\n", info->device_name, ch));
- if (!info->tx_buf)
- return 0;
- spin_lock_irqsave(&info->lock,flags);
- if (info->tx_count < info->max_frame_size) {
- info->tx_buf[info->tx_count++] = ch;
- ret = 1;
- }
- spin_unlock_irqrestore(&info->lock,flags);
- return ret;
-}
-
-static void send_xchar(struct tty_struct *tty, char ch)
-{
- struct slgt_info *info = tty->driver_data;
- unsigned long flags;
-
- if (sanity_check(info, tty->name, "send_xchar"))
- return;
- DBGINFO(("%s send_xchar(%d)\n", info->device_name, ch));
- info->x_char = ch;
- if (ch) {
- spin_lock_irqsave(&info->lock,flags);
- if (!info->tx_enabled)
- tx_start(info);
- spin_unlock_irqrestore(&info->lock,flags);
- }
-}
-
-static void wait_until_sent(struct tty_struct *tty, int timeout)
-{
- struct slgt_info *info = tty->driver_data;
- unsigned long orig_jiffies, char_time;
-
- if (!info )
- return;
- if (sanity_check(info, tty->name, "wait_until_sent"))
- return;
- DBGINFO(("%s wait_until_sent entry\n", info->device_name));
- if (!tty_port_initialized(&info->port))
- goto exit;
-
- orig_jiffies = jiffies;
-
- /* Set check interval to 1/5 of estimated time to
- * send a character, and make it at least 1. The check
- * interval should also be less than the timeout.
- * Note: use tight timings here to satisfy the NIST-PCTS.
- */
-
- if (info->params.data_rate) {
- char_time = info->timeout/(32 * 5);
- if (!char_time)
- char_time++;
- } else
- char_time = 1;
-
- if (timeout)
- char_time = min_t(unsigned long, char_time, timeout);
-
- while (info->tx_active) {
- msleep_interruptible(jiffies_to_msecs(char_time));
- if (signal_pending(current))
- break;
- if (timeout && time_after(jiffies, orig_jiffies + timeout))
- break;
- }
-exit:
- DBGINFO(("%s wait_until_sent exit\n", info->device_name));
-}
-
-static unsigned int write_room(struct tty_struct *tty)
-{
- struct slgt_info *info = tty->driver_data;
- unsigned int ret;
-
- if (sanity_check(info, tty->name, "write_room"))
- return 0;
- ret = (info->tx_active) ? 0 : HDLC_MAX_FRAME_SIZE;
- DBGINFO(("%s write_room=%u\n", info->device_name, ret));
- return ret;
-}
-
-static void flush_chars(struct tty_struct *tty)
-{
- struct slgt_info *info = tty->driver_data;
- unsigned long flags;
-
- if (sanity_check(info, tty->name, "flush_chars"))
- return;
- DBGINFO(("%s flush_chars entry tx_count=%d\n", info->device_name, info->tx_count));
-
- if (info->tx_count <= 0 || tty->flow.stopped ||
- tty->hw_stopped || !info->tx_buf)
- return;
-
- DBGINFO(("%s flush_chars start transmit\n", info->device_name));
-
- spin_lock_irqsave(&info->lock,flags);
- if (info->tx_count && tx_load(info, info->tx_buf, info->tx_count))
- info->tx_count = 0;
- spin_unlock_irqrestore(&info->lock,flags);
-}
-
-static void flush_buffer(struct tty_struct *tty)
-{
- struct slgt_info *info = tty->driver_data;
- unsigned long flags;
-
- if (sanity_check(info, tty->name, "flush_buffer"))
- return;
- DBGINFO(("%s flush_buffer\n", info->device_name));
-
- spin_lock_irqsave(&info->lock, flags);
- info->tx_count = 0;
- spin_unlock_irqrestore(&info->lock, flags);
-
- tty_wakeup(tty);
-}
-
-/*
- * throttle (stop) transmitter
- */
-static void tx_hold(struct tty_struct *tty)
-{
- struct slgt_info *info = tty->driver_data;
- unsigned long flags;
-
- if (sanity_check(info, tty->name, "tx_hold"))
- return;
- DBGINFO(("%s tx_hold\n", info->device_name));
- spin_lock_irqsave(&info->lock,flags);
- if (info->tx_enabled && info->params.mode == MGSL_MODE_ASYNC)
- tx_stop(info);
- spin_unlock_irqrestore(&info->lock,flags);
-}
-
-/*
- * release (start) transmitter
- */
-static void tx_release(struct tty_struct *tty)
-{
- struct slgt_info *info = tty->driver_data;
- unsigned long flags;
-
- if (sanity_check(info, tty->name, "tx_release"))
- return;
- DBGINFO(("%s tx_release\n", info->device_name));
- spin_lock_irqsave(&info->lock, flags);
- if (info->tx_count && tx_load(info, info->tx_buf, info->tx_count))
- info->tx_count = 0;
- spin_unlock_irqrestore(&info->lock, flags);
-}
-
-/*
- * Service an IOCTL request
- *
- * Arguments
- *
- * tty pointer to tty instance data
- * cmd IOCTL command code
- * arg command argument/context
- *
- * Return 0 if success, otherwise error code
- */
-static int ioctl(struct tty_struct *tty,
- unsigned int cmd, unsigned long arg)
-{
- struct slgt_info *info = tty->driver_data;
- void __user *argp = (void __user *)arg;
- int ret;
-
- if (sanity_check(info, tty->name, "ioctl"))
- return -ENODEV;
- DBGINFO(("%s ioctl() cmd=%08X\n", info->device_name, cmd));
-
- if (cmd != TIOCMIWAIT) {
- if (tty_io_error(tty))
- return -EIO;
- }
-
- switch (cmd) {
- case MGSL_IOCWAITEVENT:
- return wait_mgsl_event(info, argp);
- case TIOCMIWAIT:
- return modem_input_wait(info,(int)arg);
- case MGSL_IOCSGPIO:
- return set_gpio(info, argp);
- case MGSL_IOCGGPIO:
- return get_gpio(info, argp);
- case MGSL_IOCWAITGPIO:
- return wait_gpio(info, argp);
- case MGSL_IOCGXSYNC:
- return get_xsync(info, argp);
- case MGSL_IOCSXSYNC:
- return set_xsync(info, (int)arg);
- case MGSL_IOCGXCTRL:
- return get_xctrl(info, argp);
- case MGSL_IOCSXCTRL:
- return set_xctrl(info, (int)arg);
- }
- mutex_lock(&info->port.mutex);
- switch (cmd) {
- case MGSL_IOCGPARAMS:
- ret = get_params(info, argp);
- break;
- case MGSL_IOCSPARAMS:
- ret = set_params(info, argp);
- break;
- case MGSL_IOCGTXIDLE:
- ret = get_txidle(info, argp);
- break;
- case MGSL_IOCSTXIDLE:
- ret = set_txidle(info, (int)arg);
- break;
- case MGSL_IOCTXENABLE:
- ret = tx_enable(info, (int)arg);
- break;
- case MGSL_IOCRXENABLE:
- ret = rx_enable(info, (int)arg);
- break;
- case MGSL_IOCTXABORT:
- ret = tx_abort(info);
- break;
- case MGSL_IOCGSTATS:
- ret = get_stats(info, argp);
- break;
- case MGSL_IOCGIF:
- ret = get_interface(info, argp);
- break;
- case MGSL_IOCSIF:
- ret = set_interface(info,(int)arg);
- break;
- default:
- ret = -ENOIOCTLCMD;
- }
- mutex_unlock(&info->port.mutex);
- return ret;
-}
-
-static int get_icount(struct tty_struct *tty,
- struct serial_icounter_struct *icount)
-
-{
- struct slgt_info *info = tty->driver_data;
- struct mgsl_icount cnow; /* kernel counter temps */
- unsigned long flags;
-
- spin_lock_irqsave(&info->lock,flags);
- cnow = info->icount;
- spin_unlock_irqrestore(&info->lock,flags);
-
- icount->cts = cnow.cts;
- icount->dsr = cnow.dsr;
- icount->rng = cnow.rng;
- icount->dcd = cnow.dcd;
- icount->rx = cnow.rx;
- icount->tx = cnow.tx;
- icount->frame = cnow.frame;
- icount->overrun = cnow.overrun;
- icount->parity = cnow.parity;
- icount->brk = cnow.brk;
- icount->buf_overrun = cnow.buf_overrun;
-
- return 0;
-}
-
-/*
- * support for 32 bit ioctl calls on 64 bit systems
- */
-#ifdef CONFIG_COMPAT
-static long get_params32(struct slgt_info *info, struct MGSL_PARAMS32 __user *user_params)
-{
- struct MGSL_PARAMS32 tmp_params;
-
- DBGINFO(("%s get_params32\n", info->device_name));
- memset(&tmp_params, 0, sizeof(tmp_params));
- tmp_params.mode = (compat_ulong_t)info->params.mode;
- tmp_params.loopback = info->params.loopback;
- tmp_params.flags = info->params.flags;
- tmp_params.encoding = info->params.encoding;
- tmp_params.clock_speed = (compat_ulong_t)info->params.clock_speed;
- tmp_params.addr_filter = info->params.addr_filter;
- tmp_params.crc_type = info->params.crc_type;
- tmp_params.preamble_length = info->params.preamble_length;
- tmp_params.preamble = info->params.preamble;
- tmp_params.data_rate = (compat_ulong_t)info->params.data_rate;
- tmp_params.data_bits = info->params.data_bits;
- tmp_params.stop_bits = info->params.stop_bits;
- tmp_params.parity = info->params.parity;
- if (copy_to_user(user_params, &tmp_params, sizeof(struct MGSL_PARAMS32)))
- return -EFAULT;
- return 0;
-}
-
-static long set_params32(struct slgt_info *info, struct MGSL_PARAMS32 __user *new_params)
-{
- struct MGSL_PARAMS32 tmp_params;
- unsigned long flags;
-
- DBGINFO(("%s set_params32\n", info->device_name));
- if (copy_from_user(&tmp_params, new_params, sizeof(struct MGSL_PARAMS32)))
- return -EFAULT;
-
- spin_lock_irqsave(&info->lock, flags);
- if (tmp_params.mode == MGSL_MODE_BASE_CLOCK) {
- info->base_clock = tmp_params.clock_speed;
- } else {
- info->params.mode = tmp_params.mode;
- info->params.loopback = tmp_params.loopback;
- info->params.flags = tmp_params.flags;
- info->params.encoding = tmp_params.encoding;
- info->params.clock_speed = tmp_params.clock_speed;
- info->params.addr_filter = tmp_params.addr_filter;
- info->params.crc_type = tmp_params.crc_type;
- info->params.preamble_length = tmp_params.preamble_length;
- info->params.preamble = tmp_params.preamble;
- info->params.data_rate = tmp_params.data_rate;
- info->params.data_bits = tmp_params.data_bits;
- info->params.stop_bits = tmp_params.stop_bits;
- info->params.parity = tmp_params.parity;
- }
- spin_unlock_irqrestore(&info->lock, flags);
-
- program_hw(info);
-
- return 0;
-}
-
-static long slgt_compat_ioctl(struct tty_struct *tty,
- unsigned int cmd, unsigned long arg)
-{
- struct slgt_info *info = tty->driver_data;
- int rc;
-
- if (sanity_check(info, tty->name, "compat_ioctl"))
- return -ENODEV;
- DBGINFO(("%s compat_ioctl() cmd=%08X\n", info->device_name, cmd));
-
- switch (cmd) {
- case MGSL_IOCSPARAMS32:
- rc = set_params32(info, compat_ptr(arg));
- break;
-
- case MGSL_IOCGPARAMS32:
- rc = get_params32(info, compat_ptr(arg));
- break;
-
- case MGSL_IOCGPARAMS:
- case MGSL_IOCSPARAMS:
- case MGSL_IOCGTXIDLE:
- case MGSL_IOCGSTATS:
- case MGSL_IOCWAITEVENT:
- case MGSL_IOCGIF:
- case MGSL_IOCSGPIO:
- case MGSL_IOCGGPIO:
- case MGSL_IOCWAITGPIO:
- case MGSL_IOCGXSYNC:
- case MGSL_IOCGXCTRL:
- rc = ioctl(tty, cmd, (unsigned long)compat_ptr(arg));
- break;
- default:
- rc = ioctl(tty, cmd, arg);
- }
- DBGINFO(("%s compat_ioctl() cmd=%08X rc=%d\n", info->device_name, cmd, rc));
- return rc;
-}
-#else
-#define slgt_compat_ioctl NULL
-#endif /* ifdef CONFIG_COMPAT */
-
-/*
- * proc fs support
- */
-static inline void line_info(struct seq_file *m, struct slgt_info *info)
-{
- char stat_buf[30];
- unsigned long flags;
-
- seq_printf(m, "%s: IO=%08X IRQ=%d MaxFrameSize=%u\n",
- info->device_name, info->phys_reg_addr,
- info->irq_level, info->max_frame_size);
-
- /* output current serial signal states */
- spin_lock_irqsave(&info->lock,flags);
- get_gtsignals(info);
- spin_unlock_irqrestore(&info->lock,flags);
-
- stat_buf[0] = 0;
- stat_buf[1] = 0;
- if (info->signals & SerialSignal_RTS)
- strcat(stat_buf, "|RTS");
- if (info->signals & SerialSignal_CTS)
- strcat(stat_buf, "|CTS");
- if (info->signals & SerialSignal_DTR)
- strcat(stat_buf, "|DTR");
- if (info->signals & SerialSignal_DSR)
- strcat(stat_buf, "|DSR");
- if (info->signals & SerialSignal_DCD)
- strcat(stat_buf, "|CD");
- if (info->signals & SerialSignal_RI)
- strcat(stat_buf, "|RI");
-
- if (info->params.mode != MGSL_MODE_ASYNC) {
- seq_printf(m, "\tHDLC txok:%d rxok:%d",
- info->icount.txok, info->icount.rxok);
- if (info->icount.txunder)
- seq_printf(m, " txunder:%d", info->icount.txunder);
- if (info->icount.txabort)
- seq_printf(m, " txabort:%d", info->icount.txabort);
- if (info->icount.rxshort)
- seq_printf(m, " rxshort:%d", info->icount.rxshort);
- if (info->icount.rxlong)
- seq_printf(m, " rxlong:%d", info->icount.rxlong);
- if (info->icount.rxover)
- seq_printf(m, " rxover:%d", info->icount.rxover);
- if (info->icount.rxcrc)
- seq_printf(m, " rxcrc:%d", info->icount.rxcrc);
- } else {
- seq_printf(m, "\tASYNC tx:%d rx:%d",
- info->icount.tx, info->icount.rx);
- if (info->icount.frame)
- seq_printf(m, " fe:%d", info->icount.frame);
- if (info->icount.parity)
- seq_printf(m, " pe:%d", info->icount.parity);
- if (info->icount.brk)
- seq_printf(m, " brk:%d", info->icount.brk);
- if (info->icount.overrun)
- seq_printf(m, " oe:%d", info->icount.overrun);
- }
-
- /* Append serial signal status to end */
- seq_printf(m, " %s\n", stat_buf+1);
-
- seq_printf(m, "\ttxactive=%d bh_req=%d bh_run=%d pending_bh=%x\n",
- info->tx_active,info->bh_requested,info->bh_running,
- info->pending_bh);
-}
-
-/* Called to print information about devices
- */
-static int synclink_gt_proc_show(struct seq_file *m, void *v)
-{
- struct slgt_info *info;
-
- seq_puts(m, "synclink_gt driver\n");
-
- info = slgt_device_list;
- while( info ) {
- line_info(m, info);
- info = info->next_device;
- }
- return 0;
-}
-
-/*
- * return count of bytes in transmit buffer
- */
-static unsigned int chars_in_buffer(struct tty_struct *tty)
-{
- struct slgt_info *info = tty->driver_data;
- unsigned int count;
- if (sanity_check(info, tty->name, "chars_in_buffer"))
- return 0;
- count = tbuf_bytes(info);
- DBGINFO(("%s chars_in_buffer()=%u\n", info->device_name, count));
- return count;
-}
-
-/*
- * signal remote device to throttle send data (our receive data)
- */
-static void throttle(struct tty_struct * tty)
-{
- struct slgt_info *info = tty->driver_data;
- unsigned long flags;
-
- if (sanity_check(info, tty->name, "throttle"))
- return;
- DBGINFO(("%s throttle\n", info->device_name));
- if (I_IXOFF(tty))
- send_xchar(tty, STOP_CHAR(tty));
- if (C_CRTSCTS(tty)) {
- spin_lock_irqsave(&info->lock,flags);
- info->signals &= ~SerialSignal_RTS;
- set_gtsignals(info);
- spin_unlock_irqrestore(&info->lock,flags);
- }
-}
-
-/*
- * signal remote device to stop throttling send data (our receive data)
- */
-static void unthrottle(struct tty_struct * tty)
-{
- struct slgt_info *info = tty->driver_data;
- unsigned long flags;
-
- if (sanity_check(info, tty->name, "unthrottle"))
- return;
- DBGINFO(("%s unthrottle\n", info->device_name));
- if (I_IXOFF(tty)) {
- if (info->x_char)
- info->x_char = 0;
- else
- send_xchar(tty, START_CHAR(tty));
- }
- if (C_CRTSCTS(tty)) {
- spin_lock_irqsave(&info->lock,flags);
- info->signals |= SerialSignal_RTS;
- set_gtsignals(info);
- spin_unlock_irqrestore(&info->lock,flags);
- }
-}
-
-/*
- * set or clear transmit break condition
- * break_state -1=set break condition, 0=clear
- */
-static int set_break(struct tty_struct *tty, int break_state)
-{
- struct slgt_info *info = tty->driver_data;
- unsigned short value;
- unsigned long flags;
-
- if (sanity_check(info, tty->name, "set_break"))
- return -EINVAL;
- DBGINFO(("%s set_break(%d)\n", info->device_name, break_state));
-
- spin_lock_irqsave(&info->lock,flags);
- value = rd_reg16(info, TCR);
- if (break_state == -1)
- value |= BIT6;
- else
- value &= ~BIT6;
- wr_reg16(info, TCR, value);
- spin_unlock_irqrestore(&info->lock,flags);
- return 0;
-}
-
-#if SYNCLINK_GENERIC_HDLC
-
-/**
- * hdlcdev_attach - called by generic HDLC layer when protocol selected (PPP, frame relay, etc.)
- * @dev: pointer to network device structure
- * @encoding: serial encoding setting
- * @parity: FCS setting
- *
- * Set encoding and frame check sequence (FCS) options.
- *
- * Return: 0 if success, otherwise error code
- */
-static int hdlcdev_attach(struct net_device *dev, unsigned short encoding,
- unsigned short parity)
-{
- struct slgt_info *info = dev_to_port(dev);
- unsigned char new_encoding;
- unsigned short new_crctype;
-
- /* return error if TTY interface open */
- if (info->port.count)
- return -EBUSY;
-
- DBGINFO(("%s hdlcdev_attach\n", info->device_name));
-
- switch (encoding)
- {
- case ENCODING_NRZ: new_encoding = HDLC_ENCODING_NRZ; break;
- case ENCODING_NRZI: new_encoding = HDLC_ENCODING_NRZI_SPACE; break;
- case ENCODING_FM_MARK: new_encoding = HDLC_ENCODING_BIPHASE_MARK; break;
- case ENCODING_FM_SPACE: new_encoding = HDLC_ENCODING_BIPHASE_SPACE; break;
- case ENCODING_MANCHESTER: new_encoding = HDLC_ENCODING_BIPHASE_LEVEL; break;
- default: return -EINVAL;
- }
-
- switch (parity)
- {
- case PARITY_NONE: new_crctype = HDLC_CRC_NONE; break;
- case PARITY_CRC16_PR1_CCITT: new_crctype = HDLC_CRC_16_CCITT; break;
- case PARITY_CRC32_PR1_CCITT: new_crctype = HDLC_CRC_32_CCITT; break;
- default: return -EINVAL;
- }
-
- info->params.encoding = new_encoding;
- info->params.crc_type = new_crctype;
-
- /* if network interface up, reprogram hardware */
- if (info->netcount)
- program_hw(info);
-
- return 0;
-}
-
-/**
- * hdlcdev_xmit - called by generic HDLC layer to send a frame
- * @skb: socket buffer containing HDLC frame
- * @dev: pointer to network device structure
- */
-static netdev_tx_t hdlcdev_xmit(struct sk_buff *skb,
- struct net_device *dev)
-{
- struct slgt_info *info = dev_to_port(dev);
- unsigned long flags;
-
- DBGINFO(("%s hdlc_xmit\n", dev->name));
-
- if (!skb->len)
- return NETDEV_TX_OK;
-
- /* stop sending until this frame completes */
- netif_stop_queue(dev);
-
- /* update network statistics */
- dev->stats.tx_packets++;
- dev->stats.tx_bytes += skb->len;
-
- /* save start time for transmit timeout detection */
- netif_trans_update(dev);
-
- spin_lock_irqsave(&info->lock, flags);
- tx_load(info, skb->data, skb->len);
- spin_unlock_irqrestore(&info->lock, flags);
-
- /* done with socket buffer, so free it */
- dev_kfree_skb(skb);
-
- return NETDEV_TX_OK;
-}
-
-/**
- * hdlcdev_open - called by network layer when interface enabled
- * @dev: pointer to network device structure
- *
- * Claim resources and initialize hardware.
- *
- * Return: 0 if success, otherwise error code
- */
-static int hdlcdev_open(struct net_device *dev)
-{
- struct slgt_info *info = dev_to_port(dev);
- int rc;
- unsigned long flags;
-
- DBGINFO(("%s hdlcdev_open\n", dev->name));
-
- /* arbitrate between network and tty opens */
- spin_lock_irqsave(&info->netlock, flags);
- if (info->port.count != 0 || info->netcount != 0) {
- DBGINFO(("%s hdlc_open busy\n", dev->name));
- spin_unlock_irqrestore(&info->netlock, flags);
- return -EBUSY;
- }
- info->netcount=1;
- spin_unlock_irqrestore(&info->netlock, flags);
-
- /* claim resources and init adapter */
- if ((rc = startup_hw(info)) != 0) {
- spin_lock_irqsave(&info->netlock, flags);
- info->netcount=0;
- spin_unlock_irqrestore(&info->netlock, flags);
- return rc;
- }
-
- /* generic HDLC layer open processing */
- rc = hdlc_open(dev);
- if (rc) {
- shutdown_hw(info);
- spin_lock_irqsave(&info->netlock, flags);
- info->netcount = 0;
- spin_unlock_irqrestore(&info->netlock, flags);
- return rc;
- }
-
- /* assert RTS and DTR, apply hardware settings */
- info->signals |= SerialSignal_RTS | SerialSignal_DTR;
- program_hw(info);
-
- /* enable network layer transmit */
- netif_trans_update(dev);
- netif_start_queue(dev);
-
- /* inform generic HDLC layer of current DCD status */
- spin_lock_irqsave(&info->lock, flags);
- get_gtsignals(info);
- spin_unlock_irqrestore(&info->lock, flags);
- if (info->signals & SerialSignal_DCD)
- netif_carrier_on(dev);
- else
- netif_carrier_off(dev);
- return 0;
-}
-
-/**
- * hdlcdev_close - called by network layer when interface is disabled
- * @dev: pointer to network device structure
- *
- * Shutdown hardware and release resources.
- *
- * Return: 0 if success, otherwise error code
- */
-static int hdlcdev_close(struct net_device *dev)
-{
- struct slgt_info *info = dev_to_port(dev);
- unsigned long flags;
-
- DBGINFO(("%s hdlcdev_close\n", dev->name));
-
- netif_stop_queue(dev);
-
- /* shutdown adapter and release resources */
- shutdown_hw(info);
-
- hdlc_close(dev);
-
- spin_lock_irqsave(&info->netlock, flags);
- info->netcount=0;
- spin_unlock_irqrestore(&info->netlock, flags);
-
- return 0;
-}
-
-/**
- * hdlcdev_ioctl - called by network layer to process IOCTL call to network device
- * @dev: pointer to network device structure
- * @ifr: pointer to network interface request structure
- * @cmd: IOCTL command code
- *
- * Return: 0 if success, otherwise error code
- */
-static int hdlcdev_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;
- struct slgt_info *info = dev_to_port(dev);
- unsigned int flags;
-
- DBGINFO(("%s hdlcdev_ioctl\n", dev->name));
-
- /* return error if TTY interface open */
- if (info->port.count)
- return -EBUSY;
-
- memset(&new_line, 0, sizeof(new_line));
-
- switch (ifs->type) {
- case IF_GET_IFACE: /* return current sync_serial_settings */
-
- ifs->type = IF_IFACE_SYNC_SERIAL;
- if (ifs->size < size) {
- ifs->size = size; /* data size wanted */
- return -ENOBUFS;
- }
-
- flags = info->params.flags & (HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_RXC_DPLL |
- HDLC_FLAG_RXC_BRG | HDLC_FLAG_RXC_TXCPIN |
- HDLC_FLAG_TXC_TXCPIN | HDLC_FLAG_TXC_DPLL |
- HDLC_FLAG_TXC_BRG | HDLC_FLAG_TXC_RXCPIN);
-
- switch (flags){
- case (HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_TXCPIN): new_line.clock_type = CLOCK_EXT; break;
- case (HDLC_FLAG_RXC_BRG | HDLC_FLAG_TXC_BRG): new_line.clock_type = CLOCK_INT; break;
- case (HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_BRG): new_line.clock_type = CLOCK_TXINT; break;
- case (HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_RXCPIN): new_line.clock_type = CLOCK_TXFROMRX; break;
- default: new_line.clock_type = CLOCK_DEFAULT;
- }
-
- new_line.clock_rate = info->params.clock_speed;
- new_line.loopback = info->params.loopback ? 1:0;
-
- if (copy_to_user(line, &new_line, size))
- return -EFAULT;
- return 0;
-
- case IF_IFACE_SYNC_SERIAL: /* set sync_serial_settings */
-
- if(!capable(CAP_NET_ADMIN))
- return -EPERM;
- if (copy_from_user(&new_line, line, size))
- return -EFAULT;
-
- switch (new_line.clock_type)
- {
- case CLOCK_EXT: flags = HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_TXCPIN; break;
- case CLOCK_TXFROMRX: flags = HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_RXCPIN; break;
- case CLOCK_INT: flags = HDLC_FLAG_RXC_BRG | HDLC_FLAG_TXC_BRG; break;
- case CLOCK_TXINT: flags = HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_BRG; break;
- case CLOCK_DEFAULT: flags = info->params.flags &
- (HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_RXC_DPLL |
- HDLC_FLAG_RXC_BRG | HDLC_FLAG_RXC_TXCPIN |
- HDLC_FLAG_TXC_TXCPIN | HDLC_FLAG_TXC_DPLL |
- HDLC_FLAG_TXC_BRG | HDLC_FLAG_TXC_RXCPIN); break;
- default: return -EINVAL;
- }
-
- if (new_line.loopback != 0 && new_line.loopback != 1)
- return -EINVAL;
-
- info->params.flags &= ~(HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_RXC_DPLL |
- HDLC_FLAG_RXC_BRG | HDLC_FLAG_RXC_TXCPIN |
- HDLC_FLAG_TXC_TXCPIN | HDLC_FLAG_TXC_DPLL |
- HDLC_FLAG_TXC_BRG | HDLC_FLAG_TXC_RXCPIN);
- info->params.flags |= flags;
-
- info->params.loopback = new_line.loopback;
-
- if (flags & (HDLC_FLAG_RXC_BRG | HDLC_FLAG_TXC_BRG))
- info->params.clock_speed = new_line.clock_rate;
- else
- info->params.clock_speed = 0;
-
- /* if network interface up, reprogram hardware */
- if (info->netcount)
- program_hw(info);
- return 0;
-
- default:
- return hdlc_ioctl(dev, ifs);
- }
-}
-
-/**
- * hdlcdev_tx_timeout - called by network layer when transmit timeout is detected
- * @dev: pointer to network device structure
- * @txqueue: unused
- */
-static void hdlcdev_tx_timeout(struct net_device *dev, unsigned int txqueue)
-{
- struct slgt_info *info = dev_to_port(dev);
- unsigned long flags;
-
- DBGINFO(("%s hdlcdev_tx_timeout\n", dev->name));
-
- dev->stats.tx_errors++;
- dev->stats.tx_aborted_errors++;
-
- spin_lock_irqsave(&info->lock,flags);
- tx_stop(info);
- spin_unlock_irqrestore(&info->lock,flags);
-
- netif_wake_queue(dev);
-}
-
-/**
- * hdlcdev_tx_done - called by device driver when transmit completes
- * @info: pointer to device instance information
- *
- * Reenable network layer transmit if stopped.
- */
-static void hdlcdev_tx_done(struct slgt_info *info)
-{
- if (netif_queue_stopped(info->netdev))
- netif_wake_queue(info->netdev);
-}
-
-/**
- * hdlcdev_rx - called by device driver when frame received
- * @info: pointer to device instance information
- * @buf: pointer to buffer contianing frame data
- * @size: count of data bytes in buf
- *
- * Pass frame to network layer.
- */
-static void hdlcdev_rx(struct slgt_info *info, char *buf, int size)
-{
- struct sk_buff *skb = dev_alloc_skb(size);
- struct net_device *dev = info->netdev;
-
- DBGINFO(("%s hdlcdev_rx\n", dev->name));
-
- if (skb == NULL) {
- DBGERR(("%s: can't alloc skb, drop packet\n", dev->name));
- dev->stats.rx_dropped++;
- return;
- }
-
- skb_put_data(skb, buf, size);
-
- skb->protocol = hdlc_type_trans(skb, dev);
-
- dev->stats.rx_packets++;
- dev->stats.rx_bytes += size;
-
- netif_rx(skb);
-}
-
-static const struct net_device_ops hdlcdev_ops = {
- .ndo_open = hdlcdev_open,
- .ndo_stop = hdlcdev_close,
- .ndo_start_xmit = hdlc_start_xmit,
- .ndo_siocwandev = hdlcdev_ioctl,
- .ndo_tx_timeout = hdlcdev_tx_timeout,
-};
-
-/**
- * hdlcdev_init - called by device driver when adding device instance
- * @info: pointer to device instance information
- *
- * Do generic HDLC initialization.
- *
- * Return: 0 if success, otherwise error code
- */
-static int hdlcdev_init(struct slgt_info *info)
-{
- int rc;
- struct net_device *dev;
- hdlc_device *hdlc;
-
- /* allocate and initialize network and HDLC layer objects */
-
- dev = alloc_hdlcdev(info);
- if (!dev) {
- printk(KERN_ERR "%s hdlc device alloc failure\n", info->device_name);
- return -ENOMEM;
- }
-
- /* for network layer reporting purposes only */
- dev->mem_start = info->phys_reg_addr;
- dev->mem_end = info->phys_reg_addr + SLGT_REG_SIZE - 1;
- dev->irq = info->irq_level;
-
- /* network layer callbacks and settings */
- dev->netdev_ops = &hdlcdev_ops;
- dev->watchdog_timeo = 10 * HZ;
- dev->tx_queue_len = 50;
-
- /* generic HDLC layer callbacks and settings */
- hdlc = dev_to_hdlc(dev);
- hdlc->attach = hdlcdev_attach;
- hdlc->xmit = hdlcdev_xmit;
-
- /* register objects with HDLC layer */
- rc = register_hdlc_device(dev);
- if (rc) {
- printk(KERN_WARNING "%s:unable to register hdlc device\n",__FILE__);
- free_netdev(dev);
- return rc;
- }
-
- info->netdev = dev;
- return 0;
-}
-
-/**
- * hdlcdev_exit - called by device driver when removing device instance
- * @info: pointer to device instance information
- *
- * Do generic HDLC cleanup.
- */
-static void hdlcdev_exit(struct slgt_info *info)
-{
- if (!info->netdev)
- return;
- unregister_hdlc_device(info->netdev);
- free_netdev(info->netdev);
- info->netdev = NULL;
-}
-
-#endif /* ifdef CONFIG_HDLC */
-
-/*
- * get async data from rx DMA buffers
- */
-static void rx_async(struct slgt_info *info)
-{
- struct mgsl_icount *icount = &info->icount;
- unsigned int start, end;
- unsigned char *p;
- unsigned char status;
- struct slgt_desc *bufs = info->rbufs;
- int i, count;
- int chars = 0;
- int stat;
- unsigned char ch;
-
- start = end = info->rbuf_current;
-
- while(desc_complete(bufs[end])) {
- count = desc_count(bufs[end]) - info->rbuf_index;
- p = bufs[end].buf + info->rbuf_index;
-
- DBGISR(("%s rx_async count=%d\n", info->device_name, count));
- DBGDATA(info, p, count, "rx");
-
- for(i=0 ; i < count; i+=2, p+=2) {
- ch = *p;
- icount->rx++;
-
- stat = 0;
-
- status = *(p + 1) & (BIT1 + BIT0);
- if (status) {
- if (status & BIT1)
- icount->parity++;
- else if (status & BIT0)
- icount->frame++;
- /* discard char if tty control flags say so */
- if (status & info->ignore_status_mask)
- continue;
- if (status & BIT1)
- stat = TTY_PARITY;
- else if (status & BIT0)
- stat = TTY_FRAME;
- }
- tty_insert_flip_char(&info->port, ch, stat);
- chars++;
- }
-
- if (i < count) {
- /* receive buffer not completed */
- info->rbuf_index += i;
- mod_timer(&info->rx_timer, jiffies + 1);
- break;
- }
-
- info->rbuf_index = 0;
- free_rbufs(info, end, end);
-
- if (++end == info->rbuf_count)
- end = 0;
-
- /* if entire list searched then no frame available */
- if (end == start)
- break;
- }
-
- if (chars)
- tty_flip_buffer_push(&info->port);
-}
-
-/*
- * return next bottom half action to perform
- */
-static int bh_action(struct slgt_info *info)
-{
- unsigned long flags;
- int rc;
-
- spin_lock_irqsave(&info->lock,flags);
-
- if (info->pending_bh & BH_RECEIVE) {
- info->pending_bh &= ~BH_RECEIVE;
- rc = BH_RECEIVE;
- } else if (info->pending_bh & BH_TRANSMIT) {
- info->pending_bh &= ~BH_TRANSMIT;
- rc = BH_TRANSMIT;
- } else if (info->pending_bh & BH_STATUS) {
- info->pending_bh &= ~BH_STATUS;
- rc = BH_STATUS;
- } else {
- /* Mark BH routine as complete */
- info->bh_running = false;
- info->bh_requested = false;
- rc = 0;
- }
-
- spin_unlock_irqrestore(&info->lock,flags);
-
- return rc;
-}
-
-/*
- * perform bottom half processing
- */
-static void bh_handler(struct work_struct *work)
-{
- struct slgt_info *info = container_of(work, struct slgt_info, task);
- int action;
-
- info->bh_running = true;
-
- while((action = bh_action(info))) {
- switch (action) {
- case BH_RECEIVE:
- DBGBH(("%s bh receive\n", info->device_name));
- switch(info->params.mode) {
- case MGSL_MODE_ASYNC:
- rx_async(info);
- break;
- case MGSL_MODE_HDLC:
- while(rx_get_frame(info));
- break;
- case MGSL_MODE_RAW:
- case MGSL_MODE_MONOSYNC:
- case MGSL_MODE_BISYNC:
- case MGSL_MODE_XSYNC:
- while(rx_get_buf(info));
- break;
- }
- /* restart receiver if rx DMA buffers exhausted */
- if (info->rx_restart)
- rx_start(info);
- break;
- case BH_TRANSMIT:
- bh_transmit(info);
- break;
- case BH_STATUS:
- DBGBH(("%s bh status\n", info->device_name));
- info->ri_chkcount = 0;
- info->dsr_chkcount = 0;
- info->dcd_chkcount = 0;
- info->cts_chkcount = 0;
- break;
- default:
- DBGBH(("%s unknown action\n", info->device_name));
- break;
- }
- }
- DBGBH(("%s bh_handler exit\n", info->device_name));
-}
-
-static void bh_transmit(struct slgt_info *info)
-{
- struct tty_struct *tty = info->port.tty;
-
- DBGBH(("%s bh_transmit\n", info->device_name));
- if (tty)
- tty_wakeup(tty);
-}
-
-static void dsr_change(struct slgt_info *info, unsigned short status)
-{
- if (status & BIT3) {
- info->signals |= SerialSignal_DSR;
- info->input_signal_events.dsr_up++;
- } else {
- info->signals &= ~SerialSignal_DSR;
- info->input_signal_events.dsr_down++;
- }
- DBGISR(("dsr_change %s signals=%04X\n", info->device_name, info->signals));
- if ((info->dsr_chkcount)++ == IO_PIN_SHUTDOWN_LIMIT) {
- slgt_irq_off(info, IRQ_DSR);
- return;
- }
- info->icount.dsr++;
- wake_up_interruptible(&info->status_event_wait_q);
- wake_up_interruptible(&info->event_wait_q);
- info->pending_bh |= BH_STATUS;
-}
-
-static void cts_change(struct slgt_info *info, unsigned short status)
-{
- if (status & BIT2) {
- info->signals |= SerialSignal_CTS;
- info->input_signal_events.cts_up++;
- } else {
- info->signals &= ~SerialSignal_CTS;
- info->input_signal_events.cts_down++;
- }
- DBGISR(("cts_change %s signals=%04X\n", info->device_name, info->signals));
- if ((info->cts_chkcount)++ == IO_PIN_SHUTDOWN_LIMIT) {
- slgt_irq_off(info, IRQ_CTS);
- return;
- }
- info->icount.cts++;
- wake_up_interruptible(&info->status_event_wait_q);
- wake_up_interruptible(&info->event_wait_q);
- info->pending_bh |= BH_STATUS;
-
- if (tty_port_cts_enabled(&info->port)) {
- if (info->port.tty) {
- if (info->port.tty->hw_stopped) {
- if (info->signals & SerialSignal_CTS) {
- info->port.tty->hw_stopped = false;
- info->pending_bh |= BH_TRANSMIT;
- return;
- }
- } else {
- if (!(info->signals & SerialSignal_CTS))
- info->port.tty->hw_stopped = true;
- }
- }
- }
-}
-
-static void dcd_change(struct slgt_info *info, unsigned short status)
-{
- if (status & BIT1) {
- info->signals |= SerialSignal_DCD;
- info->input_signal_events.dcd_up++;
- } else {
- info->signals &= ~SerialSignal_DCD;
- info->input_signal_events.dcd_down++;
- }
- DBGISR(("dcd_change %s signals=%04X\n", info->device_name, info->signals));
- if ((info->dcd_chkcount)++ == IO_PIN_SHUTDOWN_LIMIT) {
- slgt_irq_off(info, IRQ_DCD);
- return;
- }
- info->icount.dcd++;
-#if SYNCLINK_GENERIC_HDLC
- if (info->netcount) {
- if (info->signals & SerialSignal_DCD)
- netif_carrier_on(info->netdev);
- else
- netif_carrier_off(info->netdev);
- }
-#endif
- wake_up_interruptible(&info->status_event_wait_q);
- wake_up_interruptible(&info->event_wait_q);
- info->pending_bh |= BH_STATUS;
-
- if (tty_port_check_carrier(&info->port)) {
- if (info->signals & SerialSignal_DCD)
- wake_up_interruptible(&info->port.open_wait);
- else {
- if (info->port.tty)
- tty_hangup(info->port.tty);
- }
- }
-}
-
-static void ri_change(struct slgt_info *info, unsigned short status)
-{
- if (status & BIT0) {
- info->signals |= SerialSignal_RI;
- info->input_signal_events.ri_up++;
- } else {
- info->signals &= ~SerialSignal_RI;
- info->input_signal_events.ri_down++;
- }
- DBGISR(("ri_change %s signals=%04X\n", info->device_name, info->signals));
- if ((info->ri_chkcount)++ == IO_PIN_SHUTDOWN_LIMIT) {
- slgt_irq_off(info, IRQ_RI);
- return;
- }
- info->icount.rng++;
- wake_up_interruptible(&info->status_event_wait_q);
- wake_up_interruptible(&info->event_wait_q);
- info->pending_bh |= BH_STATUS;
-}
-
-static void isr_rxdata(struct slgt_info *info)
-{
- unsigned int count = info->rbuf_fill_count;
- unsigned int i = info->rbuf_fill_index;
- unsigned short reg;
-
- while (rd_reg16(info, SSR) & IRQ_RXDATA) {
- reg = rd_reg16(info, RDR);
- DBGISR(("isr_rxdata %s RDR=%04X\n", info->device_name, reg));
- if (desc_complete(info->rbufs[i])) {
- /* all buffers full */
- rx_stop(info);
- info->rx_restart = true;
- continue;
- }
- info->rbufs[i].buf[count++] = (unsigned char)reg;
- /* async mode saves status byte to buffer for each data byte */
- if (info->params.mode == MGSL_MODE_ASYNC)
- info->rbufs[i].buf[count++] = (unsigned char)(reg >> 8);
- if (count == info->rbuf_fill_level || (reg & BIT10)) {
- /* buffer full or end of frame */
- set_desc_count(info->rbufs[i], count);
- set_desc_status(info->rbufs[i], BIT15 | (reg >> 8));
- info->rbuf_fill_count = count = 0;
- if (++i == info->rbuf_count)
- i = 0;
- info->pending_bh |= BH_RECEIVE;
- }
- }
-
- info->rbuf_fill_index = i;
- info->rbuf_fill_count = count;
-}
-
-static void isr_serial(struct slgt_info *info)
-{
- unsigned short status = rd_reg16(info, SSR);
-
- DBGISR(("%s isr_serial status=%04X\n", info->device_name, status));
-
- wr_reg16(info, SSR, status); /* clear pending */
-
- info->irq_occurred = true;
-
- if (info->params.mode == MGSL_MODE_ASYNC) {
- if (status & IRQ_TXIDLE) {
- if (info->tx_active)
- isr_txeom(info, status);
- }
- if (info->rx_pio && (status & IRQ_RXDATA))
- isr_rxdata(info);
- if ((status & IRQ_RXBREAK) && (status & RXBREAK)) {
- info->icount.brk++;
- /* process break detection if tty control allows */
- if (info->port.tty) {
- if (!(status & info->ignore_status_mask)) {
- if (info->read_status_mask & MASK_BREAK) {
- tty_insert_flip_char(&info->port, 0, TTY_BREAK);
- if (info->port.flags & ASYNC_SAK)
- do_SAK(info->port.tty);
- }
- }
- }
- }
- } else {
- if (status & (IRQ_TXIDLE + IRQ_TXUNDER))
- isr_txeom(info, status);
- if (info->rx_pio && (status & IRQ_RXDATA))
- isr_rxdata(info);
- if (status & IRQ_RXIDLE) {
- if (status & RXIDLE)
- info->icount.rxidle++;
- else
- info->icount.exithunt++;
- wake_up_interruptible(&info->event_wait_q);
- }
-
- if (status & IRQ_RXOVER)
- rx_start(info);
- }
-
- if (status & IRQ_DSR)
- dsr_change(info, status);
- if (status & IRQ_CTS)
- cts_change(info, status);
- if (status & IRQ_DCD)
- dcd_change(info, status);
- if (status & IRQ_RI)
- ri_change(info, status);
-}
-
-static void isr_rdma(struct slgt_info *info)
-{
- unsigned int status = rd_reg32(info, RDCSR);
-
- DBGISR(("%s isr_rdma status=%08x\n", info->device_name, status));
-
- /* RDCSR (rx DMA control/status)
- *
- * 31..07 reserved
- * 06 save status byte to DMA buffer
- * 05 error
- * 04 eol (end of list)
- * 03 eob (end of buffer)
- * 02 IRQ enable
- * 01 reset
- * 00 enable
- */
- wr_reg32(info, RDCSR, status); /* clear pending */
-
- if (status & (BIT5 + BIT4)) {
- DBGISR(("%s isr_rdma rx_restart=1\n", info->device_name));
- info->rx_restart = true;
- }
- info->pending_bh |= BH_RECEIVE;
-}
-
-static void isr_tdma(struct slgt_info *info)
-{
- unsigned int status = rd_reg32(info, TDCSR);
-
- DBGISR(("%s isr_tdma status=%08x\n", info->device_name, status));
-
- /* TDCSR (tx DMA control/status)
- *
- * 31..06 reserved
- * 05 error
- * 04 eol (end of list)
- * 03 eob (end of buffer)
- * 02 IRQ enable
- * 01 reset
- * 00 enable
- */
- wr_reg32(info, TDCSR, status); /* clear pending */
-
- if (status & (BIT5 + BIT4 + BIT3)) {
- // another transmit buffer has completed
- // run bottom half to get more send data from user
- info->pending_bh |= BH_TRANSMIT;
- }
-}
-
-/*
- * return true if there are unsent tx DMA buffers, otherwise false
- *
- * if there are unsent buffers then info->tbuf_start
- * is set to index of first unsent buffer
- */
-static bool unsent_tbufs(struct slgt_info *info)
-{
- unsigned int i = info->tbuf_current;
- bool rc = false;
-
- /*
- * search backwards from last loaded buffer (precedes tbuf_current)
- * for first unsent buffer (desc_count > 0)
- */
-
- do {
- if (i)
- i--;
- else
- i = info->tbuf_count - 1;
- if (!desc_count(info->tbufs[i]))
- break;
- info->tbuf_start = i;
- rc = true;
- } while (i != info->tbuf_current);
-
- return rc;
-}
-
-static void isr_txeom(struct slgt_info *info, unsigned short status)
-{
- DBGISR(("%s txeom status=%04x\n", info->device_name, status));
-
- slgt_irq_off(info, IRQ_TXDATA + IRQ_TXIDLE + IRQ_TXUNDER);
- tdma_reset(info);
- if (status & IRQ_TXUNDER) {
- unsigned short val = rd_reg16(info, TCR);
- wr_reg16(info, TCR, (unsigned short)(val | BIT2)); /* set reset bit */
- wr_reg16(info, TCR, val); /* clear reset bit */
- }
-
- if (info->tx_active) {
- if (info->params.mode != MGSL_MODE_ASYNC) {
- if (status & IRQ_TXUNDER)
- info->icount.txunder++;
- else if (status & IRQ_TXIDLE)
- info->icount.txok++;
- }
-
- if (unsent_tbufs(info)) {
- tx_start(info);
- update_tx_timer(info);
- return;
- }
- info->tx_active = false;
-
- timer_delete(&info->tx_timer);
-
- if (info->params.mode != MGSL_MODE_ASYNC && info->drop_rts_on_tx_done) {
- info->signals &= ~SerialSignal_RTS;
- info->drop_rts_on_tx_done = false;
- set_gtsignals(info);
- }
-
-#if SYNCLINK_GENERIC_HDLC
- if (info->netcount)
- hdlcdev_tx_done(info);
- else
-#endif
- {
- if (info->port.tty && (info->port.tty->flow.stopped || info->port.tty->hw_stopped)) {
- tx_stop(info);
- return;
- }
- info->pending_bh |= BH_TRANSMIT;
- }
- }
-}
-
-static void isr_gpio(struct slgt_info *info, unsigned int changed, unsigned int state)
-{
- struct cond_wait *w, *prev;
-
- /* wake processes waiting for specific transitions */
- for (w = info->gpio_wait_q, prev = NULL ; w != NULL ; w = w->next) {
- if (w->data & changed) {
- w->data = state;
- wake_up_interruptible(&w->q);
- if (prev != NULL)
- prev->next = w->next;
- else
- info->gpio_wait_q = w->next;
- } else
- prev = w;
- }
-}
-
-/* interrupt service routine
- *
- * irq interrupt number
- * dev_id device ID supplied during interrupt registration
- */
-static irqreturn_t slgt_interrupt(int dummy, void *dev_id)
-{
- struct slgt_info *info = dev_id;
- unsigned int gsr;
- unsigned int i;
-
- DBGISR(("slgt_interrupt irq=%d entry\n", info->irq_level));
-
- while((gsr = rd_reg32(info, GSR) & 0xffffff00)) {
- DBGISR(("%s gsr=%08x\n", info->device_name, gsr));
- info->irq_occurred = true;
- for(i=0; i < info->port_count ; i++) {
- if (info->port_array[i] == NULL)
- continue;
- spin_lock(&info->port_array[i]->lock);
- if (gsr & (BIT8 << i))
- isr_serial(info->port_array[i]);
- if (gsr & (BIT16 << (i*2)))
- isr_rdma(info->port_array[i]);
- if (gsr & (BIT17 << (i*2)))
- isr_tdma(info->port_array[i]);
- spin_unlock(&info->port_array[i]->lock);
- }
- }
-
- if (info->gpio_present) {
- unsigned int state;
- unsigned int changed;
- spin_lock(&info->lock);
- while ((changed = rd_reg32(info, IOSR)) != 0) {
- DBGISR(("%s iosr=%08x\n", info->device_name, changed));
- /* read latched state of GPIO signals */
- state = rd_reg32(info, IOVR);
- /* clear pending GPIO interrupt bits */
- wr_reg32(info, IOSR, changed);
- for (i=0 ; i < info->port_count ; i++) {
- if (info->port_array[i] != NULL)
- isr_gpio(info->port_array[i], changed, state);
- }
- }
- spin_unlock(&info->lock);
- }
-
- for(i=0; i < info->port_count ; i++) {
- struct slgt_info *port = info->port_array[i];
- if (port == NULL)
- continue;
- spin_lock(&port->lock);
- if ((port->port.count || port->netcount) &&
- port->pending_bh && !port->bh_running &&
- !port->bh_requested) {
- DBGISR(("%s bh queued\n", port->device_name));
- schedule_work(&port->task);
- port->bh_requested = true;
- }
- spin_unlock(&port->lock);
- }
-
- DBGISR(("slgt_interrupt irq=%d exit\n", info->irq_level));
- return IRQ_HANDLED;
-}
-
-static int startup_hw(struct slgt_info *info)
-{
- DBGINFO(("%s startup\n", info->device_name));
-
- if (tty_port_initialized(&info->port))
- return 0;
-
- if (!info->tx_buf) {
- info->tx_buf = kmalloc(info->max_frame_size, GFP_KERNEL);
- if (!info->tx_buf) {
- DBGERR(("%s can't allocate tx buffer\n", info->device_name));
- return -ENOMEM;
- }
- }
-
- info->pending_bh = 0;
-
- memset(&info->icount, 0, sizeof(info->icount));
-
- /* program hardware for current parameters */
- change_params(info);
-
- if (info->port.tty)
- clear_bit(TTY_IO_ERROR, &info->port.tty->flags);
-
- tty_port_set_initialized(&info->port, true);
-
- return 0;
-}
-
-/*
- * called by close() and hangup() to shutdown hardware
- */
-static void shutdown_hw(struct slgt_info *info)
-{
- unsigned long flags;
-
- if (!tty_port_initialized(&info->port))
- return;
-
- DBGINFO(("%s shutdown\n", info->device_name));
-
- /* clear status wait queue because status changes */
- /* can't happen after shutting down the hardware */
- wake_up_interruptible(&info->status_event_wait_q);
- wake_up_interruptible(&info->event_wait_q);
-
- timer_delete_sync(&info->tx_timer);
- timer_delete_sync(&info->rx_timer);
-
- kfree(info->tx_buf);
- info->tx_buf = NULL;
-
- spin_lock_irqsave(&info->lock,flags);
-
- tx_stop(info);
- rx_stop(info);
-
- slgt_irq_off(info, IRQ_ALL | IRQ_MASTER);
-
- if (!info->port.tty || info->port.tty->termios.c_cflag & HUPCL) {
- info->signals &= ~(SerialSignal_RTS | SerialSignal_DTR);
- set_gtsignals(info);
- }
-
- flush_cond_wait(&info->gpio_wait_q);
-
- spin_unlock_irqrestore(&info->lock,flags);
-
- if (info->port.tty)
- set_bit(TTY_IO_ERROR, &info->port.tty->flags);
-
- tty_port_set_initialized(&info->port, false);
-}
-
-static void program_hw(struct slgt_info *info)
-{
- unsigned long flags;
-
- spin_lock_irqsave(&info->lock,flags);
-
- rx_stop(info);
- tx_stop(info);
-
- if (info->params.mode != MGSL_MODE_ASYNC ||
- info->netcount)
- sync_mode(info);
- else
- async_mode(info);
-
- set_gtsignals(info);
-
- info->dcd_chkcount = 0;
- info->cts_chkcount = 0;
- info->ri_chkcount = 0;
- info->dsr_chkcount = 0;
-
- slgt_irq_on(info, IRQ_DCD | IRQ_CTS | IRQ_DSR | IRQ_RI);
- get_gtsignals(info);
-
- if (info->netcount ||
- (info->port.tty && info->port.tty->termios.c_cflag & CREAD))
- rx_start(info);
-
- spin_unlock_irqrestore(&info->lock,flags);
-}
-
-/*
- * reconfigure adapter based on new parameters
- */
-static void change_params(struct slgt_info *info)
-{
- unsigned cflag;
- int bits_per_char;
-
- if (!info->port.tty)
- return;
- DBGINFO(("%s change_params\n", info->device_name));
-
- cflag = info->port.tty->termios.c_cflag;
-
- /* if B0 rate (hangup) specified then negate RTS and DTR */
- /* otherwise assert RTS and DTR */
- if (cflag & CBAUD)
- info->signals |= SerialSignal_RTS | SerialSignal_DTR;
- else
- info->signals &= ~(SerialSignal_RTS | SerialSignal_DTR);
-
- /* byte size and parity */
-
- info->params.data_bits = tty_get_char_size(cflag);
- info->params.stop_bits = (cflag & CSTOPB) ? 2 : 1;
-
- if (cflag & PARENB)
- info->params.parity = (cflag & PARODD) ? ASYNC_PARITY_ODD : ASYNC_PARITY_EVEN;
- else
- info->params.parity = ASYNC_PARITY_NONE;
-
- /* calculate number of jiffies to transmit a full
- * FIFO (32 bytes) at specified data rate
- */
- bits_per_char = info->params.data_bits +
- info->params.stop_bits + 1;
-
- info->params.data_rate = tty_get_baud_rate(info->port.tty);
-
- if (info->params.data_rate) {
- info->timeout = (32*HZ*bits_per_char) /
- info->params.data_rate;
- }
- info->timeout += HZ/50; /* Add .02 seconds of slop */
-
- tty_port_set_cts_flow(&info->port, cflag & CRTSCTS);
- tty_port_set_check_carrier(&info->port, ~cflag & CLOCAL);
-
- /* process tty input control flags */
-
- info->read_status_mask = IRQ_RXOVER;
- if (I_INPCK(info->port.tty))
- info->read_status_mask |= MASK_PARITY | MASK_FRAMING;
- if (I_BRKINT(info->port.tty) || I_PARMRK(info->port.tty))
- info->read_status_mask |= MASK_BREAK;
- if (I_IGNPAR(info->port.tty))
- info->ignore_status_mask |= MASK_PARITY | MASK_FRAMING;
- if (I_IGNBRK(info->port.tty)) {
- info->ignore_status_mask |= MASK_BREAK;
- /* If ignoring parity and break indicators, ignore
- * overruns too. (For real raw support).
- */
- if (I_IGNPAR(info->port.tty))
- info->ignore_status_mask |= MASK_OVERRUN;
- }
-
- program_hw(info);
-}
-
-static int get_stats(struct slgt_info *info, struct mgsl_icount __user *user_icount)
-{
- DBGINFO(("%s get_stats\n", info->device_name));
- if (!user_icount) {
- memset(&info->icount, 0, sizeof(info->icount));
- } else {
- if (copy_to_user(user_icount, &info->icount, sizeof(struct mgsl_icount)))
- return -EFAULT;
- }
- return 0;
-}
-
-static int get_params(struct slgt_info *info, MGSL_PARAMS __user *user_params)
-{
- DBGINFO(("%s get_params\n", info->device_name));
- if (copy_to_user(user_params, &info->params, sizeof(MGSL_PARAMS)))
- return -EFAULT;
- return 0;
-}
-
-static int set_params(struct slgt_info *info, MGSL_PARAMS __user *new_params)
-{
- unsigned long flags;
- MGSL_PARAMS tmp_params;
-
- DBGINFO(("%s set_params\n", info->device_name));
- if (copy_from_user(&tmp_params, new_params, sizeof(MGSL_PARAMS)))
- return -EFAULT;
-
- spin_lock_irqsave(&info->lock, flags);
- if (tmp_params.mode == MGSL_MODE_BASE_CLOCK)
- info->base_clock = tmp_params.clock_speed;
- else
- memcpy(&info->params, &tmp_params, sizeof(MGSL_PARAMS));
- spin_unlock_irqrestore(&info->lock, flags);
-
- program_hw(info);
-
- return 0;
-}
-
-static int get_txidle(struct slgt_info *info, int __user *idle_mode)
-{
- DBGINFO(("%s get_txidle=%d\n", info->device_name, info->idle_mode));
- if (put_user(info->idle_mode, idle_mode))
- return -EFAULT;
- return 0;
-}
-
-static int set_txidle(struct slgt_info *info, int idle_mode)
-{
- unsigned long flags;
- DBGINFO(("%s set_txidle(%d)\n", info->device_name, idle_mode));
- spin_lock_irqsave(&info->lock,flags);
- info->idle_mode = idle_mode;
- if (info->params.mode != MGSL_MODE_ASYNC)
- tx_set_idle(info);
- spin_unlock_irqrestore(&info->lock,flags);
- return 0;
-}
-
-static int tx_enable(struct slgt_info *info, int enable)
-{
- unsigned long flags;
- DBGINFO(("%s tx_enable(%d)\n", info->device_name, enable));
- spin_lock_irqsave(&info->lock,flags);
- if (enable) {
- if (!info->tx_enabled)
- tx_start(info);
- } else {
- if (info->tx_enabled)
- tx_stop(info);
- }
- spin_unlock_irqrestore(&info->lock,flags);
- return 0;
-}
-
-/*
- * abort transmit HDLC frame
- */
-static int tx_abort(struct slgt_info *info)
-{
- unsigned long flags;
- DBGINFO(("%s tx_abort\n", info->device_name));
- spin_lock_irqsave(&info->lock,flags);
- tdma_reset(info);
- spin_unlock_irqrestore(&info->lock,flags);
- return 0;
-}
-
-static int rx_enable(struct slgt_info *info, int enable)
-{
- unsigned long flags;
- unsigned int rbuf_fill_level;
- DBGINFO(("%s rx_enable(%08x)\n", info->device_name, enable));
- spin_lock_irqsave(&info->lock,flags);
- /*
- * enable[31..16] = receive DMA buffer fill level
- * 0 = noop (leave fill level unchanged)
- * fill level must be multiple of 4 and <= buffer size
- */
- rbuf_fill_level = ((unsigned int)enable) >> 16;
- if (rbuf_fill_level) {
- if ((rbuf_fill_level > DMABUFSIZE) || (rbuf_fill_level % 4)) {
- spin_unlock_irqrestore(&info->lock, flags);
- return -EINVAL;
- }
- info->rbuf_fill_level = rbuf_fill_level;
- if (rbuf_fill_level < 128)
- info->rx_pio = 1; /* PIO mode */
- else
- info->rx_pio = 0; /* DMA mode */
- rx_stop(info); /* restart receiver to use new fill level */
- }
-
- /*
- * enable[1..0] = receiver enable command
- * 0 = disable
- * 1 = enable
- * 2 = enable or force hunt mode if already enabled
- */
- enable &= 3;
- if (enable) {
- if (!info->rx_enabled)
- rx_start(info);
- else if (enable == 2) {
- /* force hunt mode (write 1 to RCR[3]) */
- wr_reg16(info, RCR, rd_reg16(info, RCR) | BIT3);
- }
- } else {
- if (info->rx_enabled)
- rx_stop(info);
- }
- spin_unlock_irqrestore(&info->lock,flags);
- return 0;
-}
-
-/*
- * wait for specified event to occur
- */
-static int wait_mgsl_event(struct slgt_info *info, int __user *mask_ptr)
-{
- unsigned long flags;
- int s;
- int rc=0;
- struct mgsl_icount cprev, cnow;
- int events;
- int mask;
- struct _input_signal_events oldsigs, newsigs;
- DECLARE_WAITQUEUE(wait, current);
-
- if (get_user(mask, mask_ptr))
- return -EFAULT;
-
- DBGINFO(("%s wait_mgsl_event(%d)\n", info->device_name, mask));
-
- spin_lock_irqsave(&info->lock,flags);
-
- /* return immediately if state matches requested events */
- get_gtsignals(info);
- s = info->signals;
-
- events = mask &
- ( ((s & SerialSignal_DSR) ? MgslEvent_DsrActive:MgslEvent_DsrInactive) +
- ((s & SerialSignal_DCD) ? MgslEvent_DcdActive:MgslEvent_DcdInactive) +
- ((s & SerialSignal_CTS) ? MgslEvent_CtsActive:MgslEvent_CtsInactive) +
- ((s & SerialSignal_RI) ? MgslEvent_RiActive :MgslEvent_RiInactive) );
- if (events) {
- spin_unlock_irqrestore(&info->lock,flags);
- goto exit;
- }
-
- /* save current irq counts */
- cprev = info->icount;
- oldsigs = info->input_signal_events;
-
- /* enable hunt and idle irqs if needed */
- if (mask & (MgslEvent_ExitHuntMode+MgslEvent_IdleReceived)) {
- unsigned short val = rd_reg16(info, SCR);
- if (!(val & IRQ_RXIDLE))
- wr_reg16(info, SCR, (unsigned short)(val | IRQ_RXIDLE));
- }
-
- set_current_state(TASK_INTERRUPTIBLE);
- add_wait_queue(&info->event_wait_q, &wait);
-
- spin_unlock_irqrestore(&info->lock,flags);
-
- for(;;) {
- schedule();
- if (signal_pending(current)) {
- rc = -ERESTARTSYS;
- break;
- }
-
- /* get current irq counts */
- spin_lock_irqsave(&info->lock,flags);
- cnow = info->icount;
- newsigs = info->input_signal_events;
- set_current_state(TASK_INTERRUPTIBLE);
- spin_unlock_irqrestore(&info->lock,flags);
-
- /* if no change, wait aborted for some reason */
- if (newsigs.dsr_up == oldsigs.dsr_up &&
- newsigs.dsr_down == oldsigs.dsr_down &&
- newsigs.dcd_up == oldsigs.dcd_up &&
- newsigs.dcd_down == oldsigs.dcd_down &&
- newsigs.cts_up == oldsigs.cts_up &&
- newsigs.cts_down == oldsigs.cts_down &&
- newsigs.ri_up == oldsigs.ri_up &&
- newsigs.ri_down == oldsigs.ri_down &&
- cnow.exithunt == cprev.exithunt &&
- cnow.rxidle == cprev.rxidle) {
- rc = -EIO;
- break;
- }
-
- events = mask &
- ( (newsigs.dsr_up != oldsigs.dsr_up ? MgslEvent_DsrActive:0) +
- (newsigs.dsr_down != oldsigs.dsr_down ? MgslEvent_DsrInactive:0) +
- (newsigs.dcd_up != oldsigs.dcd_up ? MgslEvent_DcdActive:0) +
- (newsigs.dcd_down != oldsigs.dcd_down ? MgslEvent_DcdInactive:0) +
- (newsigs.cts_up != oldsigs.cts_up ? MgslEvent_CtsActive:0) +
- (newsigs.cts_down != oldsigs.cts_down ? MgslEvent_CtsInactive:0) +
- (newsigs.ri_up != oldsigs.ri_up ? MgslEvent_RiActive:0) +
- (newsigs.ri_down != oldsigs.ri_down ? MgslEvent_RiInactive:0) +
- (cnow.exithunt != cprev.exithunt ? MgslEvent_ExitHuntMode:0) +
- (cnow.rxidle != cprev.rxidle ? MgslEvent_IdleReceived:0) );
- if (events)
- break;
-
- cprev = cnow;
- oldsigs = newsigs;
- }
-
- remove_wait_queue(&info->event_wait_q, &wait);
- set_current_state(TASK_RUNNING);
-
-
- if (mask & (MgslEvent_ExitHuntMode + MgslEvent_IdleReceived)) {
- spin_lock_irqsave(&info->lock,flags);
- if (!waitqueue_active(&info->event_wait_q)) {
- /* disable enable exit hunt mode/idle rcvd IRQs */
- wr_reg16(info, SCR,
- (unsigned short)(rd_reg16(info, SCR) & ~IRQ_RXIDLE));
- }
- spin_unlock_irqrestore(&info->lock,flags);
- }
-exit:
- if (rc == 0)
- rc = put_user(events, mask_ptr);
- return rc;
-}
-
-static int get_interface(struct slgt_info *info, int __user *if_mode)
-{
- DBGINFO(("%s get_interface=%x\n", info->device_name, info->if_mode));
- if (put_user(info->if_mode, if_mode))
- return -EFAULT;
- return 0;
-}
-
-static int set_interface(struct slgt_info *info, int if_mode)
-{
- unsigned long flags;
- unsigned short val;
-
- DBGINFO(("%s set_interface=%x)\n", info->device_name, if_mode));
- spin_lock_irqsave(&info->lock,flags);
- info->if_mode = if_mode;
-
- msc_set_vcr(info);
-
- /* TCR (tx control) 07 1=RTS driver control */
- val = rd_reg16(info, TCR);
- if (info->if_mode & MGSL_INTERFACE_RTS_EN)
- val |= BIT7;
- else
- val &= ~BIT7;
- wr_reg16(info, TCR, val);
-
- spin_unlock_irqrestore(&info->lock,flags);
- return 0;
-}
-
-static int get_xsync(struct slgt_info *info, int __user *xsync)
-{
- DBGINFO(("%s get_xsync=%x\n", info->device_name, info->xsync));
- if (put_user(info->xsync, xsync))
- return -EFAULT;
- return 0;
-}
-
-/*
- * set extended sync pattern (1 to 4 bytes) for extended sync mode
- *
- * sync pattern is contained in least significant bytes of value
- * most significant byte of sync pattern is oldest (1st sent/detected)
- */
-static int set_xsync(struct slgt_info *info, int xsync)
-{
- unsigned long flags;
-
- DBGINFO(("%s set_xsync=%x)\n", info->device_name, xsync));
- spin_lock_irqsave(&info->lock, flags);
- info->xsync = xsync;
- wr_reg32(info, XSR, xsync);
- spin_unlock_irqrestore(&info->lock, flags);
- return 0;
-}
-
-static int get_xctrl(struct slgt_info *info, int __user *xctrl)
-{
- DBGINFO(("%s get_xctrl=%x\n", info->device_name, info->xctrl));
- if (put_user(info->xctrl, xctrl))
- return -EFAULT;
- return 0;
-}
-
-/*
- * set extended control options
- *
- * xctrl[31:19] reserved, must be zero
- * xctrl[18:17] extended sync pattern length in bytes
- * 00 = 1 byte in xsr[7:0]
- * 01 = 2 bytes in xsr[15:0]
- * 10 = 3 bytes in xsr[23:0]
- * 11 = 4 bytes in xsr[31:0]
- * xctrl[16] 1 = enable terminal count, 0=disabled
- * xctrl[15:0] receive terminal count for fixed length packets
- * value is count minus one (0 = 1 byte packet)
- * when terminal count is reached, receiver
- * automatically returns to hunt mode and receive
- * FIFO contents are flushed to DMA buffers with
- * end of frame (EOF) status
- */
-static int set_xctrl(struct slgt_info *info, int xctrl)
-{
- unsigned long flags;
-
- DBGINFO(("%s set_xctrl=%x)\n", info->device_name, xctrl));
- spin_lock_irqsave(&info->lock, flags);
- info->xctrl = xctrl;
- wr_reg32(info, XCR, xctrl);
- spin_unlock_irqrestore(&info->lock, flags);
- return 0;
-}
-
-/*
- * set general purpose IO pin state and direction
- *
- * user_gpio fields:
- * state each bit indicates a pin state
- * smask set bit indicates pin state to set
- * dir each bit indicates a pin direction (0=input, 1=output)
- * dmask set bit indicates pin direction to set
- */
-static int set_gpio(struct slgt_info *info, struct gpio_desc __user *user_gpio)
-{
- unsigned long flags;
- struct gpio_desc gpio;
- __u32 data;
-
- if (!info->gpio_present)
- return -EINVAL;
- if (copy_from_user(&gpio, user_gpio, sizeof(gpio)))
- return -EFAULT;
- DBGINFO(("%s set_gpio state=%08x smask=%08x dir=%08x dmask=%08x\n",
- info->device_name, gpio.state, gpio.smask,
- gpio.dir, gpio.dmask));
-
- spin_lock_irqsave(&info->port_array[0]->lock, flags);
- if (gpio.dmask) {
- data = rd_reg32(info, IODR);
- data |= gpio.dmask & gpio.dir;
- data &= ~(gpio.dmask & ~gpio.dir);
- wr_reg32(info, IODR, data);
- }
- if (gpio.smask) {
- data = rd_reg32(info, IOVR);
- data |= gpio.smask & gpio.state;
- data &= ~(gpio.smask & ~gpio.state);
- wr_reg32(info, IOVR, data);
- }
- spin_unlock_irqrestore(&info->port_array[0]->lock, flags);
-
- return 0;
-}
-
-/*
- * get general purpose IO pin state and direction
- */
-static int get_gpio(struct slgt_info *info, struct gpio_desc __user *user_gpio)
-{
- struct gpio_desc gpio;
- if (!info->gpio_present)
- return -EINVAL;
- gpio.state = rd_reg32(info, IOVR);
- gpio.smask = 0xffffffff;
- gpio.dir = rd_reg32(info, IODR);
- gpio.dmask = 0xffffffff;
- if (copy_to_user(user_gpio, &gpio, sizeof(gpio)))
- return -EFAULT;
- DBGINFO(("%s get_gpio state=%08x dir=%08x\n",
- info->device_name, gpio.state, gpio.dir));
- return 0;
-}
-
-/*
- * conditional wait facility
- */
-static void init_cond_wait(struct cond_wait *w, unsigned int data)
-{
- init_waitqueue_head(&w->q);
- init_waitqueue_entry(&w->wait, current);
- w->data = data;
-}
-
-static void add_cond_wait(struct cond_wait **head, struct cond_wait *w)
-{
- set_current_state(TASK_INTERRUPTIBLE);
- add_wait_queue(&w->q, &w->wait);
- w->next = *head;
- *head = w;
-}
-
-static void remove_cond_wait(struct cond_wait **head, struct cond_wait *cw)
-{
- struct cond_wait *w, *prev;
- remove_wait_queue(&cw->q, &cw->wait);
- set_current_state(TASK_RUNNING);
- for (w = *head, prev = NULL ; w != NULL ; prev = w, w = w->next) {
- if (w == cw) {
- if (prev != NULL)
- prev->next = w->next;
- else
- *head = w->next;
- break;
- }
- }
-}
-
-static void flush_cond_wait(struct cond_wait **head)
-{
- while (*head != NULL) {
- wake_up_interruptible(&(*head)->q);
- *head = (*head)->next;
- }
-}
-
-/*
- * wait for general purpose I/O pin(s) to enter specified state
- *
- * user_gpio fields:
- * state - bit indicates target pin state
- * smask - set bit indicates watched pin
- *
- * The wait ends when at least one watched pin enters the specified
- * state. When 0 (no error) is returned, user_gpio->state is set to the
- * state of all GPIO pins when the wait ends.
- *
- * Note: Each pin may be a dedicated input, dedicated output, or
- * configurable input/output. The number and configuration of pins
- * varies with the specific adapter model. Only input pins (dedicated
- * or configured) can be monitored with this function.
- */
-static int wait_gpio(struct slgt_info *info, struct gpio_desc __user *user_gpio)
-{
- unsigned long flags;
- int rc = 0;
- struct gpio_desc gpio;
- struct cond_wait wait;
- u32 state;
-
- if (!info->gpio_present)
- return -EINVAL;
- if (copy_from_user(&gpio, user_gpio, sizeof(gpio)))
- return -EFAULT;
- DBGINFO(("%s wait_gpio() state=%08x smask=%08x\n",
- info->device_name, gpio.state, gpio.smask));
- /* ignore output pins identified by set IODR bit */
- if ((gpio.smask &= ~rd_reg32(info, IODR)) == 0)
- return -EINVAL;
- init_cond_wait(&wait, gpio.smask);
-
- spin_lock_irqsave(&info->port_array[0]->lock, flags);
- /* enable interrupts for watched pins */
- wr_reg32(info, IOER, rd_reg32(info, IOER) | gpio.smask);
- /* get current pin states */
- state = rd_reg32(info, IOVR);
-
- if (gpio.smask & ~(state ^ gpio.state)) {
- /* already in target state */
- gpio.state = state;
- } else {
- /* wait for target state */
- add_cond_wait(&info->gpio_wait_q, &wait);
- spin_unlock_irqrestore(&info->port_array[0]->lock, flags);
- schedule();
- if (signal_pending(current))
- rc = -ERESTARTSYS;
- else
- gpio.state = wait.data;
- spin_lock_irqsave(&info->port_array[0]->lock, flags);
- remove_cond_wait(&info->gpio_wait_q, &wait);
- }
-
- /* disable all GPIO interrupts if no waiting processes */
- if (info->gpio_wait_q == NULL)
- wr_reg32(info, IOER, 0);
- spin_unlock_irqrestore(&info->port_array[0]->lock, flags);
-
- if ((rc == 0) && copy_to_user(user_gpio, &gpio, sizeof(gpio)))
- rc = -EFAULT;
- return rc;
-}
-
-static int modem_input_wait(struct slgt_info *info,int arg)
-{
- unsigned long flags;
- int rc;
- struct mgsl_icount cprev, cnow;
- DECLARE_WAITQUEUE(wait, current);
-
- /* save current irq counts */
- spin_lock_irqsave(&info->lock,flags);
- cprev = info->icount;
- add_wait_queue(&info->status_event_wait_q, &wait);
- set_current_state(TASK_INTERRUPTIBLE);
- spin_unlock_irqrestore(&info->lock,flags);
-
- for(;;) {
- schedule();
- if (signal_pending(current)) {
- rc = -ERESTARTSYS;
- break;
- }
-
- /* get new irq counts */
- spin_lock_irqsave(&info->lock,flags);
- cnow = info->icount;
- set_current_state(TASK_INTERRUPTIBLE);
- spin_unlock_irqrestore(&info->lock,flags);
-
- /* if no change, wait aborted for some reason */
- if (cnow.rng == cprev.rng && cnow.dsr == cprev.dsr &&
- cnow.dcd == cprev.dcd && cnow.cts == cprev.cts) {
- rc = -EIO;
- break;
- }
-
- /* check for change in caller specified modem input */
- if ((arg & TIOCM_RNG && cnow.rng != cprev.rng) ||
- (arg & TIOCM_DSR && cnow.dsr != cprev.dsr) ||
- (arg & TIOCM_CD && cnow.dcd != cprev.dcd) ||
- (arg & TIOCM_CTS && cnow.cts != cprev.cts)) {
- rc = 0;
- break;
- }
-
- cprev = cnow;
- }
- remove_wait_queue(&info->status_event_wait_q, &wait);
- set_current_state(TASK_RUNNING);
- return rc;
-}
-
-/*
- * return state of serial control and status signals
- */
-static int tiocmget(struct tty_struct *tty)
-{
- struct slgt_info *info = tty->driver_data;
- unsigned int result;
- unsigned long flags;
-
- spin_lock_irqsave(&info->lock,flags);
- get_gtsignals(info);
- spin_unlock_irqrestore(&info->lock,flags);
-
- result = ((info->signals & SerialSignal_RTS) ? TIOCM_RTS:0) +
- ((info->signals & SerialSignal_DTR) ? TIOCM_DTR:0) +
- ((info->signals & SerialSignal_DCD) ? TIOCM_CAR:0) +
- ((info->signals & SerialSignal_RI) ? TIOCM_RNG:0) +
- ((info->signals & SerialSignal_DSR) ? TIOCM_DSR:0) +
- ((info->signals & SerialSignal_CTS) ? TIOCM_CTS:0);
-
- DBGINFO(("%s tiocmget value=%08X\n", info->device_name, result));
- return result;
-}
-
-/*
- * set modem control signals (DTR/RTS)
- *
- * cmd signal command: TIOCMBIS = set bit TIOCMBIC = clear bit
- * TIOCMSET = set/clear signal values
- * value bit mask for command
- */
-static int tiocmset(struct tty_struct *tty,
- unsigned int set, unsigned int clear)
-{
- struct slgt_info *info = tty->driver_data;
- unsigned long flags;
-
- DBGINFO(("%s tiocmset(%x,%x)\n", info->device_name, set, clear));
-
- if (set & TIOCM_RTS)
- info->signals |= SerialSignal_RTS;
- if (set & TIOCM_DTR)
- info->signals |= SerialSignal_DTR;
- if (clear & TIOCM_RTS)
- info->signals &= ~SerialSignal_RTS;
- if (clear & TIOCM_DTR)
- info->signals &= ~SerialSignal_DTR;
-
- spin_lock_irqsave(&info->lock,flags);
- set_gtsignals(info);
- spin_unlock_irqrestore(&info->lock,flags);
- return 0;
-}
-
-static bool carrier_raised(struct tty_port *port)
-{
- unsigned long flags;
- struct slgt_info *info = container_of(port, struct slgt_info, port);
-
- spin_lock_irqsave(&info->lock,flags);
- get_gtsignals(info);
- spin_unlock_irqrestore(&info->lock,flags);
-
- return info->signals & SerialSignal_DCD;
-}
-
-static void dtr_rts(struct tty_port *port, bool active)
-{
- unsigned long flags;
- struct slgt_info *info = container_of(port, struct slgt_info, port);
-
- spin_lock_irqsave(&info->lock,flags);
- if (active)
- info->signals |= SerialSignal_RTS | SerialSignal_DTR;
- else
- info->signals &= ~(SerialSignal_RTS | SerialSignal_DTR);
- set_gtsignals(info);
- spin_unlock_irqrestore(&info->lock,flags);
-}
-
-
-/*
- * block current process until the device is ready to open
- */
-static int block_til_ready(struct tty_struct *tty, struct file *filp,
- struct slgt_info *info)
-{
- DECLARE_WAITQUEUE(wait, current);
- int retval;
- bool do_clocal = false;
- unsigned long flags;
- bool cd;
- struct tty_port *port = &info->port;
-
- DBGINFO(("%s block_til_ready\n", tty->driver->name));
-
- if (filp->f_flags & O_NONBLOCK || tty_io_error(tty)) {
- /* nonblock mode is set or port is not enabled */
- tty_port_set_active(port, true);
- return 0;
- }
-
- if (C_CLOCAL(tty))
- do_clocal = true;
-
- /* Wait for carrier detect and the line to become
- * free (i.e., not in use by the callout). While we are in
- * this loop, port->count is dropped by one, so that
- * close() knows when to free things. We restore it upon
- * exit, either normal or abnormal.
- */
-
- retval = 0;
- add_wait_queue(&port->open_wait, &wait);
-
- spin_lock_irqsave(&info->lock, flags);
- port->count--;
- spin_unlock_irqrestore(&info->lock, flags);
- port->blocked_open++;
-
- while (1) {
- if (C_BAUD(tty) && tty_port_initialized(port))
- tty_port_raise_dtr_rts(port);
-
- set_current_state(TASK_INTERRUPTIBLE);
-
- if (tty_hung_up_p(filp) || !tty_port_initialized(port)) {
- retval = (port->flags & ASYNC_HUP_NOTIFY) ?
- -EAGAIN : -ERESTARTSYS;
- break;
- }
-
- cd = tty_port_carrier_raised(port);
- if (do_clocal || cd)
- break;
-
- if (signal_pending(current)) {
- retval = -ERESTARTSYS;
- break;
- }
-
- DBGINFO(("%s block_til_ready wait\n", tty->driver->name));
- tty_unlock(tty);
- schedule();
- tty_lock(tty);
- }
-
- set_current_state(TASK_RUNNING);
- remove_wait_queue(&port->open_wait, &wait);
-
- if (!tty_hung_up_p(filp))
- port->count++;
- port->blocked_open--;
-
- if (!retval)
- tty_port_set_active(port, true);
-
- DBGINFO(("%s block_til_ready ready, rc=%d\n", tty->driver->name, retval));
- return retval;
-}
-
-/*
- * allocate buffers used for calling line discipline receive_buf
- * directly in synchronous mode
- * note: add 5 bytes to max frame size to allow appending
- * 32-bit CRC and status byte when configured to do so
- */
-static int alloc_tmp_rbuf(struct slgt_info *info)
-{
- info->tmp_rbuf = kmalloc(info->max_frame_size + 5, GFP_KERNEL);
- if (info->tmp_rbuf == NULL)
- return -ENOMEM;
-
- return 0;
-}
-
-static void free_tmp_rbuf(struct slgt_info *info)
-{
- kfree(info->tmp_rbuf);
- info->tmp_rbuf = NULL;
-}
-
-/*
- * allocate DMA descriptor lists.
- */
-static int alloc_desc(struct slgt_info *info)
-{
- unsigned int i;
- unsigned int pbufs;
-
- /* allocate memory to hold descriptor lists */
- info->bufs = dma_alloc_coherent(&info->pdev->dev, DESC_LIST_SIZE,
- &info->bufs_dma_addr, GFP_KERNEL);
- if (info->bufs == NULL)
- return -ENOMEM;
-
- info->rbufs = (struct slgt_desc*)info->bufs;
- info->tbufs = ((struct slgt_desc*)info->bufs) + info->rbuf_count;
-
- pbufs = (unsigned int)info->bufs_dma_addr;
-
- /*
- * Build circular lists of descriptors
- */
-
- for (i=0; i < info->rbuf_count; i++) {
- /* physical address of this descriptor */
- info->rbufs[i].pdesc = pbufs + (i * sizeof(struct slgt_desc));
-
- /* physical address of next descriptor */
- if (i == info->rbuf_count - 1)
- info->rbufs[i].next = cpu_to_le32(pbufs);
- else
- info->rbufs[i].next = cpu_to_le32(pbufs + ((i+1) * sizeof(struct slgt_desc)));
- set_desc_count(info->rbufs[i], DMABUFSIZE);
- }
-
- for (i=0; i < info->tbuf_count; i++) {
- /* physical address of this descriptor */
- info->tbufs[i].pdesc = pbufs + ((info->rbuf_count + i) * sizeof(struct slgt_desc));
-
- /* physical address of next descriptor */
- if (i == info->tbuf_count - 1)
- info->tbufs[i].next = cpu_to_le32(pbufs + info->rbuf_count * sizeof(struct slgt_desc));
- else
- info->tbufs[i].next = cpu_to_le32(pbufs + ((info->rbuf_count + i + 1) * sizeof(struct slgt_desc)));
- }
-
- return 0;
-}
-
-static void free_desc(struct slgt_info *info)
-{
- if (info->bufs != NULL) {
- dma_free_coherent(&info->pdev->dev, DESC_LIST_SIZE,
- info->bufs, info->bufs_dma_addr);
- info->bufs = NULL;
- info->rbufs = NULL;
- info->tbufs = NULL;
- }
-}
-
-static int alloc_bufs(struct slgt_info *info, struct slgt_desc *bufs, int count)
-{
- int i;
- for (i=0; i < count; i++) {
- bufs[i].buf = dma_alloc_coherent(&info->pdev->dev, DMABUFSIZE,
- &bufs[i].buf_dma_addr, GFP_KERNEL);
- if (!bufs[i].buf)
- return -ENOMEM;
- bufs[i].pbuf = cpu_to_le32((unsigned int)bufs[i].buf_dma_addr);
- }
- return 0;
-}
-
-static void free_bufs(struct slgt_info *info, struct slgt_desc *bufs, int count)
-{
- int i;
- for (i=0; i < count; i++) {
- if (bufs[i].buf == NULL)
- continue;
- dma_free_coherent(&info->pdev->dev, DMABUFSIZE, bufs[i].buf,
- bufs[i].buf_dma_addr);
- bufs[i].buf = NULL;
- }
-}
-
-static int alloc_dma_bufs(struct slgt_info *info)
-{
- info->rbuf_count = 32;
- info->tbuf_count = 32;
-
- if (alloc_desc(info) < 0 ||
- alloc_bufs(info, info->rbufs, info->rbuf_count) < 0 ||
- alloc_bufs(info, info->tbufs, info->tbuf_count) < 0 ||
- alloc_tmp_rbuf(info) < 0) {
- DBGERR(("%s DMA buffer alloc fail\n", info->device_name));
- return -ENOMEM;
- }
- reset_rbufs(info);
- return 0;
-}
-
-static void free_dma_bufs(struct slgt_info *info)
-{
- if (info->bufs) {
- free_bufs(info, info->rbufs, info->rbuf_count);
- free_bufs(info, info->tbufs, info->tbuf_count);
- free_desc(info);
- }
- free_tmp_rbuf(info);
-}
-
-static int claim_resources(struct slgt_info *info)
-{
- if (request_mem_region(info->phys_reg_addr, SLGT_REG_SIZE, "synclink_gt") == NULL) {
- DBGERR(("%s reg addr conflict, addr=%08X\n",
- info->device_name, info->phys_reg_addr));
- info->init_error = DiagStatus_AddressConflict;
- goto errout;
- }
- else
- info->reg_addr_requested = true;
-
- info->reg_addr = ioremap(info->phys_reg_addr, SLGT_REG_SIZE);
- if (!info->reg_addr) {
- DBGERR(("%s can't map device registers, addr=%08X\n",
- info->device_name, info->phys_reg_addr));
- info->init_error = DiagStatus_CantAssignPciResources;
- goto errout;
- }
- return 0;
-
-errout:
- release_resources(info);
- return -ENODEV;
-}
-
-static void release_resources(struct slgt_info *info)
-{
- if (info->irq_requested) {
- free_irq(info->irq_level, info);
- info->irq_requested = false;
- }
-
- if (info->reg_addr_requested) {
- release_mem_region(info->phys_reg_addr, SLGT_REG_SIZE);
- info->reg_addr_requested = false;
- }
-
- if (info->reg_addr) {
- iounmap(info->reg_addr);
- info->reg_addr = NULL;
- }
-}
-
-/* Add the specified device instance data structure to the
- * global linked list of devices and increment the device count.
- */
-static void add_device(struct slgt_info *info)
-{
- char *devstr;
-
- info->next_device = NULL;
- info->line = slgt_device_count;
- sprintf(info->device_name, "%s%d", tty_dev_prefix, info->line);
-
- if (info->line < MAX_DEVICES) {
- if (maxframe[info->line])
- info->max_frame_size = maxframe[info->line];
- }
-
- slgt_device_count++;
-
- if (!slgt_device_list)
- slgt_device_list = info;
- else {
- struct slgt_info *current_dev = slgt_device_list;
- while(current_dev->next_device)
- current_dev = current_dev->next_device;
- current_dev->next_device = info;
- }
-
- if (info->max_frame_size < 4096)
- info->max_frame_size = 4096;
- else if (info->max_frame_size > 65535)
- info->max_frame_size = 65535;
-
- switch(info->pdev->device) {
- case SYNCLINK_GT_DEVICE_ID:
- devstr = "GT";
- break;
- case SYNCLINK_GT2_DEVICE_ID:
- devstr = "GT2";
- break;
- case SYNCLINK_GT4_DEVICE_ID:
- devstr = "GT4";
- break;
- case SYNCLINK_AC_DEVICE_ID:
- devstr = "AC";
- info->params.mode = MGSL_MODE_ASYNC;
- break;
- default:
- devstr = "(unknown model)";
- }
- printk("SyncLink %s %s IO=%08x IRQ=%d MaxFrameSize=%u\n",
- devstr, info->device_name, info->phys_reg_addr,
- info->irq_level, info->max_frame_size);
-
-#if SYNCLINK_GENERIC_HDLC
- hdlcdev_init(info);
-#endif
-}
-
-static const struct tty_port_operations slgt_port_ops = {
- .carrier_raised = carrier_raised,
- .dtr_rts = dtr_rts,
-};
-
-/*
- * allocate device instance structure, return NULL on failure
- */
-static struct slgt_info *alloc_dev(int adapter_num, int port_num, struct pci_dev *pdev)
-{
- struct slgt_info *info;
-
- info = kzalloc_obj(struct slgt_info);
-
- if (!info) {
- DBGERR(("%s device alloc failed adapter=%d port=%d\n",
- driver_name, adapter_num, port_num));
- } else {
- tty_port_init(&info->port);
- info->port.ops = &slgt_port_ops;
- INIT_WORK(&info->task, bh_handler);
- info->max_frame_size = 4096;
- info->base_clock = 14745600;
- info->rbuf_fill_level = DMABUFSIZE;
- init_waitqueue_head(&info->status_event_wait_q);
- init_waitqueue_head(&info->event_wait_q);
- spin_lock_init(&info->netlock);
- memcpy(&info->params,&default_params,sizeof(MGSL_PARAMS));
- info->idle_mode = HDLC_TXIDLE_FLAGS;
- info->adapter_num = adapter_num;
- info->port_num = port_num;
-
- timer_setup(&info->tx_timer, tx_timeout, 0);
- timer_setup(&info->rx_timer, rx_timeout, 0);
-
- /* Copy configuration info to device instance data */
- info->pdev = pdev;
- info->irq_level = pdev->irq;
- info->phys_reg_addr = pci_resource_start(pdev,0);
-
- info->bus_type = MGSL_BUS_TYPE_PCI;
- info->irq_flags = IRQF_SHARED;
-
- info->init_error = -1; /* assume error, set to 0 on successful init */
- }
-
- return info;
-}
-
-static void device_init(int adapter_num, struct pci_dev *pdev)
-{
- struct slgt_info *port_array[SLGT_MAX_PORTS];
- int i;
- int port_count = 1;
-
- if (pdev->device == SYNCLINK_GT2_DEVICE_ID)
- port_count = 2;
- else if (pdev->device == SYNCLINK_GT4_DEVICE_ID)
- port_count = 4;
-
- /* allocate device instances for all ports */
- for (i=0; i < port_count; ++i) {
- port_array[i] = alloc_dev(adapter_num, i, pdev);
- if (port_array[i] == NULL) {
- for (--i; i >= 0; --i) {
- tty_port_destroy(&port_array[i]->port);
- kfree(port_array[i]);
- }
- return;
- }
- }
-
- /* give copy of port_array to all ports and add to device list */
- for (i=0; i < port_count; ++i) {
- memcpy(port_array[i]->port_array, port_array, sizeof(port_array));
- add_device(port_array[i]);
- port_array[i]->port_count = port_count;
- spin_lock_init(&port_array[i]->lock);
- }
-
- /* Allocate and claim adapter resources */
- if (!claim_resources(port_array[0])) {
-
- alloc_dma_bufs(port_array[0]);
-
- /* copy resource information from first port to others */
- for (i = 1; i < port_count; ++i) {
- port_array[i]->irq_level = port_array[0]->irq_level;
- port_array[i]->reg_addr = port_array[0]->reg_addr;
- alloc_dma_bufs(port_array[i]);
- }
-
- if (request_irq(port_array[0]->irq_level,
- slgt_interrupt,
- port_array[0]->irq_flags,
- port_array[0]->device_name,
- port_array[0]) < 0) {
- DBGERR(("%s request_irq failed IRQ=%d\n",
- port_array[0]->device_name,
- port_array[0]->irq_level));
- } else {
- port_array[0]->irq_requested = true;
- adapter_test(port_array[0]);
- for (i=1 ; i < port_count ; i++) {
- port_array[i]->init_error = port_array[0]->init_error;
- port_array[i]->gpio_present = port_array[0]->gpio_present;
- }
- }
- }
-
- for (i = 0; i < port_count; ++i) {
- struct slgt_info *info = port_array[i];
- tty_port_register_device(&info->port, serial_driver, info->line,
- &info->pdev->dev);
- }
-}
-
-static int init_one(struct pci_dev *dev,
- const struct pci_device_id *ent)
-{
- if (pci_enable_device(dev)) {
- printk("error enabling pci device %p\n", dev);
- return -EIO;
- }
- pci_set_master(dev);
- device_init(slgt_device_count, dev);
- return 0;
-}
-
-static void remove_one(struct pci_dev *dev)
-{
-}
-
-static const struct tty_operations ops = {
- .open = open,
- .close = close,
- .write = write,
- .put_char = put_char,
- .flush_chars = flush_chars,
- .write_room = write_room,
- .chars_in_buffer = chars_in_buffer,
- .flush_buffer = flush_buffer,
- .ioctl = ioctl,
- .compat_ioctl = slgt_compat_ioctl,
- .throttle = throttle,
- .unthrottle = unthrottle,
- .send_xchar = send_xchar,
- .break_ctl = set_break,
- .wait_until_sent = wait_until_sent,
- .set_termios = set_termios,
- .stop = tx_hold,
- .start = tx_release,
- .hangup = hangup,
- .tiocmget = tiocmget,
- .tiocmset = tiocmset,
- .get_icount = get_icount,
- .proc_show = synclink_gt_proc_show,
-};
-
-static void slgt_cleanup(void)
-{
- struct slgt_info *info;
- struct slgt_info *tmp;
-
- if (serial_driver) {
- for (info=slgt_device_list ; info != NULL ; info=info->next_device)
- tty_unregister_device(serial_driver, info->line);
- tty_unregister_driver(serial_driver);
- tty_driver_kref_put(serial_driver);
- }
-
- /* reset devices */
- info = slgt_device_list;
- while(info) {
- reset_port(info);
- info = info->next_device;
- }
-
- /* release devices */
- info = slgt_device_list;
- while(info) {
-#if SYNCLINK_GENERIC_HDLC
- hdlcdev_exit(info);
-#endif
- free_dma_bufs(info);
- free_tmp_rbuf(info);
- if (info->port_num == 0)
- release_resources(info);
- tmp = info;
- info = info->next_device;
- tty_port_destroy(&tmp->port);
- kfree(tmp);
- }
-
- if (pci_registered)
- pci_unregister_driver(&pci_driver);
-}
-
-/*
- * Driver initialization entry point.
- */
-static int __init slgt_init(void)
-{
- int rc;
-
- serial_driver = tty_alloc_driver(MAX_DEVICES, TTY_DRIVER_REAL_RAW |
- TTY_DRIVER_DYNAMIC_DEV);
- if (IS_ERR(serial_driver)) {
- printk("%s can't allocate tty driver\n", driver_name);
- return PTR_ERR(serial_driver);
- }
-
- /* Initialize the tty_driver structure */
-
- serial_driver->driver_name = "synclink_gt";
- serial_driver->name = tty_dev_prefix;
- serial_driver->major = ttymajor;
- serial_driver->minor_start = 64;
- serial_driver->type = TTY_DRIVER_TYPE_SERIAL;
- serial_driver->subtype = SERIAL_TYPE_NORMAL;
- serial_driver->init_termios = tty_std_termios;
- serial_driver->init_termios.c_cflag =
- B9600 | CS8 | CREAD | HUPCL | CLOCAL;
- serial_driver->init_termios.c_ispeed = 9600;
- serial_driver->init_termios.c_ospeed = 9600;
- tty_set_operations(serial_driver, &ops);
- if ((rc = tty_register_driver(serial_driver)) < 0) {
- DBGERR(("%s can't register serial driver\n", driver_name));
- tty_driver_kref_put(serial_driver);
- serial_driver = NULL;
- goto error;
- }
-
- slgt_device_count = 0;
- if ((rc = pci_register_driver(&pci_driver)) < 0) {
- printk("%s pci_register_driver error=%d\n", driver_name, rc);
- goto error;
- }
- pci_registered = true;
-
- return 0;
-
-error:
- slgt_cleanup();
- return rc;
-}
-
-static void __exit slgt_exit(void)
-{
- slgt_cleanup();
-}
-
-module_init(slgt_init);
-module_exit(slgt_exit);
-
-/*
- * register access routines
- */
-
-static inline void __iomem *calc_regaddr(struct slgt_info *info,
- unsigned int addr)
-{
- void __iomem *reg_addr = info->reg_addr + addr;
-
- if (addr >= 0x80)
- reg_addr += info->port_num * 32;
- else if (addr >= 0x40)
- reg_addr += info->port_num * 16;
-
- return reg_addr;
-}
-
-static __u8 rd_reg8(struct slgt_info *info, unsigned int addr)
-{
- return readb(calc_regaddr(info, addr));
-}
-
-static void wr_reg8(struct slgt_info *info, unsigned int addr, __u8 value)
-{
- writeb(value, calc_regaddr(info, addr));
-}
-
-static __u16 rd_reg16(struct slgt_info *info, unsigned int addr)
-{
- return readw(calc_regaddr(info, addr));
-}
-
-static void wr_reg16(struct slgt_info *info, unsigned int addr, __u16 value)
-{
- writew(value, calc_regaddr(info, addr));
-}
-
-static __u32 rd_reg32(struct slgt_info *info, unsigned int addr)
-{
- return readl(calc_regaddr(info, addr));
-}
-
-static void wr_reg32(struct slgt_info *info, unsigned int addr, __u32 value)
-{
- writel(value, calc_regaddr(info, addr));
-}
-
-static void rdma_reset(struct slgt_info *info)
-{
- unsigned int i;
-
- /* set reset bit */
- wr_reg32(info, RDCSR, BIT1);
-
- /* wait for enable bit cleared */
- for(i=0 ; i < 1000 ; i++)
- if (!(rd_reg32(info, RDCSR) & BIT0))
- break;
-}
-
-static void tdma_reset(struct slgt_info *info)
-{
- unsigned int i;
-
- /* set reset bit */
- wr_reg32(info, TDCSR, BIT1);
-
- /* wait for enable bit cleared */
- for(i=0 ; i < 1000 ; i++)
- if (!(rd_reg32(info, TDCSR) & BIT0))
- break;
-}
-
-/*
- * enable internal loopback
- * TxCLK and RxCLK are generated from BRG
- * and TxD is looped back to RxD internally.
- */
-static void enable_loopback(struct slgt_info *info)
-{
- /* SCR (serial control) BIT2=loopback enable */
- wr_reg16(info, SCR, (unsigned short)(rd_reg16(info, SCR) | BIT2));
-
- if (info->params.mode != MGSL_MODE_ASYNC) {
- /* CCR (clock control)
- * 07..05 tx clock source (010 = BRG)
- * 04..02 rx clock source (010 = BRG)
- * 01 auxclk enable (0 = disable)
- * 00 BRG enable (1 = enable)
- *
- * 0100 1001
- */
- wr_reg8(info, CCR, 0x49);
-
- /* set speed if available, otherwise use default */
- if (info->params.clock_speed)
- set_rate(info, info->params.clock_speed);
- else
- set_rate(info, 3686400);
- }
-}
-
-/*
- * set baud rate generator to specified rate
- */
-static void set_rate(struct slgt_info *info, u32 rate)
-{
- unsigned int div;
- unsigned int osc = info->base_clock;
-
- /* div = osc/rate - 1
- *
- * Round div up if osc/rate is not integer to
- * force to next slowest rate.
- */
-
- if (rate) {
- div = osc/rate;
- if (!(osc % rate) && div)
- div--;
- wr_reg16(info, BDR, (unsigned short)div);
- }
-}
-
-static void rx_stop(struct slgt_info *info)
-{
- unsigned short val;
-
- /* disable and reset receiver */
- val = rd_reg16(info, RCR) & ~BIT1; /* clear enable bit */
- wr_reg16(info, RCR, (unsigned short)(val | BIT2)); /* set reset bit */
- wr_reg16(info, RCR, val); /* clear reset bit */
-
- slgt_irq_off(info, IRQ_RXOVER + IRQ_RXDATA + IRQ_RXIDLE);
-
- /* clear pending rx interrupts */
- wr_reg16(info, SSR, IRQ_RXIDLE + IRQ_RXOVER);
-
- rdma_reset(info);
-
- info->rx_enabled = false;
- info->rx_restart = false;
-}
-
-static void rx_start(struct slgt_info *info)
-{
- unsigned short val;
-
- slgt_irq_off(info, IRQ_RXOVER + IRQ_RXDATA);
-
- /* clear pending rx overrun IRQ */
- wr_reg16(info, SSR, IRQ_RXOVER);
-
- /* reset and disable receiver */
- val = rd_reg16(info, RCR) & ~BIT1; /* clear enable bit */
- wr_reg16(info, RCR, (unsigned short)(val | BIT2)); /* set reset bit */
- wr_reg16(info, RCR, val); /* clear reset bit */
-
- rdma_reset(info);
- reset_rbufs(info);
-
- if (info->rx_pio) {
- /* rx request when rx FIFO not empty */
- wr_reg16(info, SCR, (unsigned short)(rd_reg16(info, SCR) & ~BIT14));
- slgt_irq_on(info, IRQ_RXDATA);
- if (info->params.mode == MGSL_MODE_ASYNC) {
- /* enable saving of rx status */
- wr_reg32(info, RDCSR, BIT6);
- }
- } else {
- /* rx request when rx FIFO half full */
- wr_reg16(info, SCR, (unsigned short)(rd_reg16(info, SCR) | BIT14));
- /* set 1st descriptor address */
- wr_reg32(info, RDDAR, info->rbufs[0].pdesc);
-
- if (info->params.mode != MGSL_MODE_ASYNC) {
- /* enable rx DMA and DMA interrupt */
- wr_reg32(info, RDCSR, (BIT2 + BIT0));
- } else {
- /* enable saving of rx status, rx DMA and DMA interrupt */
- wr_reg32(info, RDCSR, (BIT6 + BIT2 + BIT0));
- }
- }
-
- slgt_irq_on(info, IRQ_RXOVER);
-
- /* enable receiver */
- wr_reg16(info, RCR, (unsigned short)(rd_reg16(info, RCR) | BIT1));
-
- info->rx_restart = false;
- info->rx_enabled = true;
-}
-
-static void tx_start(struct slgt_info *info)
-{
- if (!info->tx_enabled) {
- wr_reg16(info, TCR,
- (unsigned short)((rd_reg16(info, TCR) | BIT1) & ~BIT2));
- info->tx_enabled = true;
- }
-
- if (desc_count(info->tbufs[info->tbuf_start])) {
- info->drop_rts_on_tx_done = false;
-
- if (info->params.mode != MGSL_MODE_ASYNC) {
- if (info->params.flags & HDLC_FLAG_AUTO_RTS) {
- get_gtsignals(info);
- if (!(info->signals & SerialSignal_RTS)) {
- info->signals |= SerialSignal_RTS;
- set_gtsignals(info);
- info->drop_rts_on_tx_done = true;
- }
- }
-
- slgt_irq_off(info, IRQ_TXDATA);
- slgt_irq_on(info, IRQ_TXUNDER + IRQ_TXIDLE);
- /* clear tx idle and underrun status bits */
- wr_reg16(info, SSR, (unsigned short)(IRQ_TXIDLE + IRQ_TXUNDER));
- } else {
- slgt_irq_off(info, IRQ_TXDATA);
- slgt_irq_on(info, IRQ_TXIDLE);
- /* clear tx idle status bit */
- wr_reg16(info, SSR, IRQ_TXIDLE);
- }
- /* set 1st descriptor address and start DMA */
- wr_reg32(info, TDDAR, info->tbufs[info->tbuf_start].pdesc);
- wr_reg32(info, TDCSR, BIT2 + BIT0);
- info->tx_active = true;
- }
-}
-
-static void tx_stop(struct slgt_info *info)
-{
- unsigned short val;
-
- timer_delete(&info->tx_timer);
-
- tdma_reset(info);
-
- /* reset and disable transmitter */
- val = rd_reg16(info, TCR) & ~BIT1; /* clear enable bit */
- wr_reg16(info, TCR, (unsigned short)(val | BIT2)); /* set reset bit */
-
- slgt_irq_off(info, IRQ_TXDATA + IRQ_TXIDLE + IRQ_TXUNDER);
-
- /* clear tx idle and underrun status bit */
- wr_reg16(info, SSR, (unsigned short)(IRQ_TXIDLE + IRQ_TXUNDER));
-
- reset_tbufs(info);
-
- info->tx_enabled = false;
- info->tx_active = false;
-}
-
-static void reset_port(struct slgt_info *info)
-{
- if (!info->reg_addr)
- return;
-
- tx_stop(info);
- rx_stop(info);
-
- info->signals &= ~(SerialSignal_RTS | SerialSignal_DTR);
- set_gtsignals(info);
-
- slgt_irq_off(info, IRQ_ALL | IRQ_MASTER);
-}
-
-static void reset_adapter(struct slgt_info *info)
-{
- int i;
- for (i=0; i < info->port_count; ++i) {
- if (info->port_array[i])
- reset_port(info->port_array[i]);
- }
-}
-
-static void async_mode(struct slgt_info *info)
-{
- unsigned short val;
-
- slgt_irq_off(info, IRQ_ALL | IRQ_MASTER);
- tx_stop(info);
- rx_stop(info);
-
- /* TCR (tx control)
- *
- * 15..13 mode, 010=async
- * 12..10 encoding, 000=NRZ
- * 09 parity enable
- * 08 1=odd parity, 0=even parity
- * 07 1=RTS driver control
- * 06 1=break enable
- * 05..04 character length
- * 00=5 bits
- * 01=6 bits
- * 10=7 bits
- * 11=8 bits
- * 03 0=1 stop bit, 1=2 stop bits
- * 02 reset
- * 01 enable
- * 00 auto-CTS enable
- */
- val = 0x4000;
-
- if (info->if_mode & MGSL_INTERFACE_RTS_EN)
- val |= BIT7;
-
- if (info->params.parity != ASYNC_PARITY_NONE) {
- val |= BIT9;
- if (info->params.parity == ASYNC_PARITY_ODD)
- val |= BIT8;
- }
-
- switch (info->params.data_bits)
- {
- case 6: val |= BIT4; break;
- case 7: val |= BIT5; break;
- case 8: val |= BIT5 + BIT4; break;
- }
-
- if (info->params.stop_bits != 1)
- val |= BIT3;
-
- if (info->params.flags & HDLC_FLAG_AUTO_CTS)
- val |= BIT0;
-
- wr_reg16(info, TCR, val);
-
- /* RCR (rx control)
- *
- * 15..13 mode, 010=async
- * 12..10 encoding, 000=NRZ
- * 09 parity enable
- * 08 1=odd parity, 0=even parity
- * 07..06 reserved, must be 0
- * 05..04 character length
- * 00=5 bits
- * 01=6 bits
- * 10=7 bits
- * 11=8 bits
- * 03 reserved, must be zero
- * 02 reset
- * 01 enable
- * 00 auto-DCD enable
- */
- val = 0x4000;
-
- if (info->params.parity != ASYNC_PARITY_NONE) {
- val |= BIT9;
- if (info->params.parity == ASYNC_PARITY_ODD)
- val |= BIT8;
- }
-
- switch (info->params.data_bits)
- {
- case 6: val |= BIT4; break;
- case 7: val |= BIT5; break;
- case 8: val |= BIT5 + BIT4; break;
- }
-
- if (info->params.flags & HDLC_FLAG_AUTO_DCD)
- val |= BIT0;
-
- wr_reg16(info, RCR, val);
-
- /* CCR (clock control)
- *
- * 07..05 011 = tx clock source is BRG/16
- * 04..02 010 = rx clock source is BRG
- * 01 0 = auxclk disabled
- * 00 1 = BRG enabled
- *
- * 0110 1001
- */
- wr_reg8(info, CCR, 0x69);
-
- msc_set_vcr(info);
-
- /* SCR (serial control)
- *
- * 15 1=tx req on FIFO half empty
- * 14 1=rx req on FIFO half full
- * 13 tx data IRQ enable
- * 12 tx idle IRQ enable
- * 11 rx break on IRQ enable
- * 10 rx data IRQ enable
- * 09 rx break off IRQ enable
- * 08 overrun IRQ enable
- * 07 DSR IRQ enable
- * 06 CTS IRQ enable
- * 05 DCD IRQ enable
- * 04 RI IRQ enable
- * 03 0=16x sampling, 1=8x sampling
- * 02 1=txd->rxd internal loopback enable
- * 01 reserved, must be zero
- * 00 1=master IRQ enable
- */
- val = BIT15 + BIT14 + BIT0;
- /* JCR[8] : 1 = x8 async mode feature available */
- if ((rd_reg32(info, JCR) & BIT8) && info->params.data_rate &&
- ((info->base_clock < (info->params.data_rate * 16)) ||
- (info->base_clock % (info->params.data_rate * 16)))) {
- /* use 8x sampling */
- val |= BIT3;
- set_rate(info, info->params.data_rate * 8);
- } else {
- /* use 16x sampling */
- set_rate(info, info->params.data_rate * 16);
- }
- wr_reg16(info, SCR, val);
-
- slgt_irq_on(info, IRQ_RXBREAK | IRQ_RXOVER);
-
- if (info->params.loopback)
- enable_loopback(info);
-}
-
-static void sync_mode(struct slgt_info *info)
-{
- unsigned short val;
-
- slgt_irq_off(info, IRQ_ALL | IRQ_MASTER);
- tx_stop(info);
- rx_stop(info);
-
- /* TCR (tx control)
- *
- * 15..13 mode
- * 000=HDLC/SDLC
- * 001=raw bit synchronous
- * 010=asynchronous/isochronous
- * 011=monosync byte synchronous
- * 100=bisync byte synchronous
- * 101=xsync byte synchronous
- * 12..10 encoding
- * 09 CRC enable
- * 08 CRC32
- * 07 1=RTS driver control
- * 06 preamble enable
- * 05..04 preamble length
- * 03 share open/close flag
- * 02 reset
- * 01 enable
- * 00 auto-CTS enable
- */
- val = BIT2;
-
- switch(info->params.mode) {
- case MGSL_MODE_XSYNC:
- val |= BIT15 + BIT13;
- break;
- case MGSL_MODE_MONOSYNC: val |= BIT14 + BIT13; break;
- case MGSL_MODE_BISYNC: val |= BIT15; break;
- case MGSL_MODE_RAW: val |= BIT13; break;
- }
- if (info->if_mode & MGSL_INTERFACE_RTS_EN)
- val |= BIT7;
-
- switch(info->params.encoding)
- {
- case HDLC_ENCODING_NRZB: val |= BIT10; break;
- case HDLC_ENCODING_NRZI_MARK: val |= BIT11; break;
- case HDLC_ENCODING_NRZI: val |= BIT11 + BIT10; break;
- case HDLC_ENCODING_BIPHASE_MARK: val |= BIT12; break;
- case HDLC_ENCODING_BIPHASE_SPACE: val |= BIT12 + BIT10; break;
- case HDLC_ENCODING_BIPHASE_LEVEL: val |= BIT12 + BIT11; break;
- case HDLC_ENCODING_DIFF_BIPHASE_LEVEL: val |= BIT12 + BIT11 + BIT10; break;
- }
-
- switch (info->params.crc_type & HDLC_CRC_MASK)
- {
- case HDLC_CRC_16_CCITT: val |= BIT9; break;
- case HDLC_CRC_32_CCITT: val |= BIT9 + BIT8; break;
- }
-
- if (info->params.preamble != HDLC_PREAMBLE_PATTERN_NONE)
- val |= BIT6;
-
- switch (info->params.preamble_length)
- {
- case HDLC_PREAMBLE_LENGTH_16BITS: val |= BIT5; break;
- case HDLC_PREAMBLE_LENGTH_32BITS: val |= BIT4; break;
- case HDLC_PREAMBLE_LENGTH_64BITS: val |= BIT5 + BIT4; break;
- }
-
- if (info->params.flags & HDLC_FLAG_AUTO_CTS)
- val |= BIT0;
-
- wr_reg16(info, TCR, val);
-
- /* TPR (transmit preamble) */
-
- switch (info->params.preamble)
- {
- case HDLC_PREAMBLE_PATTERN_FLAGS: val = 0x7e; break;
- case HDLC_PREAMBLE_PATTERN_ONES: val = 0xff; break;
- case HDLC_PREAMBLE_PATTERN_ZEROS: val = 0x00; break;
- case HDLC_PREAMBLE_PATTERN_10: val = 0x55; break;
- case HDLC_PREAMBLE_PATTERN_01: val = 0xaa; break;
- default: val = 0x7e; break;
- }
- wr_reg8(info, TPR, (unsigned char)val);
-
- /* RCR (rx control)
- *
- * 15..13 mode
- * 000=HDLC/SDLC
- * 001=raw bit synchronous
- * 010=asynchronous/isochronous
- * 011=monosync byte synchronous
- * 100=bisync byte synchronous
- * 101=xsync byte synchronous
- * 12..10 encoding
- * 09 CRC enable
- * 08 CRC32
- * 07..03 reserved, must be 0
- * 02 reset
- * 01 enable
- * 00 auto-DCD enable
- */
- val = 0;
-
- switch(info->params.mode) {
- case MGSL_MODE_XSYNC:
- val |= BIT15 + BIT13;
- break;
- case MGSL_MODE_MONOSYNC: val |= BIT14 + BIT13; break;
- case MGSL_MODE_BISYNC: val |= BIT15; break;
- case MGSL_MODE_RAW: val |= BIT13; break;
- }
-
- switch(info->params.encoding)
- {
- case HDLC_ENCODING_NRZB: val |= BIT10; break;
- case HDLC_ENCODING_NRZI_MARK: val |= BIT11; break;
- case HDLC_ENCODING_NRZI: val |= BIT11 + BIT10; break;
- case HDLC_ENCODING_BIPHASE_MARK: val |= BIT12; break;
- case HDLC_ENCODING_BIPHASE_SPACE: val |= BIT12 + BIT10; break;
- case HDLC_ENCODING_BIPHASE_LEVEL: val |= BIT12 + BIT11; break;
- case HDLC_ENCODING_DIFF_BIPHASE_LEVEL: val |= BIT12 + BIT11 + BIT10; break;
- }
-
- switch (info->params.crc_type & HDLC_CRC_MASK)
- {
- case HDLC_CRC_16_CCITT: val |= BIT9; break;
- case HDLC_CRC_32_CCITT: val |= BIT9 + BIT8; break;
- }
-
- if (info->params.flags & HDLC_FLAG_AUTO_DCD)
- val |= BIT0;
-
- wr_reg16(info, RCR, val);
-
- /* CCR (clock control)
- *
- * 07..05 tx clock source
- * 04..02 rx clock source
- * 01 auxclk enable
- * 00 BRG enable
- */
- val = 0;
-
- if (info->params.flags & HDLC_FLAG_TXC_BRG)
- {
- // when RxC source is DPLL, BRG generates 16X DPLL
- // reference clock, so take TxC from BRG/16 to get
- // transmit clock at actual data rate
- if (info->params.flags & HDLC_FLAG_RXC_DPLL)
- val |= BIT6 + BIT5; /* 011, txclk = BRG/16 */
- else
- val |= BIT6; /* 010, txclk = BRG */
- }
- else if (info->params.flags & HDLC_FLAG_TXC_DPLL)
- val |= BIT7; /* 100, txclk = DPLL Input */
- else if (info->params.flags & HDLC_FLAG_TXC_RXCPIN)
- val |= BIT5; /* 001, txclk = RXC Input */
-
- if (info->params.flags & HDLC_FLAG_RXC_BRG)
- val |= BIT3; /* 010, rxclk = BRG */
- else if (info->params.flags & HDLC_FLAG_RXC_DPLL)
- val |= BIT4; /* 100, rxclk = DPLL */
- else if (info->params.flags & HDLC_FLAG_RXC_TXCPIN)
- val |= BIT2; /* 001, rxclk = TXC Input */
-
- if (info->params.clock_speed)
- val |= BIT1 + BIT0;
-
- wr_reg8(info, CCR, (unsigned char)val);
-
- if (info->params.flags & (HDLC_FLAG_TXC_DPLL + HDLC_FLAG_RXC_DPLL))
- {
- // program DPLL mode
- switch(info->params.encoding)
- {
- case HDLC_ENCODING_BIPHASE_MARK:
- case HDLC_ENCODING_BIPHASE_SPACE:
- val = BIT7; break;
- case HDLC_ENCODING_BIPHASE_LEVEL:
- case HDLC_ENCODING_DIFF_BIPHASE_LEVEL:
- val = BIT7 + BIT6; break;
- default: val = BIT6; // NRZ encodings
- }
- wr_reg16(info, RCR, (unsigned short)(rd_reg16(info, RCR) | val));
-
- // DPLL requires a 16X reference clock from BRG
- set_rate(info, info->params.clock_speed * 16);
- }
- else
- set_rate(info, info->params.clock_speed);
-
- tx_set_idle(info);
-
- msc_set_vcr(info);
-
- /* SCR (serial control)
- *
- * 15 1=tx req on FIFO half empty
- * 14 1=rx req on FIFO half full
- * 13 tx data IRQ enable
- * 12 tx idle IRQ enable
- * 11 underrun IRQ enable
- * 10 rx data IRQ enable
- * 09 rx idle IRQ enable
- * 08 overrun IRQ enable
- * 07 DSR IRQ enable
- * 06 CTS IRQ enable
- * 05 DCD IRQ enable
- * 04 RI IRQ enable
- * 03 reserved, must be zero
- * 02 1=txd->rxd internal loopback enable
- * 01 reserved, must be zero
- * 00 1=master IRQ enable
- */
- wr_reg16(info, SCR, BIT15 + BIT14 + BIT0);
-
- if (info->params.loopback)
- enable_loopback(info);
-}
-
-/*
- * set transmit idle mode
- */
-static void tx_set_idle(struct slgt_info *info)
-{
- unsigned char val;
- unsigned short tcr;
-
- /* if preamble enabled (tcr[6] == 1) then tx idle size = 8 bits
- * else tcr[5:4] = tx idle size: 00 = 8 bits, 01 = 16 bits
- */
- tcr = rd_reg16(info, TCR);
- if (info->idle_mode & HDLC_TXIDLE_CUSTOM_16) {
- /* disable preamble, set idle size to 16 bits */
- tcr = (tcr & ~(BIT6 + BIT5)) | BIT4;
- /* MSB of 16 bit idle specified in tx preamble register (TPR) */
- wr_reg8(info, TPR, (unsigned char)((info->idle_mode >> 8) & 0xff));
- } else if (!(tcr & BIT6)) {
- /* preamble is disabled, set idle size to 8 bits */
- tcr &= ~(BIT5 + BIT4);
- }
- wr_reg16(info, TCR, tcr);
-
- if (info->idle_mode & (HDLC_TXIDLE_CUSTOM_8 | HDLC_TXIDLE_CUSTOM_16)) {
- /* LSB of custom tx idle specified in tx idle register */
- val = (unsigned char)(info->idle_mode & 0xff);
- } else {
- /* standard 8 bit idle patterns */
- switch(info->idle_mode)
- {
- case HDLC_TXIDLE_FLAGS: val = 0x7e; break;
- case HDLC_TXIDLE_ALT_ZEROS_ONES:
- case HDLC_TXIDLE_ALT_MARK_SPACE: val = 0xaa; break;
- case HDLC_TXIDLE_ZEROS:
- case HDLC_TXIDLE_SPACE: val = 0x00; break;
- default: val = 0xff;
- }
- }
-
- wr_reg8(info, TIR, val);
-}
-
-/*
- * get state of V24 status (input) signals
- */
-static void get_gtsignals(struct slgt_info *info)
-{
- unsigned short status = rd_reg16(info, SSR);
-
- /* clear all serial signals except RTS and DTR */
- info->signals &= SerialSignal_RTS | SerialSignal_DTR;
-
- if (status & BIT3)
- info->signals |= SerialSignal_DSR;
- if (status & BIT2)
- info->signals |= SerialSignal_CTS;
- if (status & BIT1)
- info->signals |= SerialSignal_DCD;
- if (status & BIT0)
- info->signals |= SerialSignal_RI;
-}
-
-/*
- * set V.24 Control Register based on current configuration
- */
-static void msc_set_vcr(struct slgt_info *info)
-{
- unsigned char val = 0;
-
- /* VCR (V.24 control)
- *
- * 07..04 serial IF select
- * 03 DTR
- * 02 RTS
- * 01 LL
- * 00 RL
- */
-
- switch(info->if_mode & MGSL_INTERFACE_MASK)
- {
- case MGSL_INTERFACE_RS232:
- val |= BIT5; /* 0010 */
- break;
- case MGSL_INTERFACE_V35:
- val |= BIT7 + BIT6 + BIT5; /* 1110 */
- break;
- case MGSL_INTERFACE_RS422:
- val |= BIT6; /* 0100 */
- break;
- }
-
- if (info->if_mode & MGSL_INTERFACE_MSB_FIRST)
- val |= BIT4;
- if (info->signals & SerialSignal_DTR)
- val |= BIT3;
- if (info->signals & SerialSignal_RTS)
- val |= BIT2;
- if (info->if_mode & MGSL_INTERFACE_LL)
- val |= BIT1;
- if (info->if_mode & MGSL_INTERFACE_RL)
- val |= BIT0;
- wr_reg8(info, VCR, val);
-}
-
-/*
- * set state of V24 control (output) signals
- */
-static void set_gtsignals(struct slgt_info *info)
-{
- unsigned char val = rd_reg8(info, VCR);
- if (info->signals & SerialSignal_DTR)
- val |= BIT3;
- else
- val &= ~BIT3;
- if (info->signals & SerialSignal_RTS)
- val |= BIT2;
- else
- val &= ~BIT2;
- wr_reg8(info, VCR, val);
-}
-
-/*
- * free range of receive DMA buffers (i to last)
- */
-static void free_rbufs(struct slgt_info *info, unsigned int i, unsigned int last)
-{
- int done = 0;
-
- while(!done) {
- /* reset current buffer for reuse */
- info->rbufs[i].status = 0;
- set_desc_count(info->rbufs[i], info->rbuf_fill_level);
- if (i == last)
- done = 1;
- if (++i == info->rbuf_count)
- i = 0;
- }
- info->rbuf_current = i;
-}
-
-/*
- * mark all receive DMA buffers as free
- */
-static void reset_rbufs(struct slgt_info *info)
-{
- free_rbufs(info, 0, info->rbuf_count - 1);
- info->rbuf_fill_index = 0;
- info->rbuf_fill_count = 0;
-}
-
-/*
- * pass receive HDLC frame to upper layer
- *
- * return true if frame available, otherwise false
- */
-static bool rx_get_frame(struct slgt_info *info)
-{
- unsigned int start, end;
- unsigned short status;
- unsigned int framesize = 0;
- unsigned long flags;
- struct tty_struct *tty = info->port.tty;
- unsigned char addr_field = 0xff;
- unsigned int crc_size = 0;
-
- switch (info->params.crc_type & HDLC_CRC_MASK) {
- case HDLC_CRC_16_CCITT: crc_size = 2; break;
- case HDLC_CRC_32_CCITT: crc_size = 4; break;
- }
-
-check_again:
-
- framesize = 0;
- addr_field = 0xff;
- start = end = info->rbuf_current;
-
- for (;;) {
- if (!desc_complete(info->rbufs[end]))
- goto cleanup;
-
- if (framesize == 0 && info->params.addr_filter != 0xff)
- addr_field = info->rbufs[end].buf[0];
-
- framesize += desc_count(info->rbufs[end]);
-
- if (desc_eof(info->rbufs[end]))
- break;
-
- if (++end == info->rbuf_count)
- end = 0;
-
- if (end == info->rbuf_current) {
- if (info->rx_enabled){
- spin_lock_irqsave(&info->lock,flags);
- rx_start(info);
- spin_unlock_irqrestore(&info->lock,flags);
- }
- goto cleanup;
- }
- }
-
- /* status
- *
- * 15 buffer complete
- * 14..06 reserved
- * 05..04 residue
- * 02 eof (end of frame)
- * 01 CRC error
- * 00 abort
- */
- status = desc_status(info->rbufs[end]);
-
- /* ignore CRC bit if not using CRC (bit is undefined) */
- if ((info->params.crc_type & HDLC_CRC_MASK) == HDLC_CRC_NONE)
- status &= ~BIT1;
-
- if (framesize == 0 ||
- (addr_field != 0xff && addr_field != info->params.addr_filter)) {
- free_rbufs(info, start, end);
- goto check_again;
- }
-
- if (framesize < (2 + crc_size) || status & BIT0) {
- info->icount.rxshort++;
- framesize = 0;
- } else if (status & BIT1) {
- info->icount.rxcrc++;
- if (!(info->params.crc_type & HDLC_CRC_RETURN_EX))
- framesize = 0;
- }
-
-#if SYNCLINK_GENERIC_HDLC
- if (framesize == 0) {
- info->netdev->stats.rx_errors++;
- info->netdev->stats.rx_frame_errors++;
- }
-#endif
-
- DBGBH(("%s rx frame status=%04X size=%d\n",
- info->device_name, status, framesize));
- DBGDATA(info, info->rbufs[start].buf, min_t(int, framesize, info->rbuf_fill_level), "rx");
-
- if (framesize) {
- if (!(info->params.crc_type & HDLC_CRC_RETURN_EX)) {
- framesize -= crc_size;
- crc_size = 0;
- }
-
- if (framesize > info->max_frame_size + crc_size)
- info->icount.rxlong++;
- else {
- /* copy dma buffer(s) to contiguous temp buffer */
- int copy_count = framesize;
- int i = start;
- unsigned char *p = info->tmp_rbuf;
- info->tmp_rbuf_count = framesize;
-
- info->icount.rxok++;
-
- while(copy_count) {
- int partial_count = min_t(int, copy_count, info->rbuf_fill_level);
- memcpy(p, info->rbufs[i].buf, partial_count);
- p += partial_count;
- copy_count -= partial_count;
- if (++i == info->rbuf_count)
- i = 0;
- }
-
- if (info->params.crc_type & HDLC_CRC_RETURN_EX) {
- *p = (status & BIT1) ? RX_CRC_ERROR : RX_OK;
- framesize++;
- }
-
-#if SYNCLINK_GENERIC_HDLC
- if (info->netcount)
- hdlcdev_rx(info,info->tmp_rbuf, framesize);
- else
-#endif
- ldisc_receive_buf(tty, info->tmp_rbuf, NULL,
- framesize);
- }
- }
- free_rbufs(info, start, end);
- return true;
-
-cleanup:
- return false;
-}
-
-/*
- * pass receive buffer (RAW synchronous mode) to tty layer
- * return true if buffer available, otherwise false
- */
-static bool rx_get_buf(struct slgt_info *info)
-{
- unsigned int i = info->rbuf_current;
- unsigned int count;
-
- if (!desc_complete(info->rbufs[i]))
- return false;
- count = desc_count(info->rbufs[i]);
- switch(info->params.mode) {
- case MGSL_MODE_MONOSYNC:
- case MGSL_MODE_BISYNC:
- case MGSL_MODE_XSYNC:
- /* ignore residue in byte synchronous modes */
- if (desc_residue(info->rbufs[i]))
- count--;
- break;
- }
- DBGDATA(info, info->rbufs[i].buf, count, "rx");
- DBGINFO(("rx_get_buf size=%d\n", count));
- if (count)
- ldisc_receive_buf(info->port.tty, info->rbufs[i].buf, NULL,
- count);
- free_rbufs(info, i, i);
- return true;
-}
-
-static void reset_tbufs(struct slgt_info *info)
-{
- unsigned int i;
- info->tbuf_current = 0;
- for (i=0 ; i < info->tbuf_count ; i++) {
- info->tbufs[i].status = 0;
- info->tbufs[i].count = 0;
- }
-}
-
-/*
- * return number of free transmit DMA buffers
- */
-static unsigned int free_tbuf_count(struct slgt_info *info)
-{
- unsigned int count = 0;
- unsigned int i = info->tbuf_current;
-
- do
- {
- if (desc_count(info->tbufs[i]))
- break; /* buffer in use */
- ++count;
- if (++i == info->tbuf_count)
- i=0;
- } while (i != info->tbuf_current);
-
- /* if tx DMA active, last zero count buffer is in use */
- if (count && (rd_reg32(info, TDCSR) & BIT0))
- --count;
-
- return count;
-}
-
-/*
- * return number of bytes in unsent transmit DMA buffers
- * and the serial controller tx FIFO
- */
-static unsigned int tbuf_bytes(struct slgt_info *info)
-{
- unsigned int total_count = 0;
- unsigned int i = info->tbuf_current;
- unsigned int reg_value;
- unsigned int count;
- unsigned int active_buf_count = 0;
-
- /*
- * Add descriptor counts for all tx DMA buffers.
- * If count is zero (cleared by DMA controller after read),
- * the buffer is complete or is actively being read from.
- *
- * Record buf_count of last buffer with zero count starting
- * from current ring position. buf_count is mirror
- * copy of count and is not cleared by serial controller.
- * If DMA controller is active, that buffer is actively
- * being read so add to total.
- */
- do {
- count = desc_count(info->tbufs[i]);
- if (count)
- total_count += count;
- else if (!total_count)
- active_buf_count = info->tbufs[i].buf_count;
- if (++i == info->tbuf_count)
- i = 0;
- } while (i != info->tbuf_current);
-
- /* read tx DMA status register */
- reg_value = rd_reg32(info, TDCSR);
-
- /* if tx DMA active, last zero count buffer is in use */
- if (reg_value & BIT0)
- total_count += active_buf_count;
-
- /* add tx FIFO count = reg_value[15..8] */
- total_count += (reg_value >> 8) & 0xff;
-
- /* if transmitter active add one byte for shift register */
- if (info->tx_active)
- total_count++;
-
- return total_count;
-}
-
-/*
- * load data into transmit DMA buffer ring and start transmitter if needed
- * return true if data accepted, otherwise false (buffers full)
- */
-static bool tx_load(struct slgt_info *info, const u8 *buf, unsigned int size)
-{
- unsigned short count;
- unsigned int i;
- struct slgt_desc *d;
-
- /* check required buffer space */
- if (DIV_ROUND_UP(size, DMABUFSIZE) > free_tbuf_count(info))
- return false;
-
- DBGDATA(info, buf, size, "tx");
-
- /*
- * copy data to one or more DMA buffers in circular ring
- * tbuf_start = first buffer for this data
- * tbuf_current = next free buffer
- *
- * Copy all data before making data visible to DMA controller by
- * setting descriptor count of the first buffer.
- * This prevents an active DMA controller from reading the first DMA
- * buffers of a frame and stopping before the final buffers are filled.
- */
-
- info->tbuf_start = i = info->tbuf_current;
-
- while (size) {
- d = &info->tbufs[i];
-
- count = (unsigned short)((size > DMABUFSIZE) ? DMABUFSIZE : size);
- memcpy(d->buf, buf, count);
-
- size -= count;
- buf += count;
-
- /*
- * set EOF bit for last buffer of HDLC frame or
- * for every buffer in raw mode
- */
- if ((!size && info->params.mode == MGSL_MODE_HDLC) ||
- info->params.mode == MGSL_MODE_RAW)
- set_desc_eof(*d, 1);
- else
- set_desc_eof(*d, 0);
-
- /* set descriptor count for all but first buffer */
- if (i != info->tbuf_start)
- set_desc_count(*d, count);
- d->buf_count = count;
-
- if (++i == info->tbuf_count)
- i = 0;
- }
-
- info->tbuf_current = i;
-
- /* set first buffer count to make new data visible to DMA controller */
- d = &info->tbufs[info->tbuf_start];
- set_desc_count(*d, d->buf_count);
-
- /* start transmitter if needed and update transmit timeout */
- if (!info->tx_active)
- tx_start(info);
- update_tx_timer(info);
-
- return true;
-}
-
-static int register_test(struct slgt_info *info)
-{
- static unsigned short patterns[] =
- {0x0000, 0xffff, 0xaaaa, 0x5555, 0x6969, 0x9696};
- static unsigned int count = ARRAY_SIZE(patterns);
- unsigned int i;
- int rc = 0;
-
- for (i=0 ; i < count ; i++) {
- wr_reg16(info, TIR, patterns[i]);
- wr_reg16(info, BDR, patterns[(i+1)%count]);
- if ((rd_reg16(info, TIR) != patterns[i]) ||
- (rd_reg16(info, BDR) != patterns[(i+1)%count])) {
- rc = -ENODEV;
- break;
- }
- }
- info->gpio_present = (rd_reg32(info, JCR) & BIT5) ? 1 : 0;
- info->init_error = rc ? 0 : DiagStatus_AddressFailure;
- return rc;
-}
-
-static int irq_test(struct slgt_info *info)
-{
- unsigned long timeout;
- unsigned long flags;
- struct tty_struct *oldtty = info->port.tty;
- u32 speed = info->params.data_rate;
-
- info->params.data_rate = 921600;
- info->port.tty = NULL;
-
- spin_lock_irqsave(&info->lock, flags);
- async_mode(info);
- slgt_irq_on(info, IRQ_TXIDLE);
-
- /* enable transmitter */
- wr_reg16(info, TCR,
- (unsigned short)(rd_reg16(info, TCR) | BIT1));
-
- /* write one byte and wait for tx idle */
- wr_reg16(info, TDR, 0);
-
- /* assume failure */
- info->init_error = DiagStatus_IrqFailure;
- info->irq_occurred = false;
-
- spin_unlock_irqrestore(&info->lock, flags);
-
- timeout=100;
- while(timeout-- && !info->irq_occurred)
- msleep_interruptible(10);
-
- spin_lock_irqsave(&info->lock,flags);
- reset_port(info);
- spin_unlock_irqrestore(&info->lock,flags);
-
- info->params.data_rate = speed;
- info->port.tty = oldtty;
-
- info->init_error = info->irq_occurred ? 0 : DiagStatus_IrqFailure;
- return info->irq_occurred ? 0 : -ENODEV;
-}
-
-static int loopback_test_rx(struct slgt_info *info)
-{
- unsigned char *src, *dest;
- int count;
-
- if (desc_complete(info->rbufs[0])) {
- count = desc_count(info->rbufs[0]);
- src = info->rbufs[0].buf;
- dest = info->tmp_rbuf;
-
- for( ; count ; count-=2, src+=2) {
- /* src=data byte (src+1)=status byte */
- if (!(*(src+1) & (BIT9 + BIT8))) {
- *dest = *src;
- dest++;
- info->tmp_rbuf_count++;
- }
- }
- DBGDATA(info, info->tmp_rbuf, info->tmp_rbuf_count, "rx");
- return 1;
- }
- return 0;
-}
-
-static int loopback_test(struct slgt_info *info)
-{
-#define TESTFRAMESIZE 20
-
- unsigned long timeout;
- u16 count;
- unsigned char buf[TESTFRAMESIZE];
- int rc = -ENODEV;
- unsigned long flags;
-
- struct tty_struct *oldtty = info->port.tty;
- MGSL_PARAMS params;
-
- memcpy(¶ms, &info->params, sizeof(params));
-
- info->params.mode = MGSL_MODE_ASYNC;
- info->params.data_rate = 921600;
- info->params.loopback = 1;
- info->port.tty = NULL;
-
- /* build and send transmit frame */
- for (count = 0; count < TESTFRAMESIZE; ++count)
- buf[count] = (unsigned char)count;
-
- info->tmp_rbuf_count = 0;
- memset(info->tmp_rbuf, 0, TESTFRAMESIZE);
-
- /* program hardware for HDLC and enabled receiver */
- spin_lock_irqsave(&info->lock,flags);
- async_mode(info);
- rx_start(info);
- tx_load(info, buf, count);
- spin_unlock_irqrestore(&info->lock, flags);
-
- /* wait for receive complete */
- for (timeout = 100; timeout; --timeout) {
- msleep_interruptible(10);
- if (loopback_test_rx(info)) {
- rc = 0;
- break;
- }
- }
-
- /* verify received frame length and contents */
- if (!rc && (info->tmp_rbuf_count != count ||
- memcmp(buf, info->tmp_rbuf, count))) {
- rc = -ENODEV;
- }
-
- spin_lock_irqsave(&info->lock,flags);
- reset_adapter(info);
- spin_unlock_irqrestore(&info->lock,flags);
-
- memcpy(&info->params, ¶ms, sizeof(info->params));
- info->port.tty = oldtty;
-
- info->init_error = rc ? DiagStatus_DmaFailure : 0;
- return rc;
-}
-
-static int adapter_test(struct slgt_info *info)
-{
- DBGINFO(("testing %s\n", info->device_name));
- if (register_test(info) < 0) {
- printk("register test failure %s addr=%08X\n",
- info->device_name, info->phys_reg_addr);
- } else if (irq_test(info) < 0) {
- printk("IRQ test failure %s IRQ=%d\n",
- info->device_name, info->irq_level);
- } else if (loopback_test(info) < 0) {
- printk("loopback test failure %s\n", info->device_name);
- }
- return info->init_error;
-}
-
-/*
- * transmit timeout handler
- */
-static void tx_timeout(struct timer_list *t)
-{
- struct slgt_info *info = timer_container_of(info, t, tx_timer);
- unsigned long flags;
-
- DBGINFO(("%s tx_timeout\n", info->device_name));
- if(info->tx_active && info->params.mode == MGSL_MODE_HDLC) {
- info->icount.txtimeout++;
- }
- spin_lock_irqsave(&info->lock,flags);
- tx_stop(info);
- spin_unlock_irqrestore(&info->lock,flags);
-
-#if SYNCLINK_GENERIC_HDLC
- if (info->netcount)
- hdlcdev_tx_done(info);
- else
-#endif
- bh_transmit(info);
-}
-
-/*
- * receive buffer polling timer
- */
-static void rx_timeout(struct timer_list *t)
-{
- struct slgt_info *info = timer_container_of(info, t, rx_timer);
- unsigned long flags;
-
- DBGINFO(("%s rx_timeout\n", info->device_name));
- spin_lock_irqsave(&info->lock, flags);
- info->pending_bh |= BH_RECEIVE;
- spin_unlock_irqrestore(&info->lock, flags);
- bh_handler(&info->task);
-}
-
diff --git a/include/linux/synclink.h b/include/linux/synclink.h
deleted file mode 100644
index f1405b1c71ba..000000000000
--- a/include/linux/synclink.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * SyncLink Multiprotocol Serial Adapter Driver
- *
- * $Id: synclink.h,v 3.14 2006/07/17 20:15:43 paulkf Exp $
- *
- * Copyright (C) 1998-2000 by Microgate Corporation
- *
- * Redistribution of this file is permitted under
- * the terms of the GNU Public License (GPL)
- */
-#ifndef _SYNCLINK_H_
-#define _SYNCLINK_H_
-
-#include <uapi/linux/synclink.h>
-
-/* provide 32 bit ioctl compatibility on 64 bit systems */
-#ifdef CONFIG_COMPAT
-#include <linux/compat.h>
-struct MGSL_PARAMS32 {
- compat_ulong_t mode;
- unsigned char loopback;
- unsigned short flags;
- unsigned char encoding;
- compat_ulong_t clock_speed;
- unsigned char addr_filter;
- unsigned short crc_type;
- unsigned char preamble_length;
- unsigned char preamble;
- compat_ulong_t data_rate;
- unsigned char data_bits;
- unsigned char stop_bits;
- unsigned char parity;
-};
-#define MGSL_IOCSPARAMS32 _IOW(MGSL_MAGIC_IOC,0,struct MGSL_PARAMS32)
-#define MGSL_IOCGPARAMS32 _IOR(MGSL_MAGIC_IOC,1,struct MGSL_PARAMS32)
-#endif
-#endif /* _SYNCLINK_H_ */
--
2.43.0
^ permalink raw reply related
* Re: [PATCH net-next 5/5] dt-bindings: net: Add bindings for the ADIN1140
From: Andrew Lunn @ 2026-05-03 1:06 UTC (permalink / raw)
To: ciprian.regus
Cc: Parthiban Veerasooran, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Heiner Kallweit, Russell King, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, netdev, linux-kernel,
linux-doc, devicetree
In-Reply-To: <20260503-adin1140-driver-v1-5-dd043cdd88f0@analog.com>
> + The ADIN1140 (also called AD3306) is a low power single port
> + 10BASE-T1S MAC-PHY. It integrates an Ethernet PHY with a MAC
> + and all the associated analog circuitry.
> + The device implements the Open Alliance TC6 10BASE-T1x MAC-PHY
The device _tries_ to implements the Open Alliance TC6 10BASE-T1x MAC-PHY.
> +examples:
> + - |
> + #include <dt-bindings/interrupt-controller/irq.h>
> +
> + spi {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + ethernet@0 {
> + compatible = "adi,adin1140";
> + reg = <0>;
> + spi-max-frequency = <23000000>;
> +
> + interrupt-parent = <&gpio>;
> + interrupts = <6 IRQ_TYPE_EDGE_FALLING>;
Table 1: OPEN serial 10BASE-T1x Interface Pin Definition
IRQn MAC-PHY Interrupt Request (Active Low)
Or is this something else which the device gets wrong?
Andrew
^ permalink raw reply
* Re: [PATCH net-next 4/5] net: ethernet: adi: Add a driver for the ADIN1140 MACPHY
From: Andrew Lunn @ 2026-05-03 1:01 UTC (permalink / raw)
To: ciprian.regus
Cc: Parthiban Veerasooran, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Heiner Kallweit, Russell King, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, netdev, linux-kernel,
linux-doc, devicetree
In-Reply-To: <20260503-adin1140-driver-v1-4-dd043cdd88f0@analog.com>
> +static int adin1140_ioctl(struct net_device *netdev, struct ifreq *rq, int cmd)
> +{
> + if (!netif_running(netdev))
> + return -EINVAL;
> +
> + return phy_do_ioctl(netdev, rq, cmd);
> +}
phy_do_ioctl_running()
Andrew
^ permalink raw reply
* Re: [PATCH net-next 4/5] net: ethernet: adi: Add a driver for the ADIN1140 MACPHY
From: Andrew Lunn @ 2026-05-03 0:59 UTC (permalink / raw)
To: ciprian.regus
Cc: Parthiban Veerasooran, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Heiner Kallweit, Russell King, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, netdev, linux-kernel,
linux-doc, devicetree
In-Reply-To: <20260503-adin1140-driver-v1-4-dd043cdd88f0@analog.com>
> +static int adin1140_get_phy_c45_mms(int devnum)
> +{
> + switch (devnum) {
> + case MDIO_MMD_PCS:
> + return ADIN1140_PHY_C45_PCS_MMS2;
> + case MDIO_MMD_PMAPMD:
> + return ADIN1140_PHY_C45_PMA_PMD_MMS3;
> + case MDIO_MMD_VEND2:
> + return ADIN1140_PHY_C45_VS_PLCA_MMS4;
> + default:
> + return devnum;
> + }
> +}
> +
> +static int adin1140_mdiobus_read_c45(struct mii_bus *bus, int addr,
> + int devnum, int regnum)
> +{
> + struct oa_tc6 *tc6 = bus->priv;
> + u32 regval;
> + u32 mms;
> + int ret;
> +
> + mms = adin1140_get_phy_c45_mms(devnum);
> + ret = oa_tc6_read_register(tc6, ADIN1140_MMS_REG(mms, regnum),
> + ®val);
> + if (ret)
> + return ret;
> +
> + return regval;
> +}
> +
> +static int adin1140_mdiobus_write_c45(struct mii_bus *bus, int addr,
> + int devnum, int regnum, u16 val)
> +{
> + struct oa_tc6 *tc6 = bus->priv;
> + int ret;
> +
> + ret = adin1140_get_phy_c45_mms(devnum);
> + if (ret < 0)
> + return ret;
> +
> + return oa_tc6_write_register(tc6, ADIN1140_MMS_REG(ret, regnum), val);
> +}
At a quick look, these seem the same as oa_tc6_mdiobus_read_c45() and
oa_tc6_mdiobus_write_c45(). Please export them and use them.
> +static int adin1140_mdiobus_read(struct mii_bus *bus, int addr, int regnum)
> +{
> + struct oa_tc6 *tc6 = bus->priv;
> + u32 reg_val;
> + int ret;
> +
> + /* The ADIN1140's standard PHY C22 register map (OA TC6 0xFF00 -
> + * 0xFF1F), of which only 0xFF00 - 0xFF03 are implemented) cannot be
> + * accessed while frames are being received by the PHY. In case this
> + * happens the CONFIG0 and CONFIG2 register values will get corrupted,
> + * getting a random value. Both reads and writes cause the same
> + * behavior. This is a workaround that avoids MDIO accesses all
> + * together. Since this is a 10BASE-T1S PHY, only the loopback and
> + * reset (AN) bits in the control register (0x0) can be written.
> + * These functionalities have custom implementations in the PHY
> + * driver. Since the MAC and PHY are integrated in the same device, we
> + * can read the OA TC6 MACPHY ID register instead of the PHYID (0x2
> + * and 0x3) ones, as their value matches. C45 accesses do not cause
> + * this issue.
> + */
> +
> + switch (regnum) {
> + case MII_BMCR:
> + return ADIN1140_PHY_CTRL_DEFAULT;
> + case MII_BMSR:
> + return ADIN1140_PHY_STATUS_DEFAULT;
> + case MII_PHYSID1:
> + ret = oa_tc6_read_register(tc6, ADIN1140_MACPHY_ID_REG,
> + ®_val);
> + if (ret)
> + return ret;
> +
> + return FIELD_GET(GENMASK(31, 16), reg_val);
> + case MII_PHYSID2:
> + ret = oa_tc6_read_register(tc6, ADIN1140_MACPHY_ID_REG,
> + ®_val);
> + if (ret)
> + return ret;
Is it even worth reading this register? Why not hard code this as
well? Or do you expect a new version of the device which is less
FUBAR, and having a different PHY ID?
> +static int adin1140_mdiobus_write(struct mii_bus *bus, int addr, int regnum,
> + u16 val)
> +{
> + return 0;
-EIO. Since writes are not support, you want to know if something
actually does a write.
> +static int adin1140_mdio_register(struct adin1140_priv *priv)
> +{
> + priv->mdiobus = mdiobus_alloc();
> + if (!priv->mdiobus) {
> + netdev_err(priv->netdev, "MDIO bus alloc failed\n");
> + return -ENOMEM;
> + }
> +
> + priv->mdiobus->read = adin1140_mdiobus_read;
> + priv->mdiobus->write = adin1140_mdiobus_write;
> + priv->mdiobus->read_c45 = adin1140_mdiobus_read_c45;
> + priv->mdiobus->write_c45 = adin1140_mdiobus_write_c45;
Name? id?
Andrew
^ permalink raw reply
* [PATCH v2] Documentation: gpu: todo: fix typo 'themsevles' -> 'themselves'
From: Francisco Maestre @ 2026-05-03 0:57 UTC (permalink / raw)
To: airlied, simona, maarten.lankhorst, mripard, tzimmermann, corbet
Cc: dri-devel, linux-doc, linux-kernel, Francisco Maestre
Fix a spelling mistake in the panel-simple/panel-edp TODO section.
Signed-off-by: Francisco Maestre <francisco@maestretorreblanca.com>
---
v2: Resend as individual patch, not part of an unrelated series.
Documentation/gpu/todo.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Documentation/gpu/todo.rst b/Documentation/gpu/todo.rst
index 520da44a04a6..e371134782f8 100644
--- a/Documentation/gpu/todo.rst
+++ b/Documentation/gpu/todo.rst
@@ -456,7 +456,7 @@ be turned into a WARN_ON() or somehow made louder.
At the moment, we expect that we may still encounter the warnings in the
drm_panel core when using panel-simple and panel-edp. Since those panel
drivers are used with a lot of different DRM modeset drivers they still
-make an extra effort to disable/unprepare the panel themsevles at shutdown
+make an extra effort to disable/unprepare the panel themselves at shutdown
time. Specifically we could still encounter those warnings if the panel
driver gets shutdown() _before_ the DRM modeset driver and the DRM modeset
driver properly calls drm_atomic_helper_shutdown() in its own shutdown()
--
2.50.1 (Apple Git-155)
^ permalink raw reply related
* Re: [PATCH net-next 3/5] net: phy: Add support for the ADIN1140 PHY
From: Andrew Lunn @ 2026-05-03 0:40 UTC (permalink / raw)
To: ciprian.regus
Cc: Parthiban Veerasooran, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Heiner Kallweit, Russell King, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, netdev, linux-kernel,
linux-doc, devicetree
In-Reply-To: <20260503-adin1140-driver-v1-3-dd043cdd88f0@analog.com>
> +static int adin1140_phy_read_mmd(struct phy_device *phydev, int devnum,
> + u16 regnum)
> +{
> + struct mii_bus *bus = phydev->mdio.bus;
> + int addr = phydev->mdio.addr;
> +
> + return __mdiobus_c45_read(bus, addr, devnum, regnum);
> +}
> +
> +static int adin1140_phy_write_mmd(struct phy_device *phydev, int devnum,
> + u16 regnum, u16 val)
> +{
> + struct mii_bus *bus = phydev->mdio.bus;
> + int addr = phydev->mdio.addr;
> +
> + return __mdiobus_c45_write(bus, addr, devnum, regnum, val);
> +}
Why do these exist?
> +static int adin1140_config_init(struct phy_device *phydev)
> +{
> + /* The link status of the PHY doesn't need to be polled, because
> + * the device doesn't implement AN and there is no other mechanism
> + * to report the link state.
> + */
> + phydev->irq = PHY_MAC_INTERRUPT;
I would prefer you don't abuse this.
> +static int adin1140_read_status(struct phy_device *phydev)
> +{
> + phydev->link = 1;
> + phydev->duplex = DUPLEX_HALF;
> + phydev->speed = SPEED_10;
> + phydev->autoneg = AUTONEG_DISABLE;
> +
> + return 0;
> +}
This should have no really cost, so just let phylib poll.
Andrew
^ permalink raw reply
* [PATCH 3/3] Documentation: gpu: todo: fix typo 'themsevles' -> 'themselves'
From: Francisco Maestre @ 2026-05-03 0:35 UTC (permalink / raw)
To: airlied, simona, maarten.lankhorst, mripard, tzimmermann, corbet
Cc: dri-devel, linux-doc, linux-kernel, Francisco Maestre
Fix a spelling mistake in the panel-simple/panel-edp TODO section.
Signed-off-by: Francisco Maestre <francisco@maestretorreblanca.com>
---
Documentation/gpu/todo.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Documentation/gpu/todo.rst b/Documentation/gpu/todo.rst
index 520da44a04a6..e371134782f8 100644
--- a/Documentation/gpu/todo.rst
+++ b/Documentation/gpu/todo.rst
@@ -456,7 +456,7 @@ be turned into a WARN_ON() or somehow made louder.
At the moment, we expect that we may still encounter the warnings in the
drm_panel core when using panel-simple and panel-edp. Since those panel
drivers are used with a lot of different DRM modeset drivers they still
-make an extra effort to disable/unprepare the panel themsevles at shutdown
+make an extra effort to disable/unprepare the panel themselves at shutdown
time. Specifically we could still encounter those warnings if the panel
driver gets shutdown() _before_ the DRM modeset driver and the DRM modeset
driver properly calls drm_atomic_helper_shutdown() in its own shutdown()
--
2.50.1 (Apple Git-155)
^ permalink raw reply related
* [PATCH 2/2] docs/dyndbg: explain flags parse 1st
From: Jim Cromie @ 2026-05-02 23:32 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan
Cc: linux-doc, linux-kernel, Jim Cromie, Louis Chauvet
In-Reply-To: <20260502-dyndbg-doc-v1-0-67cc4a93a77e@gmail.com>
When writing queries to >control, flags are parsed 1st, since they are
the only required field, and they require specific compositions. So
if the flags draw an error (on those specifics), then keyword errors
aren't reported. This can be mildly confusing/annoying, so explain it
instead.
cc: linux-doc@vger.kernel.org
Reviewed-by: Louis Chauvet <louis.chauvet@bootlin.com>
Signed-off-by: Jim Cromie <jim.cromie@gmail.com>
---
Documentation/admin-guide/dynamic-debug-howto.rst | 17 +++++++++++++----
1 file changed, 13 insertions(+), 4 deletions(-)
diff --git a/Documentation/admin-guide/dynamic-debug-howto.rst b/Documentation/admin-guide/dynamic-debug-howto.rst
index 4b14d9fd0300..9c2f096ed1d8 100644
--- a/Documentation/admin-guide/dynamic-debug-howto.rst
+++ b/Documentation/admin-guide/dynamic-debug-howto.rst
@@ -109,10 +109,19 @@ The match-spec's select *prdbgs* from the catalog, upon which to apply
the flags-spec, all constraints are ANDed together. An absent keyword
is the same as keyword "*".
-
-A match specification is a keyword, which selects the attribute of
-the callsite to be compared, and a value to compare against. Possible
-keywords are:::
+Note that since the match-spec can be empty, the flags are checked 1st,
+then the pairs of keyword and value. Flag errs will hide keyword errs::
+
+ bash-5.2# ddcmd mod bar +foo
+ dyndbg: read 13 bytes from userspace
+ dyndbg: query 0: "mod bar +foo" mod:*
+ dyndbg: unknown flag 'o'
+ dyndbg: flags parse failed
+ dyndbg: processed 1 queries, with 0 matches, 1 errs
+
+So a match-spec is a keyword, which selects the attribute of the
+callsite to be compared, and a value to compare against. Possible
+keywords are::
match-spec ::= 'func' string |
'file' string |
--
2.54.0
^ permalink raw reply related
* [PATCH 1/2] docs/dyndbg: update examples \012 to \n
From: Jim Cromie @ 2026-05-02 23:32 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan
Cc: linux-doc, linux-kernel, Jim Cromie, Louis Chauvet
In-Reply-To: <20260502-dyndbg-doc-v1-0-67cc4a93a77e@gmail.com>
commit 47ea6f99d06e ("dyndbg: use ESCAPE_SPACE for cat control")
changed the control-file to display format strings with "\n" rather
than "\012". Update the docs to match the new reality.
Reviewed-by: Louis Chauvet <louis.chauvet@bootlin.com>
Tested-by: Louis Chauvet <louis.chauvet@bootlin.com>
Signed-off-by: Jim Cromie <jim.cromie@gmail.com>
---
Documentation/admin-guide/dynamic-debug-howto.rst | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/Documentation/admin-guide/dynamic-debug-howto.rst b/Documentation/admin-guide/dynamic-debug-howto.rst
index 095a63892257..4b14d9fd0300 100644
--- a/Documentation/admin-guide/dynamic-debug-howto.rst
+++ b/Documentation/admin-guide/dynamic-debug-howto.rst
@@ -38,12 +38,12 @@ You can view the currently configured behaviour in the *prdbg* catalog::
:#> head -n7 /proc/dynamic_debug/control
# filename:lineno [module]function flags format
- init/main.c:1179 [main]initcall_blacklist =_ "blacklisting initcall %s\012
- init/main.c:1218 [main]initcall_blacklisted =_ "initcall %s blacklisted\012"
- init/main.c:1424 [main]run_init_process =_ " with arguments:\012"
- init/main.c:1426 [main]run_init_process =_ " %s\012"
- init/main.c:1427 [main]run_init_process =_ " with environment:\012"
- init/main.c:1429 [main]run_init_process =_ " %s\012"
+ init/main.c:1179 [main]initcall_blacklist =_ "blacklisting initcall %s\n"
+ init/main.c:1218 [main]initcall_blacklisted =_ "initcall %s blacklisted\n"
+ init/main.c:1424 [main]run_init_process =_ " with arguments:\n"
+ init/main.c:1426 [main]run_init_process =_ " %s\n"
+ init/main.c:1427 [main]run_init_process =_ " with environment:\n"
+ init/main.c:1429 [main]run_init_process =_ " %s\n"
The 3rd space-delimited column shows the current flags, preceded by
a ``=`` for easy use with grep/cut. ``=p`` shows enabled callsites.
@@ -59,10 +59,10 @@ query/commands to the control file. Example::
:#> ddcmd '-p; module main func run* +p'
:#> grep =p /proc/dynamic_debug/control
- init/main.c:1424 [main]run_init_process =p " with arguments:\012"
- init/main.c:1426 [main]run_init_process =p " %s\012"
- init/main.c:1427 [main]run_init_process =p " with environment:\012"
- init/main.c:1429 [main]run_init_process =p " %s\012"
+ init/main.c:1424 [main]run_init_process =p " with arguments:\n"
+ init/main.c:1426 [main]run_init_process =p " %s\n"
+ init/main.c:1427 [main]run_init_process =p " with environment:\n"
+ init/main.c:1429 [main]run_init_process =p " %s\n"
Error messages go to console/syslog::
--
2.54.0
^ permalink raw reply related
* [PATCH 0/2] 2 dydnbg doc fixes
From: Jim Cromie @ 2026-05-02 23:32 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan
Cc: linux-doc, linux-kernel, Jim Cromie, Louis Chauvet
1st swaps \012 for \n to match actual output in dynamic_debug/control
2nd explains that flags input to >dynamic_debug/control is checked
before keyword value pairs, which might not be there.
Both reflect current code behavior.
Signed-off-by: Jim Cromie <jim.cromie@gmail.com>
---
Jim Cromie (2):
docs/dyndbg: update examples \012 to \n
docs/dyndbg: explain flags parse 1st
Documentation/admin-guide/dynamic-debug-howto.rst | 35 ++++++++++++++---------
1 file changed, 22 insertions(+), 13 deletions(-)
---
base-commit: 254f49634ee16a731174d2ae34bc50bd5f45e731
change-id: 20260502-dyndbg-doc-7b56eb4c1b1f
Best regards,
--
Jim Cromie <jim.cromie@gmail.com>
^ permalink raw reply
* [PATCH net-next 5/5] dt-bindings: net: Add bindings for the ADIN1140
From: Ciprian Regus via B4 Relay @ 2026-05-02 23:24 UTC (permalink / raw)
To: Parthiban Veerasooran, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Andrew Lunn, Heiner Kallweit, Russell King,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: netdev, linux-kernel, linux-doc, devicetree, Ciprian Regus
In-Reply-To: <20260503-adin1140-driver-v1-0-dd043cdd88f0@analog.com>
From: Ciprian Regus <ciprian.regus@analog.com>
Add DT bindings for the ADIN1140 10BASE-T1S MACPHY. Update the
MAINTAINERS entry to include the bindings file as well.
Signed-off-by: Ciprian Regus <ciprian.regus@analog.com>
---
.../devicetree/bindings/net/adi,adin1140.yaml | 69 ++++++++++++++++++++++
MAINTAINERS | 1 +
2 files changed, 70 insertions(+)
diff --git a/Documentation/devicetree/bindings/net/adi,adin1140.yaml b/Documentation/devicetree/bindings/net/adi,adin1140.yaml
new file mode 100644
index 000000000000..26cd40d36f9b
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/adi,adin1140.yaml
@@ -0,0 +1,69 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/adi,adin1140.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: ADI ADIN1140 10BASE-T1S MAC-PHY
+
+maintainers:
+ - Ciprian Regus <ciprian.regus@analog.com>
+
+description: |
+ The ADIN1140 (also called AD3306) is a low power single port
+ 10BASE-T1S MAC-PHY. It integrates an Ethernet PHY with a MAC
+ and all the associated analog circuitry.
+ The device implements the Open Alliance TC6 10BASE-T1x MAC-PHY
+ Serial Interface specification and is compliant with the
+ IEEE 802.3cg-2019 Ethernet standard for 10 Mbps single pair
+ Ethernet (SPE). The device has a 4-wire SPI interface for
+ communication between the MAC and host processor.
+
+allOf:
+ - $ref: /schemas/net/ethernet-controller.yaml#
+ - $ref: /schemas/spi/spi-peripheral-props.yaml#
+
+properties:
+ compatible:
+ enum:
+ - adi,adin1140
+ - adi,ad3306
+
+ reg:
+ maxItems: 1
+
+ spi-max-frequency:
+ maximum: 25000000
+
+ interrupts:
+ maxItems: 1
+ description: Interrupt from the MAC-PHY for receive data available
+ and error conditions
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - spi-max-frequency
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ethernet@0 {
+ compatible = "adi,adin1140";
+ reg = <0>;
+ spi-max-frequency = <23000000>;
+
+ interrupt-parent = <&gpio>;
+ interrupts = <6 IRQ_TYPE_EDGE_FALLING>;
+
+ local-mac-address = [ 00 11 22 33 44 55 ];
+ };
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index f9784c25beac..55e1e78fe04e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1848,6 +1848,7 @@ M: Ciprian Regus <ciprian.regus@analog.com>
L: netdev@vger.kernel.org
S: Maintained
W: https://ez.analog.com/linux-software-drivers
+F: Documentation/devicetree/bindings/net/adi,adin1140.yaml
F: drivers/net/ethernet/adi/adin1140.c
ANALOG DEVICES INC ETHERNET PHY DRIVERS
--
2.43.0
^ permalink raw reply related
* [PATCH net-next 3/5] net: phy: Add support for the ADIN1140 PHY
From: Ciprian Regus via B4 Relay @ 2026-05-02 23:24 UTC (permalink / raw)
To: Parthiban Veerasooran, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Andrew Lunn, Heiner Kallweit, Russell King,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: netdev, linux-kernel, linux-doc, devicetree, Ciprian Regus
In-Reply-To: <20260503-adin1140-driver-v1-0-dd043cdd88f0@analog.com>
From: Ciprian Regus <ciprian.regus@analog.com>
Add a driver for the ADIN1140's internal 10BASE-T1S PHY. The device
doesn't implement autonegotiation, so the link is always reported as
being up. Since the PHY has no link-change interrupts and the link is
always up, we set phydev->irq = PHY_MAC_INTERRUPT to prevent phylib from
polling the link state.
The device implements both C22 and C45 MDIO access methods, but can only
be discovered over C22, since the C45 MMD devices lack the MDIO_DEVID1 and
MDIO_DEVID2 registers. The indirect C45 over C22 feature is not
supported.
Signed-off-by: Ciprian Regus <ciprian.regus@analog.com>
---
MAINTAINERS | 7 ++++
drivers/net/phy/Kconfig | 6 +++
drivers/net/phy/Makefile | 1 +
drivers/net/phy/adin1140.c | 102 +++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 116 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 27a073f53cea..1e58da5ef47a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1843,6 +1843,13 @@ S: Supported
W: https://ez.analog.com/linux-software-drivers
F: drivers/dma/dma-axi-dmac.c
+ANALOG DEVICES INC ETHERNET PHY DRIVERS
+M: Ciprian Regus <ciprian.regus@analog.com>
+L: netdev@vger.kernel.org
+S: Maintained
+W: https://ez.analog.com/linux-software-drivers
+F: drivers/net/phy/adin1140.c
+
ANALOG DEVICES INC IIO DRIVERS
M: Lars-Peter Clausen <lars@metafoo.de>
M: Michael Hennerich <Michael.Hennerich@analog.com>
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index b5ee338b620d..fa5cd59a3825 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -124,6 +124,12 @@ config ADIN1100_PHY
Currently supports the:
- ADIN1100 - Robust,Industrial, Low Power 10BASE-T1L Ethernet PHY
+config ADIN1140_PHY
+ tristate "Analog Devices ADIN1140 10BASE-T1S PHY"
+ help
+ Adds support for the Analog Devices, Inc. ADIN1140's internal
+ 10BASE-T1S PHY.
+
config AMCC_QT2025_PHY
tristate "AMCC QT2025 PHY"
depends on RUST_PHYLIB_ABSTRACTIONS
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index 05e4878af27a..2519364bc334 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -29,6 +29,7 @@ obj-y += $(sfp-obj-y) $(sfp-obj-m)
obj-$(CONFIG_ADIN_PHY) += adin.o
obj-$(CONFIG_ADIN1100_PHY) += adin1100.o
+obj-$(CONFIG_ADIN1140_PHY) += adin1140.o
obj-$(CONFIG_AIR_EN8811H_PHY) += air_en8811h.o
obj-$(CONFIG_AMD_PHY) += amd.o
obj-$(CONFIG_AMCC_QT2025_PHY) += qt2025.o
diff --git a/drivers/net/phy/adin1140.c b/drivers/net/phy/adin1140.c
new file mode 100644
index 000000000000..3244107ce9ef
--- /dev/null
+++ b/drivers/net/phy/adin1140.c
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Driver for Analog Devices, Inc. ADIN1140 10BASE-T1S PHY
+ *
+ * Copyright 2026 Analog Devices Inc.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/phy.h>
+
+#define ADIN1140_PHY_ID 0x0283be00
+
+#define ADIN1140_PCS_CTRL 0x08f3
+#define ADIN1140_PCS_CTRL_LOOPBACK BIT(14)
+
+static int adin1140_phy_read_mmd(struct phy_device *phydev, int devnum,
+ u16 regnum)
+{
+ struct mii_bus *bus = phydev->mdio.bus;
+ int addr = phydev->mdio.addr;
+
+ return __mdiobus_c45_read(bus, addr, devnum, regnum);
+}
+
+static int adin1140_phy_write_mmd(struct phy_device *phydev, int devnum,
+ u16 regnum, u16 val)
+{
+ struct mii_bus *bus = phydev->mdio.bus;
+ int addr = phydev->mdio.addr;
+
+ return __mdiobus_c45_write(bus, addr, devnum, regnum, val);
+}
+
+static int adin1140_config_init(struct phy_device *phydev)
+{
+ /* The link status of the PHY doesn't need to be polled, because
+ * the device doesn't implement AN and there is no other mechanism
+ * to report the link state.
+ */
+ phydev->irq = PHY_MAC_INTERRUPT;
+
+ return 0;
+}
+
+static int adin1140_config_aneg(struct phy_device *phydev)
+{
+ /* phylib tries to clear BIT(12) in MDIO_CTRL1, since AN is disabled.
+ * However, on the ADIN1140, that field is non-standard, being used
+ * to control the reset status of the PHY (thus it needs to remain set).
+ */
+ return 0;
+}
+
+static int adin1140_loopback(struct phy_device *phydev, bool enable, int speed)
+{
+ if (enable && speed)
+ return -EOPNOTSUPP;
+
+ return phy_modify_mmd(phydev, MDIO_MMD_PCS, ADIN1140_PCS_CTRL,
+ ADIN1140_PCS_CTRL_LOOPBACK,
+ enable ? ADIN1140_PCS_CTRL_LOOPBACK : 0);
+}
+
+static int adin1140_read_status(struct phy_device *phydev)
+{
+ phydev->link = 1;
+ phydev->duplex = DUPLEX_HALF;
+ phydev->speed = SPEED_10;
+ phydev->autoneg = AUTONEG_DISABLE;
+
+ return 0;
+}
+
+static struct phy_driver adin1140_driver[] = {
+ {
+ PHY_ID_MATCH_EXACT(ADIN1140_PHY_ID),
+ .name = "ADIN1140",
+ .features = PHY_BASIC_T1S_P2MP_FEATURES,
+ .read_status = adin1140_read_status,
+ .config_init = adin1140_config_init,
+ .config_aneg = adin1140_config_aneg,
+ .set_loopback = adin1140_loopback,
+ .read_mmd = adin1140_phy_read_mmd,
+ .write_mmd = adin1140_phy_write_mmd,
+ .get_plca_cfg = genphy_c45_plca_get_cfg,
+ .set_plca_cfg = genphy_c45_plca_set_cfg,
+ .get_plca_status = genphy_c45_plca_get_status,
+ },
+};
+module_phy_driver(adin1140_driver);
+
+static const struct mdio_device_id __maybe_unused adin1140_tbl[] = {
+ { PHY_ID_MATCH_EXACT(ADIN1140_PHY_ID) },
+ { }
+};
+
+MODULE_DEVICE_TABLE(mdio, adin1140_tbl);
+
+MODULE_DESCRIPTION("Analog Devices, Inc. ADIN1140 10BASE-T1S PHY");
+MODULE_AUTHOR("Ciprian Regus <ciprian.regus@analog.com>");
+MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply related
* [PATCH net-next 4/5] net: ethernet: adi: Add a driver for the ADIN1140 MACPHY
From: Ciprian Regus via B4 Relay @ 2026-05-02 23:24 UTC (permalink / raw)
To: Parthiban Veerasooran, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Andrew Lunn, Heiner Kallweit, Russell King,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: netdev, linux-kernel, linux-doc, devicetree, Ciprian Regus
In-Reply-To: <20260503-adin1140-driver-v1-0-dd043cdd88f0@analog.com>
From: Ciprian Regus <ciprian.regus@analog.com>
Add a driver for ADIN1140. The device is a 10BASE-T1S MAC-PHY
(integrated in the same package) that connects to a CPU over an SPI bus,
and implements the Open Alliance TC6 protocol for control and frame
transfers. As such, this driver relies on oa_tc6 for the communication
with the device. The device has an alternative name (AD3306), so the
driver can be probed using one of the two compatible strings.
For control transactions, ADIN1140 only implements the protected mode.
The driver has a custom implementation for the mii_bus access methods as a
workaround for hardware issues:
1. The OA TC6 standard defines the direct and indirect access modes for
MDIO transactions. The ADIN1140 incorrectly advertises indirect mode
only (supported capabilities register - 0x2, bit 9), while actually
implementing just the direct mode. We cannot rely on the CAP register
to choose an access method (which oa_tc6 does by default, even though
it only implements the direct mode), so the driver has to use its
own.
2. The ADIN1140 cannot access the C22 register space of the internal
PHY, while the PHY is busy receiving frames. If that happens, the
CONFIG0 and CONFIG2 registers of the MAC will get corrupted and the
data transfer will stop. Those two registers configure settings for
the transfer protocol between the MAC and host, so the value for some
of their subfields shouldn't be changed while the netdev is up.
Since we know the PHY is internal, the MAC driver can implement a
custom mii_bus, which can intercept C22 accesses. Most of the
registers mapped in the 0x0 - 0x3 range (the only ones the PHY offers)
are read only, and their value can be read from somewhere else (e.g
the PHYID 1 & 2 have the same value as 0x1 in the MAC memory map).
For the fields that are R/W (loopback and AN/reset) in the control
register, the PHY driver already implements the set_loopback() and
config_aneg() functions. The C22 write function of the driver is a
no-op and is used to protect against the ioctl MDIO access path.
C45 accesses do not cause this issue, so we can properly implement
them.
Signed-off-by: Ciprian Regus <ciprian.regus@analog.com>
---
MAINTAINERS | 7 +
drivers/net/ethernet/adi/Kconfig | 12 +
drivers/net/ethernet/adi/Makefile | 1 +
drivers/net/ethernet/adi/adin1140.c | 805 ++++++++++++++++++++++++++++++++++++
4 files changed, 825 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 1e58da5ef47a..f9784c25beac 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1843,6 +1843,13 @@ S: Supported
W: https://ez.analog.com/linux-software-drivers
F: drivers/dma/dma-axi-dmac.c
+ANALOG DEVICES INC ETHERNET DRIVERS
+M: Ciprian Regus <ciprian.regus@analog.com>
+L: netdev@vger.kernel.org
+S: Maintained
+W: https://ez.analog.com/linux-software-drivers
+F: drivers/net/ethernet/adi/adin1140.c
+
ANALOG DEVICES INC ETHERNET PHY DRIVERS
M: Ciprian Regus <ciprian.regus@analog.com>
L: netdev@vger.kernel.org
diff --git a/drivers/net/ethernet/adi/Kconfig b/drivers/net/ethernet/adi/Kconfig
index 760a9a60bc15..bdb8ff7d15da 100644
--- a/drivers/net/ethernet/adi/Kconfig
+++ b/drivers/net/ethernet/adi/Kconfig
@@ -26,4 +26,16 @@ config ADIN1110
Say yes here to build support for Analog Devices ADIN1110
Low Power 10BASE-T1L Ethernet MAC-PHY.
+config ADIN1140
+ tristate "Analog Devices ADIN1140 MAC-PHY"
+ depends on SPI
+ select ADIN1140_PHY
+ select OA_TC6
+ help
+ Say yes here to build support for Analog Devices, Inc. ADIN1140
+ 10BASE-T1S Ethernet MAC-PHY.
+
+ To compile this driver as a module, choose M here. The module will be
+ called adin1140.
+
endif # NET_VENDOR_ADI
diff --git a/drivers/net/ethernet/adi/Makefile b/drivers/net/ethernet/adi/Makefile
index d0383d94303c..0390ca8ccc49 100644
--- a/drivers/net/ethernet/adi/Makefile
+++ b/drivers/net/ethernet/adi/Makefile
@@ -4,3 +4,4 @@
#
obj-$(CONFIG_ADIN1110) += adin1110.o
+obj-$(CONFIG_ADIN1140) += adin1140.o
diff --git a/drivers/net/ethernet/adi/adin1140.c b/drivers/net/ethernet/adi/adin1140.c
new file mode 100644
index 000000000000..5bc3f5732ed8
--- /dev/null
+++ b/drivers/net/ethernet/adi/adin1140.c
@@ -0,0 +1,805 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Driver for Analog Devices, Inc. ADIN1140 10BASE-T1S MAC-PHY
+ *
+ * Copyright 2026 Analog Devices Inc.
+ */
+
+#include <linux/etherdevice.h>
+#include <linux/kernel.h>
+#include <linux/mdio.h>
+#include <linux/module.h>
+#include <linux/oa_tc6.h>
+#include <linux/phy.h>
+
+#define ADIN1140_MMS_REG(m, r) ((((m) & GENMASK(3, 0)) << 16) | \
+ ((r) & GENMASK(15, 0)))
+
+#define ADIN1140_MACPHY_ID_REG ADIN1140_MMS_REG(0x0, 0x1)
+
+#define ADIN1140_CONFIG0_REG 0x0004
+#define ADIN1140_CONFIG0_TXFCSVE BIT(14)
+#define ADIN1140_CONFIG0_RFA_ZARFE BIT(12)
+#define ADIN1140_CONFIG0_CPS_64 GENMASK(2, 1)
+
+#define ADIN1140_CONFIG2_REG ADIN1140_MMS_REG(0x0, 0x6)
+#define ADIN1140_CONFIG2_FWD_UNK2HOST BIT(2)
+
+#define ADIN1140_MAC_P1_LOOP_ADDR_REG ADIN1140_MMS_REG(0x1, 0xC4)
+
+#define ADIN1140_MAC_ADDR_FILT_UPR_REG ADIN1140_MMS_REG(0x1, 0x50)
+#define ADIN1140_MAC_ADDR_FILT_APPLY2PORT1 BIT(30)
+#define ADIN1140_MAC_ADDR_FILT_TO_HOST BIT(16)
+
+#define ADIN1140_MAC_ADDR_FILT_LWR_REG ADIN1140_MMS_REG(0x1, 0x51)
+
+#define ADIN1140_MAC_ADDR_MASK_UPR_REG ADIN1140_MMS_REG(0x1, 0x70)
+#define ADIN1140_MAC_ADDR_MASK_LWR_REG ADIN1140_MMS_REG(0x1, 0x71)
+
+#define ADIN1140_MAC_FILT_MC_SLOT 0U
+#define ADIN1140_MAC_FILT_BC_SLOT 1U
+#define ADIN1140_MAC_FILT_UC_SLOT 2U
+#define ADIN1140_MAC_FILT_MAX_SLOT 16U
+
+#define ADIN1140_RX_FRAME_CNT ADIN1140_MMS_REG(0x1, 0xA1)
+#define ADIN1140_RX_BC_FRAME_CNT ADIN1140_MMS_REG(0x1, 0xA2)
+#define ADIN1140_RX_MC_FRAME_CNT ADIN1140_MMS_REG(0x1, 0xA3)
+#define ADIN1140_RX_UC_FRAME_CNT ADIN1140_MMS_REG(0x1, 0xA4)
+#define ADIN1140_RX_CRC_ERR_CNT ADIN1140_MMS_REG(0x1, 0xA5)
+#define ADIN1140_RX_ALIGN_ERR_CNT ADIN1140_MMS_REG(0x1, 0xA6)
+#define ADIN1140_RX_PREAMBLE_ERR_CNT ADIN1140_MMS_REG(0x1, 0xA7)
+#define ADIN1140_RX_SHORT_ERR_CNT ADIN1140_MMS_REG(0x1, 0xA8)
+#define ADIN1140_RX_LONG_ERR_CNT ADIN1140_MMS_REG(0x1, 0xA9)
+#define ADIN1140_RX_PHY_ERR_CNT ADIN1140_MMS_REG(0x1, 0xAA)
+#define ADIN1140_RX_DRP_FULL_CNT ADIN1140_MMS_REG(0x1, 0xAB)
+#define ADIN1140_RX_DRP_FILTER_CNT ADIN1140_MMS_REG(0x1, 0xAD)
+#define ADIN1140_RX_IFG_ERR_CNT ADIN1140_MMS_REG(0x1, 0xAE)
+#define ADIN1140_TX_FRAME_CNT ADIN1140_MMS_REG(0x1, 0xB1)
+#define ADIN1140_TX_BC_FRAME_CNT ADIN1140_MMS_REG(0x1, 0xB2)
+#define ADIN1140_TX_MC_FRAME_CNT ADIN1140_MMS_REG(0x1, 0xB3)
+#define ADIN1140_TX_UC_FRAME_CNT ADIN1140_MMS_REG(0x1, 0xB4)
+#define ADIN1140_TX_SINGLE_COL_CNT ADIN1140_MMS_REG(0x1, 0xB5)
+#define ADIN1140_TX_MULTI_COL_CNT ADIN1140_MMS_REG(0x1, 0xB6)
+#define ADIN1140_TX_DEFERRED_CNT ADIN1140_MMS_REG(0x1, 0xB7)
+#define ADIN1140_TX_LATE_COL_CNT ADIN1140_MMS_REG(0x1, 0xB8)
+#define ADIN1140_TX_EXCESS_COL_CNT ADIN1140_MMS_REG(0x1, 0xB9)
+#define ADIN1140_TX_UNDERRUN_CNT ADIN1140_MMS_REG(0x1, 0xBA)
+
+/* ADIN1140_MAC_FILT_MAX_SLOT - 3 (multicast, broadcast and unicast
+ * reserved slots)
+ */
+#define ADIN1140_MAC_FILT_AVAIL 13U
+
+#define ADIN1140_PHY_CTRL_DEFAULT 0x1000
+#define ADIN1140_PHY_STATUS_DEFAULT 0x082D
+
+#define ADIN1140_PHY_C45_PCS_MMS2 2 /* MMD 3 */
+#define ADIN1140_PHY_C45_PMA_PMD_MMS3 3 /* MMD 1 */
+#define ADIN1140_PHY_C45_VS_PLCA_MMS4 4 /* MMD 31 */
+
+#define ADIN1140_STATS_CNT 23
+#define ADIN1140_STATS_CHECK_DELAY (3 * HZ)
+
+struct adin1140_statistics_reg {
+ const char *name;
+ u32 addr;
+};
+
+struct adin1140_priv {
+ struct net_device *netdev;
+ struct oa_tc6 *tc6;
+ struct mii_bus *mdiobus;
+ struct work_struct rx_mode_work;
+ struct delayed_work stats_work;
+ /* Protect the stats array from concurrent accesses from
+ * adin1140_stats_work, adin1140_ndo_get_stats64
+ * and adin1140_get_ethtool_stats
+ */
+ spinlock_t stat_lock;
+
+ u64 stats[ADIN1140_STATS_CNT];
+};
+
+enum adin1140_statistics_entry {
+ rx_frames,
+ rx_broadcast_frames,
+ rx_multicast_frames,
+ rx_unicast_frames,
+ rx_crc_errors,
+ rx_align_errors,
+ rx_preamble_errors,
+ rx_short_frame_errors,
+ rx_long_frame_errors,
+ rx_phy_errors,
+ rx_fifo_full_dropped,
+ rx_addr_filter_dropped,
+ rx_ifg_errors,
+ tx_frames,
+ tx_broadcast_frames,
+ tx_multicast_frames,
+ tx_unicast_frames,
+ tx_single_collision,
+ tx_multi_collision,
+ tx_deferred,
+ tx_late_collision,
+ tx_excess_collision,
+ tx_underrun,
+};
+
+static const struct adin1140_statistics_reg adin1140_stats[] = {
+ {.name = "rx_frames", .addr = ADIN1140_RX_FRAME_CNT},
+ {.name = "rx_broadcast_frames", .addr = ADIN1140_RX_BC_FRAME_CNT},
+ {.name = "rx_multicast_frames", .addr = ADIN1140_RX_MC_FRAME_CNT},
+ {.name = "rx_unicast_frames", .addr = ADIN1140_RX_UC_FRAME_CNT},
+ {.name = "rx_crc_errors", .addr = ADIN1140_RX_CRC_ERR_CNT},
+ {.name = "rx_align_errors", .addr = ADIN1140_RX_ALIGN_ERR_CNT},
+ {.name = "rx_preamble_errors", .addr = ADIN1140_RX_PREAMBLE_ERR_CNT},
+ {.name = "rx_short_frame_errors", .addr = ADIN1140_RX_SHORT_ERR_CNT},
+ {.name = "rx_long_frame_errors", .addr = ADIN1140_RX_LONG_ERR_CNT},
+ {.name = "rx_phy_errors", .addr = ADIN1140_RX_PHY_ERR_CNT},
+ {.name = "rx_fifo_full_dropped", .addr = ADIN1140_RX_DRP_FULL_CNT},
+ {.name = "rx_addr_filt_dropped", .addr = ADIN1140_RX_DRP_FILTER_CNT},
+ {.name = "rx_ifg_errors", .addr = ADIN1140_RX_IFG_ERR_CNT},
+ {.name = "tx_frames", .addr = ADIN1140_TX_FRAME_CNT},
+ {.name = "tx_broadcast_frames", .addr = ADIN1140_TX_BC_FRAME_CNT},
+ {.name = "tx_multicast_frames", .addr = ADIN1140_TX_MC_FRAME_CNT},
+ {.name = "tx_unicast_frames", .addr = ADIN1140_TX_UC_FRAME_CNT},
+ {.name = "tx_single_collision", .addr = ADIN1140_TX_SINGLE_COL_CNT},
+ {.name = "tx_multi_collision", .addr = ADIN1140_TX_MULTI_COL_CNT},
+ {.name = "tx_deferred", .addr = ADIN1140_TX_DEFERRED_CNT},
+ {.name = "tx_late_collision", .addr = ADIN1140_TX_LATE_COL_CNT},
+ {.name = "tx_excess_collision", .addr = ADIN1140_TX_EXCESS_COL_CNT},
+ {.name = "tx_underrun", .addr = ADIN1140_TX_UNDERRUN_CNT},
+};
+
+static int adin1140_mac_filter_set(struct adin1140_priv *priv,
+ const u8 *addr, const u8 *mask,
+ u8 slot)
+{
+ u32 mask_reg;
+ u32 val;
+ int ret;
+
+ if (slot >= ADIN1140_MAC_FILT_MAX_SLOT)
+ return -ENOSPC;
+
+ ret = oa_tc6_write_register(priv->tc6,
+ ADIN1140_MAC_ADDR_FILT_UPR_REG + 2 * slot,
+ get_unaligned_be16(&addr[0]) |
+ ADIN1140_MAC_ADDR_FILT_APPLY2PORT1 |
+ ADIN1140_MAC_ADDR_FILT_TO_HOST);
+ if (ret)
+ return ret;
+
+ ret = oa_tc6_write_register(priv->tc6,
+ ADIN1140_MAC_ADDR_FILT_LWR_REG + 2 * slot,
+ get_unaligned_be32(&addr[2]));
+ if (ret)
+ return ret;
+
+ val = get_unaligned_be16(&mask[0]);
+ mask_reg = ADIN1140_MAC_ADDR_MASK_UPR_REG + (2 * slot);
+
+ ret = oa_tc6_write_register(priv->tc6, mask_reg, val);
+ if (ret)
+ return ret;
+
+ val = get_unaligned_be32(&mask[2]);
+ mask_reg = ADIN1140_MAC_ADDR_MASK_LWR_REG + (2 * slot);
+
+ return oa_tc6_write_register(priv->tc6, mask_reg, val);
+}
+
+static int adin1140_mac_filter_clear(struct adin1140_priv *priv, u8 slot)
+{
+ u8 mask[ETH_ALEN];
+ u8 addr[ETH_ALEN];
+
+ memset(mask, 0xFF, ETH_ALEN);
+ memset(addr, 0x0, ETH_ALEN);
+
+ return adin1140_mac_filter_set(priv, addr, mask, slot);
+}
+
+static int adin1140_filter_unicast(struct adin1140_priv *priv)
+{
+ u8 mask[ETH_ALEN];
+
+ memset(mask, 0xFF, ETH_ALEN);
+
+ return adin1140_mac_filter_set(priv, priv->netdev->dev_addr, mask,
+ ADIN1140_MAC_FILT_UC_SLOT);
+}
+
+static int adin1140_filter_all_multicast(struct adin1140_priv *priv, bool en)
+{
+ u8 multicast_addr[ETH_ALEN] = {1, 0, 0, 0, 0, 0};
+
+ if (en)
+ return adin1140_mac_filter_set(priv, multicast_addr,
+ multicast_addr,
+ ADIN1140_MAC_FILT_MC_SLOT);
+
+ return adin1140_mac_filter_clear(priv, ADIN1140_MAC_FILT_MC_SLOT);
+}
+
+static int adin1140_filter_broadcast(struct adin1140_priv *priv, bool enabled)
+{
+ u8 mask[ETH_ALEN];
+
+ if (enabled) {
+ memset(mask, 0xFF, ETH_ALEN);
+ return adin1140_mac_filter_set(priv, mask, mask,
+ ADIN1140_MAC_FILT_BC_SLOT);
+ }
+
+ return adin1140_mac_filter_clear(priv, ADIN1140_MAC_FILT_BC_SLOT);
+}
+
+static int adin1140_default_filter_config(struct adin1140_priv *priv)
+{
+ int ret;
+
+ ret = adin1140_filter_broadcast(priv, true);
+ if (ret)
+ return ret;
+
+ return adin1140_filter_unicast(priv);
+}
+
+static int adin1140_promiscuous_mode(struct adin1140_priv *priv, bool enabled)
+{
+ int ret;
+ u32 val;
+
+ ret = oa_tc6_read_register(priv->tc6, ADIN1140_CONFIG2_REG, &val);
+ if (ret)
+ return ret;
+
+ if (enabled)
+ val |= ADIN1140_CONFIG2_FWD_UNK2HOST;
+ else
+ val &= ~ADIN1140_CONFIG2_FWD_UNK2HOST;
+
+ return oa_tc6_write_register(priv->tc6, ADIN1140_CONFIG2_REG, val);
+}
+
+static void adin1140_rx_mode_work(struct work_struct *work)
+{
+ struct adin1140_priv *priv = container_of(work, struct adin1140_priv,
+ rx_mode_work);
+ struct netdev_hw_addr *ha;
+ bool all_multi, promisc;
+ u8 mask[ETH_ALEN];
+ u8 start, end;
+ u32 mac_addrs;
+ u8 slot, i;
+ int ret;
+
+ /* The ADIN1140 has 16 dest MAC address filter slots:
+ * 0 - reserved for all multicast filter.
+ * 1 - reserved for broadcast filter.
+ * 2 - reserved for the device's own unicast MAC.
+ * 3 -> 15 - available for other unicast/multicast filters.
+ */
+
+ mac_addrs = netdev_uc_count(priv->netdev) +
+ netdev_mc_count(priv->netdev);
+
+ if (priv->netdev->flags & IFF_PROMISC) {
+ promisc = true;
+ all_multi = false;
+ } else if (priv->netdev->flags & IFF_ALLMULTI) {
+ promisc = false;
+ all_multi = true;
+ } else if (mac_addrs <= ADIN1140_MAC_FILT_AVAIL) {
+ promisc = false;
+ all_multi = false;
+
+ slot = ADIN1140_MAC_FILT_UC_SLOT + 1;
+ memset(mask, 0xFF, ETH_ALEN);
+
+ netdev_for_each_uc_addr(ha, priv->netdev) {
+ ret = adin1140_mac_filter_set(priv, ha->addr, mask,
+ slot);
+ if (ret)
+ return;
+
+ slot++;
+ }
+
+ netdev_for_each_mc_addr(ha, priv->netdev) {
+ ret = adin1140_mac_filter_set(priv, ha->addr, mask,
+ slot);
+ if (ret)
+ return;
+
+ slot++;
+ }
+ } else {
+ /* The filter table is full. Enable promisc mode. */
+ promisc = true;
+ all_multi = false;
+
+ start = ADIN1140_MAC_FILT_UC_SLOT + 1;
+ end = ADIN1140_MAC_FILT_MAX_SLOT;
+ for (i = start; i < end; i++) {
+ ret = adin1140_mac_filter_clear(priv, i);
+ if (ret)
+ return;
+ }
+ }
+
+ ret = adin1140_promiscuous_mode(priv, promisc);
+ if (ret)
+ return;
+
+ adin1140_filter_all_multicast(priv, all_multi);
+}
+
+static void adin1140_rx_mode(struct net_device *netdev)
+{
+ struct adin1140_priv *priv = netdev_priv(netdev);
+
+ schedule_work(&priv->rx_mode_work);
+}
+
+static void adin1140_stats_work(struct work_struct *work)
+{
+ struct delayed_work *dwork = to_delayed_work(work);
+ u64 stat_buff[ADIN1140_STATS_CNT] = {};
+ struct adin1140_priv *priv;
+ u32 reg_val;
+ int ret;
+ u32 i;
+
+ priv = container_of(dwork, struct adin1140_priv, stats_work);
+
+ for (i = 0; i < ARRAY_SIZE(adin1140_stats); i++) {
+ ret = oa_tc6_read_register(priv->tc6, adin1140_stats[i].addr,
+ ®_val);
+ if (ret)
+ break;
+
+ stat_buff[i] = reg_val;
+ }
+
+ spin_lock(&priv->stat_lock);
+ memcpy(&priv->stats, stat_buff, sizeof(priv->stats));
+ spin_unlock(&priv->stat_lock);
+
+ schedule_delayed_work(dwork, ADIN1140_STATS_CHECK_DELAY);
+}
+
+static int adin1140_configure(struct adin1140_priv *priv)
+{
+ u32 val;
+ int ret;
+
+ ret = oa_tc6_zero_align_receive_frame_enable(priv->tc6);
+ if (ret)
+ return ret;
+
+ ret = oa_tc6_read_register(priv->tc6, ADIN1140_CONFIG0_REG, &val);
+ if (ret)
+ return ret;
+
+ /* Zero-Align Receive Frame Enable */
+ val |= ADIN1140_CONFIG0_RFA_ZARFE;
+
+ /* Transmit Frame Check Sequence Validation must be disabled
+ * to allow CRC appending by MAC (CONFIG2.CRC_APPEND)
+ */
+ val &= ~ADIN1140_CONFIG0_TXFCSVE;
+ val |= ADIN1140_CONFIG0_CPS_64;
+
+ ret = oa_tc6_write_register(priv->tc6, ADIN1140_CONFIG0_REG, val);
+ if (ret)
+ return ret;
+
+ /* Disable MAC loopback */
+ ret = oa_tc6_write_register(priv->tc6, ADIN1140_MAC_P1_LOOP_ADDR_REG,
+ 0x0);
+ if (ret)
+ return ret;
+
+ return adin1140_default_filter_config(priv);
+}
+
+static int adin1140_open(struct net_device *netdev)
+{
+ struct adin1140_priv *priv = netdev_priv(netdev);
+
+ schedule_delayed_work(&priv->stats_work, ADIN1140_STATS_CHECK_DELAY);
+
+ phy_start(netdev->phydev);
+ netif_start_queue(netdev);
+
+ return 0;
+}
+
+static int adin1140_close(struct net_device *netdev)
+{
+ struct adin1140_priv *priv = netdev_priv(netdev);
+
+ cancel_delayed_work_sync(&priv->stats_work);
+
+ netif_stop_queue(netdev);
+ phy_stop(netdev->phydev);
+
+ return 0;
+}
+
+static netdev_tx_t adin1140_start_xmit(struct sk_buff *skb,
+ struct net_device *netdev)
+{
+ struct adin1140_priv *priv = netdev_priv(netdev);
+
+ /* Pad frames to minimum Ethernet frame size (60 bytes without FCS).
+ * The MAC will append the FCS, but we need to ensure the frame is
+ * at least ETH_ZLEN bytes.
+ */
+ if (skb_put_padto(skb, ETH_ZLEN))
+ return NETDEV_TX_OK;
+
+ return oa_tc6_start_xmit(priv->tc6, skb);
+}
+
+static int adin1140_ioctl(struct net_device *netdev, struct ifreq *rq, int cmd)
+{
+ if (!netif_running(netdev))
+ return -EINVAL;
+
+ return phy_do_ioctl(netdev, rq, cmd);
+}
+
+static int adin1140_set_mac_address(struct net_device *netdev, void *addr)
+{
+ struct adin1140_priv *priv = netdev_priv(netdev);
+ struct sockaddr *address = addr;
+ u8 mask[ETH_ALEN];
+ int ret;
+
+ ret = eth_prepare_mac_addr_change(netdev, addr);
+ if (ret < 0)
+ return ret;
+
+ if (ether_addr_equal(address->sa_data, netdev->dev_addr))
+ return 0;
+
+ memset(mask, 0xFF, ETH_ALEN);
+ ret = adin1140_mac_filter_set(priv, address->sa_data, mask,
+ ADIN1140_MAC_FILT_UC_SLOT);
+ if (ret)
+ return ret;
+
+ eth_commit_mac_addr_change(netdev, addr);
+
+ return 0;
+}
+
+static void adin1140_ndo_get_stats64(struct net_device *dev,
+ struct rtnl_link_stats64 *storage)
+{
+ struct adin1140_priv *priv = netdev_priv(dev);
+
+ storage->rx_packets = priv->netdev->stats.rx_packets;
+ storage->tx_packets = priv->netdev->stats.tx_packets;
+
+ storage->rx_bytes = priv->netdev->stats.rx_bytes;
+ storage->tx_bytes = priv->netdev->stats.tx_bytes;
+
+ spin_lock(&priv->stat_lock);
+
+ storage->rx_errors = priv->stats[rx_crc_errors] +
+ priv->stats[rx_align_errors] +
+ priv->stats[rx_preamble_errors] +
+ priv->stats[rx_short_frame_errors] +
+ priv->stats[rx_long_frame_errors] +
+ priv->stats[rx_phy_errors] +
+ priv->stats[rx_ifg_errors];
+
+ storage->tx_errors = priv->stats[tx_excess_collision] +
+ priv->stats[tx_underrun];
+
+ storage->rx_dropped = priv->stats[rx_fifo_full_dropped] +
+ priv->stats[rx_addr_filter_dropped];
+
+ storage->multicast = priv->stats[rx_multicast_frames];
+
+ storage->collisions = priv->stats[tx_single_collision] +
+ priv->stats[tx_multi_collision];
+
+ storage->rx_length_errors = priv->stats[rx_short_frame_errors] +
+ priv->stats[rx_long_frame_errors];
+ storage->rx_over_errors = priv->stats[rx_fifo_full_dropped];
+ storage->rx_crc_errors = priv->stats[rx_crc_errors];
+ storage->rx_frame_errors = priv->stats[rx_align_errors];
+ storage->rx_missed_errors = priv->stats[rx_fifo_full_dropped];
+
+ storage->tx_aborted_errors = priv->stats[tx_excess_collision];
+ storage->tx_fifo_errors = priv->stats[tx_underrun];
+ storage->tx_window_errors = priv->stats[tx_late_collision];
+
+ spin_unlock(&priv->stat_lock);
+}
+
+static void adin1140_get_drvinfo(struct net_device *netdev,
+ struct ethtool_drvinfo *info)
+{
+ strscpy(info->driver, "ADIN1140", sizeof(info->driver));
+ strscpy(info->bus_info, dev_name(netdev->dev.parent),
+ sizeof(info->bus_info));
+}
+
+static void adin1140_get_ethtool_stats(struct net_device *netdev,
+ struct ethtool_stats *stats, u64 *data)
+{
+ struct adin1140_priv *priv = netdev_priv(netdev);
+
+ spin_lock(&priv->stat_lock);
+ memcpy(data, &priv->stats, sizeof(u64) * ARRAY_SIZE(adin1140_stats));
+ spin_unlock(&priv->stat_lock);
+}
+
+static void adin1140_get_ethtool_strings(struct net_device *netdev, u32 sset,
+ u8 *p)
+{
+ u32 i;
+
+ switch (sset) {
+ case ETH_SS_STATS:
+ for (i = 0; i < ARRAY_SIZE(adin1140_stats); i++)
+ ethtool_puts(&p, adin1140_stats[i].name);
+
+ break;
+ }
+}
+
+static int adin1140_get_sset_count(struct net_device *netdev, int sset)
+{
+ switch (sset) {
+ case ETH_SS_STATS:
+ return ARRAY_SIZE(adin1140_stats);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int adin1140_get_phy_c45_mms(int devnum)
+{
+ switch (devnum) {
+ case MDIO_MMD_PCS:
+ return ADIN1140_PHY_C45_PCS_MMS2;
+ case MDIO_MMD_PMAPMD:
+ return ADIN1140_PHY_C45_PMA_PMD_MMS3;
+ case MDIO_MMD_VEND2:
+ return ADIN1140_PHY_C45_VS_PLCA_MMS4;
+ default:
+ return devnum;
+ }
+}
+
+static int adin1140_mdiobus_read_c45(struct mii_bus *bus, int addr,
+ int devnum, int regnum)
+{
+ struct oa_tc6 *tc6 = bus->priv;
+ u32 regval;
+ u32 mms;
+ int ret;
+
+ mms = adin1140_get_phy_c45_mms(devnum);
+ ret = oa_tc6_read_register(tc6, ADIN1140_MMS_REG(mms, regnum),
+ ®val);
+ if (ret)
+ return ret;
+
+ return regval;
+}
+
+static int adin1140_mdiobus_write_c45(struct mii_bus *bus, int addr,
+ int devnum, int regnum, u16 val)
+{
+ struct oa_tc6 *tc6 = bus->priv;
+ int ret;
+
+ ret = adin1140_get_phy_c45_mms(devnum);
+ if (ret < 0)
+ return ret;
+
+ return oa_tc6_write_register(tc6, ADIN1140_MMS_REG(ret, regnum), val);
+}
+
+static int adin1140_mdiobus_read(struct mii_bus *bus, int addr, int regnum)
+{
+ struct oa_tc6 *tc6 = bus->priv;
+ u32 reg_val;
+ int ret;
+
+ /* The ADIN1140's standard PHY C22 register map (OA TC6 0xFF00 -
+ * 0xFF1F), of which only 0xFF00 - 0xFF03 are implemented) cannot be
+ * accessed while frames are being received by the PHY. In case this
+ * happens the CONFIG0 and CONFIG2 register values will get corrupted,
+ * getting a random value. Both reads and writes cause the same
+ * behavior. This is a workaround that avoids MDIO accesses all
+ * together. Since this is a 10BASE-T1S PHY, only the loopback and
+ * reset (AN) bits in the control register (0x0) can be written.
+ * These functionalities have custom implementations in the PHY
+ * driver. Since the MAC and PHY are integrated in the same device, we
+ * can read the OA TC6 MACPHY ID register instead of the PHYID (0x2
+ * and 0x3) ones, as their value matches. C45 accesses do not cause
+ * this issue.
+ */
+
+ switch (regnum) {
+ case MII_BMCR:
+ return ADIN1140_PHY_CTRL_DEFAULT;
+ case MII_BMSR:
+ return ADIN1140_PHY_STATUS_DEFAULT;
+ case MII_PHYSID1:
+ ret = oa_tc6_read_register(tc6, ADIN1140_MACPHY_ID_REG,
+ ®_val);
+ if (ret)
+ return ret;
+
+ return FIELD_GET(GENMASK(31, 16), reg_val);
+ case MII_PHYSID2:
+ ret = oa_tc6_read_register(tc6, ADIN1140_MACPHY_ID_REG,
+ ®_val);
+ if (ret)
+ return ret;
+
+ return FIELD_GET(GENMASK(15, 0), reg_val);
+ default:
+ return 0xFFFF;
+ }
+}
+
+static int adin1140_mdiobus_write(struct mii_bus *bus, int addr, int regnum,
+ u16 val)
+{
+ return 0;
+}
+
+static int adin1140_mdio_register(struct adin1140_priv *priv)
+{
+ priv->mdiobus = mdiobus_alloc();
+ if (!priv->mdiobus) {
+ netdev_err(priv->netdev, "MDIO bus alloc failed\n");
+ return -ENOMEM;
+ }
+
+ priv->mdiobus->read = adin1140_mdiobus_read;
+ priv->mdiobus->write = adin1140_mdiobus_write;
+ priv->mdiobus->read_c45 = adin1140_mdiobus_read_c45;
+ priv->mdiobus->write_c45 = adin1140_mdiobus_write_c45;
+
+ return 0;
+}
+
+static const struct ethtool_ops adin1140_ethtool_ops = {
+ .get_drvinfo = adin1140_get_drvinfo,
+ .get_link = ethtool_op_get_link,
+ .get_ethtool_stats = adin1140_get_ethtool_stats,
+ .get_sset_count = adin1140_get_sset_count,
+ .get_strings = adin1140_get_ethtool_strings,
+ .get_link_ksettings = phy_ethtool_get_link_ksettings,
+ .set_link_ksettings = phy_ethtool_set_link_ksettings,
+};
+
+static const struct net_device_ops adin1140_netdev_ops = {
+ .ndo_open = adin1140_open,
+ .ndo_stop = adin1140_close,
+ .ndo_start_xmit = adin1140_start_xmit,
+ .ndo_set_mac_address = adin1140_set_mac_address,
+ .ndo_validate_addr = eth_validate_addr,
+ .ndo_set_rx_mode = adin1140_rx_mode,
+ .ndo_eth_ioctl = adin1140_ioctl,
+ .ndo_get_stats64 = adin1140_ndo_get_stats64,
+};
+
+static int adin1140_probe(struct spi_device *spi)
+{
+ struct oa_tc6_config tc6_config = {};
+ struct net_device *netdev;
+ struct adin1140_priv *priv;
+ int ret;
+
+ netdev = alloc_etherdev(sizeof(struct adin1140_priv));
+ if (!netdev)
+ return -ENOMEM;
+
+ priv = netdev_priv(netdev);
+ priv->netdev = netdev;
+ spi_set_drvdata(spi, priv);
+ spin_lock_init(&priv->stat_lock);
+
+ ret = adin1140_mdio_register(priv);
+ if (ret)
+ goto netdev_free;
+
+ tc6_config.spi = spi;
+ tc6_config.netdev = netdev;
+ tc6_config.mii_bus = priv->mdiobus;
+
+ priv->tc6 = oa_tc6_init(&tc6_config);
+ if (!priv->tc6) {
+ ret = -ENODEV;
+ goto mdio_free;
+ }
+
+ if (device_get_ethdev_address(&spi->dev, netdev))
+ eth_hw_addr_random(netdev);
+
+ ret = adin1140_configure(priv);
+ if (ret)
+ goto oa_tc6_exit;
+
+ INIT_WORK(&priv->rx_mode_work, adin1140_rx_mode_work);
+ INIT_DELAYED_WORK(&priv->stats_work, adin1140_stats_work);
+
+ netdev->if_port = IF_PORT_10BASET;
+ netdev->irq = spi->irq;
+ netdev->netdev_ops = &adin1140_netdev_ops;
+ netdev->ethtool_ops = &adin1140_ethtool_ops;
+ netdev->netns_immutable = true;
+ netdev->priv_flags |= IFF_LIVE_ADDR_CHANGE |
+ IFF_UNICAST_FLT;
+
+ ret = register_netdev(netdev);
+ if (ret) {
+ dev_err(&spi->dev, "Failed to register netdev (%d)", ret);
+ goto oa_tc6_exit;
+ }
+
+ return 0;
+
+oa_tc6_exit:
+ oa_tc6_exit(priv->tc6);
+mdio_free:
+ mdiobus_free(priv->mdiobus);
+netdev_free:
+ free_netdev(priv->netdev);
+
+ return ret;
+}
+
+static void adin1140_remove(struct spi_device *spi)
+{
+ struct adin1140_priv *priv = spi_get_drvdata(spi);
+
+ cancel_work_sync(&priv->rx_mode_work);
+ unregister_netdev(priv->netdev);
+ oa_tc6_exit(priv->tc6);
+ mdiobus_free(priv->mdiobus);
+ free_netdev(priv->netdev);
+}
+
+static const struct spi_device_id adin1140_spi_id[] = {
+ { .name = "adin1140" },
+ { .name = "ad3306" },
+ {},
+};
+MODULE_DEVICE_TABLE(spi, adin1140_spi_id);
+
+static const struct of_device_id adin1140_match_table[] = {
+ { .compatible = "adi,adin1140" },
+ { .compatible = "adi,ad3306" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, adin1140_match_table);
+
+static struct spi_driver adin1140_driver = {
+ .driver = {
+ .name = "adin1140",
+ .of_match_table = adin1140_match_table,
+ },
+ .probe = adin1140_probe,
+ .remove = adin1140_remove,
+ .id_table = adin1140_spi_id,
+};
+module_spi_driver(adin1140_driver);
+
+MODULE_DESCRIPTION("Analog Devices, Inc. ADIN1140 10BASE-T1S MAC-PHY");
+MODULE_AUTHOR("Ciprian Regus <ciprian.regus@analog.com>");
+MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply related
* [PATCH net-next 2/5] net: ethernet: oa_tc6: Allow custom mii_bus
From: Ciprian Regus via B4 Relay @ 2026-05-02 23:24 UTC (permalink / raw)
To: Parthiban Veerasooran, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Andrew Lunn, Heiner Kallweit, Russell King,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: netdev, linux-kernel, linux-doc, devicetree, Ciprian Regus
In-Reply-To: <20260503-adin1140-driver-v1-0-dd043cdd88f0@analog.com>
From: Ciprian Regus <ciprian.regus@analog.com>
Some drivers that use oa_tc6 have to use their own mdio bus access
functions as a workaround for hardware issues. Support these cases by
adding a new parameter for the mii_bus in the oa_tc6_init(). In this
case, drivers are responsible for allocating the mii_bus struct, assign
the bus access methods and free the memory after it's no longer used
by oa_tc6. The mii_bus is registered/unregistered by oa_tc6. The phy
connection process does not change and it's still done by oa_tc6.
Drivers can still choose to use the default mii_bus access functions
implemented by oa_tc6 by passing a NULL reference in the mii_bus param.
To avoid extending the function signature every time a new configuration
option is needed, convert oa_tc6_init() to take a config struct.
Also, update the affected drivers and the oa_tc6 framework documentation.
Signed-off-by: Ciprian Regus <ciprian.regus@analog.com>
---
Documentation/networking/oa-tc6-framework.rst | 3 +-
drivers/net/ethernet/microchip/lan865x/lan865x.c | 6 +-
drivers/net/ethernet/oa_tc6.c | 89 +++++++++++++++---------
include/linux/oa_tc6.h | 9 ++-
4 files changed, 70 insertions(+), 37 deletions(-)
diff --git a/Documentation/networking/oa-tc6-framework.rst b/Documentation/networking/oa-tc6-framework.rst
index fe2aabde923a..eaa5b4b85b34 100644
--- a/Documentation/networking/oa-tc6-framework.rst
+++ b/Documentation/networking/oa-tc6-framework.rst
@@ -453,8 +453,7 @@ Device drivers API
The include/linux/oa_tc6.h defines the following functions:
-.. c:function:: struct oa_tc6 *oa_tc6_init(struct spi_device *spi, \
- struct net_device *netdev)
+.. c:function:: struct oa_tc6 *oa_tc6_init(struct oa_tc6_config *config);
Initialize OA TC6 lib.
diff --git a/drivers/net/ethernet/microchip/lan865x/lan865x.c b/drivers/net/ethernet/microchip/lan865x/lan865x.c
index 0277d9737369..c509c8a3e321 100644
--- a/drivers/net/ethernet/microchip/lan865x/lan865x.c
+++ b/drivers/net/ethernet/microchip/lan865x/lan865x.c
@@ -332,6 +332,7 @@ static const struct net_device_ops lan865x_netdev_ops = {
static int lan865x_probe(struct spi_device *spi)
{
+ struct oa_tc6_config tc6_config = {};
struct net_device *netdev;
struct lan865x_priv *priv;
int ret;
@@ -346,7 +347,10 @@ static int lan865x_probe(struct spi_device *spi)
spi_set_drvdata(spi, priv);
INIT_WORK(&priv->multicast_work, lan865x_multicast_work_handler);
- priv->tc6 = oa_tc6_init(spi, netdev);
+ tc6_config.spi = spi;
+ tc6_config.netdev = netdev;
+
+ priv->tc6 = oa_tc6_init(&tc6_config);
if (!priv->tc6) {
ret = -ENODEV;
goto free_netdev;
diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c
index 546ca652d974..fa89b820133f 100644
--- a/drivers/net/ethernet/oa_tc6.c
+++ b/drivers/net/ethernet/oa_tc6.c
@@ -139,6 +139,7 @@ struct oa_tc6 {
bool rx_buf_overflow;
bool int_flag;
bool prot_ctrl;
+ bool own_mdiobus;
};
enum oa_tc6_header_type {
@@ -538,32 +539,37 @@ static int oa_tc6_mdiobus_register(struct oa_tc6 *tc6)
{
int ret;
- tc6->mdiobus = mdiobus_alloc();
if (!tc6->mdiobus) {
- netdev_err(tc6->netdev, "MDIO bus alloc failed\n");
- return -ENOMEM;
+ tc6->mdiobus = mdiobus_alloc();
+ if (!tc6->mdiobus) {
+ netdev_err(tc6->netdev, "MDIO bus alloc failed\n");
+ return -ENOMEM;
+ }
+
+ tc6->mdiobus->read = oa_tc6_mdiobus_read;
+ tc6->mdiobus->write = oa_tc6_mdiobus_write;
+ /* OPEN Alliance 10BASE-T1x compliance MAC-PHYs will have both C22 and
+ * C45 registers space. If the PHY is discovered via C22 bus protocol it
+ * assumes it uses C22 protocol and always uses C22 registers indirect
+ * access to access C45 registers. This is because, we don't have a
+ * clean separation between C22/C45 register space and C22/C45 MDIO bus
+ * protocols. Resulting, PHY C45 registers direct access can't be used
+ * which can save multiple SPI bus access. To support this feature, PHY
+ * drivers can set .read_mmd/.write_mmd in the PHY driver to call
+ * .read_c45/.write_c45. Ex: drivers/net/phy/microchip_t1s.c
+ */
+ tc6->mdiobus->read_c45 = oa_tc6_mdiobus_read_c45;
+ tc6->mdiobus->write_c45 = oa_tc6_mdiobus_write_c45;
+
+ tc6->own_mdiobus = true;
}
tc6->mdiobus->priv = tc6;
- tc6->mdiobus->read = oa_tc6_mdiobus_read;
- tc6->mdiobus->write = oa_tc6_mdiobus_write;
- /* OPEN Alliance 10BASE-T1x compliance MAC-PHYs will have both C22 and
- * C45 registers space. If the PHY is discovered via C22 bus protocol it
- * assumes it uses C22 protocol and always uses C22 registers indirect
- * access to access C45 registers. This is because, we don't have a
- * clean separation between C22/C45 register space and C22/C45 MDIO bus
- * protocols. Resulting, PHY C45 registers direct access can't be used
- * which can save multiple SPI bus access. To support this feature, PHY
- * drivers can set .read_mmd/.write_mmd in the PHY driver to call
- * .read_c45/.write_c45. Ex: drivers/net/phy/microchip_t1s.c
- */
- tc6->mdiobus->read_c45 = oa_tc6_mdiobus_read_c45;
- tc6->mdiobus->write_c45 = oa_tc6_mdiobus_write_c45;
- tc6->mdiobus->name = "oa-tc6-mdiobus";
tc6->mdiobus->parent = tc6->dev;
+ tc6->mdiobus->name = "oa-tc6-mdiobus";
snprintf(tc6->mdiobus->id, ARRAY_SIZE(tc6->mdiobus->id), "%s",
- dev_name(&tc6->spi->dev));
+ dev_name(&tc6->spi->dev));
ret = mdiobus_register(tc6->mdiobus);
if (ret) {
@@ -577,19 +583,30 @@ static int oa_tc6_mdiobus_register(struct oa_tc6 *tc6)
static void oa_tc6_mdiobus_unregister(struct oa_tc6 *tc6)
{
+ if (!tc6->mdiobus)
+ return;
+
mdiobus_unregister(tc6->mdiobus);
- mdiobus_free(tc6->mdiobus);
+
+ if (tc6->own_mdiobus)
+ mdiobus_free(tc6->mdiobus);
}
static int oa_tc6_phy_init(struct oa_tc6 *tc6)
{
int ret;
- ret = oa_tc6_check_phy_reg_direct_access_capability(tc6);
- if (ret) {
- netdev_err(tc6->netdev,
- "Direct PHY register access is not supported by the MAC-PHY\n");
- return ret;
+ /* If the driver provided a mii_bus, it is also responsible for
+ * implementing the bus access methods, so we don't have to worry
+ * about checking the PHY access mode.
+ */
+ if (!tc6->mdiobus) {
+ ret = oa_tc6_check_phy_reg_direct_access_capability(tc6);
+ if (ret) {
+ netdev_err(tc6->netdev,
+ "Direct PHY register access is not supported by the MAC-PHY\n");
+ return ret;
+ }
}
ret = oa_tc6_mdiobus_register(tc6);
@@ -621,7 +638,9 @@ static int oa_tc6_phy_init(struct oa_tc6 *tc6)
static void oa_tc6_phy_exit(struct oa_tc6 *tc6)
{
- phy_disconnect(tc6->phydev);
+ if (tc6->phydev)
+ phy_disconnect(tc6->phydev);
+
oa_tc6_mdiobus_unregister(tc6);
}
@@ -1282,24 +1301,28 @@ static int oa_tc6_check_ctrl_protection(struct oa_tc6 *tc6)
/**
* oa_tc6_init - allocates and initializes oa_tc6 structure.
- * @spi: device with which data will be exchanged.
- * @netdev: network device interface structure.
+ * @config: pointer to a caller-filled structure describing the MACPHY
+ * (SPI device, net_device, and config flags).
*
* Return: pointer reference to the oa_tc6 structure if the MAC-PHY
* initialization is successful otherwise NULL.
*/
-struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev)
+struct oa_tc6 *oa_tc6_init(struct oa_tc6_config *config)
{
struct oa_tc6 *tc6;
int ret;
- tc6 = devm_kzalloc(&spi->dev, sizeof(*tc6), GFP_KERNEL);
+ if (!config)
+ return NULL;
+
+ tc6 = devm_kzalloc(&config->spi->dev, sizeof(*tc6), GFP_KERNEL);
if (!tc6)
return NULL;
- tc6->spi = spi;
- tc6->netdev = netdev;
- SET_NETDEV_DEV(netdev, &spi->dev);
+ tc6->spi = config->spi;
+ tc6->netdev = config->netdev;
+ tc6->mdiobus = config->mii_bus;
+ SET_NETDEV_DEV(tc6->netdev, &tc6->spi->dev);
mutex_init(&tc6->spi_ctrl_lock);
spin_lock_init(&tc6->tx_skb_lock);
diff --git a/include/linux/oa_tc6.h b/include/linux/oa_tc6.h
index 15f58e3c56c7..7ed7769bac88 100644
--- a/include/linux/oa_tc6.h
+++ b/include/linux/oa_tc6.h
@@ -8,11 +8,18 @@
*/
#include <linux/etherdevice.h>
+#include <linux/mdio.h>
#include <linux/spi/spi.h>
struct oa_tc6;
-struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev);
+struct oa_tc6_config {
+ struct spi_device *spi;
+ struct net_device *netdev;
+ struct mii_bus *mii_bus;
+};
+
+struct oa_tc6 *oa_tc6_init(struct oa_tc6_config *config);
void oa_tc6_exit(struct oa_tc6 *tc6);
int oa_tc6_write_register(struct oa_tc6 *tc6, u32 address, u32 value);
int oa_tc6_write_registers(struct oa_tc6 *tc6, u32 address, u32 value[],
--
2.43.0
^ permalink raw reply related
* [PATCH net-next 0/5] net: Add ADIN1140 support
From: Ciprian Regus via B4 Relay @ 2026-05-02 23:24 UTC (permalink / raw)
To: Parthiban Veerasooran, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Andrew Lunn, Heiner Kallweit, Russell King,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: netdev, linux-kernel, linux-doc, devicetree, Ciprian Regus
This series introduces support for the ADIN1140 (also called AD3306)
10BASE-T1S single port MACPHY. The device integrates the MAC and PHY in
the same package. The communication with the host CPU is done through an
SPI interface, using the Open Alliance TC6 protocol for control and data
transactions. As a result, the oa_tc6 framework is used to implement
the communication with the device (register accesses and Ethernet frame
RX/TX).
The MAC and PHY are connected internally using an MII and MDIO bus.
The PHY is a half duplex 10Mbps device, which implements both the PLCA
RS (IEEE 802.3 clause 148) and CSMA/CD methods of accessing the Ethernet
medium. The 10BASE-T1S standard allows multiple PHY devices to be
connected (in parallel) on the same single twisted pair network segment,
so PLCA can be configured in order to provide a fair access scheme to
all the nodes and reduce the jitter introduced by the unordered CSMA/CD
transmits. The PHY's internal register map can be accessed using the
direct MDIO mode of the OA TC6. The control, status, phy id 1 & 2 C22
registers are mapped to the 0xFF00 - 0xFF03 range. As for C45
addressable devices, the PHY has PCS, PMA and PLCA blocks.
The first 2 patches in the series are changes to the oa_tc6, that would
make the framework usable by the subsequent ADIN1140 MAC driver.
The first commit is required because the ADIN1140 only allows protected
mode OA TC6 control transactions, which the oa_tc6 framework doesn't
currently implement.
The second commit is required in order to allow the MAC driver to have a
custom implementation for the mii_bus access methods as a workaround for
hardware issues:
1. The OA TC6 standard defines the direct and indirect access modes for
MDIO transactions. The ADIN1140 incorrectly advertises indirect mode
only (supported capabilities register - 0x2, bit 9), while actually
implementing just the direct mode. We cannot rely on the CAP register
to choose an access method (which oa_tc6 does by default, even though
it only implements the direct mode), so the driver has to use its
own.
2. The ADIN1140 cannot access the C22 register space of the internal
PHY, while the PHY is busy receiving frames. If that happens, the
CONFIG0 and CONFIG2 registers of the MAC will get corrupted and the
data transfer will stop. Those two registers configure settings for
the transfer protocol between the MAC and host, so the value for some
of their subfields shouldn't be changed while the netdev is up.
Since we know the PHY is internal, the MAC driver can implement a
custom mii_bus, which can intercept C22 accesses. Most of the
registers mapped in the 0x0 - 0x3 range (the only ones the PHY offers)
are read only, and their value can be read from somewhere else (e.g
the PHYID 1 & 2 have the same value as 0x1 in the MAC memory map).
C45 accesses do not cause this issue, so we can properly implement
them.
Even though they have different driver, the MAC one cannot function
without the PHY driver, since the PHY is not compatible with the generic
c22 driver. As such CONFIG_ADIN1140 selects CONFIG_ADIN1140_PHY.
Signed-off-by: Ciprian Regus <ciprian.regus@analog.com>
---
Ciprian Regus (5):
net: ethernet: oa_tc6: Handle the OA TC6 SPI protected mode
net: ethernet: oa_tc6: Allow custom mii_bus
net: phy: Add support for the ADIN1140 PHY
net: ethernet: adi: Add a driver for the ADIN1140 MACPHY
dt-bindings: net: Add bindings for the ADIN1140
.../devicetree/bindings/net/adi,adin1140.yaml | 69 ++
Documentation/networking/oa-tc6-framework.rst | 3 +-
MAINTAINERS | 15 +
drivers/net/ethernet/adi/Kconfig | 12 +
drivers/net/ethernet/adi/Makefile | 1 +
drivers/net/ethernet/adi/adin1140.c | 805 +++++++++++++++++++++
drivers/net/ethernet/microchip/lan865x/lan865x.c | 6 +-
drivers/net/ethernet/oa_tc6.c | 194 +++--
drivers/net/phy/Kconfig | 6 +
drivers/net/phy/Makefile | 1 +
drivers/net/phy/adin1140.c | 102 +++
include/linux/oa_tc6.h | 9 +-
12 files changed, 1173 insertions(+), 50 deletions(-)
---
base-commit: fbf6f64a4322cfeb0d98f39baf8ce18246dd12c0
change-id: 20260429-adin1140-driver-93ae0d376318
Best regards,
--
Ciprian Regus <ciprian.regus@analog.com>
^ permalink raw reply
* [PATCH net-next 1/5] net: ethernet: oa_tc6: Handle the OA TC6 SPI protected mode
From: Ciprian Regus via B4 Relay @ 2026-05-02 23:24 UTC (permalink / raw)
To: Parthiban Veerasooran, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Andrew Lunn, Heiner Kallweit, Russell King,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: netdev, linux-kernel, linux-doc, devicetree, Ciprian Regus
In-Reply-To: <20260503-adin1140-driver-v1-0-dd043cdd88f0@analog.com>
From: Ciprian Regus <ciprian.regus@analog.com>
Implement the OA TC6 standard defined protected mode for control (register
access) transactions. In addition to the current register access formats
the oa_tc6 driver handles, 1's complement values of the data field
are included (by both the host and the MACPHY) in the SPI transfer frames.
This feature acts as an integrity check.
Control write transactions look like this:
|<- 32 bits ->|<--- data_size --->|<- 32 bits ->|
MOSI: | ctrl header | reg write data | ignored |
MISO: | (discard) | echoed ctrl hdr | echoed data |
data_size (LEN = number of registers to read in a sequence):
Unprotected: 32 x (LEN + 1) bits
Protected: 2 x 32 x (LEN + 1) bits
Control read transaction:
|<- 32 bits ->|<--- 32 bits --> |<- data_size ->|
MOSI: | ctrl header | ignored ... |
MISO: | (discard) | echoed ctrl hdr | reg read data |
data_size (LEN = number of registers to read in a sequence):
Unprotected: 32 x (LEN + 1) bits
Protected: 2 x 32 x (LEN + 1) bits
Register data format ("reg write data" and "reg read data"):
Unprotected:
| W1 (normal) | W2 (normal) | ... | Wx (normal) |
Protected:
| W1 (normal) | W1 (complement) | ... | Wx (normal) | Wx (complement)|
The protected mode state can be read from the bit 5 of CONFIG0 (0x4)
register, and this setting is usually only configured during the
MACPHY's reset (depending on the device it can be done by setting the
state of a pin). We can read the protected mode configuration before any
other register access and since the SPI transfer is initially sized for an
unprotected read, the MACPHY's complement words are never clocked out
and no checking is required. The data transactions (Ethernet frames)
remain unchanged.
Signed-off-by: Ciprian Regus <ciprian.regus@analog.com>
---
drivers/net/ethernet/oa_tc6.c | 105 ++++++++++++++++++++++++++++++++++++------
1 file changed, 92 insertions(+), 13 deletions(-)
diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c
index 91a906a7918a..546ca652d974 100644
--- a/drivers/net/ethernet/oa_tc6.c
+++ b/drivers/net/ethernet/oa_tc6.c
@@ -24,6 +24,7 @@
#define OA_TC6_REG_CONFIG0 0x0004
#define CONFIG0_SYNC BIT(15)
#define CONFIG0_ZARFE_ENABLE BIT(12)
+#define CONFIG0_PROTE BIT(5)
/* Status Register #0 */
#define OA_TC6_REG_STATUS0 0x0008
@@ -87,6 +88,7 @@
#define OA_TC6_PHY_C45_AUTO_NEG_MMS5 5 /* MMD 7 */
#define OA_TC6_PHY_C45_POWER_UNIT_MMS6 6 /* MMD 13 */
+#define OA_TC6_CTRL_PROT_REPLY_SIZE 4
#define OA_TC6_CTRL_HEADER_SIZE 4
#define OA_TC6_CTRL_REG_VALUE_SIZE 4
#define OA_TC6_CTRL_IGNORED_SIZE 4
@@ -95,6 +97,13 @@
(OA_TC6_CTRL_MAX_REGISTERS *\
OA_TC6_CTRL_REG_VALUE_SIZE) +\
OA_TC6_CTRL_IGNORED_SIZE)
+
+#define OA_TC6_CTRL_SPI_BUF_PROT_SIZE (OA_TC6_CTRL_HEADER_SIZE +\
+ (OA_TC6_CTRL_MAX_REGISTERS *\
+ (OA_TC6_CTRL_REG_VALUE_SIZE +\
+ OA_TC6_CTRL_PROT_REPLY_SIZE)) +\
+ OA_TC6_CTRL_IGNORED_SIZE)
+
#define OA_TC6_CHUNK_PAYLOAD_SIZE 64
#define OA_TC6_DATA_HEADER_SIZE 4
#define OA_TC6_CHUNK_SIZE (OA_TC6_DATA_HEADER_SIZE +\
@@ -129,6 +138,7 @@ struct oa_tc6 {
u8 rx_chunks_available;
bool rx_buf_overflow;
bool int_flag;
+ bool prot_ctrl;
};
enum oa_tc6_header_type {
@@ -212,25 +222,36 @@ static void oa_tc6_update_ctrl_write_data(struct oa_tc6 *tc6, u32 value[],
{
__be32 *tx_buf = tc6->spi_ctrl_tx_buf + OA_TC6_CTRL_HEADER_SIZE;
- for (int i = 0; i < length; i++)
+ for (int i = 0; i < length; i++) {
*tx_buf++ = cpu_to_be32(value[i]);
+ if (tc6->prot_ctrl)
+ *tx_buf++ = cpu_to_be32(~value[i]);
+ }
}
-static u16 oa_tc6_calculate_ctrl_buf_size(u8 length)
+static u16 oa_tc6_calculate_ctrl_buf_size(u8 length, bool ctrl_prot)
{
+ u32 reply_size = OA_TC6_CTRL_REG_VALUE_SIZE;
+
+ if (ctrl_prot)
+ reply_size += OA_TC6_CTRL_PROT_REPLY_SIZE;
+
/* Control command consists 4 bytes header + 4 bytes register value for
- * each register + 4 bytes ignored value.
+ * each register (+ 4 bytes for the register value complement in case
+ * protected mode is used) + 4 bytes ignored value.
*/
- return OA_TC6_CTRL_HEADER_SIZE + OA_TC6_CTRL_REG_VALUE_SIZE * length +
+ return OA_TC6_CTRL_HEADER_SIZE + reply_size * length +
OA_TC6_CTRL_IGNORED_SIZE;
}
static void oa_tc6_prepare_ctrl_spi_buf(struct oa_tc6 *tc6, u32 address,
u32 value[], u8 length,
- enum oa_tc6_register_op reg_op)
+ enum oa_tc6_register_op reg_op,
+ u16 buf_size)
{
__be32 *tx_buf = tc6->spi_ctrl_tx_buf;
+ memset(tx_buf, 0, buf_size);
*tx_buf = oa_tc6_prepare_ctrl_header(address, length, reg_op);
if (reg_op == OA_TC6_CTRL_REG_WRITE)
@@ -253,10 +274,12 @@ static int oa_tc6_check_ctrl_write_reply(struct oa_tc6 *tc6, u8 size)
return 0;
}
-static int oa_tc6_check_ctrl_read_reply(struct oa_tc6 *tc6, u8 size)
+static int oa_tc6_check_ctrl_read_reply(struct oa_tc6 *tc6, u8 length)
{
- u32 *rx_buf = tc6->spi_ctrl_rx_buf + OA_TC6_CTRL_IGNORED_SIZE;
- u32 *tx_buf = tc6->spi_ctrl_tx_buf;
+ __be32 *rx_buf = tc6->spi_ctrl_rx_buf + OA_TC6_CTRL_IGNORED_SIZE;
+ __be32 *tx_buf = tc6->spi_ctrl_tx_buf;
+ u32 complement;
+ u32 reply;
/* The echoed control read header must match with the one that was
* transmitted.
@@ -264,6 +287,20 @@ static int oa_tc6_check_ctrl_read_reply(struct oa_tc6 *tc6, u8 size)
if (*tx_buf != *rx_buf)
return -EPROTO;
+ if (tc6->prot_ctrl) {
+ /* Skip past the echoed header to the value/complement pairs */
+ rx_buf += 1;
+ for (int i = 0; i < length; i++) {
+ reply = be32_to_cpu(rx_buf[0]);
+ complement = be32_to_cpu(rx_buf[1]);
+
+ if (complement != ~reply)
+ return -EPROTO;
+
+ rx_buf += 2;
+ }
+ }
+
return 0;
}
@@ -273,8 +310,13 @@ static void oa_tc6_copy_ctrl_read_data(struct oa_tc6 *tc6, u32 value[],
__be32 *rx_buf = tc6->spi_ctrl_rx_buf + OA_TC6_CTRL_IGNORED_SIZE +
OA_TC6_CTRL_HEADER_SIZE;
- for (int i = 0; i < length; i++)
+ for (int i = 0; i < length; i++) {
value[i] = be32_to_cpu(*rx_buf++);
+
+ /* skip complement word */
+ if (tc6->prot_ctrl)
+ rx_buf++;
+ }
}
static int oa_tc6_perform_ctrl(struct oa_tc6 *tc6, u32 address, u32 value[],
@@ -283,10 +325,10 @@ static int oa_tc6_perform_ctrl(struct oa_tc6 *tc6, u32 address, u32 value[],
u16 size;
int ret;
- /* Prepare control command and copy to SPI control buffer */
- oa_tc6_prepare_ctrl_spi_buf(tc6, address, value, length, reg_op);
+ size = oa_tc6_calculate_ctrl_buf_size(length, tc6->prot_ctrl);
- size = oa_tc6_calculate_ctrl_buf_size(length);
+ /* Prepare control command and copy to SPI control buffer */
+ oa_tc6_prepare_ctrl_spi_buf(tc6, address, value, length, reg_op, size);
/* Perform SPI transfer */
ret = oa_tc6_spi_transfer(tc6, OA_TC6_CTRL_HEADER, size);
@@ -301,7 +343,7 @@ static int oa_tc6_perform_ctrl(struct oa_tc6 *tc6, u32 address, u32 value[],
return oa_tc6_check_ctrl_write_reply(tc6, size);
/* Check echoed/received control read command reply for errors */
- ret = oa_tc6_check_ctrl_read_reply(tc6, size);
+ ret = oa_tc6_check_ctrl_read_reply(tc6, length);
if (ret)
return ret;
@@ -1224,6 +1266,20 @@ netdev_tx_t oa_tc6_start_xmit(struct oa_tc6 *tc6, struct sk_buff *skb)
}
EXPORT_SYMBOL_GPL(oa_tc6_start_xmit);
+static int oa_tc6_check_ctrl_protection(struct oa_tc6 *tc6)
+{
+ u32 regval;
+ int ret;
+
+ ret = oa_tc6_read_register(tc6, OA_TC6_REG_CONFIG0, ®val);
+ if (ret)
+ return ret;
+
+ tc6->prot_ctrl = FIELD_GET(CONFIG0_PROTE, regval);
+
+ return 0;
+}
+
/**
* oa_tc6_init - allocates and initializes oa_tc6 structure.
* @spi: device with which data will be exchanged.
@@ -1276,6 +1332,29 @@ struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev)
if (!tc6->spi_data_rx_buf)
return NULL;
+ ret = oa_tc6_check_ctrl_protection(tc6);
+ if (ret) {
+ dev_err(&tc6->spi->dev,
+ "Failed to check the protection mode: %d\n", ret);
+ return NULL;
+ }
+
+ if (tc6->prot_ctrl) {
+ tc6->spi_ctrl_tx_buf = devm_krealloc(&tc6->spi->dev,
+ tc6->spi_ctrl_tx_buf,
+ OA_TC6_CTRL_SPI_BUF_PROT_SIZE,
+ GFP_KERNEL);
+ if (!tc6->spi_ctrl_tx_buf)
+ return NULL;
+
+ tc6->spi_ctrl_rx_buf = devm_krealloc(&tc6->spi->dev,
+ tc6->spi_ctrl_rx_buf,
+ OA_TC6_CTRL_SPI_BUF_PROT_SIZE,
+ GFP_KERNEL);
+ if (!tc6->spi_ctrl_rx_buf)
+ return NULL;
+ }
+
ret = oa_tc6_sw_reset_macphy(tc6);
if (ret) {
dev_err(&tc6->spi->dev,
--
2.43.0
^ permalink raw reply related
* Re: [PATCH v12 12/22] gpu: nova-core: mm: Add page table entry operation traits
From: Joel Fernandes @ 2026-05-02 19:19 UTC (permalink / raw)
To: Alexandre Courbot
Cc: linux-kernel, Miguel Ojeda, Boqun Feng, Gary Guo, Bjorn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Dave Airlie, Daniel Almeida, dri-devel,
rust-for-linux, nova-gpu, Nikola Djukic, David Airlie, Boqun Feng,
John Hubbard, Alistair Popple, Timur Tabi, Edwin Peer,
Andrea Righi, Andy Ritger, Zhi Wang, Balbir Singh,
Philipp Stanner, alexeyi, Eliot Courtney, joel, linux-doc
In-Reply-To: <DI8B0IOXNP2L.1NFX4OTABNHA0@nvidia.com>
On 5/2/2026 11:42 AM, Alexandre Courbot wrote:
> On Sun Apr 26, 2026 at 6:14 AM JST, Joel Fernandes wrote:
>> Introduce trait-based abstractions for GPU page table entries: PteOps,
>> PdeOps, and DualPdeOps, along with the MmuConfig trait that ties them
>> together with version-specific constants.
>>
>> Refactor the ver2 and ver3 page-table modules to implement these traits
>> and expose the shared entry/PDE/PTE operations uniformly.
>
> Please, no. We don't introduce code that gets refactored the very next
> commit. This patch supersedes 270 lines of diff that reviewers will have
> processed for nothing.
I really don't know what you mean by '270 lines of diff for nothing'
because this patch only adds a negative delta of 90 lines or so, before
adding yet another 300+ new lines. So it really is building on the previous
patch, which is how we do things upstream. It is iterative. My sense is you
reviewed this in a hurry and missed that fact while getting hung up on the
words 'refactor'.
Keep in mind also that the addition of PteOps set of traits refactor was
done much later in the iterations.
> Please reorder things so they land, as much as possible, in their final
> form. In this case this probably means defining the trait *before* the V2
> and V3 page table definitions, so they can implement it from the get-go.
That is a reasonable approach too, I can try to do that, but it is
misleading to say '270 lines of diff that reviewers will have processed for
nothing' which is nothing but fiction. Please look more carefully, the
patch is iterative on the series.
Thanks.
^ permalink raw reply
* Re: [PATCH v2] docs: Remove stale ISDN parameters
From: Costa Shulyupin @ 2026-05-02 18:18 UTC (permalink / raw)
To: Randy Dunlap
Cc: Jakub Kicinski, Jonathan Corbet, Shuah Khan, linux-doc,
linux-kernel
In-Reply-To: <70322b63-be09-457d-a73a-2fd2612b71b7@infradead.org>
On Sat, 2 May 2026 at 19:35, Randy Dunlap <rdunlap@infradead.org> wrote:
> I'm curious: how and why did Claude assist you in this?
I've asked Claude to find and fix outdated content in the documentation.
Thanks
Costa
^ permalink raw reply
* Re: [PATCH v12 09/22] gpu: nova-core: mm: Add common types for all page table formats
From: Joel Fernandes @ 2026-05-02 17:55 UTC (permalink / raw)
To: Alexandre Courbot
Cc: linux-kernel, Miguel Ojeda, Boqun Feng, Gary Guo, Bjorn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Dave Airlie, Daniel Almeida, dri-devel,
rust-for-linux, nova-gpu, Nikola Djukic, David Airlie, Boqun Feng,
John Hubbard, Alistair Popple, Timur Tabi, Edwin Peer,
Andrea Righi, Andy Ritger, Zhi Wang, Balbir Singh,
Philipp Stanner, alexeyi, Eliot Courtney, joel, linux-doc
In-Reply-To: <DI8B094FNUYM.3MA4K3VXN53NV@nvidia.com>
On 5/2/2026 11:42 AM, Alexandre Courbot wrote:
> On Sun Apr 26, 2026 at 6:14 AM JST, Joel Fernandes wrote:
>> Add common page table types shared between MMU v2 and v3. These types
>> are hardware-agnostic and used by both MMU versions.
>>
>> Cc: Nikola Djukic <ndjukic@nvidia.com>
>> Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
>> ---
>> drivers/gpu/nova-core/mm.rs | 1 +
>> drivers/gpu/nova-core/mm/pagetable.rs | 157 ++++++++++++++++++++++++++
>> 2 files changed, 158 insertions(+)
>> create mode 100644 drivers/gpu/nova-core/mm/pagetable.rs
>>
>> diff --git a/drivers/gpu/nova-core/mm.rs b/drivers/gpu/nova-core/mm.rs
>> index 8b8a86980bb6..045e35c92b78 100644
>> --- a/drivers/gpu/nova-core/mm.rs
>> +++ b/drivers/gpu/nova-core/mm.rs
>> @@ -32,6 +32,7 @@ macro_rules! impl_pfn_bounded {
>> };
>> }
>>
>> +pub(super) mod pagetable;
>> pub(crate) mod pramin;
>> pub(super) mod tlb;
>>
>> diff --git a/drivers/gpu/nova-core/mm/pagetable.rs b/drivers/gpu/nova-core/mm/pagetable.rs
>> new file mode 100644
>> index 000000000000..637ff43ea83a
>> --- /dev/null
>> +++ b/drivers/gpu/nova-core/mm/pagetable.rs
>> @@ -0,0 +1,157 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +
>> +//! Common page table types shared between MMU v2 and v3.
>> +//!
>> +//! This module provides foundational types used by both MMU versions:
>> +//! - Page table level hierarchy
>> +//! - Memory aperture types for PDEs and PTEs
>> +
>> +#![expect(dead_code)]
>> +
>> +use kernel::num::Bounded;
>> +
>> +use crate::gpu::Architecture;
>> +
>> +/// Extracts the page table index at a given level from a virtual address.
>> +pub(super) trait VaLevelIndex {
>> + /// Return the page table index at `level` for this virtual address.
>> + fn level_index(&self, level: u64) -> u64;
>> +}
>> +
>> +/// MMU version enumeration.
>> +#[derive(Debug, Clone, Copy, PartialEq, Eq)]
>> +pub(crate) enum MmuVersion {
>> + /// MMU v2 for Turing/Ampere/Ada.
>> + V2,
>> + /// MMU v3 for Hopper and later.
>> + V3,
>> +}
>> +
>> +impl From<Architecture> for MmuVersion {
>> + fn from(arch: Architecture) -> Self {
>> + match arch {
>> + Architecture::Turing | Architecture::Ampere | Architecture::Ada => Self::V2,
>> + // In the future, uncomment the following to support V3.
>> + // _ => Self::V3,
>
> The architecture definitions for Blackwell are now in `drm-rust-next`,
> so I think the next iteration can handle this.
>
> Which reminds me: is V3 working? I remember some fixup by Eliot
> wandering around, have you integrated it to the series?
MMU v3 is working. But this series needs more patches on top of it for
Blackwell, I am carrying those extras in a different tree based on this
series. The fixes from have all been integrated (either in this series or
that other tree of extras).
Have all the base blackwell patches made it into drm-rust-next? If yes, I
can rebase this series on it, pull in the extras, and continue posting the
next mm iteration with blackwell support.
Thanks.
^ permalink raw reply
* Re: [PATCH v12 22/22] rust: maple_tree: implement Send and Sync for MapleTree
From: Joel Fernandes @ 2026-05-02 17:36 UTC (permalink / raw)
To: Alexandre Courbot
Cc: linux-kernel, Miguel Ojeda, Boqun Feng, Gary Guo, Bjorn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Dave Airlie, Daniel Almeida, dri-devel,
rust-for-linux, nova-gpu, Nikola Djukic, David Airlie, Boqun Feng,
John Hubbard, Alistair Popple, Timur Tabi, Edwin Peer,
Andrea Righi, Andy Ritger, Zhi Wang, Balbir Singh,
Philipp Stanner, alexeyi, Eliot Courtney, joel, linux-doc
In-Reply-To: <DI8B0RAI4S86.2K4SLZH2YS2W5@nvidia.com>
On 5/2/2026 11:42 AM, Alexandre Courbot wrote:
> On Sun Apr 26, 2026 at 6:14 AM JST, Joel Fernandes wrote:
>> The C maple_tree struct contains a *mut c_void, which prevents Rust from
>> auto-deriving Send/Sync. Following is an example error message when using
>> MapleTree in nova-core's Vmm.
>>
>> This propagates up through MapleTreeAlloc to Vmm, BarUser, Gpu, and NovaCore,
>> causing NovaCore to fail the Send bound required by pci::Driver:
>
> This patch is not at the right place - the last few patches of the
> series won't build without it.
Right, the maple tree change in an earlier patch makes this one required to
go before it.
> Besides you have sent it separately before this series, so just mention
> it as a dependency in the cover letter, so reviewers can pick it up
> before applying the series and not witness it breaking in the course of
> reviewing it. The cover letter already mentions the bitfield series as a
> dependency, so the same could have been done for this one.
The sending separately bit was actually an early RFC I wanted to get
feedback on. But indeed, now that I have got that I can just post it
independently. I'll post it separately once I am back to work. Thanks,
--
Joel Fernandes
^ permalink raw reply
* Re: [PATCH] Documentation: watchdog: Fix typo "oncse" -> "once"
From: Randy Dunlap @ 2026-05-02 16:48 UTC (permalink / raw)
To: Wang Zihan, linux-watchdog
Cc: wim, linux, corbet, skhan, linux-doc, linux-kernel
In-Reply-To: <9a6393f4-6aec-4f64-b3ef-9566206c4ac7@infradead.org>
On 5/2/26 9:32 AM, Randy Dunlap wrote:
>
>
> On 5/2/26 4:19 AM, Wang Zihan wrote:
>> Fix a typo in mlx-wdt.rst documentation.
>>
>> Signed-off-by: Wang Zihan <3772548978@qq.com>
>> ---
>> Documentation/watchdog/mlx-wdt.rst | 2 +-
>> 1 file changed, 1 insertion(+), 1 deletion(-)
>>
>> diff --git a/Documentation/watchdog/mlx-wdt.rst b/Documentation/watchdog/mlx-wdt.rst
>> index 35e690dea..3778f85d1 100644
>> --- a/Documentation/watchdog/mlx-wdt.rst
>> +++ b/Documentation/watchdog/mlx-wdt.rst
>> @@ -48,7 +48,7 @@ which is optional.
>> Watchdog can be started during a probe, in this case it will be
>> pinged by watchdog core before watchdog device will be opened by
>> user space application.
>> -Watchdog can be initialised in nowayout way, i.e. oncse started
>> +Watchdog can be initialised in nowayout way, i.e. once started
>> it can't be stopped.
>>
>> This mlx-wdt driver supports both HW watchdog implementations.
>
> This typo is also fixed in my 5-patch series (pending).
[PATCH 1/5] docs: watchdog: mlx-wdt: small fixes
https://lore.kernel.org/linux-watchdog/20260228010402.2389343-2-rdunlap@infradead.org/
--
~Randy
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox