Installing OEM Specific UART Driver

I am trying to install a driver for UART ports on a custom hardware build using the conga-SA5. I am using the legacy ports and they are controlled by the Board Controller. They provide a Linux driver for them. In the tarball I downloaded from their website are three files:

Readme

=================================================================
          congatec BC serial port driver for linux v1.1
=================================================================

------------
INTRODUCTION
------------

Some newer congatec boards contain two onboard module serial ports.
This package provides the congatec BC serial port driver for linux.

Currently kernel versions 3.x are supported. Please contact your 
local congatec sales partner if you need support for other kernels.

The driver has been compiled with kernel version 3.2 and 3.13 under
Ubuntu/Xubuntu 12.04 LTS and 14.04 LTS.

------------
INSTALLATION
------------

Please make sure that the *module* serial ports are enabled in the BIOS
setup and configured as CGT0501 and CGT0502.

Extract the driver from the cgbcser.tar.gz archive to a new directory.
  tar -xzf cgbcser.tar.gz

The driver builds as a module cgbcser.ko
To compile the driver just run
  make

The driver can be installed with
  make install

To remove the driver run
  make uninstall

Makefile

obj-m += cgbcser.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
	
install:
	install -m 644 -o root -g root cgbcser.ko /lib/modules/`uname -r`/kernel/drivers/tty/serial
	depmod -a

mod:
	modprobe cgbcser

uninstall:
	rmmod cgbcser
	rm /lib/modules/`uname -r`/kernel/drivers/tty/serial/cgbcser.ko
	depmod -a

mship:
	tar -czf cgbcser.tar.gz cgbcser.c Makefile readme

cgbser.c

/*
 *  Serial port driver for congatec board controller
 *  Copyright (C) 2014 <info@congatec.com>, congatec
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License.
 * V1.1
 */
#include <linux/version.h>
#include <linux/module.h>
#include <linux/pnp.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/serial_core.h>
#include <linux/serial_reg.h>
#include <linux/serial_8250.h>

#define dbpf(pars)
/* #define dbpf(pars) printk pars */

static const struct pnp_device_id pnp_dev_table[] = {
	/* congatec BC serial Plug & Play */
	{	"CGT0501",		0	},
	{	"CGT0502",		1	},
	{	"",       		0	}
};

MODULE_DEVICE_TABLE(pnp, pnp_dev_table);


/*
 * Board Controller Serial Port 0
 */

static unsigned int io_serial_in(struct uart_port *p, int offset)
{
	unsigned int value;
	value = inb(p->iobase + offset);
	if (offset == UART_IIR) {
		unsigned int ier = inb(p->iobase + UART_IER);
		if ((ier & 2) && (value & 6) == 2)
			outb(ier & ~2, p->iobase + UART_IER);
		value = (value & 6) ? value & ~1 : value | 1;
	}
	dbpf((KERN_DEBUG "cgbcser: IN(%d)->0x%02x\n", offset, value));
	return value;
}

static void io_serial_out(struct uart_port *p, int offset, int value)
{
	dbpf((KERN_DEBUG "cgbcser: OUT(%d,0x%02x)\n", offset, value));
	switch (offset) {
		case UART_LCR:
			if ((value & UART_LCR_DLAB)) {
				outb(value, p->iobase + offset);
				udelay(10);
				return;
			} else udelay(2);
			break;
			
		case UART_DLM:
			if ((inb(p->iobase + UART_LCR) & UART_LCR_DLAB))
				return;
			break;
			
		case UART_TX: {
			unsigned int ier = inb(p->iobase + UART_IER);		
			if ((ier & 0x0f)) {
				outb(value, p->iobase + offset);
				outb(ier | 2, p->iobase + UART_IER);
				return;
			}
			break;
		}
	}
	
	outb(value, p->iobase + offset);
}


/*
 * Board Controller Serial Port 1
 */

#define BC_COM1_CMD 0
#define BC_COM1_DAT 1
#define BC_COM1_STR 3

static unsigned int io_serial_in1(struct uart_port *p, int offset)
{
	unsigned int value;
	while (inb(p->iobase + BC_COM1_STR));
	outb(offset, p->iobase + BC_COM1_CMD);
	outb(1, p->iobase + BC_COM1_STR);
	while (inb(p->iobase + BC_COM1_STR));
	value = inb(p->iobase + BC_COM1_DAT);
	if (offset == UART_IIR) value |= 0xC0; // kernel tests to see if FIFOs are enabled to see if 16550A is supported
	if (offset == UART_IER) value &= ~(UART_IER_UUE); // turn off UUE to ensure not detected as Xscale instead of 16550A
	dbpf((KERN_DEBUG "cgbcser: IN(%d)->0x%02x)\n", offset, value));
	return value;
}

static void io_serial_out1(struct uart_port *p, int offset, int value)
{
	dbpf((KERN_DEBUG "cgbcser: OUT(%d,0x%02x)\n", offset, value));
	while (inb(p->iobase + BC_COM1_STR));
	outb(8 | offset, p->iobase + BC_COM1_CMD);
	outb(value, p->iobase + BC_COM1_DAT);
	outb(1, p->iobase + BC_COM1_STR);
}


/*
 * Driver interface
 */

static int 
cgbcser_probe(struct pnp_dev *dev, const struct pnp_device_id *dev_id)
{
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0)
	struct uart_port uart;
	struct uart_port *port=&uart;
	#define serial8250_register_8250_port(p) serial8250_register_port(p)
#else
	struct uart_8250_port uart;
	struct uart_port *port=&uart.port;
#endif	
	int line, flags = dev_id->driver_data;

	memset(&uart, 0, sizeof(uart));
	if (pnp_irq_valid(dev, 0))
		port->irq = pnp_irq(dev, 0);
	if (pnp_port_valid(dev, 0)) {
		port->iobase = pnp_port_start(dev, 0);
		port->iotype = UPIO_PORT;
	} else
		return -ENODEV;

	dbpf((KERN_DEBUG
		"Setup cgbcser port: port 0x%lx, irq %d, type %d\n",
		       port->iobase, port->irq, port->iotype));

	port->flags |= UPF_SKIP_TEST | UPF_BOOT_AUTOCONF | UPF_BUGGY_UART;
	if (pnp_irq_flags(dev, 0) & IORESOURCE_IRQ_SHAREABLE)
		port->flags |= UPF_SHARE_IRQ;
	port->uartclk = 1843200;
	/*port->fifosize = 16*/;
	port->dev = &dev->dev;

	port->serial_in = (flags) ? io_serial_in1 : io_serial_in;
	port->serial_out = (flags) ? io_serial_out1 : io_serial_out;

	line = serial8250_register_8250_port(&uart);
	if (line < 0)
		return -ENODEV;

	pnp_set_drvdata(dev, (void *)((long)line + 1));
	return 0;
}

static void cgbcser_remove(struct pnp_dev *dev)
{
	long line = (long)pnp_get_drvdata(dev);
	if (line)
		serial8250_unregister_port(line - 1);
}

#ifdef CONFIG_PM
static int cgbcser_suspend(struct pnp_dev *dev, pm_message_t state)
{
	long line = (long)pnp_get_drvdata(dev);

	if (!line)
		return -ENODEV;
	serial8250_suspend_port(line - 1);
	return 0;
}

static int cgbcser_resume(struct pnp_dev *dev)
{
	long line = (long)pnp_get_drvdata(dev);

	if (!line)
		return -ENODEV;
	serial8250_resume_port(line - 1);
	return 0;
}
#else
#define cgbcser_suspend NULL
#define cgbcser_resume NULL
#endif /* CONFIG_PM */

static struct pnp_driver cgbcser_driver = {
	.name		= "cgbcser",
	.probe		= cgbcser_probe,
	.remove		= cgbcser_remove,
	.suspend	= cgbcser_suspend,
	.resume		= cgbcser_resume,
	.id_table	= pnp_dev_table,
};

static int __init cgbcser_init(void)
{
	dbpf((KERN_DEBUG "cgbcser: init\n"));
	return pnp_register_driver(&cgbcser_driver);
}

static void __exit cgbcser_exit(void)
{
	dbpf((KERN_DEBUG "cgbcser: exit\n"));
	pnp_unregister_driver(&cgbcser_driver);
}

module_init(cgbcser_init);
module_exit(cgbcser_exit);

MODULE_AUTHOR("congatec <info@congatec.com>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("congatec BC serial driver");

I tried following the Readme, but I'm getting a make error. I'm guessing it's because I don't have the kernel headers. Ideally, I would compile this on the router, but I don't mind cross compiling on Ubuntu first then copying the modules onto the router, so long as I can find the correct version of the linux-headers package on Ubuntu for 21.02.0.

The build system isn't designed to run on the router itself, and would most likely fail.

You need to cross compile, probably by checking out the whole build environment for openwrt.

I followed this and this to the letter and found the path to the kernel headers for 21.02.0, which I believe to be ~/openwrt/build_dir/target-x86_64_musl/linux-x86_64/linux-5.4.143. In my make file, posted above, I changed the first two make options all and clean to

make -C ~/openwrt/build_dir/target-x86_64_musl/linux-x86_64/linux-5.4.143 M=$(PWD)

followed with modules and clean respectively.

I ran the Makefile and it worked! Now I have some compiled modules in the directory and one of them is cgbcser.ko. I scp'd it to the router and put it in /lib/modules/5.4.143. Since there is no install command on the router, I chmod'd and chown'd the file to 644 and root:root respectively. And since there is no depmod command, I used modprobe to add the module.

I still have not been able to get it to work. How can I add the cross compiled driver to the router?