285 lines
6.5 KiB
C
285 lines
6.5 KiB
C
/* Copyright (c) 2010, The Linux Foundation. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 and
|
|
* only version 2 as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/err.h>
|
|
#include <linux/thermal.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/io.h>
|
|
#include <mach/msm_memtypes.h>
|
|
|
|
#define POP_MEM_LPDDR1_REFRESH_MASK 0x00000700
|
|
#define POP_MEM_LPDDR1_REFRESH_SHIFT 0x8
|
|
|
|
#define POP_MEM_LPDDR2_REFRESH_MASK 0x00000007
|
|
#define POP_MEM_LPDDR2_REFRESH_SHIFT 0x0
|
|
|
|
#define POP_MEM_REFRESH_REG 0x3C
|
|
|
|
#define POP_MEM_LOW_TEMPERATURE 25000
|
|
#define POP_MEM_NORMAL_TEMPERATURE 50000
|
|
#define POP_MEM_HIGH_TEMPERATURE 85000
|
|
|
|
#define POP_MEM_TRIP_OUT_OF_SPEC 0
|
|
#define POP_MEM_TRIP_NUM 1
|
|
|
|
struct pop_mem_tm_device {
|
|
unsigned long baseaddr;
|
|
struct thermal_zone_device *tz_dev;
|
|
unsigned long refresh_mask;
|
|
unsigned int refresh_shift;
|
|
};
|
|
|
|
|
|
static int pop_mem_tm_read_refresh(struct pop_mem_tm_device *tm,
|
|
unsigned int *ref_rate){
|
|
unsigned int ref;
|
|
|
|
ref = __raw_readl(tm->baseaddr + POP_MEM_REFRESH_REG);
|
|
*ref_rate = (ref & tm->refresh_mask) >> tm->refresh_shift;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int pop_mem_tm_get_temperature(struct thermal_zone_device *thermal,
|
|
unsigned long *temperature)
|
|
{
|
|
struct pop_mem_tm_device *tm = thermal->devdata;
|
|
unsigned int ref_rate;
|
|
int rc;
|
|
|
|
if (!tm || !temperature)
|
|
return -EINVAL;
|
|
|
|
rc = pop_mem_tm_read_refresh(tm, &ref_rate);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
switch (ref_rate) {
|
|
case 0:
|
|
case 1:
|
|
case 2:
|
|
*temperature = POP_MEM_LOW_TEMPERATURE;
|
|
break;
|
|
case 3:
|
|
case 4:
|
|
*temperature = POP_MEM_NORMAL_TEMPERATURE;
|
|
break;
|
|
case 5:
|
|
case 6:
|
|
case 7:
|
|
*temperature = POP_MEM_HIGH_TEMPERATURE;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pop_mem_tm_get_trip_type(struct thermal_zone_device *thermal,
|
|
int trip, enum thermal_trip_type *type)
|
|
{
|
|
struct pop_mem_tm_device *tm = thermal->devdata;
|
|
|
|
if (!tm || trip < 0 || !type)
|
|
return -EINVAL;
|
|
|
|
if (trip == POP_MEM_TRIP_OUT_OF_SPEC)
|
|
*type = THERMAL_TRIP_CRITICAL;
|
|
else
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pop_mem_tm_get_trip_temperature(struct thermal_zone_device *thermal,
|
|
int trip, unsigned long *temperature)
|
|
{
|
|
struct pop_mem_tm_device *tm = thermal->devdata;
|
|
|
|
if (!tm || trip < 0 || !temperature)
|
|
return -EINVAL;
|
|
|
|
if (trip == POP_MEM_TRIP_OUT_OF_SPEC)
|
|
*temperature = POP_MEM_HIGH_TEMPERATURE;
|
|
else
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int pop_mem_tm_get_crit_temperature(struct thermal_zone_device *thermal,
|
|
unsigned long *temperature)
|
|
{
|
|
struct pop_mem_tm_device *tm = thermal->devdata;
|
|
|
|
if (!tm || !temperature)
|
|
return -EINVAL;
|
|
|
|
*temperature = POP_MEM_HIGH_TEMPERATURE;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static struct thermal_zone_device_ops pop_mem_thermal_zone_ops = {
|
|
.get_temp = pop_mem_tm_get_temperature,
|
|
.get_trip_type = pop_mem_tm_get_trip_type,
|
|
.get_trip_temp = pop_mem_tm_get_trip_temperature,
|
|
.get_crit_temp = pop_mem_tm_get_crit_temperature,
|
|
};
|
|
|
|
|
|
static int __devinit pop_mem_tm_probe(struct platform_device *pdev)
|
|
{
|
|
int rc, len, numcontrollers;
|
|
struct resource *controller_mem = NULL;
|
|
struct resource *res_mem = NULL;
|
|
struct pop_mem_tm_device *tmdev = NULL;
|
|
void __iomem *base = NULL;
|
|
|
|
rc = len = 0;
|
|
numcontrollers = get_num_populated_chipselects();
|
|
|
|
if (pdev->id >= numcontrollers) {
|
|
pr_err("%s: memory controller %d does not exist", __func__,
|
|
pdev->id);
|
|
rc = -ENODEV;
|
|
goto fail;
|
|
}
|
|
|
|
controller_mem = platform_get_resource_byname(pdev,
|
|
IORESOURCE_MEM, "physbase");
|
|
if (!controller_mem) {
|
|
pr_err("%s: could not get resources for controller %d",
|
|
__func__, pdev->id);
|
|
rc = -EFAULT;
|
|
goto fail;
|
|
}
|
|
|
|
len = controller_mem->end - controller_mem->start + 1;
|
|
|
|
res_mem = request_mem_region(controller_mem->start, len,
|
|
controller_mem->name);
|
|
if (!res_mem) {
|
|
pr_err("%s: Could not request memory region: "
|
|
"start=%p, len=%d\n", __func__,
|
|
(void *) controller_mem->start, len);
|
|
rc = -EBUSY;
|
|
goto fail;
|
|
|
|
}
|
|
|
|
base = ioremap(res_mem->start, len);
|
|
if (!base) {
|
|
pr_err("%s: Could not ioremap: start=%p, len=%d\n",
|
|
__func__, (void *) controller_mem->start, len);
|
|
rc = -EBUSY;
|
|
goto fail;
|
|
|
|
}
|
|
|
|
tmdev = kzalloc(sizeof(*tmdev), GFP_KERNEL);
|
|
if (tmdev == NULL) {
|
|
pr_err("%s: kzalloc() failed.\n", __func__);
|
|
rc = -ENOMEM;
|
|
goto fail;
|
|
}
|
|
|
|
if (numcontrollers == 1) {
|
|
tmdev->refresh_mask = POP_MEM_LPDDR1_REFRESH_MASK;
|
|
tmdev->refresh_shift = POP_MEM_LPDDR1_REFRESH_SHIFT;
|
|
} else {
|
|
tmdev->refresh_mask = POP_MEM_LPDDR2_REFRESH_MASK;
|
|
tmdev->refresh_shift = POP_MEM_LPDDR2_REFRESH_SHIFT;
|
|
}
|
|
tmdev->baseaddr = (unsigned long) base;
|
|
tmdev->tz_dev = thermal_zone_device_register("msm_popmem_tz",
|
|
POP_MEM_TRIP_NUM, tmdev,
|
|
&pop_mem_thermal_zone_ops,
|
|
0, 0, 0, 0);
|
|
|
|
if (tmdev->tz_dev == NULL) {
|
|
pr_err("%s: thermal_zone_device_register() failed.\n",
|
|
__func__);
|
|
goto fail;
|
|
}
|
|
|
|
platform_set_drvdata(pdev, tmdev);
|
|
|
|
pr_notice("%s: device %d probed successfully\n", __func__, pdev->id);
|
|
|
|
return rc;
|
|
|
|
fail:
|
|
if (base)
|
|
iounmap(base);
|
|
if (res_mem)
|
|
release_mem_region(controller_mem->start, len);
|
|
kfree(tmdev);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int __devexit pop_mem_tm_remove(struct platform_device *pdev)
|
|
{
|
|
|
|
int len;
|
|
struct pop_mem_tm_device *tmdev = platform_get_drvdata(pdev);
|
|
struct resource *controller_mem;
|
|
|
|
iounmap((void __iomem *)tmdev->baseaddr);
|
|
|
|
controller_mem = platform_get_resource_byname(pdev,
|
|
IORESOURCE_MEM, "physbase");
|
|
len = controller_mem->end - controller_mem->start + 1;
|
|
release_mem_region(controller_mem->start, len);
|
|
|
|
thermal_zone_device_unregister(tmdev->tz_dev);
|
|
platform_set_drvdata(pdev, NULL);
|
|
kfree(tmdev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver pop_mem_tm_driver = {
|
|
.probe = pop_mem_tm_probe,
|
|
.remove = pop_mem_tm_remove,
|
|
.driver = {
|
|
.name = "msm_popmem-tm",
|
|
.owner = THIS_MODULE
|
|
},
|
|
};
|
|
|
|
static int __init pop_mem_tm_init(void)
|
|
{
|
|
return platform_driver_register(&pop_mem_tm_driver);
|
|
}
|
|
|
|
static void __exit pop_mem_tm_exit(void)
|
|
{
|
|
platform_driver_unregister(&pop_mem_tm_driver);
|
|
}
|
|
|
|
module_init(pop_mem_tm_init);
|
|
module_exit(pop_mem_tm_exit);
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_DESCRIPTION("Pop memory thermal manager driver");
|
|
MODULE_VERSION("1.0");
|
|
MODULE_ALIAS("platform:popmem-tm");
|