1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* apc - Driver implementation for power management functions |
3 | * of Aurora Personality Chip (APC) on SPARCstation-4/5 and |
4 | * derivatives. |
5 | * |
6 | * Copyright (c) 2002 Eric Brower (ebrower@usa.net) |
7 | */ |
8 | |
9 | #include <linux/kernel.h> |
10 | #include <linux/fs.h> |
11 | #include <linux/errno.h> |
12 | #include <linux/init.h> |
13 | #include <linux/miscdevice.h> |
14 | #include <linux/pm.h> |
15 | #include <linux/of.h> |
16 | #include <linux/platform_device.h> |
17 | #include <linux/module.h> |
18 | |
19 | #include <asm/io.h> |
20 | #include <asm/oplib.h> |
21 | #include <linux/uaccess.h> |
22 | #include <asm/auxio.h> |
23 | #include <asm/apc.h> |
24 | #include <asm/processor.h> |
25 | |
26 | /* Debugging |
27 | * |
28 | * #define APC_DEBUG_LED |
29 | */ |
30 | |
31 | #define APC_MINOR MISC_DYNAMIC_MINOR |
32 | #define APC_OBPNAME "power-management" |
33 | #define APC_DEVNAME "apc" |
34 | |
35 | static u8 __iomem *regs; |
36 | static int apc_no_idle = 0; |
37 | |
38 | #define apc_readb(offs) (sbus_readb(regs+offs)) |
39 | #define apc_writeb(val, offs) (sbus_writeb(val, regs+offs)) |
40 | |
41 | /* Specify "apc=noidle" on the kernel command line to |
42 | * disable APC CPU standby support. Certain prototype |
43 | * systems (SPARCstation-Fox) do not play well with APC |
44 | * CPU idle, so disable this if your system has APC and |
45 | * crashes randomly. |
46 | */ |
47 | static int __init apc_setup(char *str) |
48 | { |
49 | if(!strncmp(str, "noidle" , strlen("noidle" ))) { |
50 | apc_no_idle = 1; |
51 | return 1; |
52 | } |
53 | return 0; |
54 | } |
55 | __setup("apc=" , apc_setup); |
56 | |
57 | /* |
58 | * CPU idle callback function |
59 | * See .../arch/sparc/kernel/process.c |
60 | */ |
61 | static void apc_swift_idle(void) |
62 | { |
63 | #ifdef APC_DEBUG_LED |
64 | set_auxio(0x00, AUXIO_LED); |
65 | #endif |
66 | |
67 | apc_writeb(apc_readb(APC_IDLE_REG) | APC_IDLE_ON, APC_IDLE_REG); |
68 | |
69 | #ifdef APC_DEBUG_LED |
70 | set_auxio(AUXIO_LED, 0x00); |
71 | #endif |
72 | } |
73 | |
74 | static inline void apc_free(struct platform_device *op) |
75 | { |
76 | of_iounmap(&op->resource[0], regs, resource_size(res: &op->resource[0])); |
77 | } |
78 | |
79 | static int apc_open(struct inode *inode, struct file *f) |
80 | { |
81 | return 0; |
82 | } |
83 | |
84 | static int apc_release(struct inode *inode, struct file *f) |
85 | { |
86 | return 0; |
87 | } |
88 | |
89 | static long apc_ioctl(struct file *f, unsigned int cmd, unsigned long __arg) |
90 | { |
91 | __u8 inarg, __user *arg = (__u8 __user *) __arg; |
92 | |
93 | switch (cmd) { |
94 | case APCIOCGFANCTL: |
95 | if (put_user(apc_readb(APC_FANCTL_REG) & APC_REGMASK, arg)) |
96 | return -EFAULT; |
97 | break; |
98 | |
99 | case APCIOCGCPWR: |
100 | if (put_user(apc_readb(APC_CPOWER_REG) & APC_REGMASK, arg)) |
101 | return -EFAULT; |
102 | break; |
103 | |
104 | case APCIOCGBPORT: |
105 | if (put_user(apc_readb(APC_BPORT_REG) & APC_BPMASK, arg)) |
106 | return -EFAULT; |
107 | break; |
108 | |
109 | case APCIOCSFANCTL: |
110 | if (get_user(inarg, arg)) |
111 | return -EFAULT; |
112 | apc_writeb(inarg & APC_REGMASK, APC_FANCTL_REG); |
113 | break; |
114 | |
115 | case APCIOCSCPWR: |
116 | if (get_user(inarg, arg)) |
117 | return -EFAULT; |
118 | apc_writeb(inarg & APC_REGMASK, APC_CPOWER_REG); |
119 | break; |
120 | |
121 | case APCIOCSBPORT: |
122 | if (get_user(inarg, arg)) |
123 | return -EFAULT; |
124 | apc_writeb(inarg & APC_BPMASK, APC_BPORT_REG); |
125 | break; |
126 | |
127 | default: |
128 | return -EINVAL; |
129 | } |
130 | |
131 | return 0; |
132 | } |
133 | |
134 | static const struct file_operations apc_fops = { |
135 | .unlocked_ioctl = apc_ioctl, |
136 | .open = apc_open, |
137 | .release = apc_release, |
138 | .llseek = noop_llseek, |
139 | }; |
140 | |
141 | static struct miscdevice apc_miscdev = { APC_MINOR, APC_DEVNAME, &apc_fops }; |
142 | |
143 | static int apc_probe(struct platform_device *op) |
144 | { |
145 | int err; |
146 | |
147 | regs = of_ioremap(&op->resource[0], 0, |
148 | resource_size(res: &op->resource[0]), APC_OBPNAME); |
149 | if (!regs) { |
150 | printk(KERN_ERR "%s: unable to map registers\n" , APC_DEVNAME); |
151 | return -ENODEV; |
152 | } |
153 | |
154 | err = misc_register(misc: &apc_miscdev); |
155 | if (err) { |
156 | printk(KERN_ERR "%s: unable to register device\n" , APC_DEVNAME); |
157 | apc_free(op); |
158 | return -ENODEV; |
159 | } |
160 | |
161 | /* Assign power management IDLE handler */ |
162 | if (!apc_no_idle) |
163 | sparc_idle = apc_swift_idle; |
164 | |
165 | printk(KERN_INFO "%s: power management initialized%s\n" , |
166 | APC_DEVNAME, apc_no_idle ? " (CPU idle disabled)" : "" ); |
167 | |
168 | return 0; |
169 | } |
170 | |
171 | static const struct of_device_id apc_match[] = { |
172 | { |
173 | .name = APC_OBPNAME, |
174 | }, |
175 | {}, |
176 | }; |
177 | MODULE_DEVICE_TABLE(of, apc_match); |
178 | |
179 | static struct platform_driver apc_driver = { |
180 | .driver = { |
181 | .name = "apc" , |
182 | .of_match_table = apc_match, |
183 | }, |
184 | .probe = apc_probe, |
185 | }; |
186 | |
187 | static int __init apc_init(void) |
188 | { |
189 | return platform_driver_register(&apc_driver); |
190 | } |
191 | |
192 | /* This driver is not critical to the boot process |
193 | * and is easiest to ioremap when SBus is already |
194 | * initialized, so we install ourselves thusly: |
195 | */ |
196 | __initcall(apc_init); |
197 | |