*.rc
*/*/bin/*
!*/*/bin/.dummy
+/tmp/
modules/unipi/tmp*
modules/unipi/.tmp_versions
modules/unipi/unipi.mod.c
# if necessary.
MODULES_DIR_PATH = ${PWD}/modules/
-MODULES_LIST = unipi/
-TRANSFER_MODULE = unipi/
+MODULES_LIST = unipi/ rtc-unipi/
.PHONY: default
#default: symlink ;
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
+
+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/
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
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.
+
#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
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 <info@unipi.technology> %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 <info@unipi.technology> %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:
+
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.*}"
+
+++ /dev/null
-modules/unipi/src usr/src/unipi-#MOD_VERSION#
-modules/unipi/Makefile usr/src/unipi-#MOD_VERSION#
--- /dev/null
+/*
+ * 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 <linux/device.h>
+#include <linux/gpio.h>
+#include <linux/hrtimer.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/serial_8250.h>
+#include <linux/serial_reg.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/acpi.h>
+#include <linux/clk.h>
+#include <linux/reset.h>
+#include <linux/pm_runtime.h>
+
+#include <asm/byteorder.h>
+
+#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");
--- /dev/null
+#
+# 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.
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+/*
+ * 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 <linux/acpi.h>
+#include <linux/bcd.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/rtc/ds1307.h>
+#include <linux/rtc.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/clk-provider.h>
+#include <linux/regmap.h>
+#include <linux/version.h>
+
+
+/* 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");
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 ;
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}
#if NEURONSPI_SCHED_REQUIRED > 0
#include <uapi/linux/sched/types.h>
#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
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)
{
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) {
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) &&
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
}
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);