summaryrefslogtreecommitdiffstats
path: root/drivers/net/wwan/iosm/iosm_ipc_mmio.c
blob: 63eb08c43c0517ff2e95a25c3d5ee4b326bfdcc0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (C) 2020-21 Intel Corporation.
 */

#include <linux/delay.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/io-64-nonatomic-lo-hi.h>
#include <linux/slab.h>

#include "iosm_ipc_mmio.h"
#include "iosm_ipc_mux.h"

/* Definition of MMIO offsets
 * note that MMIO_CI offsets are relative to end of chip info structure
 */

/* MMIO chip info size in bytes */
#define MMIO_CHIP_INFO_SIZE 60

/* CP execution stage */
#define MMIO_OFFSET_EXECUTION_STAGE 0x00

/* Boot ROM Chip Info struct */
#define MMIO_OFFSET_CHIP_INFO 0x04

#define MMIO_OFFSET_ROM_EXIT_CODE 0x40

#define MMIO_OFFSET_PSI_ADDRESS 0x54

#define MMIO_OFFSET_PSI_SIZE 0x5C

#define MMIO_OFFSET_IPC_STATUS 0x60

#define MMIO_OFFSET_CONTEXT_INFO 0x64

#define MMIO_OFFSET_BASE_ADDR 0x6C

#define MMIO_OFFSET_END_ADDR 0x74

#define MMIO_OFFSET_CP_VERSION 0xF0

#define MMIO_OFFSET_CP_CAPABILITIES 0xF4

/* Timeout in 50 msec to wait for the modem boot code to write a valid
 * execution stage into mmio area
 */
#define IPC_MMIO_EXEC_STAGE_TIMEOUT 50

/* check if exec stage has one of the valid values */
static bool ipc_mmio_is_valid_exec_stage(enum ipc_mem_exec_stage stage)
{
	switch (stage) {
	case IPC_MEM_EXEC_STAGE_BOOT:
	case IPC_MEM_EXEC_STAGE_PSI:
	case IPC_MEM_EXEC_STAGE_EBL:
	case IPC_MEM_EXEC_STAGE_RUN:
	case IPC_MEM_EXEC_STAGE_CRASH:
	case IPC_MEM_EXEC_STAGE_CD_READY:
		return true;
	default:
		return false;
	}
}

void ipc_mmio_update_cp_capability(struct iosm_mmio *ipc_mmio)
{
	u32 cp_cap;
	unsigned int ver;

	ver = ipc_mmio_get_cp_version(ipc_mmio);
	cp_cap = ioread32(ipc_mmio->base + ipc_mmio->offset.cp_capability);

	ipc_mmio->mux_protocol = ((ver >= IOSM_CP_VERSION) && (cp_cap &
				 (UL_AGGR | DL_AGGR))) ? MUX_AGGREGATION
				 : MUX_LITE;

	ipc_mmio->has_ul_flow_credit =
		(ver >= IOSM_CP_VERSION) && (cp_cap & UL_FLOW_CREDIT);
}

struct iosm_mmio *ipc_mmio_init(void __iomem *mmio, struct device *dev)
{
	struct iosm_mmio *ipc_mmio = kzalloc(sizeof(*ipc_mmio), GFP_KERNEL);
	int retries = IPC_MMIO_EXEC_STAGE_TIMEOUT;
	enum ipc_mem_exec_stage stage;

	if (!ipc_mmio)
		return NULL;

	ipc_mmio->dev = dev;

	ipc_mmio->base = mmio;

	ipc_mmio->offset.exec_stage = MMIO_OFFSET_EXECUTION_STAGE;

	/* Check for a valid execution stage to make sure that the boot code
	 * has correctly initialized the MMIO area.
	 */
	do {
		stage = ipc_mmio_get_exec_stage(ipc_mmio);
		if (ipc_mmio_is_valid_exec_stage(stage))
			break;

		msleep(20);
	} while (retries-- > 0);

	if (!retries) {
		dev_err(ipc_mmio->dev, "invalid exec stage %X", stage);
		goto init_fail;
	}

	ipc_mmio->offset.chip_info = MMIO_OFFSET_CHIP_INFO;

	/* read chip info size and version from chip info structure */
	ipc_mmio->chip_info_version =
		ioread8(ipc_mmio->base + ipc_mmio->offset.chip_info);

	/* Increment of 2 is needed as the size value in the chip info
	 * excludes the version and size field, which are always present
	 */
	ipc_mmio->chip_info_size =
		ioread8(ipc_mmio->base + ipc_mmio->offset.chip_info + 1) + 2;

	if (ipc_mmio->chip_info_size != MMIO_CHIP_INFO_SIZE) {
		dev_err(ipc_mmio->dev, "Unexpected Chip Info");
		goto init_fail;
	}

	ipc_mmio->offset.rom_exit_code = MMIO_OFFSET_ROM_EXIT_CODE;

	ipc_mmio->offset.psi_address = MMIO_OFFSET_PSI_ADDRESS;
	ipc_mmio->offset.psi_size = MMIO_OFFSET_PSI_SIZE;
	ipc_mmio->offset.ipc_status = MMIO_OFFSET_IPC_STATUS;
	ipc_mmio->offset.context_info = MMIO_OFFSET_CONTEXT_INFO;
	ipc_mmio->offset.ap_win_base = MMIO_OFFSET_BASE_ADDR;
	ipc_mmio->offset.ap_win_end = MMIO_OFFSET_END_ADDR;

	ipc_mmio->offset.cp_version = MMIO_OFFSET_CP_VERSION;
	ipc_mmio->offset.cp_capability = MMIO_OFFSET_CP_CAPABILITIES;

	return ipc_mmio;

init_fail:
	kfree(ipc_mmio);
	return NULL;
}

enum ipc_mem_exec_stage ipc_mmio_get_exec_stage(struct iosm_mmio *ipc_mmio)
{
	if (!ipc_mmio)
		return IPC_MEM_EXEC_STAGE_INVALID;

	return (enum ipc_mem_exec_stage)ioread32(ipc_mmio->base +
						 ipc_mmio->offset.exec_stage);
}

void ipc_mmio_copy_chip_info(struct iosm_mmio *ipc_mmio, void *dest,
			     size_t size)
{
	if (ipc_mmio && dest)
		memcpy_fromio(dest, ipc_mmio->base + ipc_mmio->offset.chip_info,
			      size);
}

enum ipc_mem_device_ipc_state ipc_mmio_get_ipc_state(struct iosm_mmio *ipc_mmio)
{
	if (!ipc_mmio)
		return IPC_MEM_DEVICE_IPC_INVALID;

	return (enum ipc_mem_device_ipc_state)ioread32(ipc_mmio->base +
						       ipc_mmio->offset.ipc_status);
}

enum rom_exit_code ipc_mmio_get_rom_exit_code(struct iosm_mmio *ipc_mmio)
{
	if (!ipc_mmio)
		return IMEM_ROM_EXIT_FAIL;

	return (enum rom_exit_code)ioread32(ipc_mmio->base +
					    ipc_mmio->offset.rom_exit_code);
}

void ipc_mmio_config(struct iosm_mmio *ipc_mmio)
{
	if (!ipc_mmio)
		return;

	/* AP memory window (full window is open and active so that modem checks
	 * each AP address) 0 means don't check on modem side.
	 */
	iowrite64(0, ipc_mmio->base + ipc_mmio->offset.ap_win_base);
	iowrite64(0, ipc_mmio->base + ipc_mmio->offset.ap_win_end);

	iowrite64(ipc_mmio->context_info_addr,
		  ipc_mmio->base + ipc_mmio->offset.context_info);
}

void ipc_mmio_set_psi_addr_and_size(struct iosm_mmio *ipc_mmio, dma_addr_t addr,
				    u32 size)
{
	if (!ipc_mmio)
		return;

	iowrite64(addr, ipc_mmio->base + ipc_mmio->offset.psi_address);
	iowrite32(size, ipc_mmio->base + ipc_mmio->offset.psi_size);
}

void ipc_mmio_set_contex_info_addr(struct iosm_mmio *ipc_mmio, phys_addr_t addr)
{
	if (!ipc_mmio)
		return;

	/* store context_info address. This will be stored in the mmio area
	 * during IPC_MEM_DEVICE_IPC_INIT state via ipc_mmio_config()
	 */
	ipc_mmio->context_info_addr = addr;
}

int ipc_mmio_get_cp_version(struct iosm_mmio *ipc_mmio)
{
	if (ipc_mmio)
		return ioread32(ipc_mmio->base + ipc_mmio->offset.cp_version);

	return -EFAULT;
}