/* 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 #include #include #include #include #include #include #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");