| // SPDX-License-Identifier: BSD-3-Clause |
| /* |
| * Copyright (c) 2020, MIPI Alliance, Inc. |
| * |
| * Author: Nicolas Pitre <npitre@baylibre.com> |
| * |
| * Core driver code with main interface to the I3C subsystem. |
| */ |
| |
| #include <linux/bitfield.h> |
| #include <linux/device.h> |
| #include <linux/errno.h> |
| #include <linux/i3c/master.h> |
| #include <linux/interrupt.h> |
| #include <linux/io.h> |
| #include <linux/iopoll.h> |
| #include <linux/module.h> |
| #include <linux/platform_device.h> |
| |
| #include "hci.h" |
| #include "ext_caps.h" |
| #include "cmd.h" |
| #include "dat.h" |
| |
| |
| /* |
| * Host Controller Capabilities and Operation Registers |
| */ |
| |
| #define reg_read(r) readl(hci->base_regs + (r)) |
| #define reg_write(r, v) writel(v, hci->base_regs + (r)) |
| #define reg_set(r, v) reg_write(r, reg_read(r) | (v)) |
| #define reg_clear(r, v) reg_write(r, reg_read(r) & ~(v)) |
| |
| #define HCI_VERSION 0x00 /* HCI Version (in BCD) */ |
| |
| #define HC_CONTROL 0x04 |
| #define HC_CONTROL_BUS_ENABLE BIT(31) |
| #define HC_CONTROL_RESUME BIT(30) |
| #define HC_CONTROL_ABORT BIT(29) |
| #define HC_CONTROL_HALT_ON_CMD_TIMEOUT BIT(12) |
| #define HC_CONTROL_HOT_JOIN_CTRL BIT(8) /* Hot-Join ACK/NACK Control */ |
| #define HC_CONTROL_I2C_TARGET_PRESENT BIT(7) |
| #define HC_CONTROL_PIO_MODE BIT(6) /* DMA/PIO Mode Selector */ |
| #define HC_CONTROL_DATA_BIG_ENDIAN BIT(4) |
| #define HC_CONTROL_IBA_INCLUDE BIT(0) /* Include I3C Broadcast Address */ |
| |
| #define MASTER_DEVICE_ADDR 0x08 /* Master Device Address */ |
| #define MASTER_DYNAMIC_ADDR_VALID BIT(31) /* Dynamic Address is Valid */ |
| #define MASTER_DYNAMIC_ADDR(v) FIELD_PREP(GENMASK(22, 16), v) |
| |
| #define HC_CAPABILITIES 0x0c |
| #define HC_CAP_SG_DC_EN BIT(30) |
| #define HC_CAP_SG_IBI_EN BIT(29) |
| #define HC_CAP_SG_CR_EN BIT(28) |
| #define HC_CAP_MAX_DATA_LENGTH GENMASK(24, 22) |
| #define HC_CAP_CMD_SIZE GENMASK(21, 20) |
| #define HC_CAP_DIRECT_COMMANDS_EN BIT(18) |
| #define HC_CAP_MULTI_LANE_EN BIT(15) |
| #define HC_CAP_CMD_CCC_DEFBYTE BIT(10) |
| #define HC_CAP_HDR_BT_EN BIT(8) |
| #define HC_CAP_HDR_TS_EN BIT(7) |
| #define HC_CAP_HDR_DDR_EN BIT(6) |
| #define HC_CAP_NON_CURRENT_MASTER_CAP BIT(5) /* master handoff capable */ |
| #define HC_CAP_DATA_BYTE_CFG_EN BIT(4) /* endian selection possible */ |
| #define HC_CAP_AUTO_COMMAND BIT(3) |
| #define HC_CAP_COMBO_COMMAND BIT(2) |
| |
| #define RESET_CONTROL 0x10 |
| #define BUS_RESET BIT(31) |
| #define BUS_RESET_TYPE GENMASK(30, 29) |
| #define IBI_QUEUE_RST BIT(5) |
| #define RX_FIFO_RST BIT(4) |
| #define TX_FIFO_RST BIT(3) |
| #define RESP_QUEUE_RST BIT(2) |
| #define CMD_QUEUE_RST BIT(1) |
| #define SOFT_RST BIT(0) /* Core Reset */ |
| |
| #define PRESENT_STATE 0x14 |
| #define STATE_CURRENT_MASTER BIT(2) |
| |
| #define INTR_STATUS 0x20 |
| #define INTR_STATUS_ENABLE 0x24 |
| #define INTR_SIGNAL_ENABLE 0x28 |
| #define INTR_FORCE 0x2c |
| #define INTR_HC_CMD_SEQ_UFLOW_STAT BIT(12) /* Cmd Sequence Underflow */ |
| #define INTR_HC_RESET_CANCEL BIT(11) /* HC Cancelled Reset */ |
| #define INTR_HC_INTERNAL_ERR BIT(10) /* HC Internal Error */ |
| #define INTR_HC_PIO BIT(8) /* cascaded PIO interrupt */ |
| #define INTR_HC_RINGS GENMASK(7, 0) |
| |
| #define DAT_SECTION 0x30 /* Device Address Table */ |
| #define DAT_ENTRY_SIZE GENMASK(31, 28) |
| #define DAT_TABLE_SIZE GENMASK(18, 12) |
| #define DAT_TABLE_OFFSET GENMASK(11, 0) |
| |
| #define DCT_SECTION 0x34 /* Device Characteristics Table */ |
| #define DCT_ENTRY_SIZE GENMASK(31, 28) |
| #define DCT_TABLE_INDEX GENMASK(23, 19) |
| #define DCT_TABLE_SIZE GENMASK(18, 12) |
| #define DCT_TABLE_OFFSET GENMASK(11, 0) |
| |
| #define RING_HEADERS_SECTION 0x38 |
| #define RING_HEADERS_OFFSET GENMASK(15, 0) |
| |
| #define PIO_SECTION 0x3c |
| #define PIO_REGS_OFFSET GENMASK(15, 0) /* PIO Offset */ |
| |
| #define EXT_CAPS_SECTION 0x40 |
| #define EXT_CAPS_OFFSET GENMASK(15, 0) |
| |
| #define IBI_NOTIFY_CTRL 0x58 /* IBI Notify Control */ |
| #define IBI_NOTIFY_SIR_REJECTED BIT(3) /* Rejected Target Interrupt Request */ |
| #define IBI_NOTIFY_MR_REJECTED BIT(1) /* Rejected Master Request Control */ |
| #define IBI_NOTIFY_HJ_REJECTED BIT(0) /* Rejected Hot-Join Control */ |
| |
| #define DEV_CTX_BASE_LO 0x60 |
| #define DEV_CTX_BASE_HI 0x64 |
| |
| |
| static inline struct i3c_hci *to_i3c_hci(struct i3c_master_controller *m) |
| { |
| return container_of(m, struct i3c_hci, master); |
| } |
| |
| static int i3c_hci_bus_init(struct i3c_master_controller *m) |
| { |
| struct i3c_hci *hci = to_i3c_hci(m); |
| struct i3c_device_info info; |
| int ret; |
| |
| DBG(""); |
| |
| if (hci->cmd == &mipi_i3c_hci_cmd_v1) { |
| ret = mipi_i3c_hci_dat_v1.init(hci); |
| if (ret) |
| return ret; |
| } |
| |
| ret = i3c_master_get_free_addr(m, 0); |
| if (ret < 0) |
| return ret; |
| reg_write(MASTER_DEVICE_ADDR, |
| MASTER_DYNAMIC_ADDR(ret) | MASTER_DYNAMIC_ADDR_VALID); |
| memset(&info, 0, sizeof(info)); |
| info.dyn_addr = ret; |
| ret = i3c_master_set_info(m, &info); |
| if (ret) |
| return ret; |
| |
| ret = hci->io->init(hci); |
| if (ret) |
| return ret; |
| |
| reg_set(HC_CONTROL, HC_CONTROL_BUS_ENABLE); |
| DBG("HC_CONTROL = %#x", reg_read(HC_CONTROL)); |
| |
| return 0; |
| } |
| |
| static void i3c_hci_bus_cleanup(struct i3c_master_controller *m) |
| { |
| struct i3c_hci *hci = to_i3c_hci(m); |
| |
| DBG(""); |
| |
| reg_clear(HC_CONTROL, HC_CONTROL_BUS_ENABLE); |
| hci->io->cleanup(hci); |
| if (hci->cmd == &mipi_i3c_hci_cmd_v1) |
| mipi_i3c_hci_dat_v1.cleanup(hci); |
| } |
| |
| void mipi_i3c_hci_resume(struct i3c_hci *hci) |
| { |
| /* the HC_CONTROL_RESUME bit is R/W1C so just read and write back */ |
| reg_write(HC_CONTROL, reg_read(HC_CONTROL)); |
| } |
| |
| /* located here rather than pio.c because needed bits are in core reg space */ |
| void mipi_i3c_hci_pio_reset(struct i3c_hci *hci) |
| { |
| reg_write(RESET_CONTROL, RX_FIFO_RST | TX_FIFO_RST | RESP_QUEUE_RST); |
| } |
| |
| /* located here rather than dct.c because needed bits are in core reg space */ |
| void mipi_i3c_hci_dct_index_reset(struct i3c_hci *hci) |
| { |
| reg_write(DCT_SECTION, FIELD_PREP(DCT_TABLE_INDEX, 0)); |
| } |
| |
| static int i3c_hci_send_ccc_cmd(struct i3c_master_controller *m, |
| struct i3c_ccc_cmd *ccc) |
| { |
| struct i3c_hci *hci = to_i3c_hci(m); |
| struct hci_xfer *xfer; |
| bool raw = !!(hci->quirks & HCI_QUIRK_RAW_CCC); |
| bool prefixed = raw && !!(ccc->id & I3C_CCC_DIRECT); |
| unsigned int nxfers = ccc->ndests + prefixed; |
| DECLARE_COMPLETION_ONSTACK(done); |
| int i, last, ret = 0; |
| |
| DBG("cmd=%#x rnw=%d ndests=%d data[0].len=%d", |
| ccc->id, ccc->rnw, ccc->ndests, ccc->dests[0].payload.len); |
| |
| xfer = hci_alloc_xfer(nxfers); |
| if (!xfer) |
| return -ENOMEM; |
| |
| if (prefixed) { |
| xfer->data = NULL; |
| xfer->data_len = 0; |
| xfer->rnw = false; |
| hci->cmd->prep_ccc(hci, xfer, I3C_BROADCAST_ADDR, |
| ccc->id, true); |
| xfer++; |
| } |
| |
| for (i = 0; i < nxfers - prefixed; i++) { |
| xfer[i].data = ccc->dests[i].payload.data; |
| xfer[i].data_len = ccc->dests[i].payload.len; |
| xfer[i].rnw = ccc->rnw; |
| ret = hci->cmd->prep_ccc(hci, &xfer[i], ccc->dests[i].addr, |
| ccc->id, raw); |
| if (ret) |
| goto out; |
| xfer[i].cmd_desc[0] |= CMD_0_ROC; |
| } |
| last = i - 1; |
| xfer[last].cmd_desc[0] |= CMD_0_TOC; |
| xfer[last].completion = &done; |
| |
| if (prefixed) |
| xfer--; |
| |
| ret = hci->io->queue_xfer(hci, xfer, nxfers); |
| if (ret) |
| goto out; |
| if (!wait_for_completion_timeout(&done, HZ) && |
| hci->io->dequeue_xfer(hci, xfer, nxfers)) { |
| ret = -ETIME; |
| goto out; |
| } |
| for (i = prefixed; i < nxfers; i++) { |
| if (ccc->rnw) |
| ccc->dests[i - prefixed].payload.len = |
| RESP_DATA_LENGTH(xfer[i].response); |
| if (RESP_STATUS(xfer[i].response) != RESP_SUCCESS) { |
| ret = -EIO; |
| goto out; |
| } |
| } |
| |
| if (ccc->rnw) |
| DBG("got: %*ph", |
| ccc->dests[0].payload.len, ccc->dests[0].payload.data); |
| |
| out: |
| hci_free_xfer(xfer, nxfers); |
| return ret; |
| } |
| |
| static int i3c_hci_daa(struct i3c_master_controller *m) |
| { |
| struct i3c_hci *hci = to_i3c_hci(m); |
| |
| DBG(""); |
| |
| return hci->cmd->perform_daa(hci); |
| } |
| |
| static int i3c_hci_priv_xfers(struct i3c_dev_desc *dev, |
| struct i3c_priv_xfer *i3c_xfers, |
| int nxfers) |
| { |
| struct i3c_master_controller *m = i3c_dev_get_master(dev); |
| struct i3c_hci *hci = to_i3c_hci(m); |
| struct hci_xfer *xfer; |
| DECLARE_COMPLETION_ONSTACK(done); |
| unsigned int size_limit; |
| int i, last, ret = 0; |
| |
| DBG("nxfers = %d", nxfers); |
| |
| xfer = hci_alloc_xfer(nxfers); |
| if (!xfer) |
| return -ENOMEM; |
| |
| size_limit = 1U << (16 + FIELD_GET(HC_CAP_MAX_DATA_LENGTH, hci->caps)); |
| |
| for (i = 0; i < nxfers; i++) { |
| xfer[i].data_len = i3c_xfers[i].len; |
| ret = -EFBIG; |
| if (xfer[i].data_len >= size_limit) |
| goto out; |
| xfer[i].rnw = i3c_xfers[i].rnw; |
| if (i3c_xfers[i].rnw) { |
| xfer[i].data = i3c_xfers[i].data.in; |
| } else { |
| /* silence the const qualifier warning with a cast */ |
| xfer[i].data = (void *) i3c_xfers[i].data.out; |
| } |
| hci->cmd->prep_i3c_xfer(hci, dev, &xfer[i]); |
| xfer[i].cmd_desc[0] |= CMD_0_ROC; |
| } |
| last = i - 1; |
| xfer[last].cmd_desc[0] |= CMD_0_TOC; |
| xfer[last].completion = &done; |
| |
| ret = hci->io->queue_xfer(hci, xfer, nxfers); |
| if (ret) |
| goto out; |
| if (!wait_for_completion_timeout(&done, HZ) && |
| hci->io->dequeue_xfer(hci, xfer, nxfers)) { |
| ret = -ETIME; |
| goto out; |
| } |
| for (i = 0; i < nxfers; i++) { |
| if (i3c_xfers[i].rnw) |
| i3c_xfers[i].len = RESP_DATA_LENGTH(xfer[i].response); |
| if (RESP_STATUS(xfer[i].response) != RESP_SUCCESS) { |
| ret = -EIO; |
| goto out; |
| } |
| } |
| |
| out: |
| hci_free_xfer(xfer, nxfers); |
| return ret; |
| } |
| |
| static int i3c_hci_i2c_xfers(struct i2c_dev_desc *dev, |
| const struct i2c_msg *i2c_xfers, int nxfers) |
| { |
| struct i3c_master_controller *m = i2c_dev_get_master(dev); |
| struct i3c_hci *hci = to_i3c_hci(m); |
| struct hci_xfer *xfer; |
| DECLARE_COMPLETION_ONSTACK(done); |
| int i, last, ret = 0; |
| |
| DBG("nxfers = %d", nxfers); |
| |
| xfer = hci_alloc_xfer(nxfers); |
| if (!xfer) |
| return -ENOMEM; |
| |
| for (i = 0; i < nxfers; i++) { |
| xfer[i].data = i2c_xfers[i].buf; |
| xfer[i].data_len = i2c_xfers[i].len; |
| xfer[i].rnw = i2c_xfers[i].flags & I2C_M_RD; |
| hci->cmd->prep_i2c_xfer(hci, dev, &xfer[i]); |
| xfer[i].cmd_desc[0] |= CMD_0_ROC; |
| } |
| last = i - 1; |
| xfer[last].cmd_desc[0] |= CMD_0_TOC; |
| xfer[last].completion = &done; |
| |
| ret = hci->io->queue_xfer(hci, xfer, nxfers); |
| if (ret) |
| goto out; |
| if (!wait_for_completion_timeout(&done, HZ) && |
| hci->io->dequeue_xfer(hci, xfer, nxfers)) { |
| ret = -ETIME; |
| goto out; |
| } |
| for (i = 0; i < nxfers; i++) { |
| if (RESP_STATUS(xfer[i].response) != RESP_SUCCESS) { |
| ret = -EIO; |
| goto out; |
| } |
| } |
| |
| out: |
| hci_free_xfer(xfer, nxfers); |
| return ret; |
| } |
| |
| static int i3c_hci_attach_i3c_dev(struct i3c_dev_desc *dev) |
| { |
| struct i3c_master_controller *m = i3c_dev_get_master(dev); |
| struct i3c_hci *hci = to_i3c_hci(m); |
| struct i3c_hci_dev_data *dev_data; |
| int ret; |
| |
| DBG(""); |
| |
| dev_data = kzalloc(sizeof(*dev_data), GFP_KERNEL); |
| if (!dev_data) |
| return -ENOMEM; |
| if (hci->cmd == &mipi_i3c_hci_cmd_v1) { |
| ret = mipi_i3c_hci_dat_v1.alloc_entry(hci); |
| if (ret < 0) { |
| kfree(dev_data); |
| return ret; |
| } |
| mipi_i3c_hci_dat_v1.set_dynamic_addr(hci, ret, dev->info.dyn_addr); |
| dev_data->dat_idx = ret; |
| } |
| i3c_dev_set_master_data(dev, dev_data); |
| return 0; |
| } |
| |
| static int i3c_hci_reattach_i3c_dev(struct i3c_dev_desc *dev, u8 old_dyn_addr) |
| { |
| struct i3c_master_controller *m = i3c_dev_get_master(dev); |
| struct i3c_hci *hci = to_i3c_hci(m); |
| struct i3c_hci_dev_data *dev_data = i3c_dev_get_master_data(dev); |
| |
| DBG(""); |
| |
| if (hci->cmd == &mipi_i3c_hci_cmd_v1) |
| mipi_i3c_hci_dat_v1.set_dynamic_addr(hci, dev_data->dat_idx, |
| dev->info.dyn_addr); |
| return 0; |
| } |
| |
| static void i3c_hci_detach_i3c_dev(struct i3c_dev_desc *dev) |
| { |
| struct i3c_master_controller *m = i3c_dev_get_master(dev); |
| struct i3c_hci *hci = to_i3c_hci(m); |
| struct i3c_hci_dev_data *dev_data = i3c_dev_get_master_data(dev); |
| |
| DBG(""); |
| |
| i3c_dev_set_master_data(dev, NULL); |
| if (hci->cmd == &mipi_i3c_hci_cmd_v1) |
| mipi_i3c_hci_dat_v1.free_entry(hci, dev_data->dat_idx); |
| kfree(dev_data); |
| } |
| |
| static int i3c_hci_attach_i2c_dev(struct i2c_dev_desc *dev) |
| { |
| struct i3c_master_controller *m = i2c_dev_get_master(dev); |
| struct i3c_hci *hci = to_i3c_hci(m); |
| struct i3c_hci_dev_data *dev_data; |
| int ret; |
| |
| DBG(""); |
| |
| if (hci->cmd != &mipi_i3c_hci_cmd_v1) |
| return 0; |
| dev_data = kzalloc(sizeof(*dev_data), GFP_KERNEL); |
| if (!dev_data) |
| return -ENOMEM; |
| ret = mipi_i3c_hci_dat_v1.alloc_entry(hci); |
| if (ret < 0) { |
| kfree(dev_data); |
| return ret; |
| } |
| mipi_i3c_hci_dat_v1.set_static_addr(hci, ret, dev->addr); |
| mipi_i3c_hci_dat_v1.set_flags(hci, ret, DAT_0_I2C_DEVICE, 0); |
| dev_data->dat_idx = ret; |
| i2c_dev_set_master_data(dev, dev_data); |
| return 0; |
| } |
| |
| static void i3c_hci_detach_i2c_dev(struct i2c_dev_desc *dev) |
| { |
| struct i3c_master_controller *m = i2c_dev_get_master(dev); |
| struct i3c_hci *hci = to_i3c_hci(m); |
| struct i3c_hci_dev_data *dev_data = i2c_dev_get_master_data(dev); |
| |
| DBG(""); |
| |
| if (dev_data) { |
| i2c_dev_set_master_data(dev, NULL); |
| if (hci->cmd == &mipi_i3c_hci_cmd_v1) |
| mipi_i3c_hci_dat_v1.free_entry(hci, dev_data->dat_idx); |
| kfree(dev_data); |
| } |
| } |
| |
| static int i3c_hci_request_ibi(struct i3c_dev_desc *dev, |
| const struct i3c_ibi_setup *req) |
| { |
| struct i3c_master_controller *m = i3c_dev_get_master(dev); |
| struct i3c_hci *hci = to_i3c_hci(m); |
| struct i3c_hci_dev_data *dev_data = i3c_dev_get_master_data(dev); |
| unsigned int dat_idx = dev_data->dat_idx; |
| |
| if (req->max_payload_len != 0) |
| mipi_i3c_hci_dat_v1.set_flags(hci, dat_idx, DAT_0_IBI_PAYLOAD, 0); |
| else |
| mipi_i3c_hci_dat_v1.clear_flags(hci, dat_idx, DAT_0_IBI_PAYLOAD, 0); |
| return hci->io->request_ibi(hci, dev, req); |
| } |
| |
| static void i3c_hci_free_ibi(struct i3c_dev_desc *dev) |
| { |
| struct i3c_master_controller *m = i3c_dev_get_master(dev); |
| struct i3c_hci *hci = to_i3c_hci(m); |
| |
| hci->io->free_ibi(hci, dev); |
| } |
| |
| static int i3c_hci_enable_ibi(struct i3c_dev_desc *dev) |
| { |
| struct i3c_master_controller *m = i3c_dev_get_master(dev); |
| struct i3c_hci *hci = to_i3c_hci(m); |
| struct i3c_hci_dev_data *dev_data = i3c_dev_get_master_data(dev); |
| |
| mipi_i3c_hci_dat_v1.clear_flags(hci, dev_data->dat_idx, DAT_0_SIR_REJECT, 0); |
| return i3c_master_enec_locked(m, dev->info.dyn_addr, I3C_CCC_EVENT_SIR); |
| } |
| |
| static int i3c_hci_disable_ibi(struct i3c_dev_desc *dev) |
| { |
| struct i3c_master_controller *m = i3c_dev_get_master(dev); |
| struct i3c_hci *hci = to_i3c_hci(m); |
| struct i3c_hci_dev_data *dev_data = i3c_dev_get_master_data(dev); |
| |
| mipi_i3c_hci_dat_v1.set_flags(hci, dev_data->dat_idx, DAT_0_SIR_REJECT, 0); |
| return i3c_master_disec_locked(m, dev->info.dyn_addr, I3C_CCC_EVENT_SIR); |
| } |
| |
| static void i3c_hci_recycle_ibi_slot(struct i3c_dev_desc *dev, |
| struct i3c_ibi_slot *slot) |
| { |
| struct i3c_master_controller *m = i3c_dev_get_master(dev); |
| struct i3c_hci *hci = to_i3c_hci(m); |
| |
| hci->io->recycle_ibi_slot(hci, dev, slot); |
| } |
| |
| static const struct i3c_master_controller_ops i3c_hci_ops = { |
| .bus_init = i3c_hci_bus_init, |
| .bus_cleanup = i3c_hci_bus_cleanup, |
| .do_daa = i3c_hci_daa, |
| .send_ccc_cmd = i3c_hci_send_ccc_cmd, |
| .priv_xfers = i3c_hci_priv_xfers, |
| .i2c_xfers = i3c_hci_i2c_xfers, |
| .attach_i3c_dev = i3c_hci_attach_i3c_dev, |
| .reattach_i3c_dev = i3c_hci_reattach_i3c_dev, |
| .detach_i3c_dev = i3c_hci_detach_i3c_dev, |
| .attach_i2c_dev = i3c_hci_attach_i2c_dev, |
| .detach_i2c_dev = i3c_hci_detach_i2c_dev, |
| .request_ibi = i3c_hci_request_ibi, |
| .free_ibi = i3c_hci_free_ibi, |
| .enable_ibi = i3c_hci_enable_ibi, |
| .disable_ibi = i3c_hci_disable_ibi, |
| .recycle_ibi_slot = i3c_hci_recycle_ibi_slot, |
| }; |
| |
| static irqreturn_t i3c_hci_irq_handler(int irq, void *dev_id) |
| { |
| struct i3c_hci *hci = dev_id; |
| irqreturn_t result = IRQ_NONE; |
| u32 val; |
| |
| val = reg_read(INTR_STATUS); |
| DBG("INTR_STATUS = %#x", val); |
| |
| if (val) { |
| reg_write(INTR_STATUS, val); |
| } else { |
| /* v1.0 does not have PIO cascaded notification bits */ |
| val |= INTR_HC_PIO; |
| } |
| |
| if (val & INTR_HC_RESET_CANCEL) { |
| DBG("cancelled reset"); |
| val &= ~INTR_HC_RESET_CANCEL; |
| } |
| if (val & INTR_HC_INTERNAL_ERR) { |
| dev_err(&hci->master.dev, "Host Controller Internal Error\n"); |
| val &= ~INTR_HC_INTERNAL_ERR; |
| } |
| if (val & INTR_HC_PIO) { |
| hci->io->irq_handler(hci, 0); |
| val &= ~INTR_HC_PIO; |
| } |
| if (val & INTR_HC_RINGS) { |
| hci->io->irq_handler(hci, val & INTR_HC_RINGS); |
| val &= ~INTR_HC_RINGS; |
| } |
| if (val) |
| dev_err(&hci->master.dev, "unexpected INTR_STATUS %#x\n", val); |
| else |
| result = IRQ_HANDLED; |
| |
| return result; |
| } |
| |
| static int i3c_hci_init(struct i3c_hci *hci) |
| { |
| u32 regval, offset; |
| int ret; |
| |
| /* Validate HCI hardware version */ |
| regval = reg_read(HCI_VERSION); |
| hci->version_major = (regval >> 8) & 0xf; |
| hci->version_minor = (regval >> 4) & 0xf; |
| hci->revision = regval & 0xf; |
| dev_notice(&hci->master.dev, "MIPI I3C HCI v%u.%u r%02u\n", |
| hci->version_major, hci->version_minor, hci->revision); |
| /* known versions */ |
| switch (regval & ~0xf) { |
| case 0x100: /* version 1.0 */ |
| case 0x110: /* version 1.1 */ |
| case 0x200: /* version 2.0 */ |
| break; |
| default: |
| dev_err(&hci->master.dev, "unsupported HCI version\n"); |
| return -EPROTONOSUPPORT; |
| } |
| |
| hci->caps = reg_read(HC_CAPABILITIES); |
| DBG("caps = %#x", hci->caps); |
| |
| regval = reg_read(DAT_SECTION); |
| offset = FIELD_GET(DAT_TABLE_OFFSET, regval); |
| hci->DAT_regs = offset ? hci->base_regs + offset : NULL; |
| hci->DAT_entries = FIELD_GET(DAT_TABLE_SIZE, regval); |
| hci->DAT_entry_size = FIELD_GET(DAT_ENTRY_SIZE, regval); |
| dev_info(&hci->master.dev, "DAT: %u %u-bytes entries at offset %#x\n", |
| hci->DAT_entries, hci->DAT_entry_size * 4, offset); |
| |
| regval = reg_read(DCT_SECTION); |
| offset = FIELD_GET(DCT_TABLE_OFFSET, regval); |
| hci->DCT_regs = offset ? hci->base_regs + offset : NULL; |
| hci->DCT_entries = FIELD_GET(DCT_TABLE_SIZE, regval); |
| hci->DCT_entry_size = FIELD_GET(DCT_ENTRY_SIZE, regval); |
| dev_info(&hci->master.dev, "DCT: %u %u-bytes entries at offset %#x\n", |
| hci->DCT_entries, hci->DCT_entry_size * 4, offset); |
| |
| regval = reg_read(RING_HEADERS_SECTION); |
| offset = FIELD_GET(RING_HEADERS_OFFSET, regval); |
| hci->RHS_regs = offset ? hci->base_regs + offset : NULL; |
| dev_info(&hci->master.dev, "Ring Headers at offset %#x\n", offset); |
| |
| regval = reg_read(PIO_SECTION); |
| offset = FIELD_GET(PIO_REGS_OFFSET, regval); |
| hci->PIO_regs = offset ? hci->base_regs + offset : NULL; |
| dev_info(&hci->master.dev, "PIO section at offset %#x\n", offset); |
| |
| regval = reg_read(EXT_CAPS_SECTION); |
| offset = FIELD_GET(EXT_CAPS_OFFSET, regval); |
| hci->EXTCAPS_regs = offset ? hci->base_regs + offset : NULL; |
| dev_info(&hci->master.dev, "Extended Caps at offset %#x\n", offset); |
| |
| ret = i3c_hci_parse_ext_caps(hci); |
| if (ret) |
| return ret; |
| |
| /* |
| * Now let's reset the hardware. |
| * SOFT_RST must be clear before we write to it. |
| * Then we must wait until it clears again. |
| */ |
| ret = readx_poll_timeout(reg_read, RESET_CONTROL, regval, |
| !(regval & SOFT_RST), 1, 10000); |
| if (ret) |
| return -ENXIO; |
| reg_write(RESET_CONTROL, SOFT_RST); |
| ret = readx_poll_timeout(reg_read, RESET_CONTROL, regval, |
| !(regval & SOFT_RST), 1, 10000); |
| if (ret) |
| return -ENXIO; |
| |
| /* Disable all interrupts and allow all signal updates */ |
| reg_write(INTR_SIGNAL_ENABLE, 0x0); |
| reg_write(INTR_STATUS_ENABLE, 0xffffffff); |
| |
| /* Make sure our data ordering fits the host's */ |
| regval = reg_read(HC_CONTROL); |
| if (IS_ENABLED(CONFIG_BIG_ENDIAN)) { |
| if (!(regval & HC_CONTROL_DATA_BIG_ENDIAN)) { |
| regval |= HC_CONTROL_DATA_BIG_ENDIAN; |
| reg_write(HC_CONTROL, regval); |
| regval = reg_read(HC_CONTROL); |
| if (!(regval & HC_CONTROL_DATA_BIG_ENDIAN)) { |
| dev_err(&hci->master.dev, "cannot set BE mode\n"); |
| return -EOPNOTSUPP; |
| } |
| } |
| } else { |
| if (regval & HC_CONTROL_DATA_BIG_ENDIAN) { |
| regval &= ~HC_CONTROL_DATA_BIG_ENDIAN; |
| reg_write(HC_CONTROL, regval); |
| regval = reg_read(HC_CONTROL); |
| if (regval & HC_CONTROL_DATA_BIG_ENDIAN) { |
| dev_err(&hci->master.dev, "cannot clear BE mode\n"); |
| return -EOPNOTSUPP; |
| } |
| } |
| } |
| |
| /* Select our command descriptor model */ |
| switch (FIELD_GET(HC_CAP_CMD_SIZE, hci->caps)) { |
| case 0: |
| hci->cmd = &mipi_i3c_hci_cmd_v1; |
| break; |
| case 1: |
| hci->cmd = &mipi_i3c_hci_cmd_v2; |
| break; |
| default: |
| dev_err(&hci->master.dev, "wrong CMD_SIZE capability value\n"); |
| return -EINVAL; |
| } |
| |
| /* Try activating DMA operations first */ |
| if (hci->RHS_regs) { |
| reg_clear(HC_CONTROL, HC_CONTROL_PIO_MODE); |
| if (reg_read(HC_CONTROL) & HC_CONTROL_PIO_MODE) { |
| dev_err(&hci->master.dev, "PIO mode is stuck\n"); |
| ret = -EIO; |
| } else { |
| hci->io = &mipi_i3c_hci_dma; |
| dev_info(&hci->master.dev, "Using DMA\n"); |
| } |
| } |
| |
| /* If no DMA, try PIO */ |
| if (!hci->io && hci->PIO_regs) { |
| reg_set(HC_CONTROL, HC_CONTROL_PIO_MODE); |
| if (!(reg_read(HC_CONTROL) & HC_CONTROL_PIO_MODE)) { |
| dev_err(&hci->master.dev, "DMA mode is stuck\n"); |
| ret = -EIO; |
| } else { |
| hci->io = &mipi_i3c_hci_pio; |
| dev_info(&hci->master.dev, "Using PIO\n"); |
| } |
| } |
| |
| if (!hci->io) { |
| dev_err(&hci->master.dev, "neither DMA nor PIO can be used\n"); |
| if (!ret) |
| ret = -EINVAL; |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int i3c_hci_probe(struct platform_device *pdev) |
| { |
| struct i3c_hci *hci; |
| int irq, ret; |
| |
| hci = devm_kzalloc(&pdev->dev, sizeof(*hci), GFP_KERNEL); |
| if (!hci) |
| return -ENOMEM; |
| hci->base_regs = devm_platform_ioremap_resource(pdev, 0); |
| if (IS_ERR(hci->base_regs)) |
| return PTR_ERR(hci->base_regs); |
| |
| platform_set_drvdata(pdev, hci); |
| /* temporary for dev_printk's, to be replaced in i3c_master_register */ |
| hci->master.dev.init_name = dev_name(&pdev->dev); |
| |
| ret = i3c_hci_init(hci); |
| if (ret) |
| return ret; |
| |
| irq = platform_get_irq(pdev, 0); |
| ret = devm_request_irq(&pdev->dev, irq, i3c_hci_irq_handler, |
| 0, NULL, hci); |
| if (ret) |
| return ret; |
| |
| ret = i3c_master_register(&hci->master, &pdev->dev, |
| &i3c_hci_ops, false); |
| if (ret) |
| return ret; |
| |
| return 0; |
| } |
| |
| static int i3c_hci_remove(struct platform_device *pdev) |
| { |
| struct i3c_hci *hci = platform_get_drvdata(pdev); |
| int ret; |
| |
| ret = i3c_master_unregister(&hci->master); |
| if (ret) |
| return ret; |
| |
| return 0; |
| } |
| |
| static const __maybe_unused struct of_device_id i3c_hci_of_match[] = { |
| { .compatible = "mipi-i3c-hci", }, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(of, i3c_hci_of_match); |
| |
| static struct platform_driver i3c_hci_driver = { |
| .probe = i3c_hci_probe, |
| .remove = i3c_hci_remove, |
| .driver = { |
| .name = "mipi-i3c-hci", |
| .of_match_table = of_match_ptr(i3c_hci_of_match), |
| }, |
| }; |
| module_platform_driver(i3c_hci_driver); |
| |
| MODULE_AUTHOR("Nicolas Pitre <npitre@baylibre.com>"); |
| MODULE_DESCRIPTION("MIPI I3C HCI driver"); |
| MODULE_LICENSE("Dual BSD/GPL"); |