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(addr: (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(val: b, addr: (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(addr: (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(val: b << io->regshift, addr: (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(addr: (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(val: b << io->regshift, addr: (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(addr: (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(val: (u64)b << io->regshift, addr: (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(addr: io->addr);
72 mem_region_cleanup(io, num: 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, num: 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(offset: addr, size: mapsize);
138 if (io->addr == NULL) {
139 mem_region_cleanup(io, num: io->io_size);
140 return -EIO;
141 }
142
143 io->io_cleanup = mem_cleanup;
144
145 return 0;
146}
147

source code of linux/drivers/char/ipmi/ipmi_si_mem_io.c