mirror of
https://github.com/Fishwaldo/build.git
synced 2025-06-25 15:48:43 +00:00
Update sunxi-next to 4.4.1
This commit is contained in:
parent
e08358cc6c
commit
e221e29753
6 changed files with 878 additions and 6344 deletions
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,941 +0,0 @@
|
|||
diff --git a/Documentation/devicetree/bindings/net/can/sun4i_can.txt b/Documentation/devicetree/bindings/net/can/sun4i_can.txt
|
||||
new file mode 100644
|
||||
index 0000000..cd0f50c
|
||||
--- /dev/null
|
||||
+++ b/Documentation/devicetree/bindings/net/can/sun4i_can.txt
|
||||
@@ -0,0 +1,38 @@
|
||||
+Allwinner A10/A20 CAN controller Device Tree Bindings
|
||||
+-----------------------------------------------------
|
||||
+
|
||||
+Required properties:
|
||||
+- compatible: "allwinner,sun4i-a10-can"
|
||||
+- reg: physical base address and size of the Allwinner A10/A20 CAN register map.
|
||||
+- interrupts: interrupt specifier for the sole interrupt.
|
||||
+- clock: phandle and clock specifier.
|
||||
+
|
||||
+
|
||||
+Example
|
||||
+-------
|
||||
+
|
||||
+SoC common .dtsi file:
|
||||
+
|
||||
+ can0_pins_a: can0@0 {
|
||||
+ allwinner,pins = "PH20","PH21";
|
||||
+ allwinner,function = "can";
|
||||
+ allwinner,drive = <0>;
|
||||
+ allwinner,pull = <0>;
|
||||
+ };
|
||||
+...
|
||||
+ can0: can@01c2bc00 {
|
||||
+ compatible = "allwinner,sun4i-a10-can";
|
||||
+ reg = <0x01c2bc00 0x400>;
|
||||
+ interrupts = <0 26 4>;
|
||||
+ clocks = <&apb1_gates 4>;
|
||||
+ status = "disabled";
|
||||
+ };
|
||||
+
|
||||
+Board specific .dts file:
|
||||
+
|
||||
+ can0: can@01c2bc00 {
|
||||
+ pinctrl-names = "default";
|
||||
+ pinctrl-0 = <&can0_pins_a>;
|
||||
+ status = "okay";
|
||||
+ };
|
||||
+
|
||||
|
||||
diff --git a/drivers/net/can/Kconfig b/drivers/net/can/Kconfig
|
||||
index e8c96b8..6d04183 100644
|
||||
--- a/drivers/net/can/Kconfig
|
||||
+++ b/drivers/net/can/Kconfig
|
||||
@@ -129,6 +129,16 @@ config CAN_RCAR
|
||||
To compile this driver as a module, choose M here: the module will
|
||||
be called rcar_can.
|
||||
|
||||
+config CAN_SUN4I
|
||||
+ tristate "Allwinner A10 CAN controller"
|
||||
+ depends on MACH_SUN4I || MACH_SUN7I || COMPILE_TEST
|
||||
+ ---help---
|
||||
+ Say Y here if you want to use CAN controller found on Allwinner
|
||||
+ A10/A20 SoCs.
|
||||
+
|
||||
+ To compile this driver as a module, choose M here: the module will
|
||||
+ be called sun4i_can.
|
||||
+
|
||||
config CAN_XILINXCAN
|
||||
tristate "Xilinx CAN"
|
||||
depends on ARCH_ZYNQ || ARM64 || MICROBLAZE || COMPILE_TEST
|
||||
diff --git a/drivers/net/can/Makefile b/drivers/net/can/Makefile
|
||||
index c533c62..1f21cef 100644
|
||||
--- a/drivers/net/can/Makefile
|
||||
+++ b/drivers/net/can/Makefile
|
||||
@@ -27,6 +27,7 @@ obj-$(CONFIG_CAN_FLEXCAN) += flexcan.o
|
||||
obj-$(CONFIG_PCH_CAN) += pch_can.o
|
||||
obj-$(CONFIG_CAN_GRCAN) += grcan.o
|
||||
obj-$(CONFIG_CAN_RCAR) += rcar_can.o
|
||||
+obj-$(CONFIG_CAN_SUN4I) += sun4i_can.o
|
||||
obj-$(CONFIG_CAN_XILINXCAN) += xilinx_can.o
|
||||
|
||||
subdir-ccflags-y += -D__CHECK_ENDIAN__
|
||||
diff --git a/drivers/net/can/sun4i_can.c b/drivers/net/can/sun4i_can.c
|
||||
new file mode 100644
|
||||
index 0000000..10d8497
|
||||
--- /dev/null
|
||||
+++ b/drivers/net/can/sun4i_can.c
|
||||
@@ -0,0 +1,857 @@
|
||||
+/*
|
||||
+ * sun4i_can.c - CAN bus controller driver for Allwinner SUN4I&SUN7I based SoCs
|
||||
+ *
|
||||
+ * Copyright (C) 2013 Peter Chen
|
||||
+ * Copyright (C) 2015 Gerhard Bertelsmann
|
||||
+ * All rights reserved.
|
||||
+ *
|
||||
+ * Parts of this software are based on (derived from) the SJA1000 code by:
|
||||
+ * Copyright (C) 2014 Oliver Hartkopp <oliver.hartkopp@volkswagen.de>
|
||||
+ * Copyright (C) 2007 Wolfgang Grandegger <wg@grandegger.com>
|
||||
+ * Copyright (C) 2002-2007 Volkswagen Group Electronic Research
|
||||
+ * Copyright (C) 2003 Matthias Brukner, Trajet Gmbh, Rebenring 33,
|
||||
+ * 38106 Braunschweig, GERMANY
|
||||
+ *
|
||||
+ * Redistribution and use in source and binary forms, with or without
|
||||
+ * modification, are permitted provided that the following conditions
|
||||
+ * are met:
|
||||
+ * 1. Redistributions of source code must retain the above copyright
|
||||
+ * notice, this list of conditions and the following disclaimer.
|
||||
+ * 2. Redistributions in binary form must reproduce the above copyright
|
||||
+ * notice, this list of conditions and the following disclaimer in the
|
||||
+ * documentation and/or other materials provided with the distribution.
|
||||
+ * 3. Neither the name of Volkswagen nor the names of its contributors
|
||||
+ * may be used to endorse or promote products derived from this software
|
||||
+ * without specific prior written permission.
|
||||
+ *
|
||||
+ * Alternatively, provided that this notice is retained in full, this
|
||||
+ * software may be distributed under the terms of the GNU General
|
||||
+ * Public License ("GPL") version 2, in which case the provisions of the
|
||||
+ * GPL apply INSTEAD OF those given above.
|
||||
+ *
|
||||
+ * The provided data structures and external interfaces from this code
|
||||
+ * are not restricted to be used by modules with a GPL compatible license.
|
||||
+ *
|
||||
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
+ * DAMAGE.
|
||||
+ *
|
||||
+ */
|
||||
+
|
||||
+#include <linux/netdevice.h>
|
||||
+#include <linux/can.h>
|
||||
+#include <linux/can/dev.h>
|
||||
+#include <linux/can/error.h>
|
||||
+#include <linux/can/led.h>
|
||||
+#include <linux/clk.h>
|
||||
+#include <linux/delay.h>
|
||||
+#include <linux/interrupt.h>
|
||||
+#include <linux/init.h>
|
||||
+#include <linux/io.h>
|
||||
+#include <linux/module.h>
|
||||
+#include <linux/of.h>
|
||||
+#include <linux/of_device.h>
|
||||
+#include <linux/platform_device.h>
|
||||
+
|
||||
+#define DRV_NAME "sun4i_can"
|
||||
+
|
||||
+/* Registers address (physical base address 0x01C2BC00) */
|
||||
+#define SUN4I_REG_MSEL_ADDR 0x0000 /* CAN Mode Select */
|
||||
+#define SUN4I_REG_CMD_ADDR 0x0004 /* CAN Command */
|
||||
+#define SUN4I_REG_STA_ADDR 0x0008 /* CAN Status */
|
||||
+#define SUN4I_REG_INT_ADDR 0x000c /* CAN Interrupt Flag */
|
||||
+#define SUN4I_REG_INTEN_ADDR 0x0010 /* CAN Interrupt Enable */
|
||||
+#define SUN4I_REG_BTIME_ADDR 0x0014 /* CAN Bus Timing 0 */
|
||||
+#define SUN4I_REG_TEWL_ADDR 0x0018 /* CAN Tx Error Warning Limit */
|
||||
+#define SUN4I_REG_ERRC_ADDR 0x001c /* CAN Error Counter */
|
||||
+#define SUN4I_REG_RMCNT_ADDR 0x0020 /* CAN Receive Message Counter */
|
||||
+#define SUN4I_REG_RBUFSA_ADDR 0x0024 /* CAN Receive Buffer Start Address */
|
||||
+#define SUN4I_REG_BUF0_ADDR 0x0040 /* CAN Tx/Rx Buffer 0 */
|
||||
+#define SUN4I_REG_BUF1_ADDR 0x0044 /* CAN Tx/Rx Buffer 1 */
|
||||
+#define SUN4I_REG_BUF2_ADDR 0x0048 /* CAN Tx/Rx Buffer 2 */
|
||||
+#define SUN4I_REG_BUF3_ADDR 0x004c /* CAN Tx/Rx Buffer 3 */
|
||||
+#define SUN4I_REG_BUF4_ADDR 0x0050 /* CAN Tx/Rx Buffer 4 */
|
||||
+#define SUN4I_REG_BUF5_ADDR 0x0054 /* CAN Tx/Rx Buffer 5 */
|
||||
+#define SUN4I_REG_BUF6_ADDR 0x0058 /* CAN Tx/Rx Buffer 6 */
|
||||
+#define SUN4I_REG_BUF7_ADDR 0x005c /* CAN Tx/Rx Buffer 7 */
|
||||
+#define SUN4I_REG_BUF8_ADDR 0x0060 /* CAN Tx/Rx Buffer 8 */
|
||||
+#define SUN4I_REG_BUF9_ADDR 0x0064 /* CAN Tx/Rx Buffer 9 */
|
||||
+#define SUN4I_REG_BUF10_ADDR 0x0068 /* CAN Tx/Rx Buffer 10 */
|
||||
+#define SUN4I_REG_BUF11_ADDR 0x006c /* CAN Tx/Rx Buffer 11 */
|
||||
+#define SUN4I_REG_BUF12_ADDR 0x0070 /* CAN Tx/Rx Buffer 12 */
|
||||
+#define SUN4I_REG_ACPC_ADDR 0x0040 /* CAN Acceptance Code 0 */
|
||||
+#define SUN4I_REG_ACPM_ADDR 0x0044 /* CAN Acceptance Mask 0 */
|
||||
+#define SUN4I_REG_RBUF_RBACK_START_ADDR 0x0180 /* CAN transmit buffer start */
|
||||
+#define SUN4I_REG_RBUF_RBACK_END_ADDR 0x01b0 /* CAN transmit buffer end */
|
||||
+
|
||||
+/* Controller Register Description */
|
||||
+
|
||||
+/* mode select register (r/w)
|
||||
+ * offset:0x0000 default:0x0000_0001
|
||||
+ */
|
||||
+#define SUN4I_MSEL_SLEEP_MODE (0x01 << 4) /* write in reset mode */
|
||||
+#define SUN4I_MSEL_WAKE_UP (0x00 << 4)
|
||||
+#define SUN4I_MSEL_SINGLE_FILTER (0x01 << 3) /* write in reset mode */
|
||||
+#define SUN4I_MSEL_DUAL_FILTERS (0x00 << 3)
|
||||
+#define SUN4I_MSEL_LOOPBACK_MODE BIT(2)
|
||||
+#define SUN4I_MSEL_LISTEN_ONLY_MODE BIT(1)
|
||||
+#define SUN4I_MSEL_RESET_MODE BIT(0)
|
||||
+
|
||||
+/* command register (w)
|
||||
+ * offset:0x0004 default:0x0000_0000
|
||||
+ */
|
||||
+#define SUN4I_CMD_BUS_OFF_REQ BIT(5)
|
||||
+#define SUN4I_CMD_SELF_RCV_REQ BIT(4)
|
||||
+#define SUN4I_CMD_CLEAR_OR_FLAG BIT(3)
|
||||
+#define SUN4I_CMD_RELEASE_RBUF BIT(2)
|
||||
+#define SUN4I_CMD_ABORT_REQ BIT(1)
|
||||
+#define SUN4I_CMD_TRANS_REQ BIT(0)
|
||||
+
|
||||
+/* status register (r)
|
||||
+ * offset:0x0008 default:0x0000_003c
|
||||
+ */
|
||||
+#define SUN4I_STA_BIT_ERR (0x00 << 22)
|
||||
+#define SUN4I_STA_FORM_ERR (0x01 << 22)
|
||||
+#define SUN4I_STA_STUFF_ERR (0x02 << 22)
|
||||
+#define SUN4I_STA_OTHER_ERR (0x03 << 22)
|
||||
+#define SUN4I_STA_MASK_ERR (0x03 << 22)
|
||||
+#define SUN4I_STA_ERR_DIR BIT(21)
|
||||
+#define SUN4I_STA_ERR_SEG_CODE (0x1f << 16)
|
||||
+#define SUN4I_STA_START (0x03 << 16)
|
||||
+#define SUN4I_STA_ID28_21 (0x02 << 16)
|
||||
+#define SUN4I_STA_ID20_18 (0x06 << 16)
|
||||
+#define SUN4I_STA_SRTR (0x04 << 16)
|
||||
+#define SUN4I_STA_IDE (0x05 << 16)
|
||||
+#define SUN4I_STA_ID17_13 (0x07 << 16)
|
||||
+#define SUN4I_STA_ID12_5 (0x0f << 16)
|
||||
+#define SUN4I_STA_ID4_0 (0x0e << 16)
|
||||
+#define SUN4I_STA_RTR (0x0c << 16)
|
||||
+#define SUN4I_STA_RB1 (0x0d << 16)
|
||||
+#define SUN4I_STA_RB0 (0x09 << 16)
|
||||
+#define SUN4I_STA_DLEN (0x0b << 16)
|
||||
+#define SUN4I_STA_DATA_FIELD (0x0a << 16)
|
||||
+#define SUN4I_STA_CRC_SEQUENCE (0x08 << 16)
|
||||
+#define SUN4I_STA_CRC_DELIMITER (0x18 << 16)
|
||||
+#define SUN4I_STA_ACK (0x19 << 16)
|
||||
+#define SUN4I_STA_ACK_DELIMITER (0x1b << 16)
|
||||
+#define SUN4I_STA_END (0x1a << 16)
|
||||
+#define SUN4I_STA_INTERMISSION (0x12 << 16)
|
||||
+#define SUN4I_STA_ACTIVE_ERROR (0x11 << 16)
|
||||
+#define SUN4I_STA_PASSIVE_ERROR (0x16 << 16)
|
||||
+#define SUN4I_STA_TOLERATE_DOMINANT_BITS (0x13 << 16)
|
||||
+#define SUN4I_STA_ERROR_DELIMITER (0x17 << 16)
|
||||
+#define SUN4I_STA_OVERLOAD (0x1c << 16)
|
||||
+#define SUN4I_STA_BUS_OFF BIT(7)
|
||||
+#define SUN4I_STA_ERR_STA BIT(6)
|
||||
+#define SUN4I_STA_TRANS_BUSY BIT(5)
|
||||
+#define SUN4I_STA_RCV_BUSY BIT(4)
|
||||
+#define SUN4I_STA_TRANS_OVER BIT(3)
|
||||
+#define SUN4I_STA_TBUF_RDY BIT(2)
|
||||
+#define SUN4I_STA_DATA_ORUN BIT(1)
|
||||
+#define SUN4I_STA_RBUF_RDY BIT(0)
|
||||
+
|
||||
+/* interrupt register (r)
|
||||
+ * offset:0x000c default:0x0000_0000
|
||||
+ */
|
||||
+#define SUN4I_INT_BUS_ERR BIT(7)
|
||||
+#define SUN4I_INT_ARB_LOST BIT(6)
|
||||
+#define SUN4I_INT_ERR_PASSIVE BIT(5)
|
||||
+#define SUN4I_INT_WAKEUP BIT(4)
|
||||
+#define SUN4I_INT_DATA_OR BIT(3)
|
||||
+#define SUN4I_INT_ERR_WRN BIT(2)
|
||||
+#define SUN4I_INT_TBUF_VLD BIT(1)
|
||||
+#define SUN4I_INT_RBUF_VLD BIT(0)
|
||||
+
|
||||
+/* interrupt enable register (r/w)
|
||||
+ * offset:0x0010 default:0x0000_0000
|
||||
+ */
|
||||
+#define SUN4I_INTEN_BERR BIT(7)
|
||||
+#define SUN4I_INTEN_ARB_LOST BIT(6)
|
||||
+#define SUN4I_INTEN_ERR_PASSIVE BIT(5)
|
||||
+#define SUN4I_INTEN_WAKEUP BIT(4)
|
||||
+#define SUN4I_INTEN_OR BIT(3)
|
||||
+#define SUN4I_INTEN_ERR_WRN BIT(2)
|
||||
+#define SUN4I_INTEN_TX BIT(1)
|
||||
+#define SUN4I_INTEN_RX BIT(0)
|
||||
+
|
||||
+/* error code */
|
||||
+#define SUN4I_ERR_INRCV (0x1 << 5)
|
||||
+#define SUN4I_ERR_INTRANS (0x0 << 5)
|
||||
+
|
||||
+/* filter mode */
|
||||
+#define SUN4I_FILTER_CLOSE 0
|
||||
+#define SUN4I_SINGLE_FLTER_MODE 1
|
||||
+#define SUN4I_DUAL_FILTER_MODE 2
|
||||
+
|
||||
+/* message buffer flags */
|
||||
+#define SUN4I_MSG_EFF_FLAG BIT(7)
|
||||
+#define SUN4I_MSG_RTR_FLAG BIT(6)
|
||||
+
|
||||
+/* max. number of interrupts handled in ISR */
|
||||
+#define SUN4I_CAN_MAX_IRQ 20
|
||||
+#define SUN4I_MODE_MAX_RETRIES 100
|
||||
+
|
||||
+struct sun4ican_priv {
|
||||
+ struct can_priv can;
|
||||
+ void __iomem *base;
|
||||
+ struct clk *clk;
|
||||
+ spinlock_t cmdreg_lock; /* lock for concurrent cmd register writes */
|
||||
+};
|
||||
+
|
||||
+static const struct can_bittiming_const sun4ican_bittiming_const = {
|
||||
+ .name = DRV_NAME,
|
||||
+ .tseg1_min = 1,
|
||||
+ .tseg1_max = 16,
|
||||
+ .tseg2_min = 1,
|
||||
+ .tseg2_max = 8,
|
||||
+ .sjw_max = 4,
|
||||
+ .brp_min = 1,
|
||||
+ .brp_max = 64,
|
||||
+ .brp_inc = 1,
|
||||
+};
|
||||
+
|
||||
+static void sun4i_can_write_cmdreg(struct sun4ican_priv *priv, u8 val)
|
||||
+{
|
||||
+ unsigned long flags;
|
||||
+
|
||||
+ spin_lock_irqsave(&priv->cmdreg_lock, flags);
|
||||
+ writel(val, priv->base + SUN4I_REG_CMD_ADDR);
|
||||
+ spin_unlock_irqrestore(&priv->cmdreg_lock, flags);
|
||||
+}
|
||||
+
|
||||
+static int set_normal_mode(struct net_device *dev)
|
||||
+{
|
||||
+ struct sun4ican_priv *priv = netdev_priv(dev);
|
||||
+ int retry = SUN4I_MODE_MAX_RETRIES;
|
||||
+ u32 mod_reg_val = 0;
|
||||
+
|
||||
+ do {
|
||||
+ mod_reg_val = readl(priv->base + SUN4I_REG_MSEL_ADDR);
|
||||
+ mod_reg_val &= ~SUN4I_MSEL_RESET_MODE;
|
||||
+ writel(mod_reg_val, priv->base + SUN4I_REG_MSEL_ADDR);
|
||||
+ } while (retry-- && (mod_reg_val & SUN4I_MSEL_RESET_MODE));
|
||||
+
|
||||
+ if (readl(priv->base + SUN4I_REG_MSEL_ADDR) & SUN4I_MSEL_RESET_MODE) {
|
||||
+ netdev_err(dev,
|
||||
+ "setting controller into normal mode failed!\n");
|
||||
+ return -ETIMEDOUT;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int set_reset_mode(struct net_device *dev)
|
||||
+{
|
||||
+ struct sun4ican_priv *priv = netdev_priv(dev);
|
||||
+ int retry = SUN4I_MODE_MAX_RETRIES;
|
||||
+ u32 mod_reg_val = 0;
|
||||
+
|
||||
+ do {
|
||||
+ mod_reg_val = readl(priv->base + SUN4I_REG_MSEL_ADDR);
|
||||
+ mod_reg_val |= SUN4I_MSEL_RESET_MODE;
|
||||
+ writel(mod_reg_val, priv->base + SUN4I_REG_MSEL_ADDR);
|
||||
+ } while (retry-- && !(mod_reg_val & SUN4I_MSEL_RESET_MODE));
|
||||
+
|
||||
+ if (!(readl(priv->base + SUN4I_REG_MSEL_ADDR) &
|
||||
+ SUN4I_MSEL_RESET_MODE)) {
|
||||
+ netdev_err(dev, "setting controller into reset mode failed!\n");
|
||||
+ return -ETIMEDOUT;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+/* bittiming is called in reset_mode only */
|
||||
+static int sun4ican_set_bittiming(struct net_device *dev)
|
||||
+{
|
||||
+ struct sun4ican_priv *priv = netdev_priv(dev);
|
||||
+ struct can_bittiming *bt = &priv->can.bittiming;
|
||||
+ u32 cfg;
|
||||
+
|
||||
+ cfg = ((bt->brp - 1) & 0x3FF) |
|
||||
+ (((bt->sjw - 1) & 0x3) << 14) |
|
||||
+ (((bt->prop_seg + bt->phase_seg1 - 1) & 0xf) << 16) |
|
||||
+ (((bt->phase_seg2 - 1) & 0x7) << 20);
|
||||
+ if (priv->can.ctrlmode & CAN_CTRLMODE_3_SAMPLES)
|
||||
+ cfg |= 0x800000;
|
||||
+
|
||||
+ netdev_dbg(dev, "setting BITTIMING=0x%08x\n", cfg);
|
||||
+ writel(cfg, priv->base + SUN4I_REG_BTIME_ADDR);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int sun4ican_get_berr_counter(const struct net_device *dev,
|
||||
+ struct can_berr_counter *bec)
|
||||
+{
|
||||
+ struct sun4ican_priv *priv = netdev_priv(dev);
|
||||
+ u32 errors;
|
||||
+ int err;
|
||||
+
|
||||
+ err = clk_prepare_enable(priv->clk);
|
||||
+ if (err) {
|
||||
+ netdev_err(dev, "could not enable clock\n");
|
||||
+ return err;
|
||||
+ }
|
||||
+
|
||||
+ errors = readl(priv->base + SUN4I_REG_ERRC_ADDR);
|
||||
+
|
||||
+ bec->txerr = errors & 0xFF;
|
||||
+ bec->rxerr = (errors >> 16) & 0xFF;
|
||||
+
|
||||
+ clk_disable_unprepare(priv->clk);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int sun4i_can_start(struct net_device *dev)
|
||||
+{
|
||||
+ struct sun4ican_priv *priv = netdev_priv(dev);
|
||||
+ int err;
|
||||
+ u32 mod_reg_val;
|
||||
+
|
||||
+ /* we need to enter the reset mode */
|
||||
+ err = set_reset_mode(dev);
|
||||
+ if (err) {
|
||||
+ netdev_err(dev, "could not enter reset mode\n");
|
||||
+ return err;
|
||||
+ }
|
||||
+
|
||||
+ /* set filters - we accept all */
|
||||
+ writel(0x00000000, priv->base + SUN4I_REG_ACPC_ADDR);
|
||||
+ writel(0xFFFFFFFF, priv->base + SUN4I_REG_ACPM_ADDR);
|
||||
+
|
||||
+ /* clear error counters and error code capture */
|
||||
+ writel(0, priv->base + SUN4I_REG_ERRC_ADDR);
|
||||
+
|
||||
+ /* enable interrupts */
|
||||
+ if (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING)
|
||||
+ writel(0xFF, priv->base + SUN4I_REG_INTEN_ADDR);
|
||||
+ else
|
||||
+ writel(0xFF & ~SUN4I_INTEN_BERR,
|
||||
+ priv->base + SUN4I_REG_INTEN_ADDR);
|
||||
+
|
||||
+ /* enter the selected mode */
|
||||
+ mod_reg_val = readl(priv->base + SUN4I_REG_MSEL_ADDR);
|
||||
+ if (priv->can.ctrlmode & CAN_CTRLMODE_PRESUME_ACK)
|
||||
+ mod_reg_val |= SUN4I_MSEL_LOOPBACK_MODE;
|
||||
+ else if (priv->can.ctrlmode & CAN_CTRLMODE_LISTENONLY)
|
||||
+ mod_reg_val |= SUN4I_MSEL_LISTEN_ONLY_MODE;
|
||||
+ writel(mod_reg_val, priv->base + SUN4I_REG_MSEL_ADDR);
|
||||
+
|
||||
+ err = sun4ican_set_bittiming(dev);
|
||||
+ if (err)
|
||||
+ return err;
|
||||
+
|
||||
+ /* we are ready to enter the normal mode */
|
||||
+ err = set_normal_mode(dev);
|
||||
+ if (err) {
|
||||
+ netdev_err(dev, "could not enter normal mode\n");
|
||||
+ return err;
|
||||
+ }
|
||||
+
|
||||
+ priv->can.state = CAN_STATE_ERROR_ACTIVE;
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int sun4i_can_stop(struct net_device *dev)
|
||||
+{
|
||||
+ struct sun4ican_priv *priv = netdev_priv(dev);
|
||||
+ int err;
|
||||
+
|
||||
+ priv->can.state = CAN_STATE_STOPPED;
|
||||
+ /* we need to enter reset mode */
|
||||
+ err = set_reset_mode(dev);
|
||||
+ if (err) {
|
||||
+ netdev_err(dev, "could not enter reset mode\n");
|
||||
+ return err;
|
||||
+ }
|
||||
+
|
||||
+ /* disable all interrupts */
|
||||
+ writel(0, priv->base + SUN4I_REG_INTEN_ADDR);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int sun4ican_set_mode(struct net_device *dev, enum can_mode mode)
|
||||
+{
|
||||
+ int err;
|
||||
+
|
||||
+ switch (mode) {
|
||||
+ case CAN_MODE_START:
|
||||
+ err = sun4i_can_start(dev);
|
||||
+ if (err) {
|
||||
+ netdev_err(dev, "starting CAN controller failed!\n");
|
||||
+ return err;
|
||||
+ }
|
||||
+ if (netif_queue_stopped(dev))
|
||||
+ netif_wake_queue(dev);
|
||||
+ break;
|
||||
+
|
||||
+ default:
|
||||
+ return -EOPNOTSUPP;
|
||||
+ }
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+/* transmit a CAN message
|
||||
+ * message layout in the sk_buff should be like this:
|
||||
+ * xx xx xx xx ff ll 00 11 22 33 44 55 66 77
|
||||
+ * [ can_id ] [flags] [len] [can data (up to 8 bytes]
|
||||
+ */
|
||||
+static int sun4ican_start_xmit(struct sk_buff *skb, struct net_device *dev)
|
||||
+{
|
||||
+ struct sun4ican_priv *priv = netdev_priv(dev);
|
||||
+ struct can_frame *cf = (struct can_frame *)skb->data;
|
||||
+ u8 dlc;
|
||||
+ u32 dreg, msg_flag_n;
|
||||
+ canid_t id;
|
||||
+ int i;
|
||||
+
|
||||
+ if (can_dropped_invalid_skb(dev, skb))
|
||||
+ return NETDEV_TX_OK;
|
||||
+
|
||||
+ netif_stop_queue(dev);
|
||||
+
|
||||
+ id = cf->can_id;
|
||||
+ dlc = cf->can_dlc;
|
||||
+ msg_flag_n = dlc;
|
||||
+
|
||||
+ if (id & CAN_RTR_FLAG)
|
||||
+ msg_flag_n |= SUN4I_MSG_RTR_FLAG;
|
||||
+
|
||||
+ if (id & CAN_EFF_FLAG) {
|
||||
+ msg_flag_n |= SUN4I_MSG_EFF_FLAG;
|
||||
+ dreg = SUN4I_REG_BUF5_ADDR;
|
||||
+ writel((id >> 21) & 0xFF, priv->base + SUN4I_REG_BUF1_ADDR);
|
||||
+ writel((id >> 13) & 0xFF, priv->base + SUN4I_REG_BUF2_ADDR);
|
||||
+ writel((id >> 5) & 0xFF, priv->base + SUN4I_REG_BUF3_ADDR);
|
||||
+ writel((id << 3) & 0xF8, priv->base + SUN4I_REG_BUF4_ADDR);
|
||||
+ } else {
|
||||
+ dreg = SUN4I_REG_BUF3_ADDR;
|
||||
+ writel((id >> 3) & 0xFF, priv->base + SUN4I_REG_BUF1_ADDR);
|
||||
+ writel((id << 5) & 0xE0, priv->base + SUN4I_REG_BUF2_ADDR);
|
||||
+ }
|
||||
+
|
||||
+ for (i = 0; i < dlc; i++)
|
||||
+ writel(cf->data[i], priv->base + (dreg + i * 4));
|
||||
+
|
||||
+ writel(msg_flag_n, priv->base + SUN4I_REG_BUF0_ADDR);
|
||||
+
|
||||
+ can_put_echo_skb(skb, dev, 0);
|
||||
+
|
||||
+ if (priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK)
|
||||
+ sun4i_can_write_cmdreg(priv, SUN4I_CMD_SELF_RCV_REQ);
|
||||
+ else
|
||||
+ sun4i_can_write_cmdreg(priv, SUN4I_CMD_TRANS_REQ);
|
||||
+
|
||||
+ return NETDEV_TX_OK;
|
||||
+}
|
||||
+
|
||||
+static void sun4i_can_rx(struct net_device *dev)
|
||||
+{
|
||||
+ struct sun4ican_priv *priv = netdev_priv(dev);
|
||||
+ struct net_device_stats *stats = &dev->stats;
|
||||
+ struct can_frame *cf;
|
||||
+ struct sk_buff *skb;
|
||||
+ u8 fi;
|
||||
+ u32 dreg;
|
||||
+ canid_t id;
|
||||
+ int i;
|
||||
+
|
||||
+ /* create zero'ed CAN frame buffer */
|
||||
+ skb = alloc_can_skb(dev, &cf);
|
||||
+ if (!skb)
|
||||
+ return;
|
||||
+
|
||||
+ fi = readl(priv->base + SUN4I_REG_BUF0_ADDR);
|
||||
+ cf->can_dlc = get_can_dlc(fi & 0x0F);
|
||||
+ if (fi & SUN4I_MSG_EFF_FLAG) {
|
||||
+ dreg = SUN4I_REG_BUF5_ADDR;
|
||||
+ id = (readl(priv->base + SUN4I_REG_BUF1_ADDR) << 21) |
|
||||
+ (readl(priv->base + SUN4I_REG_BUF2_ADDR) << 13) |
|
||||
+ (readl(priv->base + SUN4I_REG_BUF3_ADDR) << 5) |
|
||||
+ ((readl(priv->base + SUN4I_REG_BUF4_ADDR) >> 3) & 0x1f);
|
||||
+ id |= CAN_EFF_FLAG;
|
||||
+ } else {
|
||||
+ dreg = SUN4I_REG_BUF3_ADDR;
|
||||
+ id = (readl(priv->base + SUN4I_REG_BUF1_ADDR) << 3) |
|
||||
+ ((readl(priv->base + SUN4I_REG_BUF2_ADDR) >> 5) & 0x7);
|
||||
+ }
|
||||
+
|
||||
+ /* remote frame ? */
|
||||
+ if (fi & SUN4I_MSG_RTR_FLAG)
|
||||
+ id |= CAN_RTR_FLAG;
|
||||
+ else
|
||||
+ for (i = 0; i < cf->can_dlc; i++)
|
||||
+ cf->data[i] = readl(priv->base + dreg + i * 4);
|
||||
+
|
||||
+ cf->can_id = id;
|
||||
+
|
||||
+ sun4i_can_write_cmdreg(priv, SUN4I_CMD_RELEASE_RBUF);
|
||||
+
|
||||
+ stats->rx_packets++;
|
||||
+ stats->rx_bytes += cf->can_dlc;
|
||||
+ netif_rx(skb);
|
||||
+
|
||||
+ can_led_event(dev, CAN_LED_EVENT_RX);
|
||||
+}
|
||||
+
|
||||
+static int sun4i_can_err(struct net_device *dev, u8 isrc, u8 status)
|
||||
+{
|
||||
+ struct sun4ican_priv *priv = netdev_priv(dev);
|
||||
+ struct net_device_stats *stats = &dev->stats;
|
||||
+ struct can_frame *cf;
|
||||
+ struct sk_buff *skb;
|
||||
+ enum can_state state = priv->can.state;
|
||||
+ enum can_state rx_state, tx_state;
|
||||
+ unsigned int rxerr, txerr, errc;
|
||||
+ u32 ecc, alc;
|
||||
+
|
||||
+ /* we don't skip if alloc fails because we want the stats anyhow */
|
||||
+ skb = alloc_can_err_skb(dev, &cf);
|
||||
+
|
||||
+ errc = readl(priv->base + SUN4I_REG_ERRC_ADDR);
|
||||
+ rxerr = (errc >> 16) & 0xFF;
|
||||
+ txerr = errc & 0xFF;
|
||||
+
|
||||
+ if (skb) {
|
||||
+ cf->data[6] = txerr;
|
||||
+ cf->data[7] = rxerr;
|
||||
+ }
|
||||
+
|
||||
+ if (isrc & SUN4I_INT_DATA_OR) {
|
||||
+ /* data overrun interrupt */
|
||||
+ netdev_dbg(dev, "data overrun interrupt\n");
|
||||
+ if (likely(skb)) {
|
||||
+ cf->can_id |= CAN_ERR_CRTL;
|
||||
+ cf->data[1] = CAN_ERR_CRTL_RX_OVERFLOW;
|
||||
+ }
|
||||
+ stats->rx_over_errors++;
|
||||
+ stats->rx_errors++;
|
||||
+ /* clear bit */
|
||||
+ sun4i_can_write_cmdreg(priv, SUN4I_CMD_CLEAR_OR_FLAG);
|
||||
+ }
|
||||
+ if (isrc & SUN4I_INT_ERR_WRN) {
|
||||
+ /* error warning interrupt */
|
||||
+ netdev_dbg(dev, "error warning interrupt\n");
|
||||
+
|
||||
+ if (status & SUN4I_STA_BUS_OFF)
|
||||
+ state = CAN_STATE_BUS_OFF;
|
||||
+ else if (status & SUN4I_STA_ERR_STA)
|
||||
+ state = CAN_STATE_ERROR_WARNING;
|
||||
+ else
|
||||
+ state = CAN_STATE_ERROR_ACTIVE;
|
||||
+ }
|
||||
+ if (isrc & SUN4I_INT_BUS_ERR) {
|
||||
+ /* bus error interrupt */
|
||||
+ netdev_dbg(dev, "bus error interrupt\n");
|
||||
+ priv->can.can_stats.bus_error++;
|
||||
+ stats->rx_errors++;
|
||||
+
|
||||
+ if (likely(skb)) {
|
||||
+ ecc = readl(priv->base + SUN4I_REG_STA_ADDR);
|
||||
+
|
||||
+ cf->can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR;
|
||||
+
|
||||
+ switch (ecc & SUN4I_STA_MASK_ERR) {
|
||||
+ case SUN4I_STA_BIT_ERR:
|
||||
+ cf->data[2] |= CAN_ERR_PROT_BIT;
|
||||
+ break;
|
||||
+ case SUN4I_STA_FORM_ERR:
|
||||
+ cf->data[2] |= CAN_ERR_PROT_FORM;
|
||||
+ break;
|
||||
+ case SUN4I_STA_STUFF_ERR:
|
||||
+ cf->data[2] |= CAN_ERR_PROT_STUFF;
|
||||
+ break;
|
||||
+ default:
|
||||
+ cf->data[2] |= CAN_ERR_PROT_UNSPEC;
|
||||
+ cf->data[3] = (ecc & SUN4I_STA_ERR_SEG_CODE)
|
||||
+ >> 16;
|
||||
+ break;
|
||||
+ }
|
||||
+ /* error occurred during transmission? */
|
||||
+ if ((ecc & SUN4I_STA_ERR_DIR) == 0)
|
||||
+ cf->data[2] |= CAN_ERR_PROT_TX;
|
||||
+ }
|
||||
+ }
|
||||
+ if (isrc & SUN4I_INT_ERR_PASSIVE) {
|
||||
+ /* error passive interrupt */
|
||||
+ netdev_dbg(dev, "error passive interrupt\n");
|
||||
+ if (state == CAN_STATE_ERROR_PASSIVE)
|
||||
+ state = CAN_STATE_ERROR_WARNING;
|
||||
+ else
|
||||
+ state = CAN_STATE_ERROR_PASSIVE;
|
||||
+ }
|
||||
+ if (isrc & SUN4I_INT_ARB_LOST) {
|
||||
+ /* arbitration lost interrupt */
|
||||
+ netdev_dbg(dev, "arbitration lost interrupt\n");
|
||||
+ alc = readl(priv->base + SUN4I_REG_STA_ADDR);
|
||||
+ priv->can.can_stats.arbitration_lost++;
|
||||
+ stats->tx_errors++;
|
||||
+ if (likely(skb)) {
|
||||
+ cf->can_id |= CAN_ERR_LOSTARB;
|
||||
+ cf->data[0] = (alc & 0x1f) >> 8;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if (state != priv->can.state) {
|
||||
+ tx_state = txerr >= rxerr ? state : 0;
|
||||
+ rx_state = txerr <= rxerr ? state : 0;
|
||||
+
|
||||
+ if (likely(skb))
|
||||
+ can_change_state(dev, cf, tx_state, rx_state);
|
||||
+ else
|
||||
+ priv->can.state = state;
|
||||
+ if (state == CAN_STATE_BUS_OFF)
|
||||
+ can_bus_off(dev);
|
||||
+ }
|
||||
+
|
||||
+ if (likely(skb)) {
|
||||
+ stats->rx_packets++;
|
||||
+ stats->rx_bytes += cf->can_dlc;
|
||||
+ netif_rx(skb);
|
||||
+ } else {
|
||||
+ return -ENOMEM;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static irqreturn_t sun4i_can_interrupt(int irq, void *dev_id)
|
||||
+{
|
||||
+ struct net_device *dev = (struct net_device *)dev_id;
|
||||
+ struct sun4ican_priv *priv = netdev_priv(dev);
|
||||
+ struct net_device_stats *stats = &dev->stats;
|
||||
+ u8 isrc, status;
|
||||
+ int n = 0;
|
||||
+
|
||||
+ while ((isrc = readl(priv->base + SUN4I_REG_INT_ADDR)) &&
|
||||
+ (n < SUN4I_CAN_MAX_IRQ)) {
|
||||
+ n++;
|
||||
+ status = readl(priv->base + SUN4I_REG_STA_ADDR);
|
||||
+
|
||||
+ if (isrc & SUN4I_INT_WAKEUP)
|
||||
+ netdev_warn(dev, "wakeup interrupt\n");
|
||||
+
|
||||
+ if (isrc & SUN4I_INT_TBUF_VLD) {
|
||||
+ /* transmission complete interrupt */
|
||||
+ stats->tx_bytes +=
|
||||
+ readl(priv->base +
|
||||
+ SUN4I_REG_RBUF_RBACK_START_ADDR) & 0xf;
|
||||
+ stats->tx_packets++;
|
||||
+ can_get_echo_skb(dev, 0);
|
||||
+ netif_wake_queue(dev);
|
||||
+ can_led_event(dev, CAN_LED_EVENT_TX);
|
||||
+ }
|
||||
+ if (isrc & SUN4I_INT_RBUF_VLD) {
|
||||
+ /* receive interrupt */
|
||||
+ while (status & SUN4I_STA_RBUF_RDY) {
|
||||
+ /* RX buffer is not empty */
|
||||
+ sun4i_can_rx(dev);
|
||||
+ status = readl(priv->base + SUN4I_REG_STA_ADDR);
|
||||
+ }
|
||||
+ }
|
||||
+ if (isrc &
|
||||
+ (SUN4I_INT_DATA_OR | SUN4I_INT_ERR_WRN | SUN4I_INT_BUS_ERR |
|
||||
+ SUN4I_INT_ERR_PASSIVE | SUN4I_INT_ARB_LOST)) {
|
||||
+ /* error interrupt */
|
||||
+ if (sun4i_can_err(dev, isrc, status))
|
||||
+ netdev_err(dev, "can't allocate buffer - clearing pending interrupts\n");
|
||||
+ }
|
||||
+ /* clear interrupts */
|
||||
+ writel(isrc, priv->base + SUN4I_REG_INT_ADDR);
|
||||
+ readl(priv->base + SUN4I_REG_INT_ADDR);
|
||||
+ }
|
||||
+ if (n >= SUN4I_CAN_MAX_IRQ)
|
||||
+ netdev_dbg(dev, "%d messages handled in ISR", n);
|
||||
+
|
||||
+ return (n) ? IRQ_HANDLED : IRQ_NONE;
|
||||
+}
|
||||
+
|
||||
+static int sun4ican_open(struct net_device *dev)
|
||||
+{
|
||||
+ struct sun4ican_priv *priv = netdev_priv(dev);
|
||||
+ int err;
|
||||
+
|
||||
+ /* common open */
|
||||
+ err = open_candev(dev);
|
||||
+ if (err)
|
||||
+ return err;
|
||||
+
|
||||
+ /* register interrupt handler */
|
||||
+ err = request_irq(dev->irq, sun4i_can_interrupt, 0, dev->name, dev);
|
||||
+ if (err) {
|
||||
+ netdev_err(dev, "request_irq err: %d\n", err);
|
||||
+ goto exit_irq;
|
||||
+ }
|
||||
+
|
||||
+ /* turn on clocking for CAN peripheral block */
|
||||
+ err = clk_prepare_enable(priv->clk);
|
||||
+ if (err) {
|
||||
+ netdev_err(dev, "could not enable CAN peripheral clock\n");
|
||||
+ goto exit_clock;
|
||||
+ }
|
||||
+
|
||||
+ err = sun4i_can_start(dev);
|
||||
+ if (err) {
|
||||
+ netdev_err(dev, "could not start CAN peripheral\n");
|
||||
+ goto exit_can_start;
|
||||
+ }
|
||||
+
|
||||
+ can_led_event(dev, CAN_LED_EVENT_OPEN);
|
||||
+ netif_start_queue(dev);
|
||||
+
|
||||
+ return 0;
|
||||
+
|
||||
+exit_can_start:
|
||||
+ clk_disable_unprepare(priv->clk);
|
||||
+exit_clock:
|
||||
+ free_irq(dev->irq, dev);
|
||||
+exit_irq:
|
||||
+ close_candev(dev);
|
||||
+ return err;
|
||||
+}
|
||||
+
|
||||
+static int sun4ican_close(struct net_device *dev)
|
||||
+{
|
||||
+ struct sun4ican_priv *priv = netdev_priv(dev);
|
||||
+
|
||||
+ netif_stop_queue(dev);
|
||||
+ sun4i_can_stop(dev);
|
||||
+ clk_disable_unprepare(priv->clk);
|
||||
+
|
||||
+ free_irq(dev->irq, dev);
|
||||
+ close_candev(dev);
|
||||
+ can_led_event(dev, CAN_LED_EVENT_STOP);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static const struct net_device_ops sun4ican_netdev_ops = {
|
||||
+ .ndo_open = sun4ican_open,
|
||||
+ .ndo_stop = sun4ican_close,
|
||||
+ .ndo_start_xmit = sun4ican_start_xmit,
|
||||
+};
|
||||
+
|
||||
+static const struct of_device_id sun4ican_of_match[] = {
|
||||
+ {.compatible = "allwinner,sun4i-a10-can"},
|
||||
+ {},
|
||||
+};
|
||||
+
|
||||
+MODULE_DEVICE_TABLE(of, sun4ican_of_match);
|
||||
+
|
||||
+static int sun4ican_remove(struct platform_device *pdev)
|
||||
+{
|
||||
+ struct net_device *dev = platform_get_drvdata(pdev);
|
||||
+
|
||||
+ unregister_netdev(dev);
|
||||
+ free_candev(dev);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int sun4ican_probe(struct platform_device *pdev)
|
||||
+{
|
||||
+ struct device_node *np = pdev->dev.of_node;
|
||||
+ struct resource *mem;
|
||||
+ struct clk *clk;
|
||||
+ void __iomem *addr;
|
||||
+ int err, irq;
|
||||
+ struct net_device *dev;
|
||||
+ struct sun4ican_priv *priv;
|
||||
+
|
||||
+ clk = of_clk_get(np, 0);
|
||||
+ if (IS_ERR(clk)) {
|
||||
+ dev_err(&pdev->dev, "unable to request clock\n");
|
||||
+ err = -ENODEV;
|
||||
+ goto exit;
|
||||
+ }
|
||||
+
|
||||
+ irq = platform_get_irq(pdev, 0);
|
||||
+ if (irq < 0) {
|
||||
+ dev_err(&pdev->dev, "could not get a valid irq\n");
|
||||
+ err = -ENODEV;
|
||||
+ goto exit;
|
||||
+ }
|
||||
+
|
||||
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
+ addr = devm_ioremap_resource(&pdev->dev, mem);
|
||||
+ if (IS_ERR(addr)) {
|
||||
+ err = -EBUSY;
|
||||
+ goto exit;
|
||||
+ }
|
||||
+
|
||||
+ dev = alloc_candev(sizeof(struct sun4ican_priv), 1);
|
||||
+ if (!dev) {
|
||||
+ dev_err(&pdev->dev,
|
||||
+ "could not allocate memory for CAN device\n");
|
||||
+ err = -ENOMEM;
|
||||
+ goto exit;
|
||||
+ }
|
||||
+
|
||||
+ dev->netdev_ops = &sun4ican_netdev_ops;
|
||||
+ dev->irq = irq;
|
||||
+ dev->flags |= IFF_ECHO;
|
||||
+
|
||||
+ priv = netdev_priv(dev);
|
||||
+ priv->can.clock.freq = clk_get_rate(clk);
|
||||
+ priv->can.bittiming_const = &sun4ican_bittiming_const;
|
||||
+ priv->can.do_set_mode = sun4ican_set_mode;
|
||||
+ priv->can.do_get_berr_counter = sun4ican_get_berr_counter;
|
||||
+ priv->can.ctrlmode_supported = CAN_CTRLMODE_BERR_REPORTING |
|
||||
+ CAN_CTRLMODE_LISTENONLY |
|
||||
+ CAN_CTRLMODE_LOOPBACK |
|
||||
+ CAN_CTRLMODE_PRESUME_ACK |
|
||||
+ CAN_CTRLMODE_3_SAMPLES;
|
||||
+ priv->base = addr;
|
||||
+ priv->clk = clk;
|
||||
+ spin_lock_init(&priv->cmdreg_lock);
|
||||
+
|
||||
+ platform_set_drvdata(pdev, dev);
|
||||
+ SET_NETDEV_DEV(dev, &pdev->dev);
|
||||
+
|
||||
+ err = register_candev(dev);
|
||||
+ if (err) {
|
||||
+ dev_err(&pdev->dev, "registering %s failed (err=%d)\n",
|
||||
+ DRV_NAME, err);
|
||||
+ goto exit_free;
|
||||
+ }
|
||||
+ devm_can_led_init(dev);
|
||||
+
|
||||
+ dev_info(&pdev->dev, "device registered (base=%p, irq=%d)\n",
|
||||
+ priv->base, dev->irq);
|
||||
+
|
||||
+ return 0;
|
||||
+
|
||||
+exit_free:
|
||||
+ free_candev(dev);
|
||||
+exit:
|
||||
+ return err;
|
||||
+}
|
||||
+
|
||||
+static struct platform_driver sun4i_can_driver = {
|
||||
+ .driver = {
|
||||
+ .name = DRV_NAME,
|
||||
+ .of_match_table = sun4ican_of_match,
|
||||
+ },
|
||||
+ .probe = sun4ican_probe,
|
||||
+ .remove = sun4ican_remove,
|
||||
+};
|
||||
+
|
||||
+module_platform_driver(sun4i_can_driver);
|
||||
+
|
||||
+MODULE_AUTHOR("Peter Chen <xingkongcp@gmail.com>");
|
||||
+MODULE_AUTHOR("Gerhard Bertelsmann <info@gerhard-bertelsmann.de>");
|
||||
+MODULE_LICENSE("Dual BSD/GPL");
|
||||
+MODULE_DESCRIPTION(DRV_NAME "CAN driver for Allwinner SoCs (A10/A20)");
|
531
patch/kernel/sunxi-next/axp20x-sysfs-interface.patch
Normal file
531
patch/kernel/sunxi-next/axp20x-sysfs-interface.patch
Normal file
|
@ -0,0 +1,531 @@
|
|||
diff --git a/drivers/mfd/axp20x.c b/drivers/mfd/axp20x.c
|
||||
index 9842199..15166b5 100644
|
||||
--- a/drivers/mfd/axp20x.c
|
||||
+++ b/drivers/mfd/axp20x.c
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/acpi.h>
|
||||
+#include <linux/delay.h>
|
||||
|
||||
#define AXP20X_OFF 0x80
|
||||
|
||||
@@ -70,6 +71,7 @@ static const struct regmap_range axp20x_volatile_ranges[] = {
|
||||
regmap_reg_range(AXP20X_IRQ1_EN, AXP20X_IRQ5_STATE),
|
||||
regmap_reg_range(AXP20X_ACIN_V_ADC_H, AXP20X_IPSOUT_V_HIGH_L),
|
||||
regmap_reg_range(AXP20X_GPIO20_SS, AXP20X_GPIO3_CTRL),
|
||||
+ regmap_reg_range(AXP20X_CHRG_CC_31_24, AXP20X_DISCHRG_CC_7_0),
|
||||
regmap_reg_range(AXP20X_FG_RES, AXP20X_RDC_L),
|
||||
};
|
||||
|
||||
@@ -606,6 +608,488 @@ static void axp20x_power_off(void)
|
||||
AXP20X_OFF);
|
||||
}
|
||||
|
||||
+#define kobj_to_device(x) container_of(x, struct device, kobj)
|
||||
+
|
||||
+int axp20x_get_adc_freq(struct axp20x_dev *axp)
|
||||
+{
|
||||
+ unsigned int res;
|
||||
+ int ret, freq = 25;
|
||||
+
|
||||
+ ret = regmap_read(axp->regmap, AXP20X_ADC_RATE, &res);
|
||||
+ if (ret < 0) {
|
||||
+ dev_warn(axp->dev, "Unable to read ADC sampling frequency: %d\n", ret);
|
||||
+ return freq;
|
||||
+ }
|
||||
+ res &= 0xc0;
|
||||
+ switch (res >> 6) {
|
||||
+ case 0:
|
||||
+ freq = 25;
|
||||
+ break;
|
||||
+ case 1:
|
||||
+ freq = 50;
|
||||
+ break;
|
||||
+ case 2:
|
||||
+ freq = 100;
|
||||
+ break;
|
||||
+ case 3:
|
||||
+ freq = 200;
|
||||
+ break;
|
||||
+ }
|
||||
+ return freq;
|
||||
+}
|
||||
+
|
||||
+static ssize_t axp20x_read_special(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
|
||||
+{
|
||||
+ int i, freq, ret = 0;
|
||||
+ unsigned int res;
|
||||
+ u32 lval1, lval2;
|
||||
+ s64 llval;
|
||||
+ u64 ullval;
|
||||
+
|
||||
+ const char *subsystem = kobject_name(kobj);
|
||||
+ struct device *dev = kobj_to_device(kobj->parent);
|
||||
+ struct axp20x_dev *axp = dev_get_drvdata(dev);
|
||||
+
|
||||
+ dev_dbg(axp->dev, "read_special: reading attribute %s of object %s\n", attr->attr.name, subsystem);
|
||||
+
|
||||
+ if (strcmp(subsystem, "battery") == 0) {
|
||||
+ if (strcmp(attr->attr.name, "power") == 0) {
|
||||
+ lval1 = 0;
|
||||
+ for (i = 0; i < 3; i++) {
|
||||
+ ret |= regmap_read(axp->regmap, AXP20X_PWR_BATT_H + i, &res);
|
||||
+ lval1 |= res << ((2 - i) * 8);
|
||||
+ }
|
||||
+ llval = lval1 * 1100 / 1000;
|
||||
+ } else if (strcmp(attr->attr.name, "charge") == 0) {
|
||||
+ ret = regmap_raw_read(axp->regmap, AXP20X_CHRG_CC_31_24, &lval1, sizeof(lval1));
|
||||
+ ret |= regmap_raw_read(axp->regmap, AXP20X_DISCHRG_CC_31_24, &lval2, sizeof(lval2));
|
||||
+ be32_to_cpus(&lval1);
|
||||
+ be32_to_cpus(&lval2);
|
||||
+ ullval = abs((s64)lval1 - (s64)lval2) * 65536 * 500;
|
||||
+ freq = axp20x_get_adc_freq(axp);
|
||||
+ do_div(ullval, 3600 * freq);
|
||||
+ llval = (lval1 < lval2) ? -ullval : ullval;
|
||||
+ } else if (strcmp(attr->attr.name, "capacity") == 0) {
|
||||
+ ret = regmap_read(axp->regmap, AXP20X_FG_RES, &res);
|
||||
+ llval = res & 0x7f;
|
||||
+ } else
|
||||
+ return -EINVAL;
|
||||
+ } else
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ if (ret < 0) {
|
||||
+ dev_warn(axp->dev, "Unable to read parameter: %d\n", ret);
|
||||
+ return ret;
|
||||
+ }
|
||||
+ return sprintf(buf, "%lld\n", llval);
|
||||
+}
|
||||
+
|
||||
+static ssize_t axp20x_read_bool(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
|
||||
+{
|
||||
+ int val, ret, reg, bit;
|
||||
+ unsigned int res;
|
||||
+
|
||||
+ const char *subsystem = kobject_name(kobj);
|
||||
+ struct device *dev = kobj_to_device(kobj->parent);
|
||||
+ struct axp20x_dev *axp = dev_get_drvdata(dev);
|
||||
+
|
||||
+ dev_dbg(axp->dev, "read_bool: writing attribute %s of object %s\n", attr->attr.name, subsystem);
|
||||
+
|
||||
+ if (strcmp(subsystem, "ac") == 0) {
|
||||
+ if (strcmp(attr->attr.name, "connected") == 0) {
|
||||
+ reg = AXP20X_PWR_INPUT_STATUS;
|
||||
+ bit = 7;
|
||||
+ } else if (strcmp(attr->attr.name, "used") == 0) {
|
||||
+ reg = AXP20X_PWR_INPUT_STATUS;
|
||||
+ bit = 6;
|
||||
+ } else
|
||||
+ return -EINVAL;
|
||||
+ } else if (strcmp(subsystem, "vbus") == 0) {
|
||||
+ if (strcmp(attr->attr.name, "connected") == 0) {
|
||||
+ reg = AXP20X_PWR_INPUT_STATUS;
|
||||
+ bit = 5;
|
||||
+ } else if (strcmp(attr->attr.name, "used") == 0) {
|
||||
+ reg = AXP20X_PWR_INPUT_STATUS;
|
||||
+ bit = 4;
|
||||
+ } else if (strcmp(attr->attr.name, "strong") == 0) {
|
||||
+ reg = AXP20X_PWR_INPUT_STATUS;
|
||||
+ bit = 3;
|
||||
+ } else
|
||||
+ return -EINVAL;
|
||||
+ } else if (strcmp(subsystem, "battery") == 0) {
|
||||
+ if (strcmp(attr->attr.name, "connected") == 0) {
|
||||
+ reg = AXP20X_PWR_OP_MODE;
|
||||
+ bit = 5;
|
||||
+ } else if (strcmp(attr->attr.name, "charging") == 0) {
|
||||
+ reg = AXP20X_PWR_INPUT_STATUS;
|
||||
+ bit = 2;
|
||||
+ } else
|
||||
+ return -EINVAL;
|
||||
+ } else if (strcmp(subsystem, "pmu") == 0) {
|
||||
+ if (strcmp(attr->attr.name, "overheat") == 0) {
|
||||
+ reg = AXP20X_PWR_OP_MODE;
|
||||
+ bit = 7;
|
||||
+ } else
|
||||
+ return -EINVAL;
|
||||
+ } else if (strcmp(subsystem, "charger") == 0) {
|
||||
+ if (strcmp(attr->attr.name, "charging") == 0) {
|
||||
+ reg = AXP20X_PWR_OP_MODE;
|
||||
+ bit = 6;
|
||||
+ } else if (strcmp(attr->attr.name, "cell_activation") == 0) {
|
||||
+ reg = AXP20X_PWR_OP_MODE;
|
||||
+ bit = 3;
|
||||
+ } else if (strcmp(attr->attr.name, "low_power") == 0) {
|
||||
+ reg = AXP20X_PWR_OP_MODE;
|
||||
+ bit = 2;
|
||||
+ } else
|
||||
+ return -EINVAL;
|
||||
+ } else if (strcmp(subsystem, "control") == 0) {
|
||||
+ if (strcmp(attr->attr.name, "set_vbus_direct_mode") == 0) {
|
||||
+ reg = AXP20X_VBUS_IPSOUT_MGMT;
|
||||
+ bit = 6;
|
||||
+ } else if (strcmp(attr->attr.name, "reset_charge_counter") == 0) {
|
||||
+ reg = AXP20X_CC_CTRL;
|
||||
+ bit = 5;
|
||||
+ } else
|
||||
+ return -EINVAL;
|
||||
+ } else
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ ret = regmap_read(axp->regmap, reg, &res);
|
||||
+ if (ret < 0) {
|
||||
+ dev_warn(axp->dev, "Unable to read parameter: %d\n", ret);
|
||||
+ return ret;
|
||||
+ }
|
||||
+ val = (res & BIT(bit)) == BIT(bit) ? 1 : 0;
|
||||
+ return sprintf(buf, "%d\n", val);
|
||||
+}
|
||||
+
|
||||
+static ssize_t axp20x_write_bool(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)
|
||||
+{
|
||||
+ int var, ret, reg, bit;
|
||||
+
|
||||
+ const char *subsystem = kobject_name(kobj);
|
||||
+ struct device *dev = kobj_to_device(kobj->parent);
|
||||
+ struct axp20x_dev *axp = dev_get_drvdata(dev);
|
||||
+
|
||||
+ dev_dbg(axp->dev, "write_bool: writing attribute %s of object %s", attr->attr.name, subsystem);
|
||||
+
|
||||
+ ret = kstrtoint(buf, 10, &var);
|
||||
+ if (ret < 0)
|
||||
+ return ret;
|
||||
+
|
||||
+ if (strcmp(subsystem, "control") == 0) {
|
||||
+ if (strcmp(attr->attr.name, "set_vbus_direct_mode") == 0) {
|
||||
+ reg = AXP20X_VBUS_IPSOUT_MGMT;
|
||||
+ bit = 6;
|
||||
+ } else if (strcmp(attr->attr.name, "reset_charge_counter") == 0) {
|
||||
+ reg = AXP20X_CC_CTRL;
|
||||
+ bit = 5;
|
||||
+ } else
|
||||
+ return -EINVAL;
|
||||
+ } else
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ ret = regmap_update_bits(axp->regmap, reg, var ? BIT(bit) : 0, BIT(bit));
|
||||
+ if (ret)
|
||||
+ dev_warn(axp->dev, "Unable to write value: %d", ret);
|
||||
+ return count;
|
||||
+}
|
||||
+
|
||||
+static int axp20x_averaging_helper(struct regmap *reg_map, int reg_h,
|
||||
+ int width)
|
||||
+{
|
||||
+ long acc = 0;
|
||||
+ int ret, i;
|
||||
+
|
||||
+ for (i = 0; i < 3; i++) {
|
||||
+ ret = axp20x_read_variable_width(reg_map, reg_h, width);
|
||||
+ if (ret < 0)
|
||||
+ return ret;
|
||||
+ acc += ret;
|
||||
+ msleep(20); /* For 100Hz sampling frequency */
|
||||
+ }
|
||||
+ return (int)(acc / 3);
|
||||
+}
|
||||
+
|
||||
+static ssize_t axp20x_read_int(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
|
||||
+{
|
||||
+ int val, ret, scale, reg, width = 12, offset = 0;
|
||||
+
|
||||
+ const char *subsystem = kobject_name(kobj);
|
||||
+ struct device *dev = kobj_to_device(kobj->parent);
|
||||
+ struct axp20x_dev *axp = dev_get_drvdata(dev);
|
||||
+
|
||||
+ dev_dbg(axp->dev, "read_int: reading attribute %s of object %s\n", attr->attr.name, subsystem);
|
||||
+
|
||||
+ if (strcmp(subsystem, "ac") == 0) {
|
||||
+ if (strcmp(attr->attr.name, "voltage") == 0) {
|
||||
+ reg = AXP20X_ACIN_V_ADC_H;
|
||||
+ scale = 1700;
|
||||
+ } else if (strcmp(attr->attr.name, "amperage") == 0) {
|
||||
+ reg = AXP20X_ACIN_I_ADC_H;
|
||||
+ scale = 625;
|
||||
+ } else
|
||||
+ return -EINVAL;
|
||||
+ } else if (strcmp(subsystem, "vbus") == 0) {
|
||||
+ if (strcmp(attr->attr.name, "voltage") == 0) {
|
||||
+ reg = AXP20X_VBUS_V_ADC_H;
|
||||
+ scale = 1700;
|
||||
+ } else if (strcmp(attr->attr.name, "amperage") == 0) {
|
||||
+ reg = AXP20X_VBUS_I_ADC_H;
|
||||
+ scale = 375;
|
||||
+ } else
|
||||
+ return -EINVAL;
|
||||
+ } else if (strcmp(subsystem, "battery") == 0) {
|
||||
+ if (strcmp(attr->attr.name, "voltage") == 0) {
|
||||
+ reg = AXP20X_BATT_V_H;
|
||||
+ scale = 1100;
|
||||
+ } else if (strcmp(attr->attr.name, "amperage") == 0) {
|
||||
+ reg = AXP20X_BATT_DISCHRG_I_H;
|
||||
+ scale = 500;
|
||||
+ width = 13;
|
||||
+ } else if (strcmp(attr->attr.name, "ts_voltage") == 0) {
|
||||
+ reg = AXP20X_TS_IN_H;
|
||||
+ scale = 800;
|
||||
+ } else
|
||||
+ return -EINVAL;
|
||||
+ } else if (strcmp(subsystem, "pmu") == 0) {
|
||||
+ if (strcmp(attr->attr.name, "temp") == 0) {
|
||||
+ reg = AXP20X_TEMP_ADC_H;
|
||||
+ scale = 100;
|
||||
+ offset = 144700;
|
||||
+ } else if (strcmp(attr->attr.name, "voltage") == 0) {
|
||||
+ reg = AXP20X_IPSOUT_V_HIGH_H;
|
||||
+ scale = 1400;
|
||||
+ } else
|
||||
+ return -EINVAL;
|
||||
+ } else if (strcmp(subsystem, "charger") == 0) {
|
||||
+ if (strcmp(attr->attr.name, "amperage") == 0) {
|
||||
+ reg = AXP20X_BATT_CHRG_I_H;
|
||||
+ scale = 500;
|
||||
+ } else
|
||||
+ return -EINVAL;
|
||||
+ } else
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ ret = axp20x_averaging_helper(axp->regmap, reg, width);
|
||||
+
|
||||
+ if (ret < 0) {
|
||||
+ dev_warn(axp->dev, "Unable to read parameter: %d\n", ret);
|
||||
+ return ret;
|
||||
+ }
|
||||
+ val = ret * scale - offset;
|
||||
+ return sprintf(buf, "%d\n", val);
|
||||
+}
|
||||
+
|
||||
+/* AC IN */
|
||||
+static struct kobj_attribute ac_in_voltage = __ATTR(voltage, S_IRUGO, axp20x_read_int, NULL);
|
||||
+static struct kobj_attribute ac_in_amperage = __ATTR(amperage, S_IRUGO, axp20x_read_int, NULL);
|
||||
+static struct kobj_attribute ac_in_connected = __ATTR(connected, S_IRUGO, axp20x_read_bool, NULL);
|
||||
+static struct kobj_attribute ac_in_used = __ATTR(used, S_IRUGO, axp20x_read_bool, NULL);
|
||||
+
|
||||
+static struct attribute *axp20x_attributes_ac[] = {
|
||||
+ &ac_in_voltage.attr,
|
||||
+ &ac_in_amperage.attr,
|
||||
+ &ac_in_connected.attr,
|
||||
+ &ac_in_used.attr,
|
||||
+ NULL,
|
||||
+};
|
||||
+
|
||||
+static const struct attribute_group axp20x_group_ac = {
|
||||
+ .attrs = axp20x_attributes_ac,
|
||||
+};
|
||||
+
|
||||
+/* Vbus */
|
||||
+static struct kobj_attribute vbus_voltage = __ATTR(voltage, S_IRUGO, axp20x_read_int, NULL);
|
||||
+static struct kobj_attribute vbus_amperage = __ATTR(amperage, S_IRUGO, axp20x_read_int, NULL);
|
||||
+static struct kobj_attribute vbus_connected = __ATTR(connected, S_IRUGO, axp20x_read_bool, NULL);
|
||||
+static struct kobj_attribute vbus_used = __ATTR(used, S_IRUGO, axp20x_read_bool, NULL);
|
||||
+static struct kobj_attribute vbus_strong = __ATTR(strong, S_IRUGO, axp20x_read_bool, NULL);
|
||||
+
|
||||
+static struct attribute *axp20x_attributes_vbus[] = {
|
||||
+ &vbus_voltage.attr,
|
||||
+ &vbus_amperage.attr,
|
||||
+ &vbus_connected.attr,
|
||||
+ &vbus_used.attr,
|
||||
+ &vbus_strong.attr,
|
||||
+ NULL,
|
||||
+};
|
||||
+
|
||||
+static const struct attribute_group axp20x_group_vbus = {
|
||||
+ .attrs = axp20x_attributes_vbus,
|
||||
+};
|
||||
+
|
||||
+/* Battery */
|
||||
+static struct kobj_attribute batt_voltage = __ATTR(voltage, S_IRUGO, axp20x_read_int, NULL);
|
||||
+static struct kobj_attribute batt_amperage = __ATTR(amperage, S_IRUGO, axp20x_read_int, NULL);
|
||||
+static struct kobj_attribute batt_ts_voltage = __ATTR(ts_voltage, S_IRUGO, axp20x_read_int, NULL);
|
||||
+static struct kobj_attribute batt_power = __ATTR(power, S_IRUGO, axp20x_read_special, NULL);
|
||||
+static struct kobj_attribute batt_charge = __ATTR(charge, S_IRUGO, axp20x_read_special, NULL);
|
||||
+static struct kobj_attribute batt_capacity = __ATTR(capacity, S_IRUGO, axp20x_read_special, NULL);
|
||||
+static struct kobj_attribute batt_connected = __ATTR(connected, S_IRUGO, axp20x_read_bool, NULL);
|
||||
+static struct kobj_attribute batt_charging = __ATTR(charging, S_IRUGO, axp20x_read_bool, NULL);
|
||||
+
|
||||
+static struct attribute *axp20x_attributes_battery[] = {
|
||||
+ &batt_voltage.attr,
|
||||
+ &batt_amperage.attr,
|
||||
+ &batt_ts_voltage.attr,
|
||||
+ &batt_power.attr,
|
||||
+ &batt_charge.attr,
|
||||
+ &batt_capacity.attr,
|
||||
+ &batt_connected.attr,
|
||||
+ &batt_charging.attr,
|
||||
+ NULL,
|
||||
+};
|
||||
+
|
||||
+static const struct attribute_group axp20x_group_battery = {
|
||||
+ .attrs = axp20x_attributes_battery,
|
||||
+};
|
||||
+
|
||||
+/* PMU */
|
||||
+static struct kobj_attribute pmu_temp = __ATTR(temp, S_IRUGO, axp20x_read_int, NULL);
|
||||
+static struct kobj_attribute pmu_voltage = __ATTR(voltage, S_IRUGO, axp20x_read_int, NULL);
|
||||
+static struct kobj_attribute pmu_overheat = __ATTR(overheat, S_IRUGO, axp20x_read_bool, NULL);
|
||||
+
|
||||
+static struct attribute *axp20x_attributes_pmu[] = {
|
||||
+ &pmu_temp.attr,
|
||||
+ &pmu_voltage.attr,
|
||||
+ &pmu_overheat.attr,
|
||||
+ NULL,
|
||||
+};
|
||||
+
|
||||
+static const struct attribute_group axp20x_group_pmu = {
|
||||
+ .attrs = axp20x_attributes_pmu,
|
||||
+};
|
||||
+
|
||||
+/* Charger */
|
||||
+static struct kobj_attribute charger_amperage = __ATTR(amperage, S_IRUGO, axp20x_read_int, NULL);
|
||||
+static struct kobj_attribute charger_charging = __ATTR(charging, S_IRUGO, axp20x_read_bool, NULL);
|
||||
+static struct kobj_attribute charger_cell_activation = __ATTR(cell_activation, S_IRUGO, axp20x_read_bool, NULL);
|
||||
+static struct kobj_attribute charger_low_power = __ATTR(low_power, S_IRUGO, axp20x_read_bool, NULL);
|
||||
+
|
||||
+static struct attribute *axp20x_attributes_charger[] = {
|
||||
+ &charger_amperage.attr,
|
||||
+ &charger_charging.attr,
|
||||
+ &charger_cell_activation.attr,
|
||||
+ &charger_low_power.attr,
|
||||
+ NULL,
|
||||
+};
|
||||
+
|
||||
+static const struct attribute_group axp20x_group_charger = {
|
||||
+ .attrs = axp20x_attributes_charger,
|
||||
+};
|
||||
+
|
||||
+/* Control (writeable) */
|
||||
+static struct kobj_attribute control_vbus_direct_mode = __ATTR(set_vbus_direct_mode, S_IRUGO | S_IWUSR,
|
||||
+ axp20x_read_bool, axp20x_write_bool);
|
||||
+static struct kobj_attribute control_reset_charge_counter = __ATTR(reset_charge_counter, S_IRUGO | S_IWUSR,
|
||||
+ axp20x_read_bool, axp20x_write_bool);
|
||||
+
|
||||
+static struct attribute *axp20x_attributes_control[] = {
|
||||
+ &control_vbus_direct_mode.attr,
|
||||
+ &control_reset_charge_counter.attr,
|
||||
+ NULL,
|
||||
+};
|
||||
+
|
||||
+static const struct attribute_group axp20x_group_control = {
|
||||
+ .attrs = axp20x_attributes_control,
|
||||
+};
|
||||
+
|
||||
+static struct {
|
||||
+ struct kobject *ac;
|
||||
+ struct kobject *vbus;
|
||||
+ struct kobject *battery;
|
||||
+ struct kobject *pmu;
|
||||
+ struct kobject *charger;
|
||||
+ struct kobject *control;
|
||||
+} subsystems;
|
||||
+
|
||||
+static void axp20x_sysfs_create_subgroup(const char name[], struct axp20x_dev *axp,
|
||||
+ struct kobject *subgroup, const struct attribute_group *attrs)
|
||||
+{
|
||||
+ int ret;
|
||||
+ struct kobject *parent = &axp->dev->kobj;
|
||||
+ subgroup = kobject_create_and_add(name, parent);
|
||||
+ if (subgroup != NULL) {
|
||||
+ ret = sysfs_create_group(subgroup, attrs);
|
||||
+ if (ret) {
|
||||
+ dev_warn(axp->dev, "Unable to register sysfs group: %s: %d", name, ret);
|
||||
+ kobject_put(subgroup);
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+static void axp20x_sysfs_remove_subgroup(struct kobject *subgroup,
|
||||
+ const struct attribute_group *attrs)
|
||||
+{
|
||||
+ sysfs_remove_group(subgroup, attrs);
|
||||
+ kobject_put(subgroup);
|
||||
+}
|
||||
+
|
||||
+static int axp20x_sysfs_init(struct axp20x_dev *axp)
|
||||
+{
|
||||
+ int ret;
|
||||
+ unsigned int res;
|
||||
+
|
||||
+ /* Enable all ADC channels in first register */
|
||||
+ ret = regmap_write(axp->regmap, AXP20X_ADC_EN1, 0xFF);
|
||||
+ if (ret)
|
||||
+ dev_warn(axp->dev, "Unable to enable ADC: %d", ret);
|
||||
+
|
||||
+ /*
|
||||
+ * Set ADC sampling frequency to 100Hz (default is 25)
|
||||
+ * Always measure battery temperature (default: only when charging)
|
||||
+ */
|
||||
+ ret = regmap_update_bits(axp->regmap, AXP20X_ADC_RATE, 0xC3, 0x83);
|
||||
+ if (ret)
|
||||
+ dev_warn(axp->dev, "Unable to set ADC frequency and TS current output: %d", ret);
|
||||
+
|
||||
+ /* Enable fuel gauge and charge counter */
|
||||
+ ret = regmap_update_bits(axp->regmap, AXP20X_FG_RES, 0x80, 0x00);
|
||||
+ if (ret)
|
||||
+ dev_warn(axp->dev, "Unable to enable battery fuel gauge: %d", ret);
|
||||
+ ret = regmap_update_bits(axp->regmap, AXP20X_CC_CTRL, 0xC0, 0x00);
|
||||
+ ret |= regmap_update_bits(axp->regmap, AXP20X_CC_CTRL, 0xC0, 0x80);
|
||||
+ if (ret)
|
||||
+ dev_warn(axp->dev, "Unable to enable battery charge counter: %d", ret);
|
||||
+
|
||||
+ /* Enable battery detection */
|
||||
+ ret = regmap_read(axp->regmap, AXP20X_OFF_CTRL, &res);
|
||||
+ if (ret == 0) {
|
||||
+ if ((res & 0x40) != 0x40) {
|
||||
+ dev_info(axp->dev, "Battery detection is disabled, enabling");
|
||||
+ ret = regmap_update_bits(axp->regmap, AXP20X_OFF_CTRL, 0x40, 0x40);
|
||||
+ if (ret)
|
||||
+ dev_warn(axp->dev, "Unable to enable battery detection: %d", ret);
|
||||
+ }
|
||||
+ } else
|
||||
+ dev_warn(axp->dev, "Unable to read register AXP20X_OFF_CTRL: %d", ret);
|
||||
+
|
||||
+ axp20x_sysfs_create_subgroup("ac", axp, subsystems.ac, &axp20x_group_ac);
|
||||
+ axp20x_sysfs_create_subgroup("vbus", axp, subsystems.vbus, &axp20x_group_vbus);
|
||||
+ axp20x_sysfs_create_subgroup("battery", axp, subsystems.battery, &axp20x_group_battery);
|
||||
+ axp20x_sysfs_create_subgroup("pmu", axp, subsystems.pmu, &axp20x_group_pmu);
|
||||
+ axp20x_sysfs_create_subgroup("charger", axp, subsystems.charger, &axp20x_group_charger);
|
||||
+ axp20x_sysfs_create_subgroup("control", axp, subsystems.control, &axp20x_group_control);
|
||||
+
|
||||
+ ret = sysfs_create_link_nowarn(power_kobj, &axp->dev->kobj, "axp_pmu");
|
||||
+ if (ret)
|
||||
+ dev_warn(axp->dev, "Unable to create sysfs symlink: %d", ret);
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+static void axp20x_sysfs_exit(struct axp20x_dev *axp)
|
||||
+{
|
||||
+ sysfs_delete_link(power_kobj, &axp->dev->kobj, "axp_pmu");
|
||||
+ axp20x_sysfs_remove_subgroup(subsystems.control, &axp20x_group_control);
|
||||
+ axp20x_sysfs_remove_subgroup(subsystems.charger, &axp20x_group_charger);
|
||||
+ axp20x_sysfs_remove_subgroup(subsystems.pmu, &axp20x_group_pmu);
|
||||
+ axp20x_sysfs_remove_subgroup(subsystems.battery, &axp20x_group_battery);
|
||||
+ axp20x_sysfs_remove_subgroup(subsystems.vbus, &axp20x_group_vbus);
|
||||
+ axp20x_sysfs_remove_subgroup(subsystems.ac, &axp20x_group_ac);
|
||||
+}
|
||||
+
|
||||
static int axp20x_match_device(struct axp20x_dev *axp20x, struct device *dev)
|
||||
{
|
||||
const struct acpi_device_id *acpi_id;
|
||||
@@ -711,6 +1195,10 @@ static int axp20x_i2c_probe(struct i2c_client *i2c,
|
||||
pm_power_off = axp20x_power_off;
|
||||
}
|
||||
|
||||
+ if (axp20x->variant == AXP209_ID || axp20x->variant == AXP202_ID) {
|
||||
+ axp20x_sysfs_init(axp20x);
|
||||
+ }
|
||||
+
|
||||
dev_info(&i2c->dev, "AXP20X driver loaded\n");
|
||||
|
||||
return 0;
|
||||
@@ -720,6 +1208,10 @@ static int axp20x_i2c_remove(struct i2c_client *i2c)
|
||||
{
|
||||
struct axp20x_dev *axp20x = i2c_get_clientdata(i2c);
|
||||
|
||||
+ if (axp20x->variant == AXP209_ID || axp20x->variant == AXP202_ID) {
|
||||
+ axp20x_sysfs_exit(axp20x);
|
||||
+ }
|
||||
+
|
||||
if (axp20x == axp20x_pm_power_off) {
|
||||
axp20x_pm_power_off = NULL;
|
||||
pm_power_off = NULL;
|
Loading…
Add table
Add a link
Reference in a new issue