1 // SPDX-License-Identifier: GPL-2.0+
2 
3 #include <linux/io.h>
4 #include "ipmi_si.h"
5 
6 static 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 
12 static 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 
18 static 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 
25 static 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 
31 static 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 
38 static 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
45 static 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 
51 static 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 
58 static 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 
68 static 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 
76 int 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