Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
config MHI_BUS_EP
tristate "Modem Host Interface (MHI) bus Endpoint implementation"
help
Bus driver for MHI protocol. Modem Host Interface (MHI) is a
communication protocol used by a host processor to control
and communicate a modem device over a high speed peripheral
bus or shared memory.
MHI_BUS_EP implements the MHI protocol for the endpoint devices,
such as SDX55 modem connected to the host machine over PCIe.
+2
View File
@@ -0,0 +1,2 @@
obj-$(CONFIG_MHI_BUS_EP) += mhi_ep.o
mhi_ep-y := main.o mmio.o ring.o sm.o
+222
View File
@@ -0,0 +1,222 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2022, Linaro Ltd.
*
*/
#ifndef _MHI_EP_INTERNAL_
#define _MHI_EP_INTERNAL_
#include <linux/bitfield.h>
#include "../common.h"
extern const struct bus_type mhi_ep_bus_type;
#define MHI_REG_OFFSET 0x100
#define BHI_REG_OFFSET 0x200
/* MHI registers */
#define EP_MHIREGLEN (MHI_REG_OFFSET + MHIREGLEN)
#define EP_MHIVER (MHI_REG_OFFSET + MHIVER)
#define EP_MHICFG (MHI_REG_OFFSET + MHICFG)
#define EP_CHDBOFF (MHI_REG_OFFSET + CHDBOFF)
#define EP_ERDBOFF (MHI_REG_OFFSET + ERDBOFF)
#define EP_BHIOFF (MHI_REG_OFFSET + BHIOFF)
#define EP_BHIEOFF (MHI_REG_OFFSET + BHIEOFF)
#define EP_DEBUGOFF (MHI_REG_OFFSET + DEBUGOFF)
#define EP_MHICTRL (MHI_REG_OFFSET + MHICTRL)
#define EP_MHISTATUS (MHI_REG_OFFSET + MHISTATUS)
#define EP_CCABAP_LOWER (MHI_REG_OFFSET + CCABAP_LOWER)
#define EP_CCABAP_HIGHER (MHI_REG_OFFSET + CCABAP_HIGHER)
#define EP_ECABAP_LOWER (MHI_REG_OFFSET + ECABAP_LOWER)
#define EP_ECABAP_HIGHER (MHI_REG_OFFSET + ECABAP_HIGHER)
#define EP_CRCBAP_LOWER (MHI_REG_OFFSET + CRCBAP_LOWER)
#define EP_CRCBAP_HIGHER (MHI_REG_OFFSET + CRCBAP_HIGHER)
#define EP_CRDB_LOWER (MHI_REG_OFFSET + CRDB_LOWER)
#define EP_CRDB_HIGHER (MHI_REG_OFFSET + CRDB_HIGHER)
#define EP_MHICTRLBASE_LOWER (MHI_REG_OFFSET + MHICTRLBASE_LOWER)
#define EP_MHICTRLBASE_HIGHER (MHI_REG_OFFSET + MHICTRLBASE_HIGHER)
#define EP_MHICTRLLIMIT_LOWER (MHI_REG_OFFSET + MHICTRLLIMIT_LOWER)
#define EP_MHICTRLLIMIT_HIGHER (MHI_REG_OFFSET + MHICTRLLIMIT_HIGHER)
#define EP_MHIDATABASE_LOWER (MHI_REG_OFFSET + MHIDATABASE_LOWER)
#define EP_MHIDATABASE_HIGHER (MHI_REG_OFFSET + MHIDATABASE_HIGHER)
#define EP_MHIDATALIMIT_LOWER (MHI_REG_OFFSET + MHIDATALIMIT_LOWER)
#define EP_MHIDATALIMIT_HIGHER (MHI_REG_OFFSET + MHIDATALIMIT_HIGHER)
/* MHI BHI registers */
#define EP_BHI_INTVEC (BHI_REG_OFFSET + BHI_INTVEC)
#define EP_BHI_EXECENV (BHI_REG_OFFSET + BHI_EXECENV)
/* MHI Doorbell registers */
#define CHDB_LOWER_n(n) (0x400 + 0x8 * (n))
#define CHDB_HIGHER_n(n) (0x404 + 0x8 * (n))
#define ERDB_LOWER_n(n) (0x800 + 0x8 * (n))
#define ERDB_HIGHER_n(n) (0x804 + 0x8 * (n))
#define MHI_CTRL_INT_STATUS 0x4
#define MHI_CTRL_INT_STATUS_MSK BIT(0)
#define MHI_CTRL_INT_STATUS_CRDB_MSK BIT(1)
#define MHI_CHDB_INT_STATUS_n(n) (0x28 + 0x4 * (n))
#define MHI_ERDB_INT_STATUS_n(n) (0x38 + 0x4 * (n))
#define MHI_CTRL_INT_CLEAR 0x4c
#define MHI_CTRL_INT_MMIO_WR_CLEAR BIT(2)
#define MHI_CTRL_INT_CRDB_CLEAR BIT(1)
#define MHI_CTRL_INT_CRDB_MHICTRL_CLEAR BIT(0)
#define MHI_CHDB_INT_CLEAR_n(n) (0x70 + 0x4 * (n))
#define MHI_CHDB_INT_CLEAR_n_CLEAR_ALL GENMASK(31, 0)
#define MHI_ERDB_INT_CLEAR_n(n) (0x80 + 0x4 * (n))
#define MHI_ERDB_INT_CLEAR_n_CLEAR_ALL GENMASK(31, 0)
/*
* Unlike the usual "masking" convention, writing "1" to a bit in this register
* enables the interrupt and writing "0" will disable it..
*/
#define MHI_CTRL_INT_MASK 0x94
#define MHI_CTRL_INT_MASK_MASK GENMASK(1, 0)
#define MHI_CTRL_MHICTRL_MASK BIT(0)
#define MHI_CTRL_CRDB_MASK BIT(1)
#define MHI_CHDB_INT_MASK_n(n) (0xb8 + 0x4 * (n))
#define MHI_CHDB_INT_MASK_n_EN_ALL GENMASK(31, 0)
#define MHI_ERDB_INT_MASK_n(n) (0xc8 + 0x4 * (n))
#define MHI_ERDB_INT_MASK_n_EN_ALL GENMASK(31, 0)
#define NR_OF_CMD_RINGS 1
#define MHI_MASK_ROWS_CH_DB 4
#define MHI_MASK_ROWS_EV_DB 4
#define MHI_MASK_CH_LEN 32
#define MHI_MASK_EV_LEN 32
/* Generic context */
struct mhi_generic_ctx {
__le32 reserved0;
__le32 reserved1;
__le32 reserved2;
__le64 rbase __packed __aligned(4);
__le64 rlen __packed __aligned(4);
__le64 rp __packed __aligned(4);
__le64 wp __packed __aligned(4);
};
enum mhi_ep_ring_type {
RING_TYPE_CMD,
RING_TYPE_ER,
RING_TYPE_CH,
};
/* Ring element */
union mhi_ep_ring_ctx {
struct mhi_cmd_ctxt cmd;
struct mhi_event_ctxt ev;
struct mhi_chan_ctxt ch;
struct mhi_generic_ctx generic;
};
struct mhi_ep_ring_item {
struct list_head node;
struct mhi_ep_ring *ring;
};
struct mhi_ep_ring {
struct mhi_ep_cntrl *mhi_cntrl;
union mhi_ep_ring_ctx *ring_ctx;
struct mhi_ring_element *ring_cache;
enum mhi_ep_ring_type type;
struct delayed_work intmodt_work;
u64 rbase;
size_t rd_offset;
size_t wr_offset;
size_t ring_size;
u32 db_offset_h;
u32 db_offset_l;
u32 ch_id;
u32 er_index;
u32 irq_vector;
u32 intmodt;
bool started;
bool irq_pending;
};
struct mhi_ep_cmd {
struct mhi_ep_ring ring;
};
struct mhi_ep_event {
struct mhi_ep_ring ring;
};
struct mhi_ep_state_transition {
struct list_head node;
enum mhi_state state;
};
struct mhi_ep_chan {
char *name;
struct mhi_ep_device *mhi_dev;
struct mhi_ep_ring ring;
struct mutex lock;
void (*xfer_cb)(struct mhi_ep_device *mhi_dev, struct mhi_result *result);
enum mhi_ch_state state;
enum dma_data_direction dir;
size_t rd_offset;
u64 tre_loc;
u32 tre_size;
u32 tre_bytes_left;
u32 chan;
bool skip_td;
};
/* MHI Ring related functions */
void mhi_ep_ring_init(struct mhi_ep_ring *ring, enum mhi_ep_ring_type type, u32 id);
void mhi_ep_ring_reset(struct mhi_ep_cntrl *mhi_cntrl, struct mhi_ep_ring *ring);
int mhi_ep_ring_start(struct mhi_ep_cntrl *mhi_cntrl, struct mhi_ep_ring *ring,
union mhi_ep_ring_ctx *ctx);
size_t mhi_ep_ring_addr2offset(struct mhi_ep_ring *ring, u64 ptr);
int mhi_ep_ring_add_element(struct mhi_ep_ring *ring, struct mhi_ring_element *element);
void mhi_ep_ring_inc_index(struct mhi_ep_ring *ring);
int mhi_ep_update_wr_offset(struct mhi_ep_ring *ring);
/* MMIO related functions */
u32 mhi_ep_mmio_read(struct mhi_ep_cntrl *mhi_cntrl, u32 offset);
void mhi_ep_mmio_write(struct mhi_ep_cntrl *mhi_cntrl, u32 offset, u32 val);
void mhi_ep_mmio_masked_write(struct mhi_ep_cntrl *mhi_cntrl, u32 offset, u32 mask, u32 val);
u32 mhi_ep_mmio_masked_read(struct mhi_ep_cntrl *dev, u32 offset, u32 mask);
void mhi_ep_mmio_enable_ctrl_interrupt(struct mhi_ep_cntrl *mhi_cntrl);
void mhi_ep_mmio_disable_ctrl_interrupt(struct mhi_ep_cntrl *mhi_cntrl);
void mhi_ep_mmio_enable_cmdb_interrupt(struct mhi_ep_cntrl *mhi_cntrl);
void mhi_ep_mmio_disable_cmdb_interrupt(struct mhi_ep_cntrl *mhi_cntrl);
void mhi_ep_mmio_enable_chdb(struct mhi_ep_cntrl *mhi_cntrl, u32 ch_id);
void mhi_ep_mmio_disable_chdb(struct mhi_ep_cntrl *mhi_cntrl, u32 ch_id);
void mhi_ep_mmio_enable_chdb_interrupts(struct mhi_ep_cntrl *mhi_cntrl);
bool mhi_ep_mmio_read_chdb_status_interrupts(struct mhi_ep_cntrl *mhi_cntrl);
void mhi_ep_mmio_mask_interrupts(struct mhi_ep_cntrl *mhi_cntrl);
void mhi_ep_mmio_get_chc_base(struct mhi_ep_cntrl *mhi_cntrl);
void mhi_ep_mmio_get_erc_base(struct mhi_ep_cntrl *mhi_cntrl);
void mhi_ep_mmio_get_crc_base(struct mhi_ep_cntrl *mhi_cntrl);
u64 mhi_ep_mmio_get_db(struct mhi_ep_ring *ring);
void mhi_ep_mmio_set_env(struct mhi_ep_cntrl *mhi_cntrl, u32 value);
void mhi_ep_mmio_clear_reset(struct mhi_ep_cntrl *mhi_cntrl);
void mhi_ep_mmio_reset(struct mhi_ep_cntrl *mhi_cntrl);
void mhi_ep_mmio_get_mhi_state(struct mhi_ep_cntrl *mhi_cntrl, enum mhi_state *state,
bool *mhi_reset);
void mhi_ep_mmio_init(struct mhi_ep_cntrl *mhi_cntrl);
void mhi_ep_mmio_update_ner(struct mhi_ep_cntrl *mhi_cntrl);
/* MHI EP core functions */
int mhi_ep_send_state_change_event(struct mhi_ep_cntrl *mhi_cntrl, enum mhi_state state);
int mhi_ep_send_ee_event(struct mhi_ep_cntrl *mhi_cntrl, enum mhi_ee_type exec_env);
bool mhi_ep_check_mhi_state(struct mhi_ep_cntrl *mhi_cntrl, enum mhi_state cur_mhi_state,
enum mhi_state mhi_state);
int mhi_ep_set_mhi_state(struct mhi_ep_cntrl *mhi_cntrl, enum mhi_state mhi_state);
int mhi_ep_set_m0_state(struct mhi_ep_cntrl *mhi_cntrl);
int mhi_ep_set_m3_state(struct mhi_ep_cntrl *mhi_cntrl);
int mhi_ep_set_ready_state(struct mhi_ep_cntrl *mhi_cntrl);
void mhi_ep_handle_syserr(struct mhi_ep_cntrl *mhi_cntrl);
void mhi_ep_resume_channels(struct mhi_ep_cntrl *mhi_cntrl);
void mhi_ep_suspend_channels(struct mhi_ep_cntrl *mhi_cntrl);
#endif
File diff suppressed because it is too large Load Diff
+273
View File
@@ -0,0 +1,273 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2022 Linaro Ltd.
* Author: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
*/
#include <linux/bitfield.h>
#include <linux/io.h>
#include <linux/mhi_ep.h>
#include "internal.h"
u32 mhi_ep_mmio_read(struct mhi_ep_cntrl *mhi_cntrl, u32 offset)
{
return readl(mhi_cntrl->mmio + offset);
}
void mhi_ep_mmio_write(struct mhi_ep_cntrl *mhi_cntrl, u32 offset, u32 val)
{
writel(val, mhi_cntrl->mmio + offset);
}
void mhi_ep_mmio_masked_write(struct mhi_ep_cntrl *mhi_cntrl, u32 offset, u32 mask, u32 val)
{
u32 regval;
regval = mhi_ep_mmio_read(mhi_cntrl, offset);
regval &= ~mask;
regval |= (val << __ffs(mask)) & mask;
mhi_ep_mmio_write(mhi_cntrl, offset, regval);
}
u32 mhi_ep_mmio_masked_read(struct mhi_ep_cntrl *dev, u32 offset, u32 mask)
{
u32 regval;
regval = mhi_ep_mmio_read(dev, offset);
regval &= mask;
regval >>= __ffs(mask);
return regval;
}
void mhi_ep_mmio_get_mhi_state(struct mhi_ep_cntrl *mhi_cntrl, enum mhi_state *state,
bool *mhi_reset)
{
u32 regval;
regval = mhi_ep_mmio_read(mhi_cntrl, EP_MHICTRL);
*state = FIELD_GET(MHICTRL_MHISTATE_MASK, regval);
*mhi_reset = !!FIELD_GET(MHICTRL_RESET_MASK, regval);
}
static void mhi_ep_mmio_set_chdb(struct mhi_ep_cntrl *mhi_cntrl, u32 ch_id, bool enable)
{
u32 chid_mask, chid_shift, chdb_idx, val;
chid_shift = ch_id % 32;
chid_mask = BIT(chid_shift);
chdb_idx = ch_id / 32;
val = enable ? 1 : 0;
mhi_ep_mmio_masked_write(mhi_cntrl, MHI_CHDB_INT_MASK_n(chdb_idx), chid_mask, val);
/* Update the local copy of the channel mask */
mhi_cntrl->chdb[chdb_idx].mask &= ~chid_mask;
mhi_cntrl->chdb[chdb_idx].mask |= val << chid_shift;
}
void mhi_ep_mmio_enable_chdb(struct mhi_ep_cntrl *mhi_cntrl, u32 ch_id)
{
mhi_ep_mmio_set_chdb(mhi_cntrl, ch_id, true);
}
void mhi_ep_mmio_disable_chdb(struct mhi_ep_cntrl *mhi_cntrl, u32 ch_id)
{
mhi_ep_mmio_set_chdb(mhi_cntrl, ch_id, false);
}
static void mhi_ep_mmio_set_chdb_interrupts(struct mhi_ep_cntrl *mhi_cntrl, bool enable)
{
u32 val, i;
val = enable ? MHI_CHDB_INT_MASK_n_EN_ALL : 0;
for (i = 0; i < MHI_MASK_ROWS_CH_DB; i++) {
mhi_ep_mmio_write(mhi_cntrl, MHI_CHDB_INT_MASK_n(i), val);
mhi_cntrl->chdb[i].mask = val;
}
}
void mhi_ep_mmio_enable_chdb_interrupts(struct mhi_ep_cntrl *mhi_cntrl)
{
mhi_ep_mmio_set_chdb_interrupts(mhi_cntrl, true);
}
static void mhi_ep_mmio_mask_chdb_interrupts(struct mhi_ep_cntrl *mhi_cntrl)
{
mhi_ep_mmio_set_chdb_interrupts(mhi_cntrl, false);
}
bool mhi_ep_mmio_read_chdb_status_interrupts(struct mhi_ep_cntrl *mhi_cntrl)
{
bool chdb = false;
u32 i;
for (i = 0; i < MHI_MASK_ROWS_CH_DB; i++) {
mhi_cntrl->chdb[i].status = mhi_ep_mmio_read(mhi_cntrl, MHI_CHDB_INT_STATUS_n(i));
if (mhi_cntrl->chdb[i].status)
chdb = true;
}
/* Return whether a channel doorbell interrupt occurred or not */
return chdb;
}
static void mhi_ep_mmio_set_erdb_interrupts(struct mhi_ep_cntrl *mhi_cntrl, bool enable)
{
u32 val, i;
val = enable ? MHI_ERDB_INT_MASK_n_EN_ALL : 0;
for (i = 0; i < MHI_MASK_ROWS_EV_DB; i++)
mhi_ep_mmio_write(mhi_cntrl, MHI_ERDB_INT_MASK_n(i), val);
}
static void mhi_ep_mmio_mask_erdb_interrupts(struct mhi_ep_cntrl *mhi_cntrl)
{
mhi_ep_mmio_set_erdb_interrupts(mhi_cntrl, false);
}
void mhi_ep_mmio_enable_ctrl_interrupt(struct mhi_ep_cntrl *mhi_cntrl)
{
mhi_ep_mmio_masked_write(mhi_cntrl, MHI_CTRL_INT_MASK,
MHI_CTRL_MHICTRL_MASK, 1);
}
void mhi_ep_mmio_disable_ctrl_interrupt(struct mhi_ep_cntrl *mhi_cntrl)
{
mhi_ep_mmio_masked_write(mhi_cntrl, MHI_CTRL_INT_MASK,
MHI_CTRL_MHICTRL_MASK, 0);
}
void mhi_ep_mmio_enable_cmdb_interrupt(struct mhi_ep_cntrl *mhi_cntrl)
{
mhi_ep_mmio_masked_write(mhi_cntrl, MHI_CTRL_INT_MASK,
MHI_CTRL_CRDB_MASK, 1);
}
void mhi_ep_mmio_disable_cmdb_interrupt(struct mhi_ep_cntrl *mhi_cntrl)
{
mhi_ep_mmio_masked_write(mhi_cntrl, MHI_CTRL_INT_MASK,
MHI_CTRL_CRDB_MASK, 0);
}
void mhi_ep_mmio_mask_interrupts(struct mhi_ep_cntrl *mhi_cntrl)
{
mhi_ep_mmio_disable_ctrl_interrupt(mhi_cntrl);
mhi_ep_mmio_disable_cmdb_interrupt(mhi_cntrl);
mhi_ep_mmio_mask_chdb_interrupts(mhi_cntrl);
mhi_ep_mmio_mask_erdb_interrupts(mhi_cntrl);
}
static void mhi_ep_mmio_clear_interrupts(struct mhi_ep_cntrl *mhi_cntrl)
{
u32 i;
for (i = 0; i < MHI_MASK_ROWS_CH_DB; i++)
mhi_ep_mmio_write(mhi_cntrl, MHI_CHDB_INT_CLEAR_n(i),
MHI_CHDB_INT_CLEAR_n_CLEAR_ALL);
for (i = 0; i < MHI_MASK_ROWS_EV_DB; i++)
mhi_ep_mmio_write(mhi_cntrl, MHI_ERDB_INT_CLEAR_n(i),
MHI_ERDB_INT_CLEAR_n_CLEAR_ALL);
mhi_ep_mmio_write(mhi_cntrl, MHI_CTRL_INT_CLEAR,
MHI_CTRL_INT_MMIO_WR_CLEAR |
MHI_CTRL_INT_CRDB_CLEAR |
MHI_CTRL_INT_CRDB_MHICTRL_CLEAR);
}
void mhi_ep_mmio_get_chc_base(struct mhi_ep_cntrl *mhi_cntrl)
{
u32 regval;
regval = mhi_ep_mmio_read(mhi_cntrl, EP_CCABAP_HIGHER);
mhi_cntrl->ch_ctx_host_pa = regval;
mhi_cntrl->ch_ctx_host_pa <<= 32;
regval = mhi_ep_mmio_read(mhi_cntrl, EP_CCABAP_LOWER);
mhi_cntrl->ch_ctx_host_pa |= regval;
}
void mhi_ep_mmio_get_erc_base(struct mhi_ep_cntrl *mhi_cntrl)
{
u32 regval;
regval = mhi_ep_mmio_read(mhi_cntrl, EP_ECABAP_HIGHER);
mhi_cntrl->ev_ctx_host_pa = regval;
mhi_cntrl->ev_ctx_host_pa <<= 32;
regval = mhi_ep_mmio_read(mhi_cntrl, EP_ECABAP_LOWER);
mhi_cntrl->ev_ctx_host_pa |= regval;
}
void mhi_ep_mmio_get_crc_base(struct mhi_ep_cntrl *mhi_cntrl)
{
u32 regval;
regval = mhi_ep_mmio_read(mhi_cntrl, EP_CRCBAP_HIGHER);
mhi_cntrl->cmd_ctx_host_pa = regval;
mhi_cntrl->cmd_ctx_host_pa <<= 32;
regval = mhi_ep_mmio_read(mhi_cntrl, EP_CRCBAP_LOWER);
mhi_cntrl->cmd_ctx_host_pa |= regval;
}
u64 mhi_ep_mmio_get_db(struct mhi_ep_ring *ring)
{
struct mhi_ep_cntrl *mhi_cntrl = ring->mhi_cntrl;
u64 db_offset;
u32 regval;
regval = mhi_ep_mmio_read(mhi_cntrl, ring->db_offset_h);
db_offset = regval;
db_offset <<= 32;
regval = mhi_ep_mmio_read(mhi_cntrl, ring->db_offset_l);
db_offset |= regval;
return db_offset;
}
void mhi_ep_mmio_set_env(struct mhi_ep_cntrl *mhi_cntrl, u32 value)
{
mhi_ep_mmio_write(mhi_cntrl, EP_BHI_EXECENV, value);
}
void mhi_ep_mmio_clear_reset(struct mhi_ep_cntrl *mhi_cntrl)
{
mhi_ep_mmio_masked_write(mhi_cntrl, EP_MHICTRL, MHICTRL_RESET_MASK, 0);
}
void mhi_ep_mmio_reset(struct mhi_ep_cntrl *mhi_cntrl)
{
mhi_ep_mmio_write(mhi_cntrl, EP_MHICTRL, 0);
mhi_ep_mmio_write(mhi_cntrl, EP_MHISTATUS, 0);
mhi_ep_mmio_clear_interrupts(mhi_cntrl);
}
void mhi_ep_mmio_init(struct mhi_ep_cntrl *mhi_cntrl)
{
u32 regval;
mhi_cntrl->chdb_offset = mhi_ep_mmio_read(mhi_cntrl, EP_CHDBOFF);
mhi_cntrl->erdb_offset = mhi_ep_mmio_read(mhi_cntrl, EP_ERDBOFF);
regval = mhi_ep_mmio_read(mhi_cntrl, EP_MHICFG);
mhi_cntrl->event_rings = FIELD_GET(MHICFG_NER_MASK, regval);
mhi_cntrl->hw_event_rings = FIELD_GET(MHICFG_NHWER_MASK, regval);
mhi_ep_mmio_reset(mhi_cntrl);
}
void mhi_ep_mmio_update_ner(struct mhi_ep_cntrl *mhi_cntrl)
{
u32 regval;
regval = mhi_ep_mmio_read(mhi_cntrl, EP_MHICFG);
mhi_cntrl->event_rings = FIELD_GET(MHICFG_NER_MASK, regval);
mhi_cntrl->hw_event_rings = FIELD_GET(MHICFG_NHWER_MASK, regval);
}
+234
View File
@@ -0,0 +1,234 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2022 Linaro Ltd.
* Author: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
*/
#include <linux/mhi_ep.h>
#include "internal.h"
size_t mhi_ep_ring_addr2offset(struct mhi_ep_ring *ring, u64 ptr)
{
return (ptr - ring->rbase) / sizeof(struct mhi_ring_element);
}
static u32 mhi_ep_ring_num_elems(struct mhi_ep_ring *ring)
{
__le64 rlen;
memcpy_fromio(&rlen, (void __iomem *) &ring->ring_ctx->generic.rlen, sizeof(u64));
return le64_to_cpu(rlen) / sizeof(struct mhi_ring_element);
}
void mhi_ep_ring_inc_index(struct mhi_ep_ring *ring)
{
ring->rd_offset = (ring->rd_offset + 1) % ring->ring_size;
}
static int __mhi_ep_cache_ring(struct mhi_ep_ring *ring, size_t end)
{
struct mhi_ep_cntrl *mhi_cntrl = ring->mhi_cntrl;
struct device *dev = &mhi_cntrl->mhi_dev->dev;
struct mhi_ep_buf_info buf_info = {};
size_t start;
int ret;
/* Don't proceed in the case of event ring. This happens during mhi_ep_ring_start(). */
if (ring->type == RING_TYPE_ER)
return 0;
/* No need to cache the ring if write pointer is unmodified */
if (ring->wr_offset == end)
return 0;
start = ring->wr_offset;
if (start < end) {
buf_info.size = (end - start) * sizeof(struct mhi_ring_element);
buf_info.host_addr = ring->rbase + (start * sizeof(struct mhi_ring_element));
buf_info.dev_addr = &ring->ring_cache[start];
ret = mhi_cntrl->read_sync(mhi_cntrl, &buf_info);
if (ret)
return ret;
} else {
buf_info.size = (ring->ring_size - start) * sizeof(struct mhi_ring_element);
buf_info.host_addr = ring->rbase + (start * sizeof(struct mhi_ring_element));
buf_info.dev_addr = &ring->ring_cache[start];
ret = mhi_cntrl->read_sync(mhi_cntrl, &buf_info);
if (ret)
return ret;
if (end) {
buf_info.host_addr = ring->rbase;
buf_info.dev_addr = &ring->ring_cache[0];
buf_info.size = end * sizeof(struct mhi_ring_element);
ret = mhi_cntrl->read_sync(mhi_cntrl, &buf_info);
if (ret)
return ret;
}
}
dev_dbg(dev, "Cached ring: start %zu end %zu size %zu\n", start, end, buf_info.size);
return 0;
}
static int mhi_ep_cache_ring(struct mhi_ep_ring *ring, u64 wr_ptr)
{
size_t wr_offset;
int ret;
wr_offset = mhi_ep_ring_addr2offset(ring, wr_ptr);
/* Cache the host ring till write offset */
ret = __mhi_ep_cache_ring(ring, wr_offset);
if (ret)
return ret;
ring->wr_offset = wr_offset;
return 0;
}
int mhi_ep_update_wr_offset(struct mhi_ep_ring *ring)
{
u64 wr_ptr;
wr_ptr = mhi_ep_mmio_get_db(ring);
return mhi_ep_cache_ring(ring, wr_ptr);
}
/* TODO: Support for adding multiple ring elements to the ring */
int mhi_ep_ring_add_element(struct mhi_ep_ring *ring, struct mhi_ring_element *el)
{
struct mhi_ep_cntrl *mhi_cntrl = ring->mhi_cntrl;
struct device *dev = &mhi_cntrl->mhi_dev->dev;
struct mhi_ep_buf_info buf_info = {};
size_t old_offset = 0;
u32 num_free_elem;
__le64 rp;
int ret;
ret = mhi_ep_update_wr_offset(ring);
if (ret) {
dev_err(dev, "Error updating write pointer\n");
return ret;
}
if (ring->rd_offset < ring->wr_offset)
num_free_elem = (ring->wr_offset - ring->rd_offset) - 1;
else
num_free_elem = ((ring->ring_size - ring->rd_offset) + ring->wr_offset) - 1;
/* Check if there is space in ring for adding at least an element */
if (!num_free_elem) {
dev_err(dev, "No space left in the ring\n");
return -ENOSPC;
}
old_offset = ring->rd_offset;
dev_dbg(dev, "Adding an element to ring at offset (%zu)\n", ring->rd_offset);
buf_info.host_addr = ring->rbase + (old_offset * sizeof(*el));
buf_info.dev_addr = el;
buf_info.size = sizeof(*el);
ret = mhi_cntrl->write_sync(mhi_cntrl, &buf_info);
if (ret)
return ret;
mhi_ep_ring_inc_index(ring);
/* Update rp in ring context */
rp = cpu_to_le64(ring->rd_offset * sizeof(*el) + ring->rbase);
memcpy_toio((void __iomem *) &ring->ring_ctx->generic.rp, &rp, sizeof(u64));
return ret;
}
void mhi_ep_ring_init(struct mhi_ep_ring *ring, enum mhi_ep_ring_type type, u32 id)
{
ring->type = type;
if (ring->type == RING_TYPE_CMD) {
ring->db_offset_h = EP_CRDB_HIGHER;
ring->db_offset_l = EP_CRDB_LOWER;
} else if (ring->type == RING_TYPE_CH) {
ring->db_offset_h = CHDB_HIGHER_n(id);
ring->db_offset_l = CHDB_LOWER_n(id);
ring->ch_id = id;
} else {
ring->db_offset_h = ERDB_HIGHER_n(id);
ring->db_offset_l = ERDB_LOWER_n(id);
}
}
static void mhi_ep_raise_irq(struct work_struct *work)
{
struct mhi_ep_ring *ring = container_of(work, struct mhi_ep_ring, intmodt_work.work);
struct mhi_ep_cntrl *mhi_cntrl = ring->mhi_cntrl;
mhi_cntrl->raise_irq(mhi_cntrl, ring->irq_vector);
WRITE_ONCE(ring->irq_pending, false);
}
int mhi_ep_ring_start(struct mhi_ep_cntrl *mhi_cntrl, struct mhi_ep_ring *ring,
union mhi_ep_ring_ctx *ctx)
{
struct device *dev = &mhi_cntrl->mhi_dev->dev;
__le64 val;
int ret;
ring->mhi_cntrl = mhi_cntrl;
ring->ring_ctx = ctx;
ring->ring_size = mhi_ep_ring_num_elems(ring);
memcpy_fromio(&val, (void __iomem *) &ring->ring_ctx->generic.rbase, sizeof(u64));
ring->rbase = le64_to_cpu(val);
if (ring->type == RING_TYPE_CH)
ring->er_index = le32_to_cpu(ring->ring_ctx->ch.erindex);
if (ring->type == RING_TYPE_ER) {
ring->irq_vector = le32_to_cpu(ring->ring_ctx->ev.msivec);
ring->intmodt = FIELD_GET(EV_CTX_INTMODT_MASK,
le32_to_cpu(ring->ring_ctx->ev.intmod));
INIT_DELAYED_WORK(&ring->intmodt_work, mhi_ep_raise_irq);
}
/* During ring init, both rp and wp are equal */
memcpy_fromio(&val, (void __iomem *) &ring->ring_ctx->generic.rp, sizeof(u64));
ring->rd_offset = mhi_ep_ring_addr2offset(ring, le64_to_cpu(val));
ring->wr_offset = mhi_ep_ring_addr2offset(ring, le64_to_cpu(val));
/* Allocate ring cache memory for holding the copy of host ring */
ring->ring_cache = kzalloc_objs(struct mhi_ring_element,
ring->ring_size);
if (!ring->ring_cache)
return -ENOMEM;
memcpy_fromio(&val, (void __iomem *) &ring->ring_ctx->generic.wp, sizeof(u64));
ret = mhi_ep_cache_ring(ring, le64_to_cpu(val));
if (ret) {
dev_err(dev, "Failed to cache ring\n");
kfree(ring->ring_cache);
return ret;
}
ring->started = true;
return 0;
}
void mhi_ep_ring_reset(struct mhi_ep_cntrl *mhi_cntrl, struct mhi_ep_ring *ring)
{
if (ring->type == RING_TYPE_ER)
cancel_delayed_work_sync(&ring->intmodt_work);
ring->started = false;
kfree(ring->ring_cache);
ring->ring_cache = NULL;
}
+154
View File
@@ -0,0 +1,154 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2022 Linaro Ltd.
* Author: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
*/
#include <linux/errno.h>
#include <linux/mhi_ep.h>
#include "internal.h"
bool __must_check mhi_ep_check_mhi_state(struct mhi_ep_cntrl *mhi_cntrl,
enum mhi_state cur_mhi_state,
enum mhi_state mhi_state)
{
if (mhi_state == MHI_STATE_SYS_ERR)
return true; /* Allowed in any state */
if (mhi_state == MHI_STATE_READY)
return cur_mhi_state == MHI_STATE_RESET;
if (mhi_state == MHI_STATE_M0)
return cur_mhi_state == MHI_STATE_M3 || cur_mhi_state == MHI_STATE_READY;
if (mhi_state == MHI_STATE_M3)
return cur_mhi_state == MHI_STATE_M0;
return false;
}
int mhi_ep_set_mhi_state(struct mhi_ep_cntrl *mhi_cntrl, enum mhi_state mhi_state)
{
struct device *dev = &mhi_cntrl->mhi_dev->dev;
if (!mhi_ep_check_mhi_state(mhi_cntrl, mhi_cntrl->mhi_state, mhi_state)) {
dev_err(dev, "MHI state change to %s from %s is not allowed!\n",
mhi_state_str(mhi_state),
mhi_state_str(mhi_cntrl->mhi_state));
return -EACCES;
}
/* TODO: Add support for M1 and M2 states */
if (mhi_state == MHI_STATE_M1 || mhi_state == MHI_STATE_M2) {
dev_err(dev, "MHI state (%s) not supported\n", mhi_state_str(mhi_state));
return -EOPNOTSUPP;
}
mhi_ep_mmio_masked_write(mhi_cntrl, EP_MHISTATUS, MHISTATUS_MHISTATE_MASK, mhi_state);
mhi_cntrl->mhi_state = mhi_state;
if (mhi_state == MHI_STATE_READY)
mhi_ep_mmio_masked_write(mhi_cntrl, EP_MHISTATUS, MHISTATUS_READY_MASK, 1);
if (mhi_state == MHI_STATE_SYS_ERR)
mhi_ep_mmio_masked_write(mhi_cntrl, EP_MHISTATUS, MHISTATUS_SYSERR_MASK, 1);
return 0;
}
int mhi_ep_set_m0_state(struct mhi_ep_cntrl *mhi_cntrl)
{
struct device *dev = &mhi_cntrl->mhi_dev->dev;
enum mhi_state old_state;
int ret;
/* If MHI is in M3, resume suspended channels */
mutex_lock(&mhi_cntrl->state_lock);
old_state = mhi_cntrl->mhi_state;
if (old_state == MHI_STATE_M3)
mhi_ep_resume_channels(mhi_cntrl);
ret = mhi_ep_set_mhi_state(mhi_cntrl, MHI_STATE_M0);
if (ret) {
mhi_ep_handle_syserr(mhi_cntrl);
goto err_unlock;
}
/* Signal host that the device moved to M0 */
ret = mhi_ep_send_state_change_event(mhi_cntrl, MHI_STATE_M0);
if (ret) {
dev_err(dev, "Failed sending M0 state change event\n");
goto err_unlock;
}
if (old_state == MHI_STATE_READY) {
/* Send AMSS EE event to host */
ret = mhi_ep_send_ee_event(mhi_cntrl, MHI_EE_AMSS);
if (ret) {
dev_err(dev, "Failed sending AMSS EE event\n");
goto err_unlock;
}
}
err_unlock:
mutex_unlock(&mhi_cntrl->state_lock);
return ret;
}
int mhi_ep_set_m3_state(struct mhi_ep_cntrl *mhi_cntrl)
{
struct device *dev = &mhi_cntrl->mhi_dev->dev;
int ret;
mutex_lock(&mhi_cntrl->state_lock);
ret = mhi_ep_set_mhi_state(mhi_cntrl, MHI_STATE_M3);
if (ret) {
mhi_ep_handle_syserr(mhi_cntrl);
goto err_unlock;
}
mhi_ep_suspend_channels(mhi_cntrl);
/* Signal host that the device moved to M3 */
ret = mhi_ep_send_state_change_event(mhi_cntrl, MHI_STATE_M3);
if (ret) {
dev_err(dev, "Failed sending M3 state change event\n");
goto err_unlock;
}
err_unlock:
mutex_unlock(&mhi_cntrl->state_lock);
return ret;
}
int mhi_ep_set_ready_state(struct mhi_ep_cntrl *mhi_cntrl)
{
struct device *dev = &mhi_cntrl->mhi_dev->dev;
enum mhi_state mhi_state;
int ret, is_ready;
mutex_lock(&mhi_cntrl->state_lock);
/* Ensure that the MHISTATUS is set to RESET by host */
mhi_state = mhi_ep_mmio_masked_read(mhi_cntrl, EP_MHISTATUS, MHISTATUS_MHISTATE_MASK);
is_ready = mhi_ep_mmio_masked_read(mhi_cntrl, EP_MHISTATUS, MHISTATUS_READY_MASK);
if (mhi_state != MHI_STATE_RESET || is_ready) {
dev_err(dev, "READY state transition failed. MHI host not in RESET state\n");
ret = -EIO;
goto err_unlock;
}
ret = mhi_ep_set_mhi_state(mhi_cntrl, MHI_STATE_READY);
if (ret)
mhi_ep_handle_syserr(mhi_cntrl);
err_unlock:
mutex_unlock(&mhi_cntrl->state_lock);
return ret;
}