1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Intel SOC Punit device state debug driver |
4 | * Punit controls power management for North Complex devices (Graphics |
5 | * blocks, Image Signal Processing, video processing, display, DSP etc.) |
6 | * |
7 | * Copyright (c) 2015, Intel Corporation. |
8 | */ |
9 | |
10 | #define pr_fmt(fmt) "punit_atom: " fmt |
11 | |
12 | #include <linux/acpi.h> |
13 | #include <linux/module.h> |
14 | #include <linux/init.h> |
15 | #include <linux/device.h> |
16 | #include <linux/debugfs.h> |
17 | #include <linux/seq_file.h> |
18 | #include <linux/io.h> |
19 | #include <asm/cpu_device_id.h> |
20 | #include <asm/intel-family.h> |
21 | #include <asm/iosf_mbi.h> |
22 | |
23 | /* Subsystem config/status Video processor */ |
24 | #define VED_SS_PM0 0x32 |
25 | /* Subsystem config/status ISP (Image Signal Processor) */ |
26 | #define ISP_SS_PM0 0x39 |
27 | /* Subsystem config/status Input/output controller */ |
28 | #define MIO_SS_PM 0x3B |
29 | /* Shift bits for getting status for video, isp and i/o */ |
30 | #define SSS_SHIFT 24 |
31 | |
32 | /* Power gate status reg */ |
33 | #define PWRGT_STATUS 0x61 |
34 | /* Shift bits for getting status for graphics rendering */ |
35 | #define RENDER_POS 0 |
36 | /* Shift bits for getting status for media control */ |
37 | #define MEDIA_POS 2 |
38 | /* Shift bits for getting status for Valley View/Baytrail display */ |
39 | #define VLV_DISPLAY_POS 6 |
40 | |
41 | /* Subsystem config/status display for Cherry Trail SOC */ |
42 | #define CHT_DSP_SSS 0x36 |
43 | /* Shift bits for getting status for display */ |
44 | #define CHT_DSP_SSS_POS 16 |
45 | |
46 | struct punit_device { |
47 | char *name; |
48 | int reg; |
49 | int sss_pos; |
50 | }; |
51 | |
52 | static const struct punit_device punit_device_tng[] = { |
53 | { "DISPLAY" , CHT_DSP_SSS, SSS_SHIFT }, |
54 | { "VED" , VED_SS_PM0, SSS_SHIFT }, |
55 | { "ISP" , ISP_SS_PM0, SSS_SHIFT }, |
56 | { "MIO" , MIO_SS_PM, SSS_SHIFT }, |
57 | { NULL } |
58 | }; |
59 | |
60 | static const struct punit_device punit_device_byt[] = { |
61 | { "GFX RENDER" , PWRGT_STATUS, RENDER_POS }, |
62 | { "GFX MEDIA" , PWRGT_STATUS, MEDIA_POS }, |
63 | { "DISPLAY" , PWRGT_STATUS, VLV_DISPLAY_POS }, |
64 | { "VED" , VED_SS_PM0, SSS_SHIFT }, |
65 | { "ISP" , ISP_SS_PM0, SSS_SHIFT }, |
66 | { "MIO" , MIO_SS_PM, SSS_SHIFT }, |
67 | { NULL } |
68 | }; |
69 | |
70 | static const struct punit_device punit_device_cht[] = { |
71 | { "GFX RENDER" , PWRGT_STATUS, RENDER_POS }, |
72 | { "GFX MEDIA" , PWRGT_STATUS, MEDIA_POS }, |
73 | { "DISPLAY" , CHT_DSP_SSS, CHT_DSP_SSS_POS }, |
74 | { "VED" , VED_SS_PM0, SSS_SHIFT }, |
75 | { "ISP" , ISP_SS_PM0, SSS_SHIFT }, |
76 | { "MIO" , MIO_SS_PM, SSS_SHIFT }, |
77 | { NULL } |
78 | }; |
79 | |
80 | static const char * const dstates[] = {"D0" , "D0i1" , "D0i2" , "D0i3" }; |
81 | |
82 | static int punit_dev_state_show(struct seq_file *seq_file, void *unused) |
83 | { |
84 | u32 punit_pwr_status; |
85 | struct punit_device *punit_devp = seq_file->private; |
86 | int index; |
87 | int status; |
88 | |
89 | seq_puts(m: seq_file, s: "\n\nPUNIT NORTH COMPLEX DEVICES :\n" ); |
90 | while (punit_devp->name) { |
91 | status = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ, |
92 | offset: punit_devp->reg, mdr: &punit_pwr_status); |
93 | if (status) { |
94 | seq_printf(m: seq_file, fmt: "%9s : Read Failed\n" , |
95 | punit_devp->name); |
96 | } else { |
97 | index = (punit_pwr_status >> punit_devp->sss_pos) & 3; |
98 | seq_printf(m: seq_file, fmt: "%9s : %s\n" , punit_devp->name, |
99 | dstates[index]); |
100 | } |
101 | punit_devp++; |
102 | } |
103 | |
104 | return 0; |
105 | } |
106 | DEFINE_SHOW_ATTRIBUTE(punit_dev_state); |
107 | |
108 | static struct dentry *punit_dbg_file; |
109 | |
110 | static void punit_dbgfs_register(struct punit_device *punit_device) |
111 | { |
112 | punit_dbg_file = debugfs_create_dir(name: "punit_atom" , NULL); |
113 | |
114 | debugfs_create_file(name: "dev_power_state" , mode: 0444, parent: punit_dbg_file, |
115 | data: punit_device, fops: &punit_dev_state_fops); |
116 | } |
117 | |
118 | static void punit_dbgfs_unregister(void) |
119 | { |
120 | debugfs_remove_recursive(dentry: punit_dbg_file); |
121 | } |
122 | |
123 | #if defined(CONFIG_ACPI) && defined(CONFIG_SUSPEND) |
124 | static const struct punit_device *punit_dev; |
125 | |
126 | static void punit_s2idle_check(void) |
127 | { |
128 | const struct punit_device *punit_devp; |
129 | u32 punit_pwr_status, dstate; |
130 | int status; |
131 | |
132 | for (punit_devp = punit_dev; punit_devp->name; punit_devp++) { |
133 | /* Skip MIO, it is on till the very last moment */ |
134 | if (punit_devp->reg == MIO_SS_PM) |
135 | continue; |
136 | |
137 | status = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ, |
138 | offset: punit_devp->reg, mdr: &punit_pwr_status); |
139 | if (status) { |
140 | pr_err("%s read failed\n" , punit_devp->name); |
141 | } else { |
142 | dstate = (punit_pwr_status >> punit_devp->sss_pos) & 3; |
143 | if (!dstate) |
144 | pr_err("%s is in D0 prior to s2idle\n" , punit_devp->name); |
145 | } |
146 | } |
147 | } |
148 | |
149 | static struct acpi_s2idle_dev_ops punit_s2idle_ops = { |
150 | .check = punit_s2idle_check, |
151 | }; |
152 | |
153 | static void punit_s2idle_check_register(struct punit_device *punit_device) |
154 | { |
155 | punit_dev = punit_device; |
156 | acpi_register_lps0_dev(arg: &punit_s2idle_ops); |
157 | } |
158 | |
159 | static void punit_s2idle_check_unregister(void) |
160 | { |
161 | acpi_unregister_lps0_dev(arg: &punit_s2idle_ops); |
162 | } |
163 | #else |
164 | static void punit_s2idle_check_register(struct punit_device *punit_device) {} |
165 | static void punit_s2idle_check_unregister(void) {} |
166 | #endif |
167 | |
168 | #define X86_MATCH(model, data) \ |
169 | X86_MATCH_VENDOR_FAM_MODEL_FEATURE(INTEL, 6, INTEL_FAM6_##model, \ |
170 | X86_FEATURE_MWAIT, data) |
171 | |
172 | static const struct x86_cpu_id intel_punit_cpu_ids[] = { |
173 | X86_MATCH(ATOM_SILVERMONT, &punit_device_byt), |
174 | X86_MATCH(ATOM_SILVERMONT_MID, &punit_device_tng), |
175 | X86_MATCH(ATOM_AIRMONT, &punit_device_cht), |
176 | {} |
177 | }; |
178 | MODULE_DEVICE_TABLE(x86cpu, intel_punit_cpu_ids); |
179 | |
180 | static int __init punit_atom_debug_init(void) |
181 | { |
182 | struct punit_device *punit_device; |
183 | const struct x86_cpu_id *id; |
184 | |
185 | id = x86_match_cpu(match: intel_punit_cpu_ids); |
186 | if (!id) |
187 | return -ENODEV; |
188 | |
189 | punit_device = (struct punit_device *)id->driver_data; |
190 | punit_dbgfs_register(punit_device); |
191 | punit_s2idle_check_register(punit_device); |
192 | |
193 | return 0; |
194 | } |
195 | |
196 | static void __exit punit_atom_debug_exit(void) |
197 | { |
198 | punit_s2idle_check_unregister(); |
199 | punit_dbgfs_unregister(); |
200 | } |
201 | |
202 | module_init(punit_atom_debug_init); |
203 | module_exit(punit_atom_debug_exit); |
204 | |
205 | MODULE_AUTHOR("Kumar P, Mahesh <mahesh.kumar.p@intel.com>" ); |
206 | MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>" ); |
207 | MODULE_DESCRIPTION("Driver for Punit devices states debugging" ); |
208 | MODULE_LICENSE("GPL v2" ); |
209 | |