1 /* $OpenBSD: glxclk.c,v 1.11 2023/09/17 14:50:51 cheloha Exp $ */
2
3 /*
4 * Copyright (c) 2013 Paul Irofti.
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19 #include <sys/param.h>
20 #include <sys/systm.h>
21 #include <sys/clockintr.h>
22 #include <sys/device.h>
23 #include <sys/kernel.h>
24 #include <sys/stdint.h>
25
26 #include <machine/bus.h>
27 #include <machine/autoconf.h>
28
29 #include <dev/isa/isavar.h>
30
31 #include <dev/pci/pcireg.h>
32 #include <dev/pci/pcivar.h>
33 #include <dev/pci/pcidevs.h>
34
35 #include <dev/pci/glxreg.h>
36 #include <dev/pci/glxvar.h>
37
38 struct glxclk_softc {
39 struct device sc_dev;
40
41 bus_space_tag_t sc_iot;
42 bus_space_handle_t sc_ioh;
43
44 struct intrclock sc_intrclock;
45 };
46
47 struct cfdriver glxclk_cd = {
48 NULL, "glxclk", DV_DULL
49 };
50
51 int glxclk_match(struct device *, void *, void *);
52 void glxclk_attach(struct device *, struct device *, void *);
53 int glxclk_intr(void *);
54 void glxclk_initclock(void);
55 void glxclk_startclock(struct cpu_info *);
56 void glxclk_rearm(void *, uint64_t);
57 void glxclk_trigger(void *);
58
59 const struct cfattach glxclk_ca = {
60 sizeof(struct glxclk_softc), glxclk_match, glxclk_attach,
61 };
62
63 #define MSR_LBAR_ENABLE 0x100000000ULL
64 #define MSR_LBAR_MFGPT DIVIL_LBAR_MFGPT
65 #define MSR_MFGPT_SIZE 0x40
66 #define MSR_MFGPT_ADDR_MASK 0xffc0
67
68 /*
69 * Experience shows that the clock source goes crazy on scale factors
70 * lower than 8, so keep it at 8.
71 */
72
73 #define AMD5536_MFGPT1_CMP2 0x0000000a /* Compare value for CMP2 */
74 #define AMD5536_MFGPT1_CNT 0x0000000c /* Up counter */
75 #define AMD5536_MFGPT1_SETUP 0x0000000e /* Setup register */
76 #define AMD5536_MFGPT1_SCALE 0x3 /* Set divider to 8 */
77 #define AMD5536_MFGPT1_CLOCK (1 << 15) /* Clock frequency */
78 #define AMD5536_MFGPT1_C2_IRQM 0x00000200
79
80 #define AMD5536_MFGPT_CNT_EN (1 << 15) /* Enable counting */
81 #define AMD5536_MFGPT_CMP2 (1 << 14) /* Compare 2 output */
82 #define AMD5536_MFGPT_CMP1 (1 << 13) /* Compare 1 output */
83 #define AMD5536_MFGPT_SETUP (1 << 12) /* Set to 1 after 1st write */
84 #define AMD5536_MFGPT_STOP_EN (1 << 11) /* Stop enable */
85 #define AMD5536_MFGPT_CMP2MODE (1 << 9)|(1 << 8)/* Set to GE + activate IRQ */
86 #define AMD5536_MFGPT_CLKSEL (1 << 4) /* Clock select 14MHz */
87
88
89 struct glxclk_softc *glxclk_sc;
90
91 int
glxclk_match(struct device * parent,void * match,void * aux)92 glxclk_match(struct device *parent, void *match, void *aux)
93 {
94 struct glxpcib_attach_args *gaa = aux;
95 struct cfdata *cf = match;
96
97 if (strcmp(gaa->gaa_name, cf->cf_driver->cd_name) != 0)
98 return 0;
99
100 return 1;
101 }
102
103 void
glxclk_attach(struct device * parent,struct device * self,void * aux)104 glxclk_attach(struct device *parent, struct device *self, void *aux)
105 {
106 glxclk_sc = (struct glxclk_softc *)self;
107 struct glxpcib_attach_args *gaa = aux;
108 u_int64_t wa;
109
110 glxclk_sc->sc_iot = gaa->gaa_iot;
111 glxclk_sc->sc_ioh = gaa->gaa_ioh;
112
113 wa = rdmsr(MSR_LBAR_MFGPT);
114
115 if ((wa & MSR_LBAR_ENABLE) == 0) {
116 printf(" not configured\n");
117 return;
118 }
119
120 if (bus_space_map(glxclk_sc->sc_iot, wa & MSR_MFGPT_ADDR_MASK,
121 MSR_MFGPT_SIZE, 0, &glxclk_sc->sc_ioh)) {
122 printf(" not configured\n");
123 return;
124 }
125
126 /* Set comparator 2 */
127 bus_space_write_2(glxclk_sc->sc_iot, glxclk_sc->sc_ioh,
128 AMD5536_MFGPT1_CMP2, 1);
129
130 /* Reset counter to 0 */
131 bus_space_write_2(glxclk_sc->sc_iot, glxclk_sc->sc_ioh,
132 AMD5536_MFGPT1_CNT, 0);
133
134 /*
135 * All the bits in the range 11:0 have to be written at once.
136 * After they're set the first time all further writes are
137 * ignored.
138 */
139 uint16_t setup = (AMD5536_MFGPT1_SCALE | AMD5536_MFGPT_CMP2MODE);
140
141 bus_space_write_2(glxclk_sc->sc_iot, glxclk_sc->sc_ioh,
142 AMD5536_MFGPT1_SETUP, setup);
143
144 /* Check to see if the MFGPT_SETUP bit was set */
145 setup = bus_space_read_2(glxclk_sc->sc_iot, glxclk_sc->sc_ioh,
146 AMD5536_MFGPT1_SETUP);
147 if ((setup & AMD5536_MFGPT_SETUP) == 0) {
148 printf(" not configured\n");
149 return;
150 }
151
152 /* Enable MFGPT1 Comparator 2 Output to the Interrupt Mapper */
153 wa = rdmsr(MFGPT_IRQ);
154 wa |= AMD5536_MFGPT1_C2_IRQM;
155 wrmsr(MFGPT_IRQ, wa);
156
157 /*
158 * Tie PIC input 5 to IG7 for glxclk(4).
159 */
160 wa = rdmsr(PIC_ZSEL_LOW);
161 wa &= ~(0xfUL << 20);
162 wa |= 7 << 20;
163 wrmsr(PIC_ZSEL_LOW, wa);
164
165 /*
166 * The interrupt argument is NULL in order to notify the dispatcher
167 * to pass the clock frame as argument. This trick also forces keeping
168 * the soft state global because during the interrupt we need to
169 * disable the counter in the MFGPT setup register.
170 */
171 isa_intr_establish(sys_platform->isa_chipset, 7, IST_LEVEL, IPL_CLOCK,
172 glxclk_intr, NULL, "clock");
173
174 glxclk_sc->sc_intrclock.ic_cookie = glxclk_sc;
175 glxclk_sc->sc_intrclock.ic_rearm = glxclk_rearm;
176 glxclk_sc->sc_intrclock.ic_trigger = glxclk_trigger;
177
178 md_initclock = glxclk_initclock;
179 md_startclock = glxclk_startclock;
180
181 printf("\n");
182 }
183
184 void
glxclk_initclock(void)185 glxclk_initclock(void)
186 {
187 /*
188 * MFGPT runs on powers of two, adjust the hz value accordingly.
189 */
190 stathz = hz = 128;
191 profhz = hz * 10;
192 statclock_is_randomized = 1;
193 tick = 1000000 / hz;
194 tick_nsec = 1000000000 / hz;
195 }
196
197 void
glxclk_startclock(struct cpu_info * ci)198 glxclk_startclock(struct cpu_info *ci)
199 {
200 clockintr_cpu_init(&glxclk_sc->sc_intrclock);
201
202 /* Start the clock. */
203 int s = splclock();
204 ci->ci_clock_started++;
205 clockintr_trigger();
206 splx(s);
207 }
208
209 int
glxclk_intr(void * arg)210 glxclk_intr(void *arg)
211 {
212 struct clockframe *frame = arg;
213 struct cpu_info *ci = curcpu();
214 struct glxclk_softc *sc = glxclk_sc;
215
216 /* Clear the current event and disable the counter. */
217 bus_space_write_2(sc->sc_iot, sc->sc_ioh, AMD5536_MFGPT1_SETUP,
218 AMD5536_MFGPT_CMP1 | AMD5536_MFGPT_CMP2);
219
220 if (ci->ci_clock_started == 0)
221 return 1;
222
223 clockintr_dispatch(frame);
224
225 return 1;
226 }
227
228 void
glxclk_rearm(void * cookie,uint64_t nsecs)229 glxclk_rearm(void *cookie, uint64_t nsecs)
230 {
231 const uint64_t freq = AMD5536_MFGPT1_CLOCK >> AMD5536_MFGPT1_SCALE;
232 const uint64_t nsec_ratio = (freq << 32) / 1000000000;
233 /* Subtract 1 to leave room for rounding. */
234 const uint64_t nsec_max = UINT64_MAX / nsec_ratio - 1;
235 struct glxclk_softc *sc = cookie;
236 uint64_t count;
237 register_t sr;
238
239 nsecs = MIN(nsecs, nsec_max);
240 count = MAX((nsecs * nsec_ratio + (1U << 31)) >> 32, 1);
241
242 sr = disableintr();
243 bus_space_write_2(sc->sc_iot, sc->sc_ioh, AMD5536_MFGPT1_CMP2, count);
244 bus_space_write_2(sc->sc_iot, sc->sc_ioh, AMD5536_MFGPT1_CNT, 0);
245 bus_space_write_2(sc->sc_iot, sc->sc_ioh, AMD5536_MFGPT1_SETUP,
246 AMD5536_MFGPT_CNT_EN | AMD5536_MFGPT_CMP2);
247 setsr(sr);
248 }
249
250 void
glxclk_trigger(void * cookie)251 glxclk_trigger(void *cookie)
252 {
253 glxclk_rearm(cookie, 0);
254 }
255