Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
L
linux
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
linux
Commits
b9ed919f
Commit
b9ed919f
authored
Oct 15, 2013
by
Ben Skeggs
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
drm/nouveau/drm/pm: remove everything except the hwmon interfaces to THERM
Signed-off-by:
Ben Skeggs
<
bskeggs@redhat.com
>
parent
c52f4fa6
Changes
14
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
69 additions
and
4722 deletions
+69
-4722
drivers/gpu/drm/nouveau/Makefile
drivers/gpu/drm/nouveau/Makefile
+1
-3
drivers/gpu/drm/nouveau/nouveau_drm.c
drivers/gpu/drm/nouveau/nouveau_drm.c
+3
-6
drivers/gpu/drm/nouveau/nouveau_drm.h
drivers/gpu/drm/nouveau/nouveau_drm.h
+1
-1
drivers/gpu/drm/nouveau/nouveau_hwmon.c
drivers/gpu/drm/nouveau/nouveau_hwmon.c
+21
-539
drivers/gpu/drm/nouveau/nouveau_hwmon.h
drivers/gpu/drm/nouveau/nouveau_hwmon.h
+43
-0
drivers/gpu/drm/nouveau/nouveau_mem.c
drivers/gpu/drm/nouveau/nouveau_mem.c
+0
-647
drivers/gpu/drm/nouveau/nouveau_perf.c
drivers/gpu/drm/nouveau/nouveau_perf.c
+0
-416
drivers/gpu/drm/nouveau/nouveau_pm.h
drivers/gpu/drm/nouveau/nouveau_pm.h
+0
-283
drivers/gpu/drm/nouveau/nouveau_volt.c
drivers/gpu/drm/nouveau/nouveau_volt.c
+0
-250
drivers/gpu/drm/nouveau/nv04_pm.c
drivers/gpu/drm/nouveau/nv04_pm.c
+0
-146
drivers/gpu/drm/nouveau/nv40_pm.c
drivers/gpu/drm/nouveau/nv40_pm.c
+0
-353
drivers/gpu/drm/nouveau/nv50_pm.c
drivers/gpu/drm/nouveau/nv50_pm.c
+0
-855
drivers/gpu/drm/nouveau/nva3_pm.c
drivers/gpu/drm/nouveau/nva3_pm.c
+0
-624
drivers/gpu/drm/nouveau/nvc0_pm.c
drivers/gpu/drm/nouveau/nvc0_pm.c
+0
-599
No files found.
drivers/gpu/drm/nouveau/Makefile
View file @
b9ed919f
...
@@ -270,9 +270,7 @@ include $(src)/dispnv04/Makefile
...
@@ -270,9 +270,7 @@ include $(src)/dispnv04/Makefile
nouveau-y
+=
nv50_display.o
nouveau-y
+=
nv50_display.o
# drm/pm
# drm/pm
nouveau-y
+=
nouveau_pm.o nouveau_volt.o nouveau_perf.o
nouveau-y
+=
nouveau_hwmon.o
nouveau-y
+=
nv04_pm.o nv40_pm.o nv50_pm.o nva3_pm.o nvc0_pm.o
nouveau-y
+=
nouveau_mem.o
# other random bits
# other random bits
nouveau-$(CONFIG_COMPAT)
+=
nouveau_ioc32.o
nouveau-$(CONFIG_COMPAT)
+=
nouveau_ioc32.o
...
...
drivers/gpu/drm/nouveau/nouveau_drm.c
View file @
b9ed919f
...
@@ -46,7 +46,7 @@
...
@@ -46,7 +46,7 @@
#include "nouveau_gem.h"
#include "nouveau_gem.h"
#include "nouveau_agp.h"
#include "nouveau_agp.h"
#include "nouveau_vga.h"
#include "nouveau_vga.h"
#include "nouveau_
pm
.h"
#include "nouveau_
hwmon
.h"
#include "nouveau_acpi.h"
#include "nouveau_acpi.h"
#include "nouveau_bios.h"
#include "nouveau_bios.h"
#include "nouveau_ioctl.h"
#include "nouveau_ioctl.h"
...
@@ -384,8 +384,7 @@ nouveau_drm_load(struct drm_device *dev, unsigned long flags)
...
@@ -384,8 +384,7 @@ nouveau_drm_load(struct drm_device *dev, unsigned long flags)
goto
fail_dispinit
;
goto
fail_dispinit
;
}
}
nouveau_pm_init
(
dev
);
nouveau_hwmon_init
(
dev
);
nouveau_accel_init
(
drm
);
nouveau_accel_init
(
drm
);
nouveau_fbcon_init
(
dev
);
nouveau_fbcon_init
(
dev
);
...
@@ -421,8 +420,7 @@ nouveau_drm_unload(struct drm_device *dev)
...
@@ -421,8 +420,7 @@ nouveau_drm_unload(struct drm_device *dev)
pm_runtime_get_sync
(
dev
->
dev
);
pm_runtime_get_sync
(
dev
->
dev
);
nouveau_fbcon_fini
(
dev
);
nouveau_fbcon_fini
(
dev
);
nouveau_accel_fini
(
drm
);
nouveau_accel_fini
(
drm
);
nouveau_hwmon_fini
(
dev
);
nouveau_pm_fini
(
dev
);
if
(
dev
->
mode_config
.
num_crtc
)
if
(
dev
->
mode_config
.
num_crtc
)
nouveau_display_fini
(
dev
);
nouveau_display_fini
(
dev
);
...
@@ -562,7 +560,6 @@ nouveau_do_resume(struct drm_device *dev)
...
@@ -562,7 +560,6 @@ nouveau_do_resume(struct drm_device *dev)
}
}
nouveau_run_vbios_init
(
dev
);
nouveau_run_vbios_init
(
dev
);
nouveau_pm_resume
(
dev
);
if
(
dev
->
mode_config
.
num_crtc
)
{
if
(
dev
->
mode_config
.
num_crtc
)
{
NV_INFO
(
drm
,
"resuming display...
\n
"
);
NV_INFO
(
drm
,
"resuming display...
\n
"
);
...
...
drivers/gpu/drm/nouveau/nouveau_drm.h
View file @
b9ed919f
...
@@ -129,7 +129,7 @@ struct nouveau_drm {
...
@@ -129,7 +129,7 @@ struct nouveau_drm {
struct
backlight_device
*
backlight
;
struct
backlight_device
*
backlight
;
/* power management */
/* power management */
struct
nouveau_
pm
*
pm
;
struct
nouveau_
hwmon
*
hwmon
;
/* display power reference */
/* display power reference */
bool
have_disp_power_ref
;
bool
have_disp_power_ref
;
...
...
drivers/gpu/drm/nouveau/nouveau_
pm
.c
→
drivers/gpu/drm/nouveau/nouveau_
hwmon
.c
View file @
b9ed919f
...
@@ -32,369 +32,12 @@
...
@@ -32,369 +32,12 @@
#include <drm/drmP.h>
#include <drm/drmP.h>
#include "nouveau_drm.h"
#include "nouveau_drm.h"
#include "nouveau_
pm
.h"
#include "nouveau_
hwmon
.h"
#include <subdev/gpio.h>
#include <subdev/gpio.h>
#include <subdev/timer.h>
#include <subdev/timer.h>
#include <subdev/therm.h>
#include <subdev/therm.h>
MODULE_PARM_DESC
(
perflvl
,
"Performance level (default: boot)"
);
static
char
*
nouveau_perflvl
;
module_param_named
(
perflvl
,
nouveau_perflvl
,
charp
,
0400
);
MODULE_PARM_DESC
(
perflvl_wr
,
"Allow perflvl changes (warning: dangerous!)"
);
static
int
nouveau_perflvl_wr
;
module_param_named
(
perflvl_wr
,
nouveau_perflvl_wr
,
int
,
0400
);
static
int
nouveau_pm_perflvl_aux
(
struct
drm_device
*
dev
,
struct
nouveau_pm_level
*
perflvl
,
struct
nouveau_pm_level
*
a
,
struct
nouveau_pm_level
*
b
)
{
struct
nouveau_drm
*
drm
=
nouveau_drm
(
dev
);
struct
nouveau_pm
*
pm
=
nouveau_pm
(
dev
);
struct
nouveau_therm
*
therm
=
nouveau_therm
(
drm
->
device
);
int
ret
;
/*XXX: not on all boards, we should control based on temperature
* on recent boards.. or maybe on some other factor we don't
* know about?
*/
if
(
therm
&&
therm
->
fan_set
&&
a
->
fanspeed
&&
b
->
fanspeed
&&
b
->
fanspeed
>
a
->
fanspeed
)
{
ret
=
therm
->
fan_set
(
therm
,
perflvl
->
fanspeed
);
if
(
ret
&&
ret
!=
-
ENODEV
)
{
NV_ERROR
(
drm
,
"fanspeed set failed: %d
\n
"
,
ret
);
}
}
if
(
pm
->
voltage
.
supported
&&
pm
->
voltage_set
)
{
if
(
perflvl
->
volt_min
&&
b
->
volt_min
>
a
->
volt_min
)
{
ret
=
pm
->
voltage_set
(
dev
,
perflvl
->
volt_min
);
if
(
ret
)
{
NV_ERROR
(
drm
,
"voltage set failed: %d
\n
"
,
ret
);
return
ret
;
}
}
}
return
0
;
}
static
int
nouveau_pm_perflvl_set
(
struct
drm_device
*
dev
,
struct
nouveau_pm_level
*
perflvl
)
{
struct
nouveau_pm
*
pm
=
nouveau_pm
(
dev
);
void
*
state
;
int
ret
;
if
(
perflvl
==
pm
->
cur
)
return
0
;
ret
=
nouveau_pm_perflvl_aux
(
dev
,
perflvl
,
pm
->
cur
,
perflvl
);
if
(
ret
)
return
ret
;
state
=
pm
->
clocks_pre
(
dev
,
perflvl
);
if
(
IS_ERR
(
state
))
{
ret
=
PTR_ERR
(
state
);
goto
error
;
}
ret
=
pm
->
clocks_set
(
dev
,
state
);
if
(
ret
)
goto
error
;
ret
=
nouveau_pm_perflvl_aux
(
dev
,
perflvl
,
perflvl
,
pm
->
cur
);
if
(
ret
)
return
ret
;
pm
->
cur
=
perflvl
;
return
0
;
error:
/* restore the fan speed and voltage before leaving */
nouveau_pm_perflvl_aux
(
dev
,
perflvl
,
perflvl
,
pm
->
cur
);
return
ret
;
}
void
nouveau_pm_trigger
(
struct
drm_device
*
dev
)
{
struct
nouveau_drm
*
drm
=
nouveau_drm
(
dev
);
struct
nouveau_timer
*
ptimer
=
nouveau_timer
(
drm
->
device
);
struct
nouveau_pm
*
pm
=
nouveau_pm
(
dev
);
struct
nouveau_pm_profile
*
profile
=
NULL
;
struct
nouveau_pm_level
*
perflvl
=
NULL
;
int
ret
;
/* select power profile based on current power source */
if
(
power_supply_is_system_supplied
())
profile
=
pm
->
profile_ac
;
else
profile
=
pm
->
profile_dc
;
if
(
profile
!=
pm
->
profile
)
{
pm
->
profile
->
func
->
fini
(
pm
->
profile
);
pm
->
profile
=
profile
;
pm
->
profile
->
func
->
init
(
pm
->
profile
);
}
/* select performance level based on profile */
perflvl
=
profile
->
func
->
select
(
profile
);
/* change perflvl, if necessary */
if
(
perflvl
!=
pm
->
cur
)
{
u64
time0
=
ptimer
->
read
(
ptimer
);
NV_INFO
(
drm
,
"setting performance level: %d"
,
perflvl
->
id
);
ret
=
nouveau_pm_perflvl_set
(
dev
,
perflvl
);
if
(
ret
)
NV_INFO
(
drm
,
"> reclocking failed: %d
\n\n
"
,
ret
);
NV_INFO
(
drm
,
"> reclocking took %lluns
\n\n
"
,
ptimer
->
read
(
ptimer
)
-
time0
);
}
}
static
struct
nouveau_pm_profile
*
profile_find
(
struct
drm_device
*
dev
,
const
char
*
string
)
{
struct
nouveau_pm
*
pm
=
nouveau_pm
(
dev
);
struct
nouveau_pm_profile
*
profile
;
list_for_each_entry
(
profile
,
&
pm
->
profiles
,
head
)
{
if
(
!
strncmp
(
profile
->
name
,
string
,
sizeof
(
profile
->
name
)))
return
profile
;
}
return
NULL
;
}
static
int
nouveau_pm_profile_set
(
struct
drm_device
*
dev
,
const
char
*
profile
)
{
struct
nouveau_pm
*
pm
=
nouveau_pm
(
dev
);
struct
nouveau_pm_profile
*
ac
=
NULL
,
*
dc
=
NULL
;
char
string
[
16
],
*
cur
=
string
,
*
ptr
;
/* safety precaution, for now */
if
(
nouveau_perflvl_wr
!=
7777
)
return
-
EPERM
;
strncpy
(
string
,
profile
,
sizeof
(
string
));
string
[
sizeof
(
string
)
-
1
]
=
0
;
if
((
ptr
=
strchr
(
string
,
'\n'
)))
*
ptr
=
'\0'
;
ptr
=
strsep
(
&
cur
,
","
);
if
(
ptr
)
ac
=
profile_find
(
dev
,
ptr
);
ptr
=
strsep
(
&
cur
,
","
);
if
(
ptr
)
dc
=
profile_find
(
dev
,
ptr
);
else
dc
=
ac
;
if
(
ac
==
NULL
||
dc
==
NULL
)
return
-
EINVAL
;
pm
->
profile_ac
=
ac
;
pm
->
profile_dc
=
dc
;
nouveau_pm_trigger
(
dev
);
return
0
;
}
static
void
nouveau_pm_static_dummy
(
struct
nouveau_pm_profile
*
profile
)
{
}
static
struct
nouveau_pm_level
*
nouveau_pm_static_select
(
struct
nouveau_pm_profile
*
profile
)
{
return
container_of
(
profile
,
struct
nouveau_pm_level
,
profile
);
}
const
struct
nouveau_pm_profile_func
nouveau_pm_static_profile_func
=
{
.
destroy
=
nouveau_pm_static_dummy
,
.
init
=
nouveau_pm_static_dummy
,
.
fini
=
nouveau_pm_static_dummy
,
.
select
=
nouveau_pm_static_select
,
};
static
int
nouveau_pm_perflvl_get
(
struct
drm_device
*
dev
,
struct
nouveau_pm_level
*
perflvl
)
{
struct
nouveau_drm
*
drm
=
nouveau_drm
(
dev
);
struct
nouveau_pm
*
pm
=
nouveau_pm
(
dev
);
struct
nouveau_therm
*
therm
=
nouveau_therm
(
drm
->
device
);
int
ret
;
memset
(
perflvl
,
0
,
sizeof
(
*
perflvl
));
if
(
pm
->
clocks_get
)
{
ret
=
pm
->
clocks_get
(
dev
,
perflvl
);
if
(
ret
)
return
ret
;
}
if
(
pm
->
voltage
.
supported
&&
pm
->
voltage_get
)
{
ret
=
pm
->
voltage_get
(
dev
);
if
(
ret
>
0
)
{
perflvl
->
volt_min
=
ret
;
perflvl
->
volt_max
=
ret
;
}
}
if
(
therm
&&
therm
->
fan_get
)
{
ret
=
therm
->
fan_get
(
therm
);
if
(
ret
>=
0
)
perflvl
->
fanspeed
=
ret
;
}
nouveau_mem_timing_read
(
dev
,
&
perflvl
->
timing
);
return
0
;
}
static
void
nouveau_pm_perflvl_info
(
struct
nouveau_pm_level
*
perflvl
,
char
*
ptr
,
int
len
)
{
char
c
[
16
],
s
[
16
],
v
[
32
],
f
[
16
],
m
[
16
];
c
[
0
]
=
'\0'
;
if
(
perflvl
->
core
)
snprintf
(
c
,
sizeof
(
c
),
" core %dMHz"
,
perflvl
->
core
/
1000
);
s
[
0
]
=
'\0'
;
if
(
perflvl
->
shader
)
snprintf
(
s
,
sizeof
(
s
),
" shader %dMHz"
,
perflvl
->
shader
/
1000
);
m
[
0
]
=
'\0'
;
if
(
perflvl
->
memory
)
snprintf
(
m
,
sizeof
(
m
),
" memory %dMHz"
,
perflvl
->
memory
/
1000
);
v
[
0
]
=
'\0'
;
if
(
perflvl
->
volt_min
&&
perflvl
->
volt_min
!=
perflvl
->
volt_max
)
{
snprintf
(
v
,
sizeof
(
v
),
" voltage %dmV-%dmV"
,
perflvl
->
volt_min
/
1000
,
perflvl
->
volt_max
/
1000
);
}
else
if
(
perflvl
->
volt_min
)
{
snprintf
(
v
,
sizeof
(
v
),
" voltage %dmV"
,
perflvl
->
volt_min
/
1000
);
}
f
[
0
]
=
'\0'
;
if
(
perflvl
->
fanspeed
)
snprintf
(
f
,
sizeof
(
f
),
" fanspeed %d%%"
,
perflvl
->
fanspeed
);
snprintf
(
ptr
,
len
,
"%s%s%s%s%s
\n
"
,
c
,
s
,
m
,
v
,
f
);
}
static
ssize_t
nouveau_pm_get_perflvl_info
(
struct
device
*
d
,
struct
device_attribute
*
a
,
char
*
buf
)
{
struct
nouveau_pm_level
*
perflvl
=
container_of
(
a
,
struct
nouveau_pm_level
,
dev_attr
);
char
*
ptr
=
buf
;
int
len
=
PAGE_SIZE
;
snprintf
(
ptr
,
len
,
"%d:"
,
perflvl
->
id
);
ptr
+=
strlen
(
buf
);
len
-=
strlen
(
buf
);
nouveau_pm_perflvl_info
(
perflvl
,
ptr
,
len
);
return
strlen
(
buf
);
}
static
ssize_t
nouveau_pm_get_perflvl
(
struct
device
*
d
,
struct
device_attribute
*
a
,
char
*
buf
)
{
struct
drm_device
*
dev
=
pci_get_drvdata
(
to_pci_dev
(
d
));
struct
nouveau_pm
*
pm
=
nouveau_pm
(
dev
);
struct
nouveau_pm_level
cur
;
int
len
=
PAGE_SIZE
,
ret
;
char
*
ptr
=
buf
;
snprintf
(
ptr
,
len
,
"profile: %s, %s
\n
c:"
,
pm
->
profile_ac
->
name
,
pm
->
profile_dc
->
name
);
ptr
+=
strlen
(
buf
);
len
-=
strlen
(
buf
);
ret
=
nouveau_pm_perflvl_get
(
dev
,
&
cur
);
if
(
ret
==
0
)
nouveau_pm_perflvl_info
(
&
cur
,
ptr
,
len
);
return
strlen
(
buf
);
}
static
ssize_t
nouveau_pm_set_perflvl
(
struct
device
*
d
,
struct
device_attribute
*
a
,
const
char
*
buf
,
size_t
count
)
{
struct
drm_device
*
dev
=
pci_get_drvdata
(
to_pci_dev
(
d
));
int
ret
;
ret
=
nouveau_pm_profile_set
(
dev
,
buf
);
if
(
ret
)
return
ret
;
return
strlen
(
buf
);
}
static
DEVICE_ATTR
(
performance_level
,
S_IRUGO
|
S_IWUSR
,
nouveau_pm_get_perflvl
,
nouveau_pm_set_perflvl
);
static
int
nouveau_sysfs_init
(
struct
drm_device
*
dev
)
{
struct
nouveau_drm
*
drm
=
nouveau_drm
(
dev
);
struct
nouveau_pm
*
pm
=
nouveau_pm
(
dev
);
struct
device
*
d
=
&
dev
->
pdev
->
dev
;
int
ret
,
i
;
ret
=
device_create_file
(
d
,
&
dev_attr_performance_level
);
if
(
ret
)
return
ret
;
for
(
i
=
0
;
i
<
pm
->
nr_perflvl
;
i
++
)
{
struct
nouveau_pm_level
*
perflvl
=
&
pm
->
perflvl
[
i
];
perflvl
->
dev_attr
.
attr
.
name
=
perflvl
->
name
;
perflvl
->
dev_attr
.
attr
.
mode
=
S_IRUGO
;
perflvl
->
dev_attr
.
show
=
nouveau_pm_get_perflvl_info
;
perflvl
->
dev_attr
.
store
=
NULL
;
sysfs_attr_init
(
&
perflvl
->
dev_attr
.
attr
);
ret
=
device_create_file
(
d
,
&
perflvl
->
dev_attr
);
if
(
ret
)
{
NV_ERROR
(
drm
,
"failed pervlvl %d sysfs: %d
\n
"
,
perflvl
->
id
,
i
);
perflvl
->
dev_attr
.
attr
.
name
=
NULL
;
nouveau_pm_fini
(
dev
);
return
ret
;
}
}
return
0
;
}
static
void
nouveau_sysfs_fini
(
struct
drm_device
*
dev
)
{
struct
nouveau_pm
*
pm
=
nouveau_pm
(
dev
);
struct
device
*
d
=
&
dev
->
pdev
->
dev
;
int
i
;
device_remove_file
(
d
,
&
dev_attr_performance_level
);
for
(
i
=
0
;
i
<
pm
->
nr_perflvl
;
i
++
)
{
struct
nouveau_pm_level
*
pl
=
&
pm
->
perflvl
[
i
];
if
(
!
pl
->
dev_attr
.
attr
.
name
)
break
;
device_remove_file
(
d
,
&
pl
->
dev_attr
);
}
}
#if defined(CONFIG_HWMON) || (defined(MODULE) && defined(CONFIG_HWMON_MODULE))
#if defined(CONFIG_HWMON) || (defined(MODULE) && defined(CONFIG_HWMON_MODULE))
static
ssize_t
static
ssize_t
nouveau_hwmon_show_temp
(
struct
device
*
d
,
struct
device_attribute
*
a
,
char
*
buf
)
nouveau_hwmon_show_temp
(
struct
device
*
d
,
struct
device_attribute
*
a
,
char
*
buf
)
...
@@ -778,9 +421,6 @@ nouveau_hwmon_set_pwm1(struct device *d, struct device_attribute *a,
...
@@ -778,9 +421,6 @@ nouveau_hwmon_set_pwm1(struct device *d, struct device_attribute *a,
int
ret
=
-
ENODEV
;
int
ret
=
-
ENODEV
;
long
value
;
long
value
;
if
(
nouveau_perflvl_wr
!=
7777
)
return
-
EPERM
;
if
(
kstrtol
(
buf
,
10
,
&
value
)
==
-
EINVAL
)
if
(
kstrtol
(
buf
,
10
,
&
value
)
==
-
EINVAL
)
return
-
EINVAL
;
return
-
EINVAL
;
...
@@ -919,17 +559,21 @@ static const struct attribute_group hwmon_pwm_fan_attrgroup = {
...
@@ -919,17 +559,21 @@ static const struct attribute_group hwmon_pwm_fan_attrgroup = {
};
};
#endif
#endif
static
int
int
nouveau_hwmon_init
(
struct
drm_device
*
dev
)
nouveau_hwmon_init
(
struct
drm_device
*
dev
)
{
{
struct
nouveau_pm
*
pm
=
nouveau_pm
(
dev
);
#if defined(CONFIG_HWMON) || (defined(MODULE) && defined(CONFIG_HWMON_MODULE))
#if defined(CONFIG_HWMON) || (defined(MODULE) && defined(CONFIG_HWMON_MODULE))
struct
nouveau_drm
*
drm
=
nouveau_drm
(
dev
);
struct
nouveau_drm
*
drm
=
nouveau_drm
(
dev
);
struct
nouveau_therm
*
therm
=
nouveau_therm
(
drm
->
device
);
struct
nouveau_therm
*
therm
=
nouveau_therm
(
drm
->
device
);
struct
nouveau_hwmon
*
hwmon
;
struct
device
*
hwmon_dev
;
struct
device
*
hwmon_dev
;
int
ret
=
0
;
int
ret
=
0
;
hwmon
=
drm
->
hwmon
=
kzalloc
(
sizeof
(
*
hwmon
),
GFP_KERNEL
);
if
(
!
hwmon
)
return
-
ENOMEM
;
hwmon
->
dev
=
dev
;
if
(
!
therm
||
!
therm
->
temp_get
||
!
therm
->
attr_get
||
!
therm
->
attr_set
)
if
(
!
therm
||
!
therm
->
temp_get
||
!
therm
->
attr_get
||
!
therm
->
attr_set
)
return
-
ENODEV
;
return
-
ENODEV
;
...
@@ -976,199 +620,37 @@ nouveau_hwmon_init(struct drm_device *dev)
...
@@ -976,199 +620,37 @@ nouveau_hwmon_init(struct drm_device *dev)
goto
error
;
goto
error
;
}
}
pm
->
hwmon
=
hwmon_dev
;
hwmon
->
hwmon
=
hwmon_dev
;
return
0
;
return
0
;
error:
error:
NV_ERROR
(
drm
,
"Unable to create some hwmon sysfs files: %d
\n
"
,
ret
);
NV_ERROR
(
drm
,
"Unable to create some hwmon sysfs files: %d
\n
"
,
ret
);
hwmon_device_unregister
(
hwmon_dev
);
hwmon_device_unregister
(
hwmon_dev
);
pm
->
hwmon
=
NULL
;
hwmon
->
hwmon
=
NULL
;
return
ret
;
return
ret
;
#else
#else
pm
->
hwmon
=
NULL
;
hwmon
->
hwmon
=
NULL
;
return
0
;
return
0
;
#endif
#endif
}
}
static
void
void
nouveau_hwmon_fini
(
struct
drm_device
*
dev
)
nouveau_hwmon_fini
(
struct
drm_device
*
dev
)
{
{
#if defined(CONFIG_HWMON) || (defined(MODULE) && defined(CONFIG_HWMON_MODULE))
#if defined(CONFIG_HWMON) || (defined(MODULE) && defined(CONFIG_HWMON_MODULE))
struct
nouveau_
pm
*
pm
=
nouveau_pm
(
dev
);
struct
nouveau_
hwmon
*
hwmon
=
nouveau_hwmon
(
dev
);
if
(
pm
->
hwmon
)
{
if
(
hwmon
->
hwmon
)
{
sysfs_remove_group
(
&
pm
->
hwmon
->
kobj
,
&
hwmon_default_attrgroup
);
sysfs_remove_group
(
&
hwmon
->
hwmon
->
kobj
,
&
hwmon_default_attrgroup
);
sysfs_remove_group
(
&
pm
->
hwmon
->
kobj
,
&
hwmon_temp_attrgroup
);
sysfs_remove_group
(
&
hwmon
->
hwmon
->
kobj
,
&
hwmon_temp_attrgroup
);
sysfs_remove_group
(
&
pm
->
hwmon
->
kobj
,
&
hwmon_pwm_fan_attrgroup
);
sysfs_remove_group
(
&
hwmon
->
hwmon
->
kobj
,
&
hwmon_pwm_fan_attrgroup
);
sysfs_remove_group
(
&
pm
->
hwmon
->
kobj
,
&
hwmon_fan_rpm_attrgroup
);
sysfs_remove_group
(
&
hwmon
->
hwmon
->
kobj
,
&
hwmon_fan_rpm_attrgroup
);
hwmon_device_unregister
(
pm
->
hwmon
);
hwmon_device_unregister
(
hwmon
->
hwmon
);
}
}
#endif
}
#if defined(CONFIG_ACPI) && defined(CONFIG_POWER_SUPPLY)
static
int
nouveau_pm_acpi_event
(
struct
notifier_block
*
nb
,
unsigned
long
val
,
void
*
data
)
{
struct
nouveau_pm
*
pm
=
container_of
(
nb
,
struct
nouveau_pm
,
acpi_nb
);
struct
nouveau_drm
*
drm
=
nouveau_drm
(
pm
->
dev
);
struct
acpi_bus_event
*
entry
=
(
struct
acpi_bus_event
*
)
data
;
if
(
strcmp
(
entry
->
device_class
,
"ac_adapter"
)
==
0
)
{
bool
ac
=
power_supply_is_system_supplied
();
NV_DEBUG
(
drm
,
"power supply changed: %s
\n
"
,
ac
?
"AC"
:
"DC"
);
nouveau_drm
(
dev
)
->
hwmon
=
NULL
;
nouveau_pm_trigger
(
pm
->
dev
);
kfree
(
hwmon
);
}
return
NOTIFY_OK
;
}
#endif
#endif
int
nouveau_pm_init
(
struct
drm_device
*
dev
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
struct
nouveau_drm
*
drm
=
nouveau_drm
(
dev
);
struct
nouveau_pm
*
pm
;
char
info
[
256
];
int
ret
,
i
;
pm
=
drm
->
pm
=
kzalloc
(
sizeof
(
*
pm
),
GFP_KERNEL
);
if
(
!
pm
)
return
-
ENOMEM
;
pm
->
dev
=
dev
;
if
(
device
->
card_type
<
NV_40
)
{
pm
->
clocks_get
=
nv04_pm_clocks_get
;
pm
->
clocks_pre
=
nv04_pm_clocks_pre
;
pm
->
clocks_set
=
nv04_pm_clocks_set
;
if
(
nouveau_gpio
(
drm
->
device
))
{
pm
->
voltage_get
=
nouveau_voltage_gpio_get
;
pm
->
voltage_set
=
nouveau_voltage_gpio_set
;
}
}
else
if
(
device
->
card_type
<
NV_50
)
{
pm
->
clocks_get
=
nv40_pm_clocks_get
;
pm
->
clocks_pre
=
nv40_pm_clocks_pre
;
pm
->
clocks_set
=
nv40_pm_clocks_set
;
pm
->
voltage_get
=
nouveau_voltage_gpio_get
;
pm
->
voltage_set
=
nouveau_voltage_gpio_set
;
}
else
if
(
device
->
card_type
<
NV_C0
)
{
if
(
device
->
chipset
<
0xa3
||
device
->
chipset
==
0xaa
||
device
->
chipset
==
0xac
)
{
pm
->
clocks_get
=
nv50_pm_clocks_get
;
pm
->
clocks_pre
=
nv50_pm_clocks_pre
;
pm
->
clocks_set
=
nv50_pm_clocks_set
;
}
else
{
pm
->
clocks_get
=
nva3_pm_clocks_get
;
pm
->
clocks_pre
=
nva3_pm_clocks_pre
;
pm
->
clocks_set
=
nva3_pm_clocks_set
;
}
pm
->
voltage_get
=
nouveau_voltage_gpio_get
;
pm
->
voltage_set
=
nouveau_voltage_gpio_set
;
}
else
if
(
device
->
card_type
<
NV_E0
)
{
pm
->
clocks_get
=
nvc0_pm_clocks_get
;
pm
->
clocks_pre
=
nvc0_pm_clocks_pre
;
pm
->
clocks_set
=
nvc0_pm_clocks_set
;
pm
->
voltage_get
=
nouveau_voltage_gpio_get
;
pm
->
voltage_set
=
nouveau_voltage_gpio_set
;
}
/* parse aux tables from vbios */
nouveau_volt_init
(
dev
);
INIT_LIST_HEAD
(
&
pm
->
profiles
);
/* determine current ("boot") performance level */
ret
=
nouveau_pm_perflvl_get
(
dev
,
&
pm
->
boot
);
if
(
ret
)
{
NV_ERROR
(
drm
,
"failed to determine boot perflvl
\n
"
);
return
ret
;
}
strncpy
(
pm
->
boot
.
name
,
"boot"
,
4
);
strncpy
(
pm
->
boot
.
profile
.
name
,
"boot"
,
4
);
pm
->
boot
.
profile
.
func
=
&
nouveau_pm_static_profile_func
;
list_add
(
&
pm
->
boot
.
profile
.
head
,
&
pm
->
profiles
);
pm
->
profile_ac
=
&
pm
->
boot
.
profile
;
pm
->
profile_dc
=
&
pm
->
boot
.
profile
;
pm
->
profile
=
&
pm
->
boot
.
profile
;
pm
->
cur
=
&
pm
->
boot
;
/* add performance levels from vbios */
nouveau_perf_init
(
dev
);
/* display available performance levels */
NV_INFO
(
drm
,
"%d available performance level(s)
\n
"
,
pm
->
nr_perflvl
);
for
(
i
=
0
;
i
<
pm
->
nr_perflvl
;
i
++
)
{
nouveau_pm_perflvl_info
(
&
pm
->
perflvl
[
i
],
info
,
sizeof
(
info
));
NV_INFO
(
drm
,
"%d:%s"
,
pm
->
perflvl
[
i
].
id
,
info
);
}
nouveau_pm_perflvl_info
(
&
pm
->
boot
,
info
,
sizeof
(
info
));
NV_INFO
(
drm
,
"c:%s"
,
info
);
/* switch performance levels now if requested */
if
(
nouveau_perflvl
!=
NULL
)
nouveau_pm_profile_set
(
dev
,
nouveau_perflvl
);
nouveau_sysfs_init
(
dev
);
nouveau_hwmon_init
(
dev
);
#if defined(CONFIG_ACPI) && defined(CONFIG_POWER_SUPPLY)
pm
->
acpi_nb
.
notifier_call
=
nouveau_pm_acpi_event
;
register_acpi_notifier
(
&
pm
->
acpi_nb
);
#endif
return
0
;
}
void
nouveau_pm_fini
(
struct
drm_device
*
dev
)
{
struct
nouveau_pm
*
pm
=
nouveau_pm
(
dev
);
struct
nouveau_pm_profile
*
profile
,
*
tmp
;
list_for_each_entry_safe
(
profile
,
tmp
,
&
pm
->
profiles
,
head
)
{
list_del
(
&
profile
->
head
);
profile
->
func
->
destroy
(
profile
);
}
if
(
pm
->
cur
!=
&
pm
->
boot
)
nouveau_pm_perflvl_set
(
dev
,
&
pm
->
boot
);
nouveau_perf_fini
(
dev
);
nouveau_volt_fini
(
dev
);
#if defined(CONFIG_ACPI) && defined(CONFIG_POWER_SUPPLY)
unregister_acpi_notifier
(
&
pm
->
acpi_nb
);
#endif
nouveau_hwmon_fini
(
dev
);
nouveau_sysfs_fini
(
dev
);
nouveau_drm
(
dev
)
->
pm
=
NULL
;
kfree
(
pm
);
}
void
nouveau_pm_resume
(
struct
drm_device
*
dev
)
{
struct
nouveau_pm
*
pm
=
nouveau_pm
(
dev
);
struct
nouveau_pm_level
*
perflvl
;
if
(
!
pm
->
cur
||
pm
->
cur
==
&
pm
->
boot
)
return
;
perflvl
=
pm
->
cur
;
pm
->
cur
=
&
pm
->
boot
;
nouveau_pm_perflvl_set
(
dev
,
perflvl
);
}
}
drivers/gpu/drm/nouveau/nouveau_hw
sq
.h
→
drivers/gpu/drm/nouveau/nouveau_hw
mon
.h
View file @
b9ed919f
...
@@ -22,94 +22,22 @@
...
@@ -22,94 +22,22 @@
* Authors: Ben Skeggs
* Authors: Ben Skeggs
*/
*/
#ifndef __NOUVEAU_
HWSQ
_H__
#ifndef __NOUVEAU_
PM
_H__
#define __NOUVEAU_
HWSQ
_H__
#define __NOUVEAU_
PM
_H__
struct
hwsq_ucode
{
struct
nouveau_hwmon
{
u8
data
[
0x200
];
struct
drm_device
*
dev
;
union
{
struct
device
*
hwmon
;
u8
*
u08
;
u16
*
u16
;
u32
*
u32
;
}
ptr
;
u16
len
;
u32
reg
;
u32
val
;
};
};
static
inline
void
static
inline
struct
nouveau_hwmon
*
hwsq_init
(
struct
hwsq_ucode
*
hwsq
)
nouveau_hwmon
(
struct
drm_device
*
dev
)
{
hwsq
->
ptr
.
u08
=
hwsq
->
data
;
hwsq
->
reg
=
0xffffffff
;
hwsq
->
val
=
0xffffffff
;
}
static
inline
void
hwsq_fini
(
struct
hwsq_ucode
*
hwsq
)
{
do
{
*
hwsq
->
ptr
.
u08
++
=
0x7f
;
hwsq
->
len
=
hwsq
->
ptr
.
u08
-
hwsq
->
data
;
}
while
(
hwsq
->
len
&
3
);
hwsq
->
ptr
.
u08
=
hwsq
->
data
;
}
static
inline
void
hwsq_usec
(
struct
hwsq_ucode
*
hwsq
,
u8
usec
)
{
u32
shift
=
0
;
while
(
usec
&
~
3
)
{
usec
>>=
2
;
shift
++
;
}
*
hwsq
->
ptr
.
u08
++
=
(
shift
<<
2
)
|
usec
;
}
static
inline
void
hwsq_setf
(
struct
hwsq_ucode
*
hwsq
,
u8
flag
,
int
val
)
{
flag
+=
0x80
;
if
(
val
>=
0
)
flag
+=
0x20
;
if
(
val
>=
1
)
flag
+=
0x20
;
*
hwsq
->
ptr
.
u08
++
=
flag
;
}
static
inline
void
hwsq_op5f
(
struct
hwsq_ucode
*
hwsq
,
u8
v0
,
u8
v1
)
{
{
*
hwsq
->
ptr
.
u08
++
=
0x5f
;
return
nouveau_drm
(
dev
)
->
hwmon
;
*
hwsq
->
ptr
.
u08
++
=
v0
;
*
hwsq
->
ptr
.
u08
++
=
v1
;
}
}
static
inline
void
/* nouveau_hwmon.c */
hwsq_wr32
(
struct
hwsq_ucode
*
hwsq
,
u32
reg
,
u32
val
)
int
nouveau_hwmon_init
(
struct
drm_device
*
dev
);
{
void
nouveau_hwmon_fini
(
struct
drm_device
*
dev
);
if
(
val
!=
hwsq
->
val
)
{
if
((
val
&
0xffff0000
)
==
(
hwsq
->
val
&
0xffff0000
))
{
*
hwsq
->
ptr
.
u08
++
=
0x42
;
*
hwsq
->
ptr
.
u16
++
=
(
val
&
0x0000ffff
);
}
else
{
*
hwsq
->
ptr
.
u08
++
=
0xe2
;
*
hwsq
->
ptr
.
u32
++
=
val
;
}
hwsq
->
val
=
val
;
}
if
((
reg
&
0xffff0000
)
==
(
hwsq
->
reg
&
0xffff0000
))
{
*
hwsq
->
ptr
.
u08
++
=
0x40
;
*
hwsq
->
ptr
.
u16
++
=
(
reg
&
0x0000ffff
);
}
else
{
*
hwsq
->
ptr
.
u08
++
=
0xe0
;
*
hwsq
->
ptr
.
u32
++
=
reg
;
}
hwsq
->
reg
=
reg
;
}
#endif
#endif
drivers/gpu/drm/nouveau/nouveau_mem.c
deleted
100644 → 0
View file @
c52f4fa6
/*
* Copyright (C) The Weather Channel, Inc. 2002. All Rights Reserved.
* Copyright 2005 Stephane Marchesin
*
* The Weather Channel (TM) funded Tungsten Graphics to develop the
* initial release of the Radeon 8500 driver under the XFree86 license.
* This notice must be preserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS AND/OR THEIR SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
* Authors:
* Ben Skeggs <bskeggs@redhat.com>
* Roy Spliet <r.spliet@student.tudelft.nl>
*/
#include "nouveau_drm.h"
#include "nouveau_pm.h"
#include <subdev/fb.h>
static
int
nv40_mem_timing_calc
(
struct
drm_device
*
dev
,
u32
freq
,
struct
nouveau_pm_tbl_entry
*
e
,
u8
len
,
struct
nouveau_pm_memtiming
*
boot
,
struct
nouveau_pm_memtiming
*
t
)
{
struct
nouveau_drm
*
drm
=
nouveau_drm
(
dev
);
t
->
reg
[
0
]
=
(
e
->
tRP
<<
24
|
e
->
tRAS
<<
16
|
e
->
tRFC
<<
8
|
e
->
tRC
);
/* XXX: I don't trust the -1's and +1's... they must come
* from somewhere! */
t
->
reg
[
1
]
=
(
e
->
tWR
+
2
+
(
t
->
tCWL
-
1
))
<<
24
|
1
<<
16
|
(
e
->
tWTR
+
2
+
(
t
->
tCWL
-
1
))
<<
8
|
(
e
->
tCL
+
2
-
(
t
->
tCWL
-
1
));
t
->
reg
[
2
]
=
0x20200000
|
((
t
->
tCWL
-
1
)
<<
24
|
e
->
tRRD
<<
16
|
e
->
tRCDWR
<<
8
|
e
->
tRCDRD
);
NV_DEBUG
(
drm
,
"Entry %d: 220: %08x %08x %08x
\n
"
,
t
->
id
,
t
->
reg
[
0
],
t
->
reg
[
1
],
t
->
reg
[
2
]);
return
0
;
}
static
int
nv50_mem_timing_calc
(
struct
drm_device
*
dev
,
u32
freq
,
struct
nouveau_pm_tbl_entry
*
e
,
u8
len
,
struct
nouveau_pm_memtiming
*
boot
,
struct
nouveau_pm_memtiming
*
t
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
struct
nouveau_fb
*
pfb
=
nouveau_fb
(
device
);
struct
nouveau_drm
*
drm
=
nouveau_drm
(
dev
);
struct
bit_entry
P
;
uint8_t
unk18
=
1
,
unk20
=
0
,
unk21
=
0
,
tmp7_3
;
if
(
bit_table
(
dev
,
'P'
,
&
P
))
return
-
EINVAL
;
switch
(
min
(
len
,
(
u8
)
22
))
{
case
22
:
unk21
=
e
->
tUNK_21
;
case
21
:
unk20
=
e
->
tUNK_20
;
case
20
:
if
(
e
->
tCWL
>
0
)
t
->
tCWL
=
e
->
tCWL
;
case
19
:
unk18
=
e
->
tUNK_18
;
break
;
}
t
->
reg
[
0
]
=
(
e
->
tRP
<<
24
|
e
->
tRAS
<<
16
|
e
->
tRFC
<<
8
|
e
->
tRC
);
t
->
reg
[
1
]
=
(
e
->
tWR
+
2
+
(
t
->
tCWL
-
1
))
<<
24
|
max
(
unk18
,
(
u8
)
1
)
<<
16
|
(
e
->
tWTR
+
2
+
(
t
->
tCWL
-
1
))
<<
8
;
t
->
reg
[
2
]
=
((
t
->
tCWL
-
1
)
<<
24
|
e
->
tRRD
<<
16
|
e
->
tRCDWR
<<
8
|
e
->
tRCDRD
);
t
->
reg
[
4
]
=
e
->
tUNK_13
<<
8
|
e
->
tUNK_13
;
t
->
reg
[
5
]
=
(
e
->
tRFC
<<
24
|
max
(
e
->
tRCDRD
,
e
->
tRCDWR
)
<<
16
|
e
->
tRP
);
t
->
reg
[
8
]
=
boot
->
reg
[
8
]
&
0xffffff00
;
if
(
P
.
version
==
1
)
{
t
->
reg
[
1
]
|=
(
e
->
tCL
+
2
-
(
t
->
tCWL
-
1
));
t
->
reg
[
3
]
=
(
0x14
+
e
->
tCL
)
<<
24
|
0x16
<<
16
|
(
e
->
tCL
-
1
)
<<
8
|
(
e
->
tCL
-
1
);
t
->
reg
[
4
]
|=
boot
->
reg
[
4
]
&
0xffff0000
;
t
->
reg
[
6
]
=
(
0x33
-
t
->
tCWL
)
<<
16
|
t
->
tCWL
<<
8
|
(
0x2e
+
e
->
tCL
-
t
->
tCWL
);
t
->
reg
[
7
]
=
0x4000202
|
(
e
->
tCL
-
1
)
<<
16
;
/* XXX: P.version == 1 only has DDR2 and GDDR3? */
if
(
pfb
->
ram
->
type
==
NV_MEM_TYPE_DDR2
)
{
t
->
reg
[
5
]
|=
(
e
->
tCL
+
3
)
<<
8
;
t
->
reg
[
6
]
|=
(
t
->
tCWL
-
2
)
<<
8
;
t
->
reg
[
8
]
|=
(
e
->
tCL
-
4
);
}
else
{
t
->
reg
[
5
]
|=
(
e
->
tCL
+
2
)
<<
8
;
t
->
reg
[
6
]
|=
t
->
tCWL
<<
8
;
t
->
reg
[
8
]
|=
(
e
->
tCL
-
2
);
}
}
else
{
t
->
reg
[
1
]
|=
(
5
+
e
->
tCL
-
(
t
->
tCWL
));
/* XXX: 0xb? 0x30? */
t
->
reg
[
3
]
=
(
0x30
+
e
->
tCL
)
<<
24
|
(
boot
->
reg
[
3
]
&
0x00ff0000
)
|
(
0xb
+
e
->
tCL
)
<<
8
|
(
e
->
tCL
-
1
);
t
->
reg
[
4
]
|=
(
unk20
<<
24
|
unk21
<<
16
);
/* XXX: +6? */
t
->
reg
[
5
]
|=
(
t
->
tCWL
+
6
)
<<
8
;
t
->
reg
[
6
]
=
(
0x5a
+
e
->
tCL
)
<<
16
|
(
6
-
e
->
tCL
+
t
->
tCWL
)
<<
8
|
(
0x50
+
e
->
tCL
-
t
->
tCWL
);
tmp7_3
=
(
boot
->
reg
[
7
]
&
0xff000000
)
>>
24
;
t
->
reg
[
7
]
=
(
tmp7_3
<<
24
)
|
((
tmp7_3
-
6
+
e
->
tCL
)
<<
16
)
|
0x202
;
}
NV_DEBUG
(
drm
,
"Entry %d: 220: %08x %08x %08x %08x
\n
"
,
t
->
id
,
t
->
reg
[
0
],
t
->
reg
[
1
],
t
->
reg
[
2
],
t
->
reg
[
3
]);
NV_DEBUG
(
drm
,
" 230: %08x %08x %08x %08x
\n
"
,
t
->
reg
[
4
],
t
->
reg
[
5
],
t
->
reg
[
6
],
t
->
reg
[
7
]);
NV_DEBUG
(
drm
,
" 240: %08x
\n
"
,
t
->
reg
[
8
]);
return
0
;
}
static
int
nvc0_mem_timing_calc
(
struct
drm_device
*
dev
,
u32
freq
,
struct
nouveau_pm_tbl_entry
*
e
,
u8
len
,
struct
nouveau_pm_memtiming
*
boot
,
struct
nouveau_pm_memtiming
*
t
)
{
struct
nouveau_drm
*
drm
=
nouveau_drm
(
dev
);
if
(
e
->
tCWL
>
0
)
t
->
tCWL
=
e
->
tCWL
;
t
->
reg
[
0
]
=
(
e
->
tRP
<<
24
|
(
e
->
tRAS
&
0x7f
)
<<
17
|
e
->
tRFC
<<
8
|
e
->
tRC
);
t
->
reg
[
1
]
=
(
boot
->
reg
[
1
]
&
0xff000000
)
|
(
e
->
tRCDWR
&
0x0f
)
<<
20
|
(
e
->
tRCDRD
&
0x0f
)
<<
14
|
(
t
->
tCWL
<<
7
)
|
(
e
->
tCL
&
0x0f
);
t
->
reg
[
2
]
=
(
boot
->
reg
[
2
]
&
0xff0000ff
)
|
e
->
tWR
<<
16
|
e
->
tWTR
<<
8
;
t
->
reg
[
3
]
=
(
e
->
tUNK_20
&
0x1f
)
<<
9
|
(
e
->
tUNK_21
&
0xf
)
<<
5
|
(
e
->
tUNK_13
&
0x1f
);
t
->
reg
[
4
]
=
(
boot
->
reg
[
4
]
&
0xfff00fff
)
|
(
e
->
tRRD
&
0x1f
)
<<
15
;
NV_DEBUG
(
drm
,
"Entry %d: 290: %08x %08x %08x %08x
\n
"
,
t
->
id
,
t
->
reg
[
0
],
t
->
reg
[
1
],
t
->
reg
[
2
],
t
->
reg
[
3
]);
NV_DEBUG
(
drm
,
" 2a0: %08x
\n
"
,
t
->
reg
[
4
]);
return
0
;
}
/**
* MR generation methods
*/
static
int
nouveau_mem_ddr2_mr
(
struct
drm_device
*
dev
,
u32
freq
,
struct
nouveau_pm_tbl_entry
*
e
,
u8
len
,
struct
nouveau_pm_memtiming
*
boot
,
struct
nouveau_pm_memtiming
*
t
)
{
struct
nouveau_drm
*
drm
=
nouveau_drm
(
dev
);
t
->
drive_strength
=
0
;
if
(
len
<
15
)
{
t
->
odt
=
boot
->
odt
;
}
else
{
t
->
odt
=
e
->
RAM_FT1
&
0x07
;
}
if
(
e
->
tCL
>=
NV_MEM_CL_DDR2_MAX
)
{
NV_WARN
(
drm
,
"(%u) Invalid tCL: %u"
,
t
->
id
,
e
->
tCL
);
return
-
ERANGE
;
}
if
(
e
->
tWR
>=
NV_MEM_WR_DDR2_MAX
)
{
NV_WARN
(
drm
,
"(%u) Invalid tWR: %u"
,
t
->
id
,
e
->
tWR
);
return
-
ERANGE
;
}
if
(
t
->
odt
>
3
)
{
NV_WARN
(
drm
,
"(%u) Invalid odt value, assuming disabled: %x"
,
t
->
id
,
t
->
odt
);
t
->
odt
=
0
;
}
t
->
mr
[
0
]
=
(
boot
->
mr
[
0
]
&
0x100f
)
|
(
e
->
tCL
)
<<
4
|
(
e
->
tWR
-
1
)
<<
9
;
t
->
mr
[
1
]
=
(
boot
->
mr
[
1
]
&
0x101fbb
)
|
(
t
->
odt
&
0x1
)
<<
2
|
(
t
->
odt
&
0x2
)
<<
5
;
NV_DEBUG
(
drm
,
"(%u) MR: %08x"
,
t
->
id
,
t
->
mr
[
0
]);
return
0
;
}
static
const
uint8_t
nv_mem_wr_lut_ddr3
[
NV_MEM_WR_DDR3_MAX
]
=
{
0
,
0
,
0
,
0
,
0
,
1
,
2
,
3
,
4
,
5
,
5
,
6
,
6
,
7
,
7
,
0
,
0
};
static
int
nouveau_mem_ddr3_mr
(
struct
drm_device
*
dev
,
u32
freq
,
struct
nouveau_pm_tbl_entry
*
e
,
u8
len
,
struct
nouveau_pm_memtiming
*
boot
,
struct
nouveau_pm_memtiming
*
t
)
{
struct
nouveau_drm
*
drm
=
nouveau_drm
(
dev
);
u8
cl
=
e
->
tCL
-
4
;
t
->
drive_strength
=
0
;
if
(
len
<
15
)
{
t
->
odt
=
boot
->
odt
;
}
else
{
t
->
odt
=
e
->
RAM_FT1
&
0x07
;
}
if
(
e
->
tCL
>=
NV_MEM_CL_DDR3_MAX
||
e
->
tCL
<
4
)
{
NV_WARN
(
drm
,
"(%u) Invalid tCL: %u"
,
t
->
id
,
e
->
tCL
);
return
-
ERANGE
;
}
if
(
e
->
tWR
>=
NV_MEM_WR_DDR3_MAX
||
e
->
tWR
<
4
)
{
NV_WARN
(
drm
,
"(%u) Invalid tWR: %u"
,
t
->
id
,
e
->
tWR
);
return
-
ERANGE
;
}
if
(
e
->
tCWL
<
5
)
{
NV_WARN
(
drm
,
"(%u) Invalid tCWL: %u"
,
t
->
id
,
e
->
tCWL
);
return
-
ERANGE
;
}
t
->
mr
[
0
]
=
(
boot
->
mr
[
0
]
&
0x180b
)
|
/* CAS */
(
cl
&
0x7
)
<<
4
|
(
cl
&
0x8
)
>>
1
|
(
nv_mem_wr_lut_ddr3
[
e
->
tWR
])
<<
9
;
t
->
mr
[
1
]
=
(
boot
->
mr
[
1
]
&
0x101dbb
)
|
(
t
->
odt
&
0x1
)
<<
2
|
(
t
->
odt
&
0x2
)
<<
5
|
(
t
->
odt
&
0x4
)
<<
7
;
t
->
mr
[
2
]
=
(
boot
->
mr
[
2
]
&
0x20ffb7
)
|
(
e
->
tCWL
-
5
)
<<
3
;
NV_DEBUG
(
drm
,
"(%u) MR: %08x %08x"
,
t
->
id
,
t
->
mr
[
0
],
t
->
mr
[
2
]);
return
0
;
}
static
const
uint8_t
nv_mem_cl_lut_gddr3
[
NV_MEM_CL_GDDR3_MAX
]
=
{
0
,
0
,
0
,
0
,
4
,
5
,
6
,
7
,
0
,
1
,
2
,
3
,
8
,
9
,
10
,
11
};
static
const
uint8_t
nv_mem_wr_lut_gddr3
[
NV_MEM_WR_GDDR3_MAX
]
=
{
0
,
0
,
0
,
0
,
0
,
2
,
3
,
8
,
9
,
10
,
11
,
0
,
0
,
1
,
1
,
0
,
3
};
static
int
nouveau_mem_gddr3_mr
(
struct
drm_device
*
dev
,
u32
freq
,
struct
nouveau_pm_tbl_entry
*
e
,
u8
len
,
struct
nouveau_pm_memtiming
*
boot
,
struct
nouveau_pm_memtiming
*
t
)
{
struct
nouveau_drm
*
drm
=
nouveau_drm
(
dev
);
if
(
len
<
15
)
{
t
->
drive_strength
=
boot
->
drive_strength
;
t
->
odt
=
boot
->
odt
;
}
else
{
t
->
drive_strength
=
(
e
->
RAM_FT1
&
0x30
)
>>
4
;
t
->
odt
=
e
->
RAM_FT1
&
0x07
;
}
if
(
e
->
tCL
>=
NV_MEM_CL_GDDR3_MAX
)
{
NV_WARN
(
drm
,
"(%u) Invalid tCL: %u"
,
t
->
id
,
e
->
tCL
);
return
-
ERANGE
;
}
if
(
e
->
tWR
>=
NV_MEM_WR_GDDR3_MAX
)
{
NV_WARN
(
drm
,
"(%u) Invalid tWR: %u"
,
t
->
id
,
e
->
tWR
);
return
-
ERANGE
;
}
if
(
t
->
odt
>
3
)
{
NV_WARN
(
drm
,
"(%u) Invalid odt value, assuming autocal: %x"
,
t
->
id
,
t
->
odt
);
t
->
odt
=
0
;
}
t
->
mr
[
0
]
=
(
boot
->
mr
[
0
]
&
0xe0b
)
|
/* CAS */
((
nv_mem_cl_lut_gddr3
[
e
->
tCL
]
&
0x7
)
<<
4
)
|
((
nv_mem_cl_lut_gddr3
[
e
->
tCL
]
&
0x8
)
>>
2
);
t
->
mr
[
1
]
=
(
boot
->
mr
[
1
]
&
0x100f40
)
|
t
->
drive_strength
|
(
t
->
odt
<<
2
)
|
(
nv_mem_wr_lut_gddr3
[
e
->
tWR
]
&
0xf
)
<<
4
;
t
->
mr
[
2
]
=
boot
->
mr
[
2
];
NV_DEBUG
(
drm
,
"(%u) MR: %08x %08x %08x"
,
t
->
id
,
t
->
mr
[
0
],
t
->
mr
[
1
],
t
->
mr
[
2
]);
return
0
;
}
static
int
nouveau_mem_gddr5_mr
(
struct
drm_device
*
dev
,
u32
freq
,
struct
nouveau_pm_tbl_entry
*
e
,
u8
len
,
struct
nouveau_pm_memtiming
*
boot
,
struct
nouveau_pm_memtiming
*
t
)
{
struct
nouveau_drm
*
drm
=
nouveau_drm
(
dev
);
if
(
len
<
15
)
{
t
->
drive_strength
=
boot
->
drive_strength
;
t
->
odt
=
boot
->
odt
;
}
else
{
t
->
drive_strength
=
(
e
->
RAM_FT1
&
0x30
)
>>
4
;
t
->
odt
=
e
->
RAM_FT1
&
0x03
;
}
if
(
e
->
tCL
>=
NV_MEM_CL_GDDR5_MAX
)
{
NV_WARN
(
drm
,
"(%u) Invalid tCL: %u"
,
t
->
id
,
e
->
tCL
);
return
-
ERANGE
;
}
if
(
e
->
tWR
>=
NV_MEM_WR_GDDR5_MAX
)
{
NV_WARN
(
drm
,
"(%u) Invalid tWR: %u"
,
t
->
id
,
e
->
tWR
);
return
-
ERANGE
;
}
if
(
t
->
odt
>
3
)
{
NV_WARN
(
drm
,
"(%u) Invalid odt value, assuming autocal: %x"
,
t
->
id
,
t
->
odt
);
t
->
odt
=
0
;
}
t
->
mr
[
0
]
=
(
boot
->
mr
[
0
]
&
0x007
)
|
((
e
->
tCL
-
5
)
<<
3
)
|
((
e
->
tWR
-
4
)
<<
8
);
t
->
mr
[
1
]
=
(
boot
->
mr
[
1
]
&
0x1007f0
)
|
t
->
drive_strength
|
(
t
->
odt
<<
2
);
NV_DEBUG
(
drm
,
"(%u) MR: %08x %08x"
,
t
->
id
,
t
->
mr
[
0
],
t
->
mr
[
1
]);
return
0
;
}
int
nouveau_mem_timing_calc
(
struct
drm_device
*
dev
,
u32
freq
,
struct
nouveau_pm_memtiming
*
t
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
struct
nouveau_fb
*
pfb
=
nouveau_fb
(
device
);
struct
nouveau_pm
*
pm
=
nouveau_pm
(
dev
);
struct
nouveau_pm_memtiming
*
boot
=
&
pm
->
boot
.
timing
;
struct
nouveau_pm_tbl_entry
*
e
;
u8
ver
,
len
,
*
ptr
,
*
ramcfg
;
int
ret
;
ptr
=
nouveau_perf_timing
(
dev
,
freq
,
&
ver
,
&
len
);
if
(
!
ptr
||
ptr
[
0
]
==
0x00
)
{
*
t
=
*
boot
;
return
0
;
}
e
=
(
struct
nouveau_pm_tbl_entry
*
)
ptr
;
t
->
tCWL
=
boot
->
tCWL
;
switch
(
device
->
card_type
)
{
case
NV_40
:
ret
=
nv40_mem_timing_calc
(
dev
,
freq
,
e
,
len
,
boot
,
t
);
break
;
case
NV_50
:
ret
=
nv50_mem_timing_calc
(
dev
,
freq
,
e
,
len
,
boot
,
t
);
break
;
case
NV_C0
:
case
NV_D0
:
ret
=
nvc0_mem_timing_calc
(
dev
,
freq
,
e
,
len
,
boot
,
t
);
break
;
default:
ret
=
-
ENODEV
;
break
;
}
switch
(
pfb
->
ram
->
type
*
!
ret
)
{
case
NV_MEM_TYPE_GDDR3
:
ret
=
nouveau_mem_gddr3_mr
(
dev
,
freq
,
e
,
len
,
boot
,
t
);
break
;
case
NV_MEM_TYPE_GDDR5
:
ret
=
nouveau_mem_gddr5_mr
(
dev
,
freq
,
e
,
len
,
boot
,
t
);
break
;
case
NV_MEM_TYPE_DDR2
:
ret
=
nouveau_mem_ddr2_mr
(
dev
,
freq
,
e
,
len
,
boot
,
t
);
break
;
case
NV_MEM_TYPE_DDR3
:
ret
=
nouveau_mem_ddr3_mr
(
dev
,
freq
,
e
,
len
,
boot
,
t
);
break
;
default:
ret
=
-
EINVAL
;
break
;
}
ramcfg
=
nouveau_perf_ramcfg
(
dev
,
freq
,
&
ver
,
&
len
);
if
(
ramcfg
)
{
int
dll_off
;
if
(
ver
==
0x00
)
dll_off
=
!!
(
ramcfg
[
3
]
&
0x04
);
else
dll_off
=
!!
(
ramcfg
[
2
]
&
0x40
);
switch
(
pfb
->
ram
->
type
)
{
case
NV_MEM_TYPE_GDDR3
:
t
->
mr
[
1
]
&=
~
0x00000040
;
t
->
mr
[
1
]
|=
0x00000040
*
dll_off
;
break
;
default:
t
->
mr
[
1
]
&=
~
0x00000001
;
t
->
mr
[
1
]
|=
0x00000001
*
dll_off
;
break
;
}
}
return
ret
;
}
void
nouveau_mem_timing_read
(
struct
drm_device
*
dev
,
struct
nouveau_pm_memtiming
*
t
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
struct
nouveau_fb
*
pfb
=
nouveau_fb
(
device
);
u32
timing_base
,
timing_regs
,
mr_base
;
int
i
;
if
(
device
->
card_type
>=
0xC0
)
{
timing_base
=
0x10f290
;
mr_base
=
0x10f300
;
}
else
{
timing_base
=
0x100220
;
mr_base
=
0x1002c0
;
}
t
->
id
=
-
1
;
switch
(
device
->
card_type
)
{
case
NV_50
:
timing_regs
=
9
;
break
;
case
NV_C0
:
case
NV_D0
:
timing_regs
=
5
;
break
;
case
NV_30
:
case
NV_40
:
timing_regs
=
3
;
break
;
default:
timing_regs
=
0
;
return
;
}
for
(
i
=
0
;
i
<
timing_regs
;
i
++
)
t
->
reg
[
i
]
=
nv_rd32
(
device
,
timing_base
+
(
0x04
*
i
));
t
->
tCWL
=
0
;
if
(
device
->
card_type
<
NV_C0
)
{
t
->
tCWL
=
((
nv_rd32
(
device
,
0x100228
)
&
0x0f000000
)
>>
24
)
+
1
;
}
else
if
(
device
->
card_type
<=
NV_D0
)
{
t
->
tCWL
=
((
nv_rd32
(
device
,
0x10f294
)
&
0x00000f80
)
>>
7
);
}
t
->
mr
[
0
]
=
nv_rd32
(
device
,
mr_base
);
t
->
mr
[
1
]
=
nv_rd32
(
device
,
mr_base
+
0x04
);
t
->
mr
[
2
]
=
nv_rd32
(
device
,
mr_base
+
0x20
);
t
->
mr
[
3
]
=
nv_rd32
(
device
,
mr_base
+
0x24
);
t
->
odt
=
0
;
t
->
drive_strength
=
0
;
switch
(
pfb
->
ram
->
type
)
{
case
NV_MEM_TYPE_DDR3
:
t
->
odt
|=
(
t
->
mr
[
1
]
&
0x200
)
>>
7
;
case
NV_MEM_TYPE_DDR2
:
t
->
odt
|=
(
t
->
mr
[
1
]
&
0x04
)
>>
2
|
(
t
->
mr
[
1
]
&
0x40
)
>>
5
;
break
;
case
NV_MEM_TYPE_GDDR3
:
case
NV_MEM_TYPE_GDDR5
:
t
->
drive_strength
=
t
->
mr
[
1
]
&
0x03
;
t
->
odt
=
(
t
->
mr
[
1
]
&
0x0c
)
>>
2
;
break
;
default:
break
;
}
}
int
nouveau_mem_exec
(
struct
nouveau_mem_exec_func
*
exec
,
struct
nouveau_pm_level
*
perflvl
)
{
struct
nouveau_drm
*
drm
=
nouveau_drm
(
exec
->
dev
);
struct
nouveau_device
*
device
=
nouveau_dev
(
exec
->
dev
);
struct
nouveau_fb
*
pfb
=
nouveau_fb
(
device
);
struct
nouveau_pm_memtiming
*
info
=
&
perflvl
->
timing
;
u32
tMRD
=
1000
,
tCKSRE
=
0
,
tCKSRX
=
0
,
tXS
=
0
,
tDLLK
=
0
;
u32
mr
[
3
]
=
{
info
->
mr
[
0
],
info
->
mr
[
1
],
info
->
mr
[
2
]
};
u32
mr1_dlloff
;
switch
(
pfb
->
ram
->
type
)
{
case
NV_MEM_TYPE_DDR2
:
tDLLK
=
2000
;
mr1_dlloff
=
0x00000001
;
break
;
case
NV_MEM_TYPE_DDR3
:
tDLLK
=
12000
;
tCKSRE
=
2000
;
tXS
=
1000
;
mr1_dlloff
=
0x00000001
;
break
;
case
NV_MEM_TYPE_GDDR3
:
tDLLK
=
40000
;
mr1_dlloff
=
0x00000040
;
break
;
default:
NV_ERROR
(
drm
,
"cannot reclock unsupported memtype
\n
"
);
return
-
ENODEV
;
}
/* fetch current MRs */
switch
(
pfb
->
ram
->
type
)
{
case
NV_MEM_TYPE_GDDR3
:
case
NV_MEM_TYPE_DDR3
:
mr
[
2
]
=
exec
->
mrg
(
exec
,
2
);
default:
mr
[
1
]
=
exec
->
mrg
(
exec
,
1
);
mr
[
0
]
=
exec
->
mrg
(
exec
,
0
);
break
;
}
/* DLL 'on' -> DLL 'off' mode, disable before entering self-refresh */
if
(
!
(
mr
[
1
]
&
mr1_dlloff
)
&&
(
info
->
mr
[
1
]
&
mr1_dlloff
))
{
exec
->
precharge
(
exec
);
exec
->
mrs
(
exec
,
1
,
mr
[
1
]
|
mr1_dlloff
);
exec
->
wait
(
exec
,
tMRD
);
}
/* enter self-refresh mode */
exec
->
precharge
(
exec
);
exec
->
refresh
(
exec
);
exec
->
refresh
(
exec
);
exec
->
refresh_auto
(
exec
,
false
);
exec
->
refresh_self
(
exec
,
true
);
exec
->
wait
(
exec
,
tCKSRE
);
/* modify input clock frequency */
exec
->
clock_set
(
exec
);
/* exit self-refresh mode */
exec
->
wait
(
exec
,
tCKSRX
);
exec
->
precharge
(
exec
);
exec
->
refresh_self
(
exec
,
false
);
exec
->
refresh_auto
(
exec
,
true
);
exec
->
wait
(
exec
,
tXS
);
exec
->
wait
(
exec
,
tXS
);
/* update MRs */
if
(
mr
[
2
]
!=
info
->
mr
[
2
])
{
exec
->
mrs
(
exec
,
2
,
info
->
mr
[
2
]);
exec
->
wait
(
exec
,
tMRD
);
}
if
(
mr
[
1
]
!=
info
->
mr
[
1
])
{
/* need to keep DLL off until later, at least on GDDR3 */
exec
->
mrs
(
exec
,
1
,
info
->
mr
[
1
]
|
(
mr
[
1
]
&
mr1_dlloff
));
exec
->
wait
(
exec
,
tMRD
);
}
if
(
mr
[
0
]
!=
info
->
mr
[
0
])
{
exec
->
mrs
(
exec
,
0
,
info
->
mr
[
0
]);
exec
->
wait
(
exec
,
tMRD
);
}
/* update PFB timing registers */
exec
->
timing_set
(
exec
);
/* DLL (enable + ) reset */
if
(
!
(
info
->
mr
[
1
]
&
mr1_dlloff
))
{
if
(
mr
[
1
]
&
mr1_dlloff
)
{
exec
->
mrs
(
exec
,
1
,
info
->
mr
[
1
]);
exec
->
wait
(
exec
,
tMRD
);
}
exec
->
mrs
(
exec
,
0
,
info
->
mr
[
0
]
|
0x00000100
);
exec
->
wait
(
exec
,
tMRD
);
exec
->
mrs
(
exec
,
0
,
info
->
mr
[
0
]
|
0x00000000
);
exec
->
wait
(
exec
,
tMRD
);
exec
->
wait
(
exec
,
tDLLK
);
if
(
pfb
->
ram
->
type
==
NV_MEM_TYPE_GDDR3
)
exec
->
precharge
(
exec
);
}
return
0
;
}
drivers/gpu/drm/nouveau/nouveau_perf.c
deleted
100644 → 0
View file @
c52f4fa6
/*
* Copyright 2010 Red Hat Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* Authors: Ben Skeggs
*/
#include <drm/drmP.h>
#include "nouveau_drm.h"
#include "nouveau_reg.h"
#include "nouveau_pm.h"
static
u8
*
nouveau_perf_table
(
struct
drm_device
*
dev
,
u8
*
ver
)
{
struct
nouveau_drm
*
drm
=
nouveau_drm
(
dev
);
struct
nvbios
*
bios
=
&
drm
->
vbios
;
struct
bit_entry
P
;
if
(
!
bit_table
(
dev
,
'P'
,
&
P
)
&&
P
.
version
&&
P
.
version
<=
2
)
{
u8
*
perf
=
ROMPTR
(
dev
,
P
.
data
[
0
]);
if
(
perf
)
{
*
ver
=
perf
[
0
];
return
perf
;
}
}
if
(
bios
->
type
==
NVBIOS_BMP
)
{
if
(
bios
->
data
[
bios
->
offset
+
6
]
>=
0x25
)
{
u8
*
perf
=
ROMPTR
(
dev
,
bios
->
data
[
bios
->
offset
+
0x94
]);
if
(
perf
)
{
*
ver
=
perf
[
1
];
return
perf
;
}
}
}
return
NULL
;
}
static
u8
*
nouveau_perf_entry
(
struct
drm_device
*
dev
,
int
idx
,
u8
*
ver
,
u8
*
hdr
,
u8
*
cnt
,
u8
*
len
)
{
u8
*
perf
=
nouveau_perf_table
(
dev
,
ver
);
if
(
perf
)
{
if
(
*
ver
>=
0x12
&&
*
ver
<
0x20
&&
idx
<
perf
[
2
])
{
*
hdr
=
perf
[
3
];
*
cnt
=
0
;
*
len
=
0
;
return
perf
+
perf
[
0
]
+
idx
*
perf
[
3
];
}
else
if
(
*
ver
>=
0x20
&&
*
ver
<
0x40
&&
idx
<
perf
[
2
])
{
*
hdr
=
perf
[
3
];
*
cnt
=
perf
[
4
];
*
len
=
perf
[
5
];
return
perf
+
perf
[
1
]
+
idx
*
(
*
hdr
+
(
*
cnt
*
*
len
));
}
else
if
(
*
ver
>=
0x40
&&
*
ver
<
0x41
&&
idx
<
perf
[
5
])
{
*
hdr
=
perf
[
2
];
*
cnt
=
perf
[
4
];
*
len
=
perf
[
3
];
return
perf
+
perf
[
1
]
+
idx
*
(
*
hdr
+
(
*
cnt
*
*
len
));
}
}
return
NULL
;
}
u8
*
nouveau_perf_rammap
(
struct
drm_device
*
dev
,
u32
freq
,
u8
*
ver
,
u8
*
hdr
,
u8
*
cnt
,
u8
*
len
)
{
struct
nouveau_drm
*
drm
=
nouveau_drm
(
dev
);
struct
bit_entry
P
;
u8
*
perf
,
i
=
0
;
if
(
!
bit_table
(
dev
,
'P'
,
&
P
)
&&
P
.
version
==
2
)
{
u8
*
rammap
=
ROMPTR
(
dev
,
P
.
data
[
4
]);
if
(
rammap
)
{
u8
*
ramcfg
=
rammap
+
rammap
[
1
];
*
ver
=
rammap
[
0
];
*
hdr
=
rammap
[
2
];
*
cnt
=
rammap
[
4
];
*
len
=
rammap
[
3
];
freq
/=
1000
;
for
(
i
=
0
;
i
<
rammap
[
5
];
i
++
)
{
if
(
freq
>=
ROM16
(
ramcfg
[
0
])
&&
freq
<=
ROM16
(
ramcfg
[
2
]))
return
ramcfg
;
ramcfg
+=
*
hdr
+
(
*
cnt
*
*
len
);
}
}
return
NULL
;
}
if
(
nv_device
(
drm
->
device
)
->
chipset
==
0x49
||
nv_device
(
drm
->
device
)
->
chipset
==
0x4b
)
freq
/=
2
;
while
((
perf
=
nouveau_perf_entry
(
dev
,
i
++
,
ver
,
hdr
,
cnt
,
len
)))
{
if
(
*
ver
>=
0x20
&&
*
ver
<
0x25
)
{
if
(
perf
[
0
]
!=
0xff
&&
freq
<=
ROM16
(
perf
[
11
])
*
1000
)
break
;
}
else
if
(
*
ver
>=
0x25
&&
*
ver
<
0x40
)
{
if
(
perf
[
0
]
!=
0xff
&&
freq
<=
ROM16
(
perf
[
12
])
*
1000
)
break
;
}
}
if
(
perf
)
{
u8
*
ramcfg
=
perf
+
*
hdr
;
*
ver
=
0x00
;
*
hdr
=
0
;
return
ramcfg
;
}
return
NULL
;
}
u8
*
nouveau_perf_ramcfg
(
struct
drm_device
*
dev
,
u32
freq
,
u8
*
ver
,
u8
*
len
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
struct
nouveau_drm
*
drm
=
nouveau_drm
(
dev
);
struct
nvbios
*
bios
=
&
drm
->
vbios
;
u8
strap
,
hdr
,
cnt
;
u8
*
rammap
;
strap
=
(
nv_rd32
(
device
,
0x101000
)
&
0x0000003c
)
>>
2
;
if
(
bios
->
ram_restrict_tbl_ptr
)
strap
=
bios
->
data
[
bios
->
ram_restrict_tbl_ptr
+
strap
];
rammap
=
nouveau_perf_rammap
(
dev
,
freq
,
ver
,
&
hdr
,
&
cnt
,
len
);
if
(
rammap
&&
strap
<
cnt
)
return
rammap
+
hdr
+
(
strap
*
*
len
);
return
NULL
;
}
u8
*
nouveau_perf_timing
(
struct
drm_device
*
dev
,
u32
freq
,
u8
*
ver
,
u8
*
len
)
{
struct
nouveau_drm
*
drm
=
nouveau_drm
(
dev
);
struct
nvbios
*
bios
=
&
drm
->
vbios
;
struct
bit_entry
P
;
u8
*
perf
,
*
timing
=
NULL
;
u8
i
=
0
,
hdr
,
cnt
;
if
(
bios
->
type
==
NVBIOS_BMP
)
{
while
((
perf
=
nouveau_perf_entry
(
dev
,
i
++
,
ver
,
&
hdr
,
&
cnt
,
len
))
&&
*
ver
==
0x15
)
{
if
(
freq
<=
ROM32
(
perf
[
5
])
*
20
)
{
*
ver
=
0x00
;
*
len
=
14
;
return
perf
+
41
;
}
}
return
NULL
;
}
if
(
!
bit_table
(
dev
,
'P'
,
&
P
))
{
if
(
P
.
version
==
1
)
timing
=
ROMPTR
(
dev
,
P
.
data
[
4
]);
else
if
(
P
.
version
==
2
)
timing
=
ROMPTR
(
dev
,
P
.
data
[
8
]);
}
if
(
timing
&&
timing
[
0
]
==
0x10
)
{
u8
*
ramcfg
=
nouveau_perf_ramcfg
(
dev
,
freq
,
ver
,
len
);
if
(
ramcfg
&&
ramcfg
[
1
]
<
timing
[
2
])
{
*
ver
=
timing
[
0
];
*
len
=
timing
[
3
];
return
timing
+
timing
[
1
]
+
(
ramcfg
[
1
]
*
timing
[
3
]);
}
}
return
NULL
;
}
static
void
legacy_perf_init
(
struct
drm_device
*
dev
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
struct
nouveau_drm
*
drm
=
nouveau_drm
(
dev
);
struct
nvbios
*
bios
=
&
drm
->
vbios
;
struct
nouveau_pm
*
pm
=
nouveau_pm
(
dev
);
char
*
perf
,
*
entry
,
*
bmp
=
&
bios
->
data
[
bios
->
offset
];
int
headerlen
,
use_straps
;
if
(
bmp
[
5
]
<
0x5
||
bmp
[
6
]
<
0x14
)
{
NV_DEBUG
(
drm
,
"BMP version too old for perf
\n
"
);
return
;
}
perf
=
ROMPTR
(
dev
,
bmp
[
0x73
]);
if
(
!
perf
)
{
NV_DEBUG
(
drm
,
"No memclock table pointer found.
\n
"
);
return
;
}
switch
(
perf
[
0
])
{
case
0x12
:
case
0x14
:
case
0x18
:
use_straps
=
0
;
headerlen
=
1
;
break
;
case
0x01
:
use_straps
=
perf
[
1
]
&
1
;
headerlen
=
(
use_straps
?
8
:
2
);
break
;
default:
NV_WARN
(
drm
,
"Unknown memclock table version %x.
\n
"
,
perf
[
0
]);
return
;
}
entry
=
perf
+
headerlen
;
if
(
use_straps
)
entry
+=
(
nv_rd32
(
device
,
NV_PEXTDEV_BOOT_0
)
&
0x3c
)
>>
1
;
sprintf
(
pm
->
perflvl
[
0
].
name
,
"performance_level_0"
);
pm
->
perflvl
[
0
].
memory
=
ROM16
(
entry
[
0
])
*
20
;
pm
->
nr_perflvl
=
1
;
}
static
void
nouveau_perf_voltage
(
struct
drm_device
*
dev
,
struct
nouveau_pm_level
*
perflvl
)
{
struct
nouveau_drm
*
drm
=
nouveau_drm
(
dev
);
struct
bit_entry
P
;
u8
*
vmap
;
int
id
;
id
=
perflvl
->
volt_min
;
perflvl
->
volt_min
=
0
;
/* boards using voltage table version <0x40 store the voltage
* level directly in the perflvl entry as a multiple of 10mV
*/
if
(
drm
->
pm
->
voltage
.
version
<
0x40
)
{
perflvl
->
volt_min
=
id
*
10000
;
perflvl
->
volt_max
=
perflvl
->
volt_min
;
return
;
}
/* on newer ones, the perflvl stores an index into yet another
* vbios table containing a min/max voltage value for the perflvl
*/
if
(
bit_table
(
dev
,
'P'
,
&
P
)
||
P
.
version
!=
2
||
P
.
length
<
34
)
{
NV_DEBUG
(
drm
,
"where's our volt map table ptr? %d %d
\n
"
,
P
.
version
,
P
.
length
);
return
;
}
vmap
=
ROMPTR
(
dev
,
P
.
data
[
32
]);
if
(
!
vmap
)
{
NV_DEBUG
(
drm
,
"volt map table pointer invalid
\n
"
);
return
;
}
if
(
id
<
vmap
[
3
])
{
vmap
+=
vmap
[
1
]
+
(
vmap
[
2
]
*
id
);
perflvl
->
volt_min
=
ROM32
(
vmap
[
0
]);
perflvl
->
volt_max
=
ROM32
(
vmap
[
4
]);
}
}
void
nouveau_perf_init
(
struct
drm_device
*
dev
)
{
struct
nouveau_drm
*
drm
=
nouveau_drm
(
dev
);
struct
nouveau_pm
*
pm
=
nouveau_pm
(
dev
);
struct
nvbios
*
bios
=
&
drm
->
vbios
;
u8
*
perf
,
ver
,
hdr
,
cnt
,
len
;
int
ret
,
vid
,
i
=
-
1
;
if
(
bios
->
type
==
NVBIOS_BMP
&&
bios
->
data
[
bios
->
offset
+
6
]
<
0x25
)
{
legacy_perf_init
(
dev
);
return
;
}
perf
=
nouveau_perf_table
(
dev
,
&
ver
);
while
((
perf
=
nouveau_perf_entry
(
dev
,
++
i
,
&
ver
,
&
hdr
,
&
cnt
,
&
len
)))
{
struct
nouveau_pm_level
*
perflvl
=
&
pm
->
perflvl
[
pm
->
nr_perflvl
];
if
(
perf
[
0
]
==
0xff
)
continue
;
switch
(
ver
)
{
case
0x12
:
case
0x13
:
case
0x15
:
perflvl
->
fanspeed
=
perf
[
55
];
if
(
hdr
>
56
)
perflvl
->
volt_min
=
perf
[
56
];
perflvl
->
core
=
ROM32
(
perf
[
1
])
*
10
;
perflvl
->
memory
=
ROM32
(
perf
[
5
])
*
20
;
break
;
case
0x21
:
case
0x23
:
case
0x24
:
perflvl
->
fanspeed
=
perf
[
4
];
perflvl
->
volt_min
=
perf
[
5
];
perflvl
->
shader
=
ROM16
(
perf
[
6
])
*
1000
;
perflvl
->
core
=
perflvl
->
shader
;
perflvl
->
core
+=
(
signed
char
)
perf
[
8
]
*
1000
;
if
(
nv_device
(
drm
->
device
)
->
chipset
==
0x49
||
nv_device
(
drm
->
device
)
->
chipset
==
0x4b
)
perflvl
->
memory
=
ROM16
(
perf
[
11
])
*
1000
;
else
perflvl
->
memory
=
ROM16
(
perf
[
11
])
*
2000
;
break
;
case
0x25
:
perflvl
->
fanspeed
=
perf
[
4
];
perflvl
->
volt_min
=
perf
[
5
];
perflvl
->
core
=
ROM16
(
perf
[
6
])
*
1000
;
perflvl
->
shader
=
ROM16
(
perf
[
10
])
*
1000
;
perflvl
->
memory
=
ROM16
(
perf
[
12
])
*
1000
;
break
;
case
0x30
:
perflvl
->
memscript
=
ROM16
(
perf
[
2
]);
case
0x35
:
perflvl
->
fanspeed
=
perf
[
6
];
perflvl
->
volt_min
=
perf
[
7
];
perflvl
->
core
=
ROM16
(
perf
[
8
])
*
1000
;
perflvl
->
shader
=
ROM16
(
perf
[
10
])
*
1000
;
perflvl
->
memory
=
ROM16
(
perf
[
12
])
*
1000
;
perflvl
->
vdec
=
ROM16
(
perf
[
16
])
*
1000
;
perflvl
->
dom6
=
ROM16
(
perf
[
20
])
*
1000
;
break
;
case
0x40
:
#define subent(n) ((ROM16(perf[hdr + (n) * len]) & 0xfff) * 1000)
perflvl
->
fanspeed
=
0
;
/*XXX*/
perflvl
->
volt_min
=
perf
[
2
];
if
(
nv_device
(
drm
->
device
)
->
card_type
==
NV_50
)
{
perflvl
->
core
=
subent
(
0
);
perflvl
->
shader
=
subent
(
1
);
perflvl
->
memory
=
subent
(
2
);
perflvl
->
vdec
=
subent
(
3
);
perflvl
->
unka0
=
subent
(
4
);
}
else
{
perflvl
->
hub06
=
subent
(
0
);
perflvl
->
hub01
=
subent
(
1
);
perflvl
->
copy
=
subent
(
2
);
perflvl
->
shader
=
subent
(
3
);
perflvl
->
rop
=
subent
(
4
);
perflvl
->
memory
=
subent
(
5
);
perflvl
->
vdec
=
subent
(
6
);
perflvl
->
daemon
=
subent
(
10
);
perflvl
->
hub07
=
subent
(
11
);
perflvl
->
core
=
perflvl
->
shader
/
2
;
}
break
;
}
/* make sure vid is valid */
nouveau_perf_voltage
(
dev
,
perflvl
);
if
(
pm
->
voltage
.
supported
&&
perflvl
->
volt_min
)
{
vid
=
nouveau_volt_vid_lookup
(
dev
,
perflvl
->
volt_min
);
if
(
vid
<
0
)
{
NV_DEBUG
(
drm
,
"perflvl %d, bad vid
\n
"
,
i
);
continue
;
}
}
/* get the corresponding memory timings */
ret
=
nouveau_mem_timing_calc
(
dev
,
perflvl
->
memory
,
&
perflvl
->
timing
);
if
(
ret
)
{
NV_DEBUG
(
drm
,
"perflvl %d, bad timing: %d
\n
"
,
i
,
ret
);
continue
;
}
snprintf
(
perflvl
->
name
,
sizeof
(
perflvl
->
name
),
"performance_level_%d"
,
i
);
perflvl
->
id
=
i
;
snprintf
(
perflvl
->
profile
.
name
,
sizeof
(
perflvl
->
profile
.
name
),
"%d"
,
perflvl
->
id
);
perflvl
->
profile
.
func
=
&
nouveau_pm_static_profile_func
;
list_add_tail
(
&
perflvl
->
profile
.
head
,
&
pm
->
profiles
);
pm
->
nr_perflvl
++
;
}
}
void
nouveau_perf_fini
(
struct
drm_device
*
dev
)
{
}
drivers/gpu/drm/nouveau/nouveau_pm.h
deleted
100644 → 0
View file @
c52f4fa6
/*
* Copyright 2010 Red Hat Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* Authors: Ben Skeggs
*/
#ifndef __NOUVEAU_PM_H__
#define __NOUVEAU_PM_H__
#include <subdev/bios/pll.h>
#include <subdev/clock.h>
struct
nouveau_pm_voltage_level
{
u32
voltage
;
/* microvolts */
u8
vid
;
};
struct
nouveau_pm_voltage
{
bool
supported
;
u8
version
;
u8
vid_mask
;
struct
nouveau_pm_voltage_level
*
level
;
int
nr_level
;
};
/* Exclusive upper limits */
#define NV_MEM_CL_DDR2_MAX 8
#define NV_MEM_WR_DDR2_MAX 9
#define NV_MEM_CL_DDR3_MAX 17
#define NV_MEM_WR_DDR3_MAX 17
#define NV_MEM_CL_GDDR3_MAX 16
#define NV_MEM_WR_GDDR3_MAX 18
#define NV_MEM_CL_GDDR5_MAX 21
#define NV_MEM_WR_GDDR5_MAX 20
struct
nouveau_pm_memtiming
{
int
id
;
u32
reg
[
9
];
u32
mr
[
4
];
u8
tCWL
;
u8
odt
;
u8
drive_strength
;
};
struct
nouveau_pm_tbl_header
{
u8
version
;
u8
header_len
;
u8
entry_cnt
;
u8
entry_len
;
};
struct
nouveau_pm_tbl_entry
{
u8
tWR
;
u8
tWTR
;
u8
tCL
;
u8
tRC
;
u8
empty_4
;
u8
tRFC
;
/* Byte 5 */
u8
empty_6
;
u8
tRAS
;
/* Byte 7 */
u8
empty_8
;
u8
tRP
;
/* Byte 9 */
u8
tRCDRD
;
u8
tRCDWR
;
u8
tRRD
;
u8
tUNK_13
;
u8
RAM_FT1
;
/* 14, a bitmask of random RAM features */
u8
empty_15
;
u8
tUNK_16
;
u8
empty_17
;
u8
tUNK_18
;
u8
tCWL
;
u8
tUNK_20
,
tUNK_21
;
};
struct
nouveau_pm_profile
;
struct
nouveau_pm_profile_func
{
void
(
*
destroy
)(
struct
nouveau_pm_profile
*
);
void
(
*
init
)(
struct
nouveau_pm_profile
*
);
void
(
*
fini
)(
struct
nouveau_pm_profile
*
);
struct
nouveau_pm_level
*
(
*
select
)(
struct
nouveau_pm_profile
*
);
};
struct
nouveau_pm_profile
{
const
struct
nouveau_pm_profile_func
*
func
;
struct
list_head
head
;
char
name
[
8
];
};
#define NOUVEAU_PM_MAX_LEVEL 8
struct
nouveau_pm_level
{
struct
nouveau_pm_profile
profile
;
struct
device_attribute
dev_attr
;
char
name
[
32
];
int
id
;
struct
nouveau_pm_memtiming
timing
;
u32
memory
;
u16
memscript
;
u32
core
;
u32
shader
;
u32
rop
;
u32
copy
;
u32
daemon
;
u32
vdec
;
u32
dom6
;
u32
unka0
;
/* nva3:nvc0 */
u32
hub01
;
/* nvc0- */
u32
hub06
;
/* nvc0- */
u32
hub07
;
/* nvc0- */
u32
volt_min
;
/* microvolts */
u32
volt_max
;
u8
fanspeed
;
};
struct
nouveau_pm_temp_sensor_constants
{
u16
offset_constant
;
s16
offset_mult
;
s16
offset_div
;
s16
slope_mult
;
s16
slope_div
;
};
struct
nouveau_pm_threshold_temp
{
s16
critical
;
s16
down_clock
;
};
struct
nouveau_pm
{
struct
drm_device
*
dev
;
struct
nouveau_pm_voltage
voltage
;
struct
nouveau_pm_level
perflvl
[
NOUVEAU_PM_MAX_LEVEL
];
int
nr_perflvl
;
struct
nouveau_pm_temp_sensor_constants
sensor_constants
;
struct
nouveau_pm_threshold_temp
threshold_temp
;
struct
nouveau_pm_profile
*
profile_ac
;
struct
nouveau_pm_profile
*
profile_dc
;
struct
nouveau_pm_profile
*
profile
;
struct
list_head
profiles
;
struct
nouveau_pm_level
boot
;
struct
nouveau_pm_level
*
cur
;
struct
device
*
hwmon
;
struct
notifier_block
acpi_nb
;
int
(
*
clocks_get
)(
struct
drm_device
*
,
struct
nouveau_pm_level
*
);
void
*
(
*
clocks_pre
)(
struct
drm_device
*
,
struct
nouveau_pm_level
*
);
int
(
*
clocks_set
)(
struct
drm_device
*
,
void
*
);
int
(
*
voltage_get
)(
struct
drm_device
*
);
int
(
*
voltage_set
)(
struct
drm_device
*
,
int
voltage
);
};
static
inline
struct
nouveau_pm
*
nouveau_pm
(
struct
drm_device
*
dev
)
{
return
nouveau_drm
(
dev
)
->
pm
;
}
struct
nouveau_mem_exec_func
{
struct
drm_device
*
dev
;
void
(
*
precharge
)(
struct
nouveau_mem_exec_func
*
);
void
(
*
refresh
)(
struct
nouveau_mem_exec_func
*
);
void
(
*
refresh_auto
)(
struct
nouveau_mem_exec_func
*
,
bool
);
void
(
*
refresh_self
)(
struct
nouveau_mem_exec_func
*
,
bool
);
void
(
*
wait
)(
struct
nouveau_mem_exec_func
*
,
u32
nsec
);
u32
(
*
mrg
)(
struct
nouveau_mem_exec_func
*
,
int
mr
);
void
(
*
mrs
)(
struct
nouveau_mem_exec_func
*
,
int
mr
,
u32
data
);
void
(
*
clock_set
)(
struct
nouveau_mem_exec_func
*
);
void
(
*
timing_set
)(
struct
nouveau_mem_exec_func
*
);
void
*
priv
;
};
/* nouveau_mem.c */
int
nouveau_mem_exec
(
struct
nouveau_mem_exec_func
*
,
struct
nouveau_pm_level
*
);
/* nouveau_pm.c */
int
nouveau_pm_init
(
struct
drm_device
*
dev
);
void
nouveau_pm_fini
(
struct
drm_device
*
dev
);
void
nouveau_pm_resume
(
struct
drm_device
*
dev
);
extern
const
struct
nouveau_pm_profile_func
nouveau_pm_static_profile_func
;
void
nouveau_pm_trigger
(
struct
drm_device
*
dev
);
/* nouveau_volt.c */
void
nouveau_volt_init
(
struct
drm_device
*
);
void
nouveau_volt_fini
(
struct
drm_device
*
);
int
nouveau_volt_vid_lookup
(
struct
drm_device
*
,
int
voltage
);
int
nouveau_volt_lvl_lookup
(
struct
drm_device
*
,
int
vid
);
int
nouveau_voltage_gpio_get
(
struct
drm_device
*
);
int
nouveau_voltage_gpio_set
(
struct
drm_device
*
,
int
voltage
);
/* nouveau_perf.c */
void
nouveau_perf_init
(
struct
drm_device
*
);
void
nouveau_perf_fini
(
struct
drm_device
*
);
u8
*
nouveau_perf_rammap
(
struct
drm_device
*
,
u32
freq
,
u8
*
ver
,
u8
*
hdr
,
u8
*
cnt
,
u8
*
len
);
u8
*
nouveau_perf_ramcfg
(
struct
drm_device
*
,
u32
freq
,
u8
*
ver
,
u8
*
len
);
u8
*
nouveau_perf_timing
(
struct
drm_device
*
,
u32
freq
,
u8
*
ver
,
u8
*
len
);
/* nouveau_mem.c */
void
nouveau_mem_timing_init
(
struct
drm_device
*
);
void
nouveau_mem_timing_fini
(
struct
drm_device
*
);
/* nv04_pm.c */
int
nv04_pm_clocks_get
(
struct
drm_device
*
,
struct
nouveau_pm_level
*
);
void
*
nv04_pm_clocks_pre
(
struct
drm_device
*
,
struct
nouveau_pm_level
*
);
int
nv04_pm_clocks_set
(
struct
drm_device
*
,
void
*
);
/* nv40_pm.c */
int
nv40_pm_clocks_get
(
struct
drm_device
*
,
struct
nouveau_pm_level
*
);
void
*
nv40_pm_clocks_pre
(
struct
drm_device
*
,
struct
nouveau_pm_level
*
);
int
nv40_pm_clocks_set
(
struct
drm_device
*
,
void
*
);
int
nv40_pm_pwm_get
(
struct
drm_device
*
,
int
,
u32
*
,
u32
*
);
int
nv40_pm_pwm_set
(
struct
drm_device
*
,
int
,
u32
,
u32
);
/* nv50_pm.c */
int
nv50_pm_clocks_get
(
struct
drm_device
*
,
struct
nouveau_pm_level
*
);
void
*
nv50_pm_clocks_pre
(
struct
drm_device
*
,
struct
nouveau_pm_level
*
);
int
nv50_pm_clocks_set
(
struct
drm_device
*
,
void
*
);
int
nv50_pm_pwm_get
(
struct
drm_device
*
,
int
,
u32
*
,
u32
*
);
int
nv50_pm_pwm_set
(
struct
drm_device
*
,
int
,
u32
,
u32
);
/* nva3_pm.c */
int
nva3_pm_clocks_get
(
struct
drm_device
*
,
struct
nouveau_pm_level
*
);
void
*
nva3_pm_clocks_pre
(
struct
drm_device
*
,
struct
nouveau_pm_level
*
);
int
nva3_pm_clocks_set
(
struct
drm_device
*
,
void
*
);
/* nvc0_pm.c */
int
nvc0_pm_clocks_get
(
struct
drm_device
*
,
struct
nouveau_pm_level
*
);
void
*
nvc0_pm_clocks_pre
(
struct
drm_device
*
,
struct
nouveau_pm_level
*
);
int
nvc0_pm_clocks_set
(
struct
drm_device
*
,
void
*
);
/* nouveau_mem.c */
int
nouveau_mem_timing_calc
(
struct
drm_device
*
,
u32
freq
,
struct
nouveau_pm_memtiming
*
);
void
nouveau_mem_timing_read
(
struct
drm_device
*
,
struct
nouveau_pm_memtiming
*
);
static
inline
int
nva3_calc_pll
(
struct
drm_device
*
dev
,
struct
nvbios_pll
*
pll
,
u32
freq
,
int
*
N
,
int
*
fN
,
int
*
M
,
int
*
P
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
struct
nouveau_clock
*
clk
=
nouveau_clock
(
device
);
struct
nouveau_pll_vals
pv
;
int
ret
;
ret
=
clk
->
pll_calc
(
clk
,
pll
,
freq
,
&
pv
);
*
N
=
pv
.
N1
;
*
M
=
pv
.
M1
;
*
P
=
pv
.
log2P
;
return
ret
;
}
#endif
drivers/gpu/drm/nouveau/nouveau_volt.c
deleted
100644 → 0
View file @
c52f4fa6
/*
* Copyright 2010 Red Hat Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* Authors: Ben Skeggs
*/
#include <drm/drmP.h>
#include "nouveau_drm.h"
#include "nouveau_pm.h"
#include <subdev/bios/gpio.h>
#include <subdev/gpio.h>
static
const
enum
dcb_gpio_func_name
vidtag
[]
=
{
0x04
,
0x05
,
0x06
,
0x1a
,
0x73
};
static
int
nr_vidtag
=
sizeof
(
vidtag
)
/
sizeof
(
vidtag
[
0
]);
int
nouveau_voltage_gpio_get
(
struct
drm_device
*
dev
)
{
struct
nouveau_pm_voltage
*
volt
=
&
nouveau_pm
(
dev
)
->
voltage
;
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
struct
nouveau_gpio
*
gpio
=
nouveau_gpio
(
device
);
u8
vid
=
0
;
int
i
;
for
(
i
=
0
;
i
<
nr_vidtag
;
i
++
)
{
if
(
!
(
volt
->
vid_mask
&
(
1
<<
i
)))
continue
;
vid
|=
gpio
->
get
(
gpio
,
0
,
vidtag
[
i
],
0xff
)
<<
i
;
}
return
nouveau_volt_lvl_lookup
(
dev
,
vid
);
}
int
nouveau_voltage_gpio_set
(
struct
drm_device
*
dev
,
int
voltage
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
struct
nouveau_gpio
*
gpio
=
nouveau_gpio
(
device
);
struct
nouveau_pm_voltage
*
volt
=
&
nouveau_pm
(
dev
)
->
voltage
;
int
vid
,
i
;
vid
=
nouveau_volt_vid_lookup
(
dev
,
voltage
);
if
(
vid
<
0
)
return
vid
;
for
(
i
=
0
;
i
<
nr_vidtag
;
i
++
)
{
if
(
!
(
volt
->
vid_mask
&
(
1
<<
i
)))
continue
;
gpio
->
set
(
gpio
,
0
,
vidtag
[
i
],
0xff
,
!!
(
vid
&
(
1
<<
i
)));
}
return
0
;
}
int
nouveau_volt_vid_lookup
(
struct
drm_device
*
dev
,
int
voltage
)
{
struct
nouveau_pm_voltage
*
volt
=
&
nouveau_pm
(
dev
)
->
voltage
;
int
i
;
for
(
i
=
0
;
i
<
volt
->
nr_level
;
i
++
)
{
if
(
volt
->
level
[
i
].
voltage
==
voltage
)
return
volt
->
level
[
i
].
vid
;
}
return
-
ENOENT
;
}
int
nouveau_volt_lvl_lookup
(
struct
drm_device
*
dev
,
int
vid
)
{
struct
nouveau_pm_voltage
*
volt
=
&
nouveau_pm
(
dev
)
->
voltage
;
int
i
;
for
(
i
=
0
;
i
<
volt
->
nr_level
;
i
++
)
{
if
(
volt
->
level
[
i
].
vid
==
vid
)
return
volt
->
level
[
i
].
voltage
;
}
return
-
ENOENT
;
}
void
nouveau_volt_init
(
struct
drm_device
*
dev
)
{
struct
nouveau_drm
*
drm
=
nouveau_drm
(
dev
);
struct
nouveau_gpio
*
gpio
=
nouveau_gpio
(
drm
->
device
);
struct
nouveau_pm
*
pm
=
nouveau_pm
(
dev
);
struct
nouveau_pm_voltage
*
voltage
=
&
pm
->
voltage
;
struct
nvbios
*
bios
=
&
drm
->
vbios
;
struct
dcb_gpio_func
func
;
struct
bit_entry
P
;
u8
*
volt
=
NULL
,
*
entry
;
int
i
,
headerlen
,
recordlen
,
entries
,
vidmask
,
vidshift
;
if
(
bios
->
type
==
NVBIOS_BIT
)
{
if
(
bit_table
(
dev
,
'P'
,
&
P
))
return
;
if
(
P
.
version
==
1
)
volt
=
ROMPTR
(
dev
,
P
.
data
[
16
]);
else
if
(
P
.
version
==
2
)
volt
=
ROMPTR
(
dev
,
P
.
data
[
12
]);
else
{
NV_WARN
(
drm
,
"unknown volt for BIT P %d
\n
"
,
P
.
version
);
}
}
else
{
if
(
bios
->
data
[
bios
->
offset
+
6
]
<
0x27
)
{
NV_DEBUG
(
drm
,
"BMP version too old for voltage
\n
"
);
return
;
}
volt
=
ROMPTR
(
dev
,
bios
->
data
[
bios
->
offset
+
0x98
]);
}
if
(
!
volt
)
{
NV_DEBUG
(
drm
,
"voltage table pointer invalid
\n
"
);
return
;
}
switch
(
volt
[
0
])
{
case
0x10
:
case
0x11
:
case
0x12
:
headerlen
=
5
;
recordlen
=
volt
[
1
];
entries
=
volt
[
2
];
vidshift
=
0
;
vidmask
=
volt
[
4
];
break
;
case
0x20
:
headerlen
=
volt
[
1
];
recordlen
=
volt
[
3
];
entries
=
volt
[
2
];
vidshift
=
0
;
/* could be vidshift like 0x30? */
vidmask
=
volt
[
5
];
break
;
case
0x30
:
headerlen
=
volt
[
1
];
recordlen
=
volt
[
2
];
entries
=
volt
[
3
];
vidmask
=
volt
[
4
];
/* no longer certain what volt[5] is, if it's related to
* the vid shift then it's definitely not a function of
* how many bits are set.
*
* after looking at a number of nva3+ vbios images, they
* all seem likely to have a static shift of 2.. lets
* go with that for now until proven otherwise.
*/
vidshift
=
2
;
break
;
case
0x40
:
headerlen
=
volt
[
1
];
recordlen
=
volt
[
2
];
entries
=
volt
[
3
];
/* not a clue what the entries are for.. */
vidmask
=
volt
[
11
];
/* guess.. */
vidshift
=
0
;
break
;
default:
NV_WARN
(
drm
,
"voltage table 0x%02x unknown
\n
"
,
volt
[
0
]);
return
;
}
/* validate vid mask */
voltage
->
vid_mask
=
vidmask
;
if
(
!
voltage
->
vid_mask
)
return
;
i
=
0
;
while
(
vidmask
)
{
if
(
i
>
nr_vidtag
)
{
NV_DEBUG
(
drm
,
"vid bit %d unknown
\n
"
,
i
);
return
;
}
if
(
gpio
&&
gpio
->
find
(
gpio
,
0
,
vidtag
[
i
],
0xff
,
&
func
))
{
NV_DEBUG
(
drm
,
"vid bit %d has no gpio tag
\n
"
,
i
);
return
;
}
vidmask
>>=
1
;
i
++
;
}
/* parse vbios entries into common format */
voltage
->
version
=
volt
[
0
];
if
(
voltage
->
version
<
0x40
)
{
voltage
->
nr_level
=
entries
;
voltage
->
level
=
kcalloc
(
entries
,
sizeof
(
*
voltage
->
level
),
GFP_KERNEL
);
if
(
!
voltage
->
level
)
return
;
entry
=
volt
+
headerlen
;
for
(
i
=
0
;
i
<
entries
;
i
++
,
entry
+=
recordlen
)
{
voltage
->
level
[
i
].
voltage
=
entry
[
0
]
*
10000
;
voltage
->
level
[
i
].
vid
=
entry
[
1
]
>>
vidshift
;
}
}
else
{
u32
volt_uv
=
ROM32
(
volt
[
4
]);
s16
step_uv
=
ROM16
(
volt
[
8
]);
u8
vid
;
voltage
->
nr_level
=
voltage
->
vid_mask
+
1
;
voltage
->
level
=
kcalloc
(
voltage
->
nr_level
,
sizeof
(
*
voltage
->
level
),
GFP_KERNEL
);
if
(
!
voltage
->
level
)
return
;
for
(
vid
=
0
;
vid
<=
voltage
->
vid_mask
;
vid
++
)
{
voltage
->
level
[
vid
].
voltage
=
volt_uv
;
voltage
->
level
[
vid
].
vid
=
vid
;
volt_uv
+=
step_uv
;
}
}
voltage
->
supported
=
true
;
}
void
nouveau_volt_fini
(
struct
drm_device
*
dev
)
{
struct
nouveau_pm_voltage
*
volt
=
&
nouveau_pm
(
dev
)
->
voltage
;
kfree
(
volt
->
level
);
}
drivers/gpu/drm/nouveau/nv04_pm.c
deleted
100644 → 0
View file @
c52f4fa6
/*
* Copyright 2010 Red Hat Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* Authors: Ben Skeggs
*/
#include <drm/drmP.h>
#include "nouveau_drm.h"
#include "nouveau_reg.h"
#include "dispnv04/hw.h"
#include "nouveau_pm.h"
#include <subdev/bios/pll.h>
#include <subdev/clock.h>
#include <subdev/timer.h>
int
nv04_pm_clocks_get
(
struct
drm_device
*
dev
,
struct
nouveau_pm_level
*
perflvl
)
{
int
ret
;
ret
=
nouveau_hw_get_clock
(
dev
,
PLL_CORE
);
if
(
ret
<
0
)
return
ret
;
perflvl
->
core
=
ret
;
ret
=
nouveau_hw_get_clock
(
dev
,
PLL_MEMORY
);
if
(
ret
<
0
)
return
ret
;
perflvl
->
memory
=
ret
;
return
0
;
}
struct
nv04_pm_clock
{
struct
nvbios_pll
pll
;
struct
nouveau_pll_vals
calc
;
};
struct
nv04_pm_state
{
struct
nv04_pm_clock
core
;
struct
nv04_pm_clock
memory
;
};
static
int
calc_pll
(
struct
drm_device
*
dev
,
u32
id
,
int
khz
,
struct
nv04_pm_clock
*
clk
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
struct
nouveau_bios
*
bios
=
nouveau_bios
(
device
);
struct
nouveau_clock
*
pclk
=
nouveau_clock
(
device
);
int
ret
;
ret
=
nvbios_pll_parse
(
bios
,
id
,
&
clk
->
pll
);
if
(
ret
)
return
ret
;
ret
=
pclk
->
pll_calc
(
pclk
,
&
clk
->
pll
,
khz
,
&
clk
->
calc
);
if
(
!
ret
)
return
-
EINVAL
;
return
0
;
}
void
*
nv04_pm_clocks_pre
(
struct
drm_device
*
dev
,
struct
nouveau_pm_level
*
perflvl
)
{
struct
nv04_pm_state
*
info
;
int
ret
;
info
=
kzalloc
(
sizeof
(
*
info
),
GFP_KERNEL
);
if
(
!
info
)
return
ERR_PTR
(
-
ENOMEM
);
ret
=
calc_pll
(
dev
,
PLL_CORE
,
perflvl
->
core
,
&
info
->
core
);
if
(
ret
)
goto
error
;
if
(
perflvl
->
memory
)
{
ret
=
calc_pll
(
dev
,
PLL_MEMORY
,
perflvl
->
memory
,
&
info
->
memory
);
if
(
ret
)
goto
error
;
}
return
info
;
error:
kfree
(
info
);
return
ERR_PTR
(
ret
);
}
static
void
prog_pll
(
struct
drm_device
*
dev
,
struct
nv04_pm_clock
*
clk
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
struct
nouveau_clock
*
pclk
=
nouveau_clock
(
device
);
u32
reg
=
clk
->
pll
.
reg
;
/* thank the insane nouveau_hw_setpll() interface for this */
if
(
device
->
card_type
>=
NV_40
)
reg
+=
4
;
pclk
->
pll_prog
(
pclk
,
reg
,
&
clk
->
calc
);
}
int
nv04_pm_clocks_set
(
struct
drm_device
*
dev
,
void
*
pre_state
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
struct
nouveau_timer
*
ptimer
=
nouveau_timer
(
device
);
struct
nv04_pm_state
*
state
=
pre_state
;
prog_pll
(
dev
,
&
state
->
core
);
if
(
state
->
memory
.
pll
.
reg
)
{
prog_pll
(
dev
,
&
state
->
memory
);
if
(
device
->
card_type
<
NV_30
)
{
if
(
device
->
card_type
==
NV_20
)
nv_mask
(
device
,
0x1002c4
,
0
,
1
<<
20
);
/* Reset the DLLs */
nv_mask
(
device
,
0x1002c0
,
0
,
1
<<
8
);
}
}
nv_ofuncs
(
ptimer
)
->
init
(
nv_object
(
ptimer
));
kfree
(
state
);
return
0
;
}
drivers/gpu/drm/nouveau/nv40_pm.c
deleted
100644 → 0
View file @
c52f4fa6
/*
* Copyright 2011 Red Hat Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* Authors: Ben Skeggs
*/
#include <drm/drmP.h>
#include "nouveau_drm.h"
#include "nouveau_bios.h"
#include "nouveau_pm.h"
#include "dispnv04/hw.h"
#include <subdev/bios/pll.h>
#include <subdev/clock.h>
#include <subdev/timer.h>
#include <engine/fifo.h>
#define min2(a,b) ((a) < (b) ? (a) : (b))
static
u32
read_pll_1
(
struct
drm_device
*
dev
,
u32
reg
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
u32
ctrl
=
nv_rd32
(
device
,
reg
+
0x00
);
int
P
=
(
ctrl
&
0x00070000
)
>>
16
;
int
N
=
(
ctrl
&
0x0000ff00
)
>>
8
;
int
M
=
(
ctrl
&
0x000000ff
)
>>
0
;
u32
ref
=
27000
,
clk
=
0
;
if
(
ctrl
&
0x80000000
)
clk
=
ref
*
N
/
M
;
return
clk
>>
P
;
}
static
u32
read_pll_2
(
struct
drm_device
*
dev
,
u32
reg
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
u32
ctrl
=
nv_rd32
(
device
,
reg
+
0x00
);
u32
coef
=
nv_rd32
(
device
,
reg
+
0x04
);
int
N2
=
(
coef
&
0xff000000
)
>>
24
;
int
M2
=
(
coef
&
0x00ff0000
)
>>
16
;
int
N1
=
(
coef
&
0x0000ff00
)
>>
8
;
int
M1
=
(
coef
&
0x000000ff
)
>>
0
;
int
P
=
(
ctrl
&
0x00070000
)
>>
16
;
u32
ref
=
27000
,
clk
=
0
;
if
((
ctrl
&
0x80000000
)
&&
M1
)
{
clk
=
ref
*
N1
/
M1
;
if
((
ctrl
&
0x40000100
)
==
0x40000000
)
{
if
(
M2
)
clk
=
clk
*
N2
/
M2
;
else
clk
=
0
;
}
}
return
clk
>>
P
;
}
static
u32
read_clk
(
struct
drm_device
*
dev
,
u32
src
)
{
switch
(
src
)
{
case
3
:
return
read_pll_2
(
dev
,
0x004000
);
case
2
:
return
read_pll_1
(
dev
,
0x004008
);
default:
break
;
}
return
0
;
}
int
nv40_pm_clocks_get
(
struct
drm_device
*
dev
,
struct
nouveau_pm_level
*
perflvl
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
u32
ctrl
=
nv_rd32
(
device
,
0x00c040
);
perflvl
->
core
=
read_clk
(
dev
,
(
ctrl
&
0x00000003
)
>>
0
);
perflvl
->
shader
=
read_clk
(
dev
,
(
ctrl
&
0x00000030
)
>>
4
);
perflvl
->
memory
=
read_pll_2
(
dev
,
0x4020
);
return
0
;
}
struct
nv40_pm_state
{
u32
ctrl
;
u32
npll_ctrl
;
u32
npll_coef
;
u32
spll
;
u32
mpll_ctrl
;
u32
mpll_coef
;
};
static
int
nv40_calc_pll
(
struct
drm_device
*
dev
,
u32
reg
,
struct
nvbios_pll
*
pll
,
u32
clk
,
int
*
N1
,
int
*
M1
,
int
*
N2
,
int
*
M2
,
int
*
log2P
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
struct
nouveau_bios
*
bios
=
nouveau_bios
(
device
);
struct
nouveau_clock
*
pclk
=
nouveau_clock
(
device
);
struct
nouveau_pll_vals
coef
;
int
ret
;
ret
=
nvbios_pll_parse
(
bios
,
reg
,
pll
);
if
(
ret
)
return
ret
;
if
(
clk
<
pll
->
vco1
.
max_freq
)
pll
->
vco2
.
max_freq
=
0
;
ret
=
pclk
->
pll_calc
(
pclk
,
pll
,
clk
,
&
coef
);
if
(
ret
==
0
)
return
-
ERANGE
;
*
N1
=
coef
.
N1
;
*
M1
=
coef
.
M1
;
if
(
N2
&&
M2
)
{
if
(
pll
->
vco2
.
max_freq
)
{
*
N2
=
coef
.
N2
;
*
M2
=
coef
.
M2
;
}
else
{
*
N2
=
1
;
*
M2
=
1
;
}
}
*
log2P
=
coef
.
log2P
;
return
0
;
}
void
*
nv40_pm_clocks_pre
(
struct
drm_device
*
dev
,
struct
nouveau_pm_level
*
perflvl
)
{
struct
nv40_pm_state
*
info
;
struct
nvbios_pll
pll
;
int
N1
,
N2
,
M1
,
M2
,
log2P
;
int
ret
;
info
=
kmalloc
(
sizeof
(
*
info
),
GFP_KERNEL
);
if
(
!
info
)
return
ERR_PTR
(
-
ENOMEM
);
/* core/geometric clock */
ret
=
nv40_calc_pll
(
dev
,
0x004000
,
&
pll
,
perflvl
->
core
,
&
N1
,
&
M1
,
&
N2
,
&
M2
,
&
log2P
);
if
(
ret
<
0
)
goto
out
;
if
(
N2
==
M2
)
{
info
->
npll_ctrl
=
0x80000100
|
(
log2P
<<
16
);
info
->
npll_coef
=
(
N1
<<
8
)
|
M1
;
}
else
{
info
->
npll_ctrl
=
0xc0000000
|
(
log2P
<<
16
);
info
->
npll_coef
=
(
N2
<<
24
)
|
(
M2
<<
16
)
|
(
N1
<<
8
)
|
M1
;
}
/* use the second PLL for shader/rop clock, if it differs from core */
if
(
perflvl
->
shader
&&
perflvl
->
shader
!=
perflvl
->
core
)
{
ret
=
nv40_calc_pll
(
dev
,
0x004008
,
&
pll
,
perflvl
->
shader
,
&
N1
,
&
M1
,
NULL
,
NULL
,
&
log2P
);
if
(
ret
<
0
)
goto
out
;
info
->
spll
=
0xc0000000
|
(
log2P
<<
16
)
|
(
N1
<<
8
)
|
M1
;
info
->
ctrl
=
0x00000223
;
}
else
{
info
->
spll
=
0x00000000
;
info
->
ctrl
=
0x00000333
;
}
/* memory clock */
if
(
!
perflvl
->
memory
)
{
info
->
mpll_ctrl
=
0x00000000
;
goto
out
;
}
ret
=
nv40_calc_pll
(
dev
,
0x004020
,
&
pll
,
perflvl
->
memory
,
&
N1
,
&
M1
,
&
N2
,
&
M2
,
&
log2P
);
if
(
ret
<
0
)
goto
out
;
info
->
mpll_ctrl
=
0x80000000
|
(
log2P
<<
16
);
info
->
mpll_ctrl
|=
min2
(
pll
.
bias_p
+
log2P
,
pll
.
max_p
)
<<
20
;
if
(
N2
==
M2
)
{
info
->
mpll_ctrl
|=
0x00000100
;
info
->
mpll_coef
=
(
N1
<<
8
)
|
M1
;
}
else
{
info
->
mpll_ctrl
|=
0x40000000
;
info
->
mpll_coef
=
(
N2
<<
24
)
|
(
M2
<<
16
)
|
(
N1
<<
8
)
|
M1
;
}
out:
if
(
ret
<
0
)
{
kfree
(
info
);
info
=
ERR_PTR
(
ret
);
}
return
info
;
}
static
bool
nv40_pm_gr_idle
(
void
*
data
)
{
struct
drm_device
*
dev
=
data
;
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
if
((
nv_rd32
(
device
,
0x400760
)
&
0x000000f0
)
>>
4
!=
(
nv_rd32
(
device
,
0x400760
)
&
0x0000000f
))
return
false
;
if
(
nv_rd32
(
device
,
0x400700
))
return
false
;
return
true
;
}
int
nv40_pm_clocks_set
(
struct
drm_device
*
dev
,
void
*
pre_state
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
struct
nouveau_fifo
*
pfifo
=
nouveau_fifo
(
device
);
struct
nouveau_drm
*
drm
=
nouveau_drm
(
dev
);
struct
nv40_pm_state
*
info
=
pre_state
;
unsigned
long
flags
;
struct
bit_entry
M
;
u32
crtc_mask
=
0
;
u8
sr1
[
2
];
int
i
,
ret
=
-
EAGAIN
;
/* determine which CRTCs are active, fetch VGA_SR1 for each */
for
(
i
=
0
;
i
<
2
;
i
++
)
{
u32
vbl
=
nv_rd32
(
device
,
0x600808
+
(
i
*
0x2000
));
u32
cnt
=
0
;
do
{
if
(
vbl
!=
nv_rd32
(
device
,
0x600808
+
(
i
*
0x2000
)))
{
nv_wr08
(
device
,
0x0c03c4
+
(
i
*
0x2000
),
0x01
);
sr1
[
i
]
=
nv_rd08
(
device
,
0x0c03c5
+
(
i
*
0x2000
));
if
(
!
(
sr1
[
i
]
&
0x20
))
crtc_mask
|=
(
1
<<
i
);
break
;
}
udelay
(
1
);
}
while
(
cnt
++
<
32
);
}
/* halt and idle engines */
pfifo
->
pause
(
pfifo
,
&
flags
);
if
(
!
nv_wait_cb
(
device
,
nv40_pm_gr_idle
,
dev
))
goto
resume
;
ret
=
0
;
/* set engine clocks */
nv_mask
(
device
,
0x00c040
,
0x00000333
,
0x00000000
);
nv_wr32
(
device
,
0x004004
,
info
->
npll_coef
);
nv_mask
(
device
,
0x004000
,
0xc0070100
,
info
->
npll_ctrl
);
nv_mask
(
device
,
0x004008
,
0xc007ffff
,
info
->
spll
);
mdelay
(
5
);
nv_mask
(
device
,
0x00c040
,
0x00000333
,
info
->
ctrl
);
if
(
!
info
->
mpll_ctrl
)
goto
resume
;
/* wait for vblank start on active crtcs, disable memory access */
for
(
i
=
0
;
i
<
2
;
i
++
)
{
if
(
!
(
crtc_mask
&
(
1
<<
i
)))
continue
;
nv_wait
(
device
,
0x600808
+
(
i
*
0x2000
),
0x00010000
,
0x00000000
);
nv_wait
(
device
,
0x600808
+
(
i
*
0x2000
),
0x00010000
,
0x00010000
);
nv_wr08
(
device
,
0x0c03c4
+
(
i
*
0x2000
),
0x01
);
nv_wr08
(
device
,
0x0c03c5
+
(
i
*
0x2000
),
sr1
[
i
]
|
0x20
);
}
/* prepare ram for reclocking */
nv_wr32
(
device
,
0x1002d4
,
0x00000001
);
/* precharge */
nv_wr32
(
device
,
0x1002d0
,
0x00000001
);
/* refresh */
nv_wr32
(
device
,
0x1002d0
,
0x00000001
);
/* refresh */
nv_mask
(
device
,
0x100210
,
0x80000000
,
0x00000000
);
/* no auto refresh */
nv_wr32
(
device
,
0x1002dc
,
0x00000001
);
/* enable self-refresh */
/* change the PLL of each memory partition */
nv_mask
(
device
,
0x00c040
,
0x0000c000
,
0x00000000
);
switch
(
nv_device
(
drm
->
device
)
->
chipset
)
{
case
0x40
:
case
0x45
:
case
0x41
:
case
0x42
:
case
0x47
:
nv_mask
(
device
,
0x004044
,
0xc0771100
,
info
->
mpll_ctrl
);
nv_mask
(
device
,
0x00402c
,
0xc0771100
,
info
->
mpll_ctrl
);
nv_wr32
(
device
,
0x004048
,
info
->
mpll_coef
);
nv_wr32
(
device
,
0x004030
,
info
->
mpll_coef
);
case
0x43
:
case
0x49
:
case
0x4b
:
nv_mask
(
device
,
0x004038
,
0xc0771100
,
info
->
mpll_ctrl
);
nv_wr32
(
device
,
0x00403c
,
info
->
mpll_coef
);
default:
nv_mask
(
device
,
0x004020
,
0xc0771100
,
info
->
mpll_ctrl
);
nv_wr32
(
device
,
0x004024
,
info
->
mpll_coef
);
break
;
}
udelay
(
100
);
nv_mask
(
device
,
0x00c040
,
0x0000c000
,
0x0000c000
);
/* re-enable normal operation of memory controller */
nv_wr32
(
device
,
0x1002dc
,
0x00000000
);
nv_mask
(
device
,
0x100210
,
0x80000000
,
0x80000000
);
udelay
(
100
);
/* execute memory reset script from vbios */
if
(
!
bit_table
(
dev
,
'M'
,
&
M
))
nouveau_bios_run_init_table
(
dev
,
ROM16
(
M
.
data
[
0
]),
NULL
,
0
);
/* make sure we're in vblank (hopefully the same one as before), and
* then re-enable crtc memory access
*/
for
(
i
=
0
;
i
<
2
;
i
++
)
{
if
(
!
(
crtc_mask
&
(
1
<<
i
)))
continue
;
nv_wait
(
device
,
0x600808
+
(
i
*
0x2000
),
0x00010000
,
0x00010000
);
nv_wr08
(
device
,
0x0c03c4
+
(
i
*
0x2000
),
0x01
);
nv_wr08
(
device
,
0x0c03c5
+
(
i
*
0x2000
),
sr1
[
i
]);
}
/* resume engines */
resume:
pfifo
->
start
(
pfifo
,
&
flags
);
kfree
(
info
);
return
ret
;
}
drivers/gpu/drm/nouveau/nv50_pm.c
deleted
100644 → 0
View file @
c52f4fa6
/*
* Copyright 2010 Red Hat Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* Authors: Ben Skeggs
*/
#include <drm/drmP.h>
#include "nouveau_drm.h"
#include "nouveau_bios.h"
#include "dispnv04/hw.h"
#include "nouveau_pm.h"
#include "nouveau_hwsq.h"
#include "nv50_display.h"
#include <subdev/bios/pll.h>
#include <subdev/clock.h>
#include <subdev/timer.h>
#include <subdev/fb.h>
enum
clk_src
{
clk_src_crystal
,
clk_src_href
,
clk_src_hclk
,
clk_src_hclkm3
,
clk_src_hclkm3d2
,
clk_src_host
,
clk_src_nvclk
,
clk_src_sclk
,
clk_src_mclk
,
clk_src_vdec
,
clk_src_dom6
};
static
u32
read_clk
(
struct
drm_device
*
,
enum
clk_src
);
static
u32
read_div
(
struct
drm_device
*
dev
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
struct
nouveau_drm
*
drm
=
nouveau_drm
(
dev
);
switch
(
nv_device
(
drm
->
device
)
->
chipset
)
{
case
0x50
:
/* it exists, but only has bit 31, not the dividers.. */
case
0x84
:
case
0x86
:
case
0x98
:
case
0xa0
:
return
nv_rd32
(
device
,
0x004700
);
case
0x92
:
case
0x94
:
case
0x96
:
return
nv_rd32
(
device
,
0x004800
);
default:
return
0x00000000
;
}
}
static
u32
read_pll_src
(
struct
drm_device
*
dev
,
u32
base
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
struct
nouveau_drm
*
drm
=
nouveau_drm
(
dev
);
u32
coef
,
ref
=
read_clk
(
dev
,
clk_src_crystal
);
u32
rsel
=
nv_rd32
(
device
,
0x00e18c
);
int
P
,
N
,
M
,
id
;
switch
(
nv_device
(
drm
->
device
)
->
chipset
)
{
case
0x50
:
case
0xa0
:
switch
(
base
)
{
case
0x4020
:
case
0x4028
:
id
=
!!
(
rsel
&
0x00000004
);
break
;
case
0x4008
:
id
=
!!
(
rsel
&
0x00000008
);
break
;
case
0x4030
:
id
=
0
;
break
;
default:
NV_ERROR
(
drm
,
"ref: bad pll 0x%06x
\n
"
,
base
);
return
0
;
}
coef
=
nv_rd32
(
device
,
0x00e81c
+
(
id
*
0x0c
));
ref
*=
(
coef
&
0x01000000
)
?
2
:
4
;
P
=
(
coef
&
0x00070000
)
>>
16
;
N
=
((
coef
&
0x0000ff00
)
>>
8
)
+
1
;
M
=
((
coef
&
0x000000ff
)
>>
0
)
+
1
;
break
;
case
0x84
:
case
0x86
:
case
0x92
:
coef
=
nv_rd32
(
device
,
0x00e81c
);
P
=
(
coef
&
0x00070000
)
>>
16
;
N
=
(
coef
&
0x0000ff00
)
>>
8
;
M
=
(
coef
&
0x000000ff
)
>>
0
;
break
;
case
0x94
:
case
0x96
:
case
0x98
:
rsel
=
nv_rd32
(
device
,
0x00c050
);
switch
(
base
)
{
case
0x4020
:
rsel
=
(
rsel
&
0x00000003
)
>>
0
;
break
;
case
0x4008
:
rsel
=
(
rsel
&
0x0000000c
)
>>
2
;
break
;
case
0x4028
:
rsel
=
(
rsel
&
0x00001800
)
>>
11
;
break
;
case
0x4030
:
rsel
=
3
;
break
;
default:
NV_ERROR
(
drm
,
"ref: bad pll 0x%06x
\n
"
,
base
);
return
0
;
}
switch
(
rsel
)
{
case
0
:
id
=
1
;
break
;
case
1
:
return
read_clk
(
dev
,
clk_src_crystal
);
case
2
:
return
read_clk
(
dev
,
clk_src_href
);
case
3
:
id
=
0
;
break
;
}
coef
=
nv_rd32
(
device
,
0x00e81c
+
(
id
*
0x28
));
P
=
(
nv_rd32
(
device
,
0x00e824
+
(
id
*
0x28
))
>>
16
)
&
7
;
P
+=
(
coef
&
0x00070000
)
>>
16
;
N
=
(
coef
&
0x0000ff00
)
>>
8
;
M
=
(
coef
&
0x000000ff
)
>>
0
;
break
;
default:
BUG_ON
(
1
);
}
if
(
M
)
return
(
ref
*
N
/
M
)
>>
P
;
return
0
;
}
static
u32
read_pll_ref
(
struct
drm_device
*
dev
,
u32
base
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
struct
nouveau_drm
*
drm
=
nouveau_drm
(
dev
);
u32
src
,
mast
=
nv_rd32
(
device
,
0x00c040
);
switch
(
base
)
{
case
0x004028
:
src
=
!!
(
mast
&
0x00200000
);
break
;
case
0x004020
:
src
=
!!
(
mast
&
0x00400000
);
break
;
case
0x004008
:
src
=
!!
(
mast
&
0x00010000
);
break
;
case
0x004030
:
src
=
!!
(
mast
&
0x02000000
);
break
;
case
0x00e810
:
return
read_clk
(
dev
,
clk_src_crystal
);
default:
NV_ERROR
(
drm
,
"bad pll 0x%06x
\n
"
,
base
);
return
0
;
}
if
(
src
)
return
read_clk
(
dev
,
clk_src_href
);
return
read_pll_src
(
dev
,
base
);
}
static
u32
read_pll
(
struct
drm_device
*
dev
,
u32
base
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
struct
nouveau_drm
*
drm
=
nouveau_drm
(
dev
);
u32
mast
=
nv_rd32
(
device
,
0x00c040
);
u32
ctrl
=
nv_rd32
(
device
,
base
+
0
);
u32
coef
=
nv_rd32
(
device
,
base
+
4
);
u32
ref
=
read_pll_ref
(
dev
,
base
);
u32
clk
=
0
;
int
N1
,
N2
,
M1
,
M2
;
if
(
base
==
0x004028
&&
(
mast
&
0x00100000
))
{
/* wtf, appears to only disable post-divider on nva0 */
if
(
nv_device
(
drm
->
device
)
->
chipset
!=
0xa0
)
return
read_clk
(
dev
,
clk_src_dom6
);
}
N2
=
(
coef
&
0xff000000
)
>>
24
;
M2
=
(
coef
&
0x00ff0000
)
>>
16
;
N1
=
(
coef
&
0x0000ff00
)
>>
8
;
M1
=
(
coef
&
0x000000ff
);
if
((
ctrl
&
0x80000000
)
&&
M1
)
{
clk
=
ref
*
N1
/
M1
;
if
((
ctrl
&
0x40000100
)
==
0x40000000
)
{
if
(
M2
)
clk
=
clk
*
N2
/
M2
;
else
clk
=
0
;
}
}
return
clk
;
}
static
u32
read_clk
(
struct
drm_device
*
dev
,
enum
clk_src
src
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
struct
nouveau_drm
*
drm
=
nouveau_drm
(
dev
);
u32
mast
=
nv_rd32
(
device
,
0x00c040
);
u32
P
=
0
;
switch
(
src
)
{
case
clk_src_crystal
:
return
device
->
crystal
;
case
clk_src_href
:
return
100000
;
/* PCIE reference clock */
case
clk_src_hclk
:
return
read_clk
(
dev
,
clk_src_href
)
*
27778
/
10000
;
case
clk_src_hclkm3
:
return
read_clk
(
dev
,
clk_src_hclk
)
*
3
;
case
clk_src_hclkm3d2
:
return
read_clk
(
dev
,
clk_src_hclk
)
*
3
/
2
;
case
clk_src_host
:
switch
(
mast
&
0x30000000
)
{
case
0x00000000
:
return
read_clk
(
dev
,
clk_src_href
);
case
0x10000000
:
break
;
case
0x20000000
:
/* !0x50 */
case
0x30000000
:
return
read_clk
(
dev
,
clk_src_hclk
);
}
break
;
case
clk_src_nvclk
:
if
(
!
(
mast
&
0x00100000
))
P
=
(
nv_rd32
(
device
,
0x004028
)
&
0x00070000
)
>>
16
;
switch
(
mast
&
0x00000003
)
{
case
0x00000000
:
return
read_clk
(
dev
,
clk_src_crystal
)
>>
P
;
case
0x00000001
:
return
read_clk
(
dev
,
clk_src_dom6
);
case
0x00000002
:
return
read_pll
(
dev
,
0x004020
)
>>
P
;
case
0x00000003
:
return
read_pll
(
dev
,
0x004028
)
>>
P
;
}
break
;
case
clk_src_sclk
:
P
=
(
nv_rd32
(
device
,
0x004020
)
&
0x00070000
)
>>
16
;
switch
(
mast
&
0x00000030
)
{
case
0x00000000
:
if
(
mast
&
0x00000080
)
return
read_clk
(
dev
,
clk_src_host
)
>>
P
;
return
read_clk
(
dev
,
clk_src_crystal
)
>>
P
;
case
0x00000010
:
break
;
case
0x00000020
:
return
read_pll
(
dev
,
0x004028
)
>>
P
;
case
0x00000030
:
return
read_pll
(
dev
,
0x004020
)
>>
P
;
}
break
;
case
clk_src_mclk
:
P
=
(
nv_rd32
(
device
,
0x004008
)
&
0x00070000
)
>>
16
;
if
(
nv_rd32
(
device
,
0x004008
)
&
0x00000200
)
{
switch
(
mast
&
0x0000c000
)
{
case
0x00000000
:
return
read_clk
(
dev
,
clk_src_crystal
)
>>
P
;
case
0x00008000
:
case
0x0000c000
:
return
read_clk
(
dev
,
clk_src_href
)
>>
P
;
}
}
else
{
return
read_pll
(
dev
,
0x004008
)
>>
P
;
}
break
;
case
clk_src_vdec
:
P
=
(
read_div
(
dev
)
&
0x00000700
)
>>
8
;
switch
(
nv_device
(
drm
->
device
)
->
chipset
)
{
case
0x84
:
case
0x86
:
case
0x92
:
case
0x94
:
case
0x96
:
case
0xa0
:
switch
(
mast
&
0x00000c00
)
{
case
0x00000000
:
if
(
nv_device
(
drm
->
device
)
->
chipset
==
0xa0
)
/* wtf?? */
return
read_clk
(
dev
,
clk_src_nvclk
)
>>
P
;
return
read_clk
(
dev
,
clk_src_crystal
)
>>
P
;
case
0x00000400
:
return
0
;
case
0x00000800
:
if
(
mast
&
0x01000000
)
return
read_pll
(
dev
,
0x004028
)
>>
P
;
return
read_pll
(
dev
,
0x004030
)
>>
P
;
case
0x00000c00
:
return
read_clk
(
dev
,
clk_src_nvclk
)
>>
P
;
}
break
;
case
0x98
:
switch
(
mast
&
0x00000c00
)
{
case
0x00000000
:
return
read_clk
(
dev
,
clk_src_nvclk
)
>>
P
;
case
0x00000400
:
return
0
;
case
0x00000800
:
return
read_clk
(
dev
,
clk_src_hclkm3d2
)
>>
P
;
case
0x00000c00
:
return
read_clk
(
dev
,
clk_src_mclk
)
>>
P
;
}
break
;
}
break
;
case
clk_src_dom6
:
switch
(
nv_device
(
drm
->
device
)
->
chipset
)
{
case
0x50
:
case
0xa0
:
return
read_pll
(
dev
,
0x00e810
)
>>
2
;
case
0x84
:
case
0x86
:
case
0x92
:
case
0x94
:
case
0x96
:
case
0x98
:
P
=
(
read_div
(
dev
)
&
0x00000007
)
>>
0
;
switch
(
mast
&
0x0c000000
)
{
case
0x00000000
:
return
read_clk
(
dev
,
clk_src_href
);
case
0x04000000
:
break
;
case
0x08000000
:
return
read_clk
(
dev
,
clk_src_hclk
);
case
0x0c000000
:
return
read_clk
(
dev
,
clk_src_hclkm3
)
>>
P
;
}
break
;
default:
break
;
}
default:
break
;
}
NV_DEBUG
(
drm
,
"unknown clock source %d 0x%08x
\n
"
,
src
,
mast
);
return
0
;
}
int
nv50_pm_clocks_get
(
struct
drm_device
*
dev
,
struct
nouveau_pm_level
*
perflvl
)
{
struct
nouveau_drm
*
drm
=
nouveau_drm
(
dev
);
if
(
nv_device
(
drm
->
device
)
->
chipset
==
0xaa
||
nv_device
(
drm
->
device
)
->
chipset
==
0xac
)
return
0
;
perflvl
->
core
=
read_clk
(
dev
,
clk_src_nvclk
);
perflvl
->
shader
=
read_clk
(
dev
,
clk_src_sclk
);
perflvl
->
memory
=
read_clk
(
dev
,
clk_src_mclk
);
if
(
nv_device
(
drm
->
device
)
->
chipset
!=
0x50
)
{
perflvl
->
vdec
=
read_clk
(
dev
,
clk_src_vdec
);
perflvl
->
dom6
=
read_clk
(
dev
,
clk_src_dom6
);
}
return
0
;
}
struct
nv50_pm_state
{
struct
nouveau_pm_level
*
perflvl
;
struct
hwsq_ucode
eclk_hwsq
;
struct
hwsq_ucode
mclk_hwsq
;
u32
mscript
;
u32
mmast
;
u32
mctrl
;
u32
mcoef
;
};
static
u32
calc_pll
(
struct
drm_device
*
dev
,
u32
reg
,
struct
nvbios_pll
*
pll
,
u32
clk
,
int
*
N1
,
int
*
M1
,
int
*
log2P
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
struct
nouveau_bios
*
bios
=
nouveau_bios
(
device
);
struct
nouveau_clock
*
pclk
=
nouveau_clock
(
device
);
struct
nouveau_pll_vals
coef
;
int
ret
;
ret
=
nvbios_pll_parse
(
bios
,
reg
,
pll
);
if
(
ret
)
return
0
;
pll
->
vco2
.
max_freq
=
0
;
pll
->
refclk
=
read_pll_ref
(
dev
,
reg
);
if
(
!
pll
->
refclk
)
return
0
;
ret
=
pclk
->
pll_calc
(
pclk
,
pll
,
clk
,
&
coef
);
if
(
ret
==
0
)
return
0
;
*
N1
=
coef
.
N1
;
*
M1
=
coef
.
M1
;
*
log2P
=
coef
.
log2P
;
return
ret
;
}
static
inline
u32
calc_div
(
u32
src
,
u32
target
,
int
*
div
)
{
u32
clk0
=
src
,
clk1
=
src
;
for
(
*
div
=
0
;
*
div
<=
7
;
(
*
div
)
++
)
{
if
(
clk0
<=
target
)
{
clk1
=
clk0
<<
(
*
div
?
1
:
0
);
break
;
}
clk0
>>=
1
;
}
if
(
target
-
clk0
<=
clk1
-
target
)
return
clk0
;
(
*
div
)
--
;
return
clk1
;
}
static
inline
u32
clk_same
(
u32
a
,
u32
b
)
{
return
((
a
/
1000
)
==
(
b
/
1000
));
}
static
void
mclk_precharge
(
struct
nouveau_mem_exec_func
*
exec
)
{
struct
nv50_pm_state
*
info
=
exec
->
priv
;
struct
hwsq_ucode
*
hwsq
=
&
info
->
mclk_hwsq
;
hwsq_wr32
(
hwsq
,
0x1002d4
,
0x00000001
);
}
static
void
mclk_refresh
(
struct
nouveau_mem_exec_func
*
exec
)
{
struct
nv50_pm_state
*
info
=
exec
->
priv
;
struct
hwsq_ucode
*
hwsq
=
&
info
->
mclk_hwsq
;
hwsq_wr32
(
hwsq
,
0x1002d0
,
0x00000001
);
}
static
void
mclk_refresh_auto
(
struct
nouveau_mem_exec_func
*
exec
,
bool
enable
)
{
struct
nv50_pm_state
*
info
=
exec
->
priv
;
struct
hwsq_ucode
*
hwsq
=
&
info
->
mclk_hwsq
;
hwsq_wr32
(
hwsq
,
0x100210
,
enable
?
0x80000000
:
0x00000000
);
}
static
void
mclk_refresh_self
(
struct
nouveau_mem_exec_func
*
exec
,
bool
enable
)
{
struct
nv50_pm_state
*
info
=
exec
->
priv
;
struct
hwsq_ucode
*
hwsq
=
&
info
->
mclk_hwsq
;
hwsq_wr32
(
hwsq
,
0x1002dc
,
enable
?
0x00000001
:
0x00000000
);
}
static
void
mclk_wait
(
struct
nouveau_mem_exec_func
*
exec
,
u32
nsec
)
{
struct
nv50_pm_state
*
info
=
exec
->
priv
;
struct
hwsq_ucode
*
hwsq
=
&
info
->
mclk_hwsq
;
if
(
nsec
>
1000
)
hwsq_usec
(
hwsq
,
(
nsec
+
500
)
/
1000
);
}
static
u32
mclk_mrg
(
struct
nouveau_mem_exec_func
*
exec
,
int
mr
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
exec
->
dev
);
if
(
mr
<=
1
)
return
nv_rd32
(
device
,
0x1002c0
+
((
mr
-
0
)
*
4
));
if
(
mr
<=
3
)
return
nv_rd32
(
device
,
0x1002e0
+
((
mr
-
2
)
*
4
));
return
0
;
}
static
void
mclk_mrs
(
struct
nouveau_mem_exec_func
*
exec
,
int
mr
,
u32
data
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
exec
->
dev
);
struct
nouveau_fb
*
pfb
=
nouveau_fb
(
device
);
struct
nv50_pm_state
*
info
=
exec
->
priv
;
struct
hwsq_ucode
*
hwsq
=
&
info
->
mclk_hwsq
;
if
(
mr
<=
1
)
{
if
(
pfb
->
ram
->
ranks
>
1
)
hwsq_wr32
(
hwsq
,
0x1002c8
+
((
mr
-
0
)
*
4
),
data
);
hwsq_wr32
(
hwsq
,
0x1002c0
+
((
mr
-
0
)
*
4
),
data
);
}
else
if
(
mr
<=
3
)
{
if
(
pfb
->
ram
->
ranks
>
1
)
hwsq_wr32
(
hwsq
,
0x1002e8
+
((
mr
-
2
)
*
4
),
data
);
hwsq_wr32
(
hwsq
,
0x1002e0
+
((
mr
-
2
)
*
4
),
data
);
}
}
static
void
mclk_clock_set
(
struct
nouveau_mem_exec_func
*
exec
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
exec
->
dev
);
struct
nv50_pm_state
*
info
=
exec
->
priv
;
struct
hwsq_ucode
*
hwsq
=
&
info
->
mclk_hwsq
;
u32
ctrl
=
nv_rd32
(
device
,
0x004008
);
info
->
mmast
=
nv_rd32
(
device
,
0x00c040
);
info
->
mmast
&=
~
0xc0000000
;
/* get MCLK_2 from HREF */
info
->
mmast
|=
0x0000c000
;
/* use MCLK_2 as MPLL_BYPASS clock */
hwsq_wr32
(
hwsq
,
0xc040
,
info
->
mmast
);
hwsq_wr32
(
hwsq
,
0x4008
,
ctrl
|
0x00000200
);
/* bypass MPLL */
if
(
info
->
mctrl
&
0x80000000
)
hwsq_wr32
(
hwsq
,
0x400c
,
info
->
mcoef
);
hwsq_wr32
(
hwsq
,
0x4008
,
info
->
mctrl
);
}
static
void
mclk_timing_set
(
struct
nouveau_mem_exec_func
*
exec
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
exec
->
dev
);
struct
nv50_pm_state
*
info
=
exec
->
priv
;
struct
nouveau_pm_level
*
perflvl
=
info
->
perflvl
;
struct
hwsq_ucode
*
hwsq
=
&
info
->
mclk_hwsq
;
int
i
;
for
(
i
=
0
;
i
<
9
;
i
++
)
{
u32
reg
=
0x100220
+
(
i
*
4
);
u32
val
=
nv_rd32
(
device
,
reg
);
if
(
val
!=
perflvl
->
timing
.
reg
[
i
])
hwsq_wr32
(
hwsq
,
reg
,
perflvl
->
timing
.
reg
[
i
]);
}
}
static
int
calc_mclk
(
struct
drm_device
*
dev
,
struct
nouveau_pm_level
*
perflvl
,
struct
nv50_pm_state
*
info
)
{
struct
nouveau_drm
*
drm
=
nouveau_drm
(
dev
);
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
u32
crtc_mask
=
0
;
/*XXX: nv50_display_active_crtcs(dev); */
struct
nouveau_mem_exec_func
exec
=
{
.
dev
=
dev
,
.
precharge
=
mclk_precharge
,
.
refresh
=
mclk_refresh
,
.
refresh_auto
=
mclk_refresh_auto
,
.
refresh_self
=
mclk_refresh_self
,
.
wait
=
mclk_wait
,
.
mrg
=
mclk_mrg
,
.
mrs
=
mclk_mrs
,
.
clock_set
=
mclk_clock_set
,
.
timing_set
=
mclk_timing_set
,
.
priv
=
info
};
struct
hwsq_ucode
*
hwsq
=
&
info
->
mclk_hwsq
;
struct
nvbios_pll
pll
;
int
N
,
M
,
P
;
int
ret
;
/* use pcie refclock if possible, otherwise use mpll */
info
->
mctrl
=
nv_rd32
(
device
,
0x004008
);
info
->
mctrl
&=
~
0x81ff0200
;
if
(
clk_same
(
perflvl
->
memory
,
read_clk
(
dev
,
clk_src_href
)))
{
info
->
mctrl
|=
0x00000200
|
(
pll
.
bias_p
<<
19
);
}
else
{
ret
=
calc_pll
(
dev
,
0x4008
,
&
pll
,
perflvl
->
memory
,
&
N
,
&
M
,
&
P
);
if
(
ret
==
0
)
return
-
EINVAL
;
info
->
mctrl
|=
0x80000000
|
(
P
<<
22
)
|
(
P
<<
16
);
info
->
mctrl
|=
pll
.
bias_p
<<
19
;
info
->
mcoef
=
(
N
<<
8
)
|
M
;
}
/* build the ucode which will reclock the memory for us */
hwsq_init
(
hwsq
);
if
(
crtc_mask
)
{
hwsq_op5f
(
hwsq
,
crtc_mask
,
0x00
);
/* wait for scanout */
hwsq_op5f
(
hwsq
,
crtc_mask
,
0x01
);
/* wait for vblank */
}
if
(
nv_device
(
drm
->
device
)
->
chipset
>=
0x92
)
hwsq_wr32
(
hwsq
,
0x611200
,
0x00003300
);
/* disable scanout */
hwsq_setf
(
hwsq
,
0x10
,
0
);
/* disable bus access */
hwsq_op5f
(
hwsq
,
0x00
,
0x01
);
/* no idea :s */
ret
=
nouveau_mem_exec
(
&
exec
,
perflvl
);
if
(
ret
)
return
ret
;
hwsq_setf
(
hwsq
,
0x10
,
1
);
/* enable bus access */
hwsq_op5f
(
hwsq
,
0x00
,
0x00
);
/* no idea, reverse of 0x00, 0x01? */
if
(
nv_device
(
drm
->
device
)
->
chipset
>=
0x92
)
hwsq_wr32
(
hwsq
,
0x611200
,
0x00003330
);
/* enable scanout */
hwsq_fini
(
hwsq
);
return
0
;
}
void
*
nv50_pm_clocks_pre
(
struct
drm_device
*
dev
,
struct
nouveau_pm_level
*
perflvl
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
struct
nouveau_drm
*
drm
=
nouveau_drm
(
dev
);
struct
nv50_pm_state
*
info
;
struct
hwsq_ucode
*
hwsq
;
struct
nvbios_pll
pll
;
u32
out
,
mast
,
divs
,
ctrl
;
int
clk
,
ret
=
-
EINVAL
;
int
N
,
M
,
P1
,
P2
;
if
(
nv_device
(
drm
->
device
)
->
chipset
==
0xaa
||
nv_device
(
drm
->
device
)
->
chipset
==
0xac
)
return
ERR_PTR
(
-
ENODEV
);
info
=
kmalloc
(
sizeof
(
*
info
),
GFP_KERNEL
);
if
(
!
info
)
return
ERR_PTR
(
-
ENOMEM
);
info
->
perflvl
=
perflvl
;
/* memory: build hwsq ucode which we'll use to reclock memory.
* use pcie refclock if possible, otherwise use mpll */
info
->
mclk_hwsq
.
len
=
0
;
if
(
perflvl
->
memory
)
{
ret
=
calc_mclk
(
dev
,
perflvl
,
info
);
if
(
ret
)
goto
error
;
info
->
mscript
=
perflvl
->
memscript
;
}
divs
=
read_div
(
dev
);
mast
=
info
->
mmast
;
/* start building HWSQ script for engine reclocking */
hwsq
=
&
info
->
eclk_hwsq
;
hwsq_init
(
hwsq
);
hwsq_setf
(
hwsq
,
0x10
,
0
);
/* disable bus access */
hwsq_op5f
(
hwsq
,
0x00
,
0x01
);
/* wait for access disabled? */
/* vdec/dom6: switch to "safe" clocks temporarily */
if
(
perflvl
->
vdec
)
{
mast
&=
~
0x00000c00
;
divs
&=
~
0x00000700
;
}
if
(
perflvl
->
dom6
)
{
mast
&=
~
0x0c000000
;
divs
&=
~
0x00000007
;
}
hwsq_wr32
(
hwsq
,
0x00c040
,
mast
);
/* vdec: avoid modifying xpll until we know exactly how the other
* clock domains work, i suspect at least some of them can also be
* tied to xpll...
*/
if
(
perflvl
->
vdec
)
{
/* see how close we can get using nvclk as a source */
clk
=
calc_div
(
perflvl
->
core
,
perflvl
->
vdec
,
&
P1
);
/* see how close we can get using xpll/hclk as a source */
if
(
nv_device
(
drm
->
device
)
->
chipset
!=
0x98
)
out
=
read_pll
(
dev
,
0x004030
);
else
out
=
read_clk
(
dev
,
clk_src_hclkm3d2
);
out
=
calc_div
(
out
,
perflvl
->
vdec
,
&
P2
);
/* select whichever gets us closest */
if
(
abs
((
int
)
perflvl
->
vdec
-
clk
)
<=
abs
((
int
)
perflvl
->
vdec
-
out
))
{
if
(
nv_device
(
drm
->
device
)
->
chipset
!=
0x98
)
mast
|=
0x00000c00
;
divs
|=
P1
<<
8
;
}
else
{
mast
|=
0x00000800
;
divs
|=
P2
<<
8
;
}
}
/* dom6: nfi what this is, but we're limited to various combinations
* of the host clock frequency
*/
if
(
perflvl
->
dom6
)
{
if
(
clk_same
(
perflvl
->
dom6
,
read_clk
(
dev
,
clk_src_href
)))
{
mast
|=
0x00000000
;
}
else
if
(
clk_same
(
perflvl
->
dom6
,
read_clk
(
dev
,
clk_src_hclk
)))
{
mast
|=
0x08000000
;
}
else
{
clk
=
read_clk
(
dev
,
clk_src_hclk
)
*
3
;
clk
=
calc_div
(
clk
,
perflvl
->
dom6
,
&
P1
);
mast
|=
0x0c000000
;
divs
|=
P1
;
}
}
/* vdec/dom6: complete switch to new clocks */
switch
(
nv_device
(
drm
->
device
)
->
chipset
)
{
case
0x92
:
case
0x94
:
case
0x96
:
hwsq_wr32
(
hwsq
,
0x004800
,
divs
);
break
;
default:
hwsq_wr32
(
hwsq
,
0x004700
,
divs
);
break
;
}
hwsq_wr32
(
hwsq
,
0x00c040
,
mast
);
/* core/shader: make sure sclk/nvclk are disconnected from their
* PLLs (nvclk to dom6, sclk to hclk)
*/
if
(
nv_device
(
drm
->
device
)
->
chipset
<
0x92
)
mast
=
(
mast
&
~
0x001000b0
)
|
0x00100080
;
else
mast
=
(
mast
&
~
0x000000b3
)
|
0x00000081
;
hwsq_wr32
(
hwsq
,
0x00c040
,
mast
);
/* core: for the moment at least, always use nvpll */
clk
=
calc_pll
(
dev
,
0x4028
,
&
pll
,
perflvl
->
core
,
&
N
,
&
M
,
&
P1
);
if
(
clk
==
0
)
goto
error
;
ctrl
=
nv_rd32
(
device
,
0x004028
)
&
~
0xc03f0100
;
mast
&=
~
0x00100000
;
mast
|=
3
;
hwsq_wr32
(
hwsq
,
0x004028
,
0x80000000
|
(
P1
<<
19
)
|
(
P1
<<
16
)
|
ctrl
);
hwsq_wr32
(
hwsq
,
0x00402c
,
(
N
<<
8
)
|
M
);
/* shader: tie to nvclk if possible, otherwise use spll. have to be
* very careful that the shader clock is at least twice the core, or
* some chipsets will be very unhappy. i expect most or all of these
* cases will be handled by tying to nvclk, but it's possible there's
* corners
*/
ctrl
=
nv_rd32
(
device
,
0x004020
)
&
~
0xc03f0100
;
if
(
P1
--
&&
perflvl
->
shader
==
(
perflvl
->
core
<<
1
))
{
hwsq_wr32
(
hwsq
,
0x004020
,
(
P1
<<
19
)
|
(
P1
<<
16
)
|
ctrl
);
hwsq_wr32
(
hwsq
,
0x00c040
,
0x00000020
|
mast
);
}
else
{
clk
=
calc_pll
(
dev
,
0x4020
,
&
pll
,
perflvl
->
shader
,
&
N
,
&
M
,
&
P1
);
if
(
clk
==
0
)
goto
error
;
ctrl
|=
0x80000000
;
hwsq_wr32
(
hwsq
,
0x004020
,
(
P1
<<
19
)
|
(
P1
<<
16
)
|
ctrl
);
hwsq_wr32
(
hwsq
,
0x004024
,
(
N
<<
8
)
|
M
);
hwsq_wr32
(
hwsq
,
0x00c040
,
0x00000030
|
mast
);
}
hwsq_setf
(
hwsq
,
0x10
,
1
);
/* enable bus access */
hwsq_op5f
(
hwsq
,
0x00
,
0x00
);
/* wait for access enabled? */
hwsq_fini
(
hwsq
);
return
info
;
error:
kfree
(
info
);
return
ERR_PTR
(
ret
);
}
static
int
prog_hwsq
(
struct
drm_device
*
dev
,
struct
hwsq_ucode
*
hwsq
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
struct
nouveau_drm
*
drm
=
nouveau_drm
(
dev
);
u32
hwsq_data
,
hwsq_kick
;
int
i
;
if
(
nv_device
(
drm
->
device
)
->
chipset
<
0x94
)
{
hwsq_data
=
0x001400
;
hwsq_kick
=
0x00000003
;
}
else
{
hwsq_data
=
0x080000
;
hwsq_kick
=
0x00000001
;
}
/* upload hwsq ucode */
nv_mask
(
device
,
0x001098
,
0x00000008
,
0x00000000
);
nv_wr32
(
device
,
0x001304
,
0x00000000
);
if
(
nv_device
(
drm
->
device
)
->
chipset
>=
0x92
)
nv_wr32
(
device
,
0x001318
,
0x00000000
);
for
(
i
=
0
;
i
<
hwsq
->
len
/
4
;
i
++
)
nv_wr32
(
device
,
hwsq_data
+
(
i
*
4
),
hwsq
->
ptr
.
u32
[
i
]);
nv_mask
(
device
,
0x001098
,
0x00000018
,
0x00000018
);
/* launch, and wait for completion */
nv_wr32
(
device
,
0x00130c
,
hwsq_kick
);
if
(
!
nv_wait
(
device
,
0x001308
,
0x00000100
,
0x00000000
))
{
NV_ERROR
(
drm
,
"hwsq ucode exec timed out
\n
"
);
NV_ERROR
(
drm
,
"0x001308: 0x%08x
\n
"
,
nv_rd32
(
device
,
0x001308
));
for
(
i
=
0
;
i
<
hwsq
->
len
/
4
;
i
++
)
{
NV_ERROR
(
drm
,
"0x%06x: 0x%08x
\n
"
,
0x1400
+
(
i
*
4
),
nv_rd32
(
device
,
0x001400
+
(
i
*
4
)));
}
return
-
EIO
;
}
return
0
;
}
int
nv50_pm_clocks_set
(
struct
drm_device
*
dev
,
void
*
data
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
struct
nv50_pm_state
*
info
=
data
;
struct
bit_entry
M
;
int
ret
=
-
EBUSY
;
/* halt and idle execution engines */
nv_mask
(
device
,
0x002504
,
0x00000001
,
0x00000001
);
if
(
!
nv_wait
(
device
,
0x002504
,
0x00000010
,
0x00000010
))
goto
resume
;
if
(
!
nv_wait
(
device
,
0x00251c
,
0x0000003f
,
0x0000003f
))
goto
resume
;
/* program memory clock, if necessary - must come before engine clock
* reprogramming due to how we construct the hwsq scripts in pre()
*/
#define nouveau_bios_init_exec(a,b) nouveau_bios_run_init_table((a), (b), NULL, 0)
if
(
info
->
mclk_hwsq
.
len
)
{
/* execute some scripts that do ??? from the vbios.. */
if
(
!
bit_table
(
dev
,
'M'
,
&
M
)
&&
M
.
version
==
1
)
{
if
(
M
.
length
>=
6
)
nouveau_bios_init_exec
(
dev
,
ROM16
(
M
.
data
[
5
]));
if
(
M
.
length
>=
8
)
nouveau_bios_init_exec
(
dev
,
ROM16
(
M
.
data
[
7
]));
if
(
M
.
length
>=
10
)
nouveau_bios_init_exec
(
dev
,
ROM16
(
M
.
data
[
9
]));
nouveau_bios_init_exec
(
dev
,
info
->
mscript
);
}
ret
=
prog_hwsq
(
dev
,
&
info
->
mclk_hwsq
);
if
(
ret
)
goto
resume
;
}
/* program engine clocks */
ret
=
prog_hwsq
(
dev
,
&
info
->
eclk_hwsq
);
resume:
nv_mask
(
device
,
0x002504
,
0x00000001
,
0x00000000
);
kfree
(
info
);
return
ret
;
}
drivers/gpu/drm/nouveau/nva3_pm.c
deleted
100644 → 0
View file @
c52f4fa6
/*
* Copyright 2010 Red Hat Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* Authors: Ben Skeggs
*/
#include <drm/drmP.h>
#include "nouveau_drm.h"
#include "nouveau_bios.h"
#include "nouveau_pm.h"
#include <subdev/bios/pll.h>
#include <subdev/bios.h>
#include <subdev/clock.h>
#include <subdev/timer.h>
#include <subdev/fb.h>
static
u32
read_clk
(
struct
drm_device
*
,
int
,
bool
);
static
u32
read_pll
(
struct
drm_device
*
,
int
,
u32
);
static
u32
read_vco
(
struct
drm_device
*
dev
,
int
clk
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
u32
sctl
=
nv_rd32
(
device
,
0x4120
+
(
clk
*
4
));
if
((
sctl
&
0x00000030
)
!=
0x00000030
)
return
read_pll
(
dev
,
0x41
,
0x00e820
);
return
read_pll
(
dev
,
0x42
,
0x00e8a0
);
}
static
u32
read_clk
(
struct
drm_device
*
dev
,
int
clk
,
bool
ignore_en
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
struct
nouveau_drm
*
drm
=
nouveau_drm
(
dev
);
u32
sctl
,
sdiv
,
sclk
;
/* refclk for the 0xe8xx plls is a fixed frequency */
if
(
clk
>=
0x40
)
{
if
(
nv_device
(
drm
->
device
)
->
chipset
==
0xaf
)
{
/* no joke.. seriously.. sigh.. */
return
nv_rd32
(
device
,
0x00471c
)
*
1000
;
}
return
device
->
crystal
;
}
sctl
=
nv_rd32
(
device
,
0x4120
+
(
clk
*
4
));
if
(
!
ignore_en
&&
!
(
sctl
&
0x00000100
))
return
0
;
switch
(
sctl
&
0x00003000
)
{
case
0x00000000
:
return
device
->
crystal
;
case
0x00002000
:
if
(
sctl
&
0x00000040
)
return
108000
;
return
100000
;
case
0x00003000
:
sclk
=
read_vco
(
dev
,
clk
);
sdiv
=
((
sctl
&
0x003f0000
)
>>
16
)
+
2
;
return
(
sclk
*
2
)
/
sdiv
;
default:
return
0
;
}
}
static
u32
read_pll
(
struct
drm_device
*
dev
,
int
clk
,
u32
pll
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
u32
ctrl
=
nv_rd32
(
device
,
pll
+
0
);
u32
sclk
=
0
,
P
=
1
,
N
=
1
,
M
=
1
;
if
(
!
(
ctrl
&
0x00000008
))
{
if
(
ctrl
&
0x00000001
)
{
u32
coef
=
nv_rd32
(
device
,
pll
+
4
);
M
=
(
coef
&
0x000000ff
)
>>
0
;
N
=
(
coef
&
0x0000ff00
)
>>
8
;
P
=
(
coef
&
0x003f0000
)
>>
16
;
/* no post-divider on these.. */
if
((
pll
&
0x00ff00
)
==
0x00e800
)
P
=
1
;
sclk
=
read_clk
(
dev
,
0x00
+
clk
,
false
);
}
}
else
{
sclk
=
read_clk
(
dev
,
0x10
+
clk
,
false
);
}
if
(
M
*
P
)
return
sclk
*
N
/
(
M
*
P
);
return
0
;
}
struct
creg
{
u32
clk
;
u32
pll
;
};
static
int
calc_clk
(
struct
drm_device
*
dev
,
int
clk
,
u32
pll
,
u32
khz
,
struct
creg
*
reg
)
{
struct
nouveau_drm
*
drm
=
nouveau_drm
(
dev
);
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
struct
nouveau_bios
*
bios
=
nouveau_bios
(
device
);
struct
nvbios_pll
limits
;
u32
oclk
,
sclk
,
sdiv
;
int
P
,
N
,
M
,
diff
;
int
ret
;
reg
->
pll
=
0
;
reg
->
clk
=
0
;
if
(
!
khz
)
{
NV_DEBUG
(
drm
,
"no clock for 0x%04x/0x%02x
\n
"
,
pll
,
clk
);
return
0
;
}
switch
(
khz
)
{
case
27000
:
reg
->
clk
=
0x00000100
;
return
khz
;
case
100000
:
reg
->
clk
=
0x00002100
;
return
khz
;
case
108000
:
reg
->
clk
=
0x00002140
;
return
khz
;
default:
sclk
=
read_vco
(
dev
,
clk
);
sdiv
=
min
((
sclk
*
2
)
/
(
khz
-
2999
),
(
u32
)
65
);
/* if the clock has a PLL attached, and we can get a within
* [-2, 3) MHz of a divider, we'll disable the PLL and use
* the divider instead.
*
* divider can go as low as 2, limited here because NVIDIA
* and the VBIOS on my NVA8 seem to prefer using the PLL
* for 810MHz - is there a good reason?
*/
if
(
sdiv
>
4
)
{
oclk
=
(
sclk
*
2
)
/
sdiv
;
diff
=
khz
-
oclk
;
if
(
!
pll
||
(
diff
>=
-
2000
&&
diff
<
3000
))
{
reg
->
clk
=
(((
sdiv
-
2
)
<<
16
)
|
0x00003100
);
return
oclk
;
}
}
if
(
!
pll
)
{
NV_ERROR
(
drm
,
"bad freq %02x: %d %d
\n
"
,
clk
,
khz
,
sclk
);
return
-
ERANGE
;
}
break
;
}
ret
=
nvbios_pll_parse
(
bios
,
pll
,
&
limits
);
if
(
ret
)
return
ret
;
limits
.
refclk
=
read_clk
(
dev
,
clk
-
0x10
,
true
);
if
(
!
limits
.
refclk
)
return
-
EINVAL
;
ret
=
nva3_calc_pll
(
dev
,
&
limits
,
khz
,
&
N
,
NULL
,
&
M
,
&
P
);
if
(
ret
>=
0
)
{
reg
->
clk
=
nv_rd32
(
device
,
0x4120
+
(
clk
*
4
));
reg
->
pll
=
(
P
<<
16
)
|
(
N
<<
8
)
|
M
;
}
return
ret
;
}
static
void
prog_pll
(
struct
drm_device
*
dev
,
int
clk
,
u32
pll
,
struct
creg
*
reg
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
struct
nouveau_drm
*
drm
=
nouveau_drm
(
dev
);
const
u32
src0
=
0x004120
+
(
clk
*
4
);
const
u32
src1
=
0x004160
+
(
clk
*
4
);
const
u32
ctrl
=
pll
+
0
;
const
u32
coef
=
pll
+
4
;
if
(
!
reg
->
clk
&&
!
reg
->
pll
)
{
NV_DEBUG
(
drm
,
"no clock for %02x
\n
"
,
clk
);
return
;
}
if
(
reg
->
pll
)
{
nv_mask
(
device
,
src0
,
0x00000101
,
0x00000101
);
nv_wr32
(
device
,
coef
,
reg
->
pll
);
nv_mask
(
device
,
ctrl
,
0x00000015
,
0x00000015
);
nv_mask
(
device
,
ctrl
,
0x00000010
,
0x00000000
);
nv_wait
(
device
,
ctrl
,
0x00020000
,
0x00020000
);
nv_mask
(
device
,
ctrl
,
0x00000010
,
0x00000010
);
nv_mask
(
device
,
ctrl
,
0x00000008
,
0x00000000
);
nv_mask
(
device
,
src1
,
0x00000100
,
0x00000000
);
nv_mask
(
device
,
src1
,
0x00000001
,
0x00000000
);
}
else
{
nv_mask
(
device
,
src1
,
0x003f3141
,
0x00000101
|
reg
->
clk
);
nv_mask
(
device
,
ctrl
,
0x00000018
,
0x00000018
);
udelay
(
20
);
nv_mask
(
device
,
ctrl
,
0x00000001
,
0x00000000
);
nv_mask
(
device
,
src0
,
0x00000100
,
0x00000000
);
nv_mask
(
device
,
src0
,
0x00000001
,
0x00000000
);
}
}
static
void
prog_clk
(
struct
drm_device
*
dev
,
int
clk
,
struct
creg
*
reg
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
struct
nouveau_drm
*
drm
=
nouveau_drm
(
dev
);
if
(
!
reg
->
clk
)
{
NV_DEBUG
(
drm
,
"no clock for %02x
\n
"
,
clk
);
return
;
}
nv_mask
(
device
,
0x004120
+
(
clk
*
4
),
0x003f3141
,
0x00000101
|
reg
->
clk
);
}
int
nva3_pm_clocks_get
(
struct
drm_device
*
dev
,
struct
nouveau_pm_level
*
perflvl
)
{
perflvl
->
core
=
read_pll
(
dev
,
0x00
,
0x4200
);
perflvl
->
shader
=
read_pll
(
dev
,
0x01
,
0x4220
);
perflvl
->
memory
=
read_pll
(
dev
,
0x02
,
0x4000
);
perflvl
->
unka0
=
read_clk
(
dev
,
0x20
,
false
);
perflvl
->
vdec
=
read_clk
(
dev
,
0x21
,
false
);
perflvl
->
daemon
=
read_clk
(
dev
,
0x25
,
false
);
perflvl
->
copy
=
perflvl
->
core
;
return
0
;
}
struct
nva3_pm_state
{
struct
nouveau_pm_level
*
perflvl
;
struct
creg
nclk
;
struct
creg
sclk
;
struct
creg
vdec
;
struct
creg
unka0
;
struct
creg
mclk
;
u8
*
rammap
;
u8
rammap_ver
;
u8
rammap_len
;
u8
*
ramcfg
;
u8
ramcfg_len
;
u32
r004018
;
u32
r100760
;
};
void
*
nva3_pm_clocks_pre
(
struct
drm_device
*
dev
,
struct
nouveau_pm_level
*
perflvl
)
{
struct
nva3_pm_state
*
info
;
u8
ramcfg_cnt
;
int
ret
;
info
=
kzalloc
(
sizeof
(
*
info
),
GFP_KERNEL
);
if
(
!
info
)
return
ERR_PTR
(
-
ENOMEM
);
ret
=
calc_clk
(
dev
,
0x10
,
0x4200
,
perflvl
->
core
,
&
info
->
nclk
);
if
(
ret
<
0
)
goto
out
;
ret
=
calc_clk
(
dev
,
0x11
,
0x4220
,
perflvl
->
shader
,
&
info
->
sclk
);
if
(
ret
<
0
)
goto
out
;
ret
=
calc_clk
(
dev
,
0x12
,
0x4000
,
perflvl
->
memory
,
&
info
->
mclk
);
if
(
ret
<
0
)
goto
out
;
ret
=
calc_clk
(
dev
,
0x20
,
0x0000
,
perflvl
->
unka0
,
&
info
->
unka0
);
if
(
ret
<
0
)
goto
out
;
ret
=
calc_clk
(
dev
,
0x21
,
0x0000
,
perflvl
->
vdec
,
&
info
->
vdec
);
if
(
ret
<
0
)
goto
out
;
info
->
rammap
=
nouveau_perf_rammap
(
dev
,
perflvl
->
memory
,
&
info
->
rammap_ver
,
&
info
->
rammap_len
,
&
ramcfg_cnt
,
&
info
->
ramcfg_len
);
if
(
info
->
rammap_ver
!=
0x10
||
info
->
rammap_len
<
5
)
info
->
rammap
=
NULL
;
info
->
ramcfg
=
nouveau_perf_ramcfg
(
dev
,
perflvl
->
memory
,
&
info
->
rammap_ver
,
&
info
->
ramcfg_len
);
if
(
info
->
rammap_ver
!=
0x10
)
info
->
ramcfg
=
NULL
;
info
->
perflvl
=
perflvl
;
out:
if
(
ret
<
0
)
{
kfree
(
info
);
info
=
ERR_PTR
(
ret
);
}
return
info
;
}
static
bool
nva3_pm_grcp_idle
(
void
*
data
)
{
struct
drm_device
*
dev
=
data
;
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
if
(
!
(
nv_rd32
(
device
,
0x400304
)
&
0x00000001
))
return
true
;
if
(
nv_rd32
(
device
,
0x400308
)
==
0x0050001c
)
return
true
;
return
false
;
}
static
void
mclk_precharge
(
struct
nouveau_mem_exec_func
*
exec
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
exec
->
dev
);
nv_wr32
(
device
,
0x1002d4
,
0x00000001
);
}
static
void
mclk_refresh
(
struct
nouveau_mem_exec_func
*
exec
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
exec
->
dev
);
nv_wr32
(
device
,
0x1002d0
,
0x00000001
);
}
static
void
mclk_refresh_auto
(
struct
nouveau_mem_exec_func
*
exec
,
bool
enable
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
exec
->
dev
);
nv_wr32
(
device
,
0x100210
,
enable
?
0x80000000
:
0x00000000
);
}
static
void
mclk_refresh_self
(
struct
nouveau_mem_exec_func
*
exec
,
bool
enable
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
exec
->
dev
);
nv_wr32
(
device
,
0x1002dc
,
enable
?
0x00000001
:
0x00000000
);
}
static
void
mclk_wait
(
struct
nouveau_mem_exec_func
*
exec
,
u32
nsec
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
exec
->
dev
);
volatile
u32
post
=
nv_rd32
(
device
,
0
);
(
void
)
post
;
udelay
((
nsec
+
500
)
/
1000
);
}
static
u32
mclk_mrg
(
struct
nouveau_mem_exec_func
*
exec
,
int
mr
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
exec
->
dev
);
if
(
mr
<=
1
)
return
nv_rd32
(
device
,
0x1002c0
+
((
mr
-
0
)
*
4
));
if
(
mr
<=
3
)
return
nv_rd32
(
device
,
0x1002e0
+
((
mr
-
2
)
*
4
));
return
0
;
}
static
void
mclk_mrs
(
struct
nouveau_mem_exec_func
*
exec
,
int
mr
,
u32
data
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
exec
->
dev
);
struct
nouveau_fb
*
pfb
=
nouveau_fb
(
device
);
if
(
mr
<=
1
)
{
if
(
pfb
->
ram
->
ranks
>
1
)
nv_wr32
(
device
,
0x1002c8
+
((
mr
-
0
)
*
4
),
data
);
nv_wr32
(
device
,
0x1002c0
+
((
mr
-
0
)
*
4
),
data
);
}
else
if
(
mr
<=
3
)
{
if
(
pfb
->
ram
->
ranks
>
1
)
nv_wr32
(
device
,
0x1002e8
+
((
mr
-
2
)
*
4
),
data
);
nv_wr32
(
device
,
0x1002e0
+
((
mr
-
2
)
*
4
),
data
);
}
}
static
void
mclk_clock_set
(
struct
nouveau_mem_exec_func
*
exec
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
exec
->
dev
);
struct
nva3_pm_state
*
info
=
exec
->
priv
;
u32
ctrl
;
ctrl
=
nv_rd32
(
device
,
0x004000
);
if
(
!
(
ctrl
&
0x00000008
)
&&
info
->
mclk
.
pll
)
{
nv_wr32
(
device
,
0x004000
,
(
ctrl
|=
0x00000008
));
nv_mask
(
device
,
0x1110e0
,
0x00088000
,
0x00088000
);
nv_wr32
(
device
,
0x004018
,
0x00001000
);
nv_wr32
(
device
,
0x004000
,
(
ctrl
&=
~
0x00000001
));
nv_wr32
(
device
,
0x004004
,
info
->
mclk
.
pll
);
nv_wr32
(
device
,
0x004000
,
(
ctrl
|=
0x00000001
));
udelay
(
64
);
nv_wr32
(
device
,
0x004018
,
0x00005000
|
info
->
r004018
);
udelay
(
20
);
}
else
if
(
!
info
->
mclk
.
pll
)
{
nv_mask
(
device
,
0x004168
,
0x003f3040
,
info
->
mclk
.
clk
);
nv_wr32
(
device
,
0x004000
,
(
ctrl
|=
0x00000008
));
nv_mask
(
device
,
0x1110e0
,
0x00088000
,
0x00088000
);
nv_wr32
(
device
,
0x004018
,
0x0000d000
|
info
->
r004018
);
}
if
(
info
->
rammap
)
{
if
(
info
->
ramcfg
&&
(
info
->
rammap
[
4
]
&
0x08
))
{
u32
unk5a0
=
(
ROM16
(
info
->
ramcfg
[
5
])
<<
8
)
|
info
->
ramcfg
[
5
];
u32
unk5a4
=
ROM16
(
info
->
ramcfg
[
7
]);
u32
unk804
=
(
info
->
ramcfg
[
9
]
&
0xf0
)
<<
16
|
(
info
->
ramcfg
[
3
]
&
0x0f
)
<<
16
|
(
info
->
ramcfg
[
9
]
&
0x0f
)
|
0x80000000
;
nv_wr32
(
device
,
0x1005a0
,
unk5a0
);
nv_wr32
(
device
,
0x1005a4
,
unk5a4
);
nv_wr32
(
device
,
0x10f804
,
unk804
);
nv_mask
(
device
,
0x10053c
,
0x00001000
,
0x00000000
);
}
else
{
nv_mask
(
device
,
0x10053c
,
0x00001000
,
0x00001000
);
nv_mask
(
device
,
0x10f804
,
0x80000000
,
0x00000000
);
nv_mask
(
device
,
0x100760
,
0x22222222
,
info
->
r100760
);
nv_mask
(
device
,
0x1007a0
,
0x22222222
,
info
->
r100760
);
nv_mask
(
device
,
0x1007e0
,
0x22222222
,
info
->
r100760
);
}
}
if
(
info
->
mclk
.
pll
)
{
nv_mask
(
device
,
0x1110e0
,
0x00088000
,
0x00011000
);
nv_wr32
(
device
,
0x004000
,
(
ctrl
&=
~
0x00000008
));
}
}
static
void
mclk_timing_set
(
struct
nouveau_mem_exec_func
*
exec
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
exec
->
dev
);
struct
nva3_pm_state
*
info
=
exec
->
priv
;
struct
nouveau_pm_level
*
perflvl
=
info
->
perflvl
;
int
i
;
for
(
i
=
0
;
i
<
9
;
i
++
)
nv_wr32
(
device
,
0x100220
+
(
i
*
4
),
perflvl
->
timing
.
reg
[
i
]);
if
(
info
->
ramcfg
)
{
u32
data
=
(
info
->
ramcfg
[
2
]
&
0x08
)
?
0x00000000
:
0x00001000
;
nv_mask
(
device
,
0x100200
,
0x00001000
,
data
);
}
if
(
info
->
ramcfg
)
{
u32
unk714
=
nv_rd32
(
device
,
0x100714
)
&
~
0xf0000010
;
u32
unk718
=
nv_rd32
(
device
,
0x100718
)
&
~
0x00000100
;
u32
unk71c
=
nv_rd32
(
device
,
0x10071c
)
&
~
0x00000100
;
if
(
(
info
->
ramcfg
[
2
]
&
0x20
))
unk714
|=
0xf0000000
;
if
(
!
(
info
->
ramcfg
[
2
]
&
0x04
))
unk714
|=
0x00000010
;
nv_wr32
(
device
,
0x100714
,
unk714
);
if
(
info
->
ramcfg
[
2
]
&
0x01
)
unk71c
|=
0x00000100
;
nv_wr32
(
device
,
0x10071c
,
unk71c
);
if
(
info
->
ramcfg
[
2
]
&
0x02
)
unk718
|=
0x00000100
;
nv_wr32
(
device
,
0x100718
,
unk718
);
if
(
info
->
ramcfg
[
2
]
&
0x10
)
nv_wr32
(
device
,
0x111100
,
0x48000000
);
/*XXX*/
}
}
static
void
prog_mem
(
struct
drm_device
*
dev
,
struct
nva3_pm_state
*
info
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
struct
nouveau_mem_exec_func
exec
=
{
.
dev
=
dev
,
.
precharge
=
mclk_precharge
,
.
refresh
=
mclk_refresh
,
.
refresh_auto
=
mclk_refresh_auto
,
.
refresh_self
=
mclk_refresh_self
,
.
wait
=
mclk_wait
,
.
mrg
=
mclk_mrg
,
.
mrs
=
mclk_mrs
,
.
clock_set
=
mclk_clock_set
,
.
timing_set
=
mclk_timing_set
,
.
priv
=
info
};
u32
ctrl
;
/* XXX: where the fuck does 750MHz come from? */
if
(
info
->
perflvl
->
memory
<=
750000
)
{
info
->
r004018
=
0x10000000
;
info
->
r100760
=
0x22222222
;
}
ctrl
=
nv_rd32
(
device
,
0x004000
);
if
(
ctrl
&
0x00000008
)
{
if
(
info
->
mclk
.
pll
)
{
nv_mask
(
device
,
0x004128
,
0x00000101
,
0x00000101
);
nv_wr32
(
device
,
0x004004
,
info
->
mclk
.
pll
);
nv_wr32
(
device
,
0x004000
,
(
ctrl
|=
0x00000001
));
nv_wr32
(
device
,
0x004000
,
(
ctrl
&=
0xffffffef
));
nv_wait
(
device
,
0x004000
,
0x00020000
,
0x00020000
);
nv_wr32
(
device
,
0x004000
,
(
ctrl
|=
0x00000010
));
nv_wr32
(
device
,
0x004018
,
0x00005000
|
info
->
r004018
);
nv_wr32
(
device
,
0x004000
,
(
ctrl
|=
0x00000004
));
}
}
else
{
u32
ssel
=
0x00000101
;
if
(
info
->
mclk
.
clk
)
ssel
|=
info
->
mclk
.
clk
;
else
ssel
|=
0x00080000
;
/* 324MHz, shouldn't matter... */
nv_mask
(
device
,
0x004168
,
0x003f3141
,
ctrl
);
}
if
(
info
->
ramcfg
)
{
if
(
info
->
ramcfg
[
2
]
&
0x10
)
{
nv_mask
(
device
,
0x111104
,
0x00000600
,
0x00000000
);
}
else
{
nv_mask
(
device
,
0x111100
,
0x40000000
,
0x40000000
);
nv_mask
(
device
,
0x111104
,
0x00000180
,
0x00000000
);
}
}
if
(
info
->
rammap
&&
!
(
info
->
rammap
[
4
]
&
0x02
))
nv_mask
(
device
,
0x100200
,
0x00000800
,
0x00000000
);
nv_wr32
(
device
,
0x611200
,
0x00003300
);
if
(
!
(
info
->
ramcfg
[
2
]
&
0x10
))
nv_wr32
(
device
,
0x111100
,
0x4c020000
);
/*XXX*/
nouveau_mem_exec
(
&
exec
,
info
->
perflvl
);
nv_wr32
(
device
,
0x611200
,
0x00003330
);
if
(
info
->
rammap
&&
(
info
->
rammap
[
4
]
&
0x02
))
nv_mask
(
device
,
0x100200
,
0x00000800
,
0x00000800
);
if
(
info
->
ramcfg
)
{
if
(
info
->
ramcfg
[
2
]
&
0x10
)
{
nv_mask
(
device
,
0x111104
,
0x00000180
,
0x00000180
);
nv_mask
(
device
,
0x111100
,
0x40000000
,
0x00000000
);
}
else
{
nv_mask
(
device
,
0x111104
,
0x00000600
,
0x00000600
);
}
}
if
(
info
->
mclk
.
pll
)
{
nv_mask
(
device
,
0x004168
,
0x00000001
,
0x00000000
);
nv_mask
(
device
,
0x004168
,
0x00000100
,
0x00000000
);
}
else
{
nv_mask
(
device
,
0x004000
,
0x00000001
,
0x00000000
);
nv_mask
(
device
,
0x004128
,
0x00000001
,
0x00000000
);
nv_mask
(
device
,
0x004128
,
0x00000100
,
0x00000000
);
}
}
int
nva3_pm_clocks_set
(
struct
drm_device
*
dev
,
void
*
pre_state
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
struct
nouveau_drm
*
drm
=
nouveau_drm
(
dev
);
struct
nva3_pm_state
*
info
=
pre_state
;
int
ret
=
-
EAGAIN
;
/* prevent any new grctx switches from starting */
nv_wr32
(
device
,
0x400324
,
0x00000000
);
nv_wr32
(
device
,
0x400328
,
0x0050001c
);
/* wait flag 0x1c */
/* wait for any pending grctx switches to complete */
if
(
!
nv_wait_cb
(
device
,
nva3_pm_grcp_idle
,
dev
))
{
NV_ERROR
(
drm
,
"pm: ctxprog didn't go idle
\n
"
);
goto
cleanup
;
}
/* freeze PFIFO */
nv_mask
(
device
,
0x002504
,
0x00000001
,
0x00000001
);
if
(
!
nv_wait
(
device
,
0x002504
,
0x00000010
,
0x00000010
))
{
NV_ERROR
(
drm
,
"pm: fifo didn't go idle
\n
"
);
goto
cleanup
;
}
prog_pll
(
dev
,
0x00
,
0x004200
,
&
info
->
nclk
);
prog_pll
(
dev
,
0x01
,
0x004220
,
&
info
->
sclk
);
prog_clk
(
dev
,
0x20
,
&
info
->
unka0
);
prog_clk
(
dev
,
0x21
,
&
info
->
vdec
);
if
(
info
->
mclk
.
clk
||
info
->
mclk
.
pll
)
prog_mem
(
dev
,
info
);
ret
=
0
;
cleanup:
/* unfreeze PFIFO */
nv_mask
(
device
,
0x002504
,
0x00000001
,
0x00000000
);
/* restore ctxprog to normal */
nv_wr32
(
device
,
0x400324
,
0x00000000
);
nv_wr32
(
device
,
0x400328
,
0x0070009c
);
/* set flag 0x1c */
/* unblock it if necessary */
if
(
nv_rd32
(
device
,
0x400308
)
==
0x0050001c
)
nv_mask
(
device
,
0x400824
,
0x10000000
,
0x10000000
);
kfree
(
info
);
return
ret
;
}
drivers/gpu/drm/nouveau/nvc0_pm.c
deleted
100644 → 0
View file @
c52f4fa6
/*
* Copyright 2011 Red Hat Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* Authors: Ben Skeggs
*/
#include "nouveau_drm.h"
#include "nouveau_bios.h"
#include "nouveau_pm.h"
#include <subdev/bios/pll.h>
#include <subdev/bios.h>
#include <subdev/clock.h>
#include <subdev/timer.h>
#include <subdev/fb.h>
static
u32
read_div
(
struct
drm_device
*
,
int
,
u32
,
u32
);
static
u32
read_pll
(
struct
drm_device
*
,
u32
);
static
u32
read_vco
(
struct
drm_device
*
dev
,
u32
dsrc
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
u32
ssrc
=
nv_rd32
(
device
,
dsrc
);
if
(
!
(
ssrc
&
0x00000100
))
return
read_pll
(
dev
,
0x00e800
);
return
read_pll
(
dev
,
0x00e820
);
}
static
u32
read_pll
(
struct
drm_device
*
dev
,
u32
pll
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
u32
ctrl
=
nv_rd32
(
device
,
pll
+
0
);
u32
coef
=
nv_rd32
(
device
,
pll
+
4
);
u32
P
=
(
coef
&
0x003f0000
)
>>
16
;
u32
N
=
(
coef
&
0x0000ff00
)
>>
8
;
u32
M
=
(
coef
&
0x000000ff
)
>>
0
;
u32
sclk
,
doff
;
if
(
!
(
ctrl
&
0x00000001
))
return
0
;
switch
(
pll
&
0xfff000
)
{
case
0x00e000
:
sclk
=
27000
;
P
=
1
;
break
;
case
0x137000
:
doff
=
(
pll
-
0x137000
)
/
0x20
;
sclk
=
read_div
(
dev
,
doff
,
0x137120
,
0x137140
);
break
;
case
0x132000
:
switch
(
pll
)
{
case
0x132000
:
sclk
=
read_pll
(
dev
,
0x132020
);
break
;
case
0x132020
:
sclk
=
read_div
(
dev
,
0
,
0x137320
,
0x137330
);
break
;
default:
return
0
;
}
break
;
default:
return
0
;
}
return
sclk
*
N
/
M
/
P
;
}
static
u32
read_div
(
struct
drm_device
*
dev
,
int
doff
,
u32
dsrc
,
u32
dctl
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
u32
ssrc
=
nv_rd32
(
device
,
dsrc
+
(
doff
*
4
));
u32
sctl
=
nv_rd32
(
device
,
dctl
+
(
doff
*
4
));
switch
(
ssrc
&
0x00000003
)
{
case
0
:
if
((
ssrc
&
0x00030000
)
!=
0x00030000
)
return
27000
;
return
108000
;
case
2
:
return
100000
;
case
3
:
if
(
sctl
&
0x80000000
)
{
u32
sclk
=
read_vco
(
dev
,
dsrc
+
(
doff
*
4
));
u32
sdiv
=
(
sctl
&
0x0000003f
)
+
2
;
return
(
sclk
*
2
)
/
sdiv
;
}
return
read_vco
(
dev
,
dsrc
+
(
doff
*
4
));
default:
return
0
;
}
}
static
u32
read_mem
(
struct
drm_device
*
dev
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
u32
ssel
=
nv_rd32
(
device
,
0x1373f0
);
if
(
ssel
&
0x00000001
)
return
read_div
(
dev
,
0
,
0x137300
,
0x137310
);
return
read_pll
(
dev
,
0x132000
);
}
static
u32
read_clk
(
struct
drm_device
*
dev
,
int
clk
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
u32
sctl
=
nv_rd32
(
device
,
0x137250
+
(
clk
*
4
));
u32
ssel
=
nv_rd32
(
device
,
0x137100
);
u32
sclk
,
sdiv
;
if
(
ssel
&
(
1
<<
clk
))
{
if
(
clk
<
7
)
sclk
=
read_pll
(
dev
,
0x137000
+
(
clk
*
0x20
));
else
sclk
=
read_pll
(
dev
,
0x1370e0
);
sdiv
=
((
sctl
&
0x00003f00
)
>>
8
)
+
2
;
}
else
{
sclk
=
read_div
(
dev
,
clk
,
0x137160
,
0x1371d0
);
sdiv
=
((
sctl
&
0x0000003f
)
>>
0
)
+
2
;
}
if
(
sctl
&
0x80000000
)
return
(
sclk
*
2
)
/
sdiv
;
return
sclk
;
}
int
nvc0_pm_clocks_get
(
struct
drm_device
*
dev
,
struct
nouveau_pm_level
*
perflvl
)
{
perflvl
->
shader
=
read_clk
(
dev
,
0x00
);
perflvl
->
core
=
perflvl
->
shader
/
2
;
perflvl
->
memory
=
read_mem
(
dev
);
perflvl
->
rop
=
read_clk
(
dev
,
0x01
);
perflvl
->
hub07
=
read_clk
(
dev
,
0x02
);
perflvl
->
hub06
=
read_clk
(
dev
,
0x07
);
perflvl
->
hub01
=
read_clk
(
dev
,
0x08
);
perflvl
->
copy
=
read_clk
(
dev
,
0x09
);
perflvl
->
daemon
=
read_clk
(
dev
,
0x0c
);
perflvl
->
vdec
=
read_clk
(
dev
,
0x0e
);
return
0
;
}
struct
nvc0_pm_clock
{
u32
freq
;
u32
ssel
;
u32
mdiv
;
u32
dsrc
;
u32
ddiv
;
u32
coef
;
};
struct
nvc0_pm_state
{
struct
nouveau_pm_level
*
perflvl
;
struct
nvc0_pm_clock
eng
[
16
];
struct
nvc0_pm_clock
mem
;
};
static
u32
calc_div
(
struct
drm_device
*
dev
,
int
clk
,
u32
ref
,
u32
freq
,
u32
*
ddiv
)
{
u32
div
=
min
((
ref
*
2
)
/
freq
,
(
u32
)
65
);
if
(
div
<
2
)
div
=
2
;
*
ddiv
=
div
-
2
;
return
(
ref
*
2
)
/
div
;
}
static
u32
calc_src
(
struct
drm_device
*
dev
,
int
clk
,
u32
freq
,
u32
*
dsrc
,
u32
*
ddiv
)
{
u32
sclk
;
/* use one of the fixed frequencies if possible */
*
ddiv
=
0x00000000
;
switch
(
freq
)
{
case
27000
:
case
108000
:
*
dsrc
=
0x00000000
;
if
(
freq
==
108000
)
*
dsrc
|=
0x00030000
;
return
freq
;
case
100000
:
*
dsrc
=
0x00000002
;
return
freq
;
default:
*
dsrc
=
0x00000003
;
break
;
}
/* otherwise, calculate the closest divider */
sclk
=
read_vco
(
dev
,
clk
);
if
(
clk
<
7
)
sclk
=
calc_div
(
dev
,
clk
,
sclk
,
freq
,
ddiv
);
return
sclk
;
}
static
u32
calc_pll
(
struct
drm_device
*
dev
,
int
clk
,
u32
freq
,
u32
*
coef
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
struct
nouveau_bios
*
bios
=
nouveau_bios
(
device
);
struct
nvbios_pll
limits
;
int
N
,
M
,
P
,
ret
;
ret
=
nvbios_pll_parse
(
bios
,
0x137000
+
(
clk
*
0x20
),
&
limits
);
if
(
ret
)
return
0
;
limits
.
refclk
=
read_div
(
dev
,
clk
,
0x137120
,
0x137140
);
if
(
!
limits
.
refclk
)
return
0
;
ret
=
nva3_calc_pll
(
dev
,
&
limits
,
freq
,
&
N
,
NULL
,
&
M
,
&
P
);
if
(
ret
<=
0
)
return
0
;
*
coef
=
(
P
<<
16
)
|
(
N
<<
8
)
|
M
;
return
ret
;
}
/* A (likely rather simplified and incomplete) view of the clock tree
*
* Key:
*
* S: source select
* D: divider
* P: pll
* F: switch
*
* Engine clocks:
*
* 137250(D) ---- 137100(F0) ---- 137160(S)/1371d0(D) ------------------- ref
* (F1) ---- 1370X0(P) ---- 137120(S)/137140(D) ---- ref
*
* Not all registers exist for all clocks. For example: clocks >= 8 don't
* have their own PLL (all tied to clock 7's PLL when in PLL mode), nor do
* they have the divider at 1371d0, though the source selection at 137160
* still exists. You must use the divider at 137250 for these instead.
*
* Memory clock:
*
* TBD, read_mem() above is likely very wrong...
*
*/
static
int
calc_clk
(
struct
drm_device
*
dev
,
int
clk
,
struct
nvc0_pm_clock
*
info
,
u32
freq
)
{
u32
src0
,
div0
,
div1D
,
div1P
=
0
;
u32
clk0
,
clk1
=
0
;
/* invalid clock domain */
if
(
!
freq
)
return
0
;
/* first possible path, using only dividers */
clk0
=
calc_src
(
dev
,
clk
,
freq
,
&
src0
,
&
div0
);
clk0
=
calc_div
(
dev
,
clk
,
clk0
,
freq
,
&
div1D
);
/* see if we can get any closer using PLLs */
if
(
clk0
!=
freq
&&
(
0x00004387
&
(
1
<<
clk
)))
{
if
(
clk
<
7
)
clk1
=
calc_pll
(
dev
,
clk
,
freq
,
&
info
->
coef
);
else
clk1
=
read_pll
(
dev
,
0x1370e0
);
clk1
=
calc_div
(
dev
,
clk
,
clk1
,
freq
,
&
div1P
);
}
/* select the method which gets closest to target freq */
if
(
abs
((
int
)
freq
-
clk0
)
<=
abs
((
int
)
freq
-
clk1
))
{
info
->
dsrc
=
src0
;
if
(
div0
)
{
info
->
ddiv
|=
0x80000000
;
info
->
ddiv
|=
div0
<<
8
;
info
->
ddiv
|=
div0
;
}
if
(
div1D
)
{
info
->
mdiv
|=
0x80000000
;
info
->
mdiv
|=
div1D
;
}
info
->
ssel
=
0
;
info
->
freq
=
clk0
;
}
else
{
if
(
div1P
)
{
info
->
mdiv
|=
0x80000000
;
info
->
mdiv
|=
div1P
<<
8
;
}
info
->
ssel
=
(
1
<<
clk
);
info
->
freq
=
clk1
;
}
return
0
;
}
static
int
calc_mem
(
struct
drm_device
*
dev
,
struct
nvc0_pm_clock
*
info
,
u32
freq
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
struct
nouveau_bios
*
bios
=
nouveau_bios
(
device
);
struct
nvbios_pll
pll
;
int
N
,
M
,
P
,
ret
;
u32
ctrl
;
/* mclk pll input freq comes from another pll, make sure it's on */
ctrl
=
nv_rd32
(
device
,
0x132020
);
if
(
!
(
ctrl
&
0x00000001
))
{
/* if not, program it to 567MHz. nfi where this value comes
* from - it looks like it's in the pll limits table for
* 132000 but the binary driver ignores all my attempts to
* change this value.
*/
nv_wr32
(
device
,
0x137320
,
0x00000103
);
nv_wr32
(
device
,
0x137330
,
0x81200606
);
nv_wait
(
device
,
0x132020
,
0x00010000
,
0x00010000
);
nv_wr32
(
device
,
0x132024
,
0x0001150f
);
nv_mask
(
device
,
0x132020
,
0x00000001
,
0x00000001
);
nv_wait
(
device
,
0x137390
,
0x00020000
,
0x00020000
);
nv_mask
(
device
,
0x132020
,
0x00000004
,
0x00000004
);
}
/* for the moment, until the clock tree is better understood, use
* pll mode for all clock frequencies
*/
ret
=
nvbios_pll_parse
(
bios
,
0x132000
,
&
pll
);
if
(
ret
==
0
)
{
pll
.
refclk
=
read_pll
(
dev
,
0x132020
);
if
(
pll
.
refclk
)
{
ret
=
nva3_calc_pll
(
dev
,
&
pll
,
freq
,
&
N
,
NULL
,
&
M
,
&
P
);
if
(
ret
>
0
)
{
info
->
coef
=
(
P
<<
16
)
|
(
N
<<
8
)
|
M
;
return
0
;
}
}
}
return
-
EINVAL
;
}
void
*
nvc0_pm_clocks_pre
(
struct
drm_device
*
dev
,
struct
nouveau_pm_level
*
perflvl
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
struct
nvc0_pm_state
*
info
;
int
ret
;
info
=
kzalloc
(
sizeof
(
*
info
),
GFP_KERNEL
);
if
(
!
info
)
return
ERR_PTR
(
-
ENOMEM
);
/* NFI why this is still in the performance table, the ROPCs appear
* to get their clock from clock 2 ("hub07", actually hub05 on this
* chip, but, anyway...) as well. nvatiming confirms hub05 and ROP
* are always the same freq with the binary driver even when the
* performance table says they should differ.
*/
if
(
device
->
chipset
==
0xd9
)
perflvl
->
rop
=
0
;
if
((
ret
=
calc_clk
(
dev
,
0x00
,
&
info
->
eng
[
0x00
],
perflvl
->
shader
))
||
(
ret
=
calc_clk
(
dev
,
0x01
,
&
info
->
eng
[
0x01
],
perflvl
->
rop
))
||
(
ret
=
calc_clk
(
dev
,
0x02
,
&
info
->
eng
[
0x02
],
perflvl
->
hub07
))
||
(
ret
=
calc_clk
(
dev
,
0x07
,
&
info
->
eng
[
0x07
],
perflvl
->
hub06
))
||
(
ret
=
calc_clk
(
dev
,
0x08
,
&
info
->
eng
[
0x08
],
perflvl
->
hub01
))
||
(
ret
=
calc_clk
(
dev
,
0x09
,
&
info
->
eng
[
0x09
],
perflvl
->
copy
))
||
(
ret
=
calc_clk
(
dev
,
0x0c
,
&
info
->
eng
[
0x0c
],
perflvl
->
daemon
))
||
(
ret
=
calc_clk
(
dev
,
0x0e
,
&
info
->
eng
[
0x0e
],
perflvl
->
vdec
)))
{
kfree
(
info
);
return
ERR_PTR
(
ret
);
}
if
(
perflvl
->
memory
)
{
ret
=
calc_mem
(
dev
,
&
info
->
mem
,
perflvl
->
memory
);
if
(
ret
)
{
kfree
(
info
);
return
ERR_PTR
(
ret
);
}
}
info
->
perflvl
=
perflvl
;
return
info
;
}
static
void
prog_clk
(
struct
drm_device
*
dev
,
int
clk
,
struct
nvc0_pm_clock
*
info
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
/* program dividers at 137160/1371d0 first */
if
(
clk
<
7
&&
!
info
->
ssel
)
{
nv_mask
(
device
,
0x1371d0
+
(
clk
*
0x04
),
0x80003f3f
,
info
->
ddiv
);
nv_wr32
(
device
,
0x137160
+
(
clk
*
0x04
),
info
->
dsrc
);
}
/* switch clock to non-pll mode */
nv_mask
(
device
,
0x137100
,
(
1
<<
clk
),
0x00000000
);
nv_wait
(
device
,
0x137100
,
(
1
<<
clk
),
0x00000000
);
/* reprogram pll */
if
(
clk
<
7
)
{
/* make sure it's disabled first... */
u32
base
=
0x137000
+
(
clk
*
0x20
);
u32
ctrl
=
nv_rd32
(
device
,
base
+
0x00
);
if
(
ctrl
&
0x00000001
)
{
nv_mask
(
device
,
base
+
0x00
,
0x00000004
,
0x00000000
);
nv_mask
(
device
,
base
+
0x00
,
0x00000001
,
0x00000000
);
}
/* program it to new values, if necessary */
if
(
info
->
ssel
)
{
nv_wr32
(
device
,
base
+
0x04
,
info
->
coef
);
nv_mask
(
device
,
base
+
0x00
,
0x00000001
,
0x00000001
);
nv_wait
(
device
,
base
+
0x00
,
0x00020000
,
0x00020000
);
nv_mask
(
device
,
base
+
0x00
,
0x00020004
,
0x00000004
);
}
}
/* select pll/non-pll mode, and program final clock divider */
nv_mask
(
device
,
0x137100
,
(
1
<<
clk
),
info
->
ssel
);
nv_wait
(
device
,
0x137100
,
(
1
<<
clk
),
info
->
ssel
);
nv_mask
(
device
,
0x137250
+
(
clk
*
0x04
),
0x00003f3f
,
info
->
mdiv
);
}
static
void
mclk_precharge
(
struct
nouveau_mem_exec_func
*
exec
)
{
}
static
void
mclk_refresh
(
struct
nouveau_mem_exec_func
*
exec
)
{
}
static
void
mclk_refresh_auto
(
struct
nouveau_mem_exec_func
*
exec
,
bool
enable
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
exec
->
dev
);
nv_wr32
(
device
,
0x10f210
,
enable
?
0x80000000
:
0x00000000
);
}
static
void
mclk_refresh_self
(
struct
nouveau_mem_exec_func
*
exec
,
bool
enable
)
{
}
static
void
mclk_wait
(
struct
nouveau_mem_exec_func
*
exec
,
u32
nsec
)
{
udelay
((
nsec
+
500
)
/
1000
);
}
static
u32
mclk_mrg
(
struct
nouveau_mem_exec_func
*
exec
,
int
mr
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
exec
->
dev
);
struct
nouveau_fb
*
pfb
=
nouveau_fb
(
device
);
if
(
pfb
->
ram
->
type
!=
NV_MEM_TYPE_GDDR5
)
{
if
(
mr
<=
1
)
return
nv_rd32
(
device
,
0x10f300
+
((
mr
-
0
)
*
4
));
return
nv_rd32
(
device
,
0x10f320
+
((
mr
-
2
)
*
4
));
}
else
{
if
(
mr
==
0
)
return
nv_rd32
(
device
,
0x10f300
+
(
mr
*
4
));
else
if
(
mr
<=
7
)
return
nv_rd32
(
device
,
0x10f32c
+
(
mr
*
4
));
return
nv_rd32
(
device
,
0x10f34c
);
}
}
static
void
mclk_mrs
(
struct
nouveau_mem_exec_func
*
exec
,
int
mr
,
u32
data
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
exec
->
dev
);
struct
nouveau_fb
*
pfb
=
nouveau_fb
(
device
);
if
(
pfb
->
ram
->
type
!=
NV_MEM_TYPE_GDDR5
)
{
if
(
mr
<=
1
)
{
nv_wr32
(
device
,
0x10f300
+
((
mr
-
0
)
*
4
),
data
);
if
(
pfb
->
ram
->
ranks
>
1
)
nv_wr32
(
device
,
0x10f308
+
((
mr
-
0
)
*
4
),
data
);
}
else
if
(
mr
<=
3
)
{
nv_wr32
(
device
,
0x10f320
+
((
mr
-
2
)
*
4
),
data
);
if
(
pfb
->
ram
->
ranks
>
1
)
nv_wr32
(
device
,
0x10f328
+
((
mr
-
2
)
*
4
),
data
);
}
}
else
{
if
(
mr
==
0
)
nv_wr32
(
device
,
0x10f300
+
(
mr
*
4
),
data
);
else
if
(
mr
<=
7
)
nv_wr32
(
device
,
0x10f32c
+
(
mr
*
4
),
data
);
else
if
(
mr
==
15
)
nv_wr32
(
device
,
0x10f34c
,
data
);
}
}
static
void
mclk_clock_set
(
struct
nouveau_mem_exec_func
*
exec
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
exec
->
dev
);
struct
nvc0_pm_state
*
info
=
exec
->
priv
;
u32
ctrl
=
nv_rd32
(
device
,
0x132000
);
nv_wr32
(
device
,
0x137360
,
0x00000001
);
nv_wr32
(
device
,
0x137370
,
0x00000000
);
nv_wr32
(
device
,
0x137380
,
0x00000000
);
if
(
ctrl
&
0x00000001
)
nv_wr32
(
device
,
0x132000
,
(
ctrl
&=
~
0x00000001
));
nv_wr32
(
device
,
0x132004
,
info
->
mem
.
coef
);
nv_wr32
(
device
,
0x132000
,
(
ctrl
|=
0x00000001
));
nv_wait
(
device
,
0x137390
,
0x00000002
,
0x00000002
);
nv_wr32
(
device
,
0x132018
,
0x00005000
);
nv_wr32
(
device
,
0x137370
,
0x00000001
);
nv_wr32
(
device
,
0x137380
,
0x00000001
);
nv_wr32
(
device
,
0x137360
,
0x00000000
);
}
static
void
mclk_timing_set
(
struct
nouveau_mem_exec_func
*
exec
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
exec
->
dev
);
struct
nvc0_pm_state
*
info
=
exec
->
priv
;
struct
nouveau_pm_level
*
perflvl
=
info
->
perflvl
;
int
i
;
for
(
i
=
0
;
i
<
5
;
i
++
)
nv_wr32
(
device
,
0x10f290
+
(
i
*
4
),
perflvl
->
timing
.
reg
[
i
]);
}
static
void
prog_mem
(
struct
drm_device
*
dev
,
struct
nvc0_pm_state
*
info
)
{
struct
nouveau_device
*
device
=
nouveau_dev
(
dev
);
struct
nouveau_mem_exec_func
exec
=
{
.
dev
=
dev
,
.
precharge
=
mclk_precharge
,
.
refresh
=
mclk_refresh
,
.
refresh_auto
=
mclk_refresh_auto
,
.
refresh_self
=
mclk_refresh_self
,
.
wait
=
mclk_wait
,
.
mrg
=
mclk_mrg
,
.
mrs
=
mclk_mrs
,
.
clock_set
=
mclk_clock_set
,
.
timing_set
=
mclk_timing_set
,
.
priv
=
info
};
if
(
device
->
chipset
<
0xd0
)
nv_wr32
(
device
,
0x611200
,
0x00003300
);
else
nv_wr32
(
device
,
0x62c000
,
0x03030000
);
nouveau_mem_exec
(
&
exec
,
info
->
perflvl
);
if
(
device
->
chipset
<
0xd0
)
nv_wr32
(
device
,
0x611200
,
0x00003330
);
else
nv_wr32
(
device
,
0x62c000
,
0x03030300
);
}
int
nvc0_pm_clocks_set
(
struct
drm_device
*
dev
,
void
*
data
)
{
struct
nvc0_pm_state
*
info
=
data
;
int
i
;
if
(
info
->
mem
.
coef
)
prog_mem
(
dev
,
info
);
for
(
i
=
0
;
i
<
16
;
i
++
)
{
if
(
!
info
->
eng
[
i
].
freq
)
continue
;
prog_clk
(
dev
,
i
,
&
info
->
eng
[
i
]);
}
kfree
(
info
);
return
0
;
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment