1// SPDX-License-Identifier: GPL-2.0+
2
3#include <linux/io.h>
4#include "ipmi_si.h"
5
6static unsigned char intf_mem_inb(const struct si_sm_io *io,
7				  unsigned int offset)
8{
9	return readb((io->addr)+(offset * io->regspacing));
10}
11
12static void intf_mem_outb(const struct si_sm_io *io, unsigned int offset,
13			  unsigned char b)
14{
15	writeb(b, (io->addr)+(offset * io->regspacing));
16}
17
18static unsigned char intf_mem_inw(const struct si_sm_io *io,
19				  unsigned int offset)
20{
21	return (readw((io->addr)+(offset * io->regspacing)) >> io->regshift)
22		& 0xff;
23}
24
25static void intf_mem_outw(const struct si_sm_io *io, unsigned int offset,
26			  unsigned char b)
27{
28	writeb(b << io->regshift, (io->addr)+(offset * io->regspacing));
29}
30
31static unsigned char intf_mem_inl(const struct si_sm_io *io,
32				  unsigned int offset)
33{
34	return (readl((io->addr)+(offset * io->regspacing)) >> io->regshift)
35		& 0xff;
36}
37
38static void intf_mem_outl(const struct si_sm_io *io, unsigned int offset,
39			  unsigned char b)
40{
41	writel(b << io->regshift, (io->addr)+(offset * io->regspacing));
42}
43
44#ifdef readq
45static unsigned char mem_inq(const struct si_sm_io *io, unsigned int offset)
46{
47	return (readq((io->addr)+(offset * io->regspacing)) >> io->regshift)
48		& 0xff;
49}
50
51static void mem_outq(const struct si_sm_io *io, unsigned int offset,
52		     unsigned char b)
53{
54	writeq((u64)b << io->regshift, (io->addr)+(offset * io->regspacing));
55}
56#endif
57
58static void mem_region_cleanup(struct si_sm_io *io, int num)
59{
60	unsigned long addr = io->addr_data;
61	int idx;
62
63	for (idx = 0; idx < num; idx++)
64		release_mem_region(addr + idx * io->regspacing,
65				   io->regsize);
66}
67
68static void mem_cleanup(struct si_sm_io *io)
69{
70	if (io->addr) {
71		iounmap(io->addr);
72		mem_region_cleanup(io, io->io_size);
73	}
74}
75
76int ipmi_si_mem_setup(struct si_sm_io *io)
77{
78	unsigned long addr = io->addr_data;
79	int           mapsize, idx;
80
81	if (!addr)
82		return -ENODEV;
83
84	/*
85	 * Figure out the actual readb/readw/readl/etc routine to use based
86	 * upon the register size.
87	 */
88	switch (io->regsize) {
89	case 1:
90		io->inputb = intf_mem_inb;
91		io->outputb = intf_mem_outb;
92		break;
93	case 2:
94		io->inputb = intf_mem_inw;
95		io->outputb = intf_mem_outw;
96		break;
97	case 4:
98		io->inputb = intf_mem_inl;
99		io->outputb = intf_mem_outl;
100		break;
101#ifdef readq
102	case 8:
103		io->inputb = mem_inq;
104		io->outputb = mem_outq;
105		break;
106#endif
107	default:
108		dev_warn(io->dev, "Invalid register size: %d\n",
109			 io->regsize);
110		return -EINVAL;
111	}
112
113	/*
114	 * Some BIOSes reserve disjoint memory regions in their ACPI
115	 * tables.  This causes problems when trying to request the
116	 * entire region.  Therefore we must request each register
117	 * separately.
118	 */
119	for (idx = 0; idx < io->io_size; idx++) {
120		if (request_mem_region(addr + idx * io->regspacing,
121				       io->regsize, SI_DEVICE_NAME) == NULL) {
122			/* Undo allocations */
123			mem_region_cleanup(io, idx);
124			return -EIO;
125		}
126	}
127
128	/*
129	 * Calculate the total amount of memory to claim.  This is an
130	 * unusual looking calculation, but it avoids claiming any
131	 * more memory than it has to.  It will claim everything
132	 * between the first address to the end of the last full
133	 * register.
134	 */
135	mapsize = ((io->io_size * io->regspacing)
136		   - (io->regspacing - io->regsize));
137	io->addr = ioremap(addr, mapsize);
138	if (io->addr == NULL) {
139		mem_region_cleanup(io, io->io_size);
140		return -EIO;
141	}
142
143	io->io_cleanup = mem_cleanup;
144
145	return 0;
146}
147