2471 lines
70 KiB
C
Executable file
2471 lines
70 KiB
C
Executable file
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* i3c-hci-exynos.c - Samsung Exynos I3C HCI Controller Driver
|
|
*
|
|
* Copyright (C) 2018 Samsung Electronics Co., Ltd.
|
|
* Copyright (C) 2021 Samsung Electronics Co., Ltd.
|
|
*
|
|
* 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/kernel.h>
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/time.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/err.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/i3c/master.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/iopoll.h>
|
|
#include <linux/io.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/of_gpio.h>
|
|
#include "../../pinctrl/core.h"
|
|
#include <linux/pm_runtime.h>
|
|
#include "i3c-hci-exynos.h"
|
|
|
|
#ifdef CONFIG_CPU_IDLE
|
|
#include <soc/samsung/exynos-cpupm.h>
|
|
#endif
|
|
|
|
/* Register Map */
|
|
#define I3C_HCI_VERSION 0x00
|
|
#define I3C_HC_CONTROL 0x04
|
|
#define I3C_MASTER_DEVICE_ADDR 0x08
|
|
#define I3C_HC_CAPABILITIES 0x0C
|
|
#define I3C_RESET_CTRL 0x10
|
|
#define I3C_PRESENT_STATE 0x14
|
|
#define I3C_DAT_SECTION_OFFSET 0x30
|
|
#define I3C_DCT_SECTION_OFFSET 0x34
|
|
#define I3C_RH_SECTION_OFFSET 0x38 /* not support */
|
|
#define I3C_PIO_SECTION_OFFSET 0x3C
|
|
#define I3C_EXT_CAPS_SECTION_OFFSET 0x40
|
|
#define I3C_IBI_NOTIFY_CTRL 0x58
|
|
#define I3C_DEV_CTX_BASE_LO 0x60
|
|
#define I3C_DEV_CTX_BASE_HI 0x64
|
|
#define I3C_DEV_ADDR_TABLE(x) ((x) * 4 + 0x100)
|
|
#define I3C_DEV_CHAR_TABLE_0(x) ((x) * 16 + 0x200)
|
|
#define I3C_DEV_CHAR_TABLE_1(x) ((x) * 16 + 0x204)
|
|
#define I3C_DEV_CHAR_TABLE_2(x) ((x) * 16 + 0x208)
|
|
#define I3C_DEV_CHAR_TABLE_3(x) ((x) * 16 + 0x20C)
|
|
#define I3C_CMD_QUEUE 0x300
|
|
#define I3C_RESP_QUEUE 0x304
|
|
#define I3C_XFER_DATA_BUF 0x308
|
|
#define I3C_IBI_PORT 0x30C
|
|
#define I3C_QUEUE_THLD_CTRL 0x310
|
|
#define I3C_DATA_BUFFER_THLD_CTRL 0x314
|
|
#define I3C_QUEUE_SIZE 0x318
|
|
#define I3C_PIO_INTR_STATUS 0x320
|
|
#define I3C_PIO_INTR_EN 0x324
|
|
#define I3C_PIO_INTR_SIGNAL_EN 0x328
|
|
#define I3C_PIO_INTR_FORCE 0x32C
|
|
|
|
/* EXT Register Map */
|
|
#define I3C_EXTCAP_HDR1 0x2000
|
|
#define I3C_COMP_MANUF 0x2004
|
|
#define I3C_COMP_VERSION 0x2008
|
|
#define I3C_COMP_TYPE 0x200C
|
|
#define I3C_EXTCAP_HDR2 0x2010
|
|
#define I3C_MASTER_CONFIG 0x2014
|
|
#define I3C_EXTCAP_HDR3 0x2018
|
|
#define I3C_BUS_INSTANCE_COUNT 0x201C
|
|
#define I3C_BUS_INSTANCE_OFFSET 0x2020
|
|
#define I3C_EXTCAP_HDR4 0x2024
|
|
#define I3C_QUEUE_STATUS_LEVEL 0x2028
|
|
#define I3C_DATA_BUFFER_THLD_STATUS_LEVEL 0x202C
|
|
#define I3C_PRESENT_STATE_DEBUG 0x2030
|
|
#define I3C_MX_ERROR_COUNTERS 0x2034
|
|
#define I3C_EXTCAP_HDR5 0x20FC
|
|
#define I3C_EXT_DEV_ADDR_TABLE(x) ((x) * 4 + 0x2100)
|
|
#define I3C_EXTCAP_HDR6 0x2180
|
|
#define I3C_EXT_PIO_INTR_STATUS 0x2184
|
|
#define I3C_EXT_PIO_INTR_EN 0x2188
|
|
#define I3C_EXT_PIO_INTR_SIGNAL_EN 0x218C
|
|
#define I3C_EXT_PIO_INTR_FORCE 0x2190
|
|
#define I3C_EXTCAP_HDR_7 0x219C
|
|
#define I3C_EXT_DEVICE_CTRL 0x2198
|
|
#define I3C_EXTCAP_HDR_8 0x219C
|
|
#define I3C_EXT_RESET_CTRL 0x21A0
|
|
#define I3C_EXTCAP_HDR_9 0x21A4
|
|
#define I3C_TRAILING_CYCLE 0x21A8
|
|
#define I3C_SCL_QUARTER_PERIOD 0x21AC
|
|
#define I3C_SCL_CTRL 0x21B0
|
|
#define I3C_TSU_STA 0x21B4
|
|
#define I3C_TCBP 0x21B8
|
|
#define I3C_TLOW 0x21BC
|
|
#define I3C_THD_STA 0x21C0
|
|
#define I3C_TBUF 0x21C4
|
|
#define I3C_BUS_CONDITION_CYCLE 0x21C8
|
|
#define I3C_SAMPLE_CYCLE 0x21CC
|
|
#define I3C_EXTCAP_HDR_10 0x21D0
|
|
#define I3C_SCL_LOW_FORCE_STEP 0x21D4
|
|
#define I3C_EXTCAP_HDR_11 0x21D8
|
|
#define I3C_FILTER_CTRL 0x21DC
|
|
#define I3C_EXTCAP_HDR_12 0x21E0
|
|
#define I3C_VGPIO_TX_CONFIG 0x21E4
|
|
#define I3C_VGPIO_TX_RESP 0x21E8
|
|
#define I3C_EXTCAP_HDR_13 0x21EC
|
|
#define I3C_VGPIO_RX_MASK(x) ((x) * 4 + 0x21F0)
|
|
#define I3C_EXTCAP_HDR_14 0x2210
|
|
#define I3C_HDR_DDR_RD_ABORT 0x2214
|
|
#define I3C_EXTCAP_HDR_15 0x2218
|
|
#define I3C_EVENT_COMMAND 0x221C
|
|
#define I3C_ENTER_ACTIVITY_STATE 0x2220
|
|
#define I3C_MAX_WRITE_LENGTH 0x2224
|
|
#define I3C_MAX_READ_LENGTH 0x2228
|
|
#define I3C_DEVICE_COUNT 0x222C
|
|
#define I3C_EXT_CURRENT_MASTER 0x2230
|
|
#define I3C_TEST_MODE_BYTE 0x2234
|
|
#define I3C_HDR_MODE 0x2238
|
|
#define I3C_CHAR_REG0 0x223C
|
|
#define I3C_CHAR_REG1 0x2240
|
|
#define I3C_MAX_SPEED 0x2244
|
|
#define I3C_MAX_RD_TURN 0x2248
|
|
#define I3C_HDR_CAP 0x224C
|
|
#define I3C_DATA_LEN_SLAVE_HDR 0x2250
|
|
#define I3C_DATA_SLAVE_HDR_TS_TX_PERIOD 0x2254
|
|
#define I3C_EXTCAP_HDR_16 0x22FC
|
|
#define I3C_IBI_TX_DATA_PORT 0x2300
|
|
#define I3C_IBI_TX_BUF_LEVEL 0x2304
|
|
#define I3C_EXTCAP_HDR_17 0x230C
|
|
#define I3C_DEBUG_CMD_QUEUE_0(x) ((x) * 8 + 0x2310)
|
|
#define I3C_DEBUG_CMD_QUEUE_1(x) ((x) * 8 + 0x2314)
|
|
#define I3C_DEBUG_RESP_STAT_QUEUE(x) ((x) * 4 + 0x2330)
|
|
#define I3C_DEBUG_TX_DATA_BUF(x) ((x) * 4 + 0x2340)
|
|
#define I3C_DEBUG_RX_DATA_BUF(x) ((x) * 4 + 0x2380)
|
|
#define I3C_DEBUG_IBI_QUEUE(x) ((x) * 4 + 0x23C0)
|
|
#define I3C_DEBUG_IBI_TX_BUF(x) ((x) + 0x2400)
|
|
#define I3C_VGPIO_TX_MONITOR 0x2410
|
|
#define I3C_VGPIO_RX_PAYLOAD 0x2414
|
|
#define I3C_MASTER_STAT_0 0x2418
|
|
#define I3C_MASTER_STAT_1 0x241C
|
|
#define I3C_SLAVE_STAT 0x2420
|
|
#define I3C_EXTCAP_HDR18 0x2424
|
|
#define I3C_TS_OE_DISABLE 0x2428
|
|
#define I3C_CHICKEN 0x242C
|
|
|
|
/* I3C_HC_CONTROL(0x4) Register bits */
|
|
|
|
#define I3C_ENABLE BIT(31)
|
|
#define I3C_ABORT BIT(29)
|
|
#define I3C_HOT_JOIN_CTRL BIT(8)
|
|
#define I3C_I2C_SLV_PRESENT BIT(7)
|
|
#define I3C_IBA_INCLUDE BIT(0)
|
|
|
|
/* I3C_EXT_DEV_CTRL(0x2198) */
|
|
|
|
#define I3C_HWACG_DISABLE_S BIT(31)
|
|
#define I3C_VGPIO_TX_REQ_FORCE BIT(22)
|
|
#define I3C_NOTIFY_VGPIO_RX BIT(21)
|
|
#define I3C_VGPIO_ENABLE BIT(20)
|
|
#define I3C_IBI_REQ BIT(15)
|
|
#define I3C_HJ_REQ BIT(14)
|
|
#define I3C_MASTERSHIP_REQ BIT(13)
|
|
#define I3C_SLAVE_MODE BIT(12)
|
|
#define I3C_DATA_SWAP BIT(10)
|
|
#define I3C_GETMXDS_5BYTE BIT(9)
|
|
#define I3C_TX_DMA_FREE_RUN BIT(8)
|
|
#define I3C_RX_STALL_EN BIT(7)
|
|
#define I3C_TX_STALL_EN BIT(6)
|
|
#define I3C_RX_DMA_EN BIT(5)
|
|
#define I3C_TX_DMA_EN BIT(4)
|
|
#define I3C_ADDR_OPT_EN BIT(2)
|
|
#define I3C_SLOW_BUS BIT(0)
|
|
|
|
/* I3C_MASTER_DEVICE_ADDR(0x8) */
|
|
|
|
#define I3C_DYNAMIC_ADDR_VALID BIT(31)
|
|
#define I3C_DYNAMIC_ADDR(x) (((x) << 16) & GENMASK(22, 16))
|
|
|
|
/* I3C_HC_CAPABILITIES(0xC) */
|
|
|
|
#define I3C_HDR_TS_EN BIT(7)
|
|
#define I3C_HDR_DDR_EN BIT(6)
|
|
#define I3C_NON_CURRENT_MASTER_CAP BIT(5)
|
|
#define I3C_AUTO_CMD BIT(3)
|
|
#define I3C_COMB_CMD BIT(2)
|
|
|
|
/* I3C_RESET_CTRL(0x10) */
|
|
|
|
#define I3C_IBI_QUEUE_RST BIT(5)
|
|
#define I3C_RX_FIFO_RST BIT(4)
|
|
#define I3C_TX_FIFO_RST BIT(3)
|
|
#define I3C_RESP_QUEUE_RST BIT(2)
|
|
#define I3C_CMD_QUEUE_RST BIT(1)
|
|
#define I3C_SOFT_RST BIT(0)
|
|
|
|
/* I3C_EXT_RESET_CTRL(0x21A0) */
|
|
|
|
#define I3C_SCL_LOW_FORCE BTT(8)
|
|
#define I3C_IBI_TX_FIFO_RST BIT(5)
|
|
|
|
/* I3C_PRESENT_STATE(0x14) */
|
|
|
|
#define I3C_CURRENT_MASTER BIT(2)
|
|
|
|
/* I3C_PRESENT_STATE_DEBUG(0x2030) */
|
|
|
|
#define I3C_CMD_TID_STATUS GENMASK(27, 24)
|
|
#define I3C_CM_TFR_ST_STATUS GENMASK(21, 16)
|
|
#define I3C_CM_TFR_STATUS GENMASK(13, 8)
|
|
#define I3C_SCL_LINE_SIGNAL_LV BIT(0)
|
|
#define I3C_SDA_LINE_SIGNAL_LV BIT(1)
|
|
|
|
|
|
/* I3C_PIO_INTR_STATUS(0x320) */
|
|
|
|
#define I3C_TRANSFER_ERR_STAT BIT(9)
|
|
#define I3C_TRANSFER_ABORT_STAT BIT(5)
|
|
#define I3C_RESP_READY_STAT BIT(4)
|
|
#define I3C_CMD_QUEUE_READY_STAT BIT(3)
|
|
#define I3C_IBI_THLD_STAT BIT(2)
|
|
#define I3C_RX_THLD_STAT BIT(1)
|
|
#define I3C_TX_THLD_STAT BIT(0)
|
|
|
|
/* I3C_EXT_PIO_INTR_STATUS(0x2184) */
|
|
#define I3C_SCL_LOW_FORCE_DONE_STAT BIT(31)
|
|
#define I3C_VGPIO_TX_PARITY_ERR_STAT BIT(22)
|
|
#define I3C_VGPIO_RX_STAT BIT(21)
|
|
#define I3C_VGPIO_TX_STAT BIT(20)
|
|
#define I3C_SLAVE_INT_ACKED BIT(18)
|
|
#define I3C_GETACCMST_STAT BIT(17)
|
|
#define I3C_BUS_AVAIL_STAT BIT(16)
|
|
#define I3C_BUS_IDLE_STAT BIT(15)
|
|
#define I3C_TRAILING_STAT BIT(12)
|
|
#define I3C_BUS_AVAIL_VIOLATION_STAT BIT(11)
|
|
#define I3C_BUS_IDLE_VIOLATION_STAT BIT(10)
|
|
|
|
#define I3C_INTR_ALL (I3C_RESP_READY_STAT | \
|
|
I3C_TRANSFER_ABORT_STAT | \
|
|
I3C_TRANSFER_ERR_STAT)
|
|
|
|
#define I3C_EXT_INTR_ALL (I3C_BUS_IDLE_VIOLATION_STAT | \
|
|
I3C_BUS_AVAIL_VIOLATION_STAT | \
|
|
I3C_TRAILING_STAT | \
|
|
I3C_BUS_IDLE_VIOLATION_STAT | \
|
|
I3C_BUS_AVAIL_VIOLATION_STAT | \
|
|
I3C_GETACCMST_STAT | \
|
|
I3C_SLAVE_INT_ACKED | \
|
|
I3C_VGPIO_TX_STAT | \
|
|
I3C_VGPIO_RX_STAT | \
|
|
I3C_VGPIO_TX_PARITY_ERR_STAT | \
|
|
I3C_SCL_LOW_FORCE_DONE_STAT)
|
|
|
|
/* I3C_PIO_INTR_EN(0x324) */
|
|
|
|
#define I3C_TRANSFER_ERR_STAT_EN BIT(9)
|
|
#define I3C_TRANSFER_ABORT_STAT_EN BIT(5)
|
|
#define I3C_RESP_READY_STAT_EN BIT(4)
|
|
#define I3C_CMD_QUEUE_READY_STAT_EN BIT(3)
|
|
#define I3C_IBI_THLD_STAT_EN BIT(2)
|
|
#define I3C_RX_THLD_STAT_EN BIT(1)
|
|
#define I3C_TX_THLD_STAT_EN BIT(0)
|
|
|
|
/* I3C_EXT_PIO_INTR_EN(0x2188) */
|
|
|
|
#define I3C_SCL_LOW_FORCE_DONE_STAT_EN BIT(31)
|
|
#define I3C_VGPIO_TX_PARITY_STAT_EN BIT(22)
|
|
#define I3C_VGPIO_RX_STAT_EN BIT(21)
|
|
#define I3C_VGPIO_TX_STAT_EN BIT(20)
|
|
#define I3C_SLAVE_INT_ACKED_EN BIT(18)
|
|
#define I3C_GETACCMST_STAT_EN BIT(17)
|
|
#define I3C_BUS_AVAIL_STAT_EN BIT(16)
|
|
#define I3C_BUS_IDLE_STAT_EN BIT(15)
|
|
#define I3C_TRAILING_STAT_EN BIT(12)
|
|
#define I3C_BUS_AVAIL_VIOLATION_STAT_EN BIT(11)
|
|
#define I3C_BUS_IDLE_VIOLATION_STAT_EN BIT(10)
|
|
|
|
/* I3C_PIO_INTR_SIGNAL_EN(0x328) */
|
|
|
|
#define I3C_TRANSFER_ERR_SIG_EN BIT(9)
|
|
#define I3C_TRANSFER_ABORT_SIG_EN BIT(5)
|
|
#define I3C_RESP_READY_SIG_EN BIT(4)
|
|
#define I3C_CMD_QUEUE_READY_SIG_EN BIT(3)
|
|
#define I3C_IBI_THLD_SIG_EN BIT(2)
|
|
#define I3C_RX_THLD_SIG_EN BIT(1)
|
|
#define I3C_TX_THLD_SIG_EN BIT(0)
|
|
|
|
|
|
/* I3C_EXT_PIO_INTR_SIGNAL_EN(0x218C) */
|
|
|
|
#define I3C_SCL_LOW_FORCE_DONE_SIG_EN BIT(31)
|
|
#define I3C_VGPIO_TX_PARITY_SIG_EN BIT(22)
|
|
#define I3C_VGPIO_RX_SIG_EN BIT(21)
|
|
#define I3C_VGPIO_TX_SIG_EN BIT(20)
|
|
#define I3C_SLAVE_INT_ACKED_SIG_EN BIT(18)
|
|
#define I3C_GETACCMST_SIG_EN BIT(17)
|
|
#define I3C_BUS_AVAIL_SIG_EN BIT(16)
|
|
#define I3C_BUS_IDLE_SIG_EN BIT(15)
|
|
#define I3C_TRAILING_SIG_EN BIT(12)
|
|
#define I3C_BUS_AVAIL_VIOLATION_SIG_EN BIT(11)
|
|
#define I3C_BUS_IDLE_VIOLATION_SIG_EN BIT(10)
|
|
|
|
|
|
/* I3C_PIO_INTR_FORCE(0x32C) */
|
|
|
|
#define I3C_TRANSFER_ERR_FORCE BIT(9)
|
|
#define I3C_TRANSFER_ABORT_FORCE BIT(5)
|
|
#define I3C_RESP_READY_FORCE BIT(4)
|
|
#define I3C_CMD_QUEU_READY_FORCE BIT(3)
|
|
#define I3C_IBI_THLD_FORCE BIT(2)
|
|
#define I3C_RX_THLD_FORCE BIT(1)
|
|
#define I3C_TX_THLD_FORCE BIT(0)
|
|
|
|
/* I3C_EXT_PIO_INTR_FORCE(0x2190) */
|
|
|
|
#define I3C_SCL_LOW_FORCE_DONE_FORCE BIT(31)
|
|
#define I3C_VGPIO_TX_PARITY_FORCE BIT(22)
|
|
#define I3C_VGPIO_RX_FORCE BIT(21)
|
|
#define I3C_VGPIO_TX_FORCE BIT(20)
|
|
#define I3C_SLAVE_INT_ACKED_FORCE BIT(18)
|
|
#define I3C_GETACCMST_FORCE BIT(17)
|
|
#define I3C_BUS_AVAIL_FORCE BIT(16)
|
|
#define I3C_BUS_IDLE_FORCE BIT(15)
|
|
#define I3C_TRAILING_FORCE BIT(12)
|
|
#define I3C_BUS_AVAIL_VIOLATION_FORCE BIT(11)
|
|
#define I3C_BUS_IDLE_VIOLATION_FORCE BIT(10)
|
|
|
|
/* I3C_QUEUE_THLD_CTRL(0x310) */
|
|
|
|
#define I3C_IBI_STAT_QUEUE_THLD(x) (((x) << 24) & GENMASK(31, 24))
|
|
#define IBI_DATA_THLD(x) (((x) << 16) & GENMASK(23, 16))
|
|
#define RESP_BUF_THLD(x) (((x) << 8) & GENMASK(15, 8))
|
|
#define RESP_BUF_THLD_MASK GENMASK(15, 8)
|
|
#define I3C_CMD_EMPTY_BUF_THLD(x) (((x) << 0) & GENMASK(7, 0))
|
|
|
|
/* I3C_DATA_BUFFER_THLD_CTRL(0x314) */
|
|
|
|
#define I3C_RX_START_THLD(x) (((x) << 24) & GENMASK(26, 24))
|
|
#define I3C_TX_START_THLD(x) (((x) << 16) & GENMASK(18, 16))
|
|
#define I3C_RX_BUF_THLD(x) (((x) << 8) & GENMASK(10, 8))
|
|
#define I3C_TX_BUF_THLD(x) (((x) << 0) & GENMASK(2, 0))
|
|
|
|
/* I3C_QUEUE_SIZE(0x318) */
|
|
|
|
#define I3C_TX_DATA_BUFF_SIZE GENMASK(31, 24)
|
|
#define I3C_RX_DATA_BUFF_SIZE GENMASK(23, 16)
|
|
#define I3C_IBI_STATUS_SIZE GENMASK(15, 8)
|
|
|
|
/* I3C_QUEUE_STATUS_LEVEL(0x2028) */
|
|
|
|
#define I3C_IBI_STAT_CNT_LEVEL GENMASK(28, 24)
|
|
#define I3C_IBI_BUF_LEVEL GENMASK(23, 16)
|
|
#define RESP_BUFFER_LEVEL GENMASK(15, 8)
|
|
#define I3C_CMD_QUEUE_FREE_LEVEL GENMASK(7, 0)
|
|
|
|
/* I3C_DATA_BUF_LEVEL(0x2304) */
|
|
|
|
#define I3C_TX_BUF_LEVEL GENMASK(20, 16)
|
|
|
|
/* I3C_DATA_BUF_STATUS_LEVEL(0x202C) */
|
|
|
|
#define I3C_RX_BUF_LVL GENMASK(15, 8)
|
|
#define I3C_TX_BUF_FREE_LVL GENMASK(7, 0)
|
|
|
|
/* I3C_IBI_NOTIFY_CTRL(0x58) */
|
|
|
|
#define I3C_NOTIFY_SIR_REJECTED BIT(3)
|
|
#define I3C_NOTIFY_MR_REJECTED BIT(1)
|
|
#define I3C_NOTIFY_HJ_REJECTED BIT(0)
|
|
|
|
/* I3C_SCL_QUARTER_PERIOD(0x21AC) */
|
|
|
|
#define I3C_FM GENMASK(31, 24)
|
|
#define I3C_FM_PLUS GENMASK(23, 16)
|
|
#define I3C_OPEN_DRAIN GENMASK(15, 8)
|
|
#define I3C_PUSH_PULL GENMASK(7, 0)
|
|
|
|
/* I3C_SCL_CTRL(0x21B0) */
|
|
|
|
#define I3C_PP_HCNT GENMASK(7, 0)
|
|
|
|
/* I3C_TSU_STA(0x21B4) */
|
|
|
|
#define I3C_FM_tSU_STA GENMASK(23, 16)
|
|
#define I3C_FM_PLUS_tSU_STA GENMASK(15, 8)
|
|
#define I3C_tCBSr GENMASK(7, 0)
|
|
|
|
/* I3C_TCBP(0x21B0) */
|
|
|
|
#define I3C_TCBP_MIXED GENMASK(23, 16)
|
|
#define I3C_TCBP_PURE GENMASK(7, 0)
|
|
|
|
/* I3C_TLOW(0x21BC) */
|
|
|
|
#define I3C_FM_tLOW GENMASK(24, 16)
|
|
#define I3C_FM_PLUS_tLOW GENMASK(15, 8)
|
|
#define I3C_tLOW_OD GENMASK(7, 0)
|
|
|
|
/* I3C_THD_STA(0x21C0) */
|
|
|
|
#define I3C_FM_tHD_STA GENMASK(23, 16)
|
|
#define I3C_FM_PLUS_tHD_STA GENMASK(15, 8)
|
|
#define I3C_tCAS GENMASK(7, 0)
|
|
|
|
/* I3C_TBUF(0x21C4) */
|
|
|
|
#define I3C_TBUF_MIXED GENMASK(24, 16)
|
|
#define I3C_TBUF_PURE GENMASK(8, 0)
|
|
|
|
/* I3C_BUS_CONDITION_CYCLE(0x21C8) */
|
|
|
|
#define I3C_BUS_AVAIL_CYCLE GENMASK(31, 24)
|
|
#define I3C_BUS_IDLE_CYCLE GENMASK(23, 0)
|
|
|
|
/* I3C_SAMPLE_CYCLE(0x21CC) */
|
|
|
|
#define I3C_HDR_TS_SDA_SAMPLE_CYCLE_FOR_OE GENMASK(13, 12)
|
|
#define I3C_HDR_TS_SCL_SAMPLE_CYCLE_FOR_OE GENMASK(9, 8)
|
|
#define I3C_HDR_TS_SAMPLE_CYCLE GENMASK(6, 4)
|
|
#define I3C_SDA_SAMPLE_CYCLE GENMASK(1, 0)
|
|
|
|
/* I3C_FILTER_CTRL(0x218D) */
|
|
|
|
#define I3C_FILTER_CYCLE_SDA GENMASK(23, 20)
|
|
#define I3C_FILTER_EN_SDA BIT(16)
|
|
#define I3C_FILTER_CYCLE_SCL GENMASK(7, 4)
|
|
#define I3C_FILTER_EN_SCL BIT(0)
|
|
|
|
/* I3C_VGPIO_TX_CONFIG(0x21E4) */
|
|
|
|
#define I3C_M_VGPIO_TX_DATA_LEN GENMASK(24, 23)
|
|
#define I3C_S_VGPIO_TX_DATA_LEN GENMASK(22, 21)
|
|
#define I3C_VGPIO_TX_DELAY_STEP GENMASK(20, 16)
|
|
#define I3C_VGPIO_TX_PARITY_ERR_IDX GENMASK(12, 8)
|
|
#define I3C_VGPIO_TX_CCC GENMASK(7, 0)
|
|
|
|
/* I3C_DEV_ADDR_TABLE(0x100 ~ )*/
|
|
|
|
#define I3C_DEVICE_TYPE BIT(31)
|
|
#define I3C_NACK_RETRY_CNT(x) (((x) << 29) & GENMASK(30, 29))
|
|
#define I3C_NACK_RETRY_CNT_MASK GENMASK(30, 29)
|
|
#define I3C_DYNAMIC_ADDRESS(x) (((x) << 16) & GENMASK(26, 16))
|
|
#define I3C_DYNAMIC_ADDRESS_MASK GENMASK(23, 16)
|
|
#define I3C_GET_DYNAMIC_ADDRESS(x) ((x) >> 16 & GENMASK(7, 0))
|
|
#define I3C_MR_REJECT BIT(14)
|
|
#define I3C_SIR_REJECT BIT(13)
|
|
#define I3C_IBI_PAYLOAD BIT(12)
|
|
#define I3C_STATIC_ADDRESS_MASK GENMASK(6, 0)
|
|
#define I3C_STATIC_ADDRESS(x) (((x) << 0) & GENMASK(6, 0))
|
|
|
|
/* I3C_EXT_DEV_ADDR_TABLE(0x2100 ~ )*/
|
|
#define I3C_IBI_PAYLOAD_SIZE(x) (((x) << 16) & GENMASK(23, 16))
|
|
#define I3C_IS_VGPIO_ALIVE BIT(12)
|
|
#define I3C_DYNAMIC_ADDR_ASSIGNED BIT(7)
|
|
|
|
/* I3C_DEV_CHAR_TABLE_1(0x204 ~ ) */
|
|
|
|
#define I3C_PID_1 GENMASK(31, 0)
|
|
|
|
/* I3C_DEV_CHAR_TABLE_1(0x204 ~ ) */
|
|
|
|
#define I3C_PID_0 GENMASK(15, 0)
|
|
|
|
/* I3C_DEV_CHAR_TABLE_2(0x208 ~ */
|
|
|
|
#define I3C_DCR GENMASK(15, 8)
|
|
#define I3C_BCR GENMASK(7, 0)
|
|
|
|
/* I3C_DEV_CHAR_TABLE_3(0x20C ~ */
|
|
|
|
#define I3C_CHAR_DYNAMIC_ADDR GENMASK(7, 0)
|
|
|
|
/* I3C_CMD_QUEUE(0x300) */
|
|
|
|
#define I3C_TOC BIT(31)
|
|
#define I3C_ROC BIT(30)
|
|
#define I3C_RNW BIT(29)
|
|
#define I3C_CP BIT(15)
|
|
|
|
#define I3C_DEV_COUNT(x) (((x) << 26) & GENMASK(29, 26))
|
|
#define I3C_MODE(x) (((x) << 26) & GENMASK(28, 26))
|
|
#define I3C_BYTE_CNT(x) (((x) << 23) & GENMASK(25, 23))
|
|
#define I3C_DEVICE_INDEX(x) (((x) << 16) & GENMASK(20, 16))
|
|
#define I3C_CMD_CODE(x) (((x) << 7) & GENMASK(14, 7))
|
|
#define I3C_CMD_TID(x) (((x) << 3) & GENMASK(6, 3))
|
|
#define I3C_CMD_ATTR(x) ((x) & GENMASK(2, 0))
|
|
#define I3C_CMD_ATTR_R 0
|
|
#define I3C_CMD_ATTR_IM 1
|
|
#define I3C_CMD_ATTR_A 2
|
|
#define I3C_CMD_ATTR_C 3
|
|
#define I3C_CMD_ATTR_IC 7
|
|
|
|
//#define I3C_HDR_DDR BIT(1)
|
|
//#define I3C_HDR_TS BIT(2)
|
|
#define I3C_CMD_MAX_LEN (127)
|
|
|
|
|
|
/* I3C_HDR_DDR_RD_ABORT(0x2214) */
|
|
#define HDR_EXIT BIT(31)
|
|
#define HDR_DDR_RD_ABORT BIT(0)
|
|
|
|
/* I3C_RESP_QUEUE(0x304) */
|
|
#define RESP_ERR_STAT(x) (((x) & GENMASK(31, 28)) >> 28)
|
|
#define RESP_CMDID(x) (((x) & GENMASK(27, 24)) >> 24)
|
|
#define RESP_DATA_LENGTH(x) ((x) & GENMASK(15, 0))
|
|
|
|
/* I3C_IBI_QUEUE */
|
|
|
|
#define I3C_IBI_STAT BIT(0)
|
|
#define I3C_DATA_LENGTH(x) ((x) & 0xff)
|
|
#define I3C_IBI_ID(x) (((x) >> 8) & 0xff)
|
|
#define I3C_IBI_ADDR(x) (((x) >> 9) & 0x7f)
|
|
#define I3C_IBI_RNW(x) (((x) >> 8) & 0x1)
|
|
|
|
#define I3C_RX_CRC_ERROR 1
|
|
#define I3C_RX_PARITY_ERROR 2
|
|
#define I3C_RX_FRAME_ERROR 3
|
|
#define I3C_ADDR_HEADER_NACK_M2 4
|
|
#define I3C_NACK 5
|
|
#define I3C_OVERFLOW_OR_UNDERFLOW 6
|
|
#define I3C_ABORTED_BY_USING_ABORT 8
|
|
|
|
|
|
#define I3C_PURE_BUS 0
|
|
#define I3C_MIXED_FAST_BUS 2
|
|
#define I3C_MIXED_SLOW_BUS 3
|
|
|
|
#define CMD_QUEUE_DEPTH 4
|
|
#define RESP_STAT_QUEUE_DEPTH 4
|
|
#define RX_FIFO_DEPTH 16
|
|
#define TX_FIFO_DEPTH 16
|
|
#define IBI_STAT_QUEUE_DEPTH 16
|
|
#define IBI_DATA_BUFFER_DEPTH 16
|
|
|
|
#define I3C_CCC 0
|
|
#define I3C_READ 1
|
|
#define I3C_WRITE 2
|
|
|
|
#define EXYNOS_I3C_TIMEOUT (msecs_to_jiffies(1000))
|
|
#define EXYNOS_FIFO_SIZE 32
|
|
#define DEFAULT_CLK 200000000
|
|
|
|
#define RX_FRAME_ERROR_HDR_DDR 0
|
|
#define RX_CRC_ERROR_HDR_DDR 1
|
|
#define RX_PARITY_ERROR_HDR 2
|
|
#define TX_DATA_MISMATCH_M1 3
|
|
#define ADDR_HEADER_NACK_M2 4
|
|
#define ADDR_NACK 5
|
|
#define SYMBOL_2_ERROR_HDR_TS 6
|
|
#define ABORT 7
|
|
#define RX_OVERFLOW_TX_UNDERFLOW 8
|
|
|
|
#define I3C_OPEN_DRAIN_CLOCK 1000000
|
|
|
|
static void exynos_i3c_hci_master_enable(struct exynos_i3c_hci_master *master);
|
|
static int exynos_i3c_hci_master_disable(struct exynos_i3c_hci_master *master);
|
|
|
|
static inline void dump_i3c_dat(struct exynos_i3c_hci_master *i3c)
|
|
{
|
|
dev_info(i3c->dev, "register DAT\n"
|
|
"DAT0 0x%08x "
|
|
"DAT1 0x%08x "
|
|
"DAT2 0x%08x "
|
|
"DAT3 0x%08x\n "
|
|
"DAT4 0x%08x "
|
|
"DAT5 0x%08x "
|
|
"DAT6 0x%08x "
|
|
"DAT7 0x%08x\n "
|
|
"EXTDAT0 0x%08x "
|
|
"EXTDAT1 0x%08x "
|
|
"EXTDAT2 0x%08x "
|
|
"EXTDAT3 0x%08x\n "
|
|
"EXTDAT4 0x%08x "
|
|
"EXTDAT5 0x%08x "
|
|
"EXTDAT6 0x%08x "
|
|
"EXTDAT7 0x%08x\n "
|
|
"free_rr_slots 0x%08x "
|
|
, readl(i3c->regs + I3C_DEV_ADDR_TABLE(0))
|
|
, readl(i3c->regs + I3C_DEV_ADDR_TABLE(1))
|
|
, readl(i3c->regs + I3C_DEV_ADDR_TABLE(2))
|
|
, readl(i3c->regs + I3C_DEV_ADDR_TABLE(3))
|
|
, readl(i3c->regs + I3C_DEV_ADDR_TABLE(4))
|
|
, readl(i3c->regs + I3C_DEV_ADDR_TABLE(5))
|
|
, readl(i3c->regs + I3C_DEV_ADDR_TABLE(6))
|
|
, readl(i3c->regs + I3C_DEV_ADDR_TABLE(7))
|
|
, readl(i3c->regs + I3C_EXT_DEV_ADDR_TABLE(0))
|
|
, readl(i3c->regs + I3C_EXT_DEV_ADDR_TABLE(1))
|
|
, readl(i3c->regs + I3C_EXT_DEV_ADDR_TABLE(2))
|
|
, readl(i3c->regs + I3C_EXT_DEV_ADDR_TABLE(3))
|
|
, readl(i3c->regs + I3C_EXT_DEV_ADDR_TABLE(4))
|
|
, readl(i3c->regs + I3C_EXT_DEV_ADDR_TABLE(5))
|
|
, readl(i3c->regs + I3C_EXT_DEV_ADDR_TABLE(6))
|
|
, readl(i3c->regs + I3C_EXT_DEV_ADDR_TABLE(7))
|
|
, i3c->free_rr_slots
|
|
);
|
|
}
|
|
|
|
static inline void dump_i3c_register(struct exynos_i3c_hci_master *i3c)
|
|
{
|
|
dev_info(i3c->dev, "register dump(suspended : %d)\n"
|
|
"HC_CONTROL 0x%08x "
|
|
"MASTER_DEVICE_ADDR 0x%08x "
|
|
"RESET_CTRL 0x%08x "
|
|
"PRESENT_STATE 0x%08x \n"
|
|
"INT_STATUS 0x%08x "
|
|
"INT_EN 0x%08x "
|
|
"INT_SIGNAL_EN 0x%08x "
|
|
"QUEUE_THLD_CTRL 0x%08x \n"
|
|
"DATA_BUFFER_THLD_CTRL 0x%08x "
|
|
"QUEUE_STATUS_LV 0x%08x "
|
|
"DATA_BUF_LEVEL 0x%08x "
|
|
"IBI_QUEUE_CTRL 0x%08x \n"
|
|
"TRAILING_CYCLE 0x%08x "
|
|
"SCL_QUARTER_PERIOD 0x%08x "
|
|
"SCL_CTRL 0x%08x "
|
|
"TSU_STA 0x%08x \n"
|
|
"TCBP 0x%08x "
|
|
"TLOW 0x%08x "
|
|
"THD_STA 0x%08x "
|
|
"TBUF 0x%08x \n"
|
|
"BUS_CONDITION_CYCLE 0x%08x "
|
|
"SAMPLE_CYCLE 0x%08x "
|
|
"FILTER_CTRL 0x%08x "
|
|
"PRESENT_STATE_DEBUG 0x%08x \n"
|
|
, i3c->suspended
|
|
, readl(i3c->regs + I3C_HC_CONTROL)
|
|
, readl(i3c->regs + I3C_MASTER_DEVICE_ADDR)
|
|
, readl(i3c->regs + I3C_RESET_CTRL)
|
|
, readl(i3c->regs + I3C_PRESENT_STATE)
|
|
, readl(i3c->regs + I3C_PIO_INTR_STATUS)
|
|
, readl(i3c->regs + I3C_PIO_INTR_EN)
|
|
, readl(i3c->regs + I3C_PIO_INTR_SIGNAL_EN)
|
|
, readl(i3c->regs + I3C_QUEUE_THLD_CTRL)
|
|
, readl(i3c->regs + I3C_DATA_BUFFER_THLD_CTRL)
|
|
, readl(i3c->regs + I3C_QUEUE_STATUS_LEVEL)
|
|
, readl(i3c->regs + I3C_DATA_BUFFER_THLD_STATUS_LEVEL)
|
|
, readl(i3c->regs + I3C_IBI_NOTIFY_CTRL)
|
|
, readl(i3c->regs + I3C_TRAILING_CYCLE)
|
|
, readl(i3c->regs + I3C_SCL_QUARTER_PERIOD)
|
|
, readl(i3c->regs + I3C_SCL_CTRL)
|
|
, readl(i3c->regs + I3C_TSU_STA)
|
|
, readl(i3c->regs + I3C_TCBP)
|
|
, readl(i3c->regs + I3C_TLOW)
|
|
, readl(i3c->regs + I3C_THD_STA)
|
|
, readl(i3c->regs + I3C_TBUF)
|
|
, readl(i3c->regs + I3C_BUS_CONDITION_CYCLE)
|
|
, readl(i3c->regs + I3C_SAMPLE_CYCLE)
|
|
, readl(i3c->regs + I3C_FILTER_CTRL)
|
|
, readl(i3c->regs + I3C_PRESENT_STATE_DEBUG)
|
|
);
|
|
dump_i3c_dat(i3c);
|
|
}
|
|
|
|
static void exynos_i3c_hci_master_wr_to_tx_fifo(struct exynos_i3c_hci_master *master,
|
|
const u8 *tx_buf, int nbytes)
|
|
{
|
|
writesl(master->regs + I3C_XFER_DATA_BUF, tx_buf, nbytes / 4);
|
|
if (nbytes & 3) {
|
|
u32 tmp = 0;
|
|
|
|
memcpy(&tmp, tx_buf + (nbytes & ~3), nbytes & 3);
|
|
writesl(master->regs + I3C_XFER_DATA_BUF, &tmp, 1);
|
|
}
|
|
}
|
|
|
|
static void exynos_i3c_hci_master_rd_from_rx_fifo(struct exynos_i3c_hci_master *master,
|
|
u8 *rx_buf, int nbytes)
|
|
{
|
|
if (nbytes == 0)
|
|
return;
|
|
|
|
readsl(master->regs + I3C_XFER_DATA_BUF, rx_buf, nbytes / 4);
|
|
if (nbytes & 3) {
|
|
u32 tmp;
|
|
|
|
readsl(master->regs + I3C_XFER_DATA_BUF, &tmp, 1);
|
|
memcpy(rx_buf + (nbytes & ~3), &tmp, nbytes & 3);
|
|
}
|
|
}
|
|
|
|
static void exynos_i3c_hci_master_start_xfer_locked(struct exynos_i3c_hci_master *master)
|
|
{
|
|
struct exynos_i3c_hci_xfer *xfer = master->xfer_queue.xfer;
|
|
unsigned int i;
|
|
unsigned int queue_thld;
|
|
|
|
dev_dbg(master->dev, "Start %s\n", __func__);
|
|
|
|
if (!xfer)
|
|
return;
|
|
|
|
for (i = 0; i < xfer->ncmds; i++) {
|
|
struct exynos_i3c_hci_cmd *cmd = &xfer->cmds[i];
|
|
|
|
exynos_i3c_hci_master_wr_to_tx_fifo(master, cmd->tx_buf,
|
|
cmd->tx_len);
|
|
}
|
|
|
|
writel(I3C_RESP_READY_STAT_EN | I3C_TRANSFER_ERR_STAT_EN | I3C_TRANSFER_ABORT_STAT_EN | I3C_IBI_THLD_STAT_EN, master->regs + I3C_PIO_INTR_EN);
|
|
writel(I3C_RESP_READY_STAT_EN | I3C_TRANSFER_ERR_STAT_EN | I3C_TRANSFER_ABORT_STAT_EN | I3C_IBI_THLD_STAT_EN, master->regs + I3C_PIO_INTR_SIGNAL_EN);
|
|
|
|
queue_thld = readl(master->regs + I3C_QUEUE_THLD_CTRL);
|
|
queue_thld &= ~RESP_BUF_THLD_MASK;
|
|
queue_thld |= RESP_BUF_THLD(xfer->ncmds);
|
|
writel(queue_thld, master->regs + I3C_QUEUE_THLD_CTRL);
|
|
|
|
for (i = 0; i < xfer->ncmds; i++) {
|
|
struct exynos_i3c_hci_cmd *cmd = &xfer->cmds[i];
|
|
|
|
writel(cmd->cmd | I3C_CMD_TID(i),
|
|
master->regs + I3C_CMD_QUEUE);
|
|
/* For Address Assign command, do not send data length */
|
|
if(I3C_CMD_ATTR(cmd->cmd) == I3C_CMD_ATTR_A)
|
|
continue;
|
|
if(cmd->cmd & I3C_RNW) {
|
|
writel(cmd->rx_len << 16,
|
|
master->regs + I3C_CMD_QUEUE);
|
|
}
|
|
else{
|
|
writel(cmd->tx_len << 16,
|
|
master->regs + I3C_CMD_QUEUE);
|
|
}
|
|
}
|
|
|
|
dev_dbg(master->dev, "End %s\n", __func__);
|
|
}
|
|
|
|
static void exynos_i3c_hci_master_unqueue_xfer_locked(struct exynos_i3c_hci_master *master,
|
|
struct exynos_i3c_hci_xfer *xfer)
|
|
{
|
|
if (master->xfer_queue.xfer == xfer) {
|
|
exynos_i3c_hci_master_disable(master);
|
|
master->xfer_queue.xfer = NULL;
|
|
writel(I3C_CMD_QUEUE_RST | I3C_RESP_QUEUE_RST | I3C_TX_FIFO_RST |
|
|
I3C_RX_FIFO_RST,
|
|
master->regs + I3C_RESET_CTRL);
|
|
writel(0, master->regs + I3C_PIO_INTR_EN);
|
|
exynos_i3c_hci_master_enable(master);
|
|
} else {
|
|
list_del_init(&xfer->node);
|
|
}
|
|
|
|
}
|
|
|
|
static void exynos_i3c_hci_master_unqueue_xfer(struct exynos_i3c_hci_master *master,
|
|
struct exynos_i3c_hci_xfer *xfer)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&master->xfer_queue.lock, flags);
|
|
exynos_i3c_hci_master_unqueue_xfer_locked(master, xfer);
|
|
spin_unlock_irqrestore(&master->xfer_queue.lock, flags);
|
|
}
|
|
|
|
static void exynos_i3c_hci_master_end_xfer_locked(struct exynos_i3c_hci_master *master,
|
|
u32 isr)
|
|
{
|
|
struct exynos_i3c_hci_xfer *xfer = master->xfer_queue.xfer;
|
|
int i, ret = 0;
|
|
struct exynos_i3c_hci_cmd *cmd;
|
|
unsigned int queue_status;
|
|
unsigned int rx_len;
|
|
unsigned int resp;
|
|
|
|
dev_dbg(master->dev, "Start %s\n", __func__);
|
|
|
|
if (!xfer)
|
|
return;
|
|
|
|
/* TODO : Dequeueing RESP until RESP is EMPTY because there are multiple cmd in one Xfer */
|
|
for (queue_status = readl(master->regs + I3C_QUEUE_STATUS_LEVEL);
|
|
(queue_status & RESP_BUFFER_LEVEL);
|
|
queue_status = readl(master->regs + I3C_QUEUE_STATUS_LEVEL)) {
|
|
resp = readl(master->regs + I3C_RESP_QUEUE);
|
|
|
|
cmd = &xfer->cmds[RESP_CMDID(resp)];
|
|
dev_dbg(master->dev, "RESP 0x%08x\n", resp);
|
|
|
|
rx_len = min_t(unsigned int, RESP_DATA_LENGTH(resp), cmd->rx_len);
|
|
|
|
cmd->error = RESP_ERR_STAT(resp);
|
|
if (cmd->rx_len && !cmd->error)
|
|
exynos_i3c_hci_master_rd_from_rx_fifo(master, cmd->rx_buf, rx_len);
|
|
}
|
|
|
|
for (i = 0; i < xfer->ncmds; i++) {
|
|
unsigned int err = xfer->cmds[i].error;
|
|
if (!err)
|
|
continue;
|
|
|
|
dev_err(master->dev, "CMD[%d] Error detected: 0x%08x\n", i, err);
|
|
switch(err){
|
|
case I3C_NACK:
|
|
dev_err(master->dev, "Error: NOACK\n");
|
|
ret = -EIO;
|
|
break;
|
|
case I3C_RX_FRAME_ERROR:
|
|
dev_err(master->dev, "Error: I3C_HDR_DDR_RX_FRAME_ERROR\n");
|
|
ret = -EIO;
|
|
break;
|
|
case I3C_RX_CRC_ERROR:
|
|
dev_err(master->dev, "Error: I3C_HDR_DDR_RX_CRC_ERROR\n");
|
|
ret = -EIO;
|
|
break;
|
|
case I3C_RX_PARITY_ERROR:
|
|
dev_err(master->dev, "Error: I3C_HDR_RX_PARITY_ERROR\n");
|
|
ret = -EIO;
|
|
break;
|
|
case I3C_ADDR_HEADER_NACK_M2:
|
|
dev_err(master->dev, "Error: I3C_ADDR_HEADER_NOACK_M2_ERROR\n");
|
|
ret = -EINVAL;
|
|
break;
|
|
case I3C_ABORTED_BY_USING_ABORT:
|
|
dev_err(master->dev, "Error: I3C_ABORTED_BY_USING_ABORT\n");
|
|
ret = -EIO;
|
|
break;
|
|
case I3C_OVERFLOW_OR_UNDERFLOW:
|
|
dev_err(master->dev, "Error: I3C_OVERFLOW_OR_UNDERFLOW\n");
|
|
ret = -ENOSPC;
|
|
break;
|
|
default:
|
|
dev_err(master->dev, "Error: Reserved or Not defined\n");
|
|
ret = -EINVAL;
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
xfer->ret = ret;
|
|
complete(&xfer->comp);
|
|
|
|
if (ret < 0)
|
|
exynos_i3c_hci_master_unqueue_xfer_locked(master, xfer);
|
|
|
|
xfer = list_first_entry_or_null(&master->xfer_queue.list,
|
|
struct exynos_i3c_hci_xfer, node);
|
|
if (xfer)
|
|
list_del_init(&xfer->node);
|
|
|
|
master->xfer_queue.xfer = xfer;
|
|
exynos_i3c_hci_master_start_xfer_locked(master);
|
|
dev_dbg(master->dev, "End %s\n", __func__);
|
|
}
|
|
|
|
static void exynos_i3c_hci_master_queue_xfer(struct exynos_i3c_hci_master *master,
|
|
struct exynos_i3c_hci_xfer *xfer)
|
|
{
|
|
unsigned long flags;
|
|
|
|
dev_dbg(master->dev, "Start %s\n", __func__);
|
|
|
|
init_completion(&xfer->comp);
|
|
spin_lock_irqsave(&master->xfer_queue.lock, flags);
|
|
if (master->xfer_queue.xfer) {
|
|
list_add_tail(&xfer->node, &master->xfer_queue.list);
|
|
} else {
|
|
master->xfer_queue.xfer = xfer;
|
|
exynos_i3c_hci_master_start_xfer_locked(master);
|
|
}
|
|
spin_unlock_irqrestore(&master->xfer_queue.lock, flags);
|
|
|
|
dev_dbg(master->dev, "End %s\n", __func__);
|
|
}
|
|
|
|
static inline struct exynos_i3c_hci_master *
|
|
to_exynos_i3c_hci_master(struct i3c_master_controller *master)
|
|
{
|
|
return container_of(master, struct exynos_i3c_hci_master, base);
|
|
}
|
|
|
|
static void exynos_i3c_hci_master_enable(struct exynos_i3c_hci_master *master)
|
|
{
|
|
writel(readl(master->regs + I3C_HC_CONTROL) | I3C_ENABLE, master->regs + I3C_HC_CONTROL);
|
|
}
|
|
|
|
static int exynos_i3c_hci_master_disable(struct exynos_i3c_hci_master *master)
|
|
{
|
|
u32 status;
|
|
|
|
writel(readl(master->regs + I3C_HC_CONTROL) & ~I3C_ENABLE, master->regs + I3C_HC_CONTROL);
|
|
|
|
return readl_poll_timeout_atomic(master->regs + I3C_PRESENT_STATE_DEBUG, status,
|
|
(status & I3C_CM_TFR_ST_STATUS) == 0, 10, 1000000);
|
|
}
|
|
|
|
static enum i3c_error_code exynos_i3c_hci_cmd_get_err(struct exynos_i3c_hci_cmd *cmd)
|
|
{
|
|
switch (cmd->error) {
|
|
case TX_DATA_MISMATCH_M1:
|
|
return I3C_ERROR_M1;
|
|
|
|
case ADDR_HEADER_NACK_M2:
|
|
return I3C_ERROR_M2;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return I3C_ERROR_UNKNOWN;
|
|
}
|
|
|
|
static struct exynos_i3c_hci_xfer* exynos_i3c_hci_master_alloc_xfer(struct exynos_i3c_hci_master *master, int nxfers)
|
|
{
|
|
struct exynos_i3c_hci_xfer *xfer;
|
|
|
|
xfer = kzalloc(struct_size(xfer, cmds, nxfers), GFP_KERNEL);
|
|
|
|
if (!xfer)
|
|
return NULL;
|
|
|
|
INIT_LIST_HEAD(&xfer->node);
|
|
xfer->ncmds = nxfers;
|
|
xfer->ret = -ETIMEDOUT;
|
|
|
|
return xfer;
|
|
}
|
|
|
|
static void exynos_i3c_hci_master_free_xfer(struct exynos_i3c_hci_xfer *xfer)
|
|
{
|
|
kfree(xfer);
|
|
}
|
|
|
|
static int exynos_i3c_hci_master_get_free_slot(struct exynos_i3c_hci_master *master)
|
|
{
|
|
if (!(master->free_rr_slots & GENMASK(master->maxdevs - 1, 0)))
|
|
return -ENOSPC;
|
|
|
|
return ffs(master->free_rr_slots) - 1;
|
|
}
|
|
|
|
static int exynos_i3c_hci_master_get_addr_slot(struct exynos_i3c_hci_master *master, u8 dyn_addr)
|
|
{
|
|
u32 rr0, rr1;
|
|
int i;
|
|
|
|
for (i = 0; i < master->maxdevs; i++) {
|
|
rr0 = readl(master->regs + I3C_DEV_ADDR_TABLE(i));
|
|
rr1 = readl(master->regs + I3C_EXT_DEV_ADDR_TABLE(i));
|
|
if (!(rr1 & I3C_DYNAMIC_ADDR_ASSIGNED))
|
|
continue;
|
|
|
|
if ((rr0 & I3C_DEVICE_TYPE) ||
|
|
I3C_GET_DYNAMIC_ADDRESS(rr0) != dyn_addr)
|
|
continue;
|
|
|
|
return i;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static void exynos_i3c_hci_master_upd_i3c_scl_lim(struct exynos_i3c_hci_master *master)
|
|
{
|
|
struct i3c_master_controller *m = &master->base;
|
|
// unsigned long i3c_lim_period, pres_step, ncycles, ipclk_rate;
|
|
unsigned long new_i3c_scl_lim = 0;
|
|
struct i3c_dev_desc *dev;
|
|
// u32 prescl1, ctrl;
|
|
|
|
/* TODO : i3c_master_get_bus로 전환 */
|
|
i3c_bus_for_each_i3cdev(&m->bus, dev) {
|
|
unsigned long max_fscl;
|
|
|
|
max_fscl = max(I3C_CCC_MAX_SDR_FSCL(dev->info.max_read_ds),
|
|
I3C_CCC_MAX_SDR_FSCL(dev->info.max_write_ds));
|
|
switch (max_fscl) {
|
|
case I3C_SDR1_FSCL_8MHZ:
|
|
max_fscl = 8000000;
|
|
break;
|
|
case I3C_SDR2_FSCL_6MHZ:
|
|
max_fscl = 6000000;
|
|
break;
|
|
case I3C_SDR3_FSCL_4MHZ:
|
|
max_fscl = 4000000;
|
|
break;
|
|
case I3C_SDR4_FSCL_2MHZ:
|
|
max_fscl = 2000000;
|
|
break;
|
|
case I3C_SDR0_FSCL_MAX:
|
|
default:
|
|
max_fscl = 0;
|
|
break;
|
|
}
|
|
|
|
if (max_fscl &&
|
|
(new_i3c_scl_lim > max_fscl || !new_i3c_scl_lim))
|
|
new_i3c_scl_lim = max_fscl;
|
|
}
|
|
|
|
/* Only update PRESCL_CTRL1 if the I3C SCL limitation has changed. */
|
|
if (new_i3c_scl_lim == master->i3c_scl_lim)
|
|
return;
|
|
master->i3c_scl_lim = new_i3c_scl_lim;
|
|
if (!new_i3c_scl_lim)
|
|
return;
|
|
|
|
/* Todo : set_timing
|
|
* We only use 200MHz ipclk and sfr reset value for test
|
|
* */
|
|
// ipclk_rate = clk_get_rate(master->ipclk);
|
|
|
|
/*
|
|
pres_step = ipclk_rate / (master->base.bus->scl_rate.i3c * 4);
|
|
|
|
prescl1 = readl(master->regs + PRESCL_CTRL1) &
|
|
~PRESCL_CTRL1_PP_LOW_MASK;
|
|
ctrl = readl(master->regs + CTRL);
|
|
|
|
i3c_lim_period = DIV_ROUND_UP(1000000000, master->i3c_scl_lim);
|
|
ncycles = DIV_ROUND_UP(i3c_lim_period, pres_step);
|
|
if (ncycles < 4)
|
|
ncycles = 0;
|
|
else
|
|
ncycles -= 4;
|
|
|
|
prescl1 |= PRESCL_CTRL1_PP_LOW(ncycles);
|
|
|
|
if (ctrl & CTRL_DEV_EN)
|
|
exynos_i3c_hci_master_disable(master);
|
|
|
|
writel(prescl1, master->regs + PRESCL_CTRL1);
|
|
|
|
if (ctrl & CTRL_DEV_EN)
|
|
exynos_i3c_hci_master_enable(master);
|
|
*/
|
|
}
|
|
|
|
static void exynos_i3c_hci_master_upd_i3c_addr(struct i3c_dev_desc *dev)
|
|
{
|
|
struct i3c_master_controller *m = i3c_dev_get_master(dev);
|
|
struct exynos_i3c_hci_master *master = to_exynos_i3c_hci_master(m);
|
|
struct exynos_i3c_hci_i2c_dev_data *data = i3c_dev_get_master_data(dev);
|
|
u32 addr;
|
|
u32 dat;
|
|
|
|
dev_dbg(master->dev, "Start %s\n", __func__);
|
|
addr = dev->info.dyn_addr ? dev->info.dyn_addr : dev->info.static_addr;
|
|
dat = readl(master->regs + I3C_DEV_ADDR_TABLE(data->id));
|
|
if (dev->info.dyn_addr) {
|
|
dat &= ~(I3C_DYNAMIC_ADDRESS_MASK | I3C_DEVICE_TYPE);
|
|
dat |= I3C_DYNAMIC_ADDRESS(addr);
|
|
writel(dat, master->regs + I3C_DEV_ADDR_TABLE(data->id));
|
|
} else {
|
|
dat &= ~(I3C_STATIC_ADDRESS_MASK | I3C_DEVICE_TYPE);
|
|
dat |= I3C_STATIC_ADDRESS(addr);
|
|
writel(dat, master->regs + I3C_DEV_ADDR_TABLE(data->id));
|
|
}
|
|
master->addrs[data->id] = addr;
|
|
dev_dbg(master->dev, "End %s\n", __func__);
|
|
}
|
|
|
|
static int exynos_i3c_hci_master_attach_i3c_dev(struct i3c_dev_desc *dev)
|
|
{
|
|
struct i3c_master_controller *m = i3c_dev_get_master(dev);
|
|
struct exynos_i3c_hci_master *master = to_exynos_i3c_hci_master(m);
|
|
struct exynos_i3c_hci_i2c_dev_data *data;
|
|
int slot;
|
|
|
|
dev_dbg(master->dev, "Start %s dyn_addr : 0x%02X\n", __func__, dev->info.dyn_addr);
|
|
|
|
slot = exynos_i3c_hci_master_get_free_slot(master);
|
|
if (slot < 0) {
|
|
dev_err(master->dev, "%s: failed to get slot\n", __func__);
|
|
return slot;
|
|
}
|
|
|
|
data = kzalloc(sizeof(*data), GFP_KERNEL);
|
|
if (!data) {
|
|
dev_err(master->dev, "%s: kzalloc fail\n", __func__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
data->ibi = -1;
|
|
data->id = slot;
|
|
master->addrs[slot] = dev->info.dyn_addr ? : dev->info.static_addr;
|
|
i3c_dev_set_master_data(dev, data);
|
|
master->free_rr_slots &= ~BIT(slot);
|
|
|
|
if (!dev->info.dyn_addr) {
|
|
exynos_i3c_hci_master_upd_i3c_addr(dev);
|
|
writel(readl(master->regs + I3C_EXT_DEV_ADDR_TABLE(data->id))
|
|
| I3C_DYNAMIC_ADDR_ASSIGNED,
|
|
master->regs + I3C_EXT_DEV_ADDR_TABLE(data->id));
|
|
}
|
|
|
|
dev_dbg(master->dev, "End %s\n", __func__);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void exynos_i3c_hci_master_detach_i3c_dev(struct i3c_dev_desc *dev)
|
|
{
|
|
struct i3c_master_controller *m = i3c_dev_get_master(dev);
|
|
struct exynos_i3c_hci_master *master = to_exynos_i3c_hci_master(m);
|
|
struct exynos_i3c_hci_i2c_dev_data *data = i3c_dev_get_master_data(dev);
|
|
|
|
writel(0, master->regs + I3C_DEV_CHAR_TABLE_0(data->id));
|
|
writel(0, master->regs + I3C_DEV_CHAR_TABLE_1(data->id));
|
|
writel(0, master->regs + I3C_DEV_CHAR_TABLE_2(data->id));
|
|
writel(0, master->regs + I3C_DEV_CHAR_TABLE_3(data->id));
|
|
|
|
i3c_dev_set_master_data(dev, NULL);
|
|
master->addrs[data->id] = 0;
|
|
master->free_rr_slots |= BIT(data->id);
|
|
kfree(data);
|
|
}
|
|
|
|
static int exynos_i3c_hci_master_attach_i2c_dev(struct i2c_dev_desc *dev)
|
|
{
|
|
struct i3c_master_controller *m = i2c_dev_get_master(dev);
|
|
struct exynos_i3c_hci_master *master = to_exynos_i3c_hci_master(m);
|
|
struct exynos_i3c_hci_i2c_dev_data *data;
|
|
int slot;
|
|
unsigned dat;
|
|
|
|
dev_dbg(master->dev, "Start %s i2c addr: 0x%02X\n", __func__, dev->boardinfo->base.addr);
|
|
|
|
slot = exynos_i3c_hci_master_get_free_slot(master);
|
|
if (slot < 0)
|
|
return slot;
|
|
|
|
data = kzalloc(sizeof(*data), GFP_KERNEL);
|
|
if (!data)
|
|
return -ENOMEM;
|
|
|
|
data->id = slot;
|
|
master->addrs[slot] = dev->boardinfo->base.addr;
|
|
master->free_rr_slots &= ~BIT(slot);
|
|
i2c_dev_set_master_data(dev, data);
|
|
|
|
/* exynos cannot support 10bit address of i2c normal i2c device */
|
|
|
|
/* Write DAT */
|
|
dat = I3C_DEVICE_TYPE |
|
|
I3C_NACK_RETRY_CNT(master->nack_retry_cnt) |
|
|
I3C_STATIC_ADDRESS(dev->boardinfo->base.addr);
|
|
dev_dbg(master->dev, "DAT(%d) : %lx\n", slot, dat);
|
|
writel(dat, master->regs + I3C_DEV_ADDR_TABLE(data->id));
|
|
|
|
master->i2c_slv_present = true;
|
|
|
|
dev_dbg(master->dev, "End %s\n", __func__);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void exynos_i3c_hci_master_detach_i2c_dev(struct i2c_dev_desc *dev)
|
|
{
|
|
struct i3c_master_controller *m = i2c_dev_get_master(dev);
|
|
struct exynos_i3c_hci_master *master = to_exynos_i3c_hci_master(m);
|
|
struct exynos_i3c_hci_i2c_dev_data *data = i2c_dev_get_master_data(dev);
|
|
|
|
writel(0, master->regs + I3C_DEV_ADDR_TABLE(data->id));
|
|
writel(0, master->regs + I3C_EXT_DEV_ADDR_TABLE(data->id));
|
|
writel(0, master->regs + I3C_DEV_CHAR_TABLE_0(data->id));
|
|
writel(0, master->regs + I3C_DEV_CHAR_TABLE_1(data->id));
|
|
writel(0, master->regs + I3C_DEV_CHAR_TABLE_2(data->id));
|
|
writel(0, master->regs + I3C_DEV_CHAR_TABLE_3(data->id));
|
|
|
|
i2c_dev_set_master_data(dev, NULL);
|
|
master->addrs[data->id] = 0;
|
|
master->free_rr_slots |= BIT(data->id);
|
|
kfree(data);
|
|
}
|
|
|
|
static int exynos_i3c_hci_master_do_daa(struct i3c_master_controller *m)
|
|
{
|
|
struct exynos_i3c_hci_master *master = to_exynos_i3c_hci_master(m);
|
|
u32 olddevs = 0, newdevs = 0;
|
|
int ret, slot;
|
|
u8 last_addr = 0;
|
|
u32 dat, exdat;
|
|
|
|
dev_dbg(master->dev, "Start %s\n", __func__);
|
|
|
|
for (slot = 0; slot < master->maxdevs; slot++) {
|
|
dat = readl(master->regs + I3C_DEV_ADDR_TABLE(slot));
|
|
exdat = readl(master->regs + I3C_EXT_DEV_ADDR_TABLE(slot));
|
|
/* skip i2c device */
|
|
if (dat & I3C_DEVICE_TYPE || exdat & I3C_DYNAMIC_ADDR_ASSIGNED)
|
|
olddevs |= BIT(slot);
|
|
}
|
|
|
|
master->dat_idx = master->maxdevs;
|
|
|
|
/* Prepare RR slots before launching DAA. */
|
|
for (slot = 0; slot < master->maxdevs; slot++) {
|
|
if (olddevs & BIT(slot))
|
|
continue;
|
|
|
|
ret = i3c_master_get_free_addr(m, last_addr + 1);
|
|
if (ret < 0) {
|
|
dev_err(master->dev, "%s: Failed to get free\n", __func__);
|
|
return -ENOSPC;
|
|
}
|
|
|
|
last_addr = ret;
|
|
master->addrs[slot] = last_addr;
|
|
if(master->dat_idx > slot)
|
|
master->dat_idx = slot;
|
|
writel(I3C_DYNAMIC_ADDRESS(last_addr) | I3C_NACK_RETRY_CNT(master->nack_retry_cnt),
|
|
master->regs + I3C_DEV_ADDR_TABLE(slot));
|
|
writel(0, master->regs + I3C_DEV_CHAR_TABLE_0(slot));
|
|
writel(0, master->regs + I3C_DEV_CHAR_TABLE_1(slot));
|
|
writel(0, master->regs + I3C_DEV_CHAR_TABLE_2(slot));
|
|
writel(0, master->regs + I3C_DEV_CHAR_TABLE_3(slot));
|
|
}
|
|
|
|
writel((master->dat_idx << 19), master->regs + I3C_DCT_SECTION_OFFSET);
|
|
|
|
ret = i3c_master_entdaa_locked(&master->base);
|
|
if (ret && ret != I3C_ERROR_M2) {
|
|
dev_err(master->dev, "%s: Failed to ENTDAA CCC\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
for (slot = 0; slot < master->maxdevs; slot++) {
|
|
if (readl(master->regs + I3C_EXT_DEV_ADDR_TABLE(slot)) & I3C_DYNAMIC_ADDR_ASSIGNED)
|
|
newdevs |= BIT(slot);
|
|
}
|
|
newdevs &= ~olddevs;
|
|
|
|
/*
|
|
* Clear all retaining registers filled during DAA. We already
|
|
* have the addressed assigned to them in the addrs array.
|
|
*/
|
|
for (slot = 0; slot < master->maxdevs; slot++) {
|
|
if (newdevs & BIT(slot))
|
|
i3c_master_add_i3c_dev_locked(m, master->addrs[slot]);
|
|
}
|
|
|
|
/*
|
|
* Clear slots that ended up not being used. Can be caused by I3C
|
|
* device creation failure or when the I3C device was already known
|
|
* by the system but with a different address (in this case the device
|
|
* already has a slot and does not need a new one).
|
|
*/
|
|
for (slot = 0; slot < master->maxdevs; slot++) {
|
|
dat = readl(master->regs + I3C_DEV_ADDR_TABLE(slot));
|
|
exdat = readl(master->regs + I3C_EXT_DEV_ADDR_TABLE(slot));
|
|
if (dat & I3C_DEVICE_TYPE)
|
|
continue;
|
|
if (master->free_rr_slots & BIT(slot) &&
|
|
(exdat & I3C_DYNAMIC_ADDR_ASSIGNED)) {
|
|
writel(0, master->regs + I3C_DEV_ADDR_TABLE(slot));
|
|
writel(0, master->regs + I3C_EXT_DEV_ADDR_TABLE(slot));
|
|
}
|
|
}
|
|
|
|
i3c_master_defslvs_locked(&master->base);
|
|
|
|
exynos_i3c_hci_master_upd_i3c_scl_lim(master);
|
|
|
|
/* Mask Hot-Join and Mastership request interrupts. */
|
|
i3c_master_enec_locked(m, I3C_BROADCAST_ADDR,
|
|
I3C_CCC_EVENT_HJ | I3C_CCC_EVENT_MR);
|
|
|
|
dev_dbg(master->dev, "End %s\n", __func__);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_OF
|
|
static int exynos_i3c_hci_parse_dt(struct exynos_i3c_hci_master *i3c, struct device *dev)
|
|
{
|
|
u32 temp;
|
|
|
|
/* Mode of operation High/Fast/Fast+ Speed mode */
|
|
if (of_property_read_u32(dev->of_node, "samsung,fs-clock", &i3c->fs_clock))
|
|
i3c->fs_clock = I3C_BUS_I2C_FM_SCL_RATE;
|
|
if (of_property_read_u32(dev->of_node, "samsung,fs-plus-clock", &i3c->fs_plus_clock))
|
|
i3c->fs_plus_clock = I3C_BUS_I2C_FM_PLUS_SCL_RATE;
|
|
if (of_property_read_u32(dev->of_node, "samsung,open-drain-clock", &i3c->open_drain_clock))
|
|
i3c->open_drain_clock = I3C_OPEN_DRAIN_CLOCK;
|
|
if (of_property_read_u32(dev->of_node, "samsung,push-pull-clock", &i3c->push_pull_clock))
|
|
i3c->push_pull_clock = I3C_BUS_TYP_I3C_SCL_RATE;
|
|
|
|
if (of_get_property(dev->of_node, "samsung,disable_hold_initiating_command", NULL))
|
|
i3c->hold_initiating_command = 0;
|
|
else
|
|
i3c->hold_initiating_command = 1;
|
|
|
|
if (of_property_read_u32(dev->of_node, "samsung,nack_retry_cnt", &temp))
|
|
i3c->nack_retry_cnt = 2;
|
|
else
|
|
i3c->nack_retry_cnt = temp;
|
|
|
|
if (of_get_property(dev->of_node, "dma-mode", NULL))
|
|
i3c->dma_mode = 1;
|
|
else
|
|
i3c->dma_mode = 0;
|
|
|
|
if (of_get_property(dev->of_node, "samsung,hot_join_nack", NULL))
|
|
i3c->hot_join_nack = 1;
|
|
else
|
|
i3c->hot_join_nack = 0;
|
|
|
|
if (of_get_property(dev->of_node, "samsung,mixed-fast", NULL))
|
|
i3c->i3c_bus_mode = I3C_MIXED_FAST_BUS;
|
|
else if (of_get_property(dev->of_node, "samsung,mixed-slow", NULL))
|
|
i3c->i3c_bus_mode = I3C_MIXED_SLOW_BUS;
|
|
else
|
|
i3c->i3c_bus_mode = I3C_PURE_BUS;
|
|
|
|
if (of_get_property(dev->of_node, "samsung,notify_sir_rejected", NULL))
|
|
i3c->notify_sir_rejected = 1;
|
|
else
|
|
i3c->notify_sir_rejected = 0;
|
|
|
|
if (of_get_property(dev->of_node, "samsung,notify_mr_rejected", NULL))
|
|
i3c->notify_mr_rejected = 1;
|
|
else
|
|
i3c->notify_mr_rejected = 0;
|
|
|
|
if (of_get_property(dev->of_node, "samsung,notify_hj_rejected", NULL))
|
|
i3c->notify_hj_rejected = 1;
|
|
else
|
|
i3c->notify_hj_rejected = 0;
|
|
|
|
if (of_get_property(dev->of_node, "samsung,mr_req_nack", NULL))
|
|
i3c->mr_req_nack = 1;
|
|
else
|
|
i3c->mr_req_nack = 0;
|
|
|
|
if (of_get_property(dev->of_node, "samsung,sir_req_nack", NULL))
|
|
i3c->sir_req_nack = 1;
|
|
else
|
|
i3c->sir_req_nack = 0;
|
|
|
|
if (of_get_property(dev->of_node, "samsung,filter_disable_sda", NULL))
|
|
i3c->filter_en_sda = 0;
|
|
else
|
|
i3c->filter_en_sda = 1;
|
|
|
|
if (of_get_property(dev->of_node, "samsung,filter_disable_scl", NULL))
|
|
i3c->filter_en_scl = 0;
|
|
else
|
|
i3c->filter_en_scl = 1;
|
|
|
|
if (of_get_property(dev->of_node, "samsung, iba_exclude", NULL))
|
|
i3c->iba_include = 0;
|
|
else
|
|
i3c->iba_include = 1;
|
|
|
|
/* EXT_DEVICE_CTRL */
|
|
if (of_get_property(dev->of_node, "samsung,hwacg_enable_s", NULL))
|
|
i3c->hwacg_enable_s = 1;
|
|
else
|
|
i3c->hwacg_enable_s = 0;
|
|
|
|
if (of_get_property(dev->of_node, "samsung,slave_mode", NULL))
|
|
i3c->slave_mode = 1;
|
|
else
|
|
i3c->slave_mode = 0;
|
|
|
|
if (of_get_property(dev->of_node, "samsung,data_swap", NULL))
|
|
i3c->data_swap = 1;
|
|
else
|
|
i3c->data_swap = 0;
|
|
|
|
if (of_get_property(dev->of_node, "samsung,tx_dma_free_run", NULL))
|
|
i3c->tx_dma_free_run = 1;
|
|
else
|
|
i3c->tx_dma_free_run = 0;
|
|
|
|
if (of_get_property(dev->of_node, "samsung,rx_stall_dis", NULL))
|
|
i3c->rx_stall_en = 0;
|
|
else
|
|
i3c->rx_stall_en = 1;
|
|
|
|
if (of_get_property(dev->of_node, "samsung,tx_stall_dis", NULL))
|
|
i3c->tx_stall_en = 0;
|
|
else
|
|
i3c->tx_stall_en = 1;
|
|
|
|
if (of_get_property(dev->of_node, "samsung,addr_opt_en", NULL))
|
|
i3c->addr_opt_en = 1;
|
|
else
|
|
i3c->addr_opt_en = 0;
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static bool exynos_i3c_hci_broadcast_ccc(const struct i3c_ccc_cmd *cmd)
|
|
{
|
|
switch (cmd->id) {
|
|
/* Broadcast CCC w/ extra data */
|
|
case I3C_CCC_ENEC(true):
|
|
fallthrough;
|
|
case I3C_CCC_DISEC(true):
|
|
fallthrough;
|
|
case I3C_CCC_SETMWL(true):
|
|
fallthrough;
|
|
case I3C_CCC_SETMRL(true):
|
|
fallthrough;
|
|
case I3C_CCC_DEFSLVS:
|
|
fallthrough;
|
|
case I3C_CCC_ENTHDR(0):
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static int exynos_i3c_hci_find_first_i3c_dev(struct exynos_i3c_hci_master *master,
|
|
int slot)
|
|
{
|
|
int i;
|
|
u32 reg;
|
|
|
|
for (i = slot; i < master->maxdevs; i++) {
|
|
reg = readl(master->regs + I3C_DEV_ADDR_TABLE(i));
|
|
if ((reg & I3C_DEVICE_TYPE) == 0)
|
|
return i;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int exynos_i3c_hci_send_ccc_cmd(struct i3c_master_controller *m,
|
|
struct i3c_ccc_cmd *cmd)
|
|
{
|
|
struct exynos_i3c_hci_master *master = to_exynos_i3c_hci_master(m);
|
|
struct exynos_i3c_hci_xfer *xfer;
|
|
struct exynos_i3c_hci_cmd *ccmd;
|
|
int ret;
|
|
int slot;
|
|
|
|
dev_dbg(master->dev, "Start %s\n", __func__);
|
|
dev_info(master->dev, "addr: 0x%02X CCC: 0x%02X\n", cmd->dests[0].addr, cmd->id);
|
|
|
|
xfer = exynos_i3c_hci_master_alloc_xfer(master, 1);
|
|
if (!xfer) {
|
|
dev_err(master->dev, "%s: failed to alloc xfer\n", __func__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ccmd = xfer->cmds;
|
|
ccmd->cmd = I3C_CMD_CODE(cmd->id) |
|
|
I3C_CP |
|
|
I3C_ROC |
|
|
I3C_TOC;
|
|
|
|
if (cmd->id == I3C_CCC_ENTDAA || cmd->id == I3C_CCC_SETDASA){
|
|
if (master->dat_idx < 0 || master->dat_idx >= master->maxdevs){
|
|
dev_err(master->dev, "Invalid DAT Index\n", master->dat_idx);
|
|
return -EINVAL;
|
|
}
|
|
ccmd->cmd |= I3C_DEVICE_INDEX(master->dat_idx);
|
|
ccmd->cmd |= I3C_DEV_COUNT(master->maxdevs - master->dat_idx);
|
|
ccmd->cmd |= I3C_CMD_ATTR(I3C_CMD_ATTR_A);
|
|
}
|
|
|
|
if (cmd->id & I3C_CCC_DIRECT) {
|
|
slot = exynos_i3c_hci_master_get_addr_slot(master, cmd->dests[0].addr);
|
|
if (slot < 0) {
|
|
dev_err(master->dev, "send CCC to invalid addr. ret:%d\n", slot);
|
|
return slot;
|
|
}
|
|
ccmd->cmd |= I3C_DEVICE_INDEX(slot);
|
|
}
|
|
|
|
if (exynos_i3c_hci_broadcast_ccc(cmd)) {
|
|
/*
|
|
* For a case that I2C and I3C slaves in same bus.
|
|
* To send Broadcast CCC w/ extra data, DEV_INDEX should be one of I3C devices.
|
|
* If DEV_INDEX is I2C device, master makes NOACK response.
|
|
*/
|
|
slot = exynos_i3c_hci_find_first_i3c_dev(master, 0);
|
|
ccmd->cmd |= I3C_DEVICE_INDEX(slot);
|
|
}
|
|
|
|
if (cmd->rnw) {
|
|
ccmd->cmd |= I3C_RNW;
|
|
ccmd->rx_buf = cmd->dests[0].payload.data;
|
|
ccmd->rx_len = cmd->dests[0].payload.len;
|
|
} else {
|
|
ccmd->tx_buf = cmd->dests[0].payload.data;
|
|
ccmd->tx_len = cmd->dests[0].payload.len;
|
|
}
|
|
|
|
exynos_i3c_hci_master_queue_xfer(master, xfer);
|
|
if (!wait_for_completion_timeout(&xfer->comp, msecs_to_jiffies(1000))) {
|
|
dev_err(master->dev, "I3C CCC Transfer timeout\n");
|
|
dump_i3c_register(master);
|
|
exynos_i3c_hci_master_unqueue_xfer(master, xfer);
|
|
}
|
|
|
|
ret = xfer->ret;
|
|
cmd->err = exynos_i3c_hci_cmd_get_err(&xfer->cmds[0]);
|
|
exynos_i3c_hci_master_free_xfer(xfer);
|
|
|
|
dev_dbg(master->dev, "End %s\n", __func__);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static bool exynos_i3c_hci_supports_ccc_cmd(struct i3c_master_controller *m,
|
|
const struct i3c_ccc_cmd *cmd)
|
|
{
|
|
|
|
if (cmd->ndests > 1)
|
|
return false;
|
|
|
|
switch (cmd->id) {
|
|
case I3C_CCC_ENEC(true):
|
|
case I3C_CCC_ENEC(false):
|
|
case I3C_CCC_DISEC(true):
|
|
case I3C_CCC_DISEC(false):
|
|
case I3C_CCC_ENTAS(0, true):
|
|
case I3C_CCC_ENTAS(0, false):
|
|
case I3C_CCC_RSTDAA(true):
|
|
case I3C_CCC_RSTDAA(false):
|
|
case I3C_CCC_ENTDAA:
|
|
case I3C_CCC_SETMWL(true):
|
|
case I3C_CCC_SETMWL(false):
|
|
case I3C_CCC_SETMRL(true):
|
|
case I3C_CCC_SETMRL(false):
|
|
case I3C_CCC_DEFSLVS:
|
|
case I3C_CCC_ENTHDR(0):
|
|
case I3C_CCC_SETDASA:
|
|
case I3C_CCC_SETNEWDA:
|
|
case I3C_CCC_GETMWL:
|
|
case I3C_CCC_GETMRL:
|
|
case I3C_CCC_GETPID:
|
|
case I3C_CCC_GETBCR:
|
|
case I3C_CCC_GETDCR:
|
|
case I3C_CCC_GETSTATUS:
|
|
case I3C_CCC_GETACCMST:
|
|
case I3C_CCC_GETMXDS:
|
|
case I3C_CCC_GETHDRCAP:
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static int exynos_i3c_hci_xfer_priv(struct i3c_dev_desc *dev,
|
|
struct i3c_priv_xfer *xfers,
|
|
int nxfers)
|
|
{
|
|
struct i3c_master_controller *m = i3c_dev_get_master(dev);
|
|
struct exynos_i3c_hci_master *master = to_exynos_i3c_hci_master(m);
|
|
int i = 0;
|
|
int ret = 0;
|
|
int txslots = 0, rxslots = 0;
|
|
struct exynos_i3c_hci_xfer *xfer;
|
|
int slot;
|
|
|
|
dev_dbg(master->dev, "Start %s\n", __func__);
|
|
dev_dbg(master->dev, "addr : 0x%02X\n", dev->info.dyn_addr);
|
|
|
|
if (!nxfers)
|
|
return 0;
|
|
|
|
if (nxfers > master->caps.cmdfifodepth)
|
|
return -ENOTSUPP;
|
|
|
|
/* max lenth config */
|
|
for (i = 0; i < nxfers; i++) {
|
|
if (xfers[i].len > I3C_CMD_MAX_LEN) {
|
|
dev_err(master->dev, "Xfer size %u is more than MAX_LEN %u\n",
|
|
xfers[i].len, I3C_CMD_MAX_LEN);
|
|
return -ENOTSUPP;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < nxfers; i++) {
|
|
if (xfers[i].rnw)
|
|
rxslots += DIV_ROUND_UP(xfers[i].len, 4);
|
|
else
|
|
txslots += DIV_ROUND_UP(xfers[i].len, 4);
|
|
}
|
|
if (rxslots > master->caps.rxfifodepth ||
|
|
txslots > master->caps.txfifodepth)
|
|
return -ENOTSUPP;
|
|
|
|
xfer = exynos_i3c_hci_master_alloc_xfer(master, nxfers);
|
|
if (!xfer) {
|
|
dev_err(master->dev, "%s: failed to alloc xfer\n", __func__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
for (i = 0; i < nxfers; i++) {
|
|
struct exynos_i3c_hci_cmd *ccmd = &xfer->cmds[i];
|
|
u32 pl_len = xfers[i].len;
|
|
|
|
slot = exynos_i3c_hci_master_get_addr_slot(master, dev->info.dyn_addr);
|
|
ccmd->cmd |= I3C_DEVICE_INDEX(slot);
|
|
ccmd->cmd |= I3C_BYTE_CNT(pl_len);
|
|
|
|
if (xfers[i].rnw) {
|
|
ccmd->cmd |= I3C_RNW;
|
|
ccmd->rx_buf = xfers[i].data.in;
|
|
ccmd->rx_len = xfers[i].len;
|
|
} else {
|
|
ccmd->tx_buf = xfers[i].data.out;
|
|
ccmd->tx_len = xfers[i].len;
|
|
}
|
|
|
|
ccmd->cmd |= I3C_ROC | I3C_CMD_TID(i);
|
|
if (i == (nxfers - 1))
|
|
ccmd->cmd |= I3C_TOC;
|
|
}
|
|
|
|
exynos_i3c_hci_master_queue_xfer(master, xfer);
|
|
if (!wait_for_completion_timeout(&xfer->comp, msecs_to_jiffies(1000))) {
|
|
dev_err(master->dev, "I3C priv_xfer Transfer timeout\n");
|
|
dump_i3c_register(master);
|
|
exynos_i3c_hci_master_unqueue_xfer(master, xfer);
|
|
}
|
|
|
|
ret = xfer->ret;
|
|
|
|
for (i = 0; i < nxfers; i++)
|
|
xfers[i].err = exynos_i3c_hci_cmd_get_err(&xfer->cmds[i]);
|
|
exynos_i3c_hci_master_free_xfer(xfer);
|
|
|
|
dev_dbg(master->dev, "End %s\n", __func__);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int exynos_i3c_hci_master_i2c_xfers(struct i2c_dev_desc *dev,
|
|
const struct i2c_msg *xfers, int nxfers)
|
|
{
|
|
struct i3c_master_controller *m = i2c_dev_get_master(dev);
|
|
struct exynos_i3c_hci_i2c_dev_data *data = i2c_dev_get_master_data(dev);
|
|
struct exynos_i3c_hci_master *master = to_exynos_i3c_hci_master(m);
|
|
unsigned int nrxwords = 0, ntxwords = 0;
|
|
struct exynos_i3c_hci_xfer *xfer;
|
|
int i, ret = 0;
|
|
int slot;
|
|
|
|
dev_dbg(master->dev, "Start %s\n", __func__);
|
|
dev_dbg(master->dev, "addr : 0x%02X\n", xfers[0].addr);
|
|
|
|
if (!nxfers)
|
|
return 0;
|
|
|
|
if (nxfers > master->caps.cmdfifodepth)
|
|
return -ENOTSUPP;
|
|
|
|
/* max length config */
|
|
for (i = 0; i < nxfers; i++) {
|
|
if (xfers[i].len > I3C_CMD_MAX_LEN)
|
|
return -ENOTSUPP;
|
|
}
|
|
|
|
for (i = 0; i < nxfers; i++) {
|
|
if (xfers[i].flags & I2C_M_RD)
|
|
nrxwords += DIV_ROUND_UP(xfers[i].len, 4);
|
|
else
|
|
ntxwords += DIV_ROUND_UP(xfers[i].len, 4);
|
|
}
|
|
|
|
if (ntxwords > master->caps.txfifodepth ||
|
|
nrxwords > master->caps.rxfifodepth)
|
|
return -ENOTSUPP;
|
|
|
|
xfer = exynos_i3c_hci_master_alloc_xfer(master, nxfers);
|
|
if (!xfer)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < nxfers; i++) {
|
|
struct exynos_i3c_hci_cmd *ccmd = &xfer->cmds[i];
|
|
|
|
slot = data->id;
|
|
ccmd->cmd |= I3C_DEVICE_INDEX(slot);
|
|
|
|
if (xfers[i].flags & I2C_M_RD) {
|
|
ccmd->cmd |= I3C_RNW;
|
|
ccmd->rx_buf = xfers[i].buf;
|
|
ccmd->rx_len = xfers[i].len;
|
|
} else {
|
|
ccmd->tx_buf = xfers[i].buf;
|
|
ccmd->tx_len = xfers[i].len;
|
|
}
|
|
ccmd->cmd |= I3C_CMD_TID(i) | I3C_ROC;
|
|
if (i == (nxfers - 1))
|
|
ccmd->cmd |= I3C_TOC;
|
|
|
|
dev_dbg(master->dev, "CMD %d 0x%x\n", i, ccmd->cmd);
|
|
}
|
|
|
|
/* Exclude 0x7e for i2c private transfers */
|
|
writel(readl(master->regs + I3C_HC_CONTROL) & ~I3C_IBA_INCLUDE, master->regs+I3C_HC_CONTROL);
|
|
exynos_i3c_hci_master_queue_xfer(master, xfer);
|
|
if (!wait_for_completion_timeout(&xfer->comp, msecs_to_jiffies(1000))) {
|
|
dev_err(master->dev, "I3C i2c xfer Transfer timeout\n");
|
|
dump_i3c_register(master);
|
|
exynos_i3c_hci_master_unqueue_xfer(master, xfer);
|
|
}
|
|
|
|
ret = xfer->ret;
|
|
exynos_i3c_hci_master_free_xfer(xfer);
|
|
writel(readl(master->regs + I3C_HC_CONTROL) | I3C_IBA_INCLUDE, master->regs+I3C_HC_CONTROL);
|
|
|
|
dev_dbg(master->dev, "End %s\n", __func__);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int exynos_i3c_hci_master_reattach_i3c_dev(struct i3c_dev_desc *dev,
|
|
u8 old_dyn_addr)
|
|
{
|
|
exynos_i3c_hci_master_upd_i3c_addr(dev);
|
|
return 0;
|
|
}
|
|
|
|
static int exynos_i3c_hci_master_clk_cfg(struct exynos_i3c_hci_master *master)
|
|
{
|
|
unsigned long ipclk_rate;
|
|
//u32 tbuf;
|
|
int ret;
|
|
__u32 fs_clock_div_quat;
|
|
__u32 fs_plus_clock_div_quat;
|
|
__u32 open_drain_clock_div_quat;
|
|
__u32 push_pull_clock_div_quat;
|
|
|
|
ipclk_rate = clk_get_rate(master->ipclk);
|
|
if (!ipclk_rate) {
|
|
dev_err(master->dev, "Failed to get clock %d\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ipclk_rate != DEFAULT_CLK) {
|
|
ret = clk_set_rate(master->ipclk, DEFAULT_CLK);
|
|
if (ret < 0) {
|
|
dev_err(master->dev, "Failed to set clock %d\n", DEFAULT_CLK);
|
|
return -EINVAL;
|
|
}
|
|
ipclk_rate = clk_get_rate(master->ipclk);
|
|
}
|
|
|
|
dev_info(master->dev, "ipclk: %luHz SCL_QUAR_PER 0x%x\n",
|
|
ipclk_rate, readl(master->regs + I3C_SCL_QUARTER_PERIOD));
|
|
|
|
/* Tbuf reset value 0x09 is too short to make STOP completely */
|
|
//tbuf = readl(master->regs + I3C_TBUF);
|
|
//writel((tbuf & ~0xff) | 0x30, master->regs + I3C_TBUF);
|
|
|
|
/* Todo : setting timing parameters */
|
|
/* But We have no calculating equiation
|
|
* We only use default reset value for 200MHz ipclk and 400KHz FM, 1MHz FM+ mode */
|
|
fs_clock_div_quat = ipclk_rate / master->fs_clock / 4;
|
|
fs_plus_clock_div_quat = ipclk_rate / master->fs_plus_clock / 4;
|
|
open_drain_clock_div_quat = ipclk_rate / master->open_drain_clock / 4;
|
|
push_pull_clock_div_quat = ipclk_rate / master->push_pull_clock / 4;
|
|
|
|
writel((fs_clock_div_quat & 0xff) << 24 | (fs_plus_clock_div_quat & 0xff) << 16 |
|
|
(open_drain_clock_div_quat & 0xff) << 8 | (push_pull_clock_div_quat & 0xff) << 0,
|
|
master->regs + I3C_SCL_QUARTER_PERIOD);
|
|
|
|
/* Todo : setting PP Low, TSU_STA, TCBP, TLOW, THD_STA, TBUF, BUS_AVAIL
|
|
* I3C_PP_HCNT = 2 * PP_CLK - 1
|
|
* writel((push_pull_clock_div_quat * 2 - 1) & 0xff, i3c->regs + I3C_SCL_CTRL);
|
|
* TSU_STA = 600ns in 400KHz, 260ns in 1MHz, tCBSr = 1/4T?
|
|
* TCBP_MIXED = tSU_STA in FM, TCBP_PURE = 1/2*tCBSr
|
|
* TLOW = 1300ns in 400KHz, 500ns in 1MHz, 200ns in OD
|
|
* THD = 600ns in 400KHz, 260ns in 1MHz, 38.4n in OD
|
|
* TBUF = 1300ns in 400Khz, 45ns in OD
|
|
* tAVAL = 1000ns in OD, tIDLE = 1ms in OD
|
|
*
|
|
*/
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void exynos_i3c_hci_master_config(struct exynos_i3c_hci_master *m)
|
|
{
|
|
u32 ctrl = readl(m->regs + I3C_HC_CONTROL);
|
|
/* clear optional bits */
|
|
ctrl &= (I3C_ENABLE | I3C_ABORT | I3C_HOT_JOIN_CTRL);
|
|
|
|
if (m->i2c_slv_present)
|
|
ctrl |= I3C_I2C_SLV_PRESENT;
|
|
if (m->iba_include)
|
|
ctrl |= I3C_IBA_INCLUDE;
|
|
|
|
writel(ctrl, m->regs + I3C_HC_CONTROL);
|
|
}
|
|
|
|
static void exynos_i3c_hci_master_extconfig(struct exynos_i3c_hci_master *m)
|
|
{
|
|
u32 ctrl = readl(m->regs + I3C_EXT_DEVICE_CTRL);
|
|
/* clear optional bits */
|
|
ctrl &= (I3C_VGPIO_TX_REQ_FORCE | I3C_IBI_REQ | I3C_HJ_REQ | I3C_MASTERSHIP_REQ);
|
|
|
|
if (!m->hwacg_enable_s)
|
|
ctrl |= I3C_HWACG_DISABLE_S;
|
|
if (m->notify_vgpio_rx)
|
|
ctrl |= I3C_NOTIFY_VGPIO_RX;
|
|
if (m->vgpio_en)
|
|
ctrl |= I3C_VGPIO_ENABLE;
|
|
if (m->slave_mode)
|
|
ctrl |= I3C_SLAVE_MODE;
|
|
if (m->data_swap)
|
|
ctrl |= I3C_DATA_SWAP;
|
|
if (m->getmxds_5byte)
|
|
ctrl |= I3C_GETMXDS_5BYTE;
|
|
if (m->tx_dma_free_run)
|
|
ctrl |= I3C_TX_DMA_FREE_RUN;
|
|
if (m->rx_stall_en)
|
|
ctrl |= I3C_RX_STALL_EN;
|
|
if (m->tx_stall_en)
|
|
ctrl |= I3C_TX_STALL_EN;
|
|
if (m->addr_opt_en)
|
|
ctrl |= I3C_ADDR_OPT_EN;
|
|
if (m->i3c_bus_mode == I3C_MIXED_SLOW_BUS)
|
|
ctrl |= I3C_SLOW_BUS;
|
|
|
|
writel(ctrl, m->regs + I3C_EXT_DEVICE_CTRL);
|
|
|
|
}
|
|
|
|
static int exynos_i3c_hci_master_bus_init(struct i3c_master_controller *m)
|
|
{
|
|
struct exynos_i3c_hci_master *master = to_exynos_i3c_hci_master(m);
|
|
struct i3c_device_info info = { };
|
|
u32 ctrl;
|
|
int ret;
|
|
unsigned int char_reg0, char_reg1;
|
|
// unsigned int queue_thld, buffer_thld;
|
|
|
|
dev_dbg(master->dev, "Start %s\n", __func__);
|
|
|
|
/* Setting I3C_DEVICE_CTRL
|
|
* For now, we set sfr reset value */
|
|
|
|
switch (m->bus.mode) {
|
|
case I3C_BUS_MODE_PURE:
|
|
ctrl = I3C_PURE_BUS;
|
|
break;
|
|
|
|
case I3C_BUS_MODE_MIXED_FAST:
|
|
ctrl = I3C_MIXED_FAST_BUS;
|
|
break;
|
|
|
|
case I3C_BUS_MODE_MIXED_SLOW:
|
|
ctrl = I3C_MIXED_SLOW_BUS;
|
|
break;
|
|
|
|
default:
|
|
dev_err(master->dev, "%s: Not configured bus mode\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
exynos_i3c_hci_master_extconfig(master);
|
|
|
|
/* Set Timing */
|
|
exynos_i3c_hci_master_clk_cfg(master);
|
|
|
|
writel(I3C_EXT_INTR_ALL, master->regs + I3C_EXT_PIO_INTR_STATUS);
|
|
writel(I3C_INTR_ALL, master->regs + I3C_PIO_INTR_STATUS);
|
|
writel(0, master->regs + I3C_PIO_INTR_EN);
|
|
writel(0, master->regs + I3C_EXT_PIO_INTR_EN);
|
|
writel(0, master->regs + I3C_PIO_INTR_SIGNAL_EN);
|
|
writel(0, master->regs + I3C_EXT_PIO_INTR_SIGNAL_EN);
|
|
|
|
/* Setting I3C_DEVICE_ADDR
|
|
* Get an address for the master. */
|
|
ret = i3c_master_get_free_addr(m, 0);
|
|
if (ret < 0) {
|
|
dev_err(master->dev, "%s: Failed to get free addr for MST\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
writel(I3C_DYNAMIC_ADDR_VALID | I3C_DYNAMIC_ADDR(ret),
|
|
master->regs + I3C_MASTER_DEVICE_ADDR);
|
|
|
|
info.dyn_addr = ret;
|
|
char_reg0 = readl(master->regs + I3C_CHAR_REG0);
|
|
char_reg1 = readl(master->regs + I3C_CHAR_REG1);
|
|
info.dcr = char_reg0 & 0xff;
|
|
info.bcr = (char_reg0 >> 8) & 0xff;
|
|
info.pid = (char_reg0 >> 16) & 0xffff;
|
|
info.pid |= (u64)char_reg0 << 16;
|
|
|
|
|
|
if (info.bcr & I3C_BCR_HDR_CAP)
|
|
info.hdr_cap = I3C_CCC_HDR_MODE(I3C_HDR_DDR);
|
|
|
|
ret = i3c_master_set_info(&master->base, &info);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* S/W Reset cannot be called to prevent clearing DAT */
|
|
writel(0x3e, master->regs + I3C_RESET_CTRL);
|
|
writel(0x120, master->regs + I3C_EXT_RESET_CTRL);
|
|
|
|
/*
|
|
* Enable Hot-Join, and, when a Hot-Join request happens, disable all
|
|
* events coming from this device.
|
|
*
|
|
* We will issue ENTDAA afterwards from the threaded IRQ handler.
|
|
*/
|
|
|
|
exynos_i3c_hci_master_config(master);
|
|
|
|
exynos_i3c_hci_master_enable(master);
|
|
|
|
dev_dbg(master->dev, "End %s\n", __func__);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void exynos_i3c_hci_store_dat(struct exynos_i3c_hci_master *master)
|
|
{
|
|
int i;
|
|
for(i = 0; i < MAX_DEVS ; i++){
|
|
master->DAT[i] = readl(master->regs + I3C_DEV_ADDR_TABLE(i));
|
|
master->EXT_DAT[i] = readl(master->regs + I3C_EXT_DEV_ADDR_TABLE(i));
|
|
master->DCT[i][0] = readl(master->regs + I3C_DEV_CHAR_TABLE_0(i));
|
|
master->DCT[i][1] = readl(master->regs + I3C_DEV_CHAR_TABLE_1(i));
|
|
master->DCT[i][2] = readl(master->regs + I3C_DEV_CHAR_TABLE_2(i));
|
|
master->DCT[i][3] = readl(master->regs + I3C_DEV_CHAR_TABLE_3(i));
|
|
}
|
|
}
|
|
|
|
static void exynos_i3c_hci_load_dat(struct exynos_i3c_hci_master *master)
|
|
{
|
|
int i;
|
|
for(i = 0; i < MAX_DEVS ; i++){
|
|
writel(master->DAT[i], master->regs + I3C_DEV_ADDR_TABLE(i));
|
|
writel(master->EXT_DAT[i], master->regs + I3C_EXT_DEV_ADDR_TABLE(i));
|
|
writel(master->DCT[i][0], master->regs + I3C_DEV_CHAR_TABLE_0(i));
|
|
writel(master->DCT[i][1], master->regs + I3C_DEV_CHAR_TABLE_1(i));
|
|
writel(master->DCT[i][2], master->regs + I3C_DEV_CHAR_TABLE_2(i));
|
|
writel(master->DCT[i][3],master->regs + I3C_DEV_CHAR_TABLE_3(i));
|
|
}
|
|
}
|
|
|
|
static int exynos_i3c_hci_runtime_suspend(struct device *dev)
|
|
{
|
|
dev_dbg(dev, "runtime suspending\n", __func__);
|
|
/*
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
struct exynos_i3c_hci_master *master = platform_get_drvdata(pdev);
|
|
|
|
clk_disable(master->ipclk);
|
|
clk_disable(master->pclk);
|
|
master->runtime_resumed = 0;
|
|
*/
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int exynos_i3c_hci_runtime_resume(struct device *dev)
|
|
{
|
|
/*
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
struct exynos_i3c_hci_master *master = platform_get_drvdata(pdev);
|
|
int ret = 0;
|
|
|
|
clk_enable(master->ipclk);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
clk_enable(master->pclk);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
master->runtime_resumed = 1;
|
|
*/
|
|
dev_dbg(dev, "runtime resumed\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
static int exynos_i3c_hci_suspend_noirq(struct device *dev)
|
|
{
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
struct exynos_i3c_hci_master *master = platform_get_drvdata(pdev);
|
|
|
|
dev_dbg(master->dev, "suspend_noirq\n", __func__);
|
|
|
|
master->current_master = readl(master->regs + I3C_MASTER_DEVICE_ADDR);
|
|
exynos_i3c_hci_store_dat(master);
|
|
/* S/W reset while entering suspend */
|
|
writel(I3C_SOFT_RST, master->regs + I3C_RESET_CTRL);
|
|
exynos_i3c_hci_master_disable(master);
|
|
|
|
clk_disable(master->ipclk);
|
|
clk_disable(master->gate_clk);
|
|
master->suspended = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int exynos_i3c_hci_resume_noirq(struct device *dev)
|
|
{
|
|
int ret = 0;
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
struct exynos_i3c_hci_master *master = platform_get_drvdata(pdev);
|
|
|
|
dev_dbg(master->dev, "resume_noirq\n", __func__);
|
|
|
|
ret = clk_enable(master->ipclk);
|
|
if (ret) {
|
|
dev_err(master->dev, "ipclk enable fail\n");
|
|
return ret;
|
|
}
|
|
ret = clk_enable(master->gate_clk);
|
|
if (ret) {
|
|
dev_err(master->dev, "gate_clk enable fail\n");
|
|
clk_disable(master->ipclk);
|
|
return ret;
|
|
}
|
|
writel(master->current_master, master->regs + I3C_MASTER_DEVICE_ADDR);
|
|
exynos_i3c_hci_load_dat(master);
|
|
|
|
exynos_i3c_hci_master_extconfig(master);
|
|
|
|
/* Set Timing */
|
|
exynos_i3c_hci_master_clk_cfg(master);
|
|
|
|
exynos_i3c_hci_master_config(master);
|
|
|
|
exynos_i3c_hci_master_enable(master);
|
|
master->suspended = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void exynos_i3c_hci_master_bus_cleanup(struct i3c_master_controller *m)
|
|
{
|
|
struct exynos_i3c_hci_master *master = to_exynos_i3c_hci_master(m);
|
|
|
|
exynos_i3c_hci_master_disable(master);
|
|
}
|
|
|
|
static void exynos_i3c_hci_master_handle_ibi(struct exynos_i3c_hci_master *master,
|
|
unsigned int ibiq)
|
|
{
|
|
struct exynos_i3c_hci_i2c_dev_data *data;
|
|
bool data_consumed = false;
|
|
struct i3c_ibi_slot *slot;
|
|
u32 addr = I3C_IBI_ADDR(ibiq);
|
|
struct i3c_dev_desc *dev;
|
|
size_t nbytes;
|
|
u8 *buf;
|
|
unsigned int i;
|
|
|
|
/*
|
|
* FIXME: maybe we should report the FIFO OVF errors to the upper
|
|
* layer.
|
|
* Todo: dynamic addr to ibi slot number
|
|
*/
|
|
for (i = 0; i < master->ibi.num_slots; i++) {
|
|
dev = master->ibi.slots[i];
|
|
if (dev && dev->info.dyn_addr == addr)
|
|
break;
|
|
}
|
|
if (i == master->ibi.num_slots) {
|
|
dev_err(master->dev, "Invalid ibi slot\n");
|
|
return;
|
|
}
|
|
|
|
if (!dev) {
|
|
dev_err(master->dev, "ibi slot(%x) not found\n", addr);
|
|
return;
|
|
}
|
|
|
|
spin_lock(&master->ibi.lock);
|
|
|
|
data = i3c_dev_get_master_data(dev);
|
|
slot = i3c_generic_ibi_get_free_slot(data->ibi_pool);
|
|
if (!slot)
|
|
goto out_unlock;
|
|
|
|
buf = slot->data;
|
|
|
|
nbytes = I3C_DATA_LENGTH(ibiq);
|
|
for (i = 0; i < nbytes; i++) {
|
|
buf[i] = readl(master->regs + I3C_IBI_PORT);
|
|
}
|
|
|
|
slot->len = min_t(unsigned int, I3C_DATA_LENGTH(ibiq),
|
|
dev->ibi->max_payload_len);
|
|
i3c_master_queue_ibi(dev, slot);
|
|
data_consumed = true;
|
|
|
|
out_unlock:
|
|
spin_unlock(&master->ibi.lock);
|
|
|
|
/* Consume data from the FIFO if it's not been done already. */
|
|
if (!data_consumed) {
|
|
for (i = 0; i < I3C_DATA_LENGTH(ibiq); i++)
|
|
readl(master->regs + I3C_IBI_PORT);
|
|
}
|
|
}
|
|
|
|
static void exynos_i3c_hci_master_demux_ibis(struct exynos_i3c_hci_master *master)
|
|
{
|
|
unsigned int queue_status;
|
|
unsigned int ibiq;
|
|
|
|
for (queue_status = readl(master->regs + I3C_QUEUE_STATUS_LEVEL);
|
|
(queue_status & I3C_IBI_STAT_CNT_LEVEL);
|
|
queue_status = readl(master->regs + I3C_QUEUE_STATUS_LEVEL)) {
|
|
ibiq = readl(master->regs + I3C_IBI_PORT);
|
|
switch(I3C_IBI_ID(ibiq)) {
|
|
case 0x2:
|
|
/* HJ Case */
|
|
queue_work(master->base.wq, &master->hj_work);
|
|
break;
|
|
|
|
default:
|
|
exynos_i3c_hci_master_handle_ibi(master, ibiq);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static irqreturn_t exynos_i3c_hci_master_interrupt(int irq, void *data)
|
|
{
|
|
struct exynos_i3c_hci_master *master = data;
|
|
u32 status;
|
|
|
|
dev_dbg(master->dev, "Start %s\n", __func__);
|
|
|
|
status = readl(master->regs + I3C_PIO_INTR_STATUS);
|
|
|
|
if (!(status & readl(master->regs + I3C_PIO_INTR_EN))) {
|
|
writel(status, master->regs + I3C_PIO_INTR_STATUS);
|
|
return IRQ_NONE;
|
|
}
|
|
|
|
spin_lock(&master->xfer_queue.lock);
|
|
dev_dbg(master->dev, "INT STATUS 0x%08x\n", status);
|
|
if (status & I3C_TRANSFER_ERR_STAT)
|
|
dev_err(master->dev, "INT STATUS 0x%08x Transfer ERR Interrupt detected\n", status);
|
|
if (status & I3C_TRANSFER_ABORT_STAT)
|
|
dev_err(master->dev, "INT STATUS 0x%08x Transfer ABORT Interrupt detected\n", status);
|
|
if (status & I3C_RESP_READY_STAT)
|
|
exynos_i3c_hci_master_end_xfer_locked(master, status);
|
|
else if (!(status & I3C_IBI_THLD_STAT))
|
|
dev_err(master->dev, "INT STATUS 0x%08x Wrong with transaction\n", status);
|
|
|
|
/* Pending clear */
|
|
writel(status & ~I3C_IBI_THLD_STAT, master->regs + I3C_PIO_INTR_STATUS);
|
|
|
|
spin_unlock(&master->xfer_queue.lock);
|
|
|
|
if (status & I3C_IBI_THLD_STAT) {
|
|
exynos_i3c_hci_master_demux_ibis(master);
|
|
writel(I3C_IBI_THLD_STAT, master->regs + I3C_PIO_INTR_STATUS);
|
|
}
|
|
|
|
dev_dbg(master->dev, "End %s\n", __func__);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int exynos_i3c_hci_master_disable_ibi(struct i3c_dev_desc *dev)
|
|
{
|
|
struct i3c_master_controller *m = i3c_dev_get_master(dev);
|
|
struct exynos_i3c_hci_master *master = to_exynos_i3c_hci_master(m);
|
|
int ret;
|
|
|
|
ret = i3c_master_disec_locked(m, dev->info.dyn_addr,
|
|
I3C_CCC_EVENT_SIR);
|
|
if (ret)
|
|
dev_err(master->dev, "Fail to DISEC CCC while disable_ibi, ret = %d\n", ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int exynos_i3c_hci_master_enable_ibi(struct i3c_dev_desc *dev)
|
|
{
|
|
struct i3c_master_controller *m = i3c_dev_get_master(dev);
|
|
struct exynos_i3c_hci_master *master = to_exynos_i3c_hci_master(m);
|
|
struct exynos_i3c_hci_i2c_dev_data *data = i3c_dev_get_master_data(dev);
|
|
unsigned long flags;
|
|
// u32 sircfg, sirmap;
|
|
unsigned int dat, exdat;
|
|
int ret;
|
|
|
|
spin_lock_irqsave(&master->ibi.lock, flags);
|
|
/* CDNS SIR_MAP contains DEV_ROLE(slv or mst), DEV_DA, DEV_PL, ACK and DEV_SLOW.
|
|
* Exynos uses DAT as IBI MAP register. DAT includes DYNAMIC_ADDRESS, IBI_PAYLOAD_SIZE, SIR_REG_NACK bits.
|
|
*/
|
|
dat = readl(master->regs + I3C_DEV_ADDR_TABLE(data->id));
|
|
exdat = readl(master->regs + I3C_EXT_DEV_ADDR_TABLE(data->id));
|
|
if (!(exdat & I3C_DYNAMIC_ADDR_ASSIGNED)) {
|
|
dev_err(master->dev, "Enable_ibi fails: DYNAMIC ADDR wasn't assigned\n");
|
|
return -ENODEV;
|
|
} else if (dat & I3C_DEVICE_TYPE) {
|
|
dev_err(master->dev, "Enable_ibi fails: dev is I2C slave\n");
|
|
return -ENODEV;
|
|
};
|
|
|
|
exdat |= I3C_IBI_PAYLOAD_SIZE(dev->info.max_ibi_len);
|
|
dat |= I3C_IBI_PAYLOAD;
|
|
dat &= ~I3C_SIR_REJECT;
|
|
writel(dat, master->regs + I3C_DEV_ADDR_TABLE(data->id));
|
|
writel(exdat, master->regs + I3C_EXT_DEV_ADDR_TABLE(data->id));
|
|
|
|
spin_unlock_irqrestore(&master->ibi.lock, flags);
|
|
|
|
ret = i3c_master_enec_locked(m, dev->info.dyn_addr,
|
|
I3C_CCC_EVENT_SIR);
|
|
if (ret) {
|
|
dev_err(master->dev, "Fail to ENEC CCC while enable_ibi, ret = %d\n", ret);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int exynos_i3c_hci_master_request_ibi(struct i3c_dev_desc *dev,
|
|
const struct i3c_ibi_setup *req)
|
|
{
|
|
struct i3c_master_controller *m = i3c_dev_get_master(dev);
|
|
struct exynos_i3c_hci_master *master = to_exynos_i3c_hci_master(m);
|
|
struct exynos_i3c_hci_i2c_dev_data *data = i3c_dev_get_master_data(dev);
|
|
unsigned long flags;
|
|
unsigned int i;
|
|
|
|
data->ibi_pool = i3c_generic_ibi_alloc_pool(dev, req);
|
|
if (IS_ERR(data->ibi_pool))
|
|
return PTR_ERR(data->ibi_pool);
|
|
|
|
spin_lock_irqsave(&master->ibi.lock, flags);
|
|
for (i = 0; i < master->ibi.num_slots; i++) {
|
|
if (!master->ibi.slots[i]) {
|
|
data->ibi = i;
|
|
master->ibi.slots[i] = dev;
|
|
break;
|
|
}
|
|
}
|
|
spin_unlock_irqrestore(&master->ibi.lock, flags);
|
|
|
|
if (i < master->ibi.num_slots)
|
|
return 0;
|
|
|
|
i3c_generic_ibi_free_pool(data->ibi_pool);
|
|
data->ibi_pool = NULL;
|
|
|
|
return -ENOSPC;
|
|
}
|
|
|
|
static void exynos_i3c_hci_master_free_ibi(struct i3c_dev_desc *dev)
|
|
{
|
|
struct i3c_master_controller *m = i3c_dev_get_master(dev);
|
|
struct exynos_i3c_hci_master *master = to_exynos_i3c_hci_master(m);
|
|
struct exynos_i3c_hci_i2c_dev_data *data = i3c_dev_get_master_data(dev);
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&master->ibi.lock, flags);
|
|
master->ibi.slots[data->ibi] = NULL;
|
|
data->ibi = -1;
|
|
spin_unlock_irqrestore(&master->ibi.lock, flags);
|
|
|
|
i3c_generic_ibi_free_pool(data->ibi_pool);
|
|
}
|
|
|
|
static void exynos_i3c_hci_master_recycle_ibi_slot(struct i3c_dev_desc *dev,
|
|
struct i3c_ibi_slot *slot)
|
|
{
|
|
struct exynos_i3c_hci_i2c_dev_data *data = i3c_dev_get_master_data(dev);
|
|
|
|
i3c_generic_ibi_recycle_slot(data->ibi_pool, slot);
|
|
}
|
|
|
|
|
|
static const struct i3c_master_controller_ops exynos_i3c_hci_master_ops = {
|
|
.bus_init = exynos_i3c_hci_master_bus_init,
|
|
.bus_cleanup = exynos_i3c_hci_master_bus_cleanup,
|
|
.priv_xfers = exynos_i3c_hci_xfer_priv,
|
|
.i2c_xfers = exynos_i3c_hci_master_i2c_xfers,
|
|
.attach_i3c_dev = exynos_i3c_hci_master_attach_i3c_dev,
|
|
.reattach_i3c_dev = exynos_i3c_hci_master_reattach_i3c_dev,
|
|
.detach_i3c_dev = exynos_i3c_hci_master_detach_i3c_dev,
|
|
.attach_i2c_dev = exynos_i3c_hci_master_attach_i2c_dev,
|
|
.detach_i2c_dev = exynos_i3c_hci_master_detach_i2c_dev,
|
|
.do_daa = exynos_i3c_hci_master_do_daa,
|
|
.send_ccc_cmd = exynos_i3c_hci_send_ccc_cmd,
|
|
.supports_ccc_cmd = exynos_i3c_hci_supports_ccc_cmd,
|
|
.enable_ibi = exynos_i3c_hci_master_enable_ibi,
|
|
.disable_ibi = exynos_i3c_hci_master_disable_ibi,
|
|
.request_ibi = exynos_i3c_hci_master_request_ibi,
|
|
.free_ibi = exynos_i3c_hci_master_free_ibi,
|
|
.recycle_ibi_slot = exynos_i3c_hci_master_recycle_ibi_slot,
|
|
};
|
|
|
|
static void exynos_i3c_hci_master_hj(struct work_struct *work)
|
|
{
|
|
struct exynos_i3c_hci_master *master = container_of(work,
|
|
struct exynos_i3c_hci_master,
|
|
hj_work);
|
|
|
|
i3c_master_do_daa(&master->base);
|
|
}
|
|
|
|
static int exynos_i3c_hci_probe(struct platform_device *pdev)
|
|
{
|
|
struct device_node *np = pdev->dev.of_node;
|
|
struct exynos_i3c_hci_master *master;
|
|
struct resource *mem;
|
|
int ret, irq;
|
|
|
|
dev_info(&pdev->dev, "Entering I3C probe\n");
|
|
|
|
if (!np) {
|
|
dev_err(&pdev->dev, "No device node\n");
|
|
return -ENOENT;
|
|
}
|
|
|
|
master = devm_kzalloc(&pdev->dev, sizeof(struct exynos_i3c_hci_master), GFP_KERNEL);
|
|
if (!master) {
|
|
dev_err(&pdev->dev, "No memory for I3C device\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
master->regs = devm_ioremap_resource(&pdev->dev, mem);
|
|
if (IS_ERR(master->regs)) {
|
|
dev_err(&pdev->dev, "faile to ioremap\n");
|
|
return PTR_ERR(master->regs);
|
|
}
|
|
|
|
master->ipclk = devm_clk_get(&pdev->dev, "ipclk");
|
|
if (IS_ERR(master->ipclk)) {
|
|
dev_err(&pdev->dev, "cannot get ipclk(sysclk)\n");
|
|
return PTR_ERR(master->ipclk);
|
|
}
|
|
|
|
master->gate_clk = devm_clk_get(&pdev->dev, "gate_clk");
|
|
if (IS_ERR(master->gate_clk)) {
|
|
dev_err(&pdev->dev, "cannot get gateclk(pclk)\n");
|
|
return PTR_ERR(master->gate_clk);
|
|
}
|
|
|
|
irq = platform_get_irq(pdev, 0);
|
|
if (irq < 0) {
|
|
dev_err(&pdev->dev, "cannot get I3C IRQ\n");
|
|
return irq;
|
|
}
|
|
|
|
ret = clk_prepare_enable(master->gate_clk);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "failed to gateclk enable\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = clk_prepare_enable(master->ipclk);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "failed to ipclk enable\n");
|
|
goto err_enable_ipclk;
|
|
}
|
|
|
|
if(pdev->dev.of_node)
|
|
exynos_i3c_hci_parse_dt(master, &pdev->dev);
|
|
|
|
spin_lock_init(&master->xfer_queue.lock);
|
|
INIT_LIST_HEAD(&master->xfer_queue.list);
|
|
|
|
INIT_WORK(&master->hj_work, exynos_i3c_hci_master_hj);
|
|
|
|
ret = devm_request_irq(&pdev->dev, irq, exynos_i3c_hci_master_interrupt, 0,
|
|
dev_name(&pdev->dev), master);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "failed to request irq\n");
|
|
goto err_disable_pclk;
|
|
}
|
|
|
|
/* TOTO : load from DAT entries */
|
|
master->maxdevs = 8;
|
|
master->free_rr_slots = GENMASK(master->maxdevs, 0);
|
|
|
|
platform_set_drvdata(pdev, master);
|
|
master->dev = &pdev->dev;
|
|
|
|
/* TODO : IBI work */
|
|
spin_lock_init(&master->ibi.lock);
|
|
master->ibi.num_slots = IBI_STAT_QUEUE_DEPTH;
|
|
master->ibi.slots = devm_kcalloc(&pdev->dev, master->ibi.num_slots,
|
|
sizeof(*master->ibi.slots),
|
|
GFP_KERNEL);
|
|
if (!master->ibi.slots)
|
|
goto err_disable_pclk;
|
|
|
|
master->caps.cmdfifodepth = CMD_QUEUE_DEPTH;
|
|
master->caps.respfifodepth = RESP_STAT_QUEUE_DEPTH;
|
|
master->caps.rxfifodepth = RX_FIFO_DEPTH;
|
|
master->caps.txfifodepth = TX_FIFO_DEPTH;
|
|
master->caps.ibiqfifodepth = IBI_STAT_QUEUE_DEPTH;
|
|
master->caps.ibidfifodepth = IBI_DATA_BUFFER_DEPTH;
|
|
|
|
// writel(IBIR_THR(1), master->regs + CMD_IBI_THR_CTRL);
|
|
writel(I3C_IBI_THLD_STAT_EN, master->regs + I3C_PIO_INTR_EN);
|
|
writel(I3C_IBI_THLD_SIG_EN, master->regs + I3C_PIO_INTR_SIGNAL_EN);
|
|
|
|
ret = i3c_master_register(&master->base, &pdev->dev,
|
|
&exynos_i3c_hci_master_ops, false);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "failed to i3c master register ret(%d)\n", ret);
|
|
goto err_disable_pclk;
|
|
}
|
|
|
|
dev_info(&pdev->dev, "I3C probe success\n");
|
|
|
|
return 0;
|
|
|
|
err_disable_pclk:
|
|
clk_disable_unprepare(master->ipclk);
|
|
|
|
err_enable_ipclk:
|
|
clk_disable_unprepare(master->gate_clk);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int exynos_i3c_hci_remove(struct platform_device *pdev)
|
|
{
|
|
struct exynos_i3c_hci_master *master = platform_get_drvdata(pdev);
|
|
int ret;
|
|
|
|
ret = i3c_master_unregister(&master->base);
|
|
if (ret)
|
|
return ret;
|
|
|
|
clk_disable_unprepare(master->ipclk);
|
|
clk_disable_unprepare(master->gate_clk);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static const struct dev_pm_ops exynos_i3c_hci_pm = {
|
|
SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(exynos_i3c_hci_suspend_noirq,
|
|
exynos_i3c_hci_resume_noirq)
|
|
SET_RUNTIME_PM_OPS(exynos_i3c_hci_runtime_suspend,
|
|
exynos_i3c_hci_runtime_resume, NULL)
|
|
};
|
|
|
|
static const struct of_device_id exynos_i3c_hci_match[] = {
|
|
{ .compatible = "samsung,exynos-i3c-hci" },
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, exynos_i3c_hci_match);
|
|
|
|
static struct platform_driver exynos_i3c_hci_driver = {
|
|
.probe = exynos_i3c_hci_probe,
|
|
.remove = exynos_i3c_hci_remove,
|
|
.driver = {
|
|
.name = "exynos-i3c-hci",
|
|
.pm = &exynos_i3c_hci_pm,
|
|
.of_match_table = of_match_ptr(exynos_i3c_hci_match),
|
|
},
|
|
};
|
|
module_platform_driver(exynos_i3c_hci_driver);
|
|
|
|
MODULE_AUTHOR("Kang Kyungwoo <kwoo.kang@samsung.com> \
|
|
Cha Myungsu <myung-su.cha@samsung.com>");
|
|
MODULE_DESCRIPTION("Exynos I3C HCI master driver");
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_ALIAS("platform:exynos-i3c-hci-master");
|