summaryrefslogtreecommitdiff
path: root/drivers/i3c/master/mipi-i3c-hci/dat_v1.c
blob: 783e551a2c85a3dbde96344b7971f8532dc20399 (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
// SPDX-License-Identifier: BSD-3-Clause
/*
 * Copyright (c) 2020, MIPI Alliance, Inc.
 *
 * Author: Nicolas Pitre <npitre@baylibre.com>
 */

#include <linux/bitfield.h>
#include <linux/bitmap.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/i3c/master.h>
#include <linux/io.h>

#include "hci.h"
#include "dat.h"


/*
 * Device Address Table Structure
 */

#define DAT_1_AUTOCMD_HDR_CODE		W1_MASK(58, 51)
#define DAT_1_AUTOCMD_MODE		W1_MASK(50, 48)
#define DAT_1_AUTOCMD_VALUE		W1_MASK(47, 40)
#define DAT_1_AUTOCMD_MASK		W1_MASK(39, 32)
/*	DAT_0_I2C_DEVICE		W0_BIT_(31) */
#define DAT_0_DEV_NACK_RETRY_CNT	W0_MASK(30, 29)
#define DAT_0_RING_ID			W0_MASK(28, 26)
#define DAT_0_DYNADDR_PARITY		W0_BIT_(23)
#define DAT_0_DYNAMIC_ADDRESS		W0_MASK(22, 16)
#define DAT_0_TS			W0_BIT_(15)
#define DAT_0_MR_REJECT			W0_BIT_(14)
/*	DAT_0_SIR_REJECT		W0_BIT_(13) */
/*	DAT_0_IBI_PAYLOAD		W0_BIT_(12) */
#define DAT_0_STATIC_ADDRESS		W0_MASK(6, 0)

#define dat_w0_read(i)		readl(hci->DAT_regs + (i) * 8)
#define dat_w1_read(i)		readl(hci->DAT_regs + (i) * 8 + 4)
#define dat_w0_write(i, v)	writel(v, hci->DAT_regs + (i) * 8)
#define dat_w1_write(i, v)	writel(v, hci->DAT_regs + (i) * 8 + 4)

static inline bool dynaddr_parity(unsigned int addr)
{
	addr |= 1 << 7;
	addr += addr >> 4;
	addr += addr >> 2;
	addr += addr >> 1;
	return (addr & 1);
}

static int hci_dat_v1_init(struct i3c_hci *hci)
{
	unsigned int dat_idx;

	if (!hci->DAT_regs) {
		dev_err(&hci->master.dev,
			"only DAT in register space is supported at the moment\n");
		return -EOPNOTSUPP;
	}
	if (hci->DAT_entry_size != 8) {
		dev_err(&hci->master.dev,
			"only 8-bytes DAT entries are supported at the moment\n");
		return -EOPNOTSUPP;
	}

	/* use a bitmap for faster free slot search */
	hci->DAT_data = bitmap_zalloc(hci->DAT_entries, GFP_KERNEL);
	if (!hci->DAT_data)
		return -ENOMEM;

	/* clear them */
	for (dat_idx = 0; dat_idx < hci->DAT_entries; dat_idx++) {
		dat_w0_write(dat_idx, 0);
		dat_w1_write(dat_idx, 0);
	}

	return 0;
}

static void hci_dat_v1_cleanup(struct i3c_hci *hci)
{
	bitmap_free(hci->DAT_data);
	hci->DAT_data = NULL;
}

static int hci_dat_v1_alloc_entry(struct i3c_hci *hci)
{
	unsigned int dat_idx;

	dat_idx = find_first_zero_bit(hci->DAT_data, hci->DAT_entries);
	if (dat_idx >= hci->DAT_entries)
		return -ENOENT;
	__set_bit(dat_idx, hci->DAT_data);

	/* default flags */
	dat_w0_write(dat_idx, DAT_0_SIR_REJECT | DAT_0_MR_REJECT);

	return dat_idx;
}

static void hci_dat_v1_free_entry(struct i3c_hci *hci, unsigned int dat_idx)
{
	dat_w0_write(dat_idx, 0);
	dat_w1_write(dat_idx, 0);
	__clear_bit(dat_idx, hci->DAT_data);
}

static void hci_dat_v1_set_dynamic_addr(struct i3c_hci *hci,
					unsigned int dat_idx, u8 address)
{
	u32 dat_w0;

	dat_w0 = dat_w0_read(dat_idx);
	dat_w0 &= ~(DAT_0_DYNAMIC_ADDRESS | DAT_0_DYNADDR_PARITY);
	dat_w0 |= FIELD_PREP(DAT_0_DYNAMIC_ADDRESS, address) |
		  (dynaddr_parity(address) ? DAT_0_DYNADDR_PARITY : 0);
	dat_w0_write(dat_idx, dat_w0);
}

static void hci_dat_v1_set_static_addr(struct i3c_hci *hci,
				       unsigned int dat_idx, u8 address)
{
	u32 dat_w0;

	dat_w0 = dat_w0_read(dat_idx);
	dat_w0 &= ~DAT_0_STATIC_ADDRESS;
	dat_w0 |= FIELD_PREP(DAT_0_STATIC_ADDRESS, address);
	dat_w0_write(dat_idx, dat_w0);
}

static void hci_dat_v1_set_flags(struct i3c_hci *hci, unsigned int dat_idx,
				 u32 w0_flags, u32 w1_flags)
{
	u32 dat_w0, dat_w1;

	dat_w0 = dat_w0_read(dat_idx);
	dat_w1 = dat_w1_read(dat_idx);
	dat_w0 |= w0_flags;
	dat_w1 |= w1_flags;
	dat_w0_write(dat_idx, dat_w0);
	dat_w1_write(dat_idx, dat_w1);
}

static void hci_dat_v1_clear_flags(struct i3c_hci *hci, unsigned int dat_idx,
				   u32 w0_flags, u32 w1_flags)
{
	u32 dat_w0, dat_w1;

	dat_w0 = dat_w0_read(dat_idx);
	dat_w1 = dat_w1_read(dat_idx);
	dat_w0 &= ~w0_flags;
	dat_w1 &= ~w1_flags;
	dat_w0_write(dat_idx, dat_w0);
	dat_w1_write(dat_idx, dat_w1);
}

static int hci_dat_v1_get_index(struct i3c_hci *hci, u8 dev_addr)
{
	unsigned int dat_idx;
	u32 dat_w0;

	for (dat_idx = find_first_bit(hci->DAT_data, hci->DAT_entries);
	     dat_idx < hci->DAT_entries;
	     dat_idx = find_next_bit(hci->DAT_data, hci->DAT_entries, dat_idx)) {
		dat_w0 = dat_w0_read(dat_idx);
		if (FIELD_GET(DAT_0_DYNAMIC_ADDRESS, dat_w0) == dev_addr)
			return dat_idx;
	}

	return -ENODEV;
}

const struct hci_dat_ops mipi_i3c_hci_dat_v1 = {
	.init			= hci_dat_v1_init,
	.cleanup		= hci_dat_v1_cleanup,
	.alloc_entry		= hci_dat_v1_alloc_entry,
	.free_entry		= hci_dat_v1_free_entry,
	.set_dynamic_addr	= hci_dat_v1_set_dynamic_addr,
	.set_static_addr	= hci_dat_v1_set_static_addr,
	.set_flags		= hci_dat_v1_set_flags,
	.clear_flags		= hci_dat_v1_clear_flags,
	.get_index		= hci_dat_v1_get_index,
};