/* * vlv2_plat_clock.c - VLV2 platform clock driver * Copyright (C) 2013 Intel Corporation * * Author: Asutosh Pathak * Author: Chandra Sekhar Anagani * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * 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. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ #include #include #include #include #include "../../include/linux/vlv2_plat_clock.h" /* NOTE: Most of below constants could come from platform data. * To be fixed when appropriate ACPI support comes. */ #define VLV2_PMC_CLK_BASE_ADDRESS 0xfed03060 #define PLT_CLK_CTL_OFFSET(x) (0x04 * (x)) #define CLK_CONFG_BIT_POS 0 #define CLK_CONFG_BIT_LEN 2 #define CLK_CONFG_D3_GATED 0 #define CLK_CONFG_FORCE_ON 1 #define CLK_CONFG_FORCE_OFF 2 #define CLK_FREQ_TYPE_BIT_POS 2 #define CLK_FREQ_TYPE_BIT_LEN 1 #define CLK_FREQ_TYPE_XTAL 0 /* 25 MHz */ #define CLK_FREQ_TYPE_PLL 1 /* 19.2 MHz */ #define MAX_CLK_COUNT 5 /* Helper macros to manipulate bitfields */ #define REG_MASK(n) (((1 << (n##_BIT_LEN)) - 1) << (n##_BIT_POS)) #define REG_SET_FIELD(r, n, v) (((r) & ~REG_MASK(n)) | \ (((v) << (n##_BIT_POS)) & REG_MASK(n))) #define REG_GET_FIELD(r, n) (((r) & REG_MASK(n)) >> n##_BIT_POS) /* * vlv2 platform has 6 platform clocks, controlled by 4 byte registers * Total size required for mapping is 6*4 = 24 bytes */ #define PMC_MAP_SIZE 24 static DEFINE_MUTEX(clk_mutex); static void __iomem *pmc_base; /* * vlv2_plat_set_clock_freq - Set clock frequency to a specified platform clock * @clk_num: Platform clock number (i.e. 0, 1, 2, ...,5) * @freq_type: Clock frequency (0-25 MHz(XTAL), 1-19.2 MHz(PLL) ) */ int vlv2_plat_set_clock_freq(int clk_num, int freq_type) { void __iomem *addr; if (clk_num < 0 || clk_num >= MAX_CLK_COUNT) { pr_err("Clock number out of range (%d)\n", clk_num); return -EINVAL; } if (freq_type != CLK_FREQ_TYPE_XTAL && freq_type != CLK_FREQ_TYPE_PLL) { pr_err("wrong clock type\n"); return -EINVAL; } if (!pmc_base) { pr_err("memio map is not set\n"); return -EINVAL; } addr = pmc_base + PLT_CLK_CTL_OFFSET(clk_num); mutex_lock(&clk_mutex); writel(REG_SET_FIELD(readl(addr), CLK_FREQ_TYPE, freq_type), addr); mutex_unlock(&clk_mutex); return 0; } EXPORT_SYMBOL_GPL(vlv2_plat_set_clock_freq); /* * vlv2_plat_get_clock_freq - Get the status of specified platform clock * @clk_num: Platform clock number (i.e. 0, 1, 2, ...,5) * * Returns 0 for 25 MHz(XTAL) and 1 for 19.2 MHz(PLL) */ int vlv2_plat_get_clock_freq(int clk_num) { u32 ret; if (clk_num < 0 || clk_num >= MAX_CLK_COUNT) { pr_err("Clock number out of range (%d)\n", clk_num); return -EINVAL; } if (!pmc_base) { pr_err("memio map is not set\n"); return -EINVAL; } mutex_lock(&clk_mutex); ret = REG_GET_FIELD(readl(pmc_base + PLT_CLK_CTL_OFFSET(clk_num)), CLK_FREQ_TYPE); mutex_unlock(&clk_mutex); return ret; } EXPORT_SYMBOL_GPL(vlv2_plat_get_clock_freq); /* * vlv2_plat_configure_clock - Configure the specified platform clock * @clk_num: Platform clock number (i.e. 0, 1, 2, ...,5) * @conf: Clock gating: * 0 - Clock gated on D3 state * 1 - Force on * 2,3 - Force off */ int vlv2_plat_configure_clock(int clk_num, u32 conf) { void __iomem *addr; if (clk_num < 0 || clk_num >= MAX_CLK_COUNT) { pr_err("Clock number out of range (%d)\n", clk_num); return -EINVAL; } if (conf != CLK_CONFG_D3_GATED && conf != CLK_CONFG_FORCE_ON && conf != CLK_CONFG_FORCE_OFF) { pr_err("Invalid clock configuration requested\n"); return -EINVAL; } if (!pmc_base) { pr_err("memio map is not set\n"); return -EINVAL; } addr = pmc_base + PLT_CLK_CTL_OFFSET(clk_num); mutex_lock(&clk_mutex); writel(REG_SET_FIELD(readl(addr), CLK_CONFG, conf), addr); mutex_unlock(&clk_mutex); return 0; } EXPORT_SYMBOL_GPL(vlv2_plat_configure_clock); /* * vlv2_plat_get_clock_status - Get the status of specified platform clock * @clk_num: Platform clock number (i.e. 0, 1, 2, ...,5) * * Returns 1 - On, 0 - Off */ int vlv2_plat_get_clock_status(int clk_num) { int ret; if (clk_num < 0 || clk_num >= MAX_CLK_COUNT) { pr_err("Clock number out of range (%d)\n", clk_num); return -EINVAL; } if (!pmc_base) { pr_err("memio map is not set\n"); return -EINVAL; } mutex_lock(&clk_mutex); ret = (int)REG_GET_FIELD(readl(pmc_base + PLT_CLK_CTL_OFFSET(clk_num)), CLK_CONFG); mutex_unlock(&clk_mutex); return ret; } EXPORT_SYMBOL_GPL(vlv2_plat_get_clock_status); static int vlv2_plat_clk_probe(struct platform_device *pdev) { int i = 0; pmc_base = ioremap_nocache(VLV2_PMC_CLK_BASE_ADDRESS, PMC_MAP_SIZE); if (!pmc_base) { dev_err(&pdev->dev, "I/O memory remapping failed\n"); return -ENOMEM; } /* Initialize all clocks as disabled */ for (i = 0; i < MAX_CLK_COUNT; i++) vlv2_plat_configure_clock(i, CLK_CONFG_FORCE_OFF); dev_info(&pdev->dev, "vlv2_plat_clk initialized\n"); return 0; } static const struct platform_device_id vlv2_plat_clk_id[] = { {"vlv2_plat_clk", 0}, {} }; static int vlv2_resume(struct device *device) { int i; /* Initialize all clocks as disabled */ for (i = 0; i < MAX_CLK_COUNT; i++) vlv2_plat_configure_clock(i, CLK_CONFG_FORCE_OFF); return 0; } static int vlv2_suspend(struct device *device) { return 0; } static const struct dev_pm_ops vlv2_pm_ops = { .suspend = vlv2_suspend, .resume = vlv2_resume, }; static struct platform_driver vlv2_plat_clk_driver = { .probe = vlv2_plat_clk_probe, .id_table = vlv2_plat_clk_id, .driver = { .name = "vlv2_plat_clk", .pm = &vlv2_pm_ops, }, }; static int __init vlv2_plat_clk_init(void) { return platform_driver_register(&vlv2_plat_clk_driver); } arch_initcall(vlv2_plat_clk_init);