| /* Copyright (c) 2010, Code Aurora Forum. 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"); |