From: Miroslav Ondra Date: Tue, 13 Aug 2019 10:36:27 +0000 (+0200) Subject: Squashed commit of the following: X-Git-Url: http://git.graph-it.com/?a=commitdiff_plain;h=8146cc9e4f50bb2782c6fa7af03f15b3f70294b4;p=graphit%2Funipi-kernel.git Squashed commit of the following: add ci/cd support for amd64 fix rtc-unipi for kernel > 4.17 fix module version 1.23. fix ci/cd. add uSE_UNIPI_CPUFREQ_PATCH directive remove cpufreq management in unipi_spi_set_cs. Hangups with Iqrf spi --- diff --git a/.gitignore b/.gitignore index 91818a1..3152749 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ *.rc */*/bin/* !*/*/bin/.dummy +/tmp/ modules/unipi/tmp* modules/unipi/.tmp_versions modules/unipi/unipi.mod.c diff --git a/Makefile b/Makefile index fb7e5a2..a3c8213 100644 --- a/Makefile +++ b/Makefile @@ -3,8 +3,7 @@ # if necessary. MODULES_DIR_PATH = ${PWD}/modules/ -MODULES_LIST = unipi/ -TRANSFER_MODULE = unipi/ +MODULES_LIST = unipi/ rtc-unipi/ .PHONY: default #default: symlink ; @@ -22,15 +21,13 @@ install: cd ${MODULES_DIR_PATH}$$m; make modules_install INSTALL_MOD_PATH=${DESTDIR};\ done -clean: +dkms: for m in ${MODULES_LIST}; do\ - cd ${MODULES_DIR_PATH}$$m; make clean;\ + cd ${MODULES_DIR_PATH}$$m; make dkms INSTALL_MOD_PATH=${DESTDIR}/$$m;\ done -transfer: - cd ${MODULES_DIR_PATH}${TRANSFER_MODULE}; make transfer; - -symlink: +clean: for m in ${MODULES_LIST}; do\ - cd ${MODULES_DIR_PATH}$$m; make symlink;\ + cd ${MODULES_DIR_PATH}$$m; make clean;\ done + diff --git a/debian/clean b/debian/clean index e9022e4..c365c7e 100644 --- a/debian/clean +++ b/debian/clean @@ -1,6 +1,7 @@ +debian/unipi-kernel-modules.changelog debian/neuron-kernel.install debian/unipi-kernel-modules-dkms.install debian/neuron-kernel.substvars debian/neuron-kernel.postinst.debhelper debian/neuron-kernel.postrm.debhelper -debian/neuron-kernel/ \ No newline at end of file +debian/neuron-kernel/ diff --git a/debian/control b/debian/control index 1bfcf09..1e87eb8 100644 --- a/debian/control +++ b/debian/control @@ -16,7 +16,7 @@ Description: UniPi Neuron kernel modules Package: unipi-kernel-modules-dkms Architecture: linux-any -Pre-Depends: raspberrypi-kernel-headers | linux-headers(>= 4.0) +Pre-Depends: raspberrypi-kernel-headers | axon-kernel-headers(>= 4.0) | linux-headers(>=4.0) Depends: ${misc:Depends}, unipi-common, dkms Replaces: neuron-kernel Conflicts: neuron-kernel @@ -25,3 +25,11 @@ Description: UniPi Neuron kernel modules - DKMS source used by internal boards in the UniPi Neuron/Axon controllers. Can be used with DKMS so that local kernel images are automatically built and installed every time relevant kernel packages are upgraded. + +Package: unipi-kernel-modules +Architecture: arm64 +Depends: ${misc:Depends}, unipi-common, axon-kernel-image(=${AXON-KERNEL-VER}) +Description: UniPi Axon kernel modules + Binary kernel modules for UniPi Axon controller. Compiled for axon-kernel-image + version. + diff --git a/debian/rules b/debian/rules index 752aa42..f7d3718 100755 --- a/debian/rules +++ b/debian/rules @@ -12,25 +12,33 @@ #export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic # package maintainers to append LDFLAGS #export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed +export DEB_LDFLAGS_SET = -zrelro MOD_VERSION=$(shell dpkg-parsechangelog |grep ^Version:|cut -d ' ' -f 2) +ifeq ($(shell dpkg-architecture -q DEB_HOST_ARCH),armhf) ifeq ($(shell dpkg-architecture -q DEB_BUILD_ARCH),armhf) - RPI_FIRMWARE_VER = $(shell dpkg-query -f='$${Version}' -W raspberrypi-kernel-headers) -LINUX_DIR_PATH = $(shell dpkg -L raspberrypi-kernel-headers | sed -n '/^\/lib\/modules\/.*-v7.*\/build$$/p') +LINUX_DIR_PATH = $(shell dpkg -L raspberrypi-kernel-headers | sed -n '/^\/lib\/modules\/.*-v7.*\/build$$/p'|head -1) KERNEL_VERSION = $(subst /lib/modules/,,$(subst /build,,$(LINUX_DIR_PATH))) - else - .raspbian-versions: misc/check-raspbian include ./.raspbian-versions LINUX_DIR_PATH = ${PWD}/tmp/linux-raspberrypi-kernel_${RPI_FIRMWARE_VER} +endif +endif +ifeq ($(shell dpkg-architecture -q DEB_HOST_ARCH),arm64) +AXON_KERNEL_VER = $(shell dpkg-query -f='$${Version}' -W axon-kernel-headers) +#AXON_KERNEL_VER = 4.14.111 +LINUX_DIR_PATH = $(shell dpkg -L axon-kernel-headers | sed -n '/^\/lib\/modules\/.*\/build$$/p') +#LINUX_DIR_PATH = '/lib/modules/4.14.111/build' +KERNEL_VERSION = $(subst /lib/modules/,,$(subst /build,,$(LINUX_DIR_PATH))) endif + %: dh $@ --with dkms @@ -38,35 +46,58 @@ endif override_dh_installmodules: DH_AUTOSCRIPTDIR=debian/autoscripts dh_installmodules - +ifeq ($(shell dpkg-architecture -q DEB_HOST_ARCH),armhf) override_dh_prep: @dh_prep --exclude=neuron-kernel.substvars @echo RPI-FIRMWARE-VER=${RPI_FIRMWARE_VER} >> debian/neuron-kernel.substvars - @sed "s/#MOD_VERSION#/$(MOD_VERSION)/g" debian/unipi-kernel-modules-dkms.install.in > debian/unipi-kernel-modules-dkms.install ( sed 's/)/.${RPI_FIRMWARE_VER})/;q' debian/changelog; \ printf "\n * Compiled for raspberrypi-kernel\n";\ printf "\n -- auto-generator %s\n\n" "`date -R`"; \ cat debian/changelog;\ ) >debian/neuron-kernel.changelog +endif +ifeq ($(shell dpkg-architecture -q DEB_HOST_ARCH),arm64) +override_dh_prep: + @dh_prep --exclude=unipi-kernel-modules.substvars + @echo AXON-KERNEL-VER=${AXON_KERNEL_VER} > debian/unipi-kernel-modules.substvars + ( sed 's/)/.${AXON_KERNEL_VER})/;q' debian/changelog; \ + printf "\n * Compiled for axon-kernel-image\n";\ + printf "\n -- auto-generator %s\n\n" "`date -R`"; \ + cat debian/changelog;\ + ) >debian/unipi-kernel-modules.changelog +endif +#ifeq ($(shell dpkg-architecture -q DEB_HOST_ARCH),arm64) override_dh_dkms: dh_dkms -V $(MOD_VERSION) + make dkms DESTDIR=$(PWD)/debian//unipi-kernel-modules-dkms/usr/src/unipi-$(MOD_VERSION) +#endif #override_dh_build: ifneq ($(shell dpkg-architecture -q DEB_BUILD_ARCH),armhf) +ifeq ($(shell dpkg-architecture -q DEB_HOST_ARCH),armhf) override_dh_auto_configure: misc/check-raspbian misc/current endif +endif override_dh_auto_build: dh_auto_build -- CCPREFIX=${DEB_TARGET_GNU_TYPE}- \ LINUX_DIR_PATH=$(LINUX_DIR_PATH) ARCH=${DEB_TARGET_ARCH_CPU} +ifeq ($(shell dpkg-architecture -q DEB_HOST_ARCH),arm64) +override_dh_auto_install: + dh_auto_install --destdir=debian/unipi-kernel-modules -- CCPREFIX=${DEB_TARGET_GNU_TYPE}- \ + LINUX_DIR_PATH=$(LINUX_DIR_PATH) ARCH=${DEB_TARGET_ARCH_CPU} +endif + +ifeq ($(shell dpkg-architecture -q DEB_HOST_ARCH),armhf) override_dh_auto_install: dh_auto_install --destdir=debian/neuron-kernel -- CCPREFIX=${DEB_TARGET_GNU_TYPE}- \ LINUX_DIR_PATH=$(LINUX_DIR_PATH) ARCH=${DEB_TARGET_ARCH_CPU} +endif #override_dh_gencontrol: diff --git a/debian/unipi-kernel-modules-dkms.dkms b/debian/unipi-kernel-modules-dkms.dkms index e02b3b4..b19708e 100644 --- a/debian/unipi-kernel-modules-dkms.dkms +++ b/debian/unipi-kernel-modules-dkms.dkms @@ -1,10 +1,18 @@ + PACKAGE_NAME="unipi" PACKAGE_VERSION="#MODULE_VERSION#" BUILT_MODULE_NAME[0]="unipi" -BUILT_MODULE_LOCATION[0]="" +BUILT_MODULE_LOCATION[0]="unipi" DEST_MODULE_LOCATION[0]="/extra" +BUILT_MODULE_NAME[1]="rtc-unipi" +BUILT_MODULE_LOCATION[1]="rtc-unipi" +DEST_MODULE_LOCATION[1]="/extra" + AUTOINSTALL=yes +REMAKE_INITRD=yes -MAKE[0]="make -C $kernel_source_dir M=${dkms_tree}/unipi/${module_version}/build modules" +MAKE[0]="make -C $kernel_source_dir M=${dkms_tree}/unipi/${module_version}/build/unipi modules;\ + make -C $kernel_source_dir M=${dkms_tree}/unipi/${module_version}/build/rtc-unipi modules" CLEAN="make -C ${dkms_tree}/unipi/${module_version} clean; rm -rf ${dkms_tree}/unipi/${module_version}/build/{Module.*,modules.order,OBJ.*}" + diff --git a/debian/unipi-kernel-modules-dkms.install.in b/debian/unipi-kernel-modules-dkms.install.in deleted file mode 100644 index fb99166..0000000 --- a/debian/unipi-kernel-modules-dkms.install.in +++ /dev/null @@ -1,2 +0,0 @@ -modules/unipi/src usr/src/unipi-#MOD_VERSION# -modules/unipi/Makefile usr/src/unipi-#MOD_VERSION# diff --git a/modules/.dw485-unipi/8250_dwunipi.c b/modules/.dw485-unipi/8250_dwunipi.c new file mode 100644 index 0000000..9c42e27 --- /dev/null +++ b/modules/.dw485-unipi/8250_dwunipi.c @@ -0,0 +1,1028 @@ +/* + * Synopsys DesignWare 8250 driver for UniPi 485 ports + * + * Copyright 2018 Miroslav Ondra, Faster CZ + * Copyright 2011 Picochip, Jamie Iles. + * Copyright 2013 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * The Synopsys DesignWare 8250 has an extra feature whereby it detects if the + * LCR is written whilst busy. If it is, then a busy detect interrupt is + * raised, the LCR needs to be rewritten and the uart status register read. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "8250.h" + +/* Offsets for the DesignWare specific registers */ +#define DW_UART_USR 0x1f /* UART Status Register */ +#define DW_UART_CPR 0xf4 /* Component Parameter Register */ +#define DW_UART_UCV 0xf8 /* UART Component Version */ + +/* Component Parameter Register bits */ +#define DW_UART_CPR_ABP_DATA_WIDTH (3 << 0) +#define DW_UART_CPR_AFCE_MODE (1 << 4) +#define DW_UART_CPR_THRE_MODE (1 << 5) +#define DW_UART_CPR_SIR_MODE (1 << 6) +#define DW_UART_CPR_SIR_LP_MODE (1 << 7) +#define DW_UART_CPR_ADDITIONAL_FEATURES (1 << 8) +#define DW_UART_CPR_FIFO_ACCESS (1 << 9) +#define DW_UART_CPR_FIFO_STAT (1 << 10) +#define DW_UART_CPR_SHADOW (1 << 11) +#define DW_UART_CPR_ENCODED_PARMS (1 << 12) +#define DW_UART_CPR_DMA_EXTRA (1 << 13) +#define DW_UART_CPR_FIFO_MODE (0xff << 16) +/* Helper for fifo size calculation */ +#define DW_UART_CPR_FIFO_SIZE(a) (((a >> 16) & 0xff) * 16) + +/* DesignWare specific register fields */ +#define DW_UART_MCR_SIRE BIT(6) + +struct dwunipi8250_data { + u8 usr_reg; + int line; + int msr_mask_on; + int msr_mask_off; + struct clk *clk; + struct clk *pclk; + struct reset_control *rst; + struct uart_8250_dma dma; + + unsigned int skip_autocfg:1; + unsigned int uart_16550_compatible:1; + int rts_gpio; + int enable_gpio; + u64 chardelay; + struct hrtimer hrt; + void (*serial_out)(struct uart_port *, int, int); +}; + +static inline int dwunipi8250_modify_msr(struct uart_port *p, int offset, int value) +{ + struct dwunipi8250_data *d = p->private_data; + + /* Override any modem control signals if needed */ + if (offset == UART_MSR) { + value |= d->msr_mask_on; + value &= ~d->msr_mask_off; + } + + return value; +} + +static void dwunipi8250_force_idle(struct uart_port *p) +{ + struct uart_8250_port *up = up_to_u8250p(p); + + serial8250_clear_and_reinit_fifos(up); + (void)p->serial_in(p, UART_RX); +} + +static void dwunipi8250_check_lcr(struct uart_port *p, int value) +{ + void __iomem *offset = p->membase + (UART_LCR << p->regshift); + int tries = 1000; + + /* Make sure LCR write wasn't ignored */ + while (tries--) { + unsigned int lcr = p->serial_in(p, UART_LCR); + + if ((value & ~UART_LCR_SPAR) == (lcr & ~UART_LCR_SPAR)) + return; + + dwunipi8250_force_idle(p); + +#ifdef CONFIG_64BIT + if (p->type == PORT_OCTEON) + __raw_writeq(value & 0xff, offset); + else +#endif + if (p->iotype == UPIO_MEM32) + writel(value, offset); + else if (p->iotype == UPIO_MEM32BE) + iowrite32be(value, offset); + else + writeb(value, offset); + } + /* + * FIXME: this deadlocks if port->lock is already held + * dev_err(p->dev, "Couldn't set LCR to %d\n", value); + */ +} + +static void dwunipi8250_gpio_serial_out(struct uart_port *p, int offset, int value) +{ + struct dwunipi8250_data *d = p->private_data; + + if (offset == UART_MCR) { + if (d->rts_gpio >= 0) + gpio_set_value(d->rts_gpio, (value & UART_MCR_RTS)? 0 : 1); + } + d->serial_out(p,offset,value); +} + +static void dwunipi8250_serial_out(struct uart_port *p, int offset, int value) +{ + struct dwunipi8250_data *d = p->private_data; + writeb(value, p->membase + (offset << p->regshift)); + + if (offset == UART_LCR && !d->uart_16550_compatible) + dwunipi8250_check_lcr(p, value); + /*if (offset == UART_LCR ) { + if (value & UART_LCR_SBC) { + serial_port_out(p, UART_MCR, xxx); + } + if (!d->uart_16550_compatible) + dwunipi8250_check_lcr(p, value); + }*/ +} + +static unsigned int dwunipi8250_serial_in(struct uart_port *p, int offset) +{ + unsigned int value = readb(p->membase + (offset << p->regshift)); + + return dwunipi8250_modify_msr(p, offset, value); +} + +#ifdef CONFIG_64BIT +static unsigned int dwunipi8250_serial_inq(struct uart_port *p, int offset) +{ + unsigned int value; + + value = (u8)__raw_readq(p->membase + (offset << p->regshift)); + + return dwunipi8250_modify_msr(p, offset, value); +} + +static void dwunipi8250_serial_outq(struct uart_port *p, int offset, int value) +{ + struct dwunipi8250_data *d = p->private_data; + + value &= 0xff; + __raw_writeq(value, p->membase + (offset << p->regshift)); + /* Read back to ensure register write ordering. */ + __raw_readq(p->membase + (UART_LCR << p->regshift)); + + if (offset == UART_LCR && !d->uart_16550_compatible) + dwunipi8250_check_lcr(p, value); +} +#endif /* CONFIG_64BIT */ + +static void dwunipi8250_serial_out32(struct uart_port *p, int offset, int value) +{ + struct dwunipi8250_data *d = p->private_data; + + writel(value, p->membase + (offset << p->regshift)); + + if (offset == UART_LCR && !d->uart_16550_compatible) + dwunipi8250_check_lcr(p, value); +} + +static unsigned int dwunipi8250_serial_in32(struct uart_port *p, int offset) +{ + unsigned int value = readl(p->membase + (offset << p->regshift)); + + return dwunipi8250_modify_msr(p, offset, value); +} + +static void dwunipi8250_serial_out32be(struct uart_port *p, int offset, int value) +{ + struct dwunipi8250_data *d = p->private_data; + + iowrite32be(value, p->membase + (offset << p->regshift)); + + if (offset == UART_LCR && !d->uart_16550_compatible) + dwunipi8250_check_lcr(p, value); +} + +static unsigned int dwunipi8250_serial_in32be(struct uart_port *p, int offset) +{ + unsigned int value = ioread32be(p->membase + (offset << p->regshift)); + + return dwunipi8250_modify_msr(p, offset, value); +} + + +static bool handle_rx_dma(struct uart_8250_port *up, unsigned int iir) +{ + switch (iir & 0x3f) { + case UART_IIR_RX_TIMEOUT: + serial8250_rx_dma_flush(up); + /* fall-through */ + case UART_IIR_RLSI: + return true; + } + return up->dma->rx_dma(up); +} + +static inline void dwunipi8250_em485_rts_after_send(struct uart_8250_port *p) +{ + unsigned char mcr = serial8250_in_MCR(p); + + if (p->port.rs485.flags & SER_RS485_RTS_AFTER_SEND) + mcr |= UART_MCR_RTS; + else + mcr &= ~UART_MCR_RTS; + serial8250_out_MCR(p, mcr); +} + + +int dwunipi8250_handle_irq(struct uart_port *port, unsigned int iir) +{ + unsigned char status; + unsigned long flags; + struct uart_8250_port *up = up_to_u8250p(port); + struct dwunipi8250_data *d = port->private_data; + struct circ_buf *xmit = &port->state->xmit; + int fifolen, do_timer = 0; + u64 interval; + + if (iir & UART_IIR_NO_INT) { + if ((iir & UART_IIR_BUSY) == UART_IIR_BUSY) { + /* Clear the USR */ + (void)port->serial_in(port, d->usr_reg); + return 1; + } + return 0; + } + + spin_lock_irqsave(&port->lock, flags); + status = serial_port_in(port, UART_LSR); + if (!up->dma && ((iir & 0x3f) == UART_IIR_RX_TIMEOUT)) { + /* + * There are ways to get Designware-based UARTs into a state where + * they are asserting UART_IIR_RX_TIMEOUT but there is no actual + * data available. If we see such a case then we'll do a bogus + * read. If we don't do this then the "RX TIMEOUT" interrupt will + * fire forever. + * + * This problem has only been observed so far when not in DMA mode + * so we limit the workaround only to non-DMA mode. + */ + if (!(status & (UART_LSR_DR | UART_LSR_BI))) + (void) port->serial_in(port, UART_RX); + } + + if (status & (UART_LSR_DR | UART_LSR_BI)) { + if (!up->em485 || !(up->ier & UART_IER_THRI)) { /* Don't process rx chars during transmitting - in fifo can be garbage */ + if (!up->dma || handle_rx_dma(up, iir)) { + status = serial8250_rx_chars(up, status); + } + } + } + serial8250_modem_status(up); + + if (up->em485 && (iir & UART_IIR_THRI)) { + if (!(port->x_char) && (uart_circ_empty(xmit))) { + do_timer = 1; + } + if (uart_tx_stopped(port)) { + dwunipi8250_em485_rts_after_send(up); //??? + } + } + + if ((!up->dma || up->dma->tx_err) && (status & UART_LSR_THRE) /*&& (iir & UART_IIR_THRI)*/) + serial8250_tx_chars(up); + + if (do_timer) { + fifolen = serial_port_in(port, 0x20); + if (up->ier & UART_IER_THRI) { + //serial8250_clear_and_reinit_fifos(up); /* clear fifo - garbage*/ + up->ier &= ~UART_IER_THRI; + serial_out(up, UART_IER, up->ier); + } + if (!hrtimer_active(&d->hrt)) { + interval = (fifolen+1) * d->chardelay + 5000; /* delay rts = FIFO * tim_for_one_char + 5us*/ + hrtimer_start(&d->hrt, ns_to_ktime(interval), HRTIMER_MODE_REL); + } + + } + spin_unlock_irqrestore(&port->lock, flags); + + return 1; +} + + +static int default_dwunipi8250_handle_irq(struct uart_port *p) +{ + struct uart_8250_port *up = up_to_u8250p(p); + unsigned int iir; + int ret; + + serial8250_rpm_get(up); + iir = p->serial_in(p, UART_IIR); + ret = dwunipi8250_handle_irq(p, iir); + serial8250_rpm_put(up); + return ret; +} + + +#define BOTH_EMPTY (UART_LSR_TEMT | UART_LSR_THRE) + +static enum hrtimer_restart dwunipi8250_hrtimer_callback(struct hrtimer *hrtp) +{ + struct dwunipi8250_data *d; + struct uart_8250_port *up; + unsigned char lsr; + u64 fifolen; + ktime_t interval; + + //printk(KERN_INFO "UART: hrt fired\n"); + d = container_of(hrtp, struct dwunipi8250_data, hrt); + up = serial8250_get_port(d->line); + + if ((up->ier & UART_IER_THRI)) { + /* if THR interrupt is enabled, timer is not valid anymore, port continues transmitting */ + return HRTIMER_NORESTART; + } + + lsr = serial_in(up, UART_LSR); + /* + * To provide required timeing and allow FIFO transfer, + * __stop_tx_rs485() must be called only when both FIFO and + * shift register are empty. It is for device driver to enable + * interrupt on TEMT. + */ + if ((lsr & BOTH_EMPTY) == BOTH_EMPTY) { /* THR and FIFO are empty */ + dwunipi8250_em485_rts_after_send(up); /* mnaage rts pin */ + serial8250_clear_and_reinit_fifos(up); /* clear fifo - garbage*/ + up->ier |= UART_IER_RLSI | UART_IER_RDI;/* set interrupts mask to reading */ + serial_out(up, UART_IER, up->ier); + return HRTIMER_NORESTART; /* transmitting is finished */ + } + + /* read TX queue, calc delay and restart timer */ + fifolen = serial_in(up, 0x20); + interval = (fifolen+1) * d->chardelay + 5000; /* delay rts = FIFO * tim_for_one_char + 5us*/ + hrtimer_forward_now(hrtp, ns_to_ktime(interval)); + return HRTIMER_RESTART; +} + +int dwunipi8250_em485_init(struct uart_8250_port *p) +{ + int ret; + struct dwunipi8250_data *d = p->port.private_data; + ret = serial8250_em485_init(p); + /* create high res timer for 485 driving */ + hrtimer_init(&d->hrt, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + d->hrt.function = dwunipi8250_hrtimer_callback; + return ret; +} +EXPORT_SYMBOL_GPL(dwunipi8250_em485_init); + +/** + * dwunipi8250_em485_destroy() - put uart_8250_port into normal state + * @p: uart_8250_port port instance + * + * The function is used to stop rs485 software emulating on the + * &struct uart_8250_port* @p. The function is idempotent, so it is safe to + * call it multiple times. + * + * The function is supposed to be called from .rs485_config callback + * or from any other callback protected with p->port.lock spinlock. + * + * See also serial8250_em485_init() + */ +void dwunipi8250_em485_destroy(struct uart_8250_port *p) +{ + struct dwunipi8250_data *d = p->port.private_data; + serial8250_em485_destroy(p); + hrtimer_try_to_cancel(&d->hrt); +} +EXPORT_SYMBOL_GPL(dwunipi8250_em485_destroy); + + +static int dwunipi8250_rs485_config(struct uart_port *port, struct serial_rs485 *rs485) +{ + struct uart_8250_port *up = up_to_u8250p(port); + + /* Clamp the delays to [0, 100ms] */ + rs485->delay_rts_before_send = min(rs485->delay_rts_before_send, 100U); + rs485->delay_rts_after_send = min(rs485->delay_rts_after_send, 100U); + + port->rs485 = *rs485; + + /* + * Both dwunipi8250_em485_init and dwunipi8250_em485_destroy + * are idempotent + */ + if (rs485->flags & SER_RS485_ENABLED) { + int ret = dwunipi8250_em485_init(up); + + if (ret) { + rs485->flags &= ~SER_RS485_ENABLED; + port->rs485.flags &= ~SER_RS485_ENABLED; + } + return ret; + } + + dwunipi8250_em485_destroy(up); + return 0; + } + +void dwunipi8250_do_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ + struct uart_8250_port *up = up_to_u8250p(port); + unsigned char mcr = 0; + + /* mask RTS changing in case of rs485 mode */ + if (port->rs485.flags & SER_RS485_ENABLED) { + mcr = serial8250_in_MCR(up) & UART_MCR_RTS; + } else { + if (mctrl & TIOCM_RTS) + mcr |= UART_MCR_RTS; + } + if (mctrl & TIOCM_DTR) + mcr |= UART_MCR_DTR; + if (mctrl & TIOCM_OUT1) + mcr |= UART_MCR_OUT1; + if (mctrl & TIOCM_OUT2) + mcr |= UART_MCR_OUT2; + if (mctrl & TIOCM_LOOP) + mcr |= UART_MCR_LOOP; + + mcr = (mcr & up->mcr_mask) | up->mcr_force | up->mcr; + + serial8250_out_MCR(up, mcr); +} + + +static void +dwunipi8250_do_pm(struct uart_port *port, unsigned int state, unsigned int old) +{ + if (!state) + pm_runtime_get_sync(port->dev); + + serial8250_do_pm(port, state, old); + + if (state) + pm_runtime_put_sync_suspend(port->dev); +} + +static unsigned int dwunipi8250_get_bits(unsigned int cflag) +{ + unsigned int bits; + + /* byte size and parity */ + switch (cflag & CSIZE) { + case CS5: + bits = 7; + break; + case CS6: + bits = 8; + break; + case CS7: + bits = 9; + break; + default: + bits = 10; + break; /* CS8 */ + } + + if (cflag & CSTOPB) + bits++; + if (cflag & PARENB) + bits++; + return bits; +} + +static void dwunipi8250_set_termios(struct uart_port *p, struct ktermios *termios, + struct ktermios *old) +{ + unsigned int baud = tty_termios_baud_rate(termios); + struct dwunipi8250_data *d = p->private_data; + long rate; + int ret; + + if (IS_ERR(d->clk) || !old) + goto out; + + clk_disable_unprepare(d->clk); + rate = clk_round_rate(d->clk, baud * 16); + if (rate < 0) + ret = rate; + else if (rate == 0) + ret = -ENOENT; + else + ret = clk_set_rate(d->clk, rate); + clk_prepare_enable(d->clk); + + if (!ret) + p->uartclk = rate; + +out: + p->status &= ~UPSTAT_AUTOCTS; + if (termios->c_cflag & CRTSCTS) + p->status |= UPSTAT_AUTOCTS; + + serial8250_do_set_termios(p, termios, old); + if (termios->c_ospeed) + d->chardelay = (dwunipi8250_get_bits(termios->c_cflag) * (u64)1000000000) / termios->c_ospeed ; + else + d->chardelay = 0; +} + +static void dwunipi8250_set_ldisc(struct uart_port *p, struct ktermios *termios) +{ + struct uart_8250_port *up = up_to_u8250p(p); + unsigned int mcr = p->serial_in(p, UART_MCR); + + if (up->capabilities & UART_CAP_IRDA) { + if (termios->c_line == N_IRDA) + mcr |= DW_UART_MCR_SIRE; + else + mcr &= ~DW_UART_MCR_SIRE; + + p->serial_out(p, UART_MCR, mcr); + } + serial8250_do_set_ldisc(p, termios); +} + +/* + * dwunipi8250_fallback_dma_filter will prevent the UART from getting just any free + * channel on platforms that have DMA engines, but don't have any channels + * assigned to the UART. + * + * REVISIT: This is a work around for limitation in the DMA Engine API. Once the + * core problem is fixed, this function is no longer needed. + */ +static bool dwunipi8250_fallback_dma_filter(struct dma_chan *chan, void *param) +{ + return false; +} + +static bool dwunipi8250_idma_filter(struct dma_chan *chan, void *param) +{ + return param == chan->device->dev->parent; +} + +static int dwunipi8250_startup(struct uart_port *port) +{ + struct dwunipi8250_data *d = port->private_data; + if (d->enable_gpio >= 0) { + gpio_set_value(d->enable_gpio, 1); + } + return serial8250_do_startup(port); +} + +static void dwunipi8250_gpio_setup(struct uart_port *port, struct device_node *np, struct dwunipi8250_data *data) +{ + enum of_gpio_flags flags; + int ret, gpio_pin; + + if (!np) return; + + /* check for tx enable gpio */ + gpio_pin = of_get_named_gpio_flags(np, "rts-gpio", 0, &flags); + if (gpio_is_valid(gpio_pin)) { + ret = devm_gpio_request(port->dev, gpio_pin, "dw-apb-uart"); + flags = 0; // initial value of gpio output + if (ret >= 0) { + if (gpio_direction_output(gpio_pin, flags) >= 0) { + data->rts_gpio = gpio_pin; + data->serial_out = port->serial_out; + port->serial_out = dwunipi8250_gpio_serial_out; + } + } + } + //} else if (d->rts_gpio == -EPROBE_DEFER) { + //return -EPROBE_DEFER; + + /* check for enable gpio */ + gpio_pin = of_get_named_gpio_flags(np, "enable-gpio", 0, &flags); + if (gpio_is_valid(gpio_pin)) { + ret = devm_gpio_request(port->dev, gpio_pin, "dw-apb-uart"); + flags = 0; // initial value of gpio output + if (ret >= 0) { + if (gpio_direction_output(gpio_pin, flags) >= 0) + data->enable_gpio = gpio_pin; + } + } +} + +static void dwunipi8250_rs485_setup(struct uart_port *port, struct device_node *np, struct dwunipi8250_data *data) +{ + struct serial_rs485 *rs485conf = &port->rs485; + + rs485conf->flags = 0; + + if (of_property_read_bool(np, "rs485-rts-active-high")) + rs485conf->flags |= SER_RS485_RTS_ON_SEND; + else + rs485conf->flags |= SER_RS485_RTS_AFTER_SEND; + + /*if (of_property_read_u32_array(np, "rs485-rts-delay", + rs485_delay, 2) == 0) { + rs485conf->delay_rts_before_send = rs485_delay[0]; + rs485conf->delay_rts_after_send = rs485_delay[1]; + }*/ + + if (of_property_read_bool(np, "rs485-rx-during-tx")) + rs485conf->flags |= SER_RS485_RX_DURING_TX; + + if (of_property_read_bool(np, "linux,rs485-enabled-at-boot-time")) { + rs485conf->flags |= SER_RS485_ENABLED; + } +} + +static void dwunipi8250_quirks(struct uart_port *p, struct dwunipi8250_data *data) +{ + if (p->dev->of_node) { + struct device_node *np = p->dev->of_node; + int id; + + /* get index of serial line, if found in DT aliases */ + id = of_alias_get_id(np, "serial"); + if (id >= 0) + p->line = id; +#ifdef CONFIG_64BIT + if (of_device_is_compatible(np, "cavium,octeon-3860-uart")) { + p->serial_in = dwunipi8250_serial_inq; + p->serial_out = dwunipi8250_serial_outq; + p->flags = UPF_SKIP_TEST | UPF_SHARE_IRQ | UPF_FIXED_TYPE; + p->type = PORT_OCTEON; + data->usr_reg = 0x27; + data->skip_autocfg = true; + } +#endif + if (of_device_is_big_endian(p->dev->of_node)) { + p->iotype = UPIO_MEM32BE; + p->serial_in = dwunipi8250_serial_in32be; + p->serial_out = dwunipi8250_serial_out32be; + } + dwunipi8250_gpio_setup(p, np, data); + dwunipi8250_rs485_setup(p, np, data); + } else if (has_acpi_companion(p->dev)) { + const struct acpi_device_id *id; + + id = acpi_match_device(p->dev->driver->acpi_match_table, + p->dev); + if (id && !strcmp(id->id, "APMC0D08")) { + p->iotype = UPIO_MEM32; + p->regshift = 2; + p->serial_in = dwunipi8250_serial_in32; + data->uart_16550_compatible = true; + } + } + + /* Platforms with iDMA */ + if (platform_get_resource_byname(to_platform_device(p->dev), + IORESOURCE_MEM, "lpss_priv")) { + data->dma.rx_param = p->dev->parent; + data->dma.tx_param = p->dev->parent; + data->dma.fn = dwunipi8250_idma_filter; + } +} + +static void dwunipi8250_setup_port(struct uart_port *p) +{ + struct uart_8250_port *up = up_to_u8250p(p); + u32 reg; + + /* + * If the Component Version Register returns zero, we know that + * ADDITIONAL_FEATURES are not enabled. No need to go any further. + */ + if (p->iotype == UPIO_MEM32BE) + reg = ioread32be(p->membase + DW_UART_UCV); + else + reg = readl(p->membase + DW_UART_UCV); + if (!reg) + return; + + dev_dbg(p->dev, "UniPi 485 via Designware UART version %c.%c%c\n", + (reg >> 24) & 0xff, (reg >> 16) & 0xff, (reg >> 8) & 0xff); + + if (p->iotype == UPIO_MEM32BE) + reg = ioread32be(p->membase + DW_UART_CPR); + else + reg = readl(p->membase + DW_UART_CPR); + if (!reg) + return; + + /* Select the type based on fifo */ + if (reg & DW_UART_CPR_FIFO_MODE) { + p->type = PORT_16550A; + p->flags |= UPF_FIXED_TYPE; + p->fifosize = DW_UART_CPR_FIFO_SIZE(reg); + up->capabilities = UART_CAP_FIFO; + } + + if (reg & DW_UART_CPR_AFCE_MODE) + up->capabilities |= UART_CAP_AFE; + + if (reg & DW_UART_CPR_SIR_MODE) + up->capabilities |= UART_CAP_IRDA; +} + +static int dwunipi8250_probe(struct platform_device *pdev) +{ + struct uart_8250_port uart = {}; + struct resource *regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + int irq = platform_get_irq(pdev, 0); + struct uart_port *p = &uart.port; + struct device *dev = &pdev->dev; + struct dwunipi8250_data *data; + int err; + u32 val; + + if (!regs) { + dev_err(dev, "no registers defined\n"); + return -EINVAL; + } + + if (irq < 0) { + if (irq != -EPROBE_DEFER) + dev_err(dev, "cannot get irq\n"); + return irq; + } + + spin_lock_init(&p->lock); + p->mapbase = regs->start; + p->irq = irq; + p->handle_irq = default_dwunipi8250_handle_irq; + p->pm = dwunipi8250_do_pm; + p->type = PORT_8250; + p->flags = UPF_SHARE_IRQ | UPF_FIXED_PORT; + p->dev = dev; + p->iotype = UPIO_MEM; + p->serial_in = dwunipi8250_serial_in; + p->serial_out = dwunipi8250_serial_out; + p->set_ldisc = dwunipi8250_set_ldisc; + p->set_termios = dwunipi8250_set_termios; + p->set_mctrl = dwunipi8250_do_set_mctrl; + p->rs485_config = dwunipi8250_rs485_config; + + p->membase = devm_ioremap(dev, regs->start, resource_size(regs)); + if (!p->membase) + return -ENOMEM; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->dma.fn = dwunipi8250_fallback_dma_filter; + data->usr_reg = DW_UART_USR; + data->rts_gpio = -EINVAL; + data->enable_gpio = -EINVAL; + data->chardelay = 0; + p->private_data = data; + + data->uart_16550_compatible = device_property_read_bool(dev, + "snps,uart-16550-compatible"); + + err = device_property_read_u32(dev, "reg-shift", &val); + if (!err) + p->regshift = val; + + err = device_property_read_u32(dev, "reg-io-width", &val); + if (!err && val == 4) { + p->iotype = UPIO_MEM32; + p->serial_in = dwunipi8250_serial_in32; + p->serial_out = dwunipi8250_serial_out32; + } + + if (device_property_read_bool(dev, "dcd-override")) { + /* Always report DCD as active */ + data->msr_mask_on |= UART_MSR_DCD; + data->msr_mask_off |= UART_MSR_DDCD; + } + + if (device_property_read_bool(dev, "dsr-override")) { + /* Always report DSR as active */ + data->msr_mask_on |= UART_MSR_DSR; + data->msr_mask_off |= UART_MSR_DDSR; + } + + if (device_property_read_bool(dev, "cts-override")) { + /* Always report CTS as active */ + data->msr_mask_on |= UART_MSR_CTS; + data->msr_mask_off |= UART_MSR_DCTS; + } + + if (device_property_read_bool(dev, "ri-override")) { + /* Always report Ring indicator as inactive */ + data->msr_mask_off |= UART_MSR_RI; + data->msr_mask_off |= UART_MSR_TERI; + } + + /* Always ask for fixed clock rate from a property. */ + device_property_read_u32(dev, "clock-frequency", &p->uartclk); + + /* If there is separate baudclk, get the rate from it. */ + data->clk = devm_clk_get(dev, "baudclk"); + if (IS_ERR(data->clk) && PTR_ERR(data->clk) != -EPROBE_DEFER) + data->clk = devm_clk_get(dev, NULL); + if (IS_ERR(data->clk) && PTR_ERR(data->clk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + if (!IS_ERR_OR_NULL(data->clk)) { + err = clk_prepare_enable(data->clk); + if (err) + dev_warn(dev, "could not enable optional baudclk: %d\n", + err); + else + p->uartclk = clk_get_rate(data->clk); + } + + /* If no clock rate is defined, fail. */ + if (!p->uartclk) { + dev_err(dev, "clock rate not defined\n"); + err = -EINVAL; + goto err_clk; + } + + data->pclk = devm_clk_get(dev, "apb_pclk"); + if (IS_ERR(data->pclk) && PTR_ERR(data->pclk) == -EPROBE_DEFER) { + err = -EPROBE_DEFER; + goto err_clk; + } + if (!IS_ERR(data->pclk)) { + err = clk_prepare_enable(data->pclk); + if (err) { + dev_err(dev, "could not enable apb_pclk\n"); + goto err_clk; + } + } + + data->rst = devm_reset_control_get_optional_exclusive(dev, NULL); + if (IS_ERR(data->rst)) { + err = PTR_ERR(data->rst); + goto err_pclk; + } + reset_control_deassert(data->rst); + + dwunipi8250_quirks(p, data); + if (data->enable_gpio >= 0) { + p->startup = dwunipi8250_startup; + } + + /* If the Busy Functionality is not implemented, don't handle it */ + if (data->uart_16550_compatible) + p->handle_irq = NULL; + + if (!data->skip_autocfg) + dwunipi8250_setup_port(p); + + /* If we have a valid fifosize, try hooking up DMA */ + if (p->fifosize) { + data->dma.rxconf.src_maxburst = p->fifosize / 4; + data->dma.txconf.dst_maxburst = p->fifosize / 4; + uart.dma = &data->dma; + } + + data->line = serial8250_register_8250_port(&uart); + if (data->line < 0) { + err = data->line; + goto err_reset; + } + + platform_set_drvdata(pdev, data); + + if (p->rs485.flags & SER_RS485_ENABLED) { + dwunipi8250_em485_init(serial8250_get_port(data->line)); + } + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + return 0; + +err_reset: + reset_control_assert(data->rst); + +err_pclk: + if (!IS_ERR(data->pclk)) + clk_disable_unprepare(data->pclk); + +err_clk: + if (!IS_ERR(data->clk)) + clk_disable_unprepare(data->clk); + + return err; +} + +static int dwunipi8250_remove(struct platform_device *pdev) +{ + struct dwunipi8250_data *data = platform_get_drvdata(pdev); + + pm_runtime_get_sync(&pdev->dev); + + serial8250_unregister_port(data->line); + + reset_control_assert(data->rst); + + if (!IS_ERR(data->pclk)) + clk_disable_unprepare(data->pclk); + + if (!IS_ERR(data->clk)) + clk_disable_unprepare(data->clk); + + pm_runtime_disable(&pdev->dev); + pm_runtime_put_noidle(&pdev->dev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int dwunipi8250_suspend(struct device *dev) +{ + struct dwunipi8250_data *data = dev_get_drvdata(dev); + + serial8250_suspend_port(data->line); + + return 0; +} + +static int dwunipi8250_resume(struct device *dev) +{ + struct dwunipi8250_data *data = dev_get_drvdata(dev); + + serial8250_resume_port(data->line); + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +#ifdef CONFIG_PM +static int dwunipi8250_runtime_suspend(struct device *dev) +{ + struct dwunipi8250_data *data = dev_get_drvdata(dev); + + if (!IS_ERR(data->clk)) + clk_disable_unprepare(data->clk); + + if (!IS_ERR(data->pclk)) + clk_disable_unprepare(data->pclk); + + return 0; +} + +static int dwunipi8250_runtime_resume(struct device *dev) +{ + struct dwunipi8250_data *data = dev_get_drvdata(dev); + + if (!IS_ERR(data->pclk)) + clk_prepare_enable(data->pclk); + + if (!IS_ERR(data->clk)) + clk_prepare_enable(data->clk); + + return 0; +} +#endif + +static const struct dev_pm_ops dwunipi8250_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(dwunipi8250_suspend, dwunipi8250_resume) + SET_RUNTIME_PM_OPS(dwunipi8250_runtime_suspend, dwunipi8250_runtime_resume, NULL) +}; + +static const struct of_device_id dwunipi8250_of_match[] = { + { .compatible = "unipi,dw-apb-485" }, + { /* Sentinel */ } +}; +MODULE_DEVICE_TABLE(of, dwunipi8250_of_match); + +static const struct acpi_device_id dwunipi8250_acpi_match[] = { + { }, +}; +MODULE_DEVICE_TABLE(acpi, dwunipi8250_acpi_match); + +static struct platform_driver dwunipi8250_platform_driver = { + .driver = { + .name = "dw-apb-485", + .pm = &dwunipi8250_pm_ops, + .of_match_table = dwunipi8250_of_match, + .acpi_match_table = ACPI_PTR(dwunipi8250_acpi_match), + }, + .probe = dwunipi8250_probe, + .remove = dwunipi8250_remove, +}; + +module_platform_driver(dwunipi8250_platform_driver); + +MODULE_AUTHOR("Miroslav Ondra"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("UniPi 485 port via Synopsys DesignWare 8250 serial uart driver"); +MODULE_ALIAS("platform:dw-apb-485"); diff --git a/modules/.dw485-unipi/Kconfig b/modules/.dw485-unipi/Kconfig new file mode 100644 index 0000000..d117cd1 --- /dev/null +++ b/modules/.dw485-unipi/Kconfig @@ -0,0 +1,21 @@ +# +# The 8250/16550 serial drivers. You shouldn't be in this list unless +# you somehow have an implicit or explicit dependency on SERIAL_8250. +# + +config SERIAL_8250_DW_UNIPI + tristate "Support for Synopsys DesignWare 8250 in 485 mode on UniPi " + depends on SERIAL_8250 + help + Selecting this option will enable handling of 485 ports on UniPi + controllers . + + +#config RXTX_LED_TRIG +# bool "RXTX Serial LED Triggers" +# depends on LEDS_CLASS && LEDS_TRIGGERS +# help +# This option adds LED triggers for RX/TX serial activity. +# +# Say Y here if you are working on a system with led-class supported +# LEDs and you want to use them as activity indicators for serial port. diff --git a/modules/.dw485-unipi/Makefile b/modules/.dw485-unipi/Makefile new file mode 100644 index 0000000..11b4bf6 --- /dev/null +++ b/modules/.dw485-unipi/Makefile @@ -0,0 +1,35 @@ +# Note: Compiling kernel modules requires creating symlinks, which is not possible on certain +# filesystems (notably VirtualBox vmfs); therefore we allow using /run/ through the 'symlink' target, +# if necessary. + +LINUX_DIR_PATH = /lib/modules/$(shell uname -r)/build +ifdef CCPREFIX + CCPAR = CROSS_COMPILE=${CCPREFIX} +endif +ifdef ARCH + CCPAR += ARCH=${ARCH} +endif + +MODULE_MAKE_FILE = Makefile + +C_SRC_FILES = 8250_dwunipi.c + +OBJ_FILES = 8250_dwunipi.o + +KERNEL_MODULE_NAME = dw-apb-485 +obj-m += ${KERNEL_MODULE_NAME}.o +dw-apb-485-objs := ${OBJ_FILES} + +.PHONY: default +default: all ; + +all: + make $(CCPAR) -C $(LINUX_DIR_PATH) M=${PWD} modules + +modules_install: + make $(CCPAR) -C $(LINUX_DIR_PATH) M=${PWD} modules_install + +clean: + rm -f ${rtc-unipi-objs} .*.o.cmd + rm -f ${KERNEL_MODULE_NAME}.ko ${KERNEL_MODULE_NAME}.mod.c .*.o.cmd .*.ko.cmd *.o modules.order Module.symvers + rm -rf .tmp_versions diff --git a/modules/rtc-unipi/Makefile b/modules/rtc-unipi/Makefile new file mode 100644 index 0000000..2fd9e22 --- /dev/null +++ b/modules/rtc-unipi/Makefile @@ -0,0 +1,39 @@ +# Note: Compiling kernel modules requires creating symlinks, which is not possible on certain +# filesystems (notably VirtualBox vmfs); therefore we allow using /run/ through the 'symlink' target, +# if necessary. + +LINUX_DIR_PATH = /lib/modules/$(shell uname -r)/build +ifdef CCPREFIX + CCPAR = CROSS_COMPILE=${CCPREFIX} +endif +ifdef ARCH + CCPAR += ARCH=${ARCH} +endif + +MODULE_MAKE_FILE = Makefile +INSTALL = install + +C_SRC_FILES = rtc-unipi.c + +OBJ_FILES = $(C_SRC_FILES:.c=.o) + +KERNEL_MODULE_NAME = rtc-unipi +obj-m += ${KERNEL_MODULE_NAME}.o +#rtc-unipi-objs := ${OBJ_FILES} + +.PHONY: default +default: all ; + +all: + make $(CCPAR) -C $(LINUX_DIR_PATH) M=${PWD} modules + +modules_install: + make $(CCPAR) -C $(LINUX_DIR_PATH) M=${PWD} modules_install + +dkms: + $(INSTALL) -D $(C_SRC_FILES) $(MODULE_MAKE_FILE) -t $(INSTALL_MOD_PATH) + +clean: + rm -f ${rtc-unipi-objs} .*.o.cmd + rm -f ${KERNEL_MODULE_NAME}.ko ${KERNEL_MODULE_NAME}.mod.c .*.o.cmd .*.ko.cmd *.o modules.order Module.symvers + rm -rf .tmp_versions diff --git a/modules/rtc-unipi/rtc-unipi.c b/modules/rtc-unipi/rtc-unipi.c new file mode 100644 index 0000000..51b6f19 --- /dev/null +++ b/modules/rtc-unipi/rtc-unipi.c @@ -0,0 +1,704 @@ +/* + * rtc-unipi.c - RTC driver for mcp794xx based on rtc-ds1307.c + * with calibration support + * + * Copyright (C) 2005 James Chapman (ds1337 core) + * Copyright (C) 2006 David Brownell + * Copyright (C) 2009 Matthias Fuchs (rx8025 support) + * Copyright (C) 2012 Bertrand Achard (nvram access fixes) + * Copyright (C) 2019 Miroslav Ondra + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* RTC registers don't differ much, except for the century flag */ +#define DS1307_REG_SECS 0x00 /* 00-59 */ +# define DS1307_BIT_CH 0x80 +# define DS1340_BIT_nEOSC 0x80 +# define MCP794XX_BIT_ST 0x80 +#define DS1307_REG_MIN 0x01 /* 00-59 */ +# define M41T0_BIT_OF 0x80 +#define DS1307_REG_HOUR 0x02 /* 00-23, or 1-12{am,pm} */ +# define DS1307_BIT_12HR 0x40 /* in REG_HOUR */ +# define DS1307_BIT_PM 0x20 /* in REG_HOUR */ +# define DS1340_BIT_CENTURY_EN 0x80 /* in REG_HOUR */ +# define DS1340_BIT_CENTURY 0x40 /* in REG_HOUR */ +#define DS1307_REG_WDAY 0x03 /* 01-07 */ +# define MCP794XX_BIT_VBATEN 0x08 +#define DS1307_REG_MDAY 0x04 /* 01-31 */ +#define DS1307_REG_MONTH 0x05 /* 01-12 */ +# define DS1337_BIT_CENTURY 0x80 /* in REG_MONTH */ +#define DS1307_REG_YEAR 0x06 /* 00-99 */ + +/* + * Other registers (control, status, alarms, trickle charge, NVRAM, etc) + * start at 7, and they differ a LOT. Only control and status matter for + * basic RTC date and time functionality; be careful using them. + */ +#define DS1307_REG_CONTROL 0x07 /* or ds1338 */ +# define DS1307_BIT_OUT 0x80 +# define DS1338_BIT_OSF 0x20 +# define DS1307_BIT_SQWE 0x10 +# define DS1307_BIT_RS1 0x02 +# define DS1307_BIT_RS0 0x01 + + +#define MCP794XX_NVRAM_OFFSET 0x20 +#define MCP794XX_NVRAM_SIZE 0x40 + + +struct rtc_unipi { + struct nvmem_config nvmem_cfg; + /*enum ds_type type;*/ + unsigned long flags; +#define HAS_NVRAM 0 /* bit 0 == sysfs file active */ +#define HAS_ALARM 1 /* bit 1 == irq claimed */ + struct device *dev; + struct regmap *regmap; + const char *name; + struct rtc_device *rtc; +/* +#ifdef CONFIG_COMMON_CLK + struct clk_hw clks[2]; +#endif +*/ +}; + +/*struct chip_desc { */ + /*unsigned alarm:1;*/ + /*u16 nvram_offset; + u16 nvram_size;*/ + /*u8 offset; *//* register's offset */ + /*u8 century_reg; + u8 century_enable_bit; + u8 century_bit;*/ + /*u8 bbsqi_bit; */ + /*irq_handler_t irq_handler;*/ + /*const struct rtc_class_ops *rtc_ops;*/ +/*};*/ + +static int mcp794xx_get_time(struct device *dev, struct rtc_time *t); +static int mcp794xx_set_time(struct device *dev, struct rtc_time *t); +static irqreturn_t mcp794xx_irq(int irq, void *dev_id); +static int mcp794xx_read_alarm(struct device *dev, struct rtc_wkalrm *t); +static int mcp794xx_set_alarm(struct device *dev, struct rtc_wkalrm *t); +static int mcp794xx_alarm_irq_enable(struct device *dev, unsigned int enabled); +static int mcp794xx_read_offset(struct device *dev, long *offset); +static int mcp794xx_set_offset(struct device *dev, long offset); + + +static const struct rtc_class_ops mcp794xx_rtc_ops = { + .read_time = mcp794xx_get_time, + .set_time = mcp794xx_set_time, + .read_alarm = mcp794xx_read_alarm, + .set_alarm = mcp794xx_set_alarm, + .alarm_irq_enable = mcp794xx_alarm_irq_enable, + .read_offset = mcp794xx_read_offset, + .set_offset = mcp794xx_set_offset, +}; + +/*static const struct chip_desc mcpchip = {*/ + /*.alarm = 1,*/ + /* this is battery backed SRAM */ + /* .nvram_offset = 0x20, + .nvram_size = 0x40, */ + /*.irq_handler = mcp794xx_irq,*/ + /*.rtc_ops = &mcp794xx_rtc_ops,*/ +/*};*/ + +static const struct i2c_device_id rtc_unipi_id[] = { + { "unipi-mcp7941x", 0 /*mcp794xx */}, + { "rtc-unipi", 0 /*mcp794xx */}, + { } +}; +MODULE_DEVICE_TABLE(i2c, rtc_unipi_id); + +#ifdef CONFIG_OF +static const struct of_device_id rtc_unipi_of_match[] = { + { + .compatible = "unipi,unipi-mcp7941x", + .data = NULL /*(void *)mcp794xx*/ + }, + { + .compatible = "unipi,rtc-unipi", + .data = NULL /*(void *)mcp794xx*/ + }, + { } +}; +MODULE_DEVICE_TABLE(of, rtc_unipi_of_match); +#endif + +#ifdef CONFIG_ACPI +static const struct acpi_device_id rtc_unipi_acpi_ids[] = { + { .id = "MCP7940X", .driver_data = 0 /*mcp794xx */}, + { .id = "MCP7941X", .driver_data = 0 /*mcp794xx */}, + { } +}; +MODULE_DEVICE_TABLE(acpi, rtc_unipi_acpi_ids); +#endif + + +/*----------------------------------------------------------------------*/ + +static int mcp794xx_get_time(struct device *dev, struct rtc_time *t) +{ + struct rtc_unipi *rtc_unipi = dev_get_drvdata(dev); + int tmp, ret; + /*const struct chip_desc *chip = &chips[ds1307->type];*/ + u8 regs[7]; + + /* read the RTC date and time registers all at once */ + ret = regmap_bulk_read(rtc_unipi->regmap, 0/*chip->offset*/, regs, + sizeof(regs)); + if (ret) { + dev_err(dev, "%s error %d\n", "read", ret); + return ret; + } + + dev_dbg(dev, "%s: %7ph\n", "read", regs); + + t->tm_sec = bcd2bin(regs[DS1307_REG_SECS] & 0x7f); + t->tm_min = bcd2bin(regs[DS1307_REG_MIN] & 0x7f); + tmp = regs[DS1307_REG_HOUR] & 0x3f; + t->tm_hour = bcd2bin(tmp); + t->tm_wday = bcd2bin(regs[DS1307_REG_WDAY] & 0x07) - 1; + t->tm_mday = bcd2bin(regs[DS1307_REG_MDAY] & 0x3f); + tmp = regs[DS1307_REG_MONTH] & 0x1f; + t->tm_mon = bcd2bin(tmp) - 1; + t->tm_year = bcd2bin(regs[DS1307_REG_YEAR]) + 100; + dev_dbg(dev, "%s secs=%d, mins=%d, " + "hours=%d, mday=%d, mon=%d, year=%d, wday=%d\n", + "read", t->tm_sec, t->tm_min, + t->tm_hour, t->tm_mday, + t->tm_mon, t->tm_year, t->tm_wday); + + /* initial clock setting can be undefined */ + return rtc_valid_tm(t); +} + +static int mcp794xx_set_time(struct device *dev, struct rtc_time *t) +{ + struct rtc_unipi *rtc_unipi = dev_get_drvdata(dev); + /*const struct chip_desc *chip = &chips[ds1307->type];*/ + int result; + int tmp; + u8 regs[7]; + + dev_dbg(dev, "%s secs=%d, mins=%d, " + "hours=%d, mday=%d, mon=%d, year=%d, wday=%d\n", + "write", t->tm_sec, t->tm_min, + t->tm_hour, t->tm_mday, + t->tm_mon, t->tm_year, t->tm_wday); + + if (t->tm_year < 100) + return -EINVAL; +//#ifdef CONFIG_RTC_DRV_DS1307_CENTURY +// if (t->tm_year > (/*chip->century_bit ? 299 : */199)) +// return -EINVAL; +//#else + if (t->tm_year > 199) + return -EINVAL; +//#endif + + regs[DS1307_REG_SECS] = bin2bcd(t->tm_sec); + regs[DS1307_REG_MIN] = bin2bcd(t->tm_min); + regs[DS1307_REG_HOUR] = bin2bcd(t->tm_hour); + regs[DS1307_REG_WDAY] = bin2bcd(t->tm_wday + 1); + regs[DS1307_REG_MDAY] = bin2bcd(t->tm_mday); + regs[DS1307_REG_MONTH] = bin2bcd(t->tm_mon + 1); + + /* assume 20YY not 19YY */ + tmp = t->tm_year - 100; + regs[DS1307_REG_YEAR] = bin2bcd(tmp); + + + //if (ds1307->type == mcp794xx) { + /* + * these bits were cleared when preparing the date/time + * values and need to be set again before writing the + * regsfer out to the device. + */ + regs[DS1307_REG_SECS] |= MCP794XX_BIT_ST; + regs[DS1307_REG_WDAY] |= MCP794XX_BIT_VBATEN; + //} + + dev_dbg(dev, "%s: %7ph\n", "write", regs); + + result = regmap_bulk_write(rtc_unipi->regmap, 0 /*chip->offset*/, regs, + sizeof(regs)); + if (result) { + dev_err(dev, "%s error %d\n", "write", result); + return result; + } + return 0; +} + + + + +/*----------------------------------------------------------------------*/ + +/* + * Alarm support for mcp794xx devices. + */ + +#define MCP794XX_REG_WEEKDAY 0x3 +#define MCP794XX_REG_WEEKDAY_WDAY_MASK 0x7 +#define MCP794XX_REG_CONTROL 0x07 +# define MCP794XX_BIT_ALM0_EN 0x10 +# define MCP794XX_BIT_ALM1_EN 0x20 +# define MCP794XX_BIT_EXTOSC 0x08 +#define MCP794XX_REG_ALARM0_BASE 0x0a +#define MCP794XX_REG_ALARM0_CTRL 0x0d +#define MCP794XX_REG_ALARM1_BASE 0x11 +#define MCP794XX_REG_ALARM1_CTRL 0x14 +# define MCP794XX_BIT_ALMX_IF BIT(3) +# define MCP794XX_BIT_ALMX_C0 BIT(4) +# define MCP794XX_BIT_ALMX_C1 BIT(5) +# define MCP794XX_BIT_ALMX_C2 BIT(6) +# define MCP794XX_BIT_ALMX_POL BIT(7) +# define MCP794XX_MSK_ALMX_MATCH (MCP794XX_BIT_ALMX_C0 | \ + MCP794XX_BIT_ALMX_C1 | \ + MCP794XX_BIT_ALMX_C2) + +#define MCP794XX_REG_CALIBRATION 0x08 + +static irqreturn_t mcp794xx_irq(int irq, void *dev_id) +{ + struct rtc_unipi *rtc_unipi = dev_id; + struct mutex *lock = &rtc_unipi->rtc->ops_lock; + int reg, ret; + + mutex_lock(lock); + + /* Check and clear alarm 0 interrupt flag. */ + ret = regmap_read(rtc_unipi->regmap, MCP794XX_REG_ALARM0_CTRL, ®); + if (ret) + goto out; + if (!(reg & MCP794XX_BIT_ALMX_IF)) + goto out; + reg &= ~MCP794XX_BIT_ALMX_IF; + ret = regmap_write(rtc_unipi->regmap, MCP794XX_REG_ALARM0_CTRL, reg); + if (ret) + goto out; + + /* Disable alarm 0. */ + ret = regmap_update_bits(rtc_unipi->regmap, MCP794XX_REG_CONTROL, + MCP794XX_BIT_ALM0_EN, 0); + if (ret) + goto out; + + rtc_update_irq(rtc_unipi->rtc, 1, RTC_AF | RTC_IRQF); + +out: + mutex_unlock(lock); + + return IRQ_HANDLED; +} + +static int mcp794xx_read_alarm(struct device *dev, struct rtc_wkalrm *t) +{ + struct rtc_unipi *rtc_unipi = dev_get_drvdata(dev); + u8 regs[10]; + int ret; + + if (!test_bit(HAS_ALARM, &rtc_unipi->flags)) + return -EINVAL; + + /* Read control and alarm 0 registers. */ + ret = regmap_bulk_read(rtc_unipi->regmap, MCP794XX_REG_CONTROL, regs, + sizeof(regs)); + if (ret) + return ret; + + t->enabled = !!(regs[0] & MCP794XX_BIT_ALM0_EN); + + /* Report alarm 0 time assuming 24-hour and day-of-month modes. */ + t->time.tm_sec = bcd2bin(regs[3] & 0x7f); + t->time.tm_min = bcd2bin(regs[4] & 0x7f); + t->time.tm_hour = bcd2bin(regs[5] & 0x3f); + t->time.tm_wday = bcd2bin(regs[6] & 0x7) - 1; + t->time.tm_mday = bcd2bin(regs[7] & 0x3f); + t->time.tm_mon = bcd2bin(regs[8] & 0x1f) - 1; + t->time.tm_year = -1; + t->time.tm_yday = -1; + t->time.tm_isdst = -1; + + dev_dbg(dev, "%s, sec=%d min=%d hour=%d wday=%d mday=%d mon=%d " + "enabled=%d polarity=%d irq=%d match=%lu\n", __func__, + t->time.tm_sec, t->time.tm_min, t->time.tm_hour, + t->time.tm_wday, t->time.tm_mday, t->time.tm_mon, t->enabled, + !!(regs[6] & MCP794XX_BIT_ALMX_POL), + !!(regs[6] & MCP794XX_BIT_ALMX_IF), + (regs[6] & MCP794XX_MSK_ALMX_MATCH) >> 4); + + return 0; +} + +static int mcp794xx_set_alarm(struct device *dev, struct rtc_wkalrm *t) +{ + struct rtc_unipi *rtc_unipi = dev_get_drvdata(dev); + unsigned char regs[10]; + int ret; + + if (!test_bit(HAS_ALARM, &rtc_unipi->flags)) + return -EINVAL; + + dev_dbg(dev, "%s, sec=%d min=%d hour=%d wday=%d mday=%d mon=%d " + "enabled=%d pending=%d\n", __func__, + t->time.tm_sec, t->time.tm_min, t->time.tm_hour, + t->time.tm_wday, t->time.tm_mday, t->time.tm_mon, + t->enabled, t->pending); + + /* Read control and alarm 0 registers. */ + ret = regmap_bulk_read(rtc_unipi->regmap, MCP794XX_REG_CONTROL, regs, + sizeof(regs)); + if (ret) + return ret; + + /* Set alarm 0, using 24-hour and day-of-month modes. */ + regs[3] = bin2bcd(t->time.tm_sec); + regs[4] = bin2bcd(t->time.tm_min); + regs[5] = bin2bcd(t->time.tm_hour); + regs[6] = bin2bcd(t->time.tm_wday + 1); + regs[7] = bin2bcd(t->time.tm_mday); + regs[8] = bin2bcd(t->time.tm_mon + 1); + + /* Clear the alarm 0 interrupt flag. */ + regs[6] &= ~MCP794XX_BIT_ALMX_IF; + /* Set alarm match: second, minute, hour, day, date, month. */ + regs[6] |= MCP794XX_MSK_ALMX_MATCH; + /* Disable interrupt. We will not enable until completely programmed */ + regs[0] &= ~MCP794XX_BIT_ALM0_EN; + + ret = regmap_bulk_write(rtc_unipi->regmap, MCP794XX_REG_CONTROL, regs, + sizeof(regs)); + if (ret) + return ret; + + if (!t->enabled) + return 0; + regs[0] |= MCP794XX_BIT_ALM0_EN; + return regmap_write(rtc_unipi->regmap, MCP794XX_REG_CONTROL, regs[0]); +} + +static int mcp794xx_alarm_irq_enable(struct device *dev, unsigned int enabled) +{ + struct rtc_unipi *rtc_unipi = dev_get_drvdata(dev); + + if (!test_bit(HAS_ALARM, &rtc_unipi->flags)) + return -EINVAL; + + return regmap_update_bits(rtc_unipi->regmap, MCP794XX_REG_CONTROL, + MCP794XX_BIT_ALM0_EN, + enabled ? MCP794XX_BIT_ALM0_EN : 0); +} + +static int mcp794xx_read_offset(struct device *dev, long *offset) +{ + struct rtc_unipi *rtc_unipi = dev_get_drvdata(dev); + int ret; + int reg; + + ret = regmap_read(rtc_unipi->regmap, MCP794XX_REG_CALIBRATION, ®); + if (ret < 0) + return ret; + + if (reg & 0x80) { + *offset = ((long) -(reg & 0x7f)); + } else { + *offset = ((long) ((s8)(reg & 0x7f))); + } + + return 0; +} + +/* + + */ +static int mcp794xx_set_offset(struct device *dev, long offset) +{ + struct rtc_unipi *rtc_unipi = dev_get_drvdata(dev); + s8 reg; + + if (offset > 127) + reg = 127; + else if (offset < -127) + reg = 127 | 0x80; + else if (offset < 0) + reg = ((s8)(-offset)) | 0x80; + else + reg = (s8)(offset); + + return regmap_write(rtc_unipi->regmap, MCP794XX_REG_CALIBRATION, reg); +} + +/*----------------------------------------------------------------------*/ + +static int rtc_unipi_nvram_read(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + struct rtc_unipi *rtc_unipi = priv; + /*const struct chip_desc *chip = &chips[ds1307->type];*/ + + return regmap_bulk_read(rtc_unipi->regmap, MCP794XX_NVRAM_OFFSET + offset, + val, bytes); +} + +static int rtc_unipi_nvram_write(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + struct rtc_unipi *rtc_unipi = priv; + /*const struct chip_desc *chip = &chips[ds1307->type];*/ + + return regmap_bulk_write(rtc_unipi->regmap, MCP794XX_NVRAM_OFFSET + offset, + val, bytes); +} + +/*----------------------------------------------------------------------*/ + + + +static const struct regmap_config regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static int rtc_unipi_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct rtc_unipi *rtc_unipi; + int err = -ENODEV; + int tmp, wday; + /*const struct chip_desc *chip;*/ + bool want_irq; + bool rtc_unipi_can_wakeup_device = false; + unsigned char regs[8]; + /*struct rtc_unipi_platform_data *pdata = dev_get_platdata(&client->dev);*/ + struct rtc_time tm; + unsigned long timestamp; + + rtc_unipi = devm_kzalloc(&client->dev, sizeof(struct rtc_unipi), GFP_KERNEL); + if (!rtc_unipi) + return -ENOMEM; + + dev_set_drvdata(&client->dev, rtc_unipi); + rtc_unipi->dev = &client->dev; + rtc_unipi->name = client->name; + + rtc_unipi->regmap = devm_regmap_init_i2c(client, ®map_config); + if (IS_ERR(rtc_unipi->regmap)) { + dev_err(rtc_unipi->dev, "regmap allocation failed\n"); + return PTR_ERR(rtc_unipi->regmap); + } + + i2c_set_clientdata(client, rtc_unipi); + + /* + if (client->dev.of_node) { + ds1307->type = (enum ds_type) + of_device_get_match_data(&client->dev); + //chip = &chips[ds1307->type]; + } else if (id) { + //chip = &chips[id->driver_data]; + ds1307->type = id->driver_data; + } else { + const struct acpi_device_id *acpi_id; + + acpi_id = acpi_match_device(ACPI_PTR(ds1307_acpi_ids), + ds1307->dev); + if (!acpi_id) + return -ENODEV; + //chip = &chips[acpi_id->driver_data]; + ds1307->type = acpi_id->driver_data; + } + */ + want_irq = client->irq > 0/* && chip->alarm*/; + +#ifdef CONFIG_OF +/* + * For devices with no IRQ directly connected to the SoC, the RTC chip + * can be forced as a wakeup source by stating that explicitly in + * the device's .dts file using the "wakeup-source" boolean property. + * If the "wakeup-source" property is set, don't request an IRQ. + * This will guarantee the 'wakealarm' sysfs entry is available on the device, + * if supported by the RTC. + */ + if (/*chip->alarm && */ of_property_read_bool(client->dev.of_node, "wakeup-source")) + rtc_unipi_can_wakeup_device = true; +#endif + + +read_rtc: + /* read RTC registers */ + err = regmap_bulk_read(rtc_unipi->regmap, 0 /*chip->offset*/, regs, + sizeof(regs)); + if (err) { + dev_dbg(rtc_unipi->dev, "read error %d\n", err); + goto exit; + } + + /* + * minimal sanity checking; some chips (like DS1340) don't + * specify the extra bits as must-be-zero, but there are + * still a few values that are clearly out-of-range. + */ + tmp = regs[DS1307_REG_SECS]; + + /* make sure that the backup battery is enabled */ + if (!(regs[DS1307_REG_WDAY] & MCP794XX_BIT_VBATEN)) { + regmap_write(rtc_unipi->regmap, DS1307_REG_WDAY, + regs[DS1307_REG_WDAY] | + MCP794XX_BIT_VBATEN); + } + + /* clock halted? turn it on, so clock can tick. */ + if (!(tmp & MCP794XX_BIT_ST)) { + regmap_write(rtc_unipi->regmap, DS1307_REG_SECS, + MCP794XX_BIT_ST); + dev_warn(rtc_unipi->dev, "SET TIME!\n"); + goto read_rtc; + } + if (regs[MCP794XX_REG_CONTROL] & MCP794XX_BIT_EXTOSC) { + regmap_write(rtc_unipi->regmap, MCP794XX_REG_CONTROL, + regs[MCP794XX_REG_CONTROL] & ~MCP794XX_BIT_EXTOSC); + dev_warn(rtc_unipi->dev, "BAD OSCILLATOR SETTING!\n"); + } + + + tmp = regs[DS1307_REG_HOUR]; + if ((tmp & DS1307_BIT_12HR)) { + /* + * Be sure we're in 24 hour mode. Multi-master systems + * take note... + */ + tmp = bcd2bin(tmp & 0x1f); + if (tmp == 12) + tmp = 0; + if (regs[DS1307_REG_HOUR] & DS1307_BIT_PM) + tmp += 12; + regmap_write(rtc_unipi->regmap, /*chip->offset + */DS1307_REG_HOUR, + bin2bcd(tmp)); + } + /* + * Some IPs have weekday reset value = 0x1 which might not correct + * hence compute the wday using the current date/month/year values + */ + mcp794xx_get_time(rtc_unipi->dev, &tm); + wday = tm.tm_wday; + timestamp = rtc_tm_to_time64(&tm); + rtc_time64_to_tm(timestamp, &tm); + + /* + * Check if reset wday is different from the computed wday + * If different then set the wday which we computed using + * timestamp + */ + if (wday != tm.tm_wday) + regmap_update_bits(rtc_unipi->regmap, MCP794XX_REG_WEEKDAY, + MCP794XX_REG_WEEKDAY_WDAY_MASK, + tm.tm_wday + 1); + + if (want_irq || rtc_unipi_can_wakeup_device) { + device_set_wakeup_capable(rtc_unipi->dev, true); + set_bit(HAS_ALARM, &rtc_unipi->flags); + } + + rtc_unipi->rtc = devm_rtc_allocate_device(rtc_unipi->dev); + if (IS_ERR(rtc_unipi->rtc)) + return PTR_ERR(rtc_unipi->rtc); + + if (rtc_unipi_can_wakeup_device && !want_irq) { + dev_info(rtc_unipi->dev, + "'wakeup-source' is set, request for an IRQ is disabled!\n"); + /* We cannot support UIE mode if we do not have an IRQ line */ + rtc_unipi->rtc->uie_unsupported = 1; + } + + if (want_irq) { + err = devm_request_threaded_irq(rtc_unipi->dev, client->irq, NULL, + mcp794xx_irq, + IRQF_SHARED | IRQF_ONESHOT, + rtc_unipi->name, rtc_unipi); + if (err) { + client->irq = 0; + device_set_wakeup_capable(rtc_unipi->dev, false); + clear_bit(HAS_ALARM, &rtc_unipi->flags); + dev_err(rtc_unipi->dev, "unable to request IRQ!\n"); + } else { + dev_dbg(rtc_unipi->dev, "got IRQ %d\n", client->irq); + } + } + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4,17,0) + /*if (chip->nvram_size) {*/ + rtc_unipi->nvmem_cfg.name = "rtc_unipi_nvram"; + rtc_unipi->nvmem_cfg.word_size = 1; + rtc_unipi->nvmem_cfg.stride = 1; + rtc_unipi->nvmem_cfg.size = MCP794XX_NVRAM_SIZE; + rtc_unipi->nvmem_cfg.reg_read = rtc_unipi_nvram_read; + rtc_unipi->nvmem_cfg.reg_write = rtc_unipi_nvram_write; + rtc_unipi->nvmem_cfg.priv = rtc_unipi; + + rtc_unipi->rtc->nvmem_config = &rtc_unipi->nvmem_cfg; + rtc_unipi->rtc->nvram_old_abi = true; + /*}*/ +#else + struct nvmem_config nvmem_cfg = { + .name = "rtc_unipi_nvram", + .word_size = 1, + .stride = 1, + .size = MCP794XX_NVRAM_SIZE, + .reg_read = rtc_unipi_nvram_read, + .reg_write = rtc_unipi_nvram_write, + .priv = rtc_unipi, + }; + + rtc_unipi->rtc->nvram_old_abi = true; + rtc_nvmem_register(rtc_unipi->rtc, &nvmem_cfg); + +#endif + rtc_unipi->rtc->ops = &mcp794xx_rtc_ops; /*chip->rtc_ops ?: &ds13xx_rtc_ops;*/ + err = rtc_register_device(rtc_unipi->rtc); + if (err) + return err; + + return 0; + +exit: + return err; +} + +static struct i2c_driver rtc_unipi_driver = { + .driver = { + .name = "rtc-unipi", + .of_match_table = of_match_ptr(rtc_unipi_of_match), + .acpi_match_table = ACPI_PTR(rtc_unipi_acpi_ids), + }, + .probe = rtc_unipi_probe, + .id_table = rtc_unipi_id, +}; + +module_i2c_driver(rtc_unipi_driver); + +MODULE_DESCRIPTION("RTC driver for DS1307 and similar chips"); +MODULE_LICENSE("GPL"); diff --git a/modules/unipi/Makefile b/modules/unipi/Makefile index 5c3ffd3..48abdb9 100644 --- a/modules/unipi/Makefile +++ b/modules/unipi/Makefile @@ -10,47 +10,43 @@ ifdef ARCH CCPAR += ARCH=${ARCH} endif -SYMLINK_DIR_PATH = /run/kernel/unipi_spi - -SRC_DIR_PATH = $(PWD)/src -BIN_DIR_PATH = $(PWD)/bin MODULE_MAKE_FILE = Makefile +INSTALL = install -C_SRC_FILES = unipi_spi.c -C_SRC_FILES += unipi_iio.c -C_SRC_FILES += unipi_gpio.c -C_SRC_FILES += unipi_uart.c -C_SRC_FILES += unipi_sysfs.c -C_SRC_FILES += unipi_misc.c -C_SRC_FILES += unipi_platform.c -C_SRC_FILES += unipi_tty.c +C_SRC_FILES = src/unipi_spi.c +C_SRC_FILES += src/unipi_iio.c +C_SRC_FILES += src/unipi_gpio.c +C_SRC_FILES += src/unipi_uart.c +C_SRC_FILES += src/unipi_sysfs.c +C_SRC_FILES += src/unipi_misc.c +C_SRC_FILES += src/unipi_platform.c +C_SRC_FILES += src/unipi_tty.c -H_SRC_FILES = unipi_spi.h -H_SRC_FILES += unipi_iio.h -H_SRC_FILES += unipi_gpio.h -H_SRC_FILES += unipi_uart.h -H_SRC_FILES += unipi_sysfs.h -H_SRC_FILES += unipi_misc.h -H_SRC_FILES += unipi_platform.h -H_SRC_FILES += unipi_common.h -H_SRC_FILES += unipi_tty.h +H_SRC_FILES = src/unipi_spi.h +H_SRC_FILES += src/unipi_iio.h +H_SRC_FILES += src/unipi_gpio.h +H_SRC_FILES += src/unipi_uart.h +H_SRC_FILES += src/unipi_sysfs.h +H_SRC_FILES += src/unipi_misc.h +H_SRC_FILES += src/unipi_platform.h +H_SRC_FILES += src/unipi_common.h +H_SRC_FILES += src/unipi_tty.h -OBJ_FILES = src/unipi_spi.o -OBJ_FILES += src/unipi_iio.o -OBJ_FILES += src/unipi_gpio.o -OBJ_FILES += src/unipi_uart.o -OBJ_FILES += src/unipi_sysfs.o -OBJ_FILES += src/unipi_misc.o -OBJ_FILES += src/unipi_platform.o -OBJ_FILES += src/unipi_tty.o +OBJ_FILES = $(C_SRC_FILES:.c=.o) +#OBJ_FILES = src/unipi_spi.o +#OBJ_FILES += src/unipi_iio.o +#OBJ_FILES += src/unipi_gpio.o +#OBJ_FILES += src/unipi_uart.o +#OBJ_FILES += src/unipi_sysfs.o +#OBJ_FILES += src/unipi_misc.o +#OBJ_FILES += src/unipi_platform.o +#OBJ_FILES += src/unipi_tty.o KERNEL_MODULE_NAME = unipi obj-m += ${KERNEL_MODULE_NAME}.o unipi-objs := ${OBJ_FILES} -TARGET_PLC_PATH = tomunipi: - .PHONY: default default: all ; @@ -60,25 +56,12 @@ all: modules_install: make $(CCPAR) -C $(LINUX_DIR_PATH) M=${PWD} modules_install +dkms: + $(INSTALL) -D $(MODULE_MAKE_FILE) -t $(INSTALL_MOD_PATH) + $(INSTALL) -D $(C_SRC_FILES) $(H_SRC_FILES) -t $(INSTALL_MOD_PATH)/src + clean: rm -f ${unipi-objs} src/.*.o.cmd rm -f ${KERNEL_MODULE_NAME}.ko ${KERNEL_MODULE_NAME}.mod.c .*.o.cmd .*.ko.cmd *.o modules.order Module.symvers rm -rf .tmp_versions -transfer: clean symlink - scp ${BIN_DIR_PATH}/${KERNEL_MODULE_NAME}.ko ${TARGET_PLC_PATH} - -symlink: clean - rm -r -f ${SYMLINK_DIR_PATH} - mkdir -p ${SYMLINK_DIR_PATH}/src - mkdir -p ${SYMLINK_DIR_PATH}/bin - cp ${PWD}/${MODULE_MAKE_FILE} ${SYMLINK_DIR_PATH} - for f in ${C_SRC_FILES}; do\ - ln -s ${SRC_DIR_PATH}/$$f ${SYMLINK_DIR_PATH}/src ;\ - done - for f in ${H_SRC_FILES}; do\ - ln -s ${SRC_DIR_PATH}/$$f ${SYMLINK_DIR_PATH}/src ;\ - done - cd ${SYMLINK_DIR_PATH}; make all - mv ${SYMLINK_DIR_PATH}/${KERNEL_MODULE_NAME}.ko ${BIN_DIR_PATH} - rm -r -f ${SYMLINK_DIR_PATH} diff --git a/modules/unipi/src/unipi_common.h b/modules/unipi/src/unipi_common.h index 932724c..4ce9a00 100644 --- a/modules/unipi/src/unipi_common.h +++ b/modules/unipi/src/unipi_common.h @@ -52,7 +52,7 @@ #if NEURONSPI_SCHED_REQUIRED > 0 #include #endif -#define NEURONSPI_MAJOR_VERSIONSTRING "Version 1.22:2019:03:27" +#define NEURONSPI_MAJOR_VERSIONSTRING "Version 1.23:2019:07:31" #define NEURONSPI_MAX_DEVS 3 #define NEURONSPI_MAX_UART 16 diff --git a/modules/unipi/src/unipi_spi.c b/modules/unipi/src/unipi_spi.c index f189dbf..9f9e7a6 100644 --- a/modules/unipi/src/unipi_spi.c +++ b/modules/unipi/src/unipi_spi.c @@ -121,7 +121,9 @@ static struct neuronspi_op_buffer UNIPISPI_IDLE_MESSAGE = { u16 unipi_spi_master_flag = 0; void (*unipi_spi_master_set_cs)(struct spi_device *spi, bool enable) = NULL; //cycles_t unipi_spi_cs_cycles; +#ifdef USE_UNIPI_CPUFREQ_PATCH static struct cpufreq_policy * current_policy = NULL; +#endif static void unipi_spi_set_cs(struct spi_device *spi, bool enable) { @@ -138,6 +140,7 @@ static void unipi_spi_set_cs(struct spi_device *spi, bool enable) udelay(NEURONSPI_LAST_TRANSFER_DELAY - udelta); } } +#ifdef USE_UNIPI_CPUFREQ_PATCH //current_policy = cpufreq_cpu_get_raw(task_cpu(current)); current_policy = cpufreq_cpu_get_raw(0); if (current_policy && !enable) { @@ -153,7 +156,7 @@ static void unipi_spi_set_cs(struct spi_device *spi, bool enable) current_policy->transition_task = current; spin_unlock(¤t_policy->transition_lock); } - +#endif if (gpio_is_valid(-spi->cs_gpio)) { gpio_set_value(-spi->cs_gpio, enable); if ((unipi_spi_master_set_cs != NULL) && @@ -165,13 +168,13 @@ static void unipi_spi_set_cs(struct spi_device *spi, bool enable) unipi_spi_master_set_cs(spi, enable); } if (d_data) d_data->last_cs_cycles = cs_cycles; - +#ifdef USE_UNIPI_CPUFREQ_PATCH if (current_policy && enable) { current_policy->transition_ongoing = false; current_policy->transition_task = NULL; wake_up(¤t_policy->transition_wait); } - +#endif } @@ -1133,7 +1136,7 @@ void neuronspi_irq_proc(struct kthread_work *ws) static enum hrtimer_restart neuronspi_poll_timer_func(struct hrtimer *timer) { struct neuronspi_driver_data* n_spi = ((container_of((timer), struct neuronspi_driver_data, poll_timer))); - struct spi_device *spi = neuronspi_s_dev [n_spi->neuron_index]; + //struct spi_device *spi = neuronspi_s_dev [n_spi->neuron_index]; unipi_spi_trace_1(KERN_INFO "UNIPISPI: nspi%d POLL IRQ\n", n_spi->neuron_index); kthread_queue_work(n_spi->primary_worker, &n_spi->irq_work);