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(addr: (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(val: b, addr: (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(addr: (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(val: b << io->regshift, addr: (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(addr: (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(val: b << io->regshift, addr: (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(addr: (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(val: (u64)b << io->regshift, addr: (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(addr: io->addr); |
72 | mem_region_cleanup(io, num: 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, 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 | |